Skip to main content

blitz_dom/node/
custom_widget.rs

1use std::any::Any;
2
3use anyrender::ResourceId;
4use blitz_traits::events::UiEvent;
5pub use style::properties::ComputedValues as ComputedStyles;
6// use accesskit::Node as AccessKitNode;
7// use taffy::{LayoutInput, LayoutOutput};
8
9pub use anyrender::{RenderContext, Scene};
10
11use crate::BaseDocument;
12
13impl BaseDocument {
14    pub fn can_create_surfaces(&mut self, render_context: &mut dyn RenderContext) {
15        for &node_id in self.custom_widget_nodes.iter() {
16            let node = &mut self.nodes[node_id];
17            if let Some(widget_data) = node
18                .element_data_mut()
19                .and_then(|el| el.custom_widget_data_mut())
20            {
21                let mut render_context = ProxyRenderContext {
22                    resource_ids: &mut widget_data.active_resource_ids,
23                    inner: render_context,
24                };
25
26                widget_data
27                    .widget
28                    .can_create_surfaces(&mut render_context as _);
29            }
30        }
31    }
32
33    pub fn destroy_surfaces(&mut self) {
34        for &node_id in self.custom_widget_nodes.iter() {
35            let node = &mut self.nodes[node_id];
36            if let Some(widget_data) = node
37                .element_data_mut()
38                .and_then(|el| el.custom_widget_data_mut())
39            {
40                widget_data.widget.destroy_surfaces();
41            }
42        }
43    }
44}
45
46/// A `RenderContext` that proxies resource registrations through to an inner `RenderContext`
47/// and also keeps track of the `ResourceId`s of all sucessfully registered resources so that
48/// they can be automatically unregistered if the Widget's node is dropped.
49pub struct ProxyRenderContext<'widget, 'rend> {
50    pub resource_ids: &'widget mut Vec<ResourceId>,
51    pub inner: &'rend mut dyn RenderContext,
52}
53
54impl anyrender::RenderContext for ProxyRenderContext<'_, '_> {
55    fn try_register_custom_resource(
56        &mut self,
57        resource: Box<dyn Any>,
58    ) -> Result<ResourceId, anyrender::RegisterResourceError> {
59        let id = self.inner.try_register_custom_resource(resource)?;
60        self.resource_ids.push(id);
61        Ok(id)
62    }
63
64    fn unregister_resource(&mut self, resource_id: ResourceId) {
65        self.resource_ids.retain(|id| *id != resource_id);
66        self.inner.unregister_resource(resource_id);
67    }
68
69    fn renderer_specific_context(&self) -> Option<Box<dyn std::any::Any>> {
70        self.inner.renderer_specific_context()
71    }
72}
73
74pub trait Widget {
75    // DOM lifecycle
76
77    /// The widget was attached to the DOM
78    fn connected(&mut self) {}
79    /// The widget was removed from the DOM
80    fn disconnected(&mut self) {}
81    /// One of the widget's attributes changed
82    fn attribute_changed(&mut self, name: &str, old_value: Option<&str>, new_value: Option<&str>) {
83        let _ = (name, old_value, new_value);
84    }
85
86    // Renderer lifecycle
87
88    /// The renderer is active
89    ///
90    /// `ctx` parameter can be downcast to get access to renderer-specific contexts (e.g. the WGPU Device and Queue)
91    fn can_create_surfaces(&mut self, render_ctx: &mut dyn RenderContext) {
92        let _ = render_ctx;
93    }
94    /// The renderer is no longer active (destroy textures here)
95    fn destroy_surfaces(&mut self) {}
96
97    // Other
98
99    /// Whether the widget currently requires redraws (e.g. because it is animating).
100    ///
101    /// Returning `true` causes the document to continuously schedule redraws
102    /// (and hence repaints of the widget). Static widgets should return `false`.
103    fn requires_redraw(&self) -> bool {
104        false
105    }
106
107    /// Handle input events (mouse, keyboard, etc)
108    fn handle_event(&mut self, event: &UiEvent) {
109        let _ = event;
110    }
111
112    /// Callback for the widget to paint it's content.
113    ///
114    /// Output is recorded to an AnyRender `Scene`.
115    /// If the widget wants to render to a WGPU texture or similar then it should:
116    ///   - Get a handle to the Device and Queue in `can_create_surfaces`
117    ///   - Create it's own texture
118    ///   - Pass the `ResourceId` of the paint for an Image in the AnyRender `Scene`
119    fn paint(
120        &mut self,
121        render_ctx: &mut dyn RenderContext,
122        styles: &ComputedStyles,
123        width: u32,
124        height: u32,
125        scale: f64,
126    ) -> Scene {
127        let _ = (render_ctx, styles, width, height, scale);
128        Scene::new()
129    }
130
131    // TODO: allow for multiple nodes per widget
132    // fn accessibility_tree(&mut self) -> AccessKitNode;
133
134    // TODO: simpler layout mode?
135    // fn layout(&mut self, inputs: LayoutInput, styles: &ComputedStyles) -> LayoutOutput;
136}
137
138#[derive(Debug, Copy, Clone, Eq, PartialEq)]
139pub enum CustomWidgetStatus {
140    Suspended,
141    Active,
142    PendingRemoval,
143}
144
145pub struct CustomWidgetData {
146    /// The custom widget
147    pub widget: Box<dyn Widget>,
148    /// The custom widget's status
149    pub status: CustomWidgetStatus,
150    /// The IDs of active resources
151    /// (stored so that we can automatically unregister them if/when the widget is destroyed).
152    pub active_resource_ids: Vec<ResourceId>,
153}
154
155impl CustomWidgetData {
156    pub(crate) fn new(widget: Box<dyn Widget>) -> Self {
157        Self {
158            widget,
159            status: CustomWidgetStatus::Suspended,
160            active_resource_ids: Vec::new(),
161        }
162    }
163
164    pub(crate) fn take_resource_ids(&mut self) -> Vec<ResourceId> {
165        core::mem::take(&mut self.active_resource_ids)
166    }
167}