Skip to main content

rosace_widgets/tree/
custom_paint.rs

1use std::sync::Arc;
2
3use rosace_core::types::Size;
4use super::{Widget, LayoutCtx, PaintCtx};
5
6/// A leaf widget that draws with a closure (D100).
7///
8/// The closure receives the standard [`PaintCtx`] — it records DrawCommands
9/// into the display list like every built-in widget, so caching, replay,
10/// clipping, and HiDPI scaling all apply. It never touches pixels; for
11/// pixel-level control use [`DrawCommand::BlitRgba`].
12///
13/// ```rust,ignore
14/// CustomPaint::new(|cx, size| {
15///     cx.fill_circle(
16///         Point { x: cx.rect.origin.x + size.width / 2.0,
17///                 y: cx.rect.origin.y + size.height / 2.0 },
18///         size.width.min(size.height) / 2.0,
19///         Color::rgb(255, 111, 97),
20///     );
21/// })
22/// .size(120.0, 120.0)
23/// ```
24///
25/// Repaint coupling is automatic: read your atoms in the owning component's
26/// `build()` and the painter re-records whenever they change. (A per-widget
27/// `repaint_when` knob becomes meaningful once per-child picture caching
28/// lands — Phase 20 Step 5.)
29///
30/// [`DrawCommand::BlitRgba`]: rosace_render::DrawCommand::BlitRgba
31type PainterFn = Arc<dyn Fn(&mut PaintCtx, Size) + Send + Sync>;
32
33pub struct CustomPaint {
34    painter: PainterFn,
35    width: Option<f32>,
36    height: Option<f32>,
37}
38
39impl CustomPaint {
40    pub fn new(painter: impl Fn(&mut PaintCtx, Size) + Send + Sync + 'static) -> Self {
41        Self { painter: Arc::new(painter), width: None, height: None }
42    }
43
44    pub fn width(mut self, w: f32) -> Self { self.width = Some(w); self }
45    pub fn height(mut self, h: f32) -> Self { self.height = Some(h); self }
46    pub fn size(mut self, w: f32, h: f32) -> Self {
47        self.width = Some(w);
48        self.height = Some(h);
49        self
50    }
51}
52
53impl Widget for CustomPaint {
54    fn layout(&self, ctx: &LayoutCtx) -> Size {
55        let c = ctx.constraints;
56        // Explicit size wins; otherwise fill the available bounded space.
57        let w = self.width.unwrap_or_else(|| c.max_width_f32());
58        let h = self.height.unwrap_or_else(|| c.max_height_f32());
59        c.constrain(Size {
60            width:  if w.is_finite() { w } else { 0.0 },
61            height: if h.is_finite() { h } else { 0.0 },
62        })
63    }
64
65    fn paint(&self, ctx: &mut PaintCtx) {
66        let size = ctx.rect.size;
67        (self.painter)(ctx, size);
68    }
69}