vizia_core/views/slider.rs
1use std::ops::Range;
2
3use accesskit::ActionData;
4
5use crate::prelude::*;
6
7#[derive(Debug)]
8enum SliderEventInternal {
9 SetThumbSize(f32, f32),
10 SetRange(Range<f32>),
11 SetKeyboardFraction(f32),
12}
13
14/// Internal data used by the slider.
15#[derive(Clone, Debug, Default, Data)]
16pub struct SliderDataInternal {
17 /// The orientation of the slider.
18 pub orientation: Orientation,
19 /// The size of the slider.
20 pub size: f32,
21 /// The size of the thumb of the slider.
22 pub thumb_size: f32,
23 /// The range of the slider.
24 pub range: Range<f32>,
25 /// The step of the slider.
26 pub step: f32,
27 /// How much the slider should change in response to keyboard events.
28 pub keyboard_fraction: f32,
29}
30
31/// The slider control can be used to select from a continuous set of values.
32///
33/// The slider control consists of three main parts, a **thumb** element which can be moved between the extremes of a linear **track**,
34/// and an **active** element which fills the slider to indicate the current value.
35///
36/// The slider orientation is determined by its dimensions. If the slider width is greater than the height then the thumb
37/// moves horizontally, whereas if the slider height is greater than the width the thumb moves vertically.
38///
39/// # Examples
40///
41/// ## Basic Slider
42/// In the following example, a slider is bound to a value. The `on_changing` callback is used to send an event to mutate the
43/// bound value when the slider thumb is moved, or if the track is clicked on.
44/// ```
45/// # use vizia_core::prelude::*;
46/// # use vizia_derive::*;
47/// # let mut cx = &mut Context::default();
48/// # #[derive(Lens, Default)]
49/// # pub struct AppData {
50/// # value: f32,
51/// # }
52/// # impl Model for AppData {}
53/// # AppData::default().build(cx);
54/// Slider::new(cx, AppData::value)
55/// .on_changing(|cx, value| {
56/// debug!("Slider on_changing: {}", value);
57/// });
58/// ```
59///
60/// ## Slider with Label
61/// ```
62/// # use vizia_core::prelude::*;
63/// # use vizia_derive::*;
64/// # let mut cx = &mut Context::default();
65/// # #[derive(Lens, Default)]
66/// # pub struct AppData {
67/// # value: f32,
68/// # }
69/// # impl Model for AppData {}
70/// # AppData::default().build(cx);
71/// HStack::new(cx, |cx|{
72/// Slider::new(cx, AppData::value)
73/// .on_changing(|cx, value| {
74/// debug!("Slider on_changing: {}", value);
75/// });
76/// Label::new(cx, AppData::value.map(|val| format!("{:.2}", val)));
77/// });
78/// ```
79#[derive(Lens)]
80pub struct Slider<L: Lens> {
81 lens: L,
82 is_dragging: bool,
83 internal: SliderDataInternal,
84 on_changing: Option<Box<dyn Fn(&mut EventContext, f32)>>,
85}
86
87impl<L> Slider<L>
88where
89 L: Lens<Target = f32>,
90{
91 /// Creates a new slider bound to the value targeted by the lens.
92 ///
93 /// # Example
94 /// ```
95 /// # use vizia_core::prelude::*;
96 /// # use vizia_derive::*;
97 /// # let mut cx = &mut Context::default();
98 /// # #[derive(Lens, Default)]
99 /// # pub struct AppData {
100 /// # value: f32,
101 /// # }
102 /// # impl Model for AppData {}
103 /// # AppData::default().build(cx);
104 /// Slider::new(cx, AppData::value)
105 /// .on_changing(|cx, value| {
106 /// debug!("Slider on_changing: {}", value);
107 /// });
108 /// ```
109 pub fn new(cx: &mut Context, lens: L) -> Handle<Self> {
110 Self {
111 lens,
112 is_dragging: false,
113
114 internal: SliderDataInternal {
115 orientation: Orientation::Horizontal,
116 thumb_size: 0.0,
117 size: 0.0,
118 range: 0.0..1.0,
119 step: 0.01,
120 keyboard_fraction: 0.1,
121 },
122
123 on_changing: None,
124 }
125 .build(cx, move |cx| {
126 Binding::new(cx, Slider::<L>::internal, move |cx, slider_data| {
127 ZStack::new(cx, move |cx| {
128 let slider_data = slider_data.get(cx);
129 let thumb_size = slider_data.thumb_size;
130 let orientation = slider_data.orientation;
131 let size = slider_data.size;
132 let range = slider_data.range;
133
134 // Active track
135 Element::new(cx).class("active").bind(lens, move |handle, value| {
136 let val = value.get(&handle).clamp(range.start, range.end);
137
138 let normal_val = (val - range.start) / (range.end - range.start);
139 let min = thumb_size / size;
140 let max = 1.0;
141 let dx = min + normal_val * (max - min);
142
143 if orientation == Orientation::Horizontal {
144 handle
145 .height(Stretch(1.0))
146 .left(Pixels(0.0))
147 .right(Stretch(1.0))
148 .width(Percentage(dx * 100.0));
149 } else {
150 handle
151 .width(Stretch(1.0))
152 .top(Stretch(1.0))
153 .bottom(Pixels(0.0))
154 .height(Percentage(dx * 100.0));
155 }
156 });
157
158 // Thumb
159 Element::new(cx)
160 .class("thumb")
161 .on_geo_changed(|cx, geo| {
162 if geo.contains(GeoChanged::WIDTH_CHANGED)
163 || geo.contains(GeoChanged::HEIGHT_CHANGED)
164 {
165 let bounds = cx.bounds();
166 cx.emit(SliderEventInternal::SetThumbSize(bounds.w, bounds.h));
167 }
168 })
169 .bind(lens, move |handle, value| {
170 let val = value.get(&handle).clamp(range.start, range.end);
171 let normal_val = (val - range.start) / (range.end - range.start);
172 let px = normal_val * (1.0 - (thumb_size / size));
173 if orientation == Orientation::Horizontal {
174 handle
175 .right(Stretch(1.0))
176 .top(Stretch(1.0))
177 .bottom(Stretch(1.0))
178 .left(Percentage(100.0 * px));
179 } else {
180 handle
181 .top(Stretch(1.0))
182 .left(Stretch(1.0))
183 .right(Stretch(1.0))
184 .bottom(Percentage(100.0 * px));
185 }
186 });
187 });
188 });
189 })
190 .role(Role::Slider)
191 .numeric_value(lens.map(|val| (*val as f64 * 100.0).round() / 100.0))
192 .text_value(lens.map(|val| {
193 let v = (*val as f64 * 100.0).round() / 100.0;
194 format!("{}", v)
195 }))
196 .navigable(true)
197 }
198}
199
200impl<L: Lens<Target = f32>> View for Slider<L> {
201 fn element(&self) -> Option<&'static str> {
202 Some("slider")
203 }
204
205 fn accessibility(&self, _cx: &mut AccessContext, node: &mut AccessNode) {
206 node.set_numeric_value_step(self.internal.step as f64);
207 node.set_min_numeric_value(self.internal.range.start as f64);
208 node.set_max_numeric_value(self.internal.range.end as f64);
209 }
210
211 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
212 event.map(|slider_event_internal, _| match slider_event_internal {
213 SliderEventInternal::SetThumbSize(width, height) => match self.internal.orientation {
214 Orientation::Horizontal => {
215 self.internal.thumb_size = *width;
216 }
217
218 Orientation::Vertical => {
219 self.internal.thumb_size = *height;
220 }
221 },
222
223 SliderEventInternal::SetRange(range) => {
224 let mut range = range.clone();
225 range.end = range.end.max(range.start);
226 self.internal.range = range;
227 }
228
229 SliderEventInternal::SetKeyboardFraction(keyboard_fraction) => {
230 self.internal.keyboard_fraction = *keyboard_fraction;
231 }
232 });
233
234 event.map(|window_event, _| match window_event {
235 WindowEvent::GeometryChanged(_) => {
236 let current = cx.current();
237 let width = cx.cache.get_width(current);
238 let height = cx.cache.get_height(current);
239
240 if width >= height {
241 self.internal.orientation = Orientation::Horizontal;
242 self.internal.size = width;
243 } else {
244 self.internal.orientation = Orientation::Vertical;
245 self.internal.size = height;
246 }
247 }
248
249 WindowEvent::MouseDown(button) if *button == MouseButton::Left => {
250 if !cx.is_disabled() {
251 self.is_dragging = true;
252 cx.capture();
253 cx.focus_with_visibility(false);
254 cx.with_current(Entity::root(), |cx| {
255 cx.set_pointer_events(false);
256 });
257
258 let thumb_size = self.internal.thumb_size;
259 let min = self.internal.range.start;
260 let max = self.internal.range.end;
261 let step = self.internal.step;
262
263 let current = cx.current();
264 let width = cx.cache.get_width(current);
265 let height = cx.cache.get_height(current);
266 let posx = cx.cache.get_posx(current);
267 let posy = cx.cache.get_posy(current);
268
269 let mut dx = match self.internal.orientation {
270 Orientation::Horizontal => {
271 (cx.mouse.left.pos_down.0 - posx - thumb_size / 2.0)
272 / (width - thumb_size)
273 }
274
275 Orientation::Vertical => {
276 (height - (cx.mouse.left.pos_down.1 - posy) - thumb_size / 2.0)
277 / (height - thumb_size)
278 }
279 };
280
281 dx = dx.clamp(0.0, 1.0);
282
283 let mut val = min + dx * (max - min);
284
285 val = step * (val / step).ceil();
286 val = val.clamp(min, max);
287
288 if let Some(callback) = self.on_changing.take() {
289 (callback)(cx, val);
290
291 self.on_changing = Some(callback);
292 }
293 }
294 }
295
296 WindowEvent::MouseUp(button) if *button == MouseButton::Left => {
297 self.is_dragging = false;
298 cx.focus_with_visibility(false);
299 cx.release();
300 cx.with_current(Entity::root(), |cx| {
301 cx.set_pointer_events(true);
302 });
303 }
304
305 WindowEvent::MouseMove(x, y) => {
306 if self.is_dragging {
307 let thumb_size = self.internal.thumb_size;
308
309 let min = self.internal.range.start;
310 let max = self.internal.range.end;
311 let step = self.internal.step;
312
313 let current = cx.current();
314 let width = cx.cache.get_width(current);
315 let height = cx.cache.get_height(current);
316 let posx = cx.cache.get_posx(current);
317 let posy = cx.cache.get_posy(current);
318
319 let mut dx = match self.internal.orientation {
320 Orientation::Horizontal => {
321 (*x - posx - thumb_size / 2.0) / (width - thumb_size)
322 }
323
324 Orientation::Vertical => {
325 (height - (*y - posy) - thumb_size / 2.0) / (height - thumb_size)
326 }
327 };
328
329 dx = dx.clamp(0.0, 1.0);
330
331 let mut val = min + dx * (max - min);
332
333 val = step * (val / step).ceil();
334 val = val.clamp(min, max);
335
336 if let Some(callback) = &self.on_changing {
337 (callback)(cx, val);
338 }
339 }
340 }
341
342 WindowEvent::KeyDown(Code::ArrowUp | Code::ArrowRight, _) => {
343 let min = self.internal.range.start;
344 let max = self.internal.range.end;
345 let step = self.internal.step;
346 let mut val = self.lens.get(cx) + step;
347 // val = step * (val / step).ceil();
348 val = val.clamp(min, max);
349 if let Some(callback) = &self.on_changing {
350 (callback)(cx, val);
351 }
352 }
353
354 WindowEvent::KeyDown(Code::ArrowDown | Code::ArrowLeft, _) => {
355 let min = self.internal.range.start;
356 let max = self.internal.range.end;
357 let step = self.internal.step;
358 let mut val = self.lens.get(cx) - step;
359 // val = step * (val / step).ceil();
360 val = val.clamp(min, max);
361 if let Some(callback) = &self.on_changing {
362 (callback)(cx, val);
363 }
364 }
365
366 WindowEvent::ActionRequest(action) => match action.action {
367 Action::Increment => {
368 let min = self.internal.range.start;
369 let max = self.internal.range.end;
370 let step = self.internal.step;
371 let mut val = self.lens.get(cx) + step;
372 val = step * (val / step).ceil();
373 val = val.clamp(min, max);
374 if let Some(callback) = &self.on_changing {
375 (callback)(cx, val);
376 }
377 }
378
379 Action::Decrement => {
380 let min = self.internal.range.start;
381 let max = self.internal.range.end;
382 let step = self.internal.step;
383 let mut val = self.lens.get(cx) - step;
384 val = step * (val / step).ceil();
385 val = val.clamp(min, max);
386 if let Some(callback) = &self.on_changing {
387 (callback)(cx, val);
388 }
389 }
390
391 Action::SetValue => {
392 if let Some(ActionData::NumericValue(val)) = action.data {
393 let min = self.internal.range.start;
394 let max = self.internal.range.end;
395 let mut v = val as f32;
396 v = v.clamp(min, max);
397 if let Some(callback) = &self.on_changing {
398 (callback)(cx, v);
399 }
400 }
401 }
402
403 _ => {}
404 },
405
406 _ => {}
407 });
408 }
409}
410
411impl<L: Lens> Handle<'_, Slider<L>> {
412 /// Sets the callback triggered when the slider value is changed.
413 ///
414 /// Takes a closure which triggers when the slider value is changed,
415 /// either by pressing the track or dragging the thumb along the track.
416 ///
417 /// # Example
418 ///
419 /// ```
420 /// # use vizia_core::prelude::*;
421 /// # use vizia_derive::*;
422 /// # let mut cx = &mut Context::default();
423 /// # #[derive(Lens, Default)]
424 /// # pub struct AppData {
425 /// # value: f32,
426 /// # }
427 /// # impl Model for AppData {}
428 /// # AppData::default().build(cx);
429 /// Slider::new(cx, AppData::value)
430 /// .on_change(|cx, value| {
431 /// debug!("Slider on_changing: {}", value);
432 /// });
433 /// ```
434 pub fn on_change<F>(self, callback: F) -> Self
435 where
436 F: 'static + Fn(&mut EventContext, f32),
437 {
438 self.modify(|slider| slider.on_changing = Some(Box::new(callback)))
439 }
440
441 /// Sets the range of the slider.
442 ///
443 /// If the bound data is outside of the range then the slider will clip to min/max of the range.
444 ///
445 /// # Example
446 /// ```
447 /// # use vizia_core::prelude::*;
448 /// # use vizia_derive::*;
449 /// # let mut cx = &mut Context::default();
450 /// # #[derive(Lens, Default)]
451 /// # pub struct AppData {
452 /// # value: f32,
453 /// # }
454 /// # impl Model for AppData {}
455 /// # AppData::default().build(cx);
456 /// Slider::new(cx, AppData::value)
457 /// .range(-20.0..50.0)
458 /// .on_changing(|cx, value| {
459 /// debug!("Slider on_changing: {}", value);
460 /// });
461 /// ```
462 pub fn range(self, range: Range<f32>) -> Self {
463 self.cx.emit_to(self.entity, SliderEventInternal::SetRange(range));
464
465 self
466 }
467
468 /// Set the step value for the slider.
469 pub fn step(self, step: f32) -> Self {
470 self.modify(|slider: &mut Slider<L>| slider.internal.step = step)
471 }
472
473 /// Sets the fraction of a slider that a press of an arrow key will change.
474 ///
475 /// # Example
476 /// ```
477 /// # use vizia_core::prelude::*;
478 /// # use vizia_derive::*;
479 /// # let mut cx = &mut Context::default();
480 /// # #[derive(Lens, Default)]
481 /// # pub struct AppData {
482 /// # value: f32,
483 /// # }
484 /// # impl Model for AppData {}
485 /// # AppData::default().build(cx);
486 /// Slider::new(cx, AppData::value)
487 /// .keyboard_fraction(0.05)
488 /// .on_changing(|cx, value| {
489 /// debug!("Slider on_changing: {}", value);
490 /// });
491 /// ```
492 pub fn keyboard_fraction(self, keyboard_fraction: f32) -> Self {
493 self.cx.emit_to(self.entity, SliderEventInternal::SetKeyboardFraction(keyboard_fraction));
494
495 self
496 }
497}