Skip to main content

ui/
value.rs

1// value -- decimal values, gradient tracks, angle dial and pan control (U1)
2//                                                                  [src/ui/ui_value.s]
3//
4//   slider_f(id, &mut v, &rf) -> EV           slider for an f64 (RangeF): EV_ACT while the
5//        value changes, EV_COMMIT on release and on key steps.  Keys (focused): arrows = one
6//        step, PgUp / PgDn = 10 steps, Home / End = minimum / maximum.
7//   track_slider(id, &mut v, &rg, track) -> EV   slider for an i32 (Range) whose track shows
8//        the colour gradient of a Track (TK_CHECKER: over a checkerboard, for alpha);
9//        horizontal or RG_VERT; same keys as slider_f
10//   prop_slider(id, label, &mut v, &rg, default) -> EV   paint.net property row: label over
11//        slider + number box + reset button (i32; default NO_DEFAULT = no reset button);
12//        EV_ACT while the slider is dragged or a number is typed (live preview), EV_COMMIT
13//        on release, Enter / focus loss, key steps and reset
14//   prop_slider_f(id, label, &mut v, &rf, default) -> EV   the same for an f64 (None = no
15//        reset button).  Parts: slider = id, number box = part_id(id, 1), reset =
16//        part_id(id, 2); the label selects the slider in scripts.
17//   angle(id, &mut deg, flags) -> EV          angle dial (f64 degrees, -180 .. 180 or ANG_360
18//        0 .. 360, counter-clockwise, 0 = right): drag sets the angle, Shift snaps to 15
19//        degrees; keys: arrows 1 (Shift 15), PgUp / PgDn 15, Home 0.  Natural 64 DIP.
20//        Semantic value = degrees * 100.
21//   pan(id, &mut x, &mut y, thumb, ver, flags) -> EV   pan control: a thumbnail (surface or
22//        None, ver = its version) with a crosshair; drag sets the point (f64, -1 .. 1,
23//        PAN_UNIT 0 .. 1; y down); arrows move 1 % (Shift 10 %), Home centres.  Semantic
24//        value = x * 1000, max = y * 1000.
25//   part_id(id, k) -> id                      id of part k (0..15) of a composite widget
26//   fmt_f64(&v, decimals) -> String           "-12,50" with the decimal separator
27//   parse_f64(s) -> Some(v) / None            accepts ',' and '.' as decimal separator
28//   decimal_sep() / set_decimal_sep(c)        separator shown (default ',')
29// Shared with the styles and ui_color / ui_wheel: grad_at, px_mix, deg_atan2, deg_sincos,
30// Ui::cmd_track (the cmd_custom of a gradient track: track_cb / track_hash of the asm),
31// Ui::slider_track_draw; internal: rf_fix, rf_scaled, rf_t16, sld_*, vl_state, f_round.
32//
33// No per-window state: everything lives in the app's variables and in state slots.  The
34// decimal separator (ui_decimal_sep) and the cache of gradient track drawings are shared.
35
36use alloc::rc::Rc;
37use alloc::string::String;
38use alloc::vec::Vec;
39
40use super::cmd::{Draw, DrawRef, Gfx};
41use super::consts::*;
42use super::keys::*;
43use super::{Sp, Ui};
44use gfx::Surface;
45use rt::fx::{cvtt, cvttd, cvttd64, maxsd, minsd};
46/// cvtsd2si (32-bit, round to nearest even) for the styles' geometry
47pub use rt::fx::cvtd;
48
49const TRK_POOL: usize = 64; // cached gradient drawings (the asm's TRK_POOL ring)
50const PROP_NB_W: i32 = 88; // number box width in a property row (DIP)
51/// prop_slider: no reset button
52pub const NO_DEFAULT: i32 = i32::MIN;
53
54static POW10: [f64; 19] = [
55    1.0, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, 1.0e7, 1.0e8, 1.0e9, 1.0e10, 1.0e11, 1.0e12, 1.0e13,
56    1.0e14, 1.0e15, 1.0e16, 1.0e17, 1.0e18,
57];
58const RAD2DEG: f64 = 57.29577951308232;
59const DEG2RAD: f64 = 0.017453292519943295;
60
61/// RANGE_F min, max, step, decimals, flags -- descriptor of an f64 value
62#[derive(Clone, Copy, Default, Debug)]
63pub struct RangeF {
64    pub min: f64,
65    pub max: f64,
66    pub step: f64,
67    pub dec: u32,
68    pub flags: u32,
69}
70
71impl RangeF {
72    pub const fn new(min: f64, max: f64, step: f64, dec: u32, flags: u32) -> RangeF {
73        RangeF { min, max, step, dec, flags }
74    }
75}
76
77/// TRACK -- the colour gradient of track_slider: TK_* flags and 1..TK_MAXN straight-ARGB
78/// stops; with TK_CUSTOM the track is drawn by `custom` (draw object, user value, version:
79/// bump it when the drawing changes) instead
80#[derive(Clone, Default)]
81pub struct Track {
82    pub flags: u32,
83    pub n: u32,
84    pub stops: [u32; TK_MAXN],
85    pub custom: Option<(DrawRef, usize, u32)>,
86}
87
88impl Track {
89    /// TRACK flags, argb0, argb1, ... (at most TK_MAXN stops)
90    pub fn new(flags: u32, stops: &[u32]) -> Track {
91        let mut t = Track { flags, n: stops.len().min(TK_MAXN) as u32, ..Track::default() };
92        t.stops[..t.n as usize].copy_from_slice(&stops[..t.n as usize]);
93        t
94    }
95}
96
97/// shared state (ui_decimal_sep, the gradient drawings of the TRK_POOL ring)
98pub(crate) struct S {
99    pub(crate) decimal_sep: u32,
100    trk: Vec<(u64, Rc<TrackDraw>)>,
101    trk_next: usize,
102}
103
104impl Default for S {
105    fn default() -> S {
106        S { decimal_sep: 44, trk: Vec::new(), trk_next: 0 }
107    }
108}
109
110// ------------------------------------------------------------------ small helpers
111
112/// ui_part_id(id, k) -> PART_BASE | (id << 4 | k) & 0x0FFFFFFF
113pub fn part_id(id: u32, k: u32) -> u32 {
114    ((id << 4) | (k & 15)) & 0x0FFF_FFFF | PART_BASE
115}
116
117/// vl_state(ev, disabled) -> 0 rest, 1 hover, 2 pressed (held), 3 disabled
118pub(crate) fn vl_state(ev: u32, disabled: bool) -> u32 {
119    if disabled {
120        3
121    } else if ev & EV_HELD != 0 {
122        2
123    } else if ev & EV_HOVER != 0 {
124        1
125    } else {
126        0
127    }
128}
129
130/// f_round(x) -> nearest integer, halves away from zero (cvttsd2si of x +- 0.5)
131pub(crate) fn f_round(x: f64) -> i64 {
132    if x >= 0.0 { cvttd64(x + 0.5) } else { cvttd64(x - 0.5) }
133}
134
135// f_floor(x) -> floor (|x| < 2^62): truncate, step down when that went up
136fn f_floor(x: f64) -> f64 {
137    let mut t = cvttd64(x);
138    if (t as f64) > x {
139        t = t.wrapping_sub(1);
140    }
141    t as f64
142}
143
144// rf_dec(rf) -> decimals clamped to 0..6
145fn rf_dec(rf: &RangeF) -> usize {
146    rf.dec.min(6) as usize
147}
148
149/// rf_fix(rf, v) -> NaN -> min, clamped to min..max, snapped to min + k * step (step > 0),
150/// rounded to the range's decimals, -0 -> +0
151pub(crate) fn rf_fix(rf: &RangeF, v: f64) -> f64 {
152    let mut x = if v.is_nan() { rf.min } else { v };
153    x = maxsd(x, rf.min);
154    x = minsd(x, rf.max);
155    if rf.step > 0.0 {
156        x = x - rf.min;
157        x = x / rf.step;
158        x = f_round(x) as f64;
159        x = x * rf.step;
160        x = x + rf.min;
161        x = minsd(x, rf.max);
162    }
163    let p = POW10[rf_dec(rf)];
164    x = x * p;
165    x = f_round(x) as f64;
166    x = x / p;
167    x = maxsd(x, rf.min);
168    x = minsd(x, rf.max);
169    x + 0.0
170}
171
172/// rf_scaled(rf, v) -> round(v * 10^decimals), saturated to i32
173pub(crate) fn rf_scaled(rf: &RangeF, v: f64) -> i32 {
174    let r = f_round(v * POW10[rf_dec(rf)]);
175    r.clamp(i32::MIN as i64, i32::MAX as i64) as i32
176}
177
178/// rf_t16(rf, v) -> (v - min) / (max - min) as 0..65536
179pub(crate) fn rf_t16(rf: &RangeF, v: f64) -> i32 {
180    let x = v - rf.min;
181    let d = rf.max - rf.min;
182    if !(d > 0.0) {
183        return 0;
184    }
185    let r = f_round(x / d * 65536.0);
186    if r < 0 {
187        0
188    } else if r > 65536 {
189        65536
190    } else {
191        r as i32
192    }
193}
194
195// rf_from_t16(rf, t) -> min + t / 65536 * (max - min), fixed with rf_fix
196fn rf_from_t16(rf: &RangeF, t: i32) -> f64 {
197    let mut x = t as f64 / 65536.0;
198    let d = rf.max - rf.min;
199    x = x * d;
200    x = x + rf.min;
201    rf_fix(rf, x)
202}
203
204/// deg_atan2(y, x) -> degrees in (-180, 180] (x87 fpatan)
205pub fn deg_atan2(y: f64, x: f64) -> f64 {
206    rt::x87::atan2(y, x) * RAD2DEG
207}
208
209/// deg_sincos(degrees) -> (cos, sin) (x87 fsincos)
210pub fn deg_sincos(d: f64) -> (f64, f64) {
211    rt::x87::sincos(d * DEG2RAD)
212}
213
214// utf8_encode(cp) -> the bytes of one code point (no checks, as the asm)
215fn utf8_push(out: &mut Vec<u8>, c: u32) {
216    if c < 0x80 {
217        out.push(c as u8);
218    } else if c < 0x800 {
219        out.push(0xC0 | (c >> 6) as u8);
220        out.push(0x80 | (c & 63) as u8);
221    } else if c < 0x10000 {
222        out.push(0xE0 | (c >> 12) as u8);
223        out.push(0x80 | ((c >> 6) & 63) as u8);
224        out.push(0x80 | (c & 63) as u8);
225    } else {
226        out.push(0xF0 | ((c >> 18) & 7) as u8);
227        out.push(0x80 | ((c >> 12) & 63) as u8);
228        out.push(0x80 | ((c >> 6) & 63) as u8);
229        out.push(0x80 | (c & 63) as u8);
230    }
231}
232
233/// fmt_f64 with an explicit separator (ui_fmt_f64)
234pub(crate) fn fmt_f64_sep(v: f64, decimals: u32, sep: u32) -> String {
235    let dec = decimals.min(6) as usize;
236    let x = if v.is_nan() { 0.0 } else { v };
237    let mut r = f_round(x * POW10[dec]) as u64;
238    let mut out = Vec::with_capacity(32);
239    if (r as i64) < 0 {
240        out.push(b'-');
241        r = (r as i64).wrapping_neg() as u64;
242    }
243    // digits, reversed: at least decimals + 1, at most 24
244    let mut dig = [0u8; 32];
245    let mut n = 0;
246    loop {
247        dig[n] = b'0' + (r % 10) as u8;
248        r /= 10;
249        n += 1;
250        if n >= 24 {
251            break;
252        }
253        if r != 0 || n <= dec {
254            continue;
255        }
256        break;
257    }
258    let mut i = n;
259    for _ in 0..n - dec {
260        i -= 1;
261        out.push(dig[i]);
262    }
263    if dec != 0 {
264        utf8_push(&mut out, sep);
265        while i > 0 {
266            i -= 1;
267            out.push(dig[i]);
268        }
269    }
270    String::from_utf8_lossy(&out).into_owned()
271}
272
273/// parse_f64 with an explicit separator (ui_parse_f64)
274pub(crate) fn parse_f64_sep(s: &[u8], sep: u32) -> Option<f64> {
275    let at = |i: usize| if i < s.len() { s[i] } else { 0 };
276    let mut i = 0;
277    while at(i) == b' ' {
278        i += 1;
279    }
280    let mut neg = false;
281    if at(i) == b'-' {
282        neg = true;
283        i += 1;
284    } else if at(i) == b'+' {
285        i += 1;
286    }
287    let mut m: u64 = 0; // mantissa
288    let mut digits = 0; // digits seen
289    let mut frac = 0usize; // fraction digits kept
290    let mut dropped = 0usize; // integer digits dropped (too long)
291    let mut seen_sep = false;
292    loop {
293        let c = at(i);
294        if c.is_ascii_digit() {
295            digits += 1;
296            i += 1;
297            if m >= 100_000_000_000_000_000 {
298                if !seen_sep {
299                    dropped += 1; // extra fraction digits: ignored
300                }
301            } else {
302                m = m * 10 + (c - b'0') as u64;
303                if seen_sep {
304                    frac += 1;
305                }
306            }
307        } else if c == b',' || c == b'.' || c as u32 == sep {
308            if seen_sep {
309                break;
310            }
311            seen_sep = true;
312            i += 1;
313        } else {
314            break;
315        }
316    }
317    if digits == 0 {
318        return None;
319    }
320    let mut x = m as i64 as f64;
321    x = x / POW10[frac.min(18)];
322    x = x * POW10[dropped.min(18)];
323    if neg {
324        x = 0.0 - x;
325    }
326    Some(x)
327}
328
329// ------------------------------------------------------------------ tracks and gradients
330
331/// grad_at(track, t) -> colour (straight ARGB) of the gradient at t (0..65536)
332pub fn grad_at(tk: &Track, t: i32) -> u32 {
333    let n = tk.n.min(TK_MAXN as u32);
334    if n == 0 {
335        return 0;
336    }
337    if n == 1 {
338        return tk.stops[0];
339    }
340    let t = if t < 0 { 0 } else if t as u32 > 65536 { 65536 } else { t };
341    let segs = n - 1;
342    let pos = (t as u32).wrapping_mul(segs); // 16.16
343    let mut seg = pos >> 16;
344    let mut f = (pos & 0xFFFF) as i32;
345    if seg >= segs {
346        seg = segs - 1; // t = 1: the end of the last segment
347        f = 0x10000;
348    }
349    let c0 = tk.stops[seg as usize];
350    let c1 = tk.stops[seg as usize + 1];
351    let mut r = 0u32;
352    for sh in [0, 8, 16, 24] {
353        let a = ((c0 >> sh) & 255) as i32;
354        let b = ((c1 >> sh) & 255) as i32;
355        let v = a + ((b - a).wrapping_mul(f) >> 16);
356        r |= (v as u32) << sh;
357    }
358    r
359}
360
361/// px_mix(dst, src, k) -> per channel (dst * (255 - k) + src * k) / 255 of 0x..RRGGBB
362/// (alpha byte 0)
363pub fn px_mix(dst: u32, src: u32, k: u32) -> u32 {
364    let ik = 255u32.wrapping_sub(k);
365    let mut r = 0;
366    for sh in [0, 8, 16] {
367        let mut v = ((dst >> sh) & 255).wrapping_mul(ik).wrapping_add(((src >> sh) & 255).wrapping_mul(k)).wrapping_add(128);
368        v = (v.wrapping_add(v >> 8)) >> 8;
369        r |= v << sh;
370    }
371    r
372}
373
374// track_hash(track) -> 32-bit hash of the record (flags, n, stops): FNV-1a 64, folded
375fn track_hash(tk: &Track) -> u32 {
376    let n = tk.n.min(TK_MAXN as u32) as usize;
377    let mut h: u64 = 0xCBF2_9CE4_8422_2325;
378    let mut eat = |v: u32| {
379        for b in v.to_le_bytes() {
380            h ^= b as u64;
381            h = h.wrapping_mul(0x0000_0100_0000_01B3);
382        }
383    };
384    eat(tk.flags);
385    eat(tk.n);
386    for &s in &tk.stops[..n] {
387        eat(s);
388    }
389    (h as u32) ^ (h >> 32) as u32
390}
391
392/// the render callback of a gradient track (track_cb): the colours along the rectangle (the
393/// long side), rounded antialiased ends unless TK_RECT, over a checkerboard with TK_CHECKER;
394/// drawn straight into the target, inside its clip.  `cell` = the checker cell (dp(4) when
395/// the command was recorded).
396pub(crate) struct TrackDraw {
397    tk: Track,
398    cell: i32,
399}
400
401impl Draw for TrackDraw {
402    fn draw(&self, g: &mut Gfx, r: [i32; 4]) {
403        let tk = &self.tk;
404        let k = g.dc.clip;
405        // clip = clip ∩ rect
406        let cx0 = k[0].max(r[0]);
407        let cy0 = k[1].max(r[1]);
408        let cx1 = k[2].min(r[0].wrapping_add(r[2]));
409        let cy1 = k[3].min(r[1].wrapping_add(r[3]));
410        let flags = tk.flags;
411        // geometry
412        let vert = r[3] > r[2];
413        let (thick, len) = if vert { (r[2], r[3]) } else { (r[3], r[2]) };
414        let rad = thick as f32 * 0.5;
415        let inset = if flags & TK_RECT != 0 { 0 } else { ((thick as u32) >> 1) as i32 };
416        let travel = len.wrapping_sub(inset + inset).max(1);
417        let cell = self.cell as u32;
418        let stride = g.sf.stride as usize;
419        let mut y = cy0;
420        while y < cy1 {
421            let row = y as usize * stride;
422            let mut x = cx0;
423            while x < cx1 {
424                // u along the track, v across
425                let (mut u, mut v) = (x.wrapping_sub(r[0]), y.wrapping_sub(r[1]));
426                if vert {
427                    let uu = r[3].wrapping_sub(1).wrapping_sub(v);
428                    v = u;
429                    u = uu;
430                }
431                // t of the column
432                let t = ((((u.wrapping_sub(inset)) as i64) << 16) / travel as i64) as i32;
433                let c = grad_at(tk, t);
434                // coverage of the rounded ends (0..255)
435                let mut cov: u32 = 255;
436                let mut skip = false;
437                if flags & TK_RECT == 0 {
438                    let fu = u as f32 + 0.5;
439                    let mut centre = rad;
440                    let mut cap = !(fu >= rad); // jb: below or unordered
441                    if !cap {
442                        let e = len as f32 - rad;
443                        if !(fu <= e) {
444                            cap = true;
445                            centre = e;
446                        }
447                    }
448                    if cap {
449                        let du = fu - centre;
450                        let dv = v as f32 + 0.5 - rad;
451                        let d = rt::fx::sqrt(du * du + dv * dv);
452                        let kk = cvtt((rad + 0.5 - d) * 255.0);
453                        if kk <= 0 {
454                            skip = true;
455                        } else {
456                            cov = kk.min(255) as u32;
457                        }
458                    }
459                }
460                if !skip {
461                    let a = c >> 24;
462                    let (src, kk) = if flags & TK_CHECKER != 0 {
463                        // checker cell colour, then the gradient over it
464                        let odd = ((u as u32) / cell).wrapping_add((v as u32) / cell) & 1;
465                        let bg = if odd != 0 { 0xCC_CCCC } else { 0xFF_FFFF };
466                        (px_mix(bg, c, a), cov)
467                    } else {
468                        (c, (a * cov + 127) / 255)
469                    };
470                    let o = row + x as usize * 4;
471                    let px = u32::from_le_bytes([g.sf.px[o], g.sf.px[o + 1], g.sf.px[o + 2], g.sf.px[o + 3]]);
472                    let out = px_mix(px, src, kk);
473                    g.sf.px[o..o + 4].copy_from_slice(&out.to_le_bytes());
474                }
475                x += 1;
476            }
477            y += 1;
478        }
479    }
480}
481
482impl Ui {
483    /// cmd_custom of a value slider's gradient track over (x, y, w, h): the track's own
484    /// drawing (TK_CUSTOM) or the gradient (track_cb, version = track_hash)  [styles' value.s]
485    pub fn cmd_track(&mut self, x: i32, y: i32, w: i32, h: i32, tk: &Track) {
486        if tk.flags & TK_CUSTOM != 0 {
487            let (f, user, ver) = match &tk.custom {
488                Some((f, u, v)) => (Some(f.clone()), *u, *v),
489                None => (None, 0, 0),
490            };
491            self.cmd_custom(x, y, w, h, f, user, ver);
492            return;
493        }
494        let ver = track_hash(tk);
495        let f = self.track_drawing(tk);
496        self.cmd_custom(x, y, w, h, Some(f), 0, ver);
497    }
498
499    // the drawing of a gradient (trk_alloc + track_cb): one shared object per content and
500    // checker cell, so an unchanged track keeps its command hash
501    fn track_drawing(&mut self, tk: &Track) -> DrawRef {
502        let cell = self.dp(4).max(1);
503        let mut t = tk.clone();
504        t.custom = None;
505        let key = ((track_hash(&t) as u64) << 32) | cell as u32 as u64;
506        let s = &mut self.m.value;
507        let same = |d: &TrackDraw| d.cell == cell && d.tk.flags == t.flags && d.tk.n == t.n && d.tk.stops == t.stops;
508        if let Some((_, d)) = s.trk.iter().find(|(k, d)| *k == key && same(d)) {
509            return d.clone();
510        }
511        let d = Rc::new(TrackDraw { tk: t, cell });
512        if s.trk.len() < TRK_POOL {
513            s.trk.push((key, d.clone()));
514        } else {
515            let i = s.trk_next % TRK_POOL;
516            s.trk[i] = (key, d.clone());
517            s.trk_next = i + 1;
518        }
519        d
520    }
521
522    // ------------------------------------------------------------------ decimal separator
523
524    /// ui_decimal_sep: the separator fmt_f64 shows (and parse_f64 accepts besides ',' / '.')
525    pub fn decimal_sep(&self) -> u32 {
526        self.m.value.decimal_sep
527    }
528
529    /// set ui_decimal_sep (a code point)
530    pub fn set_decimal_sep(&mut self, c: u32) {
531        self.m.value.decimal_sep = c;
532    }
533
534    /// ui_fmt_f64(&v, decimals) -> fixed decimals with the decimal separator; -0 is shown as 0
535    pub fn fmt_f64(&self, v: f64, decimals: u32) -> String {
536        fmt_f64_sep(v, decimals, self.m.value.decimal_sep)
537    }
538
539    /// ui_parse_f64(s) -> the value, or None when s has no digits.  Blanks, a sign, digits,
540    /// one ',' or '.' (or the decimal separator when ASCII), digits.
541    pub fn parse_f64(&self, s: &str) -> Option<f64> {
542        parse_f64_sep(s.as_bytes(), self.m.value.decimal_sep)
543    }
544
545    // ------------------------------------------------------------------ slider building blocks
546
547    // vl_disabled: the last behave reported a disabled widget
548    fn vl_disabled(&self) -> bool {
549        self.wp.ctx.last_state == SS_DISABLED as u32
550    }
551
552    /// sld_place(rgflags): place a value slider at the style's natural size
553    pub(crate) fn sld_place(&mut self, rgflags: u32) {
554        let st = self.style;
555        let mut sp = Sp { flags: rgflags, ..Sp::default() };
556        let (w, h) = st.value_slider_size(self, &mut sp);
557        self.place(w, h);
558    }
559
560    /// sld_behave(id, stflags) -> EV: behave with focus and hand cursor (ST_DISABLED,
561    /// ST_NOFOCUS honoured)
562    pub(crate) fn sld_behave(&mut self, id: u32, stflags: u32) -> u32 {
563        let mut hb = HB_FOCUS | CUR_HAND << HB_CURSOR_SHIFT;
564        if stflags & ST_NOFOCUS != 0 {
565            hb &= !HB_FOCUS;
566        }
567        if stflags & ST_DISABLED != 0 {
568            hb |= HB_DISABLED;
569        }
570        self.behave(id, hb)
571    }
572
573    /// sld_ptr_t(rgflags) -> 0..65536: the pointer's place on the thumb travel
574    pub(crate) fn sld_ptr_t(&mut self, rgflags: u32) -> i32 {
575        let st = self.style;
576        let thumb = st.value_slider_thumb(self);
577        let half = ((thumb as u32) >> 1) as i32;
578        let r = self.wp.ctx.last_rect;
579        let (mx, my) = (self.wp.ctx.mx, self.wp.ctx.my);
580        let (mut p, len) = if rgflags & RG_VERT == 0 {
581            (mx.wrapping_sub(r[0]).wrapping_sub(half), r[2])
582        } else {
583            (r[1].wrapping_add(r[3]).wrapping_sub(half).wrapping_sub(my), r[3])
584        };
585        let len = len.wrapping_sub(thumb).max(1);
586        if p < 0 {
587            p = 0;
588        }
589        if p > len {
590            p = len;
591        }
592        (((p as i64) << 16) / len as i64) as i32
593    }
594
595    /// sld_keys() -> (signed steps (arrows 1, PgUp / PgDn 10), 0 / 1 Home / 2 End)
596    pub(crate) fn sld_keys(&mut self) -> (i32, u32) {
597        let mut steps = 0i32;
598        steps += self.key(VK_RIGHT) as i32;
599        steps += self.key(VK_UP) as i32;
600        steps -= self.key(VK_LEFT) as i32;
601        steps -= self.key(VK_DOWN) as i32;
602        steps += self.key(VK_PRIOR) as i32 * 10;
603        steps -= self.key(VK_NEXT) as i32 * 10;
604        let mut he = 0;
605        if self.key(VK_HOME) {
606            he = 1;
607        }
608        if self.key(VK_END) {
609            he = 2;
610        }
611        if steps != 0 || he != 0 {
612            self.wp.ctx.focus_visible = true;
613        }
614        (steps, he)
615    }
616
617    /// sld_draw(t, state, rgflags, track): the style paints track and thumb of a value slider
618    /// on last_rect; t = 0..65536 along the thumb travel; None = the standard accent track
619    pub(crate) fn sld_draw(&mut self, t: i32, state: u32, rgflags: u32, track: Option<&Track>) {
620        let st = self.style;
621        let mut sp = Sp { value: t, state, flags: rgflags, track, ..Sp::default() };
622        sp.place(self.wp.ctx.last_rect);
623        st.value_slider(self, &sp);
624    }
625
626    // ------------------------------------------------------------------ slider_f
627
628    /// ui_slider_f(id, &mut v, &rf) -> EV_*
629    #[cfg_attr(jfgui_dev, track_caller)]
630    pub fn slider_f(&mut self, id: u32, v: &mut f64, rf: &RangeF) -> u32 {
631        let _src = self.src_scope();
632        let old = v.to_bits();
633        let stflags = self.take_flags();
634        let rgf = rf.flags;
635        let mut keych = false;
636        self.sld_place(rgf);
637        let mut ev = self.sld_behave(id, stflags);
638        self.touch_grab(if rgf & RG_VERT != 0 { super::touch::AX_V } else { super::touch::AX_H });
639        if ev & EV_HELD != 0 {
640            let t = self.sld_ptr_t(rgf);
641            *v = rf_from_t16(rf, t);
642        } else if ev & EV_FOCUS != 0 {
643            let (steps, he) = self.sld_keys();
644            if he == 1 {
645                *v = rf.min;
646                keych = true;
647            } else if he == 2 {
648                *v = rf.max;
649                keych = true;
650            } else if steps != 0 {
651                keych = true;
652                // step (0: 1 % of the range)
653                let st = if rf.step > 0.0 { rf.step } else { (rf.max - rf.min) * 0.01 };
654                *v = steps as f64 * st + *v;
655            }
656        }
657        // a value a screen reader set (a11y.rs; scaled like the record's value)
658        if let Some(x) = self.a11y_value(id) {
659            *v = x as f64 / POW10[rf_dec(rf)];
660            keych = true;
661        }
662        *v = rf_fix(rf, *v);
663        if v.to_bits() != old {
664            ev |= EV_ACT;
665            if keych {
666                ev |= EV_COMMIT;
667            }
668            self.repass();
669        }
670        let t = rf_t16(rf, *v);
671        let state = vl_state(ev, self.vl_disabled());
672        self.sld_draw(t, state, rgf, None);
673        self.focus_ring_draw(ev);
674        let val = rf_scaled(rf, *v);
675        let r = self.sem(K_SLIDER, id, None, val);
676        r.max = rf_scaled(rf, rf.max);
677        r.min = rf_scaled(rf, rf.min);
678        ev
679    }
680
681    // ------------------------------------------------------------------ track_slider
682
683    /// ui_track_slider(id, &mut v, &rg, track) -> EV_*: i32 slider with a gradient track
684    /// (None = the standard look)
685    #[cfg_attr(jfgui_dev, track_caller)]
686    pub fn track_slider(&mut self, id: u32, v: &mut i32, rg: &Range, track: Option<&Track>) -> u32 {
687        let _src = self.src_scope();
688        let old = *v;
689        let stflags = self.take_flags();
690        let rgf = rg.flags;
691        let mut keych = false;
692        self.sld_place(rgf);
693        let mut ev = self.sld_behave(id, stflags);
694        self.touch_grab(if rgf & RG_VERT != 0 { super::touch::AX_V } else { super::touch::AX_H });
695        if ev & EV_HELD != 0 {
696            let t = self.sld_ptr_t(rgf);
697            // v = min + round(t * (max - min) / 65536), snapped to min + k * step
698            let span = rg.max.wrapping_sub(rg.min) as i64;
699            let mut x = ((t as i64).wrapping_mul(span).wrapping_add(32768) >> 16) as i32;
700            x = x.wrapping_add(rg.min);
701            if rg.step > 1 {
702                let half = ((rg.step as u32) >> 1) as i32;
703                x = (x.wrapping_sub(rg.min).wrapping_add(half) / rg.step).wrapping_mul(rg.step).wrapping_add(rg.min);
704            }
705            *v = x;
706        } else if ev & EV_FOCUS != 0 {
707            let (steps, he) = self.sld_keys();
708            if he == 1 {
709                *v = rg.min;
710                keych = true;
711            } else if he == 2 {
712                *v = rg.max;
713                keych = true;
714            } else if steps != 0 {
715                keych = true;
716                let st = rg.step.max(1);
717                *v = v.wrapping_add(steps.wrapping_mul(st));
718            }
719        }
720        // a value a screen reader set (a11y.rs)
721        if let Some(x) = self.a11y_value(id) {
722            *v = x;
723            keych = true;
724        }
725        let mut x = *v;
726        if x < rg.min {
727            x = rg.min;
728        }
729        if x > rg.max {
730            x = rg.max;
731        }
732        *v = x;
733        if x != old {
734            ev |= EV_ACT;
735            if keych {
736                ev |= EV_COMMIT;
737            }
738            self.repass();
739        }
740        // t = (v - min) * 65536 / (max - min)
741        let span = rg.max.wrapping_sub(rg.min);
742        let t: i32 = if span > 0 { ((((v.wrapping_sub(rg.min)) as i64) << 16) / span as i64) as i32 } else { 0 };
743        let state = vl_state(ev, self.vl_disabled());
744        self.sld_draw(t, state, rgf, track);
745        self.focus_ring_draw(ev);
746        let r = self.sem(K_SLIDER, id, None, *v);
747        r.max = rg.max;
748        r.min = rg.min;
749        ev
750    }
751
752    // ------------------------------------------------------------------ property rows
753
754    // prop_begin(label) -> the label of the text record (for the slider): the row's boxes,
755    // BOX_V (label, 4 DIP) and a BOX_H with centred children (8 DIP)
756    fn prop_begin(&mut self, label: &str) -> Option<String> {
757        self.box_begin(0, BOX_V, 4);
758        self.text(TS_BODY, label);
759        let l = self.sems.last().and_then(|s| s.label.clone());
760        self.box_begin(0, BOX_H | BOX_XCENTER, 8);
761        self.next_size(-1, 0);
762        l
763    }
764
765    // set_last_label(label): give the last semantic record this label
766    fn set_last_label(&mut self, l: Option<String>) {
767        if let Some(s) = self.sems.last_mut() {
768            s.label = l;
769        }
770    }
771
772    // prop_reset(id, same) -> EV of the reset button (disabled while the value is the default)
773    fn prop_reset(&mut self, id: u32, same: bool) -> u32 {
774        if same {
775            self.next_flags(ST_DISABLED);
776        }
777        self.icon_button(part_id(id, 2), IC_UNDO, crate::i18n::tr("Zurücksetzen"))
778    }
779
780    /// ui_prop_slider(id, label, &mut v, &rg, default) -> EV_* (default NO_DEFAULT: no reset
781    /// button)
782    #[cfg_attr(jfgui_dev, track_caller)]
783    pub fn prop_slider(&mut self, id: u32, label: &str, v: &mut i32, rg: &Range, default: i32) -> u32 {
784        let _src = self.src_scope();
785        let l = self.prop_begin(label);
786        let mut ev = self.slider(id, v, rg);
787        self.set_last_label(l);
788        self.next_size(PROP_NB_W, 0);
789        let nb = part_id(id, 1);
790        ev |= self.number_box(nb, v, rg);
791        if self.nb_live(nb, v) {
792            // typing previews the value
793            ev |= EV_ACT;
794            self.repass();
795        }
796        if default != NO_DEFAULT {
797            let same = *v == default;
798            if self.prop_reset(id, same) & EV_ACT != 0 {
799                *v = default;
800                ev |= EV_ACT | EV_COMMIT;
801                self.repass();
802            }
803        }
804        self.box_end();
805        self.box_end();
806        ev
807    }
808
809    /// ui_prop_slider_f(id, label, &mut v, &rf, default) -> EV_* (default None: no reset
810    /// button)
811    #[cfg_attr(jfgui_dev, track_caller)]
812    pub fn prop_slider_f(&mut self, id: u32, label: &str, v: &mut f64, rf: &RangeF, default: Option<f64>) -> u32 {
813        let _src = self.src_scope();
814        let l = self.prop_begin(label);
815        let mut ev = self.slider_f(id, v, rf);
816        self.set_last_label(l);
817        self.next_size(PROP_NB_W, 0);
818        let nb = part_id(id, 1);
819        ev |= self.number_box_f(nb, v, rf);
820        if self.nb_live_f(nb, v) {
821            // typing previews the value
822            ev |= EV_ACT;
823            self.repass();
824        }
825        if let Some(d) = default {
826            let d = rf_fix(rf, d); // the default as the range shows it
827            let same = v.to_bits() == d.to_bits();
828            if self.prop_reset(id, same) & EV_ACT != 0 {
829                *v = d;
830                ev |= EV_ACT | EV_COMMIT;
831                self.repass();
832            }
833        }
834        self.box_end();
835        self.box_end();
836        ev
837    }
838
839    // ------------------------------------------------------------------ angle dial
840
841    /// ui_angle(id, &mut deg, flags) -> EV_*
842    #[cfg_attr(jfgui_dev, track_caller)]
843    pub fn angle(&mut self, id: u32, deg: &mut f64, flags: u32) -> u32 {
844        let _src = self.src_scope();
845        let old = deg.to_bits();
846        let mut keych = false;
847        let stflags = self.take_flags();
848        let st = self.style;
849        let mut sp = Sp::default();
850        let (w, h) = st.angle_size(self, &mut sp);
851        self.place(w, h);
852        let mut ev = self.sld_behave(id, stflags);
853        self.touch_grab(super::touch::AX_ALL);
854        // geometry: circle in the centre of the placed rectangle (right-to-left: angles stay)
855        let r = self.wp.ctx.last_rect;
856        self.ltr_begin(r[0], r[2]);
857        let cx = (r[0] << 8).wrapping_add(r[2] << 7);
858        let cy = (r[1] << 8).wrapping_add(r[3] << 7);
859        if ev & EV_HELD != 0 {
860            // pointer: atan2(cy - y, x - cx)
861            let dx = (self.ltr_x(self.wp.ctx.mx) << 8).wrapping_sub(cx);
862            let dy = cy.wrapping_sub(self.wp.ctx.my << 8);
863            if dx | dy != 0 {
864                let mut a = deg_atan2(dy as f64, dx as f64);
865                if self.mods & MOD_SHIFT != 0 {
866                    a = f_round(a / 15.0) as f64 * 15.0;
867                }
868                *deg = a;
869            }
870        } else if ev & EV_FOCUS != 0 {
871            let (steps, he) = self.sld_keys();
872            if he == 1 {
873                *deg = 0.0; // Home: 0
874                keych = true;
875            } else if steps != 0 {
876                keych = true;
877                // arrows: 1 degree (Shift 15); PgUp / PgDn: 15 degrees (sld_keys reports 10)
878                let x = if steps >= 10 || steps <= -10 {
879                    ((steps / 10).wrapping_mul(15)) as f64
880                } else if self.mods & MOD_SHIFT != 0 {
881                    steps.wrapping_mul(15) as f64
882                } else {
883                    steps as f64
884                };
885                *deg = x + *deg;
886            }
887        }
888        *deg = ang_norm(*deg, flags);
889        if deg.to_bits() != old {
890            ev |= EV_ACT;
891            if keych {
892                ev |= EV_COMMIT;
893            }
894            self.repass();
895        }
896        let state = vl_state(ev, self.vl_disabled());
897        // paint (style): rectangle, state, angle, focus visual
898        sp.place(self.wp.ctx.last_rect);
899        sp.state = state;
900        sp.ev = ev;
901        sp.f = *deg;
902        sp.s[0] = self.focus_ring(ev) as i32;
903        st.angle(self, &sp);
904        self.ltr_end();
905        let v = f_round(*deg * 100.0) as i32;
906        self.sem(K_SLIDER, id, None, v).max = if flags & ANG_360 != 0 { 36000 } else { 18000 };
907        ev
908    }
909
910    // ------------------------------------------------------------------ pan control
911
912    /// ui_pan(id, &mut x, &mut y, thumb, ver, flags) -> EV_* (thumb: a surface or None; ver
913    /// stands for its version, as in draw_image)
914    #[allow(clippy::too_many_arguments)]
915    #[cfg_attr(jfgui_dev, track_caller)]
916    pub fn pan(&mut self, id: u32, x: &mut f64, y: &mut f64, thumb: Option<&Rc<Surface>>, ver: u32, flags: u32) -> u32 {
917        let _src = self.src_scope();
918        let (ox, oy) = (x.to_bits(), y.to_bits());
919        let mut keych = false;
920        let stflags = self.take_flags();
921        // range
922        let (lo, span) = if flags & PAN_UNIT != 0 { (0.0, 1.0) } else { (-1.0, 2.0) };
923        let st = self.style;
924        let mut sp = Sp { img: thumb, img_ver: ver, ..Sp::default() };
925        let (w, h) = st.pan_size(self, &mut sp);
926        self.place(w, h);
927        let mut ev = self.sld_behave(id, stflags);
928        self.touch_grab(super::touch::AX_ALL);
929        let frac = |p: i32, len: i32| {
930            let len = len.max(1);
931            let mut f = p as f64 / len as f64;
932            f = maxsd(f, 0.0);
933            f = minsd(f, 1.0);
934            f = f * span;
935            f + lo
936        };
937        let r = self.wp.ctx.last_rect;
938        self.ltr_begin(r[0], r[2]); // right-to-left: x keeps its direction
939        if ev & EV_HELD != 0 {
940            // pointer -> fraction of the rectangle -> lo + f * span
941            let r = self.wp.ctx.last_rect;
942            *x = frac(self.ltr_x(self.wp.ctx.mx).wrapping_sub(r[0]), r[2]);
943            *y = frac(self.wp.ctx.my.wrapping_sub(r[1]), r[3]);
944        } else if ev & EV_FOCUS != 0 {
945            // arrows: 1 % of the range (Shift 10 %), Home: centre
946            let mut dx = 0i32;
947            let mut dy = 0i32;
948            // right-to-left: the queue has the arrows swapped, the pad is not mirrored
949            let (right, left) = if self.rtl { (VK_LEFT, VK_RIGHT) } else { (VK_RIGHT, VK_LEFT) };
950            dx += self.key(right) as i32;
951            dx -= self.key(left) as i32;
952            dy += self.key(VK_DOWN) as i32;
953            dy -= self.key(VK_UP) as i32;
954            if self.key(VK_HOME) {
955                let c = span * 0.5 + lo;
956                *x = c;
957                *y = c;
958                keych = true;
959                self.wp.ctx.focus_visible = true;
960            } else if dx | dy != 0 {
961                keych = true;
962                self.wp.ctx.focus_visible = true;
963                let mut st = span * 0.01;
964                if self.mods & MOD_SHIFT != 0 {
965                    st = st * 10.0;
966                }
967                *x = dx as f64 * st + *x;
968                *y = dy as f64 * st + *y;
969            }
970        }
971        // clamp to lo .. lo + span, 4 decimals
972        let clamp = |v: f64| {
973            let mut v = if v.is_nan() { lo } else { v };
974            v = v * 10000.0;
975            v = f_round(v) as f64;
976            v = v / 10000.0;
977            v = maxsd(v, lo);
978            v = minsd(v, lo + span);
979            v + 0.0
980        };
981        *x = clamp(*x);
982        *y = clamp(*y);
983        if x.to_bits() != ox || y.to_bits() != oy {
984            ev |= EV_ACT;
985            if keych {
986                ev |= EV_COMMIT;
987            }
988            self.repass();
989        }
990        let state = vl_state(ev, self.vl_disabled());
991        // the point in px
992        let r = self.wp.ctx.last_rect;
993        let px = cvttd((*x - lo) / span * r[2] as f64).wrapping_add(r[0]);
994        let py = cvttd((*y - lo) / span * r[3] as f64).wrapping_add(r[1]);
995        // paint (style)
996        sp.place(r);
997        sp.state = state;
998        sp.value = px;
999        sp.value2 = py;
1000        st.pan(self, &sp);
1001        self.focus_ring_draw(ev);
1002        self.ltr_end();
1003        // semantic record: value = x * 1000, max = y * 1000
1004        let my = f_round(*y * 1000.0) as i32;
1005        let mx = f_round(*x * 1000.0) as i32;
1006        self.sem(K_SLIDER, id, None, mx).max = my;
1007        ev
1008    }
1009}
1010
1011// ang_norm(v, flags) -> the angle in [-180, 180] (outside values wrap into (-180, 180];
1012// ANG_360: [0, 360))
1013fn ang_norm(v: f64, flags: u32) -> f64 {
1014    let x = if v.is_nan() { 0.0 } else { v };
1015    let a360 = flags & ANG_360 != 0;
1016    // -180 .. 180 (both ends) stay as they are (a slider on the same value may set -180);
1017    // only values outside wrap into (-180, 180]
1018    if !a360 && x >= -180.0 && x <= 180.0 {
1019        return x + 0.0;
1020    }
1021    // k = floor((v - lo) / 360), v -= 360 k   (lo = -180 or 0)
1022    let lo = if a360 { 0.0 } else { -180.0 };
1023    let mut k = x - lo;
1024    k = k / 360.0;
1025    k = f_floor(k);
1026    k = k * 360.0;
1027    let mut y = x - k; // in [lo, lo + 360)
1028    if !a360 && !(y > -180.0) {
1029        y = 180.0; // (-180, 180]: -180 becomes 180
1030    }
1031    y + 0.0
1032}