Skip to main content

ui/
cmd.rs

1// cmd -- per-layer draw command lists, recorded during a UI pass and replayed into the
2// framebuffer afterwards (popups drawn late still end up on top, passes that are thrown
3// away cost no pixels)                                                 [src/ui/ui_cmd.s]
4//
5// Drawing calls for apps (current layer, physical pixels):
6//   draw_color(argb)                     colour for the next draw_* calls
7//   draw_rect(x, y, w, h)                filled rectangle
8//   draw_rrect(x, y, w, h, r)            filled rounded rectangle
9//   draw_frame(x, y, w, h, r, t)         rounded border of thickness t
10//   draw_text(x, y, s, font_size)        text with baseline y; font_size = FONT_* | size_fx << 8
11//   draw_icon(cp, x, y, size_fx)         icon, em box top-left at x, y
12//   icon_hook(f)                         an app draws chosen icons itself (render time)
13//   draw_circle(cx, cy, r, t)            24.8 fixed; t = 0 fills
14//   draw_line(x0, y0, x1, y1, width)     24.8 fixed, round caps
15//   draw_image(sf, x, y, w, h)           surface scaled to w x h (nearest) or 1:1
16//   draw_clip(x, y, w, h) / draw_unclip()
17// Internal: cmd_reset, cmd_layer(l) -> previous, cmd_rrect, cmd_text, cmd_custom,
18// cmd_shadow, render_all, render_popup(dx, dy).
19//
20// Every record keeps the byte size the asm gives it (cmd_alloc rounds to 8): a layer holds
21// at most its capacity (CAPS_MAIN / CAPS_SMALL), further commands are dropped as in the asm.
22// The dirty regions (dirty.rs) hash every record's content (cmd_parts: without its position,
23// which is mixed in separately, so scrollcopy.rs can hash it relative to a scrolled content)
24// and its box (cmd_bbox).
25
26use alloc::rc::Rc;
27use alloc::vec::Vec;
28use core::sync::atomic::{AtomicBool, Ordering};
29
30use super::consts::*;
31use super::Ui;
32use gfx::shapes::Shapes;
33use gfx::{Dc, Surface};
34use text::Text;
35
36/// layer capacities in bytes of the main window / of secondary windows  [cmd_caps_*]
37pub(crate) const CAPS_MAIN: [u32; L_COUNT] = [1024 * 1024, 256 * 1024, 256 * 1024, 256 * 1024];
38#[allow(dead_code)] // ui-win: secondary windows
39pub(crate) const CAPS_SMALL: [u32; L_COUNT] = [256 * 1024, 64 * 1024, 128 * 1024, 16 * 1024];
40
41/// the gfx target of a custom draw call (render time): the draw context (clip, origin,
42/// colour), the surface and the text engine (its path and rasterizer serve Shapes)
43pub struct Gfx<'a> {
44    pub dc: &'a mut Dc,
45    pub sf: &'a mut Surface,
46    pub text: &'a mut Text,
47}
48
49impl Gfx<'_> {
50    /// the anti-aliased shape calls (fill_rrect, circles, lines) on this target
51    pub fn shapes(&mut self) -> Shapes<'_> {
52        Shapes { dc: &mut *self.dc, sf: &mut *self.sf, path: &mut self.text.path, ras: &mut self.text.ras }
53    }
54}
55
56/// a custom draw function (cmd_custom: canvases, custom tracks): draw(g, rect) at render
57/// time with the clip set to rect (absolute px).  Its identity (the Rc) and the version of
58/// the command decide whether it is redrawn.
59pub trait Draw {
60    fn draw(&self, g: &mut Gfx, r: [i32; 4]);
61}
62
63pub type DrawRef = Rc<dyn Draw>;
64
65/// icon_hook: f(g, cp, x, y, size_fx, argb) -> true when it drew icon cp itself (x, y =
66/// target pixels with the origin applied), false = the icon font draws it
67pub type IconHook = Rc<dyn Fn(&mut Gfx, u32, i32, i32, i32, u32) -> bool>;
68
69/// one recorded draw command  [C_*]
70#[derive(Clone)]
71#[non_exhaustive]
72pub enum Cmd {
73    Rect { x: i32, y: i32, w: i32, h: i32, c: u32 },
74    /// fill (alpha 0 = none), border t px in `stroke`, its bottom band in `stroke2`; r and t
75    /// are kept as u16
76    RRect { x: i32, y: i32, w: i32, h: i32, r: u16, t: u16, fill: u32, stroke: u32, stroke2: u32 },
77    /// Fractional rounded panel; geometry is 24.8 pixels and composited once.
78    PanelFx { x: i32, y: i32, w: i32, h: i32, r: i32, t: i32, fill: u32, stroke: u32 },
79    /// text with baseline y; maxw != 0 clips it to x .. x + maxw
80    Text { x: i32, y: i32, font: u16, size: i32, c: u32, maxw: i32, s: Vec<u8> },
81    /// icon, em box top-left at x, y
82    Icon { x: i32, y: i32, cp: u32, size: i32, c: u32 },
83    Image { x: i32, y: i32, w: i32, h: i32, sf: Rc<Surface>, ver: u32 },
84    Clip { x: i32, y: i32, w: i32, h: i32 },
85    Unclip,
86    /// 24.8; t = 0 fills
87    Circle { x: i32, y: i32, r: i32, t: i32, c: u32 },
88    /// 24.8, round caps
89    Line { x0: i32, y0: i32, x1: i32, y1: i32, w: i32, c: u32 },
90    Custom { x: i32, y: i32, w: i32, h: i32, f: Option<DrawRef>, user: usize, ver: u32 },
91    Shadow { x: i32, y: i32, w: i32, h: i32, r: i32, depth: i32, c: u32 },
92}
93
94impl Cmd {
95    /// the record size of the asm (cmd_alloc: rounded up to 8 bytes)
96    fn size(&self) -> u32 {
97        let n = match self {
98            Cmd::Rect { .. } | Cmd::Icon { .. } | Cmd::Circle { .. } | Cmd::Clip { .. } => 24,
99            Cmd::RRect { .. } | Cmd::Image { .. } => 40,
100            Cmd::PanelFx { .. } => 40,
101            Cmd::Text { s, .. } => 28 + s.len() as u32,
102            Cmd::Unclip => 8,
103            Cmd::Line { .. } | Cmd::Shadow { .. } => 32,
104            Cmd::Custom { .. } => 48,
105        };
106        (n + 7) & !7
107    }
108}
109
110/// the per-window part of the command lists (ui_cmd_wb0 .. ui_cmd_wb1)
111pub(crate) struct W {
112    pub(crate) layers: [Vec<Cmd>; L_COUNT],
113    /// bytes used / capacity per layer (cmd_init_caps)
114    pub(crate) len: [u32; L_COUNT],
115    pub(crate) cap: [u32; L_COUNT],
116    pub(crate) layer: usize,
117    pub(crate) dcol: u32,
118    pub(crate) overflow: bool,
119}
120
121impl W {
122    /// cmd_init_caps(caps): empty lists with these capacities
123    pub(crate) fn new(caps: &[u32; L_COUNT]) -> W {
124        W {
125            layers: Default::default(),
126            len: [0; L_COUNT],
127            cap: *caps,
128            layer: 0,
129            dcol: 0xFF00_0000,
130            overflow: false,
131        }
132    }
133}
134
135// FNV-1a 64 over little-endian fields (hash_fnv64)
136struct Fnv(u64);
137
138impl Fnv {
139    fn new() -> Fnv {
140        Fnv(0xCBF2_9CE4_8422_2325)
141    }
142    fn bytes(&mut self, b: &[u8]) {
143        for &x in b {
144            self.0 ^= x as u64;
145            self.0 = self.0.wrapping_mul(0x0000_0100_0000_01B3);
146        }
147    }
148    fn u32(&mut self, v: u32) {
149        self.bytes(&v.to_le_bytes());
150    }
151    fn i32(&mut self, v: i32) {
152        self.bytes(&v.to_le_bytes());
153    }
154    fn u64(&mut self, v: u64) {
155        self.bytes(&v.to_le_bytes());
156    }
157}
158
159/// hash of a record's content (cmd_parts, its anchor mixed in) -- any visible change of a
160/// command changes it
161pub(crate) fn cmd_hash(c: &Cmd) -> u64 {
162    let (body, x, y, _) = cmd_parts(c);
163    pos_mix(body, x as i64, y as i64)
164}
165
166/// pos_mix(body, x, y): a record's hash at a position (its anchor, or the anchor relative to
167/// a scrolled content's origin: scrollcopy.rs)
168#[inline]
169pub(crate) fn pos_mix(body: u64, x: i64, y: i64) -> u64 {
170    const K: u64 = 0x9E37_79B9_7F4A_7C15;
171    let h = (body ^ x as u64).wrapping_mul(K);
172    let h = (h.rotate_left(29) ^ y as u64).wrapping_mul(K);
173    h ^ h >> 31
174}
175
176/// cmd_parts -> (the hash of a record's content without its position, anchor x, anchor y,
177/// 8 when the anchor is 24.8 else 0): the asm's record layout (header: type, flags, size,
178/// colour; then the fields; text bytes; pointers by identity), a line's end relative to its
179/// start.  A record moved by whole pixels keeps its body.
180pub(crate) fn cmd_parts(c: &Cmd) -> (u64, i32, i32, u32) {
181    let mut h = Fnv::new();
182    let size = c.size();
183    let head = |h: &mut Fnv, t: u8, argb: u32| {
184        h.bytes(&[t, 0]);
185        h.bytes(&(size as u16).to_le_bytes());
186        h.u32(argb);
187    };
188    let (x, y, sh) = match *c {
189        Cmd::Rect { x, y, w, h: hh, c } => {
190            head(&mut h, 1, c);
191            for v in [w, hh] {
192                h.i32(v);
193            }
194            (x, y, 0)
195        }
196        Cmd::RRect { x, y, w, h: hh, r, t, fill, stroke, stroke2 } => {
197            head(&mut h, 2, fill);
198            for v in [w, hh] {
199                h.i32(v);
200            }
201            h.bytes(&r.to_le_bytes());
202            h.bytes(&t.to_le_bytes());
203            h.u32(stroke);
204            h.u32(stroke2);
205            (x, y, 0)
206        }
207        Cmd::PanelFx { x, y, w, h: hh, r, t, fill, stroke } => {
208            head(&mut h, 12, fill);
209            for v in [w, hh, r, t] {
210                h.i32(v);
211            }
212            h.u32(stroke);
213            (x, y, 8)
214        }
215        Cmd::Text { x, y, font, size: sz, c, maxw, ref s } => {
216            head(&mut h, 3, c);
217            h.bytes(&font.to_le_bytes());
218            h.bytes(&(s.len() as u16).to_le_bytes());
219            h.i32(sz);
220            h.i32(maxw);
221            h.bytes(s);
222            (x, y, 0)
223        }
224        Cmd::Icon { x, y, cp, size: sz, c } => {
225            head(&mut h, 4, c);
226            h.u32(cp);
227            h.i32(sz);
228            (x, y, 0)
229        }
230        Cmd::Image { x, y, w, h: hh, ref sf, ver } => {
231            head(&mut h, 5, 0);
232            for v in [w, hh] {
233                h.i32(v);
234            }
235            h.u64(Rc::as_ptr(sf) as usize as u64);
236            h.u32(ver);
237            (x, y, 0)
238        }
239        Cmd::Clip { x, y, w, h: hh } => {
240            head(&mut h, 6, 0);
241            for v in [w, hh] {
242                h.i32(v);
243            }
244            (x, y, 0)
245        }
246        Cmd::Unclip => {
247            head(&mut h, 7, 0);
248            (0, 0, 0)
249        }
250        Cmd::Circle { x, y, r, t, c } => {
251            head(&mut h, 8, c);
252            for v in [r, t] {
253                h.i32(v);
254            }
255            (x, y, 8)
256        }
257        Cmd::Line { x0, y0, x1, y1, w, c } => {
258            head(&mut h, 9, c);
259            for v in [x1.wrapping_sub(x0), y1.wrapping_sub(y0), w] {
260                h.i32(v);
261            }
262            (x0, y0, 8)
263        }
264        Cmd::Custom { x, y, w, h: hh, ref f, user, ver } => {
265            head(&mut h, 10, 0);
266            for v in [w, hh] {
267                h.i32(v);
268            }
269            h.u64(f.as_ref().map_or(0, |f| Rc::as_ptr(f) as *const () as usize as u64));
270            h.u64(user as u64);
271            h.u32(ver);
272            (x, y, 0)
273        }
274        Cmd::Shadow { x, y, w, h: hh, r, depth, c } => {
275            head(&mut h, 11, c);
276            for v in [w, hh, r, depth] {
277                h.i32(v);
278            }
279            (x, y, 0)
280        }
281    };
282    (h.0, x, y, sh)
283}
284
285/// cmd_bbox -> the command's box x0, y0, x1, y1 (px, exclusive), None when it has no area
286/// (clip records)
287pub(crate) fn cmd_bbox(c: &Cmd) -> Option<[i32; 4]> {
288    Some(match *c {
289        Cmd::PanelFx { x, y, w, h, .. } => [x >> 8, y >> 8,
290            ((x as i64 + w as i64 + 255) >> 8) as i32,
291            ((y as i64 + h as i64 + 255) >> 8) as i32],
292        Cmd::Text { x, y, size, maxw, ref s, .. } => {
293            // x, baseline, size: a generous box
294            let px = ((size as u32) >> 8).wrapping_add(1) as i32;
295            let x0 = x.wrapping_sub(2);
296            let tw = if maxw != 0 { maxw } else { (s.len() as i32).wrapping_mul(px) };
297            let half = ((px as u32) >> 1) as i32;
298            [x0, y.wrapping_sub(half.wrapping_add(px)), x0.wrapping_add(tw).wrapping_add(4), y.wrapping_add(half).wrapping_add(1)]
299        }
300        Cmd::Icon { x, y, size, .. } => {
301            let s = ((size as u32) >> 8).wrapping_add(2) as i32; // size px + margin
302            let (x0, y0) = (x.wrapping_sub(1), y.wrapping_sub(1));
303            [x0, y0, x0.wrapping_add(s), y0.wrapping_add(s)]
304        }
305        Cmd::Circle { x, y, r, t, .. } => {
306            // centre and radius in 24.8, ring width
307            let d = (r.wrapping_add(t) >> 8).wrapping_add(2);
308            let (cx, cy) = (x >> 8, y >> 8);
309            [cx.wrapping_sub(d), cy.wrapping_sub(d), cx.wrapping_add(d).wrapping_add(1), cy.wrapping_add(d).wrapping_add(1)]
310        }
311        Cmd::Line { x0, y0, x1, y1, w, .. } => {
312            let d = (w >> 8).wrapping_add(2); // half width + margin (generous)
313            [
314                (x0.min(x1) >> 8).wrapping_sub(d),
315                (y0.min(y1) >> 8).wrapping_sub(d),
316                (x0.max(x1) >> 8).wrapping_add(d).wrapping_add(1),
317                (y0.max(y1) >> 8).wrapping_add(d).wrapping_add(1),
318            ]
319        }
320        Cmd::Shadow { x, y, w, h, depth: d, .. } => {
321            // the shadow reaches `depth` around the box and depth/3 further down
322            [x.wrapping_sub(d), y.wrapping_sub(d), x.wrapping_add(w).wrapping_add(d), y.wrapping_add(h).wrapping_add(d).wrapping_add(d)]
323        }
324        Cmd::Clip { .. } | Cmd::Unclip => return None,
325        Cmd::Rect { x, y, w, h, .. }
326        | Cmd::RRect { x, y, w, h, .. }
327        | Cmd::Image { x, y, w, h, .. }
328        | Cmd::Custom { x, y, w, h, .. } => [x, y, x.wrapping_add(w), y.wrapping_add(h)],
329    })
330}
331
332/// an icon was drawn by an icon hook or the style's own painter (vector paths): such icons
333/// may not keep their pixels when moved, so scroll copies redraw them (scrollcopy.rs; set
334/// at render time, never cleared)
335pub(crate) static ICON_PAINTED: AtomicBool = AtomicBool::new(false);
336
337/// cmd_visible: the command's box touches the current gfx clip (no box: always)
338fn cmd_visible(c: &Cmd, dc: &Dc) -> bool {
339    let Some(b) = cmd_bbox(c) else { return true };
340    let k = dc.clip;
341    !(b[0] >= k[2] || b[2] <= k[0] || b[1] >= k[3] || b[3] <= k[1])
342}
343
344/// cmd_render_layer: replay one layer into the current gfx target; the drawing commands
345/// before index `from` are skipped (their clip records still count: render_par.rs draws the
346/// part before a custom draw on other threads)
347#[allow(clippy::too_many_arguments)]
348pub(crate) fn render_layer(list: &[Cmd], from: usize, dc: &mut Dc, sf: &mut Surface, text: &mut Text, hook: &Option<IconHook>,
349                          icon_style: (&dyn crate::Style, u32, bool, u32, u32)) {
350    let mut depth = 0; // clip depth opened by this layer
351    for (i, c) in list.iter().enumerate() {
352        if !matches!(c, Cmd::Clip { .. } | Cmd::Unclip) && (i < from || !cmd_visible(c, dc)) {
353            continue;
354        }
355        match c {
356            &Cmd::PanelFx { x, y, w, h, r, t, fill, stroke } => {
357                let mut sh = Shapes { dc: &mut *dc, sf: &mut *sf, path: &mut text.path, ras: &mut text.ras };
358                sh.panel_fx(x, y, w, h, r, t, fill, stroke);
359            }
360            &Cmd::Rect { x, y, w, h, c } => {
361                dc.set_color(c);
362                dc.fill(sf, x, y, w, h);
363            }
364            &Cmd::RRect { x, y, w, h, r, t, fill, stroke, stroke2 } => {
365                let mut sh = Shapes { dc: &mut *dc, sf: &mut *sf, path: &mut text.path, ras: &mut text.ras };
366                draw_rrect(&mut sh, [x, y, w, h], r as i32, t as i32, fill, stroke, stroke2);
367            }
368            Cmd::Text { x, y, font, size, c, maxw, s } => {
369                dc.set_color(*c);
370                text.set_font(*font as u32, *size);
371                if *maxw != 0 {
372                    dc.clip_push(*x, -100000, *maxw, 200000);
373                }
374                text.draw_n(dc, sf, *x, *y, s, s.len());
375                if *maxw != 0 {
376                    dc.clip_pop();
377                }
378            }
379            &Cmd::Icon { x, y, cp, size, c } => {
380                if let Some(f) = hook {
381                    // the app draws this icon itself?
382                    let (tx, ty) = (x.wrapping_add(dc.ox), y.wrapping_add(dc.oy));
383                    let mut g = Gfx { dc: &mut *dc, sf: &mut *sf, text: &mut *text };
384                    if f(&mut g, cp, tx, ty, size, c) {
385                        ICON_PAINTED.store(true, Ordering::Relaxed);
386                        continue;
387                    }
388                }
389                let (style, accent, dark, disabled, dpi) = icon_style;
390                let (tx, ty) = (x.wrapping_add(dc.ox), y.wrapping_add(dc.oy));
391                let mut g = Gfx { dc: &mut *dc, sf: &mut *sf, text: &mut *text };
392                if style.paint_icon(&mut g, cp, tx, ty, size, c, accent, dark, c == disabled, dpi) {
393                    ICON_PAINTED.store(true, Ordering::Relaxed);
394                    continue;
395                }
396                dc.set_color(c);
397                text.icon_draw(dc, sf, cp, x, y, size);
398            }
399            Cmd::Image { x, y, w, h, sf: src, .. } => {
400                dc.blit_scaled(sf, src, *x, *y, *w, *h);
401            }
402            &Cmd::Clip { x, y, w, h } => {
403                dc.clip_push(x, y, w, h);
404                depth += 1;
405            }
406            Cmd::Unclip => {
407                if depth > 0 {
408                    dc.clip_pop();
409                    depth -= 1;
410                }
411            }
412            &Cmd::Circle { x, y, r, t, c } => {
413                dc.set_color(c);
414                let mut sh = Shapes { dc: &mut *dc, sf: &mut *sf, path: &mut text.path, ras: &mut text.ras };
415                if t != 0 {
416                    sh.stroke_circle_fx(x, y, r, t);
417                } else {
418                    sh.fill_circle_fx(x, y, r);
419                }
420            }
421            &Cmd::Line { x0, y0, x1, y1, w, c } => {
422                dc.set_color(c);
423                let mut sh = Shapes { dc: &mut *dc, sf: &mut *sf, path: &mut text.path, ras: &mut text.ras };
424                sh.line_fx(x0, y0, x1, y1, w);
425            }
426            Cmd::Custom { x, y, w, h, f, .. } => {
427                // clip to the widget, call f(g, rect)
428                dc.clip_push(*x, *y, *w, *h);
429                if let Some(f) = f {
430                    let mut g = Gfx { dc: &mut *dc, sf: &mut *sf, text: &mut *text };
431                    f.draw(&mut g, [*x, *y, *w, *h]);
432                }
433                dc.clip_pop();
434            }
435            &Cmd::Shadow { x, y, w, h, r, depth: d, c } => {
436                let mut sh = Shapes { dc: &mut *dc, sf: &mut *sf, path: &mut text.path, ras: &mut text.ras };
437                draw_shadow(&mut sh, [x, y, w, h], r, d, c);
438            }
439        }
440    }
441    while depth > 0 {
442        dc.clip_pop();
443        depth -= 1;
444    }
445}
446
447/// a Cmd::RRect: fill (alpha 0 = none), border t px in `stroke`, its bottom band in `stroke2`
448/// (render_layer and the row bands of render_par.rs)
449pub(crate) fn draw_rrect(sh: &mut Shapes, b: [i32; 4], r: i32, t: i32, fill: u32, stroke: u32, stroke2: u32) {
450    let [x, y, w, h] = b;
451    if fill & 0xFF00_0000 != 0 {
452        sh.dc.set_color(fill);
453        sh.fill_rrect(x, y, w, h, r);
454    }
455    if t == 0 {
456        return;
457    }
458    if stroke & 0xFF00_0000 != 0 {
459        sh.dc.set_color(stroke);
460        sh.stroke_rrect(x, y, w, h, r, t);
461    }
462    // different bottom edge colour: overdraw the lower corner band of the border
463    if stroke2 != stroke && stroke2 & 0xFF00_0000 != 0 {
464        sh.dc.set_color(stroke2);
465        let band = r.max(t);
466        sh.dc.clip_push(x, y.wrapping_add(h).wrapping_sub(band), w, band);
467        sh.stroke_rrect(x, y, w, h, r, t);
468        sh.dc.clip_pop();
469    }
470}
471
472/// a Cmd::Shadow: `depth` expanding rounded rectangles with fading alpha, offset downwards by
473/// depth / 3
474pub(crate) fn draw_shadow(sh: &mut Shapes, b: [i32; 4], r: i32, d: i32, c: u32) {
475    let [x, y, w, h] = b;
476    if d <= 0 {
477        return;
478    }
479    let base = c >> 24;
480    let down = (d as u32 / 3) as i32;
481    let mut i = d;
482    while i != 0 {
483        // alpha_i = base * (depth - i + 1) / (depth * 2)
484        let a = base.wrapping_mul((d - i + 1) as u32) / ((d + d) as u32);
485        sh.dc.set_color((c & 0xFF_FFFF) | a << 24);
486        sh.fill_rrect(x - i, y - i + down, w + i * 2, h + i * 2, r + i);
487        i -= 1;
488    }
489}
490
491// cmd_translate(layer, dx, dy): move every command of a layer by (dx, dy) px (circles and
492// lines in 24.8)
493#[allow(dead_code)] // ui-win: OS popup windows (U3)
494fn translate(list: &mut [Cmd], dx: i32, dy: i32) {
495    for c in list.iter_mut() {
496        match c {
497            Cmd::Unclip => {}
498            Cmd::Circle { x, y, .. } | Cmd::PanelFx { x, y, .. } => {
499                *x = x.wrapping_add(dx << 8);
500                *y = y.wrapping_add(dy << 8);
501            }
502            Cmd::Line { x0, y0, x1, y1, .. } => {
503                *x1 = x1.wrapping_add(dx << 8);
504                *y1 = y1.wrapping_add(dy << 8);
505                *x0 = x0.wrapping_add(dx << 8);
506                *y0 = y0.wrapping_add(dy << 8);
507            }
508            Cmd::Rect { x, y, .. }
509            | Cmd::RRect { x, y, .. }
510            | Cmd::Text { x, y, .. }
511            | Cmd::Icon { x, y, .. }
512            | Cmd::Image { x, y, .. }
513            | Cmd::Clip { x, y, .. }
514            | Cmd::Custom { x, y, .. }
515            | Cmd::Shadow { x, y, .. } => {
516                *x = x.wrapping_add(dx);
517                *y = y.wrapping_add(dy);
518            }
519        }
520    }
521}
522
523impl Ui {
524    /// cmd_reset: empty every layer, current layer = base
525    pub(crate) fn cmd_reset(&mut self) {
526        let w = &mut self.wp.cmd;
527        for l in w.layers.iter_mut() {
528            l.clear();
529        }
530        w.len = [0; L_COUNT];
531        w.layer = 0;
532        w.dcol = 0xFF00_0000;
533        self.wp.rtl.reset();
534        self.wp.scopy.reset(); // the scroll views recorded (scrollcopy.rs)
535        self.wp.canv.recs.clear(); // the canvases recorded (canvas.rs)
536    }
537
538    /// cmd_layer(l) -> previous layer
539    pub fn cmd_layer(&mut self, l: u32) -> u32 {
540        let prev = self.wp.cmd.layer as u32;
541        self.wp.cmd.layer = (l & 3) as usize;
542        prev
543    }
544
545    // cmd_alloc: append a record to the current layer (dropped when the layer is full)
546    fn push(&mut self, c: Cmd) {
547        let w = &mut self.wp.cmd;
548        let l = w.layer;
549        let n = w.len[l] + c.size();
550        if n > w.cap[l] {
551            w.overflow = true;
552            return;
553        }
554        w.len[l] = n;
555        w.layers[l].push(c);
556    }
557
558    /// ui_draw_color(argb): colour for the following draw_* calls
559    pub fn draw_color(&mut self, argb: u32) {
560        self.wp.cmd.dcol = argb;
561    }
562
563    /// the colour of the following draw_* calls (g_dcol)
564    pub fn draw_color_get(&self) -> u32 {
565        self.wp.cmd.dcol
566    }
567
568    /// ui_draw_rect(x, y, w, h)
569    pub fn draw_rect(&mut self, x: i32, y: i32, w: i32, h: i32) {
570        let c = self.wp.cmd.dcol;
571        self.push(Cmd::Rect { x, y, w, h, c });
572    }
573
574    /// ui_draw_rrect(x, y, w, h, r): filled rounded rectangle in the draw colour
575    pub fn draw_rrect(&mut self, x: i32, y: i32, w: i32, h: i32, r: i32) {
576        let c = self.wp.cmd.dcol;
577        self.cmd_rrect(x, y, w, h, r, 0, c, 0, 0);
578    }
579
580    /// ui_draw_frame(x, y, w, h, r, t): border only, in the draw colour
581    pub fn draw_frame(&mut self, x: i32, y: i32, w: i32, h: i32, r: i32, t: i32) {
582        let c = self.wp.cmd.dcol;
583        self.cmd_rrect(x, y, w, h, r, t, 0, c, c);
584    }
585
586    /// cmd_rrect(x, y, w, h, r, t, fill, stroke, stroke2): rounded rectangle with an
587    /// optional fill and border (stroke2: the bottom edge)
588    #[allow(clippy::too_many_arguments)]
589    pub fn cmd_rrect(&mut self, x: i32, y: i32, w: i32, h: i32, r: i32, t: i32, fill: u32, stroke: u32, stroke2: u32) {
590        let r = if self.look.rad_mode != 0 { self.look_radius(r, w, h) } else { r }; // (theme)
591        self.push(Cmd::RRect { x, y, w, h, r: r as u16, t: t as u16, fill, stroke, stroke2 });
592    }
593
594    /// Rounded fill/border with 24.8 pixel geometry and a single coverage composite.
595    /// Fractional bounds participate in dirty-region hashing and clipping.
596    #[allow(clippy::too_many_arguments)]
597    pub fn cmd_panel_fx(&mut self, x: i32, y: i32, w: i32, h: i32, r: i32, t: i32, fill: u32, stroke: u32) {
598        self.push(Cmd::PanelFx { x, y, w, h, r, t, fill, stroke });
599    }
600
601    /// ui_draw_text(x, y, s, font_size): font_size = FONT_* | size_fx << 8
602    pub fn draw_text(&mut self, x: i32, y: i32, s: &str, font_size: u32) {
603        let c = self.wp.cmd.dcol;
604        self.cmd_text(x, y, s.as_bytes(), font_size & 255, (font_size >> 8) as i32, c, 0);
605    }
606
607    /// cmd_text(x, y, s, font, size_fx, colour, maxw): at most 4000 bytes, never cut inside
608    /// a UTF-8 sequence
609    #[allow(clippy::too_many_arguments)]
610    pub fn cmd_text(&mut self, x: i32, y: i32, s: &[u8], font: u32, size: i32, c: u32, maxw: i32) {
611        let mut n = s.len();
612        if n > 4000 {
613            n = 4000;
614            // do not cut inside a UTF-8 sequence: back off over continuation bytes
615            while n > 0 && s[n] & 0xC0 == 0x80 {
616                n -= 1;
617            }
618        }
619        self.push(Cmd::Text { x, y, font: font as u16, size, c, maxw, s: s[..n].to_vec() });
620    }
621
622    /// emit_text(x, baseline, s, colour): text in the current text font (styles)
623    pub fn emit_text(&mut self, x: i32, y: i32, s: &str, c: u32) {
624        let (font, size) = self.text.get_font();
625        let b = s.as_bytes();
626        let n = b.iter().position(|&x| x == 0).unwrap_or(b.len());
627        self.cmd_text(x, y, &b[..n], font, size, c, 0);
628    }
629
630    /// ui_draw_icon(cp, x, y, size_fx)
631    pub fn draw_icon(&mut self, cp: u32, x: i32, y: i32, size: i32) {
632        let c = self.wp.cmd.dcol;
633        self.push(Cmd::Icon { x, y, cp, size, c });
634    }
635
636    /// ui_draw_circle(cx, cy, r, t): 24.8 fixed, t = 0 fills
637    pub fn draw_circle(&mut self, x: i32, y: i32, r: i32, t: i32) {
638        let c = self.wp.cmd.dcol;
639        self.push(Cmd::Circle { x, y, r, t, c });
640    }
641
642    /// ui_draw_line(x0, y0, x1, y1, width): 24.8 fixed
643    pub fn draw_line(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, w: i32) {
644        let c = self.wp.cmd.dcol;
645        self.push(Cmd::Line { x0, y0, x1, y1, w, c });
646    }
647
648    /// ui_draw_image(sf, x, y, w, h): w / h 0 = natural size; `ver` stands for the
649    /// surface's SF_AUX version (bump it when the pixels change)
650    pub fn draw_image(&mut self, sf: &Rc<Surface>, ver: u32, x: i32, y: i32, w: i32, h: i32) {
651        let w = if w == 0 { sf.w } else { w };
652        let h = if h == 0 { sf.h } else { h };
653        self.push(Cmd::Image { x, y, w, h, sf: sf.clone(), ver });
654    }
655
656    /// ui_draw_clip(x, y, w, h)
657    pub fn draw_clip(&mut self, x: i32, y: i32, w: i32, h: i32) {
658        self.push(Cmd::Clip { x, y, w, h });
659    }
660
661    /// ui_draw_unclip
662    pub fn draw_unclip(&mut self) {
663        self.push(Cmd::Unclip);
664    }
665
666    /// ui_icon_hook(f): an app draws chosen icons itself (None removes the hook)
667    pub fn icon_hook(&mut self, f: Option<IconHook>) {
668        self.icon_hook = f;
669    }
670
671    /// cmd_custom(x, y, w, h, f, user, version): f.draw(g, rect) at render time, clipped
672    #[allow(clippy::too_many_arguments)]
673    pub fn cmd_custom(&mut self, x: i32, y: i32, w: i32, h: i32, f: Option<DrawRef>, user: usize, ver: u32) {
674        self.push(Cmd::Custom { x, y, w, h, f, user, ver });
675    }
676
677    /// cmd_shadow(x, y, w, h, r, depth): soft shadow in the draw colour
678    pub fn cmd_shadow(&mut self, x: i32, y: i32, w: i32, h: i32, r: i32, depth: i32) {
679        let r = if self.look.rad_mode != 0 { self.look_radius(r, w, h) } else { r }; // (theme)
680        let c = self.wp.cmd.dcol;
681        self.push(Cmd::Shadow { x, y, w, h, r, depth, c });
682    }
683
684    /// cmd_render_all: replay every layer of the live window into the current gfx target
685    /// (sf = the target the dc began)
686    pub(crate) fn render_all_to(&mut self, shadow: bool) {
687        self.render_from(shadow, 0, 0);
688    }
689
690    /// render_all_to from command `from` of layer `layer` on (the earlier commands drawn
691    /// already; their clip records still count)
692    pub(crate) fn render_from(&mut self, shadow: bool, layer: usize, from: usize) {
693        let icon_style = (self.style, self.color(T_ACCENT), self.theme_dark, self.color(T_TEXT_DISABLED), self.wp.rt.dpi as u32);
694        let Ui { wp, dc, text, icon_hook, .. } = self;
695        let sf = if shadow { &mut wp.rt.shadow } else { &mut wp.rt.fb };
696        for (k, l) in wp.cmd.layers.iter().enumerate().skip(layer) {
697            render_layer(l, if k == layer { from } else { 0 }, dc, sf, text, icon_hook, icon_style);
698        }
699    }
700
701    /// cmd_render_popup(dx, dy): (U3) replay the popup and tooltip layers into `sf` moved
702    /// by (dx, dy) px (an OS popup window shows a part of its owner's popups)
703    #[allow(dead_code)] // ui-win: OS popup windows (U3)
704    pub(crate) fn render_popup(&mut self, sf: &mut Surface, dx: i32, dy: i32) {
705        let icon_style = (self.style, self.color(T_ACCENT), self.theme_dark, self.color(T_TEXT_DISABLED), self.wp.rt.dpi as u32);
706        let Ui { wp, dc, text, icon_hook, .. } = self;
707        let layers = &mut wp.cmd.layers;
708        translate(&mut layers[L_POPUP as usize], dx, dy);
709        translate(&mut layers[L_TIP as usize], dx, dy);
710        render_layer(&layers[L_POPUP as usize], 0, dc, sf, text, icon_hook, icon_style);
711        render_layer(&layers[L_TIP as usize], 0, dc, sf, text, icon_hook, icon_style);
712        translate(&mut layers[L_POPUP as usize], -dx, -dy);
713        translate(&mut layers[L_TIP as usize], -dx, -dy);
714    }
715}