Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
11 kB · 307 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308use std::{ fmt::Debug, ops::{AddAssign, Deref, SubAssign},};
use winit::keyboard::{Key, NamedKey};
use crate::{ EventQueue, GlobalEvent, coordinates::{CharPosition, CharRect, FONT_SIZE16, PixelRect, WINDOW_SIZE_CHARS}, dialog::slider_dialog::SliderDialog, draw_buffer::DrawBuffer,};
use super::{NextWidget, Widget, WidgetResponse};
#[derive(Debug)]pub struct BoundNumber<const MIN: i16, const MAX: i16> { inner: i16,}
impl<const MIN: i16, const MAX: i16> AddAssign<i16> for BoundNumber<MIN, MAX> { fn add_assign(&mut self, rhs: i16) { let new = self.inner.saturating_add(rhs); // need to check both low and high bound as you can add a negative number if new > MAX { self.inner = MAX; } else if new < MIN { self.inner = MIN; } else { self.inner = new; } }}
impl<const MIN: i16, const MAX: i16> SubAssign<i16> for BoundNumber<MIN, MAX> { fn sub_assign(&mut self, rhs: i16) { let new = self.inner.saturating_sub(rhs); // need to check both low and high bound as you can sub a negative number if new > MAX { self.inner = MAX; } else if new < MIN { self.inner = MIN; } else { self.inner = new; } }}
impl<const MIN: i16, const MAX: i16> Deref for BoundNumber<MIN, MAX> { type Target = i16;
fn deref(&self) -> &Self::Target { &self.inner }}
impl<const MIN: i16, const MAX: i16> BoundNumber<MIN, MAX> { const _VALID: () = assert!(MIN <= MAX, "MIN must be less than or equal to MAX");
/// sets inner to MIN or MAX if its out of bounds pub const fn new_saturating(value: i16) -> Self { if value > MAX { Self { inner: MAX } } else if value < MIN { Self { inner: MIN } } else { Self { inner: value } } }
/// panics on out of bounds value pub const fn new(value: i16) -> Self { assert!(value <= MAX); assert!(value >= MIN); Self { inner: value } }
/// sets to MAX or MIN when the value is out of bounds pub fn set_saturating(&mut self, value: i16) { if value > MAX { self.inner = MAX } else if value < MIN { self.inner = MIN; } else { self.inner = value; } }
pub fn try_set(&mut self, value: i16) -> Result<(), ()> { if MIN <= value && value <= MAX { self.inner = value; Ok(()) } else { Err(()) } }
// dont need any arguments as MIN and MAX are compile time known pub const fn get_middle() -> i16 { MIN.midpoint(MAX) }}
/// Slider needs more Space then is specified in Rect as it draws the current value with an offset of 2 right to the box./// currently this always draws 3 chars, but this can only take values between -99 and 999. If values outside of that are needed, this implementation needs to change#[derive(Debug)]pub struct Slider<const MIN: i16, const MAX: i16> { number: BoundNumber<MIN, MAX>, position: CharPosition, width: u8, next_widget: NextWidget, dialog_return: fn(i16) -> GlobalEvent, #[cfg(feature = "accesskit")] access: (accesskit::NodeId, Box<str>),}
impl<const MIN: i16, const MAX: i16> Widget for Slider<MIN, MAX> { fn draw(&self, draw_buffer: &mut DrawBuffer, selected: bool) { const BACKGROUND_COLOR: u8 = 0; const CURSOR_COLOR: u8 = 2; const CURSOR_SELECTED_COLOR: u8 = 3;
const CURSOR_WIDTH: u16 = 4;
draw_buffer.draw_string( &format!("{:03}", *self.number), self.position + (self.width + 2, 0), 1, 2, );
draw_buffer.draw_rect( BACKGROUND_COLOR, CharRect::new( self.position.y(), self.position.y(), self.position.x(), self.position.x() + self.width, ), );
let cursor_pos = if MAX == MIN { 0 } else { // shift value scale. this is the new MAX // MIN -> MAX => 0 -> (MAX-MIN) let num_possible_values = MAX.abs_diff(MIN);
// shift value scale as shown below // MIN -> MAX => 0 -> (MAX-MIN) let value = self.number.abs_diff(MIN);
// first + 1 makes it have a border on the left side // rest mostly stole from original source code 1 + value * (u16::from(self.width) * FONT_SIZE16 + 1) / num_possible_values }; let color = match selected { true => CURSOR_SELECTED_COLOR, false => CURSOR_COLOR, };
let self_pixel_rect = PixelRect::from(CharRect::from(self.position)); let cursor_pixel_rect = PixelRect::new( // +1 to have a space above // (self.position.y() * FONT_SIZE16) + 1, self_pixel_rect.top() + 1, // -2 to make if have a 1. not go into the next line and 2. have a empty row below self_pixel_rect.bot() - 2, // (self.position.y() * FONT_SIZE16) + (FONT_SIZE16 - 2), self_pixel_rect.left() + cursor_pos + CURSOR_WIDTH, // (self.position.x() * FONT_SIZE16) + cursor_pos + CURSOR_WIDTH, self_pixel_rect.left() + cursor_pos, // (self.position.x() * FONT_SIZE16) + cursor_pos, );
draw_buffer.draw_pixel_rect(color, cursor_pixel_rect); }
fn process_input( &mut self, modifiers: &winit::event::Modifiers, key_event: &winit::event::KeyEvent, event: &mut EventQueue<'_>, ) -> WidgetResponse { if !key_event.state.is_pressed() { return WidgetResponse::default(); }
// move the slider // change the internal value and call the callback // this seems stupid but allows to reduce code duplication if key_event.logical_key == Key::Named(NamedKey::ArrowRight) || key_event.logical_key == Key::Named(NamedKey::ArrowLeft) { // existance of this bracket allows us to only call the callback if the value really was changed 'move_slider: { let direction = if key_event.logical_key == Key::Named(NamedKey::ArrowRight) { // the number is at its max, so increasing it doesnt do anything so we break here if *self.number == MAX { break 'move_slider; } 1 } else if key_event.logical_key == Key::Named(NamedKey::ArrowLeft) { // analog to the ArrowRight branch if *self.number == MIN { break 'move_slider; } -1 } else { // unreachable as the outer if has already checked this. unreachable!() };
// sets amount according to the modifiers like the original. why the original SchismTracker behaves like this i don't know // only reason i can imagine is that if you know the behaviour you can move quite quickly through a slider let amount = { let mut amount = 1; if modifiers.state().super_key() { amount *= 2; } if modifiers.state().shift_key() { amount *= 4; } if modifiers.state().alt_key() { amount *= 8; } amount };
self.number += amount * direction; return WidgetResponse::RequestRedraw(true); } // set the value directly, by opening a pop-up } else if let Key::Character(text) = &key_event.logical_key { let mut chars = text.chars(); if let Some(first_char) = chars.next() { if first_char.is_ascii_digit() { let dialog = SliderDialog::new(first_char, MIN..=MAX, self.dialog_return); event.push(GlobalEvent::OpenDialog(crate::dialog::DialogEnum::Slider( dialog, ))); return WidgetResponse::None; } } } else { return self.next_widget.process_key_event(key_event, modifiers); }
WidgetResponse::None }
#[cfg(feature = "accesskit")] fn build_tree(&self, tree: &mut Vec<(accesskit::NodeId, accesskit::Node)>) { use accesskit::{Node, Role};
let mut node = Node::new(Role::Slider); node.set_numeric_value(self.number.inner as f64); node.set_min_numeric_value(MIN as f64); node.set_max_numeric_value(MAX as f64); node.set_label(self.access.1.clone());
tree.push((self.access.0, node)); }}
impl<const MIN: i16, const MAX: i16> Slider<MIN, MAX> { /// next_widget left and right must be None, because they cant be called pub fn new( inital_value: i16, position: CharPosition, width: u8, next_widget: NextWidget, dialog_return: fn(i16) -> GlobalEvent, #[cfg(feature = "accesskit")] access: (accesskit::NodeId, Box<str>), ) -> Self { assert!(MIN <= MAX, "MIN must be less than or equal to MAX"); // panic is fine, because this object only is generated with compile time values assert!(next_widget.left.is_none()); assert!(next_widget.right.is_none()); // just put them here so i remember this limitation of the current implementation in the future // if this ever fails the code that draws the current value right of the slider needs to be made aware of the lenght needed and not just draw 3 chars assert!(MIN >= -99, "draw implementation needs to be redone"); assert!(MAX <= 999, "draw implementation needs to be redone");
// need right from the slider additional space to display the current value assert!(position.x() + width + 5 < WINDOW_SIZE_CHARS.0);
Self { number: BoundNumber::new(inital_value), position, width, next_widget, dialog_return, #[cfg(feature = "accesskit")] access, } }
pub fn try_set(&mut self, value: i16) -> Result<(), ()> { self.number.try_set(value) }
pub fn get(&self) -> i16 { self.number.inner }}