bevy_extended_ui/html/
mod.rs

1mod converter;
2mod builder;
3mod reload;
4
5use std::collections::HashMap;
6use std::sync::{Arc, Mutex};
7use std::sync::atomic::{AtomicUsize, Ordering};
8use std::sync::mpsc::Receiver;
9use bevy::prelude::*;
10use notify::{ RecommendedWatcher, Event, Error };
11use crate::html::builder::HtmlBuilderSystem;
12use crate::html::converter::HtmlConverterSystem;
13use crate::html::reload::HtmlReloadSystem;
14use crate::observer::time_tick_trigger::TimeTick;
15use crate::observer::widget_init_trigger::WidgetInit;
16use crate::styling::css::apply_property_to_style;
17use crate::styling::Style;
18use crate::widgets::{CheckBox, Div, InputField, Button, HtmlBody, ChoiceBox, Slider, Headline, Paragraph, Img, ProgressBar, Widget};
19
20pub static HTML_ID_COUNTER: AtomicUsize = AtomicUsize::new(1);
21
22/// Represents a chunk of HTML source code along with its unique identifier.
23///
24/// This component stores the raw HTML source string and an ID string
25/// that uniquely identifies the source within the UI registry.
26///
27/// # Fields
28///
29/// * `source` - The raw HTML source path as a `String`.
30/// * `source_id` - A unique identifier for this HTML source, typically the name under which
31///   it is registered.
32///
33/// # Derives
34///
35/// This struct derives:
36/// - `Component` to be used as a Bevy ECS component.
37/// - `Reflect` to enable reflection, useful for editor integration and serialization.
38/// - `Debug` for formatting and debugging.
39/// - `Clone` for duplicating instances.
40/// - `Default` to provide a default empty instance.
41///
42/// # Example
43///
44/// ```rust
45/// use bevy_extended_ui::html::HtmlSource;
46/// let html = HtmlSource {
47///     source: "path/to/html".to_string(),
48///     source_id: "main_ui".to_string(),
49///     controller: None,
50/// };
51/// ```
52#[derive(Component, Reflect, Debug, Clone, Default)]
53#[reflect(Component)]
54pub struct HtmlSource {
55    /// The raw HTML source code.
56    pub source: String,
57    /// Unique identifier for the HTML source.
58    pub source_id: String,
59    /// Controls the function support location
60    pub controller: Option<String>
61}
62
63impl HtmlSource {
64
65    /// Creates a new `HtmlSource` from a file path.
66    ///
67    /// This constructor initializes the `source` field with the given path string
68    /// and uses the default values for all other fields.
69    ///
70    /// # Arguments
71    ///
72    /// * `path` - A string slice representing the file path to the HTML source.
73    ///
74    /// # Returns
75    ///
76    /// A new instance of `HtmlSource` with the specified path.
77    ///
78    /// # Example
79    ///
80    /// ```
81    /// use bevy_extended_ui::html::HtmlSource;
82    /// let html_source = HtmlSource::from_file_path("assets/ui/main.html");
83    /// ```
84    pub fn from_file_path(path: &str) -> HtmlSource {
85        HtmlSource {
86            source: path.to_string(),
87            ..default()
88        }
89    }
90    
91}
92
93#[derive(Event, Message)]
94pub struct AllWidgetsSpawned;
95
96#[derive(Component)]
97pub struct NeedHidden;
98
99#[derive(Resource, Default)]
100pub struct ShowWidgetsTimer {
101    pub timer: Timer,
102    pub active: bool,
103}
104
105#[derive(Resource)]
106pub struct HtmlWatcher {
107    pub watcher: RecommendedWatcher,
108    rx: Arc<Mutex<Receiver<std::result::Result<Event, Error>>>>,
109}
110
111#[derive(Event, Message)]
112pub struct HtmlChangeEvent;
113
114/// A component that stores parsed CSS style data using Bevy's `Style` struct.
115#[derive(Component, Reflect, Debug, Clone)]
116#[reflect(Component)]
117pub struct HtmlStyle(pub Style);
118
119impl HtmlStyle {
120    /// Parses a raw CSS style string and converts it into an `HtmlStyle`.
121    ///
122    /// The input string should be a semicolon-separated list of CSS properties.
123    ///
124    /// # Example
125    /// ```rust
126    /// use bevy_extended_ui::html::HtmlStyle;
127    /// let style = HtmlStyle::from_str("display: flex; justify-content: center;");
128    /// ```
129    pub fn from_str(style_code: &str) -> HtmlStyle {
130        let mut style = Style::default();
131
132        for part in style_code.split(';') {
133            let trimmed = part.trim();
134            if trimmed.is_empty() {
135                continue;
136            }
137
138            let (name, value) = if let Some((k, v)) = trimmed.split_once(':') {
139                (k.trim(), v.trim())
140            } else if let Some((k, v)) = trimmed.split_once(' ') {
141                (k.trim(), v.trim())
142            } else {
143                continue;
144            };
145
146            apply_property_to_style(&mut style, name, value);
147        }
148
149        HtmlStyle(style)
150    }
151}
152
153/// Metadata attached to HTML elements, such as class, id, inline styles, or embedded CSS.
154#[derive(Debug, Clone, Default)]
155pub struct HtmlMeta {
156    /// Embedded `<style>` or global CSS rules.
157    pub css: Vec<String>,
158    /// Value of the `id` attribute.
159    pub id: Option<String>,
160    /// Value(s) of the `class` attribute.
161    pub class: Option<Vec<String>>,
162    /// Inline CSS from the `style` attribute.
163    pub style: Option<String>,
164}
165
166#[derive(Debug, Clone, Default)]
167pub struct HtmlStates {
168    pub hidden: bool,
169    pub disabled: bool,
170    pub readonly: bool
171}
172
173/// An enum representing a node in the HTML DOM hierarchy,
174/// mapped to Bevy UI components.
175#[derive(Debug, Clone)]
176pub enum HtmlWidgetNode {
177    /// A `<button>` element.
178    Button(Button, HtmlMeta, HtmlStates, HtmlEventBindings, Widget, HtmlID),
179    /// An `<input type="text">` field.
180    Input(InputField, HtmlMeta, HtmlStates, HtmlEventBindings, Widget, HtmlID),
181    /// A checkbox `<input type="checkbox">`.
182    CheckBox(CheckBox, HtmlMeta, HtmlStates, HtmlEventBindings, Widget, HtmlID),
183    /// A dropdown or select box.
184    ChoiceBox(ChoiceBox, HtmlMeta, HtmlStates, HtmlEventBindings, Widget, HtmlID),
185    /// A img element (`<img>`).
186    Img(Img, HtmlMeta, HtmlStates, HtmlEventBindings, Widget, HtmlID),
187    /// A img element (`<img>`).
188    ProgressBar(ProgressBar, HtmlMeta, HtmlStates, HtmlEventBindings, Widget, HtmlID),
189    /// A heading element (`<h1>`-`<h6>`).
190    Headline(Headline, HtmlMeta, HtmlStates, HtmlEventBindings, Widget, HtmlID),
191    /// A paragraph `<p>`.
192    Paragraph(Paragraph, HtmlMeta, HtmlStates, HtmlEventBindings, Widget, HtmlID),
193    /// A slider input (range).
194    Slider(Slider, HtmlMeta, HtmlStates, HtmlEventBindings, Widget, HtmlID),
195    /// A `<div>` container element with nested child nodes.
196    Div(Div, HtmlMeta, HtmlStates, Vec<HtmlWidgetNode>, HtmlEventBindings, Widget, HtmlID),
197    /// The root `<body>` element of the HTML structure.
198    HtmlBody(HtmlBody, HtmlMeta, HtmlStates, Vec<HtmlWidgetNode>, HtmlEventBindings, Widget, HtmlID),
199}
200
201/// A resource that holds all parsed HTML structures keyed by identifier.
202/// One entry can be marked as currently active.
203#[derive(Resource)]
204pub struct HtmlStructureMap {
205    /// Map of structure names (e.g., file or document names) to their HTML node trees.
206    pub html_map: HashMap<String, Vec<HtmlWidgetNode>>,
207    /// Currently active structure identifier, if any.
208    pub active: Option<String>,
209}
210
211impl Default for HtmlStructureMap {
212    fn default() -> Self {
213        Self {
214            html_map: HashMap::new(),
215            active: None,
216        }
217    }
218}
219
220#[derive(Clone, Debug, PartialEq, Component)]
221pub struct HtmlID(pub usize);
222
223impl Default for HtmlID {
224    fn default() -> Self {
225        Self(HTML_ID_COUNTER.fetch_add(1, Ordering::Relaxed))
226    }
227}
228
229/// Function pointer type for click event observers.
230///
231/// These functions are called when a `Trigger` event for a pointer click occurs,
232/// receiving the event trigger and a `Commands` object to issue commands.
233type ClickObserverFn = fn(On<Pointer<Click>>, Commands);
234
235/// Function pointer type for mouse over event observers.
236///
237/// These functions are called when a `Trigger` event for a pointer over occurs,
238/// receiving the event trigger and a `Commands` object.
239type OverObserverFn = fn(On<Pointer<Over>>, Commands);
240
241/// Function pointer type for mouse out event observers.
242///
243/// These functions are called when a `Trigger` event for a pointer out occurs,
244/// receiving the event trigger and a `Commands` object.
245type OutObserverFn = fn(On<Pointer<Out>>, Commands);
246
247/// Function pointer type for update event observers.
248///
249/// These functions are invoked whenever a `TimeTick` event is triggered,
250/// which typically occurs on every system update tick.
251///
252/// They receive the event trigger and a `Commands` object for issuing commands.
253/// Due to the frequency of these events, observers should be designed for efficient execution.
254type UpdateObserverFn = fn(On<TimeTick>, Commands);
255
256/// Type alias for a load observer function used to handle [`WidgetInit`] events.
257///
258/// This function type defines a callback invoked when a widget initialization event is triggered,
259/// allowing custom logic to run during widget setup.
260///
261/// # Parameters
262/// - `Trigger<WidgetInit>`: The trigger object carrying the [`WidgetInit`] event data.
263/// - `Commands`: The [`Commands`] used to issue additional actions or spawn entities.
264///
265/// # See also
266/// [`WidgetInit`], [`Commands`]
267type LoadObserverFn = fn(On<WidgetInit>, Commands);
268
269/// Registry resource that maps event handler names to their observer functions.
270///
271/// Holds hash maps for click, mouse over, mouse out, and update events.
272/// Used to look up the observer system functions by name for attaching to entities.
273#[derive(Default, Resource)]
274pub struct HtmlFunctionRegistry {
275    /// Map of function names to click event observer functions.
276    pub click: HashMap<String, ClickObserverFn>,
277
278    /// Map of function names to mouse over event observer functions.
279    pub over: HashMap<String, OverObserverFn>,
280
281    /// Map of function names to mouse out event observer functions.
282    pub out: HashMap<String, OutObserverFn>,
283
284    /// Map of function names to update event observer functions.
285    pub update: HashMap<String, UpdateObserverFn>,
286
287    pub load: HashMap<String, LoadObserverFn>,
288}
289
290/// Component representing HTML event bindings on an entity.
291///
292/// Each optional field corresponds to the name of a registered observer function
293/// that will be called on the respective event.
294///
295/// Reflect is derived for use with Bevy reflection and editing tools.
296#[derive(Component, Reflect, Default, Clone, Debug)]
297#[reflect(Component)]
298pub struct HtmlEventBindings {
299    /// Optional function name to call on a click event.
300    pub onclick: Option<String>,
301
302    /// Optional function name to call on mouse enter event.
303    pub onmouseenter: Option<String>,
304
305    /// Optional function name to call on mouse leave event.
306    pub onmouseleave: Option<String>,
307
308    /// Optional function name to call on update event.
309    pub onupdate: Option<String>,
310
311    pub onload: Option<String>,
312}
313
314/// The main plugin that registers all HTML UI systems and resources.
315pub struct HtmlPlugin;
316
317impl Plugin for HtmlPlugin {
318    /// Configures the app to support HTML parsing and UI construction.
319    fn build(&self, app: &mut App) {
320        app.add_message::<HtmlChangeEvent>();
321        app.init_resource::<HtmlStructureMap>();
322        app.init_resource::<HtmlFunctionRegistry>();
323        app.register_type::<HtmlEventBindings>();
324        app.register_type::<HtmlSource>();
325        app.register_type::<HtmlStyle>();
326        app.add_plugins((HtmlConverterSystem, HtmlBuilderSystem, HtmlReloadSystem));
327    }
328}