too/views/
slider.rs

1use core::f32;
2use std::ops::RangeInclusive;
3
4use crate::{
5    layout::Axis,
6    math::{denormalize, inverse_lerp, lerp, normalize, Pos2, Size, Space},
7    renderer::{Pixel, Rgba},
8    view::{
9        Builder, Elements, EventCtx, Handled, Interest, Layout, Palette, Render, StyleKind, Ui,
10        View, ViewEvent,
11    },
12};
13
14pub type SliderClass = fn(&Palette, Axis) -> SliderStyle;
15
16#[derive(Copy, Clone, Debug, PartialEq)]
17pub struct SliderStyle {
18    pub track_color: Rgba,
19    pub knob_color: Rgba,
20    pub track_hovered: Option<Rgba>,
21    pub knob_hovered: Option<Rgba>,
22    pub knob: char,
23    pub track: char,
24}
25
26impl SliderStyle {
27    pub fn default(palette: &Palette, axis: Axis) -> Self {
28        Self {
29            track_color: palette.outline,
30            knob_color: palette.primary,
31            track_hovered: None,
32            knob_hovered: None,
33            knob: axis.main((
34                Elements::MEDIUM_RECT, //
35                Elements::LARGE_RECT,
36            )),
37            track: axis.main((
38                Elements::THICK_HORIZONTAL_LINE,
39                Elements::THICK_VERTICAL_LINE,
40            )),
41        }
42    }
43
44    pub fn small_rounded(palette: &Palette, axis: Axis) -> Self {
45        Self {
46            knob: Elements::CIRCLE,
47            track: axis.main((Elements::HORIZONTAL_LINE, Elements::VERTICAL_LINE)),
48            ..Self::default(palette, axis)
49        }
50    }
51
52    pub fn small_diamond(palette: &Palette, axis: Axis) -> Self {
53        Self {
54            knob: Elements::DIAMOND,
55            track: axis.main((Elements::HORIZONTAL_LINE, Elements::VERTICAL_LINE)),
56            ..Self::default(palette, axis)
57        }
58    }
59
60    pub fn small_square(palette: &Palette, axis: Axis) -> Self {
61        Self {
62            knob: Elements::SMALL_RECT,
63            track: axis.main((Elements::HORIZONTAL_LINE, Elements::VERTICAL_LINE)),
64            ..Self::default(palette, axis)
65        }
66    }
67
68    pub fn large(palette: &Palette, axis: Axis) -> Self {
69        Self {
70            knob: Elements::LARGE_RECT,
71            track: Elements::MEDIUM_RECT,
72            ..Self::default(palette, axis)
73        }
74    }
75
76    pub fn large_filled(palette: &Palette, axis: Axis) -> Self {
77        Self {
78            knob: Elements::LARGE_RECT,
79            track: Elements::LARGE_RECT,
80            ..Self::default(palette, axis)
81        }
82    }
83}
84
85pub fn slider(value: &mut f32) -> Slider {
86    Slider {
87        value,
88        range: 0.0..=1.0,
89        clickable: true,
90        axis: Axis::Horizontal,
91        class: StyleKind::Deferred(SliderStyle::small_rounded),
92    }
93}
94
95// TODO step by
96#[must_use = "a view does nothing unless `show()` or `show_children()` is called"]
97pub struct Slider<'v> {
98    value: &'v mut f32,
99    range: RangeInclusive<f32>,
100    clickable: bool,
101    axis: Axis,
102    class: StyleKind<SliderClass, SliderStyle>,
103}
104
105impl<'v> Slider<'v> {
106    pub const fn range(mut self, range: RangeInclusive<f32>) -> Self {
107        self.range = range;
108        self
109    }
110
111    pub const fn clickable(mut self, clickable: bool) -> Self {
112        self.clickable = clickable;
113        self
114    }
115
116    pub const fn axis(mut self, axis: Axis) -> Self {
117        self.axis = axis;
118        self
119    }
120
121    pub const fn horizontal(self) -> Self {
122        self.axis(Axis::Horizontal)
123    }
124
125    pub const fn vertical(self) -> Self {
126        self.axis(Axis::Vertical)
127    }
128
129    pub const fn class(mut self, class: SliderClass) -> Self {
130        self.class = StyleKind::Deferred(class);
131        self
132    }
133
134    pub const fn style(mut self, style: SliderStyle) -> Self {
135        self.class = StyleKind::Direct(style);
136        self
137    }
138}
139
140impl<'v> Builder<'v> for Slider<'v> {
141    type View = SliderView;
142}
143
144#[derive(Debug)]
145pub struct SliderView {
146    value: f32,
147    changed: bool,
148    range: RangeInclusive<f32>,
149    clickable: bool,
150    axis: Axis,
151    class: StyleKind<SliderClass, SliderStyle>,
152}
153
154impl View for SliderView {
155    type Args<'v> = Slider<'v>;
156    type Response = (); // TODO `changed`
157
158    fn create(args: Self::Args<'_>) -> Self {
159        Self {
160            changed: false,
161            value: *args.value,
162            range: args.range.clone(),
163            clickable: args.clickable,
164            axis: args.axis,
165            class: args.class,
166        }
167    }
168
169    fn update(&mut self, args: Self::Args<'_>, _ui: &Ui) -> Self::Response {
170        self.range = args.range.clone();
171        self.clickable = args.clickable;
172        self.axis = args.axis;
173        self.class = args.class;
174
175        if std::mem::take(&mut self.changed) {
176            *args.value = self.value;
177        } else if self.value != *args.value {
178            self.value = *args.value;
179        }
180    }
181
182    fn interests(&self) -> Interest {
183        Interest::MOUSE
184    }
185
186    fn event(&mut self, event: ViewEvent, ctx: EventCtx) -> Handled {
187        let pos = match event {
188            ViewEvent::MouseDrag {
189                current,
190                inside: true,
191                ..
192            } => current,
193
194            ViewEvent::MouseClicked {
195                pos, inside: true, ..
196            } if self.clickable => pos,
197            _ => return Handled::Bubble,
198        };
199
200        let rect = ctx.rect();
201
202        let start = self.axis.main(rect.left_top());
203        let end = self.axis.main(rect.right_bottom() - 1);
204        let pos = self.axis.main(pos);
205
206        let value = inverse_lerp(start, end, pos).unwrap_or(0.0);
207        self.value = denormalize(value, self.range.clone());
208
209        self.changed = true;
210        Handled::Sink
211    }
212
213    fn layout(&mut self, _layout: Layout, space: Space) -> Size {
214        let main = self.axis.main((20.0, 1.0));
215        let size = self.axis.pack(main, 1.0);
216        space.fit(size)
217    }
218
219    fn draw(&mut self, mut render: Render) {
220        let style = match self.class {
221            StyleKind::Deferred(style) => (style)(render.palette, self.axis),
222            StyleKind::Direct(style) => style,
223        };
224
225        let track_color = if render.is_hovered() {
226            style.track_hovered.unwrap_or(style.track_color)
227        } else {
228            style.track_color
229        };
230
231        render.fill_with(Pixel::new(style.track).fg(track_color));
232
233        let extent: f32 = self.axis.main(render.rect().size());
234        let value = normalize(self.value, self.range.clone());
235        let x = lerp(0.0, extent - 1.0, value);
236        let pos: Pos2 = self.axis.pack(x, 0.0);
237
238        let knob_color = if render.is_hovered() {
239            style.knob_hovered.unwrap_or(style.knob_color)
240        } else {
241            style.knob_color
242        };
243
244        let pixel = Pixel::new(style.knob).fg(knob_color);
245        render.set(pos, pixel);
246    }
247}