Skip to main content

bevy_extended_ui/html/
mod.rs

1mod bindings;
2pub mod builder;
3pub mod converter;
4pub mod inline_functions;
5pub mod reload;
6
7pub use bindings::HtmlEventBindingsPlugin;
8pub use inline_functions::{
9    HtmlInlineAction, HtmlInlineEventBindings, HtmlInlineFunction, parse_html_inline_action,
10};
11pub use inventory;
12
13#[cfg(feature = "extended-framework")]
14use crate::framework::sync_ui_binding_store_values;
15use crate::html::builder::HtmlBuilderSystem;
16use crate::html::converter::HtmlConverterSystem;
17use crate::html::reload::HtmlReloadPlugin;
18use crate::lang::{UILang, UiLangState, UiLangVariables, UiSharedValues, refresh_shared_values};
19use bevy::ecs::system::SystemId;
20use bevy::prelude::*;
21use std::collections::{HashMap, HashSet};
22use std::sync::atomic::{AtomicUsize, Ordering};
23
24#[cfg(feature = "extended-dialog")]
25use crate::dialog::DialogWidget;
26use crate::io::{CssAsset, HtmlAsset};
27use crate::styles::Style;
28use crate::styles::parser::apply_property_to_style;
29use crate::widgets::{
30    Badge, Body, Button, CheckBox, ChoiceBox, ColorPicker, DatePicker, Div, Divider, FieldSet,
31    Form, Headline, HyperLink, Img, InputField, ListBox, Paragraph, ProgressBar, RadioButton,
32    Scrollbar, Slider, SwitchButton, ToggleButton, ToolTip, ValidationRules, Widget,
33};
34
35pub static HTML_ID_COUNTER: AtomicUsize = AtomicUsize::new(1);
36
37/// System set ordering for the HTML UI pipeline.
38#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
39pub enum HtmlSystemSet {
40    /// Variant `Convert`.
41    Convert,
42    /// Variant `Build`.
43    Build,
44    /// Variant `ShowWidgets`.
45    ShowWidgets,
46    /// Variant `Bindings`.
47    Bindings,
48}
49
50/// Component that points to an HTML asset and related metadata.
51#[derive(Component, Reflect, Debug, Clone)]
52#[reflect(Component)]
53pub struct HtmlSource {
54    pub handle: Handle<HtmlAsset>,
55    pub source_id: String,
56    pub controller: Option<String>,
57}
58
59impl HtmlSource {
60    /// Creates a new `HtmlSource` from an asset handle.
61    pub fn from_handle(handle: Handle<HtmlAsset>) -> Self {
62        Self {
63            handle,
64            source_id: String::new(),
65            controller: None,
66        }
67    }
68
69    /// Returns the asset path (relative to assets/) of this HtmlAsset.
70    /// Example: "examples/test.html"
71    pub fn get_source_path(&self) -> String {
72        self.handle
73            .path()
74            .map(|asset_path| asset_path.path().to_string_lossy().replace('\\', "/"))
75            .unwrap_or_default()
76    }
77}
78
79/// Event fired when all widgets have been spawned.
80#[derive(Event, Message)]
81pub struct HtmlAllWidgetsSpawned;
82
83/// Event fired when all widgets are visible.
84#[derive(Event, Message)]
85pub struct HtmlAllWidgetsVisible;
86
87/// Marker component used to prevent double init events.
88#[derive(Component, Default)]
89pub struct HtmlInitEmitted;
90
91/// Resource used to delay init until a configurable number of frames.
92#[derive(Resource, Default)]
93pub struct HtmlInitDelay(pub Option<u8>);
94
95/// Marker component for nodes that should start hidden.
96#[derive(Component)]
97pub struct NeedHidden;
98
99/// Timer resource used to stagger widget visibility.
100#[derive(Resource, Default)]
101pub struct ShowWidgetsTimer {
102    pub timer: Timer,
103    pub active: bool,
104}
105
106/// Event emitted when an HTML change is detected.
107#[derive(Event, Message)]
108pub struct HtmlChangeEvent;
109
110/// Tracks whether the HTML UI needs rebuilding and, when possible, which UI keys changed.
111///
112/// We use this because mutating the internal HashMap of `HtmlStructureMap`
113/// does NOT reliably trigger `resource_changed::<HtmlStructureMap>()`.
114#[derive(Resource, Default)]
115pub struct HtmlDirty(pub bool, pub HashSet<String>);
116
117/// Tracks HTML keys that must stay hidden until CSS and styles are ready.
118#[derive(Resource, Default)]
119pub struct HtmlPendingReveal(pub HashSet<String>);
120
121/// Component storing parsed inline CSS (`style="..."`) as your custom Style struct.
122/// Component storing parsed inline CSS (`style="..."`) as a `Style`.
123#[derive(Component, Reflect, Debug, Clone, PartialEq)]
124#[reflect(Component)]
125pub struct HtmlStyle(pub Style);
126
127impl HtmlStyle {
128    /// Parses inline CSS style declarations into a `Style`.
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/// Runtime metadata for simple text bindings that can be patched without rebuilding the template.
154#[derive(Component, Reflect, Debug, Clone, Default, PartialEq)]
155#[reflect(Component)]
156pub struct HtmlTextBinding {
157    pub template: String,
158    pub bindings: Vec<String>,
159}
160
161/// Metadata collected from HTML attributes.
162#[derive(Debug, Clone, Default, PartialEq)]
163pub struct HtmlMeta {
164    /// All referenced CSS assets for this node.
165    pub css: Vec<Handle<CssAsset>>,
166    pub id: Option<String>,
167    pub class: Option<Vec<String>>,
168    pub style: Option<HtmlStyle>,
169    pub validation: Option<ValidationRules>,
170    pub inner_content: HtmlInnerContent,
171    pub text_binding: Option<HtmlTextBinding>,
172}
173
174/// Captures textual and reactive inner content for an HTML element.
175///
176/// These fields mirror common DOM concepts:
177/// - `inner_text`: plain text inside the element
178/// - `inner_html`: serialized child HTML
179/// - `inner_bindings`: placeholders such as `{{user.name}}`
180#[derive(Component, Reflect, Debug, Clone, Default, PartialEq)]
181#[reflect(Component)]
182pub struct HtmlInnerContent {
183    inner_text: String,
184    inner_html: String,
185    inner_bindings: Vec<String>,
186}
187
188impl HtmlInnerContent {
189    /// Creates a new inner content payload.
190    pub fn new(
191        inner_text: impl Into<String>,
192        inner_html: impl Into<String>,
193        inner_bindings: Vec<String>,
194    ) -> Self {
195        Self {
196            inner_text: inner_text.into(),
197            inner_html: inner_html.into(),
198            inner_bindings,
199        }
200    }
201
202    /// Returns the raw text content (`innerText`).
203    pub fn inner_text(&self) -> &str {
204        &self.inner_text
205    }
206
207    /// Returns the serialized HTML content (`innerHtml`).
208    pub fn inner_html(&self) -> &str {
209        &self.inner_html
210    }
211
212    /// Returns the discovered reactive bindings (`innerBindings`).
213    pub fn inner_bindings(&self) -> &[String] {
214        &self.inner_bindings
215    }
216
217    /// Overrides the raw text content.
218    pub fn set_inner_text(&mut self, value: impl Into<String>) {
219        self.inner_text = value.into();
220    }
221
222    /// Overrides the serialized HTML content.
223    pub fn set_inner_html(&mut self, value: impl Into<String>) {
224        self.inner_html = value.into();
225    }
226
227    /// Overrides the discovered bindings.
228    pub fn set_inner_bindings(&mut self, value: Vec<String>) {
229        self.inner_bindings = value;
230    }
231}
232
233/// Common HTML state flags for nodes.
234#[derive(Debug, Clone, Default, PartialEq, Eq)]
235pub struct HtmlStates {
236    pub hidden: bool,
237    pub disabled: bool,
238    pub readonly: bool,
239}
240
241/// Your current DOM model.
242#[derive(Debug, Clone)]
243pub enum HtmlWidgetNode {
244    /// The root `<body>` element of the HTML structure.
245    Body(
246        /// Variant `Body`.
247        Body,
248        /// Variant `HtmlMeta`.
249        HtmlMeta,
250        /// Variant `HtmlStates`.
251        HtmlStates,
252        Vec<HtmlWidgetNode>,
253        /// Variant `HtmlEventBindings`.
254        HtmlEventBindings,
255        /// Variant `Widget`.
256        Widget,
257        /// Variant `HtmlID`.
258        HtmlID,
259    ),
260    /// A `<div>` container element with nested child nodes.
261    Div(
262        /// Variant `Div`.
263        Div,
264        /// Variant `HtmlMeta`.
265        HtmlMeta,
266        /// Variant `HtmlStates`.
267        HtmlStates,
268        Vec<HtmlWidgetNode>,
269        /// Variant `HtmlEventBindings`.
270        HtmlEventBindings,
271        /// Variant `Widget`.
272        Widget,
273        /// Variant `HtmlID`.
274        HtmlID,
275    ),
276    /// A `<form>` container element with nested child nodes.
277    Form(
278        /// Variant `Form`.
279        Form,
280        /// Variant `HtmlMeta`.
281        HtmlMeta,
282        /// Variant `HtmlStates`.
283        HtmlStates,
284        Vec<HtmlWidgetNode>,
285        /// Variant `HtmlEventBindings`.
286        HtmlEventBindings,
287        /// Variant `Widget`.
288        Widget,
289        /// Variant `HtmlID`.
290        HtmlID,
291    ),
292    /// A `<dialog>` widget container with nested child nodes.
293    #[cfg(feature = "extended-dialog")]
294    Dialog(
295        /// Variant `DialogWidget`.
296        DialogWidget,
297        /// Variant `HtmlMeta`.
298        HtmlMeta,
299        /// Variant `HtmlStates`.
300        HtmlStates,
301        Vec<HtmlWidgetNode>,
302        /// Variant `HtmlEventBindings`.
303        HtmlEventBindings,
304        /// Variant `Widget`.
305        Widget,
306        /// Variant `HtmlID`.
307        HtmlID,
308    ),
309    /// A `<divider>` element.
310    Divider(
311        /// Variant `Divider`.
312        Divider,
313        /// Variant `HtmlMeta`.
314        HtmlMeta,
315        /// Variant `HtmlStates`.
316        HtmlStates,
317        /// Variant `HtmlEventBindings`.
318        HtmlEventBindings,
319        /// Variant `Widget`.
320        Widget,
321        /// Variant `HtmlID`.
322        HtmlID,
323    ),
324    /// A `<button>` element.
325    Button(
326        /// Variant `Button`.
327        Button,
328        /// Variant `HtmlMeta`.
329        HtmlMeta,
330        /// Variant `HtmlStates`.
331        HtmlStates,
332        /// Variant `HtmlEventBindings`.
333        HtmlEventBindings,
334        /// Variant `Widget`.
335        Widget,
336        /// Variant `HtmlID`.
337        HtmlID,
338    ),
339    /// A checkbox `<checkbox>`.
340    CheckBox(
341        /// Variant `CheckBox`.
342        CheckBox,
343        /// Variant `HtmlMeta`.
344        HtmlMeta,
345        /// Variant `HtmlStates`.
346        HtmlStates,
347        /// Variant `HtmlEventBindings`.
348        HtmlEventBindings,
349        /// Variant `Widget`.
350        Widget,
351        /// Variant `HtmlID`.
352        HtmlID,
353    ),
354    /// A color picker `<colorpicker>`.
355    ColorPicker(
356        /// Variant `ColorPicker`.
357        ColorPicker,
358        /// Variant `HtmlMeta`.
359        HtmlMeta,
360        /// Variant `HtmlStates`.
361        HtmlStates,
362        /// Variant `HtmlEventBindings`.
363        HtmlEventBindings,
364        /// Variant `Widget`.
365        Widget,
366        /// Variant `HtmlID`.
367        HtmlID,
368    ),
369    /// A dropdown or select box.
370    ChoiceBox(
371        /// Variant `ChoiceBox`.
372        ChoiceBox,
373        /// Variant `HtmlMeta`.
374        HtmlMeta,
375        /// Variant `HtmlStates`.
376        HtmlStates,
377        /// Variant `HtmlEventBindings`.
378        HtmlEventBindings,
379        /// Variant `Widget`.
380        Widget,
381        /// Variant `HtmlID`.
382        HtmlID,
383    ),
384    /// A date picker `<date-picker>`.
385    DatePicker(
386        /// Variant `DatePicker`.
387        DatePicker,
388        /// Variant `HtmlMeta`.
389        HtmlMeta,
390        /// Variant `HtmlStates`.
391        HtmlStates,
392        /// Variant `HtmlEventBindings`.
393        HtmlEventBindings,
394        /// Variant `Widget`.
395        Widget,
396        /// Variant `HtmlID`.
397        HtmlID,
398    ),
399    /// A `<fieldset>` container element with nested child nodes from type `<radio> and <toggle>`.
400    FieldSet(
401        /// Variant `FieldSet`.
402        FieldSet,
403        /// Variant `HtmlMeta`.
404        HtmlMeta,
405        /// Variant `HtmlStates`.
406        HtmlStates,
407        Vec<HtmlWidgetNode>,
408        /// Variant `HtmlEventBindings`.
409        HtmlEventBindings,
410        /// Variant `Widget`.
411        Widget,
412        /// Variant `HtmlID`.
413        HtmlID,
414    ),
415    /// A heading element (`<h1>`-`<h6>`).
416    Headline(
417        /// Variant `Headline`.
418        Headline,
419        /// Variant `HtmlMeta`.
420        HtmlMeta,
421        /// Variant `HtmlStates`.
422        HtmlStates,
423        /// Variant `HtmlEventBindings`.
424        HtmlEventBindings,
425        /// Variant `Widget`.
426        Widget,
427        /// Variant `HtmlID`.
428        HtmlID,
429    ),
430    /// A hyperlink `<a>`.
431    HyperLink(
432        /// Variant `HyperLink`.
433        HyperLink,
434        /// Variant `HtmlMeta`.
435        HtmlMeta,
436        /// Variant `HtmlStates`.
437        HtmlStates,
438        /// Variant `HtmlEventBindings`.
439        HtmlEventBindings,
440        /// Variant `Widget`.
441        Widget,
442        /// Variant `HtmlID`.
443        HtmlID,
444    ),
445    /// A img element (`<img>`).
446    Img(Img, HtmlMeta, HtmlStates, HtmlEventBindings, Widget, HtmlID),
447    /// An `<input ...>` field.
448    Input(
449        /// Variant `InputField`.
450        InputField,
451        /// Variant `HtmlMeta`.
452        HtmlMeta,
453        /// Variant `HtmlStates`.
454        HtmlStates,
455        /// Variant `HtmlEventBindings`.
456        HtmlEventBindings,
457        /// Variant `Widget`.
458        Widget,
459        /// Variant `HtmlID`.
460        HtmlID,
461    ),
462    /// A paragraph `<p>`.
463    Paragraph(
464        /// Variant `Paragraph`.
465        Paragraph,
466        /// Variant `HtmlMeta`.
467        HtmlMeta,
468        /// Variant `HtmlStates`.
469        HtmlStates,
470        /// Variant `HtmlEventBindings`.
471        HtmlEventBindings,
472        /// Variant `Widget`.
473        Widget,
474        /// Variant `HtmlID`.
475        HtmlID,
476    ),
477    /// A tooltip `<tool-tip>`.
478    ToolTip(
479        /// Variant `ToolTip`.
480        ToolTip,
481        /// Variant `HtmlMeta`.
482        HtmlMeta,
483        /// Variant `HtmlStates`.
484        HtmlStates,
485        /// Variant `HtmlEventBindings`.
486        HtmlEventBindings,
487        /// Variant `Widget`.
488        Widget,
489        /// Variant `HtmlID`.
490        HtmlID,
491    ),
492    /// A badge `<badge>`.
493    Badge(
494        /// Variant `Badge`.
495        Badge,
496        /// Variant `HtmlMeta`.
497        HtmlMeta,
498        /// Variant `HtmlStates`.
499        HtmlStates,
500        /// Variant `HtmlEventBindings`.
501        HtmlEventBindings,
502        /// Variant `Widget`.
503        Widget,
504        /// Variant `HtmlID`.
505        HtmlID,
506    ),
507    /// A progressbar `<progressbar>`.
508    ProgressBar(
509        /// Variant `ProgressBar`.
510        ProgressBar,
511        /// Variant `HtmlMeta`.
512        HtmlMeta,
513        /// Variant `HtmlStates`.
514        HtmlStates,
515        /// Variant `HtmlEventBindings`.
516        HtmlEventBindings,
517        /// Variant `Widget`.
518        Widget,
519        /// Variant `HtmlID`.
520        HtmlID,
521    ),
522    /// A radio-button `<radio>`.
523    RadioButton(
524        /// Variant `RadioButton`.
525        RadioButton,
526        /// Variant `HtmlMeta`.
527        HtmlMeta,
528        /// Variant `HtmlStates`.
529        HtmlStates,
530        /// Variant `HtmlEventBindings`.
531        HtmlEventBindings,
532        /// Variant `Widget`.
533        Widget,
534        /// Variant `HtmlID`.
535        HtmlID,
536    ),
537    /// A slider input `<slider>`).
538    Scrollbar(
539        /// Variant `Scrollbar`.
540        Scrollbar,
541        /// Variant `HtmlMeta`.
542        HtmlMeta,
543        /// Variant `HtmlStates`.
544        HtmlStates,
545        /// Variant `HtmlEventBindings`.
546        HtmlEventBindings,
547        /// Variant `Widget`.
548        Widget,
549        /// Variant `HtmlID`.
550        HtmlID,
551    ),
552    /// A slider input `<slider>`).
553    Slider(
554        /// Variant `Slider`.
555        Slider,
556        /// Variant `HtmlMeta`.
557        HtmlMeta,
558        /// Variant `HtmlStates`.
559        HtmlStates,
560        /// Variant `HtmlEventBindings`.
561        HtmlEventBindings,
562        /// Variant `Widget`.
563        Widget,
564        /// Variant `HtmlID`.
565        HtmlID,
566    ),
567    /// A switch-button `<switch>`).
568    SwitchButton(
569        /// Variant `SwitchButton`.
570        SwitchButton,
571        /// Variant `HtmlMeta`.
572        HtmlMeta,
573        /// Variant `HtmlStates`.
574        HtmlStates,
575        /// Variant `HtmlEventBindings`.
576        HtmlEventBindings,
577        /// Variant `Widget`.
578        Widget,
579        /// Variant `HtmlID`.
580        HtmlID,
581    ),
582    /// A toggle-button `<toggle>`.
583    ToggleButton(
584        /// Variant `ToggleButton`.
585        ToggleButton,
586        /// Variant `HtmlMeta`.
587        HtmlMeta,
588        /// Variant `HtmlStates`.
589        HtmlStates,
590        /// Variant `HtmlEventBindings`.
591        HtmlEventBindings,
592        /// Variant `Widget`.
593        Widget,
594        /// Variant `HtmlID`.
595        HtmlID,
596    ),
597    /// A list box `<listbox>`.
598    ListBox(
599        /// Variant `ListBox`.
600        ListBox,
601        /// Variant `HtmlMeta`.
602        HtmlMeta,
603        /// Variant `HtmlStates`.
604        HtmlStates,
605        /// Variant `HtmlEventBindings`.
606        HtmlEventBindings,
607        /// Variant `Widget`.
608        Widget,
609        /// Variant `HtmlID`.
610        HtmlID,
611    ),
612}
613
614/// Stores all parsed HTML structures keyed by `<meta name="...">`.
615/// Stores parsed HTML trees keyed by their `<meta name="...">` value.
616#[derive(Resource)]
617pub struct HtmlStructureMap {
618    pub html_map: HashMap<String, Vec<HtmlWidgetNode>>,
619    pub active: Option<Vec<String>>,
620}
621
622impl Default for HtmlStructureMap {
623    /// Creates an empty structure map with no active HTML.
624    fn default() -> Self {
625        Self {
626            html_map: HashMap::new(),
627            active: None,
628        }
629    }
630}
631
632/// Unique identifier for HTML nodes.
633#[derive(Clone, Debug, PartialEq, Eq, Component)]
634pub struct HtmlID(pub usize);
635
636impl Default for HtmlID {
637    /// Allocates a new HTML ID from the global counter.
638    fn default() -> Self {
639        Self(HTML_ID_COUNTER.fetch_add(1, Ordering::Relaxed))
640    }
641}
642
643/// Registry entry for HTML event handler builders.
644pub enum HtmlFnRegistration {
645    /// Variant `HtmlEvent`.
646    HtmlEvent {
647        name: &'static str,
648        build: fn(&mut World) -> SystemId<In<HtmlEvent>, ()>,
649    },
650    /// Variant `HtmlClick`.
651    HtmlClick {
652        name: &'static str,
653        build: fn(&mut World) -> SystemId<In<HtmlClick>, ()>,
654    },
655    /// Variant `HtmlMouseDown`.
656    HtmlMouseDown {
657        name: &'static str,
658        build: fn(&mut World) -> SystemId<In<HtmlMouseDown>, ()>,
659    },
660    /// Variant `HtmlMouseUp`.
661    HtmlMouseUp {
662        name: &'static str,
663        build: fn(&mut World) -> SystemId<In<HtmlMouseUp>, ()>,
664    },
665    /// Variant `HtmlChange`.
666    HtmlChange {
667        name: &'static str,
668        build: fn(&mut World) -> SystemId<In<HtmlChange>, ()>,
669    },
670    /// Variant `HtmlSubmit`.
671    HtmlSubmit {
672        name: &'static str,
673        build: fn(&mut World) -> SystemId<In<HtmlSubmit>, ()>,
674    },
675    /// Variant `HtmlInit`.
676    HtmlInit {
677        name: &'static str,
678        build: fn(&mut World) -> SystemId<In<HtmlInit>, ()>,
679    },
680    /// Variant `HtmlMouseOut`.
681    HtmlMouseOut {
682        name: &'static str,
683        build: fn(&mut World) -> SystemId<In<HtmlMouseOut>, ()>,
684    },
685    /// Variant `HtmlMouseOver`.
686    HtmlMouseOver {
687        name: &'static str,
688        build: fn(&mut World) -> SystemId<In<HtmlMouseOver>, ()>,
689    },
690    /// Variant `HtmlFocus`.
691    HtmlFocus {
692        name: &'static str,
693        build: fn(&mut World) -> SystemId<In<HtmlFocus>, ()>,
694    },
695    /// Variant `HtmlScroll`.
696    HtmlScroll {
697        name: &'static str,
698        build: fn(&mut World) -> SystemId<In<HtmlScroll>, ()>,
699    },
700    /// Variant `HtmlWheel`.
701    HtmlWheel {
702        name: &'static str,
703        build: fn(&mut World) -> SystemId<In<HtmlWheel>, ()>,
704    },
705    /// Variant `HtmlKeyDown`.
706    HtmlKeyDown {
707        name: &'static str,
708        build: fn(&mut World) -> SystemId<In<HtmlKeyDown>, ()>,
709    },
710    /// Variant `HtmlKeyUp`.
711    HtmlKeyUp {
712        name: &'static str,
713        build: fn(&mut World) -> SystemId<In<HtmlKeyUp>, ()>,
714    },
715    /// Variant `HtmlDragStart`.
716    HtmlDragStart {
717        name: &'static str,
718        build: fn(&mut World) -> SystemId<In<HtmlDragStart>, ()>,
719    },
720    /// Variant `HtmlDrag`.
721    HtmlDrag {
722        name: &'static str,
723        build: fn(&mut World) -> SystemId<In<HtmlDrag>, ()>,
724    },
725    /// Variant `HtmlDragStop`.
726    HtmlDragStop {
727        name: &'static str,
728        build: fn(&mut World) -> SystemId<In<HtmlDragStop>, ()>,
729    },
730    /// Variant `HtmlTouchStart`.
731    HtmlTouchStart {
732        name: &'static str,
733        build: fn(&mut World) -> SystemId<In<HtmlTouchStart>, ()>,
734    },
735    /// Variant `HtmlTouchMove`.
736    HtmlTouchMove {
737        name: &'static str,
738        build: fn(&mut World) -> SystemId<In<HtmlTouchMove>, ()>,
739    },
740    /// Variant `HtmlTouchEnd`.
741    HtmlTouchEnd {
742        name: &'static str,
743        build: fn(&mut World) -> SystemId<In<HtmlTouchEnd>, ()>,
744    },
745}
746
747inventory::collect!(HtmlFnRegistration);
748
749/// Registry entry for component startup constructors.
750pub struct ComponentInitRegistration {
751    pub name: &'static str,
752    pub build: fn(&mut World) -> SystemId<(), ()>,
753}
754
755inventory::collect!(ComponentInitRegistration);
756
757/// Basic event wrapper passed to untyped HTML handlers.
758#[derive(Clone, Copy)]
759pub struct HtmlEvent {
760    pub entity: Entity,
761}
762
763impl HtmlEvent {
764    /// Returns the target entity for the event.
765    pub fn target(&self) -> Entity {
766        self.entity
767    }
768}
769
770/// Registry of HTML event handlers by name and event type.
771#[derive(Default, Resource)]
772pub struct HtmlFunctionRegistry {
773    pub click: HashMap<String, SystemId<In<HtmlEvent>>>,
774    pub mousedown: HashMap<String, SystemId<In<HtmlEvent>>>,
775    pub mouseup: HashMap<String, SystemId<In<HtmlEvent>>>,
776    pub over: HashMap<String, SystemId<In<HtmlEvent>>>,
777    pub out: HashMap<String, SystemId<In<HtmlEvent>>>,
778    pub change: HashMap<String, SystemId<In<HtmlEvent>>>,
779    pub submit: HashMap<String, SystemId<In<HtmlEvent>>>,
780    pub init: HashMap<String, SystemId<In<HtmlEvent>>>,
781    pub focus: HashMap<String, SystemId<In<HtmlEvent>>>,
782    pub scroll: HashMap<String, SystemId<In<HtmlEvent>>>,
783    pub wheel: HashMap<String, SystemId<In<HtmlEvent>>>,
784    pub keydown: HashMap<String, SystemId<In<HtmlEvent>>>,
785    pub keyup: HashMap<String, SystemId<In<HtmlEvent>>>,
786    pub dragstart: HashMap<String, SystemId<In<HtmlEvent>>>,
787    pub drag: HashMap<String, SystemId<In<HtmlEvent>>>,
788    pub dragstop: HashMap<String, SystemId<In<HtmlEvent>>>,
789    pub touchstart: HashMap<String, SystemId<In<HtmlEvent>>>,
790    pub touchmove: HashMap<String, SystemId<In<HtmlEvent>>>,
791    pub touchend: HashMap<String, SystemId<In<HtmlEvent>>>,
792    pub click_typed: HashMap<String, SystemId<In<HtmlClick>>>,
793    pub mousedown_typed: HashMap<String, SystemId<In<HtmlMouseDown>>>,
794    pub mouseup_typed: HashMap<String, SystemId<In<HtmlMouseUp>>>,
795    pub over_typed: HashMap<String, SystemId<In<HtmlMouseOver>>>,
796    pub out_typed: HashMap<String, SystemId<In<HtmlMouseOut>>>,
797    pub change_typed: HashMap<String, SystemId<In<HtmlChange>>>,
798    pub submit_typed: HashMap<String, SystemId<In<HtmlSubmit>>>,
799    pub init_typed: HashMap<String, SystemId<In<HtmlInit>>>,
800    pub focus_typed: HashMap<String, SystemId<In<HtmlFocus>>>,
801    pub scroll_typed: HashMap<String, SystemId<In<HtmlScroll>>>,
802    pub wheel_typed: HashMap<String, SystemId<In<HtmlWheel>>>,
803    pub keydown_typed: HashMap<String, SystemId<In<HtmlKeyDown>>>,
804    pub keyup_typed: HashMap<String, SystemId<In<HtmlKeyUp>>>,
805    pub dragstart_typed: HashMap<String, SystemId<In<HtmlDragStart>>>,
806    pub drag_typed: HashMap<String, SystemId<In<HtmlDrag>>>,
807    pub dragstop_typed: HashMap<String, SystemId<In<HtmlDragStop>>>,
808    pub touchstart_typed: HashMap<String, SystemId<In<HtmlTouchStart>>>,
809    pub touchmove_typed: HashMap<String, SystemId<In<HtmlTouchMove>>>,
810    pub touchend_typed: HashMap<String, SystemId<In<HtmlTouchEnd>>>,
811}
812
813/// Component storing event handler names attached in HTML.
814#[derive(Component, Reflect, Default, Clone, Debug, PartialEq)]
815#[reflect(Component)]
816pub struct HtmlEventBindings {
817    pub onclick: Option<String>,
818    pub onmousedown: Option<String>,
819    pub onmouseup: Option<String>,
820    pub onmouseover: Option<String>,
821    pub onmouseout: Option<String>,
822    pub onchange: Option<String>,
823    pub oninit: Option<String>,
824    pub onfoucs: Option<String>,
825    pub onscroll: Option<String>,
826    pub onwheel: Option<String>,
827    pub onkeydown: Option<String>,
828    pub onkeyup: Option<String>,
829    pub ondragstart: Option<String>,
830    pub ondrag: Option<String>,
831    pub ondragstop: Option<String>,
832    pub ontouchstart: Option<String>,
833    pub ontouchmove: Option<String>,
834    pub ontouchend: Option<String>,
835    #[reflect(ignore)]
836    pub inline: HtmlInlineEventBindings,
837}
838
839/// Click event sent from HTML widgets.
840#[derive(EntityEvent, Clone, Copy)]
841pub struct HtmlClick {
842    #[event_target]
843    pub entity: Entity,
844    pub position: Vec2,
845    pub inner_position: Vec2,
846}
847
848/// Mouse-down event sent from HTML widgets.
849#[derive(EntityEvent, Clone, Copy)]
850pub struct HtmlMouseDown {
851    #[event_target]
852    pub entity: Entity,
853    pub button: PointerButton,
854    pub position: Vec2,
855    pub inner_position: Vec2,
856}
857
858/// Mouse-up event sent from HTML widgets.
859#[derive(EntityEvent, Clone, Copy)]
860pub struct HtmlMouseUp {
861    #[event_target]
862    pub entity: Entity,
863    pub button: PointerButton,
864    pub position: Vec2,
865    pub inner_position: Vec2,
866}
867
868/// Mouse-over event sent from HTML widgets.
869#[derive(EntityEvent, Clone, Copy)]
870pub struct HtmlMouseOver {
871    #[event_target]
872    pub entity: Entity,
873}
874
875/// Mouse-out event sent from HTML widgets.
876#[derive(EntityEvent, Clone, Copy)]
877pub struct HtmlMouseOut {
878    #[event_target]
879    pub entity: Entity,
880}
881
882/// Change action types for HTML change events.
883#[derive(Clone, Copy, Debug, PartialEq, Eq)]
884pub enum HtmlChangeAction {
885    /// Variant `State`.
886    State,
887    /// Variant `Style`.
888    Style,
889    /// Variant `Unknown`.
890    Unknown,
891}
892
893/// Change event emitted by HTML widgets.
894#[derive(EntityEvent, Clone, Copy)]
895pub struct HtmlChange {
896    #[event_target]
897    pub entity: Entity,
898    pub action: HtmlChangeAction,
899}
900
901/// Form submit event emitted by HTML forms.
902#[derive(EntityEvent, Clone)]
903pub struct HtmlSubmit {
904    #[event_target]
905    pub entity: Entity,
906    pub submitter: Entity,
907    pub action: String,
908    pub data: HashMap<String, String>,
909}
910
911/// Init event emitted after widgets are constructed.
912#[derive(EntityEvent, Clone, Copy)]
913pub struct HtmlInit {
914    #[event_target]
915    pub entity: Entity,
916}
917
918/// Focus transition state for focus events.
919#[derive(Clone, Copy, Debug, PartialEq, Eq)]
920pub enum HtmlFocusState {
921    /// Variant `Gained`.
922    Gained,
923    /// Variant `Lost`.
924    Lost,
925}
926
927/// Focus event emitted by HTML widgets.
928#[derive(EntityEvent, Clone, Copy)]
929pub struct HtmlFocus {
930    #[event_target]
931    pub entity: Entity,
932    pub state: HtmlFocusState,
933}
934
935/// Scroll event emitted by HTML widgets.
936#[derive(EntityEvent, Clone, Copy)]
937pub struct HtmlScroll {
938    #[event_target]
939    pub entity: Entity,
940    pub delta: Vec2,
941    pub x: f32,
942    pub y: f32,
943}
944
945/// Wheel event emitted by HTML widgets.
946#[derive(EntityEvent, Clone, Copy)]
947pub struct HtmlWheel {
948    #[event_target]
949    pub entity: Entity,
950    pub unit: bevy::input::mouse::MouseScrollUnit,
951    pub delta: Vec2,
952    pub position: Vec2,
953    pub inner_position: Vec2,
954}
955
956/// Key-down event emitted by HTML widgets.
957#[derive(EntityEvent, Clone, Copy)]
958pub struct HtmlKeyDown {
959    #[event_target]
960    pub entity: Entity,
961    pub key: KeyCode,
962}
963
964/// Key-up event emitted by HTML widgets.
965#[derive(EntityEvent, Clone, Copy)]
966pub struct HtmlKeyUp {
967    #[event_target]
968    pub entity: Entity,
969    pub key: KeyCode,
970}
971
972/// Drag-start event emitted by HTML widgets.
973#[derive(EntityEvent, Clone, Copy)]
974pub struct HtmlDragStart {
975    #[event_target]
976    pub entity: Entity,
977    pub position: Vec2,
978}
979
980/// Drag event emitted by HTML widgets.
981#[derive(EntityEvent, Clone, Copy)]
982pub struct HtmlDrag {
983    #[event_target]
984    pub entity: Entity,
985    pub position: Vec2,
986}
987
988/// Drag-stop event emitted by HTML widgets.
989#[derive(EntityEvent, Clone, Copy)]
990pub struct HtmlDragStop {
991    #[event_target]
992    pub entity: Entity,
993    pub position: Vec2,
994}
995
996/// Touch-start event emitted by HTML widgets.
997#[derive(EntityEvent, Clone, Copy)]
998pub struct HtmlTouchStart {
999    #[event_target]
1000    pub entity: Entity,
1001    pub touch_id: u64,
1002    pub position: Vec2,
1003    pub inner_position: Vec2,
1004}
1005
1006/// Touch-move event emitted by HTML widgets.
1007#[derive(EntityEvent, Clone, Copy)]
1008pub struct HtmlTouchMove {
1009    #[event_target]
1010    pub entity: Entity,
1011    pub touch_id: u64,
1012    pub position: Vec2,
1013    pub inner_position: Vec2,
1014    pub delta: Vec2,
1015}
1016
1017/// Touch-end event emitted by HTML widgets.
1018#[derive(EntityEvent, Clone, Copy)]
1019pub struct HtmlTouchEnd {
1020    #[event_target]
1021    pub entity: Entity,
1022    pub touch_id: u64,
1023    pub position: Vec2,
1024    pub inner_position: Vec2,
1025}
1026
1027/// Main plugin for HTML UI: converter + builder + reload integration.
1028pub struct ExtendedUiHtmlPlugin;
1029
1030impl Plugin for ExtendedUiHtmlPlugin {
1031    /// Registers HTML resources, systems, and plugins.
1032    fn build(&self, app: &mut App) {
1033        app.add_message::<HtmlChangeEvent>();
1034
1035        app.init_resource::<HtmlStructureMap>();
1036        app.init_resource::<HtmlFunctionRegistry>();
1037        app.init_resource::<HtmlDirty>();
1038        app.init_resource::<HtmlPendingReveal>();
1039        app.init_resource::<HtmlInitDelay>();
1040        app.init_resource::<UILang>();
1041        app.init_resource::<UiLangState>();
1042        app.init_resource::<UiLangVariables>();
1043        app.init_resource::<UiSharedValues>();
1044
1045        app.register_type::<HtmlEventBindings>();
1046        app.register_type::<HtmlSource>();
1047        app.register_type::<HtmlStyle>();
1048        app.register_type::<HtmlInnerContent>();
1049        app.register_type::<HtmlTextBinding>();
1050
1051        app.configure_sets(
1052            Update,
1053            (
1054                HtmlSystemSet::Convert,
1055                HtmlSystemSet::Build,
1056                HtmlSystemSet::ShowWidgets,
1057                HtmlSystemSet::Bindings,
1058            )
1059                .chain(),
1060        );
1061        app.add_plugins((
1062            HtmlConverterSystem,
1063            HtmlBuilderSystem,
1064            HtmlReloadPlugin,
1065            HtmlEventBindingsPlugin,
1066        ));
1067
1068        app.add_systems(PreUpdate, sync_shared_values_system);
1069
1070        app.add_systems(Startup, (run_component_inits, register_html_fns));
1071    }
1072}
1073
1074fn sync_shared_values_system(world: &mut World) {
1075    refresh_shared_values(world);
1076    #[cfg(feature = "extended-framework")]
1077    sync_ui_binding_store_values(world);
1078}
1079
1080/// Registers all HTML event handlers collected via `inventory`.
1081pub fn register_html_fns(world: &mut World) {
1082    let mut to_insert: Vec<(String, SystemId<In<HtmlEvent>>)> = Vec::new();
1083
1084    for item in inventory::iter::<HtmlFnRegistration> {
1085        match item {
1086            HtmlFnRegistration::HtmlEvent { name, build } => {
1087                let id = (*build)(world);
1088                to_insert.push(((*name).to_string(), id));
1089            }
1090            HtmlFnRegistration::HtmlClick { name, build } => {
1091                let id = (*build)(world);
1092                world
1093                    .resource_mut::<HtmlFunctionRegistry>()
1094                    .click_typed
1095                    .insert((*name).to_string(), id);
1096            }
1097            HtmlFnRegistration::HtmlMouseDown { name, build } => {
1098                let id = (*build)(world);
1099                world
1100                    .resource_mut::<HtmlFunctionRegistry>()
1101                    .mousedown_typed
1102                    .insert((*name).to_string(), id);
1103            }
1104            HtmlFnRegistration::HtmlMouseUp { name, build } => {
1105                let id = (*build)(world);
1106                world
1107                    .resource_mut::<HtmlFunctionRegistry>()
1108                    .mouseup_typed
1109                    .insert((*name).to_string(), id);
1110            }
1111            HtmlFnRegistration::HtmlChange { name, build } => {
1112                let id = (*build)(world);
1113                world
1114                    .resource_mut::<HtmlFunctionRegistry>()
1115                    .change_typed
1116                    .insert((*name).to_string(), id);
1117            }
1118            HtmlFnRegistration::HtmlSubmit { name, build } => {
1119                let id = (*build)(world);
1120                world
1121                    .resource_mut::<HtmlFunctionRegistry>()
1122                    .submit_typed
1123                    .insert((*name).to_string(), id);
1124            }
1125            HtmlFnRegistration::HtmlInit { name, build } => {
1126                let id = (*build)(world);
1127                world
1128                    .resource_mut::<HtmlFunctionRegistry>()
1129                    .init_typed
1130                    .insert((*name).to_string(), id);
1131            }
1132            HtmlFnRegistration::HtmlMouseOut { name, build } => {
1133                let id = (*build)(world);
1134                world
1135                    .resource_mut::<HtmlFunctionRegistry>()
1136                    .out_typed
1137                    .insert((*name).to_string(), id);
1138            }
1139            HtmlFnRegistration::HtmlMouseOver { name, build } => {
1140                let id = (*build)(world);
1141                world
1142                    .resource_mut::<HtmlFunctionRegistry>()
1143                    .over_typed
1144                    .insert((*name).to_string(), id);
1145            }
1146            HtmlFnRegistration::HtmlFocus { name, build } => {
1147                let id = (*build)(world);
1148                world
1149                    .resource_mut::<HtmlFunctionRegistry>()
1150                    .focus_typed
1151                    .insert((*name).to_string(), id);
1152            }
1153            HtmlFnRegistration::HtmlScroll { name, build } => {
1154                let id = (*build)(world);
1155                world
1156                    .resource_mut::<HtmlFunctionRegistry>()
1157                    .scroll_typed
1158                    .insert((*name).to_string(), id);
1159            }
1160            HtmlFnRegistration::HtmlWheel { name, build } => {
1161                let id = (*build)(world);
1162                world
1163                    .resource_mut::<HtmlFunctionRegistry>()
1164                    .wheel_typed
1165                    .insert((*name).to_string(), id);
1166            }
1167            HtmlFnRegistration::HtmlKeyDown { name, build } => {
1168                let id = (*build)(world);
1169                world
1170                    .resource_mut::<HtmlFunctionRegistry>()
1171                    .keydown_typed
1172                    .insert((*name).to_string(), id);
1173            }
1174            HtmlFnRegistration::HtmlKeyUp { name, build } => {
1175                let id = (*build)(world);
1176                world
1177                    .resource_mut::<HtmlFunctionRegistry>()
1178                    .keyup_typed
1179                    .insert((*name).to_string(), id);
1180            }
1181            HtmlFnRegistration::HtmlDragStart { name, build } => {
1182                let id = (*build)(world);
1183                world
1184                    .resource_mut::<HtmlFunctionRegistry>()
1185                    .dragstart_typed
1186                    .insert((*name).to_string(), id);
1187            }
1188            HtmlFnRegistration::HtmlDrag { name, build } => {
1189                let id = (*build)(world);
1190                world
1191                    .resource_mut::<HtmlFunctionRegistry>()
1192                    .drag_typed
1193                    .insert((*name).to_string(), id);
1194            }
1195            HtmlFnRegistration::HtmlDragStop { name, build } => {
1196                let id = (*build)(world);
1197                world
1198                    .resource_mut::<HtmlFunctionRegistry>()
1199                    .dragstop_typed
1200                    .insert((*name).to_string(), id);
1201            }
1202            HtmlFnRegistration::HtmlTouchStart { name, build } => {
1203                let id = (*build)(world);
1204                world
1205                    .resource_mut::<HtmlFunctionRegistry>()
1206                    .touchstart_typed
1207                    .insert((*name).to_string(), id);
1208            }
1209            HtmlFnRegistration::HtmlTouchMove { name, build } => {
1210                let id = (*build)(world);
1211                world
1212                    .resource_mut::<HtmlFunctionRegistry>()
1213                    .touchmove_typed
1214                    .insert((*name).to_string(), id);
1215            }
1216            HtmlFnRegistration::HtmlTouchEnd { name, build } => {
1217                let id = (*build)(world);
1218                world
1219                    .resource_mut::<HtmlFunctionRegistry>()
1220                    .touchend_typed
1221                    .insert((*name).to_string(), id);
1222            }
1223        }
1224    }
1225
1226    let mut reg = world.resource_mut::<HtmlFunctionRegistry>();
1227    for (name, id) in to_insert {
1228        reg.change.insert(name.clone(), id);
1229        reg.submit.insert(name.clone(), id);
1230        reg.click.insert(name.clone(), id);
1231        reg.mousedown.insert(name.clone(), id);
1232        reg.mouseup.insert(name.clone(), id);
1233        reg.focus.insert(name.clone(), id);
1234        reg.init.insert(name.clone(), id);
1235        reg.scroll.insert(name.clone(), id);
1236        reg.wheel.insert(name.clone(), id);
1237        reg.keydown.insert(name.clone(), id);
1238        reg.keyup.insert(name.clone(), id);
1239        reg.dragstart.insert(name.clone(), id);
1240        reg.drag.insert(name.clone(), id);
1241        reg.dragstop.insert(name.clone(), id);
1242        reg.touchstart.insert(name.clone(), id);
1243        reg.touchmove.insert(name.clone(), id);
1244        reg.touchend.insert(name.clone(), id);
1245        reg.out.insert(name.clone(), id);
1246        reg.over.insert(name.clone(), id);
1247        debug!("Registered html fn '{name}' with id {id:?}");
1248    }
1249}
1250
1251/// Runs all component constructors registered via `#[component_init]`.
1252pub fn run_component_inits(world: &mut World) {
1253    for item in inventory::iter::<ComponentInitRegistration> {
1254        let id = (item.build)(world);
1255        if let Err(err) = world.run_system(id) {
1256            warn!("component init '{}' failed: {err}", item.name);
1257        }
1258    }
1259}