Skip to main content

agg_gui/
draw_ctx.rs

1//! `DrawCtx` — the unified drawing interface shared by the software (`GfxCtx`)
2//! and hardware (`GlGfxCtx`) rendering paths.
3//!
4//! Every `Widget::paint` implementation receives a `&mut dyn DrawCtx`.  The
5//! concrete type is either:
6//!
7//! - **`GfxCtx`** — software AGG rasteriser (used when a widget opts into a
8//!   back-buffer or when GL is unavailable).
9//! - **`GlGfxCtx`** — hardware GL path: shapes are tessellated via `tess2`
10//!   and submitted as GPU draw calls.
11//!
12//! The two implementations expose *identical* method signatures so that widget
13//! `paint` bodies are unchanged regardless of the render target.
14
15use std::sync::Arc;
16
17use crate::color::Color;
18use crate::geometry::Rect;
19use crate::text::{Font, TextMetrics};
20use crate::theme::Visuals;
21use agg_rust::comp_op::CompOp;
22use agg_rust::math_stroke::{LineCap, LineJoin};
23use agg_rust::trans_affine::TransAffine;
24
25/// Fill rule used when rasterizing closed paths.
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub enum FillRule {
28    /// Non-zero winding rule.
29    NonZero,
30    /// Even-odd parity rule.
31    EvenOdd,
32}
33
34impl Default for FillRule {
35    fn default() -> Self {
36        Self::NonZero
37    }
38}
39
40/// How a gradient behaves outside the normalized `0..=1` range.
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub enum GradientSpread {
43    /// Clamp to the nearest edge stop.
44    Pad,
45    /// Mirror each repeated interval.
46    Reflect,
47    /// Repeat the gradient ramp.
48    Repeat,
49}
50
51impl Default for GradientSpread {
52    fn default() -> Self {
53        Self::Pad
54    }
55}
56
57/// One color stop in a bridge-level gradient paint.
58#[derive(Clone, Copy, Debug, PartialEq)]
59pub struct GradientStop {
60    pub offset: f64,
61    pub color: Color,
62}
63
64/// Linear gradient fill paint expressed in local drawing coordinates.
65#[derive(Clone, Debug, PartialEq)]
66pub struct LinearGradientPaint {
67    pub x1: f64,
68    pub y1: f64,
69    pub x2: f64,
70    pub y2: f64,
71    pub transform: TransAffine,
72    pub spread: GradientSpread,
73    pub stops: Vec<GradientStop>,
74}
75
76impl LinearGradientPaint {
77    pub fn sample(&self, mut x: f64, mut y: f64) -> Color {
78        if self.stops.is_empty() {
79            return Color::transparent();
80        }
81
82        self.transform.inverse_transform(&mut x, &mut y);
83
84        let dx = self.x2 - self.x1;
85        let dy = self.y2 - self.y1;
86        let len2 = dx * dx + dy * dy;
87        let t = if len2 > f64::EPSILON {
88            ((x - self.x1) * dx + (y - self.y1) * dy) / len2
89        } else {
90            0.0
91        };
92        let t = apply_spread(t, self.spread);
93
94        sample_stops(&self.stops, t)
95    }
96}
97
98/// Radial/focal gradient fill paint expressed in local drawing coordinates.
99#[derive(Clone, Debug, PartialEq)]
100pub struct RadialGradientPaint {
101    pub cx: f64,
102    pub cy: f64,
103    pub r: f64,
104    pub fx: f64,
105    pub fy: f64,
106    pub transform: TransAffine,
107    pub spread: GradientSpread,
108    pub stops: Vec<GradientStop>,
109}
110
111impl RadialGradientPaint {
112    /// Convenience constructor for the common case: focal point at the centre,
113    /// identity transform, `Pad` spread. Stops are `(offset, color)` pairs.
114    pub fn centered(cx: f64, cy: f64, r: f64, stops: &[(f64, Color)]) -> Self {
115        Self {
116            cx,
117            cy,
118            r,
119            fx: cx,
120            fy: cy,
121            transform: TransAffine::default(),
122            spread: GradientSpread::Pad,
123            stops: stops
124                .iter()
125                .map(|(offset, color)| GradientStop {
126                    offset: *offset,
127                    color: *color,
128                })
129                .collect(),
130        }
131    }
132
133    pub fn sample(&self, mut x: f64, mut y: f64) -> Color {
134        if self.stops.is_empty() {
135            return Color::transparent();
136        }
137
138        self.transform.inverse_transform(&mut x, &mut y);
139
140        let dx = x - self.fx;
141        let dy = y - self.fy;
142        let fx = self.fx - self.cx;
143        let fy = self.fy - self.cy;
144        let a = dx * dx + dy * dy;
145        let t = if a <= f64::EPSILON || self.r <= f64::EPSILON {
146            0.0
147        } else {
148            let b = 2.0 * (fx * dx + fy * dy);
149            let c = fx * fx + fy * fy - self.r * self.r;
150            let disc = (b * b - 4.0 * a * c).max(0.0);
151            let k = (-b + disc.sqrt()) / (2.0 * a);
152            if k > f64::EPSILON {
153                1.0 / k
154            } else {
155                0.0
156            }
157        };
158        sample_stops(&self.stops, apply_spread(t, self.spread))
159    }
160}
161
162/// Repeating raster pattern paint expressed in SVG/user drawing coordinates.
163#[derive(Clone, Debug, PartialEq)]
164pub struct PatternPaint {
165    pub x: f64,
166    pub y: f64,
167    pub width: f64,
168    pub height: f64,
169    pub transform: TransAffine,
170    /// Straight-alpha RGBA tile pixels in bottom-up row order.
171    pub pixels: Arc<Vec<u8>>,
172    pub pixel_width: u32,
173    pub pixel_height: u32,
174}
175
176impl PatternPaint {
177    pub fn sample(&self, mut x: f64, mut y: f64) -> Color {
178        if self.width <= f64::EPSILON
179            || self.height <= f64::EPSILON
180            || self.pixel_width == 0
181            || self.pixel_height == 0
182            || self.pixels.is_empty()
183        {
184            return Color::transparent();
185        }
186
187        self.transform.inverse_transform(&mut x, &mut y);
188        let tx = (x - self.x).rem_euclid(self.width);
189        let ty_down = (y - self.y).rem_euclid(self.height);
190        let px = ((tx / self.width) * self.pixel_width as f64)
191            .floor()
192            .clamp(0.0, self.pixel_width.saturating_sub(1) as f64) as usize;
193        let py = (((self.height - ty_down) / self.height) * self.pixel_height as f64)
194            .floor()
195            .clamp(0.0, self.pixel_height.saturating_sub(1) as f64) as usize;
196        let i = (py * self.pixel_width as usize + px) * 4;
197        if i + 3 >= self.pixels.len() {
198            return Color::transparent();
199        }
200
201        Color::rgba(
202            self.pixels[i] as f32 / 255.0,
203            self.pixels[i + 1] as f32 / 255.0,
204            self.pixels[i + 2] as f32 / 255.0,
205            self.pixels[i + 3] as f32 / 255.0,
206        )
207    }
208}
209
210fn apply_spread(t: f64, spread: GradientSpread) -> f64 {
211    match spread {
212        GradientSpread::Pad => t.clamp(0.0, 1.0),
213        GradientSpread::Repeat => t - t.floor(),
214        GradientSpread::Reflect => {
215            let period = t.rem_euclid(2.0);
216            if period <= 1.0 {
217                period
218            } else {
219                2.0 - period
220            }
221        }
222    }
223}
224
225fn sample_stops(stops: &[GradientStop], t: f64) -> Color {
226    if t <= stops[0].offset {
227        return stops[0].color;
228    }
229    for pair in stops.windows(2) {
230        let a = pair[0];
231        let b = pair[1];
232        if t <= b.offset {
233            let span = (b.offset - a.offset).max(f64::EPSILON);
234            let u = ((t - a.offset) / span).clamp(0.0, 1.0) as f32;
235            return lerp_color(a.color, b.color, u);
236        }
237    }
238    stops[stops.len() - 1].color
239}
240
241fn lerp_color(a: Color, b: Color, t: f32) -> Color {
242    Color::rgba(
243        a.r + (b.r - a.r) * t,
244        a.g + (b.g - a.g) * t,
245        a.b + (b.b - a.b) * t,
246        a.a + (b.a - a.a) * t,
247    )
248}
249
250// ---------------------------------------------------------------------------
251// GL paint hook
252// ---------------------------------------------------------------------------
253
254/// Trait for widgets that want to render 3-D (or other GPU) content inline
255/// during the widget paint pass.
256///
257/// `DrawCtx::gl_paint` calls this with an opaque `gl` handle — implementations
258/// downcast it to `glow::Context` (or whatever GL type the platform provides).
259/// The software `GfxCtx` never calls `paint`; see [`DrawCtx::gl_paint`].
260pub trait GlPaint {
261    /// Execute GPU draw calls for the widget's 3-D content.
262    ///
263    /// `gl` — opaque platform GL context; downcast via `std::any::Any`.
264    /// `screen_rect` — Y-up screen-space rect for this widget (for viewport/scissor).
265    /// `full_w`, `full_h` — full viewport dimensions (for restoring after).
266    /// `parent_clip` — current framework scissor rect `[x, y, w, h]` in GL/Y-up
267    ///   pixels, or `None` if no clip is active.  Implementations **must intersect**
268    ///   any scissor they set with this rect so that parent widget clips (e.g. a
269    ///   collapsed window) correctly hide GPU-rendered content.
270    fn gl_paint(
271        &mut self,
272        gl: &dyn std::any::Any,
273        screen_rect: Rect,
274        full_w: i32,
275        full_h: i32,
276        parent_clip: Option<[i32; 4]>,
277    );
278}
279
280/// Unified 2-D drawing context.
281///
282/// All coordinate parameters use the **Y-up, first-quadrant** convention:
283/// origin at the bottom-left, positive-Y upward.  This matches `GfxCtx` and
284/// the widget tree layout invariant.
285pub trait DrawCtx {
286    /// Optional escape hatch for widgets that need direct access to a
287    /// backend-specific concrete context (e.g. to push a custom GPU draw
288    /// command into the deferred command stream).
289    ///
290    /// The default returns `None`; backends that opt in override to return
291    /// `Some(self)`.  Callers must handle the `None` case gracefully — if a
292    /// widget falls back through `gl_paint` it works on every backend.
293    fn as_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
294        None
295    }
296
297    // ── State ─────────────────────────────────────────────────────────────────
298
299    fn set_fill_color(&mut self, color: Color);
300    fn set_stroke_color(&mut self, color: Color);
301    fn set_fill_linear_gradient(&mut self, _gradient: LinearGradientPaint) {}
302    fn set_fill_radial_gradient(&mut self, _gradient: RadialGradientPaint) {}
303    fn set_fill_pattern(&mut self, _pattern: PatternPaint) {}
304    fn set_stroke_linear_gradient(&mut self, _gradient: LinearGradientPaint) {}
305    fn set_stroke_radial_gradient(&mut self, _gradient: RadialGradientPaint) {}
306    fn set_stroke_pattern(&mut self, _pattern: PatternPaint) {}
307    fn supports_fill_linear_gradient(&self) -> bool {
308        false
309    }
310    fn supports_fill_radial_gradient(&self) -> bool {
311        false
312    }
313    fn supports_fill_pattern(&self) -> bool {
314        false
315    }
316    fn supports_stroke_linear_gradient(&self) -> bool {
317        false
318    }
319    fn supports_stroke_radial_gradient(&self) -> bool {
320        false
321    }
322    fn supports_stroke_pattern(&self) -> bool {
323        false
324    }
325    fn set_line_width(&mut self, w: f64);
326    fn set_line_join(&mut self, join: LineJoin);
327    fn set_line_cap(&mut self, cap: LineCap);
328    fn set_miter_limit(&mut self, limit: f64);
329    fn set_line_dash(&mut self, dashes: &[f64], offset: f64);
330    fn set_blend_mode(&mut self, mode: CompOp);
331    fn set_global_alpha(&mut self, alpha: f64);
332    fn set_fill_rule(&mut self, rule: FillRule);
333
334    // ── Font ──────────────────────────────────────────────────────────────────
335
336    fn set_font(&mut self, font: Arc<Font>);
337    fn set_font_size(&mut self, size: f64);
338
339    // ── Clipping ──────────────────────────────────────────────────────────────
340
341    fn clip_rect(&mut self, x: f64, y: f64, w: f64, h: f64);
342    fn reset_clip(&mut self);
343
344    // ── Clear ─────────────────────────────────────────────────────────────────
345
346    /// Fill the entire render target with `color`, ignoring the current clip.
347    fn clear(&mut self, color: Color);
348
349    // ── Path building ─────────────────────────────────────────────────────────
350
351    fn begin_path(&mut self);
352    fn move_to(&mut self, x: f64, y: f64);
353    fn line_to(&mut self, x: f64, y: f64);
354    fn cubic_to(&mut self, cx1: f64, cy1: f64, cx2: f64, cy2: f64, x: f64, y: f64);
355    fn quad_to(&mut self, cx: f64, cy: f64, x: f64, y: f64);
356    fn arc_to(&mut self, cx: f64, cy: f64, r: f64, start_angle: f64, end_angle: f64, ccw: bool);
357
358    /// Add a full circle contour to the current path.
359    fn circle(&mut self, cx: f64, cy: f64, r: f64);
360
361    /// Add an axis-aligned rectangle contour to the current path.
362    fn rect(&mut self, x: f64, y: f64, w: f64, h: f64);
363
364    /// Add a rounded-rectangle contour to the current path.
365    fn rounded_rect(&mut self, x: f64, y: f64, w: f64, h: f64, r: f64);
366
367    fn close_path(&mut self);
368
369    // ── Path drawing ──────────────────────────────────────────────────────────
370
371    fn fill(&mut self);
372    fn stroke(&mut self);
373    fn fill_and_stroke(&mut self);
374
375    /// Submit **pre-tessellated** AA triangles with per-vertex coverage
376    /// (`x`, `y`, `alpha`) and triangle indices.
377    ///
378    /// This is the fast path for callers that tessellate their geometry
379    /// ONCE at load time (e.g. the Lion demo, SVG icons): they do the
380    /// `tessellate_path_aa` pass themselves, cache the vertex+index
381    /// buffers, then submit them every frame with only a cheap CPU
382    /// transform applied to the x/y components.  Compared to issuing
383    /// `move_to` / `line_to` / `fill` every frame, this keeps the polygon
384    /// set deterministic (no tess2 re-running on subtly-different
385    /// coordinates), avoids thousands of re-tessellations per frame, and
386    /// produces identical output regardless of the widget's transform.
387    ///
388    /// Vertices are `(x_logical_pixels, y_logical_pixels, alpha_0_to_1)`.
389    /// `alpha` is multiplied into the supplied `color.a` in the AA shader
390    /// so halo-strip edge AA survives this fast path.
391    ///
392    /// The software `GfxCtx` ignores the alpha attribute and rasterises
393    /// each triangle as a solid fill — correct but without edge AA, which
394    /// matches the software path's existing stroke/fill behaviour.
395    fn draw_triangles_aa(
396        &mut self,
397        vertices: &[[f32; 3]],
398        indices: &[u32],
399        color: crate::color::Color,
400    );
401
402    // ── Text ──────────────────────────────────────────────────────────────────
403
404    /// Draw `text` with the bottom of the baseline at `(x, y)`.
405    fn fill_text(&mut self, text: &str, x: f64, y: f64);
406
407    /// Draw `text` using the built-in AGG Glyph-Stroke-Vector font at `size`
408    /// pixels.  Useful before a proper font is loaded.
409    fn fill_text_gsv(&mut self, text: &str, x: f64, y: f64, size: f64);
410
411    /// Measure `text` with the current font and font-size settings.
412    fn measure_text(&self, text: &str) -> Option<TextMetrics>;
413
414    // ── Transform ─────────────────────────────────────────────────────────────
415
416    /// Current accumulated transform (CTM).
417    fn transform(&self) -> TransAffine;
418
419    /// Current transform expressed in the root render target's coordinate
420    /// space, even when drawing inside an offscreen layer whose local CTM was
421    /// reset to identity. Global overlays use this to submit app-level bounds.
422    fn root_transform(&self) -> TransAffine {
423        self.transform()
424    }
425
426    fn save(&mut self);
427    fn restore(&mut self);
428    fn translate(&mut self, tx: f64, ty: f64);
429    fn rotate(&mut self, radians: f64);
430    fn scale(&mut self, sx: f64, sy: f64);
431    fn set_transform(&mut self, m: TransAffine);
432    fn reset_transform(&mut self);
433
434    /// **Opt-in** pixel snapping.  Strips the fractional part of the current
435    /// CTM translation so subsequent integer-coordinate `rect` / `fill` /
436    /// `stroke` / `draw_image_rgba*` calls land exactly on the physical pixel
437    /// grid — no AA fringe on edges, no LINEAR-filter blur on 1:1 texture
438    /// blits.
439    ///
440    /// Call this ONLY when the widget genuinely wants pixel-aligned drawing
441    /// (text backbuffers, pixel-alignment diagnostics, crisp UI strokes).
442    /// Sub-pixel positioning remains the default — e.g. a smooth-scrolling
443    /// panel or an animated marker may legitimately want a fractional offset.
444    /// Typical usage:
445    /// ```ignore
446    /// ctx.save();
447    /// ctx.snap_to_pixel();
448    /// ctx.rect(0.0, 0.0, 10.0, 10.0);
449    /// ctx.fill();
450    /// ctx.restore();
451    /// ```
452    ///
453    /// Only the translation component is affected; rotations and non-uniform
454    /// scales pass through untouched (pixel alignment under those transforms
455    /// isn't well defined, and forcing a snap would visibly jitter rotated
456    /// content).
457    fn snap_to_pixel(&mut self) {
458        let t = self.transform();
459        let fx = t.tx - t.tx.floor();
460        let fy = t.ty - t.ty.floor();
461        if fx != 0.0 || fy != 0.0 {
462            self.translate(-fx, -fy);
463        }
464    }
465
466    // ── Compositing layers ────────────────────────────────────────────────────
467
468    /// Begin a new transparent compositing layer of the given pixel dimensions.
469    ///
470    /// All subsequent drawing (by this widget and its descendants) is redirected
471    /// into the new layer until [`pop_layer`] is called.  Layers nest: each
472    /// `push_layer` must be matched by exactly one `pop_layer`.
473    ///
474    /// The current accumulated transform records the layer's screen-space origin;
475    /// drawing inside the layer uses a fresh local-space transform (origin 0,0).
476    ///
477    /// Implementations that do not support layers (e.g. the GL path) may leave
478    /// this as a no-op — the widget renders pass-through into the parent target.
479    fn push_layer(&mut self, _width: f64, _height: f64) {}
480
481    /// Whether this backend implements real offscreen compositing layers.
482    ///
483    /// The default is `false` so widgets can opt into layer-based rendering
484    /// without forcing every backend to pay for, or emulate, that feature.
485    fn supports_compositing_layers(&self) -> bool {
486        false
487    }
488
489    /// Whether this backend can retain named offscreen layers across frames.
490    ///
491    /// Generic compositing support is enough for isolated opacity groups, but
492    /// retained widget backbuffers need a backend-owned surface keyed by ID.
493    fn supports_retained_layers(&self) -> bool {
494        false
495    }
496
497    /// Begin a new transparent compositing layer that will be multiplied by
498    /// `alpha` when composited back into the parent target.
499    ///
500    /// Backends that do not support layer alpha can fall back to `push_layer`;
501    /// callers gate this through [`supports_compositing_layers`].
502    fn push_layer_with_alpha(&mut self, width: f64, height: f64, _alpha: f64) {
503        self.push_layer(width, height);
504    }
505
506    /// Constrain subsequent drawing in the current layer to a rounded-rect
507    /// mask. Used by window layers after shadows are drawn so chrome/content
508    /// cannot write into rounded transparent corners.
509    ///
510    /// This is a containment clip, not the visual antialiasing edge. Backends
511    /// should leave enough room for partially-transparent edge pixels so the
512    /// caller's normal alpha coverage can feather corners and edges.
513    fn set_layer_rounded_clip(&mut self, _x: f64, _y: f64, _w: f64, _h: f64, _r: f64) {}
514
515    /// Composite a previously retained backend layer. Returns `true` when
516    /// the backend had a retained surface for `key` and drew it.
517    fn composite_retained_layer(
518        &mut self,
519        _key: u64,
520        _width: f64,
521        _height: f64,
522        _alpha: f64,
523    ) -> bool {
524        false
525    }
526
527    /// Begin rendering into a retained backend layer identified by `key`.
528    /// Backends that do not retain layers may fall back to a transient layer.
529    fn push_retained_layer_with_alpha(&mut self, _key: u64, width: f64, height: f64, alpha: f64) {
530        self.push_layer_with_alpha(width, height, alpha);
531    }
532
533    /// Composite the current layer back into the previous render target using
534    /// SrcOver alpha blending, then discard the layer.
535    ///
536    /// Must be called after a matching `push_layer`.  Unmatched calls are ignored.
537    fn pop_layer(&mut self) {}
538
539    // ── GL / GPU content ──────────────────────────────────────────────────────
540
541    /// Render GPU content (3-D scene, video frame, etc.) inline at the correct
542    /// painter-order position.
543    ///
544    /// `screen_rect` is the widget's screen-space rect in Y-up coordinates
545    /// (i.e. `ctx.transform()` origin + `widget.bounds().size`).
546    ///
547    /// The GL implementation executes `painter.gl_paint()` immediately so that
548    /// any 2-D widgets painted after this call naturally overdraw the GPU
549    /// content — correct back-to-front ordering with no post-frame fixup.
550    ///
551    /// The **software (`GfxCtx`) path is a no-op**: widgets should draw a 2-D
552    /// placeholder before calling this method so the software render has
553    /// something visible.
554    fn gl_paint(&mut self, _screen_rect: Rect, _painter: &mut dyn GlPaint) {}
555
556    // ── LCD mask compositing ──────────────────────────────────────────────────
557
558    /// Composite a pre-rasterized LCD subpixel mask onto the current
559    /// render target, mixing `src_color` into the destination through
560    /// per-channel coverage.
561    ///
562    /// `mask` is three bytes per pixel (`cov_r`, `cov_g`, `cov_b`) as
563    /// produced by [`crate::text_lcd::rasterize_lcd_mask`].  The caller
564    /// specifies `(dst_x, dst_y)` in local coordinates (Y-up in our
565    /// convention) and `mask_w × mask_h` to tell the backend the mask's
566    /// dimensions.
567    ///
568    /// Per-channel source-over blend:
569    /// ```text
570    /// dst.r = src.r * mask.r + dst.r * (1 - mask.r)
571    /// dst.g = src.g * mask.g + dst.g * (1 - mask.g)
572    /// dst.b = src.b * mask.b + dst.b * (1 - mask.b)
573    /// ```
574    ///
575    /// **This is the universal "composite LCD text onto arbitrary bg"
576    /// primitive** — it replaces the prior walk / sample / pre-fill
577    /// approach.  Software ctx implements it as an inner-loop blend; the
578    /// GL ctx implements it via a dual-source-blend fragment shader.
579    /// Backends that haven't wired it yet use the default no-op, which
580    /// makes callers fall back to grayscale AA.
581    fn draw_lcd_mask(
582        &mut self,
583        _mask: &[u8],
584        _mask_w: u32,
585        _mask_h: u32,
586        _src_color: Color,
587        _dst_x: f64,
588        _dst_y: f64,
589    ) {
590    }
591
592    /// Arc-keyed variant so GL backends can cache the uploaded texture
593    /// on the `Arc`'s pointer identity — one `glTexImage2D` per unique
594    /// raster, lifetime tied to the mask's strong-ref count.  Software
595    /// backends fall through to the slice path.
596    fn draw_lcd_mask_arc(
597        &mut self,
598        mask: &std::sync::Arc<Vec<u8>>,
599        mask_w: u32,
600        mask_h: u32,
601        src_color: Color,
602        dst_x: f64,
603        dst_y: f64,
604    ) {
605        self.draw_lcd_mask(mask.as_slice(), mask_w, mask_h, src_color, dst_x, dst_y);
606    }
607
608    /// Returns `true` if this backend supports [`draw_lcd_mask`] — i.e.
609    /// it can composite per-channel LCD coverage onto the active target.
610    /// Label queries this to decide between the LCD and grayscale AA
611    /// paths; a backend that returns `false` will never see LCD text.
612    fn has_lcd_mask_composite(&self) -> bool {
613        false
614    }
615
616    // ── Image blitting ────────────────────────────────────────────────────────
617
618    /// Returns `true` if this context implements `draw_image_rgba` with actual
619    /// pixel blitting.  `Label` (and any other widget that uses a software
620    /// backbuffer) gates its cache path on this method so it can fall back to
621    /// direct `fill_text()` on render targets that don't support blitting
622    /// (e.g. the GL path).
623    ///
624    /// Default: `false`.  Override to `true` in `GfxCtx`.
625    fn has_image_blit(&self) -> bool {
626        false
627    }
628
629    /// Draw raw RGBA pixel data into `dst_rect` (Y-up local coordinates).
630    ///
631    /// `data` must be `img_w * img_h * 4` bytes of tightly-packed RGBA8 data
632    /// in row-major order, **top-row first** (Y-down image storage convention).
633    /// The image is scaled to fit `(dst_x, dst_y, dst_w, dst_h)`.
634    ///
635    /// Default implementation: no-op (GL path or software paths that do not
636    /// implement blitting can leave this as a placeholder).
637    fn draw_image_rgba(
638        &mut self,
639        data: &[u8],
640        img_w: u32,
641        img_h: u32,
642        dst_x: f64,
643        dst_y: f64,
644        dst_w: f64,
645        dst_h: f64,
646    ) {
647        let _ = (data, img_w, img_h, dst_x, dst_y, dst_w, dst_h);
648    }
649
650    /// Same as [`draw_image_rgba`] but accepts an `Arc<Vec<u8>>` so the GL
651    /// backend can key its texture cache on the `Arc`'s pointer identity and
652    /// hold a `Weak` ref for automatic cleanup when the underlying buffer is
653    /// dropped — the pattern MatterCAD implements with C# `ConditionalWeakTable`.
654    ///
655    /// Used by `Label` (and future glyph-atlas consumers) in tandem with the
656    /// crate-level [`image_cache`](crate::image_cache) so that rebuilt widget
657    /// trees with unchanged content never re-rasterize OR re-upload.
658    ///
659    /// Default implementation: forward to [`draw_image_rgba`] via slice
660    /// borrow.  Software backends don't benefit from GPU texture caching so
661    /// the default is usually fine; the GL backend overrides.
662    fn draw_image_rgba_arc(
663        &mut self,
664        data: &std::sync::Arc<Vec<u8>>,
665        img_w: u32,
666        img_h: u32,
667        dst_x: f64,
668        dst_y: f64,
669        dst_w: f64,
670        dst_h: f64,
671    ) {
672        self.draw_image_rgba(data.as_slice(), img_w, img_h, dst_x, dst_y, dst_w, dst_h);
673    }
674
675    // ── LCD backbuffer blit ───────────────────────────────────────────────────
676
677    /// Composite a two-plane `LcdCoverage`-mode backbuffer onto the active
678    /// render target at `(dst_x, dst_y)` with size `(dst_w, dst_h)` (in
679    /// local coords).  Inputs are two `Arc<Vec<u8>>`, each 3 bytes per
680    /// pixel, **top-row-first**:
681    ///
682    /// - `color`: premultiplied per-channel RGB.
683    /// - `alpha`: per-channel alpha (coverage).
684    ///
685    /// The compositor applies per-channel premultiplied src-over:
686    ///
687    /// ```text
688    /// dst.ch := src.color_ch + dst.ch * (1 - src.alpha_ch)
689    /// ```
690    ///
691    /// which preserves LCD subpixel chroma through the cache round-trip.
692    /// Used by [`crate::widget::paint_subtree_backbuffered`] when a widget's
693    /// [`crate::widget::BackbufferMode::LcdCoverage`] cache is ready to
694    /// composite onto its parent.
695    ///
696    /// **Default:** collapses the two planes into a single straight-alpha
697    /// RGBA8 image (max of channel alphas, divided back to straight colour)
698    /// and forwards to [`draw_image_rgba`].  Correct for any content where
699    /// the three channel alphas agree; lossy of LCD chroma where they
700    /// diverge.  Backends that want full subpixel quality through the
701    /// cache override this with a two-texture shader path.
702    fn draw_lcd_backbuffer_arc(
703        &mut self,
704        color: &std::sync::Arc<Vec<u8>>,
705        alpha: &std::sync::Arc<Vec<u8>>,
706        w: u32,
707        h: u32,
708        dst_x: f64,
709        dst_y: f64,
710        dst_w: f64,
711        dst_h: f64,
712    ) {
713        // Collapse to straight-alpha RGBA8 on the fly.  Matches the same
714        // math `LcdBuffer::to_rgba8_top_down_collapsed` uses internally,
715        // except applied to a top-down pair rather than a Y-up pair.
716        let w_u = w as usize;
717        let h_u = h as usize;
718        if color.len() < w_u * h_u * 3 || alpha.len() < w_u * h_u * 3 {
719            return;
720        }
721        let mut rgba = vec![0u8; w_u * h_u * 4];
722        for i in 0..(w_u * h_u) {
723            let ci = i * 3;
724            let ra = alpha[ci];
725            let ga = alpha[ci + 1];
726            let ba = alpha[ci + 2];
727            let a = ra.max(ga).max(ba);
728            if a == 0 {
729                continue;
730            }
731            let af = a as f32 / 255.0;
732            let rc = color[ci] as f32 / 255.0;
733            let gc = color[ci + 1] as f32 / 255.0;
734            let bc = color[ci + 2] as f32 / 255.0;
735            let di = i * 4;
736            rgba[di] = ((rc / af) * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
737            rgba[di + 1] = ((gc / af) * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
738            rgba[di + 2] = ((bc / af) * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
739            rgba[di + 3] = a;
740        }
741        self.draw_image_rgba(&rgba, w, h, dst_x, dst_y, dst_w, dst_h);
742    }
743
744    // ── Screenshot capture (GPU-direct path) ──────────────────────────────────
745    //
746    // Hardware-accelerated screenshot pipeline.  The capture lives on the GPU
747    // as a backend-internal texture, so the live preview pane samples it
748    // directly with proper downsample filtering — no CPU readback per frame,
749    // no re-upload, no mipmap generation in the hot path.  Pixels are pulled
750    // back to system memory only when the user actually clicks Save / Copy.
751    //
752    // Default impls are no-ops returning `false` / empty so the software
753    // backend stays unchanged: the screenshot widget falls back to the
754    // existing `draw_image_rgba_arc` + Vec<u8> path automatically.
755
756    /// Snapshot the current frame's surface into the backend's internal
757    /// screenshot texture (allocating / resizing as needed).  Must be
758    /// called inside the active frame, after `end_frame` has flushed the
759    /// 2-D render but before the platform shell calls present.
760    ///
761    /// Returns `true` if the backend supports the capture path.
762    fn capture_screenshot(&mut self) -> bool {
763        false
764    }
765
766    /// True if a previously-captured screenshot is held by the backend
767    /// and available for [`Self::draw_captured_screenshot`].
768    fn has_captured_screenshot(&self) -> bool {
769        false
770    }
771
772    /// Dimensions of the held capture, or `None` when no capture exists.
773    fn captured_screenshot_size(&self) -> Option<(u32, u32)> {
774        None
775    }
776
777    /// Draw the held capture into `(dst_x, dst_y, dst_w, dst_h)` using the
778    /// backend's preferred filtered sampling.  Returns `true` if the
779    /// capture exists and was drawn.
780    fn draw_captured_screenshot(
781        &mut self,
782        _dst_x: f64,
783        _dst_y: f64,
784        _dst_w: f64,
785        _dst_h: f64,
786    ) -> bool {
787        false
788    }
789
790    /// Read the held capture's pixels back to CPU memory as Y-down RGBA8 —
791    /// for Save / Copy.  This is intentionally a single-shot synchronous
792    /// readback; widgets should NOT call this every frame.  Returns
793    /// `(empty, 0, 0)` on backends without a capture or without GPU
794    /// readback support.
795    fn read_captured_screenshot(&mut self) -> (Vec<u8>, u32, u32) {
796        (Vec::new(), 0, 0)
797    }
798
799    // ── Theme / Visuals ───────────────────────────────────────────────────────
800
801    /// Return the currently-active [`Visuals`] palette.
802    ///
803    /// Delegates to [`crate::theme::current_visuals`], which reads the
804    /// thread-local set by [`crate::theme::set_visuals`].  Widget `paint()`
805    /// implementations call this to get colours instead of hardcoding them.
806    fn visuals(&self) -> Visuals {
807        crate::theme::current_visuals()
808    }
809}