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