fui/node/
custom_drawable.rs1use super::core::*;
2use super::*;
3
4#[derive(Clone)]
5pub struct CustomDrawable {
24 base: FlexBox,
25 draw_callback: DrawCallback,
26}
27
28#[derive(Clone)]
29pub struct DrawableInvalidator {
31 base: WeakFlexBox,
32}
33
34impl DrawableInvalidator {
35 pub fn mark_dirty(&self) {
37 if let Some(base) = self.base.upgrade() {
38 mark_base_dirty(&base);
39 }
40 }
41}
42
43impl CustomDrawable {
44 pub fn new(handler: impl Fn(&mut DrawContext) + 'static) -> Self {
49 let base = FlexBox::default();
50 base.custom_drawable(true);
51 Self {
52 base,
53 draw_callback: Rc::new(handler),
54 }
55 }
56
57 pub fn mark_dirty(&self) {
59 mark_base_dirty(&self.base);
60 }
61
62 pub fn invalidator(&self) -> DrawableInvalidator {
64 DrawableInvalidator {
65 base: self.base.downgrade(),
66 }
67 }
68}
69
70fn mark_base_dirty(base: &FlexBox) {
71 let handle = base.handle();
72 if handle != NodeHandle::INVALID {
73 let Some(bounds) = ui::get_visible_bounds(handle.raw()) else {
74 return;
75 };
76 if bounds[2] <= 0.0 || bounds[3] <= 0.0 {
77 return;
78 }
79 }
80 crate::frame_scheduler::mark_needs_commit();
81}
82
83impl Node for CustomDrawable {
84 fn retained_node_ref(&self) -> NodeRef {
85 NodeRef::from_node(self.base.core.clone(), self.clone())
86 }
87
88 fn build_self(&self) {
89 self.base.build_self();
90 let weak_base = self.base.downgrade();
91 let draw_callback = self.draw_callback.clone();
92 self.base.core.borrow_mut().draw_callback = Some(Rc::new(move |ctx| {
93 let Some(base) = weak_base.upgrade() else {
94 return;
95 };
96 let bounds = base.get_bounds();
97 let (tl, tr, br, bl) = base
98 .props
99 .borrow()
100 .box_style
101 .map(|style| {
102 (
103 style.radius_tl,
104 style.radius_tr,
105 style.radius_br,
106 style.radius_bl,
107 )
108 })
109 .unwrap_or((0.0, 0.0, 0.0, 0.0));
110
111 ctx.save();
112 if tl > 0.0 || tr > 0.0 || br > 0.0 || bl > 0.0 {
113 ctx.clip_round_rect(0.0, 0.0, bounds[2], bounds[3], tl, tr, br, bl);
114 } else {
115 ctx.clip_rect(0.0, 0.0, bounds[2], bounds[3]);
116 }
117 draw_callback(ctx);
118 ctx.restore();
119 ctx.flush();
120 }));
121 }
122}
123
124impl HasFlexBoxRoot for CustomDrawable {
125 fn flex_box_root(&self) -> &FlexBox {
126 &self.base
127 }
128}
129
130impl ThemeBindable for CustomDrawable {
131 fn theme_binding_node(&self) -> NodeRef {
132 self.base.retained_node_ref()
133 }
134
135 fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
136 let weak_base = self.base.downgrade();
137 let draw_callback = self.draw_callback.clone();
138 Box::new(move || {
139 weak_base.upgrade().map(|base| Self {
140 base,
141 draw_callback: draw_callback.clone(),
142 })
143 })
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use crate::app::Application;
151 use crate::ffi::{self, Call};
152
153 fn assert_flex_box_surface<T: FlexBoxSurface>() {}
154 fn assert_theme_bindable<T: ThemeBindable>() {}
155
156 #[test]
157 fn custom_drawable_exposes_generic_retained_visual_surfaces() {
158 assert_flex_box_surface::<CustomDrawable>();
159 assert_theme_bindable::<CustomDrawable>();
160
161 let drawable = CustomDrawable::new(|_| {});
162 drawable
163 .width(300.0, Unit::Pixel)
164 .height(200.0, Unit::Pixel)
165 .min_width(120.0, Unit::Pixel)
166 .margin(1.0, 2.0, 3.0, 4.0)
167 .padding(5.0, 6.0, 7.0, 8.0)
168 .corner_radius(12.0)
169 .bg_color(0x112233FF)
170 .clip_to_bounds(true);
171
172 let invalidator = drawable.invalidator();
173 drop(drawable);
174 invalidator.mark_dirty();
175 }
176
177 #[test]
178 fn custom_drawable_requests_focus_through_node_trait() {
179 ffi::test::reset();
180 let drawable = CustomDrawable::new(|_| {});
181 drawable.focusable(true, 0);
182 Application::mount(drawable.clone());
183 let handle = drawable.handle().raw();
184 ffi::test::take_calls();
185
186 drawable.focus_now();
187
188 let calls = ffi::test::take_calls();
189 assert!(calls.iter().any(
190 |call| matches!(call, Call::RequestFocus { handle: requested } if *requested == handle)
191 ));
192 Application::unmount();
193 }
194}