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    // ── Clear ─────────────────────────────────────────────────────────────────
156
157    fn clear(&mut self, color: Color) {
158        self.commands.push(DrawCommand::Clear(color));
159    }
160
161    // ── Path building ─────────────────────────────────────────────────────────
162
163    fn begin_path(&mut self) {
164        self.path = PathStorage::new();
165    }
166
167    fn move_to(&mut self, x: f64, y: f64) {
168        self.path.move_to(x, y);
169    }
170
171    fn line_to(&mut self, x: f64, y: f64) {
172        self.path.line_to(x, y);
173    }
174
175    fn cubic_to(&mut self, cx1: f64, cy1: f64, cx2: f64, cy2: f64, x: f64, y: f64) {
176        self.path.curve4(cx1, cy1, cx2, cy2, x, y);
177    }
178
179    fn quad_to(&mut self, cx: f64, cy: f64, x: f64, y: f64) {
180        self.path.curve3(cx, cy, x, y);
181    }
182
183    fn arc_to(&mut self, cx: f64, cy: f64, r: f64, start_angle: f64, end_angle: f64, ccw: bool) {
184        let mut arc = AggArc::new(cx, cy, r, r, start_angle, end_angle, ccw);
185        self.path.concat_path(&mut arc, 0);
186    }
187
188    fn circle(&mut self, cx: f64, cy: f64, r: f64) {
189        self.arc_to(cx, cy, r, 0.0, std::f64::consts::TAU, true);
190        self.path.close_polygon(PATH_FLAGS_NONE);
191    }
192
193    fn rect(&mut self, x: f64, y: f64, w: f64, h: f64) {
194        self.path.move_to(x, y);
195        self.path.line_to(x + w, y);
196        self.path.line_to(x + w, y + h);
197        self.path.line_to(x, y + h);
198        self.path.close_polygon(PATH_FLAGS_NONE);
199    }
200
201    fn rounded_rect(&mut self, x: f64, y: f64, w: f64, h: f64, r: f64) {
202        let r = r.min(w * 0.5).min(h * 0.5).max(0.0);
203        let mut rr = RoundedRect::new(x, y, x + w, y + h, r);
204        rr.normalize_radius();
205        self.path.concat_path(&mut rr, 0);
206    }
207
208    fn close_path(&mut self) {
209        self.path.close_polygon(PATH_FLAGS_NONE);
210    }
211
212    // ── Path drawing ──────────────────────────────────────────────────────────
213
214    fn fill(&mut self) {
215        self.do_fill();
216    }
217
218    fn stroke(&mut self) {
219        self.do_stroke();
220    }
221
222    fn fill_and_stroke(&mut self) {
223        // Re-use the path for both operations; path is not consumed by tessellation.
224        self.do_fill();
225        self.do_stroke();
226    }
227
228    fn draw_triangles_aa(&mut self, vertices: &[[f32; 3]], indices: &[u32], color: Color) {
229        if vertices.is_empty() || indices.is_empty() {
230            return;
231        }
232        // Apply the current CTM to each vertex's XY; alpha passes through.
233        let ctm = *self.ctm();
234        let transformed: Vec<[f32; 3]> = vertices
235            .iter()
236            .map(|v| {
237                let (mut x, mut y) = (v[0] as f64, v[1] as f64);
238                ctm.transform(&mut x, &mut y);
239                [x as f32, y as f32, v[2]]
240            })
241            .collect();
242        self.commands.push(DrawCommand::AaSolid {
243            verts: transformed,
244            indices: indices.to_vec(),
245            color,
246            global_alpha: self.global_alpha as f32,
247            clip: self.current_clip(),
248        });
249    }
250
251    // ── Text ──────────────────────────────────────────────────────────────────
252
253    fn fill_text(&mut self, text: &str, x: f64, y: f64) {
254        self.fill_text_impl(text, x, y);
255    }
256
257    fn fill_text_gsv(&mut self, _text: &str, _x: f64, _y: f64, _size: f64) {
258        // GSV (Glyph-Stroke-Vector) font is AGG-specific; not available in the
259        // wgpu path.  Silently ignore — used only in placeholder widgets.
260    }
261
262    fn measure_text(&self, text: &str) -> Option<TextMetrics> {
263        let font = self.font.as_ref()?;
264        Some(measure_text_metrics(font, text, self.font_size))
265    }
266
267    // ── Image blitting ────────────────────────────────────────────────────────
268
269    fn has_image_blit(&self) -> bool {
270        true
271    }
272
273    fn draw_image_rgba(
274        &mut self,
275        data: &[u8],
276        img_w: u32,
277        img_h: u32,
278        dst_x: f64,
279        dst_y: f64,
280        dst_w: f64,
281        dst_h: f64,
282    ) {
283        self.draw_image_rgba_slice_impl(data, img_w, img_h, dst_x, dst_y, dst_w, dst_h);
284    }
285
286    fn draw_image_rgba_arc(
287        &mut self,
288        data: &std::sync::Arc<Vec<u8>>,
289        img_w: u32,
290        img_h: u32,
291        dst_x: f64,
292        dst_y: f64,
293        dst_w: f64,
294        dst_h: f64,
295    ) {
296        self.draw_image_rgba_arc_impl(data, img_w, img_h, dst_x, dst_y, dst_w, dst_h);
297    }
298
299    fn draw_image_rgba_corners(
300        &mut self,
301        data: &std::sync::Arc<Vec<u8>>,
302        img_w: u32,
303        img_h: u32,
304        corners: [(f64, f64); 4],
305    ) {
306        self.draw_image_rgba_corners_impl(data, img_w, img_h, corners);
307    }
308
309    fn has_lcd_mask_composite(&self) -> bool {
310        true
311    }
312
313    fn draw_lcd_mask(
314        &mut self,
315        mask: &[u8],
316        mask_w: u32,
317        mask_h: u32,
318        src_color: agg_gui::Color,
319        dst_x: f64,
320        dst_y: f64,
321    ) {
322        self.draw_lcd_mask_slice_impl(mask, mask_w, mask_h, src_color, dst_x, dst_y);
323    }
324
325    fn draw_lcd_mask_arc(
326        &mut self,
327        mask: &std::sync::Arc<Vec<u8>>,
328        mask_w: u32,
329        mask_h: u32,
330        src_color: agg_gui::Color,
331        dst_x: f64,
332        dst_y: f64,
333    ) {
334        self.draw_lcd_mask_arc_impl(mask, mask_w, mask_h, src_color, dst_x, dst_y);
335    }
336
337    fn draw_lcd_backbuffer_arc(
338        &mut self,
339        color: &std::sync::Arc<Vec<u8>>,
340        alpha: &std::sync::Arc<Vec<u8>>,
341        content_version: u64,
342        w: u32,
343        h: u32,
344        dst_x: f64,
345        dst_y: f64,
346        dst_w: f64,
347        dst_h: f64,
348    ) {
349        self.draw_lcd_backbuffer_arc_impl(
350            color,
351            alpha,
352            content_version,
353            w,
354            h,
355            dst_x,
356            dst_y,
357            dst_w,
358            dst_h,
359        );
360    }
361
362    // ── Transform ─────────────────────────────────────────────────────────────
363
364    fn transform(&self) -> TransAffine {
365        *self.ctm()
366    }
367
368    fn root_transform(&self) -> TransAffine {
369        let mut t = *self.ctm();
370        for layer in self.layer_stack.iter().rev() {
371            t.premultiply(&TransAffine::new_translation(
372                layer.origin_x,
373                layer.origin_y,
374            ));
375        }
376        t
377    }
378
379    fn save(&mut self) {
380        let top = *self.state_stack.last().unwrap();
381        self.state_stack.push(top);
382    }
383
384    fn restore(&mut self) {
385        if self.state_stack.len() > 1 {
386            self.state_stack.pop();
387            // Scissor is deferred; no GPU state to restore immediately.
388            self.apply_scissor();
389        }
390    }
391
392    fn translate(&mut self, tx: f64, ty: f64) {
393        self.ctm_mut()
394            .premultiply(&TransAffine::new_translation(tx, ty));
395    }
396
397    fn rotate(&mut self, radians: f64) {
398        self.ctm_mut()
399            .premultiply(&TransAffine::new_rotation(radians));
400    }
401
402    fn scale(&mut self, sx: f64, sy: f64) {
403        self.ctm_mut()
404            .premultiply(&TransAffine::new_scaling(sx, sy));
405    }
406
407    fn set_transform(&mut self, m: TransAffine) {
408        *self.ctm_mut() = m;
409    }
410
411    fn reset_transform(&mut self) {
412        *self.ctm_mut() = TransAffine::new();
413    }
414
415    // ── Compositing layers ────────────────────────────────────────────────────
416
417    fn supports_compositing_layers(&self) -> bool {
418        true
419    }
420
421    fn supports_retained_layers(&self) -> bool {
422        true
423    }
424
425    fn push_layer(&mut self, width: f64, height: f64) {
426        self.push_layer_with_alpha_impl(width, height, 1.0, None);
427    }
428
429    fn push_layer_with_alpha(&mut self, width: f64, height: f64, alpha: f64) {
430        self.push_layer_with_alpha_impl(width, height, alpha, None);
431    }
432
433    fn pop_layer(&mut self) {
434        self.pop_layer_impl();
435    }
436
437    fn set_layer_rounded_clip(&mut self, x: f64, y: f64, w: f64, h: f64, r: f64) {
438        self.set_layer_rounded_clip_impl(x, y, w, h, r);
439    }
440
441    fn set_layer_opaque_backdrop(&mut self, opaque: bool) {
442        self.set_layer_opaque_backdrop_impl(opaque);
443    }
444
445    fn composite_retained_layer(&mut self, key: u64, width: f64, height: f64, alpha: f64) -> bool {
446        self.composite_retained_layer_impl(key, width, height, alpha)
447    }
448
449    fn push_retained_layer_with_alpha(&mut self, key: u64, width: f64, height: f64, alpha: f64) {
450        self.push_layer_with_alpha_impl(width, height, alpha, Some(key));
451    }
452
453    // ── GL / GPU content ──────────────────────────────────────────────────────
454
455    fn gl_paint(&mut self, _screen_rect: agg_gui::Rect, _painter: &mut dyn agg_gui::GlPaint) {
456        // TEMPORARY: gl_paint is a no-op on the wgpu backend.
457        //
458        // The previous synchronous-flush approach caused two architectural
459        // problems when the painter ran inside a layer (e.g. the cube widget
460        // inside a "3D Animation" window):
461        //   1. `target_view` was hard-wired to the surface, so bar-grid draws
462        //      went to the surface and were then *covered* by the layer
463        //      composite quad on pop_layer.
464        //   2. The mid-frame `flush_to_surface` discarded the local target /
465        //      load-op state, so 2-D draws after the painter targeted the
466        //      surface instead of the active layer — and on tile-based GPUs
467        //      this manifested as a hang under continuous repaint.
468        //
469        // The clean fix is the deferred `DrawCommand::Custom` command, queued
470        // via an `as_any_mut` downcast on `DrawCtx` plus
471        // `WgpuGfxCtx::push_custom_render`, with the layer target plumbed
472        // through the deferred-flush state machine — see `custom_render`.
473        // GPU widgets should use that hook; `gl_paint` stays a no-op here.
474    }
475
476    // ── Screenshot capture (GPU-direct path) ──────────────────────────────────
477
478    fn capture_screenshot(&mut self) -> bool {
479        self.capture_screenshot_impl()
480    }
481
482    fn has_captured_screenshot(&self) -> bool {
483        self.capture_texture.is_some()
484    }
485
486    fn captured_screenshot_size(&self) -> Option<(u32, u32)> {
487        self.capture_texture.as_ref().map(|(_, _, w, h)| (*w, *h))
488    }
489
490    fn draw_captured_screenshot(&mut self, dst_x: f64, dst_y: f64, dst_w: f64, dst_h: f64) -> bool {
491        self.draw_captured_screenshot_impl(dst_x, dst_y, dst_w, dst_h)
492    }
493
494    fn read_captured_screenshot(&mut self) -> (Vec<u8>, u32, u32) {
495        self.read_captured_screenshot_impl()
496    }
497}