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    /// Handle input events (mouse, keyboard, etc)
100    fn handle_event(&mut self, event: &UiEvent) {
101        let _ = event;
102    }
103
104    /// Callback for the widget to paint it's content.
105    ///
106    /// Output is recorded to an AnyRender `Scene`.
107    /// If the widget wants to render to a WGPU texture or similar then it should:
108    ///   - Get a handle to the Device and Queue in `can_create_surfaces`
109    ///   - Create it's own texture
110    ///   - Pass the `ResourceId` of the paint for an Image in the AnyRender `Scene`
111    fn paint(
112        &mut self,
113        render_ctx: &mut dyn RenderContext,
114        styles: &ComputedStyles,
115        width: u32,
116        height: u32,
117        scale: f64,
118    ) -> Scene {
119        let _ = (render_ctx, styles, width, height, scale);
120        Scene::new()
121    }
122
123    // TODO: allow for multiple nodes per widget
124    // fn accessibility_tree(&mut self) -> AccessKitNode;
125
126    // TODO: simpler layout mode?
127    // fn layout(&mut self, inputs: LayoutInput, styles: &ComputedStyles) -> LayoutOutput;
128}
129
130#[derive(Debug, Copy, Clone, Eq, PartialEq)]
131pub enum CustomWidgetStatus {
132    Suspended,
133    Active,
134    PendingRemoval,
135}
136
137pub struct CustomWidgetData {
138    /// The custom widget
139    pub widget: Box<dyn Widget>,
140    /// The custom widget's status
141    pub status: CustomWidgetStatus,
142    /// The IDs of active resources
143    /// (stored so that we can automatically unregister them if/when the widget is destroyed).
144    pub active_resource_ids: Vec<ResourceId>,
145}
146
147impl CustomWidgetData {
148    pub(crate) fn new(widget: Box<dyn Widget>) -> Self {
149        Self {
150            widget,
151            status: CustomWidgetStatus::Suspended,
152            active_resource_ids: Vec::new(),
153        }
154    }
155
156    pub(crate) fn take_resource_ids(&mut self) -> Vec<ResourceId> {
157        core::mem::take(&mut self.active_resource_ids)
158    }
159}