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