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