1use super::{GripMsg, GripPart};
9use kas::event::FocusSource;
10use kas::messages::{DecrementStep, IncrementStep, SetValueF64};
11use kas::prelude::*;
12use kas::theme::Feature;
13use std::fmt::Debug;
14use std::ops::{Add, RangeInclusive, Sub};
15
16pub trait SliderValue:
20 Copy
21 + Debug
22 + PartialOrd
23 + Add<Output = Self>
24 + Sub<Output = Self>
25 + Cast<f64>
26 + ConvApprox<f64>
27 + 'static
28{
29 fn default_step() -> Self;
31}
32
33impl SliderValue for f64 {
34 fn default_step() -> Self {
35 1.0
36 }
37}
38
39impl SliderValue for f32 {
40 fn default_step() -> Self {
41 1.0
42 }
43}
44
45macro_rules! impl_slider_ty {
46 ($ty:ty) => {
47 impl SliderValue for $ty {
48 fn default_step() -> Self {
49 1
50 }
51 }
52 };
53 ($ty:ty, $($tt:ty),*) => {
54 impl_slider_ty!($ty);
55 impl_slider_ty!($($tt),*);
56 };
57}
58impl_slider_ty!(i8, i16, i32, i64, i128, isize);
59impl_slider_ty!(u8, u16, u32, u64, u128, usize);
60
61#[impl_self]
62mod Slider {
63 #[autoimpl(Debug ignore self.state_fn, self.on_move)]
73 #[widget]
74 pub struct Slider<A, T: SliderValue, D: Directional = Direction> {
75 core: widget_core!(),
76 direction: D,
77 range: (T, T),
79 step: T,
80 value: T,
81 #[widget(&())]
82 grip: GripPart,
83 state_fn: Box<dyn Fn(&ConfigCx, &A) -> T>,
84 on_move: Option<Box<dyn Fn(&mut EventCx, &A, T)>>,
85 }
86
87 impl Self
88 where
89 D: Default,
90 {
91 #[inline]
102 pub fn new(
103 range: RangeInclusive<T>,
104 state_fn: impl Fn(&ConfigCx, &A) -> T + 'static,
105 ) -> Self {
106 Slider::new_dir(range, state_fn, D::default())
107 }
108 }
109 impl<A, T: SliderValue> Slider<A, T, kas::dir::Left> {
110 #[inline]
112 pub fn left(
113 range: RangeInclusive<T>,
114 state_fn: impl Fn(&ConfigCx, &A) -> T + 'static,
115 ) -> Self {
116 Slider::new(range, state_fn)
117 }
118 }
119 impl<A, T: SliderValue> Slider<A, T, kas::dir::Right> {
120 #[inline]
122 pub fn right(
123 range: RangeInclusive<T>,
124 state_fn: impl Fn(&ConfigCx, &A) -> T + 'static,
125 ) -> Self {
126 Slider::new(range, state_fn)
127 }
128 }
129 impl<A, T: SliderValue> Slider<A, T, kas::dir::Up> {
130 #[inline]
132 pub fn up(
133 range: RangeInclusive<T>,
134 state_fn: impl Fn(&ConfigCx, &A) -> T + 'static,
135 ) -> Self {
136 Slider::new(range, state_fn)
137 }
138 }
139
140 impl<A, T: SliderValue> Slider<A, T, kas::dir::Down> {
141 #[inline]
143 pub fn down(
144 range: RangeInclusive<T>,
145 state_fn: impl Fn(&ConfigCx, &A) -> T + 'static,
146 ) -> Self {
147 Slider::new(range, state_fn)
148 }
149 }
150
151 impl Self {
152 #[inline]
163 pub fn new_dir(
164 range: RangeInclusive<T>,
165 state_fn: impl Fn(&ConfigCx, &A) -> T + 'static,
166 direction: D,
167 ) -> Self {
168 assert!(!range.is_empty());
169 let value = *range.start();
170 Slider {
171 core: Default::default(),
172 direction,
173 range: range.into_inner(),
174 step: T::default_step(),
175 value,
176 grip: GripPart::new(),
177 state_fn: Box::new(state_fn),
178 on_move: None,
179 }
180 }
181
182 #[inline]
184 #[must_use]
185 pub fn with_msg<M>(self, f: impl Fn(T) -> M + 'static) -> Self
186 where
187 M: std::fmt::Debug + 'static,
188 {
189 self.with(move |cx, _, state| cx.push(f(state)))
190 }
191
192 #[inline]
194 #[must_use]
195 pub fn with(mut self, f: impl Fn(&mut EventCx, &A, T) + 'static) -> Self {
196 debug_assert!(self.on_move.is_none());
197 self.on_move = Some(Box::new(f));
198 self
199 }
200
201 #[inline]
203 pub fn direction(&self) -> Direction {
204 self.direction.as_direction()
205 }
206
207 #[inline]
209 #[must_use]
210 pub fn with_step(mut self, step: T) -> Self {
211 self.step = step;
212 self
213 }
214
215 #[allow(clippy::neg_cmp_op_on_partial_ord)]
220 fn set_value(&mut self, cx: &mut EventState, value: T) -> bool {
221 let value = if !(value >= self.range.0) {
222 self.range.0
223 } else if !(value <= self.range.1) {
224 self.range.1
225 } else {
226 value
227 };
228
229 if value == self.value {
230 return false;
231 }
232
233 self.value = value;
234 self.grip.set_offset(cx, self.offset());
235 true
236 }
237
238 fn offset(&self) -> Offset {
240 let a = self.value - self.range.0;
241 let b = self.range.1 - self.range.0;
242 let max_offset = self.grip.max_offset();
243 let mut frac = a.cast() / b.cast();
244 assert!((0.0..=1.0).contains(&frac));
245 if self.direction.is_reversed() {
246 frac = 1.0 - frac;
247 }
248 match self.direction.is_vertical() {
249 false => Offset((max_offset.0 as f64 * frac).cast_floor(), 0),
250 true => Offset(0, (max_offset.1 as f64 * frac).cast_floor()),
251 }
252 }
253
254 fn apply_grip_offset(&mut self, cx: &mut EventCx, data: &A, offset: Offset) {
255 let b = self.range.1 - self.range.0;
256 let max_offset = self.grip.max_offset();
257 let (offset, max) = match self.direction.is_vertical() {
258 false => (offset.0, max_offset.0),
259 true => (offset.1, max_offset.1),
260 };
261 let mut a = (b.cast() * (offset as f64 / max as f64))
262 .round()
263 .cast_approx();
264 if self.direction.is_reversed() {
265 a = b - a;
266 }
267 if self.set_value(cx, a + self.range.0) {
268 if let Some(ref f) = self.on_move {
269 f(cx, data, self.value);
270 }
271 }
272 }
273 }
274
275 impl Layout for Self {
276 fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
277 let _ = self.grip.size_rules(sizer.re(), axis);
278 sizer.feature(Feature::Slider(self.direction()), axis)
279 }
280
281 fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
282 let align = match self.direction.is_vertical() {
283 false => AlignPair::new(Align::Stretch, hints.vert.unwrap_or(Align::Center)),
284 true => AlignPair::new(hints.horiz.unwrap_or(Align::Center), Align::Stretch),
285 };
286 let mut rect = cx.align_feature(Feature::Slider(self.direction()), rect, align);
287 widget_set_rect!(rect);
288 self.grip.set_track(rect);
289
290 rect.size
293 .set_component(self.direction, cx.size_cx().grip_len());
294 self.grip.set_rect(cx, rect, AlignHints::NONE);
295 self.grip.set_offset(cx, self.offset());
297 }
298
299 fn draw(&self, mut draw: DrawCx) {
300 let dir = self.direction.as_direction();
301 draw.slider(self.rect(), &self.grip, dir);
302 }
303 }
304
305 impl Tile for Self {
306 fn navigable(&self) -> bool {
307 true
308 }
309
310 fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
311 Role::Slider {
312 min: self.range.0.cast(),
313 max: self.range.1.cast(),
314 step: self.step.cast(),
315 value: self.value.cast(),
316 direction: self.direction.as_direction(),
317 }
318 }
319
320 fn probe(&self, coord: Coord) -> Id {
321 if self.on_move.is_some() {
322 if let Some(id) = self.grip.try_probe(coord) {
323 return id;
324 }
325 }
326 self.id()
327 }
328 }
329
330 impl Events for Self {
331 const REDRAW_ON_MOUSE_OVER: bool = true;
332
333 type Data = A;
334
335 fn update(&mut self, cx: &mut ConfigCx, data: &A) {
336 let v = (self.state_fn)(cx, data);
337 self.set_value(cx, v);
338 }
339
340 fn handle_event(&mut self, cx: &mut EventCx, data: &A, event: Event) -> IsUsed {
341 if self.on_move.is_none() {
342 return Unused;
343 }
344
345 match event {
346 Event::Command(cmd, code) => {
347 let rev = self.direction.is_reversed();
348 let value = match cmd {
349 Command::Left | Command::Up => match rev {
350 false => self.value - self.step,
351 true => self.value + self.step,
352 },
353 Command::Right | Command::Down => match rev {
354 false => self.value + self.step,
355 true => self.value - self.step,
356 },
357 Command::PageUp | Command::PageDown => {
358 let mut x = self.step + self.step;
360 x = x + x;
361 x = x + x;
362 x = x + x;
363 match rev == (cmd == Command::PageDown) {
364 false => self.value + x,
365 true => self.value - x,
366 }
367 }
368 Command::Home => self.range.0,
369 Command::End => self.range.1,
370 _ => return Unused,
371 };
372
373 cx.depress_with_key(&self, code);
374
375 if self.set_value(cx, value) {
376 if let Some(ref f) = self.on_move {
377 f(cx, data, self.value);
378 }
379 }
380 }
381 Event::PressStart(press) => {
382 let offset = self.grip.handle_press_on_track(cx, &press);
383 self.apply_grip_offset(cx, data, offset);
384 }
385 _ => return Unused,
386 }
387 Used
388 }
389
390 fn handle_messages(&mut self, cx: &mut EventCx, data: &A) {
391 if self.on_move.is_none() {
392 return;
393 }
394
395 match cx.try_pop() {
396 Some(GripMsg::PressStart) => {
397 cx.request_nav_focus(self.id(), FocusSource::Synthetic)
398 }
399 Some(GripMsg::PressMove(pos)) => {
400 self.apply_grip_offset(cx, data, pos);
401 }
402 Some(GripMsg::PressEnd(_)) => (),
403 None => {
404 let mut new_value = None;
405 if let Some(SetValueF64(v)) = cx.try_pop() {
406 new_value = v
407 .try_cast_approx()
408 .map_err(|err| log::warn!("Slider failed to handle SetValueF64: {err}"))
409 .ok();
410 } else if let Some(IncrementStep) = cx.try_pop() {
411 new_value = Some(self.value + self.step);
412 } else if let Some(DecrementStep) = cx.try_pop() {
413 new_value = Some(self.value - self.step);
414 }
415
416 if let Some(value) = new_value
417 && self.set_value(cx, value)
418 && let Some(ref f) = self.on_move
419 {
420 f(cx, data, self.value);
421 }
422 }
423 }
424 }
425 }
426}