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}