1use crate::geom::{Rect, Vec2};
18use crate::input::UiEvent;
19use crate::key::Key;
20use crate::spec::AccessSpec;
21use crate::tree::OriginId;
22use crate::value::Value;
23
24#[derive(Clone, Copy, Debug, PartialEq)]
27pub struct SliderRange {
28 pub min: f64,
29 pub max: f64,
30 pub step: f64,
31 decimals: usize,
34}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub enum SliderMove {
39 Step(i32),
41 Page(i32),
43 Home,
45 End,
47}
48
49pub(crate) fn exact(v: f32) -> f64 {
52 format!("{v}").parse().unwrap_or(v as f64)
53}
54
55fn decimals_of(v: f64) -> usize {
57 let s = format!("{v}");
58 s.split_once('.').map_or(0, |(_, frac)| frac.len())
59}
60
61impl SliderRange {
62 pub fn of(ax: &AccessSpec) -> Option<Self> {
65 let min = exact(ax.value_min.unwrap_or(0.0));
66 let max = exact(ax.value_max.unwrap_or(100.0));
67 if !(min.is_finite() && max.is_finite()) || max <= min {
68 return None;
69 }
70 let step = match ax.value_step {
71 Some(s) => exact(s),
72 None => (max - min) / 100.0,
73 };
74 let decimals = decimals_of(min)
78 .max(decimals_of(max))
79 .max(decimals_of(step))
80 .min(9);
81 Some(SliderRange {
82 min,
83 max,
84 step,
85 decimals,
86 })
87 }
88
89 pub fn snap(&self, v: f64) -> f64 {
92 let v = v.clamp(self.min, self.max);
93 let k = ((v - self.min) / self.step).round();
94 let snapped = (self.min + k * self.step).min(self.max);
95 let snapped = if self.max - v < (v - snapped).abs() {
96 self.max
97 } else {
98 snapped
99 };
100 self.round(snapped)
101 }
102
103 fn round(&self, v: f64) -> f64 {
104 format!("{:.*}", self.decimals, v).parse().unwrap_or(v)
105 }
106
107 pub fn at_fraction(&self, f: f64) -> f64 {
109 self.snap(self.min + f.clamp(0.0, 1.0) * (self.max - self.min))
110 }
111
112 pub fn moved(&self, now: Option<f32>, mv: SliderMove) -> f64 {
115 let now = now.map_or(self.min, exact).clamp(self.min, self.max);
116 match mv {
117 SliderMove::Step(n) => self.snap(now + n as f64 * self.step),
118 SliderMove::Page(n) => self.snap(now + 10.0 * n as f64 * self.step),
119 SliderMove::Home => self.min,
120 SliderMove::End => self.max,
121 }
122 }
123}
124
125#[derive(Clone, Debug, PartialEq)]
129pub struct SliderTrack {
130 pub start: f32,
134 pub len: f32,
135 pub vertical: bool,
137 pub range: SliderRange,
138 pub tag: Value,
139}
140
141impl SliderTrack {
142 pub fn new(
145 rect: Rect,
146 padding: crate::geom::Edges,
147 vertical: bool,
148 range: SliderRange,
149 tag: Value,
150 ) -> Self {
151 let (start, len) = if vertical {
152 (rect.y + padding.t, rect.h - padding.y())
153 } else {
154 (rect.x + padding.l, rect.w - padding.x())
155 };
156 SliderTrack {
157 start,
158 len: len.max(0.0),
159 vertical,
160 range,
161 tag,
162 }
163 }
164
165 pub fn value_at(&self, p: Vec2) -> f64 {
167 if self.len <= 0.0 {
168 return self.range.min;
169 }
170 let along = if self.vertical { p.y } else { p.x };
171 let f = ((along - self.start) / self.len) as f64;
172 self.range
173 .at_fraction(if self.vertical { 1.0 - f } else { f })
174 }
175}
176
177pub fn change_event(origin: OriginId, key: Key, value: f64, phase: &str, tag: &Value) -> UiEvent {
179 let payload = Value::map([
180 ("kind", Value::str("change")),
181 ("value", Value::Float(value)),
182 ("phase", Value::str(phase)),
183 ]);
184 UiEvent::on(origin, key, payload).tagged(Some(tag))
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190 use crate::geom::Edges;
191
192 fn range(min: f32, max: f32, step: Option<f32>) -> SliderRange {
193 SliderRange::of(&AccessSpec {
194 value_min: Some(min),
195 value_max: Some(max),
196 value_step: step,
197 ..AccessSpec::EMPTY
198 })
199 .unwrap()
200 }
201
202 #[test]
203 fn a_step_lands_on_the_decimal_it_names() {
204 let r = range(0.0, 1.0, Some(0.1));
205 assert_eq!(r.moved(Some(0.2), SliderMove::Step(1)), 0.3);
206 assert_eq!(r.moved(Some(0.3), SliderMove::Step(-1)), 0.2);
207 assert_eq!(r.moved(Some(0.95), SliderMove::Step(1)), 1.0);
208 assert_eq!(r.moved(Some(0.0), SliderMove::Step(-1)), 0.0);
209 }
210
211 #[test]
212 fn the_default_step_is_a_hundredth_and_the_ends_are_aria_s() {
213 let r = SliderRange::of(&AccessSpec::EMPTY).unwrap();
214 assert_eq!((r.min, r.max, r.step), (0.0, 100.0, 1.0));
215 assert_eq!(r.moved(Some(41.0), SliderMove::Page(1)), 51.0);
216 assert_eq!(r.moved(None, SliderMove::End), 100.0);
217 assert_eq!(r.moved(Some(3.0), SliderMove::Home), 0.0);
218 }
219
220 #[test]
221 fn a_step_that_does_not_divide_the_range_still_reaches_the_top() {
222 let r = range(0.0, 10.0, Some(3.0));
223 assert_eq!(r.snap(9.9), 10.0);
224 assert_eq!(r.snap(8.0), 9.0);
225 assert_eq!(r.moved(Some(9.0), SliderMove::Step(1)), 10.0);
226 }
227
228 #[test]
229 fn a_range_that_is_not_one_is_none() {
230 let flat = AccessSpec {
231 value_min: Some(5.0),
232 value_max: Some(5.0),
233 ..AccessSpec::EMPTY
234 };
235 assert!(SliderRange::of(&flat).is_none());
236 }
237
238 #[test]
239 fn the_pointer_reads_the_content_box_and_a_column_runs_upward() {
240 let r = range(0.0, 100.0, Some(10.0));
241 let pad = Edges {
242 l: 8.0,
243 r: 8.0,
244 t: 8.0,
245 b: 8.0,
246 };
247 let t = SliderTrack::new(Rect::new(0.0, 0.0, 116.0, 16.0), pad, false, r, Value::Null);
248 assert_eq!(t.value_at(Vec2::new(8.0, 8.0)), 0.0);
249 assert_eq!(t.value_at(Vec2::new(58.0, 8.0)), 50.0);
250 assert_eq!(t.value_at(Vec2::new(-40.0, 8.0)), 0.0);
251 assert_eq!(t.value_at(Vec2::new(500.0, 8.0)), 100.0);
252 let v = SliderTrack::new(Rect::new(0.0, 0.0, 16.0, 116.0), pad, true, r, Value::Null);
253 assert_eq!(v.value_at(Vec2::new(8.0, 8.0)), 100.0);
254 assert_eq!(v.value_at(Vec2::new(8.0, 108.0)), 0.0);
255 }
256}