Skip to main content

apace_dom/node/
custom_widget.rs

1use std::any::Any;
2
3use anyrender::ResourceId;
4use apace_traits::events::UiEvent;
5pub use style::properties::ComputedValues as ComputedStyles;
6// use accesskit::Node as AccessKitNode;
7// use takia::{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    ///
84    /// Existing attributes are replayed on attachment with no old value.
85    fn attribute_changed(&mut self, name: &str, old_value: Option<&str>, new_value: Option<&str>) {
86        let _ = (name, old_value, new_value);
87    }
88
89    // Renderer lifecycle
90
91    /// The renderer is active
92    ///
93    /// `ctx` parameter can be downcast to get access to renderer-specific contexts (e.g. the WGPU Device and Queue)
94    fn can_create_surfaces(&mut self, render_ctx: &mut dyn RenderContext) {
95        let _ = render_ctx;
96    }
97    /// The renderer is no longer active (destroy textures here)
98    fn destroy_surfaces(&mut self) {}
99
100    // Other
101
102    /// Whether the widget currently requires redraws (e.g. because it is animating).
103    ///
104    /// Returning `true` causes the document to continuously schedule redraws
105    /// (and hence repaints of the widget). Static widgets should return `false`.
106    fn requires_redraw(&self) -> bool {
107        false
108    }
109
110    /// Handle input events (mouse, keyboard, etc)
111    fn handle_event(&mut self, event: &UiEvent) {
112        let _ = event;
113    }
114
115    /// The widget's intrinsic dimensions: an intrinsic width, height and
116    /// aspect ratio, each of which may independently be absent.
117    ///
118    /// An absent dimension is sized as it would be without the widget (for a
119    /// replaced element, the element's own intrinsic dimension or the default
120    /// object size).
121    fn intrinsic_sizes(&self) -> IntrinsicSizes {
122        IntrinsicSizes::default()
123    }
124
125    /// Callback for the widget to paint it's content.
126    ///
127    /// Output is recorded to an AnyRender `Scene`.
128    /// If the widget wants to render to a WGPU texture or similar then it should:
129    ///   - Get a handle to the Device and Queue in `can_create_surfaces`
130    ///   - Create it's own texture
131    ///   - Pass the `ResourceId` of the paint for an Image in the AnyRender `Scene`
132    fn paint(
133        &mut self,
134        render_ctx: &mut dyn RenderContext,
135        styles: &ComputedStyles,
136        width: u32,
137        height: u32,
138        scale: f64,
139    ) -> Scene {
140        let _ = (render_ctx, styles, width, height, scale);
141        Scene::new()
142    }
143
144    // TODO: allow for multiple nodes per widget
145    // fn accessibility_tree(&mut self) -> AccessKitNode;
146
147    // TODO: simpler layout mode?
148    // fn layout(&mut self, inputs: LayoutInput, styles: &ComputedStyles) -> LayoutOutput;
149}
150
151#[derive(Debug, Copy, Clone, Eq, PartialEq)]
152pub enum CustomWidgetStatus {
153    Suspended,
154    Active,
155    PendingRemoval,
156}
157
158pub struct CustomWidgetData {
159    /// The custom widget
160    pub widget: Box<dyn Widget>,
161    /// The custom widget's status
162    pub status: CustomWidgetStatus,
163    /// The IDs of active resources
164    /// (stored so that we can automatically unregister them if/when the widget is destroyed).
165    pub active_resource_ids: Vec<ResourceId>,
166}
167
168impl CustomWidgetData {
169    pub(crate) fn new(widget: Box<dyn Widget>) -> Self {
170        Self {
171            widget,
172            status: CustomWidgetStatus::Suspended,
173            active_resource_ids: Vec::new(),
174        }
175    }
176
177    pub(crate) fn take_resource_ids(&mut self) -> Vec<ResourceId> {
178        core::mem::take(&mut self.active_resource_ids)
179    }
180}