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, 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#[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 = (); 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}