Skip to main content

cotis_web/
renderer.rs

1//! Application-facing renderer type for browser/WASM targets.
2//!
3//! [`HTMLRenderer`] is the main entry point: wire it into [`CotisApp`](cotis::cotis_app::CotisApp)
4//! with a layout manager and render-list pipe, then drive frames through
5//! [`AsyncRenderApp::compute_frame_async`](cotis::cotis_app::AsyncRenderApp::compute_frame_async).
6//!
7//! Each frame:
8//!
9//! 1. [`CotisRendererAsync::draw_frame`] awaits the JS `waitForNextFrame` helper.
10//! 2. Render commands (types implementing [`HTMLDrawable`]) are
11//!    drawn via [`draw_html_render_commands`].
12//!
13//! Cotis context traits ([`CotisWindowContext`](cotis_utils::traits::CotisWindowContext),
14//! [`CotisFrameContext`](cotis_utils::traits::CotisFrameContext), text measuring, etc.) are
15//! implemented in [`crate::cotis_traits`].
16//!
17//! Custom DOM hosts and form reading: [`crate::custom_data`] and
18//! [`HTMLRenderer::get_custom_element_html`] / [`HTMLRenderer::get_custom_element_properties`].
19//!
20//! Image URLs: [`crate::images`].
21
22use crate::rendering::{HTMLDrawable, draw_html_render_commands};
23use crate::web_functions::primitives::{load_font, wait_for_next_frame};
24use cotis::renders::CotisRendererAsync;
25use cotis_defaults::element_configs::text_config::TextConfig;
26use cotis_utils::font_manager::FontManager;
27use cotis_utils::math::Dimensions;
28
29/// Reserved type alias for a text-measuring callback (currently unused).
30pub type MeasureFunType = fn(&str, &TextConfig) -> Dimensions;
31
32/// Reserved type alias for a synchronous loop callback (currently unused).
33pub type LoopRoutine = Box<dyn FnMut() + Send>;
34
35/// Async HTML/CSS renderer for Cotis applications running in the browser.
36///
37/// Implements [`CotisRendererAsync`] and Cotis context traits (see [`crate::cotis_traits`]).
38/// Holds the page origin (`window.location.origin`) for resolving relative asset URLs.
39///
40/// # WASM
41///
42/// Requires a browser environment with a global `window` object. Construct after
43/// `init_wasm()` and before starting the async frame loop.
44///
45/// # Example
46///
47/// ```rust,ignore
48/// use cotis_web::renderer::HTMLRenderer;
49///
50/// let mut renderer = HTMLRenderer::new();
51/// // Wire into CotisApp, then compute_frame_async in a loop.
52/// ```
53pub struct HTMLRenderer {
54    _url: String,
55}
56
57impl<HTMLDrawableType: HTMLDrawable> CotisRendererAsync<HTMLDrawableType> for HTMLRenderer {
58    /// Draw one frame: wait for the next animation frame, then update the DOM.
59    ///
60    /// Calls the JS `waitForNextFrame` helper, then runs each command through
61    /// [`HTMLCanvas`](crate::rendering::HTMLCanvas) and
62    /// [`draw_html_render_commands`].
63    /// Unused `cotis-{id}` elements from previous frames are removed in
64    /// [`HTMLCanvas::finish`](crate::rendering::HTMLCanvas::finish).
65    ///
66    /// # WASM
67    ///
68    /// Requires an initialized DOM root (see bundled `renderer.js` / `init_html_root`).
69    async fn draw_frame(&mut self, render_commands: impl Iterator<Item = HTMLDrawableType>) {
70        wait_for_next_frame().await;
71        draw_html_render_commands(render_commands);
72    }
73}
74
75impl Default for HTMLRenderer {
76    fn default() -> Self {
77        Self::new()
78    }
79}
80
81impl HTMLRenderer {
82    /// Creates a renderer bound to the current page origin.
83    ///
84    /// Reads `window.location.origin` (e.g. `"https://example.com:8080"`) for asset URL
85    /// resolution.
86    ///
87    /// # Panics
88    ///
89    /// Panics if there is no global `window` or if `location.origin()` is unavailable.
90    pub fn new() -> Self {
91        use web_sys::window;
92        let window = window().expect("no global `window` exists");
93        let location = window.location();
94
95        let origin = location.origin().expect("could not get origin");
96        Self { _url: origin }
97    }
98
99    /// Registers a font URL with the browser and assigns it an id in `manager`.
100    ///
101    /// Pushes `url` into `manager`, then calls the JS `loadFont` helper so CSS
102    /// `font-family: font{id}` (used by text drawables) resolves correctly.
103    ///
104    /// # WASM
105    ///
106    /// Font loading is asynchronous in the browser; allow a frame or two before relying on
107    /// measured text dimensions.
108    pub fn load_font(&mut self, manager: &FontManager<String>, url: &str) {
109        let id = manager.push_font(url.to_string());
110        load_font(url, id);
111    }
112
113    /// Returns the `innerHTML` of the custom host element for the given render command id.
114    ///
115    /// The element id in the DOM is `cotis-{element_id}`. Returns `None` if no such element
116    /// exists in the current document.
117    ///
118    /// # WASM
119    ///
120    /// Queries the live DOM; call after the frame that created or updated the element.
121    pub fn get_custom_element_html(&self, element_id: u64) -> Option<String> {
122        crate::web_functions::primitives::get_custom_element_html(element_id)
123    }
124
125    /// Returns JSON describing properties of a custom host element or a descendant.
126    ///
127    /// Use `selector` to target a child, e.g. `Some(r#"input[name="username"]"#)` to read an
128    /// input's `value`. With `None`, the JS helper auto-picks the first form-like element or
129    /// the host itself.
130    ///
131    /// Parse the returned string with [`serde_json`] to access fields like `value`,
132    /// `placeholder`, `checked`, etc.
133    ///
134    /// # WASM
135    ///
136    /// Queries the live DOM; call after the frame that created or updated the element.
137    ///
138    /// # Example
139    ///
140    /// ```rust,ignore
141    /// # use cotis_web::renderer::HTMLRenderer;
142    /// let renderer = HTMLRenderer::new();
143    /// if let Some(json) = renderer.get_custom_element_properties(42, Some("input")) {
144    ///     let props: serde_json::Value = serde_json::from_str(&json).unwrap();
145    ///     let value = props["value"].as_str();
146    /// }
147    /// ```
148    pub fn get_custom_element_properties(
149        &self,
150        element_id: u64,
151        selector: Option<&str>,
152    ) -> Option<String> {
153        crate::web_functions::primitives::get_custom_element_properties(element_id, selector)
154    }
155}