1use std::fmt::Debug;
6
7use iced_native::widget::tree::{self, Tree};
8use iced_native::{
9 event, keyboard, layout, mouse, touch, Clipboard, Element, Event, Layout,
10 Length, Point, Rectangle, Shell, Size, Widget,
11};
12
13use crate::core::{ModulationRange, Normal, NormalParam};
14use crate::native::{text_marks, tick_marks, SliderStatus};
15use crate::style::v_slider::StyleSheet;
16
17static DEFAULT_WIDTH: f32 = 14.0;
18static DEFAULT_SCALAR: f32 = 0.9575;
19static DEFAULT_WHEEL_SCALAR: f32 = 0.01;
20static DEFAULT_MODIFIER_SCALAR: f32 = 0.02;
21
22#[allow(missing_debug_implementations)]
29pub struct VSlider<'a, Message, Renderer>
30where
31 Renderer: self::Renderer,
32 Renderer::Theme: StyleSheet,
33{
34 normal_param: NormalParam,
35 on_change: Box<dyn 'a + Fn(Normal) -> Message>,
36 on_grab: Option<Box<dyn 'a + FnMut() -> Option<Message>>>,
37 on_release: Option<Box<dyn 'a + FnMut() -> Option<Message>>>,
38 scalar: f32,
39 wheel_scalar: f32,
40 modifier_scalar: f32,
41 modifier_keys: keyboard::Modifiers,
42 width: Length,
43 height: Length,
44 style: <Renderer::Theme as StyleSheet>::Style,
45 tick_marks: Option<&'a tick_marks::Group>,
46 text_marks: Option<&'a text_marks::Group>,
47 mod_range_1: Option<&'a ModulationRange>,
48 mod_range_2: Option<&'a ModulationRange>,
49}
50
51impl<'a, Message, Renderer> VSlider<'a, Message, Renderer>
52where
53 Renderer: self::Renderer,
54 Renderer::Theme: StyleSheet,
55{
56 pub fn new<F>(normal_param: NormalParam, on_change: F) -> Self
65 where
66 F: 'static + Fn(Normal) -> Message,
67 {
68 VSlider {
69 normal_param,
70 on_change: Box::new(on_change),
71 on_grab: None,
72 on_release: None,
73 scalar: DEFAULT_SCALAR,
74 wheel_scalar: DEFAULT_WHEEL_SCALAR,
75 modifier_scalar: DEFAULT_MODIFIER_SCALAR,
76 modifier_keys: keyboard::Modifiers::CTRL,
77 width: Length::Fixed(DEFAULT_WIDTH),
78 height: Length::Fill,
79 style: Default::default(),
80 tick_marks: None,
81 text_marks: None,
82 mod_range_1: None,
83 mod_range_2: None,
84 }
85 }
86
87 pub fn on_grab(
94 mut self,
95 on_grab: impl 'a + FnMut() -> Option<Message>,
96 ) -> Self {
97 self.on_grab = Some(Box::new(on_grab));
98 self
99 }
100
101 pub fn on_release(
108 mut self,
109 on_release: impl 'a + FnMut() -> Option<Message>,
110 ) -> Self {
111 self.on_release = Some(Box::new(on_release));
112 self
113 }
114
115 pub fn width(mut self, width: Length) -> Self {
120 self.width = width;
121 self
122 }
123
124 pub fn height(mut self, height: Length) -> Self {
129 self.height = height;
130 self
131 }
132
133 pub fn style(
137 mut self,
138 style: impl Into<<Renderer::Theme as StyleSheet>::Style>,
139 ) -> Self {
140 self.style = style.into();
141 self
142 }
143
144 pub fn modifier_keys(mut self, modifier_keys: keyboard::Modifiers) -> Self {
150 self.modifier_keys = modifier_keys;
151 self
152 }
153
154 pub fn scalar(mut self, scalar: f32) -> Self {
163 self.scalar = scalar;
164 self
165 }
166
167 pub fn wheel_scalar(mut self, wheel_scalar: f32) -> Self {
177 self.wheel_scalar = wheel_scalar;
178 self
179 }
180
181 pub fn modifier_scalar(mut self, scalar: f32) -> Self {
191 self.modifier_scalar = scalar;
192 self
193 }
194
195 pub fn tick_marks(mut self, tick_marks: &'a tick_marks::Group) -> Self {
201 self.tick_marks = Some(tick_marks);
202 self
203 }
204
205 pub fn text_marks(mut self, text_marks: &'a text_marks::Group) -> Self {
211 self.text_marks = Some(text_marks);
212 self
213 }
214
215 pub fn mod_range(mut self, mod_range: &'a ModulationRange) -> Self {
222 self.mod_range_1 = Some(mod_range);
223 self
224 }
225
226 pub fn mod_range_2(mut self, mod_range: &'a ModulationRange) -> Self {
233 self.mod_range_1 = Some(mod_range);
234 self
235 }
236
237 fn move_virtual_slider(
238 &mut self,
239 state: &mut State,
240 mut normal_delta: f32,
241 ) -> SliderStatus {
242 if normal_delta.abs() < f32::EPSILON {
243 return SliderStatus::Unchanged;
244 }
245
246 if state.pressed_modifiers.contains(self.modifier_keys) {
247 normal_delta *= self.modifier_scalar;
248 }
249
250 self.normal_param
251 .value
252 .set_clipped(state.continuous_normal - normal_delta);
253 state.continuous_normal = self.normal_param.value.as_f32();
254
255 SliderStatus::Moved
256 }
257
258 fn maybe_fire_on_grab(&mut self, shell: &mut Shell<'_, Message>) {
259 if let Some(message) =
260 self.on_grab.as_mut().and_then(|on_grab| on_grab())
261 {
262 shell.publish(message);
263 }
264 }
265
266 fn fire_on_change(&self, shell: &mut Shell<'_, Message>) {
267 shell.publish((self.on_change)(self.normal_param.value));
268 }
269
270 fn maybe_fire_on_release(&mut self, shell: &mut Shell<'_, Message>) {
271 if let Some(message) =
272 self.on_release.as_mut().and_then(|on_release| on_release())
273 {
274 shell.publish(message);
275 }
276 }
277}
278
279#[derive(Debug, Clone)]
283struct State {
284 dragging_status: Option<SliderStatus>,
285 prev_drag_y: f32,
286 prev_normal: Normal,
287 continuous_normal: f32,
288 pressed_modifiers: keyboard::Modifiers,
289 last_click: Option<mouse::Click>,
290 tick_marks_cache: crate::graphics::tick_marks::PrimitiveCache,
291 text_marks_cache: crate::graphics::text_marks::PrimitiveCache,
292}
293
294impl State {
295 fn new(normal: Normal) -> Self {
303 Self {
304 dragging_status: None,
305 prev_drag_y: 0.0,
306 prev_normal: normal,
307 continuous_normal: normal.as_f32(),
308 pressed_modifiers: Default::default(),
309 last_click: None,
310 tick_marks_cache: Default::default(),
311 text_marks_cache: Default::default(),
312 }
313 }
314}
315
316impl<'a, Message, Renderer> Widget<Message, Renderer>
317 for VSlider<'a, Message, Renderer>
318where
319 Renderer: self::Renderer,
320 Renderer::Theme: StyleSheet,
321{
322 fn tag(&self) -> tree::Tag {
323 tree::Tag::of::<State>()
324 }
325
326 fn state(&self) -> tree::State {
327 tree::State::new(State::new(self.normal_param.value))
328 }
329
330 fn width(&self) -> Length {
331 Length::Shrink
332 }
333
334 fn height(&self) -> Length {
335 self.height
336 }
337
338 fn layout(
339 &self,
340 _renderer: &Renderer,
341 limits: &layout::Limits,
342 ) -> layout::Node {
343 let limits = limits.width(self.width).height(self.height);
344
345 let size = limits.resolve(Size::ZERO);
346
347 layout::Node::new(size)
348 }
349
350 fn on_event(
351 &mut self,
352 state: &mut Tree,
353 event: Event,
354 layout: Layout<'_>,
355 cursor_position: Point,
356 _renderer: &Renderer,
357 _clipboard: &mut dyn Clipboard,
358 shell: &mut Shell<'_, Message>,
359 ) -> event::Status {
360 let state = state.state.downcast_mut::<State>();
361
362 if state.dragging_status.is_none()
364 && state.prev_normal != self.normal_param.value
365 {
366 state.prev_normal = self.normal_param.value;
367 state.continuous_normal = self.normal_param.value.as_f32();
368 }
369
370 match event {
371 Event::Mouse(mouse::Event::CursorMoved { .. })
372 | Event::Touch(touch::Event::FingerMoved { .. }) => {
373 if state.dragging_status.is_some() {
374 let bounds = layout.bounds();
375 if bounds.height > 0.0 {
376 let normal_delta = (cursor_position.y
377 - state.prev_drag_y)
378 / bounds.height
379 * self.scalar;
380
381 state.prev_drag_y = if cursor_position.y <= bounds.y {
382 bounds.y
383 } else {
384 cursor_position.y.min(bounds.y + bounds.height)
385 };
386
387 if self
388 .move_virtual_slider(state, normal_delta)
389 .was_moved()
390 {
391 self.fire_on_change(shell);
392
393 state
394 .dragging_status
395 .as_mut()
396 .expect("dragging_status taken")
397 .moved();
398 }
399
400 return event::Status::Captured;
401 }
402 }
403 }
404 Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
405 if self.wheel_scalar == 0.0 {
406 return event::Status::Ignored;
407 }
408
409 if layout.bounds().contains(cursor_position) {
410 let lines = match delta {
411 iced_native::mouse::ScrollDelta::Lines {
412 y, ..
413 } => y,
414 iced_native::mouse::ScrollDelta::Pixels {
415 y, ..
416 } => {
417 if y > 0.0 {
418 1.0
419 } else if y < 0.0 {
420 -1.0
421 } else {
422 0.0
423 }
424 }
425 };
426
427 if lines != 0.0 {
428 let normal_delta = -lines * self.wheel_scalar;
429
430 if self
431 .move_virtual_slider(state, normal_delta)
432 .was_moved()
433 {
434 if state.dragging_status.is_none() {
435 self.maybe_fire_on_grab(shell);
436 }
437
438 self.fire_on_change(shell);
439
440 if let Some(slider_status) =
441 state.dragging_status.as_mut()
442 {
443 slider_status.moved();
445 } else {
446 self.maybe_fire_on_release(shell);
447 }
448 }
449
450 return event::Status::Captured;
451 }
452 }
453 }
454 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
455 | Event::Touch(touch::Event::FingerPressed { .. }) => {
456 if layout.bounds().contains(cursor_position) {
457 let click =
458 mouse::Click::new(cursor_position, state.last_click);
459
460 match click.kind() {
461 mouse::click::Kind::Single => {
462 self.maybe_fire_on_grab(shell);
463
464 state.dragging_status = Some(Default::default());
465 state.prev_drag_y = cursor_position.y;
466 }
467 _ => {
468 let prev_dragging_status =
471 state.dragging_status.take();
472
473 if self.normal_param.value
474 != self.normal_param.default
475 {
476 if prev_dragging_status.is_none() {
477 self.maybe_fire_on_grab(shell);
478 }
479
480 self.normal_param.value =
481 self.normal_param.default;
482
483 self.fire_on_change(shell);
484
485 self.maybe_fire_on_release(shell);
486 } else if prev_dragging_status.is_some() {
487 self.maybe_fire_on_release(shell);
488 }
489 }
490 }
491
492 state.last_click = Some(click);
493
494 return event::Status::Captured;
495 }
496 }
497 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
498 | Event::Touch(touch::Event::FingerLifted { .. })
499 | Event::Touch(touch::Event::FingerLost { .. }) => {
500 if let Some(slider_status) = state.dragging_status.take() {
501 if self.on_grab.is_some() || slider_status.was_moved() {
502 self.maybe_fire_on_release(shell);
505 }
506
507 return event::Status::Captured;
508 }
509 }
510 Event::Keyboard(keyboard_event) => match keyboard_event {
511 keyboard::Event::KeyPressed { modifiers, .. } => {
512 state.pressed_modifiers = modifiers;
513
514 return event::Status::Captured;
515 }
516 keyboard::Event::KeyReleased { modifiers, .. } => {
517 state.pressed_modifiers = modifiers;
518
519 return event::Status::Captured;
520 }
521 keyboard::Event::ModifiersChanged(modifiers) => {
522 state.pressed_modifiers = modifiers;
523
524 return event::Status::Captured;
525 }
526 _ => {}
527 },
528 _ => {}
529 }
530
531 event::Status::Ignored
532 }
533
534 fn draw(
535 &self,
536 state: &Tree,
537 renderer: &mut Renderer,
538 theme: &Renderer::Theme,
539 _style: &iced_native::renderer::Style,
540 layout: Layout<'_>,
541 cursor_position: Point,
542 _viewport: &Rectangle,
543 ) {
544 let state = state.state.downcast_ref::<State>();
545 renderer.draw(
546 layout.bounds(),
547 cursor_position,
548 self.normal_param.value,
549 state.dragging_status.is_some(),
550 self.mod_range_1,
551 self.mod_range_2,
552 self.tick_marks,
553 self.text_marks,
554 theme,
555 &self.style,
556 &state.tick_marks_cache,
557 &state.text_marks_cache,
558 )
559 }
560}
561
562pub trait Renderer: iced_native::Renderer
569where
570 Self::Theme: StyleSheet,
571{
572 #[allow(clippy::too_many_arguments)]
586 fn draw(
587 &mut self,
588 bounds: Rectangle,
589 cursor_position: Point,
590 normal: Normal,
591 dragging_status: bool,
592 mod_range_1: Option<&ModulationRange>,
593 mod_range_2: Option<&ModulationRange>,
594 tick_marks: Option<&tick_marks::Group>,
595 text_marks: Option<&text_marks::Group>,
596 style_sheet: &dyn StyleSheet<
597 Style = <Self::Theme as StyleSheet>::Style,
598 >,
599 style: &<Self::Theme as StyleSheet>::Style,
600 tick_marks_cache: &crate::tick_marks::PrimitiveCache,
601 text_marks_cache: &crate::text_marks::PrimitiveCache,
602 );
603}
604
605impl<'a, Message, Renderer> From<VSlider<'a, Message, Renderer>>
606 for Element<'a, Message, Renderer>
607where
608 Message: 'a,
609 Renderer: 'a + self::Renderer,
610 Renderer::Theme: 'a + StyleSheet,
611{
612 fn from(
613 v_slider: VSlider<'a, Message, Renderer>,
614 ) -> Element<'a, Message, Renderer> {
615 Element::new(v_slider)
616 }
617}