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}