Skip to main content

agg_gui_wgpu/
draw_ctx_impl.rs

1//! `impl DrawCtx for WgpuGfxCtx` — the full drawing interface.
2//!
3//! State setters and path-building methods are fully implemented here; they
4//! work from Phase 1 onwards.  Rendering methods (`fill`, `stroke`, `fill_text`,
5//! etc.) push [`DrawCommand`] entries into `self.commands` and are flushed by
6//! [`WgpuGfxCtx::end_frame`] (implemented in Phase 4).
7//!
8//! The screenshot-capture methods delegate to `screenshot_capture.rs`, which
9//! owns the capture texture, its textured re-draw, and both readback paths.
10
11use super::*;
12use agg_gui::draw_ctx::DrawCtx;
13use agg_gui::text::{measure_text_metrics, TextMetrics};
14use agg_gui::CompOp;
15use agg_rust::arc::Arc as AggArc;
16use agg_rust::basics::PATH_FLAGS_NONE;
17use agg_rust::rounded_rect::RoundedRect;
18
19impl DrawCtx for WgpuGfxCtx {
20    fn as_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
21        Some(self as &mut dyn std::any::Any)
22    }
23
24    // ── State ─────────────────────────────────────────────────────────────────
25
26    fn set_fill_color(&mut self, c: Color) {
27        self.fill_color = c;
28        self.fill_linear_gradient = None;
29        self.fill_radial_gradient = None;
30    }
31
32    fn set_fill_linear_gradient(&mut self, gradient: LinearGradientPaint) {
33        self.fill_linear_gradient = Some(gradient);
34        self.fill_radial_gradient = None;
35    }
36
37    fn supports_fill_linear_gradient(&self) -> bool {
38        true
39    }
40
41    fn set_fill_radial_gradient(&mut self, gradient: RadialGradientPaint) {
42        self.fill_linear_gradient = None;
43        self.fill_radial_gradient = Some(gradient);
44    }
45
46    fn supports_fill_radial_gradient(&self) -> bool {
47        true
48    }
49
50    fn set_stroke_color(&mut self, c: Color) {
51        self.stroke_color = c;
52        self.stroke_linear_gradient = None;
53        self.stroke_radial_gradient = None;
54    }
55
56    fn set_stroke_linear_gradient(&mut self, gradient: LinearGradientPaint) {
57        self.stroke_linear_gradient = Some(gradient);
58        self.stroke_radial_gradient = None;
59    }
60
61    fn supports_stroke_linear_gradient(&self) -> bool {
62        true
63    }
64
65    fn set_stroke_radial_gradient(&mut self, gradient: RadialGradientPaint) {
66        self.stroke_linear_gradient = None;
67        self.stroke_radial_gradient = Some(gradient);
68    }
69
70    fn supports_stroke_radial_gradient(&self) -> bool {
71        true
72    }
73
74    fn set_line_width(&mut self, w: f64) {
75        self.line_width = w;
76    }
77
78    fn set_line_join(&mut self, j: LineJoin) {
79        self.line_join = j;
80    }
81
82    fn set_line_cap(&mut self, c: LineCap) {
83        self.line_cap = c;
84    }
85
86    fn set_miter_limit(&mut self, limit: f64) {
87        self.miter_limit = limit.max(1.0);
88    }
89
90    fn set_line_dash(&mut self, dashes: &[f64], offset: f64) {
91        self.line_dash.clear();
92        self.line_dash
93            .extend(dashes.iter().copied().filter(|v| *v > 0.0));
94        self.dash_offset = offset;
95    }
96
97    fn set_blend_mode(&mut self, _mode: CompOp) {
98        // wgpu blend state is baked into pipeline objects at creation time.
99        // Dynamic blend-mode changes are not supported in the initial port.
100    }
101
102    fn set_global_alpha(&mut self, a: f64) {
103        self.global_alpha = a;
104    }
105
106    fn set_fill_rule(&mut self, rule: agg_gui::draw_ctx::FillRule) {
107        self.fill_rule = rule;
108    }
109
110    // ── Font ──────────────────────────────────────────────────────────────────
111
112    fn set_font(&mut self, font: std::sync::Arc<Font>) {
113        self.font = Some(font);
114    }
115
116    fn set_font_size(&mut self, size: f64) {
117        self.font_size = size;
118    }
119
120    // ── Clipping ──────────────────────────────────────────────────────────────
121
122    fn clip_rect(&mut self, x: f64, y: f64, w: f64, h: f64) {
123        // Transform clip corners through the CTM to screen space.
124        let (mut x0, mut y0) = (x, y);
125        let (mut x1, mut y1) = (x + w, y + h);
126        self.ctm().transform(&mut x0, &mut y0);
127        self.ctm().transform(&mut x1, &mut y1);
128        let (lx, rx) = if x0 < x1 { (x0, x1) } else { (x1, x0) };
129        let (by, ty) = if y0 < y1 { (y0, y1) } else { (y1, y0) };
130        let [nx, ny, nw, nh] = Self::compute_scissor(lx, by, rx, ty);
131
132        // Intersect with the existing scissor so parent clips constrain children.
133        let [ix, iy, iw, ih] = if let Some([ex, ey, ew, eh]) = self.current_clip() {
134            let nx2 = nx.saturating_add(nw).min(ex.saturating_add(ew));
135            let ny2 = ny.saturating_add(nh).min(ey.saturating_add(eh));
136            let rx2 = nx.max(ex);
137            let ry2 = ny.max(ey);
138            [
139                rx2,
140                ry2,
141                nx2.saturating_sub(rx2).max(0),
142                ny2.saturating_sub(ry2).max(0),
143            ]
144        } else {
145            [nx, ny, nw, nh]
146        };
147
148        self.state_stack.last_mut().unwrap().1 = Some([ix, iy, iw, ih]);
149    }
150
151    fn reset_clip(&mut self) {
152        self.state_stack.last_mut().unwrap().1 = None;
153    }
154
155    fn clip_path(&mut self) {
156        self.clip_path_impl();
157    }
158
159    fn supports_clip_path(&self) -> bool {
160        true
161    }
162
163    // ── Clear ─────────────────────────────────────────────────────────────────
164
165    fn clear(&mut self, color: Color) {
166        self.commands.push(DrawCommand::Clear(color));
167    }
168
169    // ── Path building ─────────────────────────────────────────────────────────
170
171    fn begin_path(&mut self) {
172        self.path = PathStorage::new();
173    }
174
175    fn move_to(&mut self, x: f64, y: f64) {
176        self.path.move_to(x, y);
177    }
178
179    fn line_to(&mut self, x: f64, y: f64) {
180        self.path.line_to(x, y);
181    }
182
183    fn cubic_to(&mut self, cx1: f64, cy1: f64, cx2: f64, cy2: f64, x: f64, y: f64) {
184        self.path.curve4(cx1, cy1, cx2, cy2, x, y);
185    }
186
187    fn quad_to(&mut self, cx: f64, cy: f64, x: f64, y: f64) {
188        self.path.curve3(cx, cy, x, y);
189    }
190
191    fn arc_to(&mut self, cx: f64, cy: f64, r: f64, start_angle: f64, end_angle: f64, ccw: bool) {
192        let mut arc = AggArc::new(cx, cy, r, r, start_angle, end_angle, ccw);
193        self.path.concat_path(&mut arc, 0);
194    }
195
196    fn circle(&mut self, cx: f64, cy: f64, r: f64) {
197        self.arc_to(cx, cy, r, 0.0, std::f64::consts::TAU, true);
198        self.path.close_polygon(PATH_FLAGS_NONE);
199    }
200
201    fn rect(&mut self, x: f64, y: f64, w: f64, h: f64) {
202        self.path.move_to(x, y);
203        self.path.line_to(x + w, y);
204        self.path.line_to(x + w, y + h);
205        self.path.line_to(x, y + h);
206        self.path.close_polygon(PATH_FLAGS_NONE);
207    }
208
209    fn rounded_rect(&mut self, x: f64, y: f64, w: f64, h: f64, r: f64) {
210        let r = r.min(w * 0.5).min(h * 0.5).max(0.0);
211        let mut rr = RoundedRect::new(x, y, x + w, y + h, r);
212        rr.normalize_radius();
213        self.path.concat_path(&mut rr, 0);
214    }
215
216    fn close_path(&mut self) {
217        self.path.close_polygon(PATH_FLAGS_NONE);
218    }
219
220    // ── Path drawing ──────────────────────────────────────────────────────────
221
222    fn fill(&mut self) {
223        self.do_fill();
224    }
225
226    fn stroke(&mut self) {
227        self.do_stroke();
228    }
229
230    fn fill_and_stroke(&mut self) {
231        // Re-use the path for both operations; path is not consumed by tessellation.
232        self.do_fill();
233        self.do_stroke();
234    }
235
236    fn draw_triangles_aa(&mut self, vertices: &[[f32; 3]], indices: &[u32], color: Color) {
237        if vertices.is_empty() || indices.is_empty() {
238            return;
239        }
240        // Apply the current CTM to each vertex's XY; alpha passes through.
241        let ctm = *self.ctm();
242        let transformed: Vec<[f32; 3]> = vertices
243            .iter()
244            .map(|v| {
245                let (mut x, mut y) = (v[0] as f64, v[1] as f64);
246                ctm.transform(&mut x, &mut y);
247                [x as f32, y as f32, v[2]]
248            })
249            .collect();
250        self.commands.push(DrawCommand::AaSolid {
251            verts: transformed,
252            indices: indices.to_vec(),
253            color,
254            global_alpha: self.global_alpha as f32,
255            clip: self.current_clip(),
256        });
257    }
258
259    // ── Text ──────────────────────────────────────────────────────────────────
260
261    fn fill_text(&mut self, text: &str, x: f64, y: f64) {
262        self.fill_text_impl(text, x, y);
263    }
264
265    fn fill_text_gsv(&mut self, _text: &str, _x: f64, _y: f64, _size: f64) {
266        // GSV (Glyph-Stroke-Vector) font is AGG-specific; not available in the
267        // wgpu path.  Silently ignore — used only in placeholder widgets.
268    }
269
270    fn measure_text(&self, text: &str) -> Option<TextMetrics> {
271        let font = self.font.as_ref()?;
272        Some(measure_text_metrics(font, text, self.font_size))
273    }
274
275    // ── Image blitting ────────────────────────────────────────────────────────
276
277    fn has_image_blit(&self) -> bool {
278        true
279    }
280
281    fn draw_image_rgba(
282        &mut self,
283        data: &[u8],
284        img_w: u32,
285        img_h: u32,
286        dst_x: f64,
287        dst_y: f64,
288        dst_w: f64,
289        dst_h: f64,
290    ) {
291        self.draw_image_rgba_slice_impl(data, img_w, img_h, dst_x, dst_y, dst_w, dst_h);
292    }
293
294    fn draw_image_rgba_arc(
295        &mut self,
296        data: &std::sync::Arc<Vec<u8>>,
297        img_w: u32,
298        img_h: u32,
299        dst_x: f64,
300        dst_y: f64,
301        dst_w: f64,
302        dst_h: f64,
303    ) {
304        self.draw_image_rgba_arc_impl(data, img_w, img_h, dst_x, dst_y, dst_w, dst_h);
305    }
306
307    fn draw_image_rgba_corners(
308        &mut self,
309        data: &std::sync::Arc<Vec<u8>>,
310        img_w: u32,
311        img_h: u32,
312        corners: [(f64, f64); 4],
313    ) {
314        self.draw_image_rgba_corners_impl(data, img_w, img_h, corners);
315    }
316
317    fn has_lcd_mask_composite(&self) -> bool {
318        true
319    }
320
321    fn draw_lcd_mask(
322        &mut self,
323        mask: &[u8],
324        mask_w: u32,
325        mask_h: u32,
326        src_color: agg_gui::Color,
327        dst_x: f64,
328        dst_y: f64,
329    ) {
330        self.draw_lcd_mask_slice_impl(mask, mask_w, mask_h, src_color, dst_x, dst_y);
331    }
332
333    fn draw_lcd_mask_arc(
334        &mut self,
335        mask: &std::sync::Arc<Vec<u8>>,
336        mask_w: u32,
337        mask_h: u32,
338        src_color: agg_gui::Color,
339        dst_x: f64,
340        dst_y: f64,
341    ) {
342        self.draw_lcd_mask_arc_impl(mask, mask_w, mask_h, src_color, dst_x, dst_y);
343    }
344
345    fn draw_lcd_backbuffer_arc(
346        &mut self,
347        color: &std::sync::Arc<Vec<u8>>,
348        alpha: &std::sync::Arc<Vec<u8>>,
349        content_version: u64,
350        w: u32,
351        h: u32,
352        dst_x: f64,
353        dst_y: f64,
354        dst_w: f64,
355        dst_h: f64,
356    ) {
357        self.draw_lcd_backbuffer_arc_impl(
358            color,
359            alpha,
360            content_version,
361            w,
362            h,
363            dst_x,
364            dst_y,
365            dst_w,
366            dst_h,
367        );
368    }
369
370    // ── Transform ─────────────────────────────────────────────────────────────
371
372    fn transform(&self) -> TransAffine {
373        *self.ctm()
374    }
375
376    fn root_transform(&self) -> TransAffine {
377        let mut t = *self.ctm();
378        for layer in self.layer_stack.iter().rev() {
379            t.premultiply(&TransAffine::new_translation(
380                layer.origin_x,
381                layer.origin_y,
382            ));
383        }
384        t
385    }
386
387    fn save(&mut self) {
388        let top = *self.state_stack.last().unwrap();
389        self.state_stack.push(top);
390    }
391
392    fn restore(&mut self) {
393        // A clip layer owns the state that was live when `clip_path()` ran, so
394        // a single-entry local stack means this restore ends the clip's scope:
395        // composite the clip layer(s) first, then do the normal restore.
396        while self.state_stack.len() == 1
397            && self
398                .layer_stack
399                .last()
400                .is_some_and(|l| l.clip_mask.is_some())
401        {
402            self.pop_layer_impl();
403        }
404        if self.state_stack.len() > 1 {
405            self.state_stack.pop();
406            // Scissor is deferred; no GPU state to restore immediately.
407            self.apply_scissor();
408        }
409    }
410
411    fn translate(&mut self, tx: f64, ty: f64) {
412        self.ctm_mut()
413            .premultiply(&TransAffine::new_translation(tx, ty));
414    }
415
416    fn rotate(&mut self, radians: f64) {
417        self.ctm_mut()
418            .premultiply(&TransAffine::new_rotation(radians));
419    }
420
421    fn scale(&mut self, sx: f64, sy: f64) {
422        self.ctm_mut()
423            .premultiply(&TransAffine::new_scaling(sx, sy));
424    }
425
426    fn set_transform(&mut self, m: TransAffine) {
427        *self.ctm_mut() = m;
428    }
429
430    fn reset_transform(&mut self) {
431        *self.ctm_mut() = TransAffine::new();
432    }
433
434    // ── Compositing layers ────────────────────────────────────────────────────
435
436    fn supports_compositing_layers(&self) -> bool {
437        true
438    }
439
440    fn supports_retained_layers(&self) -> bool {
441        true
442    }
443
444    fn push_layer(&mut self, width: f64, height: f64) {
445        self.push_layer_with_alpha_impl(width, height, 1.0, None);
446    }
447
448    fn push_layer_with_alpha(&mut self, width: f64, height: f64, alpha: f64) {
449        self.push_layer_with_alpha_impl(width, height, alpha, None);
450    }
451
452    fn pop_layer(&mut self) {
453        self.pop_layer_impl();
454    }
455
456    fn set_layer_rounded_clip(&mut self, x: f64, y: f64, w: f64, h: f64, r: f64) {
457        self.set_layer_rounded_clip_impl(x, y, w, h, r);
458    }
459
460    fn set_layer_opaque_backdrop(&mut self, opaque: bool) {
461        self.set_layer_opaque_backdrop_impl(opaque);
462    }
463
464    fn composite_retained_layer(&mut self, key: u64, width: f64, height: f64, alpha: f64) -> bool {
465        self.composite_retained_layer_impl(key, width, height, alpha)
466    }
467
468    fn push_retained_layer_with_alpha(&mut self, key: u64, width: f64, height: f64, alpha: f64) {
469        self.push_layer_with_alpha_impl(width, height, alpha, Some(key));
470    }
471
472    // ── GL / GPU content ──────────────────────────────────────────────────────
473
474    fn gl_paint(&mut self, _screen_rect: agg_gui::Rect, _painter: &mut dyn agg_gui::GlPaint) {
475        // TEMPORARY: gl_paint is a no-op on the wgpu backend.
476        //
477        // The previous synchronous-flush approach caused two architectural
478        // problems when the painter ran inside a layer (e.g. the cube widget
479        // inside a "3D Animation" window):
480        //   1. `target_view` was hard-wired to the surface, so bar-grid draws
481        //      went to the surface and were then *covered* by the layer
482        //      composite quad on pop_layer.
483        //   2. The mid-frame `flush_to_surface` discarded the local target /
484        //      load-op state, so 2-D draws after the painter targeted the
485        //      surface instead of the active layer — and on tile-based GPUs
486        //      this manifested as a hang under continuous repaint.
487        //
488        // The clean fix is the deferred `DrawCommand::Custom` command, queued
489        // via an `as_any_mut` downcast on `DrawCtx` plus
490        // `WgpuGfxCtx::push_custom_render`, with the layer target plumbed
491        // through the deferred-flush state machine — see `custom_render`.
492        // GPU widgets should use that hook; `gl_paint` stays a no-op here.
493    }
494
495    // ── Screenshot capture (GPU-direct path) ──────────────────────────────────
496
497    fn capture_screenshot(&mut self) -> bool {
498        self.capture_screenshot_impl()
499    }
500
501    fn has_captured_screenshot(&self) -> bool {
502        self.capture_texture.is_some()
503    }
504
505    fn captured_screenshot_size(&self) -> Option<(u32, u32)> {
506        self.capture_texture.as_ref().map(|(_, _, w, h)| (*w, *h))
507    }
508
509    fn draw_captured_screenshot(&mut self, dst_x: f64, dst_y: f64, dst_w: f64, dst_h: f64) -> bool {
510        self.draw_captured_screenshot_impl(dst_x, dst_y, dst_w, dst_h)
511    }
512
513    fn read_captured_screenshot(&mut self) -> (Vec<u8>, u32, u32) {
514        self.read_captured_screenshot_impl()
515    }
516}