old school music tracker
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}