old school music tracker
at dev 307 lines 11 kB view raw
1use std::{ 2 fmt::Debug, 3 ops::{AddAssign, Deref, SubAssign}, 4}; 5 6use winit::keyboard::{Key, NamedKey}; 7 8use crate::{ 9 EventQueue, GlobalEvent, 10 coordinates::{CharPosition, CharRect, FONT_SIZE16, PixelRect, WINDOW_SIZE_CHARS}, 11 dialog::slider_dialog::SliderDialog, 12 draw_buffer::DrawBuffer, 13}; 14 15use super::{NextWidget, Widget, WidgetResponse}; 16 17#[derive(Debug)] 18pub struct BoundNumber<const MIN: i16, const MAX: i16> { 19 inner: i16, 20} 21 22impl<const MIN: i16, const MAX: i16> AddAssign<i16> for BoundNumber<MIN, MAX> { 23 fn add_assign(&mut self, rhs: i16) { 24 let new = self.inner.saturating_add(rhs); 25 // need to check both low and high bound as you can add a negative number 26 if new > MAX { 27 self.inner = MAX; 28 } else if new < MIN { 29 self.inner = MIN; 30 } else { 31 self.inner = new; 32 } 33 } 34} 35 36impl<const MIN: i16, const MAX: i16> SubAssign<i16> for BoundNumber<MIN, MAX> { 37 fn sub_assign(&mut self, rhs: i16) { 38 let new = self.inner.saturating_sub(rhs); 39 // need to check both low and high bound as you can sub a negative number 40 if new > MAX { 41 self.inner = MAX; 42 } else if new < MIN { 43 self.inner = MIN; 44 } else { 45 self.inner = new; 46 } 47 } 48} 49 50impl<const MIN: i16, const MAX: i16> Deref for BoundNumber<MIN, MAX> { 51 type Target = i16; 52 53 fn deref(&self) -> &Self::Target { 54 &self.inner 55 } 56} 57 58impl<const MIN: i16, const MAX: i16> BoundNumber<MIN, MAX> { 59 const _VALID: () = assert!(MIN <= MAX, "MIN must be less than or equal to MAX"); 60 61 /// sets inner to MIN or MAX if its out of bounds 62 pub const fn new_saturating(value: i16) -> Self { 63 if value > MAX { 64 Self { inner: MAX } 65 } else if value < MIN { 66 Self { inner: MIN } 67 } else { 68 Self { inner: value } 69 } 70 } 71 72 /// panics on out of bounds value 73 pub const fn new(value: i16) -> Self { 74 assert!(value <= MAX); 75 assert!(value >= MIN); 76 Self { inner: value } 77 } 78 79 /// sets to MAX or MIN when the value is out of bounds 80 pub fn set_saturating(&mut self, value: i16) { 81 if value > MAX { 82 self.inner = MAX 83 } else if value < MIN { 84 self.inner = MIN; 85 } else { 86 self.inner = value; 87 } 88 } 89 90 pub fn try_set(&mut self, value: i16) -> Result<(), ()> { 91 if MIN <= value && value <= MAX { 92 self.inner = value; 93 Ok(()) 94 } else { 95 Err(()) 96 } 97 } 98 99 // dont need any arguments as MIN and MAX are compile time known 100 pub const fn get_middle() -> i16 { 101 MIN.midpoint(MAX) 102 } 103} 104 105/// Slider needs more Space then is specified in Rect as it draws the current value with an offset of 2 right to the box. 106/// 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 107#[derive(Debug)] 108pub struct Slider<const MIN: i16, const MAX: i16> { 109 number: BoundNumber<MIN, MAX>, 110 position: CharPosition, 111 width: u8, 112 next_widget: NextWidget, 113 dialog_return: fn(i16) -> GlobalEvent, 114 #[cfg(feature = "accesskit")] 115 access: (accesskit::NodeId, Box<str>), 116} 117 118impl<const MIN: i16, const MAX: i16> Widget for Slider<MIN, MAX> { 119 fn draw(&self, draw_buffer: &mut DrawBuffer, selected: bool) { 120 const BACKGROUND_COLOR: u8 = 0; 121 const CURSOR_COLOR: u8 = 2; 122 const CURSOR_SELECTED_COLOR: u8 = 3; 123 124 const CURSOR_WIDTH: u16 = 4; 125 126 draw_buffer.draw_string( 127 &format!("{:03}", *self.number), 128 self.position + (self.width + 2, 0), 129 1, 130 2, 131 ); 132 133 draw_buffer.draw_rect( 134 BACKGROUND_COLOR, 135 CharRect::new( 136 self.position.y(), 137 self.position.y(), 138 self.position.x(), 139 self.position.x() + self.width, 140 ), 141 ); 142 143 let cursor_pos = if MAX == MIN { 144 0 145 } else { 146 // shift value scale. this is the new MAX 147 // MIN -> MAX => 0 -> (MAX-MIN) 148 let num_possible_values = MAX.abs_diff(MIN); 149 150 // shift value scale as shown below 151 // MIN -> MAX => 0 -> (MAX-MIN) 152 let value = self.number.abs_diff(MIN); 153 154 // first + 1 makes it have a border on the left side 155 // rest mostly stole from original source code 156 1 + value * (u16::from(self.width) * FONT_SIZE16 + 1) / num_possible_values 157 }; 158 let color = match selected { 159 true => CURSOR_SELECTED_COLOR, 160 false => CURSOR_COLOR, 161 }; 162 163 let self_pixel_rect = PixelRect::from(CharRect::from(self.position)); 164 let cursor_pixel_rect = PixelRect::new( 165 // +1 to have a space above 166 // (self.position.y() * FONT_SIZE16) + 1, 167 self_pixel_rect.top() + 1, 168 // -2 to make if have a 1. not go into the next line and 2. have a empty row below 169 self_pixel_rect.bot() - 2, 170 // (self.position.y() * FONT_SIZE16) + (FONT_SIZE16 - 2), 171 self_pixel_rect.left() + cursor_pos + CURSOR_WIDTH, 172 // (self.position.x() * FONT_SIZE16) + cursor_pos + CURSOR_WIDTH, 173 self_pixel_rect.left() + cursor_pos, 174 // (self.position.x() * FONT_SIZE16) + cursor_pos, 175 ); 176 177 draw_buffer.draw_pixel_rect(color, cursor_pixel_rect); 178 } 179 180 fn process_input( 181 &mut self, 182 modifiers: &winit::event::Modifiers, 183 key_event: &winit::event::KeyEvent, 184 event: &mut EventQueue<'_>, 185 ) -> WidgetResponse { 186 if !key_event.state.is_pressed() { 187 return WidgetResponse::default(); 188 } 189 190 // move the slider 191 // change the internal value and call the callback 192 // this seems stupid but allows to reduce code duplication 193 if key_event.logical_key == Key::Named(NamedKey::ArrowRight) 194 || key_event.logical_key == Key::Named(NamedKey::ArrowLeft) 195 { 196 // existance of this bracket allows us to only call the callback if the value really was changed 197 'move_slider: { 198 let direction = if key_event.logical_key == Key::Named(NamedKey::ArrowRight) { 199 // the number is at its max, so increasing it doesnt do anything so we break here 200 if *self.number == MAX { 201 break 'move_slider; 202 } 203 1 204 } else if key_event.logical_key == Key::Named(NamedKey::ArrowLeft) { 205 // analog to the ArrowRight branch 206 if *self.number == MIN { 207 break 'move_slider; 208 } 209 -1 210 } else { 211 // unreachable as the outer if has already checked this. 212 unreachable!() 213 }; 214 215 // sets amount according to the modifiers like the original. why the original SchismTracker behaves like this i don't know 216 // only reason i can imagine is that if you know the behaviour you can move quite quickly through a slider 217 let amount = { 218 let mut amount = 1; 219 if modifiers.state().super_key() { 220 amount *= 2; 221 } 222 if modifiers.state().shift_key() { 223 amount *= 4; 224 } 225 if modifiers.state().alt_key() { 226 amount *= 8; 227 } 228 amount 229 }; 230 231 self.number += amount * direction; 232 return WidgetResponse::RequestRedraw(true); 233 } 234 // set the value directly, by opening a pop-up 235 } else if let Key::Character(text) = &key_event.logical_key { 236 let mut chars = text.chars(); 237 if let Some(first_char) = chars.next() { 238 if first_char.is_ascii_digit() { 239 let dialog = SliderDialog::new(first_char, MIN..=MAX, self.dialog_return); 240 event.push(GlobalEvent::OpenDialog(crate::dialog::DialogEnum::Slider( 241 dialog, 242 ))); 243 return WidgetResponse::None; 244 } 245 } 246 } else { 247 return self.next_widget.process_key_event(key_event, modifiers); 248 } 249 250 WidgetResponse::None 251 } 252 253 #[cfg(feature = "accesskit")] 254 fn build_tree(&self, tree: &mut Vec<(accesskit::NodeId, accesskit::Node)>) { 255 use accesskit::{Node, Role}; 256 257 let mut node = Node::new(Role::Slider); 258 node.set_numeric_value(self.number.inner as f64); 259 node.set_min_numeric_value(MIN as f64); 260 node.set_max_numeric_value(MAX as f64); 261 node.set_label(self.access.1.clone()); 262 263 tree.push((self.access.0, node)); 264 } 265} 266 267impl<const MIN: i16, const MAX: i16> Slider<MIN, MAX> { 268 /// next_widget left and right must be None, because they cant be called 269 pub fn new( 270 inital_value: i16, 271 position: CharPosition, 272 width: u8, 273 next_widget: NextWidget, 274 dialog_return: fn(i16) -> GlobalEvent, 275 #[cfg(feature = "accesskit")] access: (accesskit::NodeId, Box<str>), 276 ) -> Self { 277 assert!(MIN <= MAX, "MIN must be less than or equal to MAX"); 278 // panic is fine, because this object only is generated with compile time values 279 assert!(next_widget.left.is_none()); 280 assert!(next_widget.right.is_none()); 281 // just put them here so i remember this limitation of the current implementation in the future 282 // 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 283 assert!(MIN >= -99, "draw implementation needs to be redone"); 284 assert!(MAX <= 999, "draw implementation needs to be redone"); 285 286 // need right from the slider additional space to display the current value 287 assert!(position.x() + width + 5 < WINDOW_SIZE_CHARS.0); 288 289 Self { 290 number: BoundNumber::new(inital_value), 291 position, 292 width, 293 next_widget, 294 dialog_return, 295 #[cfg(feature = "accesskit")] 296 access, 297 } 298 } 299 300 pub fn try_set(&mut self, value: i16) -> Result<(), ()> { 301 self.number.try_set(value) 302 } 303 304 pub fn get(&self) -> i16 { 305 self.number.inner 306 } 307}