pixel_widgets/widget/
slider.rs

1use crate::draw::*;
2use crate::event::{Event, Key};
3use crate::layout::{Rectangle, Size};
4use crate::node::{GenericNode, IntoNode, Node};
5use crate::style::Stylesheet;
6use crate::widget::{dummy::Dummy, Context, Widget};
7
8/// Select a number using a sliding handle
9/// The handle can be styled using the `handle` child widget of this widget.
10pub struct Slider<'a, T, F> {
11    scrollbar: Node<'a, T>,
12    min: f32,
13    max: f32,
14    value: f32,
15    on_slide: F,
16}
17
18/// State for [`Slider`](struct.Slider.html)
19pub struct State {
20    inner: InnerState,
21    cursor_x: f32,
22    cursor_y: f32,
23}
24
25#[derive(Clone, Copy)]
26enum InnerState {
27    Idle,
28    Hover,
29    Drag(f32),
30}
31
32impl<'a, T: 'a, F: 'a + Fn(f32) -> T> Slider<'a, T, F> {
33    /// Construct a new `Slider`
34    pub fn new(min: f32, max: f32, value: f32, on_slide: F) -> Slider<'a, T, F> {
35        Self {
36            scrollbar: Dummy::new("handle").into_node(),
37            min,
38            max,
39            value: value.max(min).min(max),
40            on_slide,
41        }
42    }
43
44    /// Sets the minimum value of the slider.
45    pub fn min(mut self, min: f32) -> Self {
46        self.min = min;
47        self.value = self.value.max(min);
48        self
49    }
50
51    /// Sets the maximum value of the slider.
52    pub fn max(mut self, max: f32) -> Self {
53        self.max = max;
54        self.value = self.value.min(max);
55        self
56    }
57
58    /// Sets the current value of the slider.
59    pub fn val(mut self, value: f32) -> Self {
60        self.value = value.min(self.max).min(self.min);
61        self
62    }
63
64    /// Sets the on_slide callback of the slider, which is called when the value is changed.
65    pub fn on_slide<N: Fn(f32) -> T>(self, on_slide: N) -> Slider<'a, T, N> {
66        Slider {
67            scrollbar: self.scrollbar,
68            min: self.min,
69            max: self.max,
70            value: self.value,
71            on_slide,
72        }
73    }
74
75    fn scrollbar(&self, layout: Rectangle, style: &Stylesheet) -> Rectangle {
76        let content = style.background.content_rect(layout, style.padding);
77
78        let (handle_width, _) = self.scrollbar.size();
79        let handle_width = match handle_width {
80            Size::Shrink => content.width() * 0.1,
81            Size::Exact(x) => x,
82            Size::Fill(_) => content.width() * 0.1,
83        };
84
85        let mut t = (self.value - self.min) / (self.max - self.min);
86        t = t.max(0.0).min(1.0);
87
88        Rectangle {
89            left: content.left + (content.width() - handle_width) * t,
90            right: content.left + (content.width() - handle_width) * t + handle_width,
91            ..content
92        }
93    }
94}
95
96impl<'a, T: 'a> Default for Slider<'a, T, fn(f32) -> T> {
97    fn default() -> Self {
98        Self {
99            scrollbar: Dummy::new("handle").into_node(),
100            min: 0.0,
101            max: 1.0,
102            value: 0.0,
103            on_slide: |_| panic!("on_slide of `Slider` must be set"),
104        }
105    }
106}
107
108impl<'a, T: 'a, F: 'a + Send + Fn(f32) -> T> Widget<'a, T> for Slider<'a, T, F> {
109    type State = State;
110
111    fn mount(&self) -> Self::State {
112        State::default()
113    }
114
115    fn widget(&self) -> &'static str {
116        "slider"
117    }
118
119    fn len(&self) -> usize {
120        1
121    }
122
123    fn visit_children(&mut self, visitor: &mut dyn FnMut(&mut dyn GenericNode<'a, T>)) {
124        visitor(&mut *self.scrollbar);
125    }
126
127    fn size(&self, _: &State, style: &Stylesheet) -> (Size, Size) {
128        style
129            .background
130            .resolve_size((style.width, style.height), self.scrollbar.size(), style.padding)
131    }
132
133    fn event(
134        &mut self,
135        state: &mut State,
136        layout: Rectangle,
137        clip: Rectangle,
138        style: &Stylesheet,
139        event: Event,
140        context: &mut Context<T>,
141    ) {
142        let content_rect = style.background.content_rect(layout, style.padding);
143        let bar = self.scrollbar(layout, style);
144
145        match (event, state.inner) {
146            (Event::Cursor(cx, cy), InnerState::Drag(x)) => {
147                context.redraw();
148                state.cursor_x = cx;
149                state.cursor_y = cy;
150
151                let begin = content_rect.left;
152                let end = content_rect.right - bar.width();
153                let next_bar_left = (cx - x).max(begin).min(end);
154                let t = (next_bar_left - begin) / (end - begin);
155
156                self.value = self.min + t * (self.max - self.min);
157                context.push((self.on_slide)(self.value));
158            }
159            (Event::Cursor(x, y), _) => {
160                state.cursor_x = x;
161                state.cursor_y = y;
162                if bar.point_inside(x, y) && clip.point_inside(x, y) {
163                    state.inner = InnerState::Hover;
164                } else {
165                    state.inner = InnerState::Idle;
166                }
167            }
168            (Event::Press(Key::LeftMouseButton), InnerState::Hover) => {
169                state.inner = InnerState::Drag(state.cursor_x - bar.left);
170            }
171            (Event::Release(Key::LeftMouseButton), InnerState::Drag(_)) => {
172                if bar.point_inside(state.cursor_x, state.cursor_y) && clip.point_inside(state.cursor_x, state.cursor_y)
173                {
174                    state.inner = InnerState::Hover;
175                } else {
176                    state.inner = InnerState::Idle;
177                }
178            }
179            _ => (),
180        }
181    }
182
183    fn draw(&mut self, _: &mut State, layout: Rectangle, clip: Rectangle, style: &Stylesheet) -> Vec<Primitive<'a>> {
184        let mut result = Vec::new();
185        result.extend(style.background.render(layout));
186        let scrollbar = self.scrollbar(layout, style);
187        result.extend(self.scrollbar.draw(scrollbar, clip));
188        result
189    }
190}
191
192impl<'a, T: 'a, F: 'a + Send + Fn(f32) -> T> IntoNode<'a, T> for Slider<'a, T, F> {
193    fn into_node(self) -> Node<'a, T> {
194        Node::from_widget(self)
195    }
196}
197
198impl Default for State {
199    fn default() -> State {
200        State {
201            inner: InnerState::Idle,
202            cursor_x: 0.0,
203            cursor_y: 0.0,
204        }
205    }
206}