1#[cfg(not(feature = "std"))]
8use alloc::string::ToString;
9use alloc::{boxed::Box, collections::btree_map::BTreeMap, string::String, vec::Vec};
10use core::{
11 fmt,
12 hash::{Hash, Hasher},
13 iter::FromIterator,
14 mem,
15 sync::atomic::{AtomicUsize, Ordering},
16};
17
18use azul_css::{
19 css::{BoxOrStatic, Css, NodeTypeTag},
20 codegen::format::GetHash,
21 props::{
22 basic::{FloatValue, FontRef},
23 layout::{LayoutDisplay, LayoutFloat, LayoutPosition},
24 property::CssProperty,
25 },
26 AzString, OptionString,
27};
28
29pub use crate::a11y::*;
31pub use crate::events::{
32 ApplicationEventFilter, ComponentEventFilter, EventFilter, FocusEventFilter, HoverEventFilter,
33 WindowEventFilter,
34};
35pub use crate::id::{Node, NodeHierarchy, NodeId};
36use crate::{
37 callbacks::{
38 CoreCallback, CoreCallbackData, CoreCallbackDataVec, CoreCallbackType, VirtualViewCallback,
39 VirtualViewCallbackType,
40 },
41 geom::LogicalPosition,
42 id::{NodeDataContainer, NodeDataContainerRef, NodeDataContainerRefMut},
43 menu::Menu,
44 prop_cache::{CssPropertyCache, CssPropertyCachePtr},
45 refany::{OptionRefAny, RefAny},
46 resources::{
47 image_ref_get_hash, CoreImageCallback, ImageMask, ImageRef, ImageRefHash, RendererResources,
48 },
49 styled_dom::{
50 CompactDom, NodeHierarchyItemId, StyleFontFamilyHash, StyledDom, StyledNode,
51 StyledNodeState,
52 },
53 window::OptionVirtualKeyCodeCombo,
54};
55pub use azul_css::dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec};
56
57static TAG_ID: AtomicUsize = AtomicUsize::new(1);
58
59#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
61#[repr(C)]
62pub enum InputType {
63 Text,
65 Button,
67 Checkbox,
69 Color,
71 Date,
73 Datetime,
75 DatetimeLocal,
77 Email,
79 File,
81 Hidden,
83 Image,
85 Month,
87 Number,
89 Password,
91 Radio,
93 Range,
95 Reset,
97 Search,
99 Submit,
101 Tel,
103 Time,
105 Url,
107 Week,
109}
110
111impl InputType {
112 #[must_use] pub const fn as_str(&self) -> &'static str {
114 match self {
115 Self::Text => "text",
116 Self::Button => "button",
117 Self::Checkbox => "checkbox",
118 Self::Color => "color",
119 Self::Date => "date",
120 Self::Datetime => "datetime",
121 Self::DatetimeLocal => "datetime-local",
122 Self::Email => "email",
123 Self::File => "file",
124 Self::Hidden => "hidden",
125 Self::Image => "image",
126 Self::Month => "month",
127 Self::Number => "number",
128 Self::Password => "password",
129 Self::Radio => "radio",
130 Self::Range => "range",
131 Self::Reset => "reset",
132 Self::Search => "search",
133 Self::Submit => "submit",
134 Self::Tel => "tel",
135 Self::Time => "time",
136 Self::Url => "url",
137 Self::Week => "week",
138 }
139 }
140}
141
142#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
143#[repr(C)]
144pub struct TagId {
145 pub inner: u64,
146}
147
148impl ::core::fmt::Display for TagId {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 f.debug_struct("TagId").field("inner", &self.inner).finish()
151 }
152}
153
154impl_option!(
155 TagId,
156 OptionTagId,
157 [Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash]
158);
159
160impl TagId {
161 #[must_use] pub const fn into_crate_internal(&self) -> Self {
162 Self { inner: self.inner }
163 }
164 #[must_use] pub const fn from_crate_internal(t: Self) -> Self {
165 t
166 }
167
168 pub fn unique() -> Self {
179 loop {
180 let current = TAG_ID.load(Ordering::SeqCst);
181 let next = if current == usize::MAX { 1 } else { current + 1 };
182 if TAG_ID.compare_exchange(current, next, Ordering::SeqCst, Ordering::SeqCst).is_ok() {
183 return Self { inner: current as u64 };
184 }
185 }
186 }
187}
188
189#[derive(Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
192#[repr(C)]
193pub struct ScrollTagId {
194 pub inner: TagId,
195}
196
197impl ::core::fmt::Display for ScrollTagId {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 f.debug_struct("ScrollTagId")
200 .field("inner", &self.inner)
201 .finish()
202 }
203}
204
205impl ::core::fmt::Debug for ScrollTagId {
206 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207 write!(f, "{self}")
208 }
209}
210
211impl ScrollTagId {
212 #[must_use] pub fn unique() -> Self {
215 Self {
216 inner: TagId::unique(),
217 }
218 }
219}
220
221#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
223#[repr(C)]
224pub enum ScrollbarOrientation {
225 Horizontal,
226 Vertical,
227}
228
229#[derive(Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
232#[repr(C)]
233pub struct DomNodeHash {
234 pub inner: u64,
235}
236
237impl ::core::fmt::Debug for DomNodeHash {
238 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239 write!(f, "DomNodeHash({})", self.inner)
240 }
241}
242
243#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)]
246#[repr(C, u8)]
247pub enum NodeType {
248 Html,
251 Head,
253 Body,
255 Div,
257 P,
259 Article,
261 Section,
263 Nav,
265 Aside,
267 Header,
269 Footer,
271 Main,
273 Figure,
275 FigCaption,
277 H1,
279 H2,
280 H3,
281 H4,
282 H5,
283 H6,
284 Br,
286 Hr,
288 Pre,
290 BlockQuote,
292 Address,
294 Details,
296 Summary,
298 Dialog,
300
301 Ul,
304 Ol,
306 Li,
308 Dl,
310 Dt,
312 Dd,
314 Menu,
316 MenuItem,
318 Dir,
320
321 Table,
324 Caption,
326 THead,
328 TBody,
330 TFoot,
332 Tr,
334 Th,
336 Td,
338 ColGroup,
340 Col,
342
343 Form,
346 FieldSet,
348 Legend,
350 Label,
352 Input,
354 Button,
356 Select,
358 OptGroup,
360 SelectOption,
362 TextArea,
364 Output,
366 Progress,
368 Meter,
370 DataList,
372
373 Span,
376 A,
378 Em,
380 Strong,
382 B,
384 I,
386 U,
388 S,
390 Mark,
392 Del,
394 Ins,
396 Code,
398 Samp,
400 Kbd,
402 Var,
404 Cite,
406 Dfn,
408 Abbr,
410 Acronym,
412 Q,
414 Time,
416 Sub,
418 Sup,
420 Small,
422 Big,
424 Bdo,
426 Bdi,
428 Wbr,
430 Ruby,
432 Rt,
434 Rtc,
436 Rp,
438 Data,
440
441 Canvas,
444 Object,
446 Param,
448 Embed,
450 Audio,
452 Video,
454 Source,
456 Track,
458 Map,
460 Area,
462 Svg,
465 SvgG,
467 SvgDefs,
469 SvgSymbol,
471 SvgUse,
473 SvgSwitch,
475
476 SvgPath,
479 SvgCircle,
481 SvgRect,
483 SvgEllipse,
485 SvgLine,
487 SvgPolygon,
489 SvgPolyline,
491
492 SvgText(AzString),
495 SvgTspan,
497 SvgTextPath,
499
500 SvgLinearGradient,
503 SvgRadialGradient,
505 SvgStop,
507 SvgPattern,
509
510 SvgClipPathElement,
513 SvgMask,
515
516 SvgFilter,
519 SvgFeBlend,
521 SvgFeColorMatrix,
523 SvgFeComponentTransfer,
525 SvgFeComposite,
527 SvgFeConvolveMatrix,
529 SvgFeDiffuseLighting,
531 SvgFeDisplacementMap,
533 SvgFeDistantLight,
535 SvgFeDropShadow,
537 SvgFeFlood,
539 SvgFeFuncR,
541 SvgFeFuncG,
543 SvgFeFuncB,
545 SvgFeFuncA,
547 SvgFeGaussianBlur,
549 SvgFeImage,
551 SvgFeMerge,
553 SvgFeMergeNode,
555 SvgFeMorphology,
557 SvgFeOffset,
559 SvgFePointLight,
561 SvgFeSpecularLighting,
563 SvgFeSpotLight,
565 SvgFeTile,
567 SvgFeTurbulence,
569
570 SvgMarker,
573 SvgImage(ImageRef),
575 SvgForeignObject,
577
578 SvgTitle,
581 SvgDesc,
583 SvgMetadata,
585 SvgA,
587 SvgView,
589 SvgStyle,
591 SvgScript,
593
594 SvgAnimate,
597 SvgAnimateMotion,
599 SvgAnimateTransform,
601 SvgSet,
603 SvgMpath,
605
606 Title,
609 Meta,
611 Link,
613 Script,
615 Style,
617 Base,
619
620 Before,
623 After,
625 Marker,
627 Placeholder,
629
630 Text(BoxOrStatic<AzString>),
635 Image(BoxOrStatic<ImageRef>),
638 VirtualView,
640 Icon(BoxOrStatic<AzString>),
644 GeolocationProbe(crate::geolocation::GeolocationProbeConfig),
650}
651
652pub type BoxOrStaticImageRef = BoxOrStatic<ImageRef>;
654
655impl_option!(NodeType, OptionNodeType, copy = false, [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]);
656
657impl NodeType {
658 #[allow(clippy::too_many_lines)] fn to_library_owned_nodetype(&self) -> Self {
660 use self::NodeType::{Html, Head, Body, Div, P, Article, Section, Nav, Aside, Header, Footer, Main, Figure, FigCaption, H1, H2, H3, H4, H5, H6, Br, Hr, Pre, BlockQuote, Address, Details, Summary, Dialog, Ul, Ol, Li, Dl, Dt, Dd, Menu, MenuItem, Dir, Table, Caption, THead, TBody, TFoot, Tr, Th, Td, ColGroup, Col, Form, FieldSet, Legend, Label, Input, Button, Select, OptGroup, SelectOption, TextArea, Output, Progress, Meter, DataList, Span, A, Em, Strong, B, I, U, S, Mark, Del, Ins, Code, Samp, Kbd, Var, Cite, Dfn, Abbr, Acronym, Q, Time, Sub, Sup, Small, Big, Bdo, Bdi, Wbr, Ruby, Rt, Rtc, Rp, Data, Canvas, Object, Param, Embed, Audio, Video, Source, Track, Map, Area, Svg, SvgG, SvgDefs, SvgSymbol, SvgUse, SvgSwitch, SvgPath, SvgCircle, SvgRect, SvgEllipse, SvgLine, SvgPolygon, SvgPolyline, SvgText, SvgTspan, SvgTextPath, SvgLinearGradient, SvgRadialGradient, SvgStop, SvgPattern, SvgClipPathElement, SvgMask, SvgFilter, SvgFeBlend, SvgFeColorMatrix, SvgFeComponentTransfer, SvgFeComposite, SvgFeConvolveMatrix, SvgFeDiffuseLighting, SvgFeDisplacementMap, SvgFeDistantLight, SvgFeDropShadow, SvgFeFlood, SvgFeFuncR, SvgFeFuncG, SvgFeFuncB, SvgFeFuncA, SvgFeGaussianBlur, SvgFeImage, SvgFeMerge, SvgFeMergeNode, SvgFeMorphology, SvgFeOffset, SvgFePointLight, SvgFeSpecularLighting, SvgFeSpotLight, SvgFeTile, SvgFeTurbulence, SvgMarker, SvgImage, SvgForeignObject, SvgTitle, SvgDesc, SvgMetadata, SvgA, SvgView, SvgStyle, SvgScript, SvgAnimate, SvgAnimateMotion, SvgAnimateTransform, SvgSet, SvgMpath, Title, Meta, Link, Script, Style, Base, Before, After, Marker, Placeholder, Text, Image, VirtualView, Icon, GeolocationProbe};
661 match self {
662 Html => Html,
663 Head => Head,
664 Body => Body,
665 Div => Div,
666 P => P,
667 Article => Article,
668 Section => Section,
669 Nav => Nav,
670 Aside => Aside,
671 Header => Header,
672 Footer => Footer,
673 Main => Main,
674 Figure => Figure,
675 FigCaption => FigCaption,
676 H1 => H1,
677 H2 => H2,
678 H3 => H3,
679 H4 => H4,
680 H5 => H5,
681 H6 => H6,
682 Br => Br,
683 Hr => Hr,
684 Pre => Pre,
685 BlockQuote => BlockQuote,
686 Address => Address,
687 Details => Details,
688 Summary => Summary,
689 Dialog => Dialog,
690 Ul => Ul,
691 Ol => Ol,
692 Li => Li,
693 Dl => Dl,
694 Dt => Dt,
695 Dd => Dd,
696 Menu => Menu,
697 MenuItem => MenuItem,
698 Dir => Dir,
699 Table => Table,
700 Caption => Caption,
701 THead => THead,
702 TBody => TBody,
703 TFoot => TFoot,
704 Tr => Tr,
705 Th => Th,
706 Td => Td,
707 ColGroup => ColGroup,
708 Col => Col,
709 Form => Form,
710 FieldSet => FieldSet,
711 Legend => Legend,
712 Label => Label,
713 Input => Input,
714 Button => Button,
715 Select => Select,
716 OptGroup => OptGroup,
717 SelectOption => SelectOption,
718 TextArea => TextArea,
719 Output => Output,
720 Progress => Progress,
721 Meter => Meter,
722 DataList => DataList,
723 Span => Span,
724 A => A,
725 Em => Em,
726 Strong => Strong,
727 B => B,
728 I => I,
729 U => U,
730 S => S,
731 Mark => Mark,
732 Del => Del,
733 Ins => Ins,
734 Code => Code,
735 Samp => Samp,
736 Kbd => Kbd,
737 Var => Var,
738 Cite => Cite,
739 Dfn => Dfn,
740 Abbr => Abbr,
741 Acronym => Acronym,
742 Q => Q,
743 Time => Time,
744 Sub => Sub,
745 Sup => Sup,
746 Small => Small,
747 Big => Big,
748 Bdo => Bdo,
749 Bdi => Bdi,
750 Wbr => Wbr,
751 Ruby => Ruby,
752 Rt => Rt,
753 Rtc => Rtc,
754 Rp => Rp,
755 Data => Data,
756 Canvas => Canvas,
757 Object => Object,
758 Param => Param,
759 Embed => Embed,
760 Audio => Audio,
761 Video => Video,
762 Source => Source,
763 Track => Track,
764 Map => Map,
765 Area => Area,
766 Svg => Svg, SvgG => SvgG, SvgDefs => SvgDefs, SvgSymbol => SvgSymbol,
768 SvgUse => SvgUse, SvgSwitch => SvgSwitch,
769 SvgPath => SvgPath, SvgCircle => SvgCircle, SvgRect => SvgRect,
771 SvgEllipse => SvgEllipse, SvgLine => SvgLine,
772 SvgPolygon => SvgPolygon, SvgPolyline => SvgPolyline,
773 SvgText(s) => SvgText(s.clone_self()),
775 SvgTspan => SvgTspan, SvgTextPath => SvgTextPath,
776 SvgLinearGradient => SvgLinearGradient, SvgRadialGradient => SvgRadialGradient,
778 SvgStop => SvgStop, SvgPattern => SvgPattern,
779 SvgClipPathElement => SvgClipPathElement, SvgMask => SvgMask,
781 SvgFilter => SvgFilter, SvgFeBlend => SvgFeBlend,
783 SvgFeColorMatrix => SvgFeColorMatrix,
784 SvgFeComponentTransfer => SvgFeComponentTransfer,
785 SvgFeComposite => SvgFeComposite, SvgFeConvolveMatrix => SvgFeConvolveMatrix,
786 SvgFeDiffuseLighting => SvgFeDiffuseLighting,
787 SvgFeDisplacementMap => SvgFeDisplacementMap,
788 SvgFeDistantLight => SvgFeDistantLight, SvgFeDropShadow => SvgFeDropShadow,
789 SvgFeFlood => SvgFeFlood,
790 SvgFeFuncR => SvgFeFuncR, SvgFeFuncG => SvgFeFuncG,
791 SvgFeFuncB => SvgFeFuncB, SvgFeFuncA => SvgFeFuncA,
792 SvgFeGaussianBlur => SvgFeGaussianBlur, SvgFeImage => SvgFeImage,
793 SvgFeMerge => SvgFeMerge, SvgFeMergeNode => SvgFeMergeNode,
794 SvgFeMorphology => SvgFeMorphology, SvgFeOffset => SvgFeOffset,
795 SvgFePointLight => SvgFePointLight,
796 SvgFeSpecularLighting => SvgFeSpecularLighting,
797 SvgFeSpotLight => SvgFeSpotLight,
798 SvgFeTile => SvgFeTile, SvgFeTurbulence => SvgFeTurbulence,
799 SvgMarker => SvgMarker,
801 SvgImage(i) => SvgImage(i.clone()),
802 SvgForeignObject => SvgForeignObject,
803 SvgTitle => SvgTitle, SvgDesc => SvgDesc, SvgMetadata => SvgMetadata,
805 SvgA => SvgA, SvgView => SvgView,
806 SvgStyle => SvgStyle, SvgScript => SvgScript,
807 SvgAnimate => SvgAnimate, SvgAnimateMotion => SvgAnimateMotion,
809 SvgAnimateTransform => SvgAnimateTransform,
810 SvgSet => SvgSet, SvgMpath => SvgMpath,
811 Title => Title,
813 Meta => Meta,
814 Link => Link,
815 Script => Script,
816 Style => Style,
817 Base => Base,
818 Before => Before,
819 After => After,
820 Marker => Marker,
821 Placeholder => Placeholder,
822
823 Text(s) => Text(BoxOrStatic::heap(s.clone_self())),
824 Image(i) => Image(i.clone()),
825 VirtualView => VirtualView,
826 Icon(s) => Icon(BoxOrStatic::heap(s.clone_self())),
827 GeolocationProbe(cfg) => GeolocationProbe(*cfg),
828 }
829 }
830
831 #[must_use] pub fn format(&self) -> Option<String> {
832 use self::NodeType::{Text, Image, VirtualView, Icon, GeolocationProbe};
833 match self {
834 Text(s) => Some(format!("{s}")),
835 Image(id) => Some(format!("image({id:?})")),
836 VirtualView => Some("virtualized-view".to_string()),
837 Icon(s) => Some(format!("icon({s})")),
838 GeolocationProbe(cfg) => Some(format!(
839 "geolocation-probe(hi={}, bg={}, max={}m, every={}ms)",
840 cfg.high_accuracy, cfg.background, cfg.max_accuracy_m, cfg.min_interval_ms
841 )),
842 _ => None,
843 }
844 }
845
846 #[allow(clippy::too_many_lines)] #[must_use] pub const fn get_path(&self) -> NodeTypeTag {
849 match self {
850 Self::Html => NodeTypeTag::Html,
851 Self::Head => NodeTypeTag::Head,
852 Self::Body => NodeTypeTag::Body,
853 Self::Div => NodeTypeTag::Div,
854 Self::P => NodeTypeTag::P,
855 Self::Article => NodeTypeTag::Article,
856 Self::Section => NodeTypeTag::Section,
857 Self::Nav => NodeTypeTag::Nav,
858 Self::Aside => NodeTypeTag::Aside,
859 Self::Header => NodeTypeTag::Header,
860 Self::Footer => NodeTypeTag::Footer,
861 Self::Main => NodeTypeTag::Main,
862 Self::Figure => NodeTypeTag::Figure,
863 Self::FigCaption => NodeTypeTag::FigCaption,
864 Self::H1 => NodeTypeTag::H1,
865 Self::H2 => NodeTypeTag::H2,
866 Self::H3 => NodeTypeTag::H3,
867 Self::H4 => NodeTypeTag::H4,
868 Self::H5 => NodeTypeTag::H5,
869 Self::H6 => NodeTypeTag::H6,
870 Self::Br => NodeTypeTag::Br,
871 Self::Hr => NodeTypeTag::Hr,
872 Self::Pre => NodeTypeTag::Pre,
873 Self::BlockQuote => NodeTypeTag::BlockQuote,
874 Self::Address => NodeTypeTag::Address,
875 Self::Details => NodeTypeTag::Details,
876 Self::Summary => NodeTypeTag::Summary,
877 Self::Dialog => NodeTypeTag::Dialog,
878 Self::Ul => NodeTypeTag::Ul,
879 Self::Ol => NodeTypeTag::Ol,
880 Self::Li => NodeTypeTag::Li,
881 Self::Dl => NodeTypeTag::Dl,
882 Self::Dt => NodeTypeTag::Dt,
883 Self::Dd => NodeTypeTag::Dd,
884 Self::Menu => NodeTypeTag::Menu,
885 Self::MenuItem => NodeTypeTag::MenuItem,
886 Self::Dir => NodeTypeTag::Dir,
887 Self::Table => NodeTypeTag::Table,
888 Self::Caption => NodeTypeTag::Caption,
889 Self::THead => NodeTypeTag::THead,
890 Self::TBody => NodeTypeTag::TBody,
891 Self::TFoot => NodeTypeTag::TFoot,
892 Self::Tr => NodeTypeTag::Tr,
893 Self::Th => NodeTypeTag::Th,
894 Self::Td => NodeTypeTag::Td,
895 Self::ColGroup => NodeTypeTag::ColGroup,
896 Self::Col => NodeTypeTag::Col,
897 Self::Form => NodeTypeTag::Form,
898 Self::FieldSet => NodeTypeTag::FieldSet,
899 Self::Legend => NodeTypeTag::Legend,
900 Self::Label => NodeTypeTag::Label,
901 Self::Input => NodeTypeTag::Input,
902 Self::Button => NodeTypeTag::Button,
903 Self::Select => NodeTypeTag::Select,
904 Self::OptGroup => NodeTypeTag::OptGroup,
905 Self::SelectOption => NodeTypeTag::SelectOption,
906 Self::TextArea => NodeTypeTag::TextArea,
907 Self::Output => NodeTypeTag::Output,
908 Self::Progress => NodeTypeTag::Progress,
909 Self::Meter => NodeTypeTag::Meter,
910 Self::DataList => NodeTypeTag::DataList,
911 Self::Span => NodeTypeTag::Span,
912 Self::A => NodeTypeTag::A,
913 Self::Em => NodeTypeTag::Em,
914 Self::Strong => NodeTypeTag::Strong,
915 Self::B => NodeTypeTag::B,
916 Self::I => NodeTypeTag::I,
917 Self::U => NodeTypeTag::U,
918 Self::S => NodeTypeTag::S,
919 Self::Mark => NodeTypeTag::Mark,
920 Self::Del => NodeTypeTag::Del,
921 Self::Ins => NodeTypeTag::Ins,
922 Self::Code => NodeTypeTag::Code,
923 Self::Samp => NodeTypeTag::Samp,
924 Self::Kbd => NodeTypeTag::Kbd,
925 Self::Var => NodeTypeTag::Var,
926 Self::Cite => NodeTypeTag::Cite,
927 Self::Dfn => NodeTypeTag::Dfn,
928 Self::Abbr => NodeTypeTag::Abbr,
929 Self::Acronym => NodeTypeTag::Acronym,
930 Self::Q => NodeTypeTag::Q,
931 Self::Time => NodeTypeTag::Time,
932 Self::Sub => NodeTypeTag::Sub,
933 Self::Sup => NodeTypeTag::Sup,
934 Self::Small => NodeTypeTag::Small,
935 Self::Big => NodeTypeTag::Big,
936 Self::Bdo => NodeTypeTag::Bdo,
937 Self::Bdi => NodeTypeTag::Bdi,
938 Self::Wbr => NodeTypeTag::Wbr,
939 Self::Ruby => NodeTypeTag::Ruby,
940 Self::Rt => NodeTypeTag::Rt,
941 Self::Rtc => NodeTypeTag::Rtc,
942 Self::Rp => NodeTypeTag::Rp,
943 Self::Data => NodeTypeTag::Data,
944 Self::Canvas => NodeTypeTag::Canvas,
945 Self::Object => NodeTypeTag::Object,
946 Self::Param => NodeTypeTag::Param,
947 Self::Embed => NodeTypeTag::Embed,
948 Self::Audio => NodeTypeTag::Audio,
949 Self::Video => NodeTypeTag::Video,
950 Self::Source => NodeTypeTag::Source,
951 Self::Track => NodeTypeTag::Track,
952 Self::Map => NodeTypeTag::Map,
953 Self::Area => NodeTypeTag::Area,
954 Self::Svg => NodeTypeTag::Svg,
956 Self::SvgG => NodeTypeTag::SvgG,
957 Self::SvgDefs => NodeTypeTag::SvgDefs,
958 Self::SvgSymbol => NodeTypeTag::SvgSymbol,
959 Self::SvgUse => NodeTypeTag::SvgUse,
960 Self::SvgSwitch => NodeTypeTag::SvgSwitch,
961 Self::SvgPath => NodeTypeTag::SvgPath,
962 Self::SvgCircle => NodeTypeTag::SvgCircle,
963 Self::SvgRect => NodeTypeTag::SvgRect,
964 Self::SvgEllipse => NodeTypeTag::SvgEllipse,
965 Self::SvgLine => NodeTypeTag::SvgLine,
966 Self::SvgPolygon => NodeTypeTag::SvgPolygon,
967 Self::SvgPolyline => NodeTypeTag::SvgPolyline,
968 Self::SvgText(_) => NodeTypeTag::SvgText,
969 Self::SvgTspan => NodeTypeTag::SvgTspan,
970 Self::SvgTextPath => NodeTypeTag::SvgTextPath,
971 Self::SvgLinearGradient => NodeTypeTag::SvgLinearGradient,
972 Self::SvgRadialGradient => NodeTypeTag::SvgRadialGradient,
973 Self::SvgStop => NodeTypeTag::SvgStop,
974 Self::SvgPattern => NodeTypeTag::SvgPattern,
975 Self::SvgClipPathElement => NodeTypeTag::SvgClipPathElement,
976 Self::SvgMask => NodeTypeTag::SvgMask,
977 Self::SvgFilter => NodeTypeTag::SvgFilter,
978 Self::SvgFeBlend => NodeTypeTag::SvgFeBlend,
979 Self::SvgFeColorMatrix => NodeTypeTag::SvgFeColorMatrix,
980 Self::SvgFeComponentTransfer => NodeTypeTag::SvgFeComponentTransfer,
981 Self::SvgFeComposite => NodeTypeTag::SvgFeComposite,
982 Self::SvgFeConvolveMatrix => NodeTypeTag::SvgFeConvolveMatrix,
983 Self::SvgFeDiffuseLighting => NodeTypeTag::SvgFeDiffuseLighting,
984 Self::SvgFeDisplacementMap => NodeTypeTag::SvgFeDisplacementMap,
985 Self::SvgFeDistantLight => NodeTypeTag::SvgFeDistantLight,
986 Self::SvgFeDropShadow => NodeTypeTag::SvgFeDropShadow,
987 Self::SvgFeFlood => NodeTypeTag::SvgFeFlood,
988 Self::SvgFeFuncR => NodeTypeTag::SvgFeFuncR,
989 Self::SvgFeFuncG => NodeTypeTag::SvgFeFuncG,
990 Self::SvgFeFuncB => NodeTypeTag::SvgFeFuncB,
991 Self::SvgFeFuncA => NodeTypeTag::SvgFeFuncA,
992 Self::SvgFeGaussianBlur => NodeTypeTag::SvgFeGaussianBlur,
993 Self::SvgFeImage => NodeTypeTag::SvgFeImage,
994 Self::SvgFeMerge => NodeTypeTag::SvgFeMerge,
995 Self::SvgFeMergeNode => NodeTypeTag::SvgFeMergeNode,
996 Self::SvgFeMorphology => NodeTypeTag::SvgFeMorphology,
997 Self::SvgFeOffset => NodeTypeTag::SvgFeOffset,
998 Self::SvgFePointLight => NodeTypeTag::SvgFePointLight,
999 Self::SvgFeSpecularLighting => NodeTypeTag::SvgFeSpecularLighting,
1000 Self::SvgFeSpotLight => NodeTypeTag::SvgFeSpotLight,
1001 Self::SvgFeTile => NodeTypeTag::SvgFeTile,
1002 Self::SvgFeTurbulence => NodeTypeTag::SvgFeTurbulence,
1003 Self::SvgMarker => NodeTypeTag::SvgMarker,
1004 Self::SvgImage(_) => NodeTypeTag::SvgImage,
1005 Self::SvgForeignObject => NodeTypeTag::SvgForeignObject,
1006 Self::SvgTitle => NodeTypeTag::SvgTitle,
1007 Self::SvgDesc => NodeTypeTag::SvgDesc,
1008 Self::SvgMetadata => NodeTypeTag::SvgMetadata,
1009 Self::SvgA => NodeTypeTag::SvgA,
1010 Self::SvgView => NodeTypeTag::SvgView,
1011 Self::SvgStyle => NodeTypeTag::SvgStyle,
1012 Self::SvgScript => NodeTypeTag::SvgScript,
1013 Self::SvgAnimate => NodeTypeTag::SvgAnimate,
1014 Self::SvgAnimateMotion => NodeTypeTag::SvgAnimateMotion,
1015 Self::SvgAnimateTransform => NodeTypeTag::SvgAnimateTransform,
1016 Self::SvgSet => NodeTypeTag::SvgSet,
1017 Self::SvgMpath => NodeTypeTag::SvgMpath,
1018 Self::Title => NodeTypeTag::Title,
1020 Self::Meta => NodeTypeTag::Meta,
1021 Self::Link => NodeTypeTag::Link,
1022 Self::Script => NodeTypeTag::Script,
1023 Self::Style => NodeTypeTag::Style,
1024 Self::Base => NodeTypeTag::Base,
1025 Self::Text(_) => NodeTypeTag::Text,
1026 Self::Image(_) => NodeTypeTag::Img,
1027 Self::VirtualView => NodeTypeTag::VirtualView,
1028 Self::Icon(_) => NodeTypeTag::Icon,
1029 Self::GeolocationProbe(_) => NodeTypeTag::GeolocationProbe,
1030 Self::Before => NodeTypeTag::Before,
1031 Self::After => NodeTypeTag::After,
1032 Self::Marker => NodeTypeTag::Marker,
1033 Self::Placeholder => NodeTypeTag::Placeholder,
1034 }
1035 }
1036
1037 #[must_use] pub const fn is_semantic_for_accessibility(&self) -> bool {
1043 matches!(
1044 self,
1045 Self::Button
1046 | Self::Input
1047 | Self::TextArea
1048 | Self::Select
1049 | Self::A
1050 | Self::H1
1051 | Self::H2
1052 | Self::H3
1053 | Self::H4
1054 | Self::H5
1055 | Self::H6
1056 | Self::Article
1057 | Self::Section
1058 | Self::Nav
1059 | Self::Main
1060 | Self::Header
1061 | Self::Footer
1062 | Self::Aside
1063 )
1064 }
1065}
1066
1067#[derive(Clone, Copy, PartialEq, Eq)]
1069#[repr(C, u8)]
1076pub enum FormattingContext {
1078 Block {
1080 establishes_new_context: bool,
1082 },
1083 Inline,
1085 InlineBlock,
1087 Flex,
1089 Float(LayoutFloat),
1091 OutOfFlow(LayoutPosition),
1093 Table,
1095 TableRowGroup,
1097 TableRow,
1099 TableCell,
1101 TableColumnGroup,
1103 TableCaption,
1105 Grid,
1107 Contents,
1109 None,
1111}
1112
1113impl fmt::Debug for FormattingContext {
1114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1115 match self {
1116 Self::Block {
1117 establishes_new_context,
1118 } => write!(
1119 f,
1120 "Block {{ establishes_new_context: {establishes_new_context:?} }}"
1121 ),
1122 Self::Inline => write!(f, "Inline"),
1123 Self::InlineBlock => write!(f, "InlineBlock"),
1124 Self::Flex => write!(f, "Flex"),
1125 Self::Float(layout_float) => write!(f, "Float({layout_float:?})"),
1126 Self::OutOfFlow(layout_position) => {
1127 write!(f, "OutOfFlow({layout_position:?})")
1128 }
1129 Self::Grid => write!(f, "Grid"),
1130 Self::None => write!(f, "None"),
1131 Self::Table => write!(f, "Table"),
1132 Self::TableRowGroup => write!(f, "TableRowGroup"),
1133 Self::TableRow => write!(f, "TableRow"),
1134 Self::TableCell => write!(f, "TableCell"),
1135 Self::TableColumnGroup => write!(f, "TableColumnGroup"),
1136 Self::TableCaption => write!(f, "TableCaption"),
1137 Self::Contents => write!(f, "Contents"),
1138 }
1139 }
1140}
1141
1142impl Default for FormattingContext {
1143 fn default() -> Self {
1144 Self::Block {
1145 establishes_new_context: false,
1146 }
1147 }
1148}
1149
1150#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1152#[repr(C)]
1153pub enum On {
1154 MouseOver,
1156 MouseDown,
1159 LeftMouseDown,
1162 MiddleMouseDown,
1165 RightMouseDown,
1168 MouseUp,
1170 LeftMouseUp,
1173 MiddleMouseUp,
1176 RightMouseUp,
1179 MouseEnter,
1181 MouseLeave,
1183 Scroll,
1185 TextInput,
1188 VirtualKeyDown,
1196 VirtualKeyUp,
1198 HoveredFile,
1200 DroppedFile,
1202 HoveredFileCancelled,
1204 FocusReceived,
1206 FocusLost,
1208
1209 Default,
1212 Collapse,
1214 Expand,
1216 Increment,
1218 Decrement,
1220}
1221
1222#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1224#[repr(C)]
1225pub struct VirtualViewNode {
1226 pub callback: VirtualViewCallback,
1228 pub refany: RefAny,
1230}
1231
1232#[repr(C, u8)]
1234#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1235pub enum IdOrClass {
1236 Id(AzString),
1237 Class(AzString),
1238}
1239
1240impl_option!(
1241 IdOrClass,
1242 OptionIdOrClass,
1243 copy = false,
1244 [Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord]
1245);
1246
1247impl_vec!(IdOrClass, IdOrClassVec, IdOrClassVecDestructor, IdOrClassVecDestructorType, IdOrClassVecSlice, OptionIdOrClass);
1248impl_vec_debug!(IdOrClass, IdOrClassVec);
1249impl_vec_partialord!(IdOrClass, IdOrClassVec);
1250impl_vec_ord!(IdOrClass, IdOrClassVec);
1251impl_vec_clone!(IdOrClass, IdOrClassVec, IdOrClassVecDestructor);
1252impl_vec_partialeq!(IdOrClass, IdOrClassVec);
1253impl_vec_eq!(IdOrClass, IdOrClassVec);
1254impl_vec_hash!(IdOrClass, IdOrClassVec);
1255
1256impl IdOrClass {
1257 #[must_use] pub fn as_id(&self) -> Option<&str> {
1258 match self {
1259 Self::Id(s) => Some(s.as_str()),
1260 Self::Class(_) => None,
1261 }
1262 }
1263 #[must_use] pub fn as_class(&self) -> Option<&str> {
1264 match self {
1265 Self::Class(s) => Some(s.as_str()),
1266 Self::Id(_) => None,
1267 }
1268 }
1269}
1270
1271#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1273#[repr(C)]
1274pub struct AttributeNameValue {
1275 pub attr_name: AzString,
1276 pub value: AzString,
1277}
1278
1279#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1285#[repr(C, u8)]
1286pub enum AttributeType {
1287 Id(AzString),
1289 Class(AzString),
1291 AriaLabel(AzString),
1293 AriaLabelledBy(AzString),
1295 AriaDescribedBy(AzString),
1297 AriaRole(AzString),
1299 AriaState(AttributeNameValue),
1301 AriaProperty(AttributeNameValue),
1303
1304 Href(AzString),
1306 Rel(AzString),
1308 Target(AzString),
1310
1311 Src(AzString),
1313 Alt(AzString),
1315 Title(AzString),
1317
1318 Name(AzString),
1320 Value(AzString),
1322 InputType(AzString),
1324 Placeholder(AzString),
1326 Required,
1328 Disabled,
1330 Readonly,
1332 CheckedTrue,
1334 CheckedFalse,
1336 Selected,
1338 Max(AzString),
1340 Min(AzString),
1342 Step(AzString),
1344 Pattern(AzString),
1346 MinLength(i32),
1348 MaxLength(i32),
1350 Autocomplete(AzString),
1352
1353 Scope(AzString),
1355 ColSpan(i32),
1357 RowSpan(i32),
1359
1360 TabIndex(i32),
1362 Focusable,
1364
1365 Lang(AzString),
1367 Dir(AzString),
1369
1370 ContentEditable(bool),
1372 Draggable(bool),
1374 Hidden,
1376
1377 Data(AttributeNameValue),
1379 Custom(AttributeNameValue),
1381}
1382
1383impl_option!(
1384 AttributeType,
1385 OptionAttributeType,
1386 copy = false,
1387 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1388);
1389
1390impl_vec!(AttributeType, AttributeTypeVec, AttributeTypeVecDestructor, AttributeTypeVecDestructorType, AttributeTypeVecSlice, OptionAttributeType);
1391impl_vec_debug!(AttributeType, AttributeTypeVec);
1392impl_vec_partialord!(AttributeType, AttributeTypeVec);
1393impl_vec_ord!(AttributeType, AttributeTypeVec);
1394impl_vec_clone!(AttributeType, AttributeTypeVec, AttributeTypeVecDestructor);
1395impl_vec_partialeq!(AttributeType, AttributeTypeVec);
1396impl_vec_eq!(AttributeType, AttributeTypeVec);
1397impl_vec_hash!(AttributeType, AttributeTypeVec);
1398
1399impl AttributeType {
1400 #[must_use] pub fn as_id(&self) -> Option<&str> {
1402 match self {
1403 Self::Id(s) => Some(s.as_str()),
1404 _ => None,
1405 }
1406 }
1407 #[must_use] pub fn as_class(&self) -> Option<&str> {
1409 match self {
1410 Self::Class(s) => Some(s.as_str()),
1411 _ => None,
1412 }
1413 }
1414 #[must_use] pub fn name(&self) -> &str {
1416 match self {
1417 Self::Id(_) => "id",
1418 Self::Class(_) => "class",
1419 Self::AriaLabel(_) => "aria-label",
1420 Self::AriaLabelledBy(_) => "aria-labelledby",
1421 Self::AriaDescribedBy(_) => "aria-describedby",
1422 Self::AriaRole(_) => "role",
1423 Self::AriaState(nv)
1424 | Self::AriaProperty(nv)
1425 | Self::Data(nv)
1426 | Self::Custom(nv) => nv.attr_name.as_str(),
1427 Self::Href(_) => "href",
1428 Self::Rel(_) => "rel",
1429 Self::Target(_) => "target",
1430 Self::Src(_) => "src",
1431 Self::Alt(_) => "alt",
1432 Self::Title(_) => "title",
1433 Self::Name(_) => "name",
1434 Self::Value(_) => "value",
1435 Self::InputType(_) => "type",
1436 Self::Placeholder(_) => "placeholder",
1437 Self::Required => "required",
1438 Self::Disabled => "disabled",
1439 Self::Readonly => "readonly",
1440 Self::CheckedTrue | Self::CheckedFalse => "checked",
1441 Self::Selected => "selected",
1442 Self::Max(_) => "max",
1443 Self::Min(_) => "min",
1444 Self::Step(_) => "step",
1445 Self::Pattern(_) => "pattern",
1446 Self::MinLength(_) => "minlength",
1447 Self::MaxLength(_) => "maxlength",
1448 Self::Autocomplete(_) => "autocomplete",
1449 Self::Scope(_) => "scope",
1450 Self::ColSpan(_) => "colspan",
1451 Self::RowSpan(_) => "rowspan",
1452 Self::TabIndex(_) | Self::Focusable => "tabindex",
1453 Self::Lang(_) => "lang",
1454 Self::Dir(_) => "dir",
1455 Self::ContentEditable(_) => "contenteditable",
1456 Self::Draggable(_) => "draggable",
1457 Self::Hidden => "hidden",
1458 }
1459 }
1460
1461 #[must_use] pub fn value(&self) -> AzString {
1463 match self {
1464 Self::Id(v)
1465 | Self::Class(v)
1466 | Self::AriaLabel(v)
1467 | Self::AriaLabelledBy(v)
1468 | Self::AriaDescribedBy(v)
1469 | Self::AriaRole(v)
1470 | Self::Href(v)
1471 | Self::Rel(v)
1472 | Self::Target(v)
1473 | Self::Src(v)
1474 | Self::Alt(v)
1475 | Self::Title(v)
1476 | Self::Name(v)
1477 | Self::Value(v)
1478 | Self::InputType(v)
1479 | Self::Placeholder(v)
1480 | Self::Max(v)
1481 | Self::Min(v)
1482 | Self::Step(v)
1483 | Self::Pattern(v)
1484 | Self::Autocomplete(v)
1485 | Self::Scope(v)
1486 | Self::Lang(v)
1487 | Self::Dir(v) => v.clone(),
1488
1489 Self::AriaState(nv)
1490 | Self::AriaProperty(nv)
1491 | Self::Data(nv)
1492 | Self::Custom(nv) => nv.value.clone(),
1493
1494 Self::MinLength(n)
1495 | Self::MaxLength(n)
1496 | Self::ColSpan(n)
1497 | Self::RowSpan(n)
1498 | Self::TabIndex(n) => n.to_string().into(),
1499
1500 Self::Focusable => "0".into(),
1501 Self::ContentEditable(b) | Self::Draggable(b) => {
1502 if *b {
1503 "true".into()
1504 } else {
1505 "false".into()
1506 }
1507 }
1508
1509 Self::Required
1510 | Self::Disabled
1511 | Self::Readonly
1512 | Self::CheckedTrue
1513 | Self::CheckedFalse
1514 | Self::Selected
1515 | Self::Hidden => "".into(), }
1517 }
1518
1519 #[must_use] pub const fn is_boolean(&self) -> bool {
1521 matches!(
1522 self,
1523 Self::Required
1524 | Self::Disabled
1525 | Self::Readonly
1526 | Self::CheckedTrue
1527 | Self::CheckedFalse
1528 | Self::Selected
1529 | Self::Hidden
1530 )
1531 }
1532}
1533
1534#[repr(C)]
1537#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
1538pub struct NodeData {
1539 pub node_type: NodeType,
1541 pub callbacks: CoreCallbackDataVec,
1545 pub style: azul_css::css::Css,
1552 pub flags: NodeFlags,
1554 pub accessibility: Option<Box<AccessibilityInfo>>,
1557 extra: Option<Box<NodeDataExt>>,
1562}
1563
1564impl_option!(
1565 NodeData,
1566 OptionNodeData,
1567 copy = false,
1568 [Debug, PartialEq, Eq, PartialOrd, Ord]
1569);
1570
1571impl Hash for NodeData {
1572 fn hash<H: Hasher>(&self, state: &mut H) {
1573 self.node_type.hash(state);
1574 self.attributes().as_ref().hash(state);
1575 self.flags.hash(state);
1576
1577 for callback in self.callbacks.as_ref() {
1580 callback.event.hash(state);
1581 callback.callback.hash(state);
1582 callback.refany.get_type_id().hash(state);
1583 }
1584
1585 for (prop, _conds) in self.style.iter_inline_properties() {
1589 mem::discriminant(prop).hash(state);
1590 }
1591 if let Some(ext) = self.extra.as_ref() {
1592 if let Some(ds) = ext.dataset.as_ref() {
1593 ds.hash(state);
1594 }
1595 if let Some(c) = ext.svg_data.as_ref() {
1596 c.hash(state);
1597 }
1598 if let Some(c) = ext.menu_bar.as_ref() {
1599 c.hash(state);
1600 }
1601 if let Some(c) = ext.context_menu.as_ref() {
1602 c.hash(state);
1603 }
1604 if let Some(vv) = ext.virtual_view.as_ref() {
1605 vv.hash(state);
1606 }
1607 }
1608 }
1609}
1610
1611#[derive(Debug, Clone, PartialEq)]
1619pub struct ComponentOrigin {
1620 pub component_id: AzString,
1622 pub data_model_json: crate::json::Json,
1626}
1627
1628impl Eq for ComponentOrigin {}
1631
1632impl PartialOrd for ComponentOrigin {
1633 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
1634 Some(self.cmp(other))
1635 }
1636}
1637
1638impl Ord for ComponentOrigin {
1639 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
1640 self.component_id.cmp(&other.component_id)
1641 .then_with(|| {
1642 let a = alloc::format!("{}", self.data_model_json);
1643 let b = alloc::format!("{}", other.data_model_json);
1644 a.cmp(&b)
1645 })
1646 }
1647}
1648
1649impl Hash for ComponentOrigin {
1650 fn hash<H: Hasher>(&self, state: &mut H) {
1651 self.component_id.hash(state);
1652 alloc::format!("{}", self.data_model_json).hash(state);
1653 }
1654}
1655
1656impl Default for ComponentOrigin {
1657 fn default() -> Self {
1658 Self {
1659 component_id: AzString::from_const_str(""),
1660 data_model_json: crate::json::Json::null(),
1661 }
1662 }
1663}
1664
1665#[derive(Debug, Clone, PartialOrd)]
1670pub enum SvgNodeData {
1671 ImageClipMask(ImageMask),
1673 Path(crate::svg::SvgMultiPolygon),
1675 Circle { cx: f32, cy: f32, r: f32 },
1677 Rect { x: f32, y: f32, width: f32, height: f32, rx: f32, ry: f32 },
1679 Ellipse { cx: f32, cy: f32, rx: f32, ry: f32 },
1681 Line { x1: f32, y1: f32, x2: f32, y2: f32 },
1683 PointsList { points: alloc::vec::Vec<azul_css::props::basic::SvgPoint>, closed: bool },
1685 ViewBox { min_x: f32, min_y: f32, width: f32, height: f32 },
1687 LinearGradient { x1: f32, y1: f32, x2: f32, y2: f32 },
1689 RadialGradient { cx: f32, cy: f32, r: f32, fx: f32, fy: f32 },
1691 GradientStop { offset: f32 },
1693 Use { href: AzString, x: f32, y: f32 },
1695 SvgImageData { href: AzString, x: f32, y: f32, width: f32, height: f32 },
1697}
1698
1699impl PartialEq for SvgNodeData {
1704 #[allow(clippy::match_same_arms, clippy::similar_names)] fn eq(&self, other: &Self) -> bool {
1706 const fn fb(a: f32, b: f32) -> bool {
1708 a.to_bits() == b.to_bits()
1709 }
1710 use self::SvgNodeData::{
1711 Circle, Ellipse, GradientStop, ImageClipMask, Line, LinearGradient, Path,
1712 PointsList, RadialGradient, Rect, SvgImageData, Use, ViewBox,
1713 };
1714 match (self, other) {
1715 (ImageClipMask(a), ImageClipMask(b)) => a == b,
1716 (Path(a), Path(b)) => {
1717 let ra = a.rings.as_ref();
1718 let rb = b.rings.as_ref();
1719 ra.len() == rb.len()
1720 && ra.iter().zip(rb.iter()).all(|(x, y)| {
1721 let ia = x.items.as_ref();
1722 let ib = y.items.as_ref();
1723 ia.len() == ib.len()
1724 && ia.iter().zip(ib.iter()).all(|(p, q)| svg_path_element_bits_eq(p, q))
1725 })
1726 }
1727 (Circle { cx, cy, r }, Circle { cx: cx2, cy: cy2, r: r2 }) => {
1728 fb(*cx, *cx2) && fb(*cy, *cy2) && fb(*r, *r2)
1729 }
1730 (
1731 Rect { x, y, width, height, rx, ry },
1732 Rect { x: x2, y: y2, width: w2, height: h2, rx: rx2, ry: ry2 },
1733 ) => {
1734 fb(*x, *x2) && fb(*y, *y2) && fb(*width, *w2)
1735 && fb(*height, *h2) && fb(*rx, *rx2) && fb(*ry, *ry2)
1736 }
1737 (Ellipse { cx, cy, rx, ry }, Ellipse { cx: cx2, cy: cy2, rx: rx2, ry: ry2 }) => {
1738 fb(*cx, *cx2) && fb(*cy, *cy2) && fb(*rx, *rx2) && fb(*ry, *ry2)
1739 }
1740 (Line { x1, y1, x2, y2 }, Line { x1: a1, y1: b1, x2: a2, y2: b2 })
1741 | (LinearGradient { x1, y1, x2, y2 }, LinearGradient { x1: a1, y1: b1, x2: a2, y2: b2 }) => {
1742 fb(*x1, *a1) && fb(*y1, *b1) && fb(*x2, *a2) && fb(*y2, *b2)
1743 }
1744 (PointsList { points: pa, closed: ca }, PointsList { points: pb, closed: cb }) => {
1745 ca == cb
1746 && pa.len() == pb.len()
1747 && pa.iter().zip(pb.iter()).all(|(p, q)| fb(p.x, q.x) && fb(p.y, q.y))
1748 }
1749 (
1750 ViewBox { min_x, min_y, width, height },
1751 ViewBox { min_x: a, min_y: b, width: w, height: h },
1752 ) => fb(*min_x, *a) && fb(*min_y, *b) && fb(*width, *w) && fb(*height, *h),
1753 (
1754 RadialGradient { cx, cy, r, fx, fy },
1755 RadialGradient { cx: cx2, cy: cy2, r: r2, fx: fx2, fy: fy2 },
1756 ) => {
1757 fb(*cx, *cx2) && fb(*cy, *cy2) && fb(*r, *r2) && fb(*fx, *fx2) && fb(*fy, *fy2)
1758 }
1759 (GradientStop { offset: a }, GradientStop { offset: b }) => fb(*a, *b),
1760 (Use { href, x, y }, Use { href: h2, x: x2, y: y2 }) => {
1761 href == h2 && fb(*x, *x2) && fb(*y, *y2)
1762 }
1763 (
1764 SvgImageData { href, x, y, width, height },
1765 SvgImageData { href: h2, x: x2, y: y2, width: w2, height: hh2 },
1766 ) => {
1767 href == h2 && fb(*x, *x2) && fb(*y, *y2) && fb(*width, *w2) && fb(*height, *hh2)
1768 }
1769 _ => false,
1771 }
1772 }
1773}
1774
1775const fn svg_path_element_bits_eq(
1778 a: &crate::svg::SvgPathElement,
1779 b: &crate::svg::SvgPathElement,
1780) -> bool {
1781 use crate::svg::SvgPathElement::{CubicCurve, Line, QuadraticCurve};
1782 const fn pb(a: azul_css::props::basic::SvgPoint, b: azul_css::props::basic::SvgPoint) -> bool {
1783 a.x.to_bits() == b.x.to_bits() && a.y.to_bits() == b.y.to_bits()
1784 }
1785 match (a, b) {
1786 (Line(a), Line(b)) => pb(a.start, b.start) && pb(a.end, b.end),
1787 (QuadraticCurve(a), QuadraticCurve(b)) => {
1788 pb(a.start, b.start) && pb(a.ctrl, b.ctrl) && pb(a.end, b.end)
1789 }
1790 (CubicCurve(a), CubicCurve(b)) => {
1791 pb(a.start, b.start) && pb(a.ctrl_1, b.ctrl_1)
1792 && pb(a.ctrl_2, b.ctrl_2) && pb(a.end, b.end)
1793 }
1794 _ => false,
1795 }
1796}
1797
1798impl Eq for SvgNodeData {}
1799
1800#[allow(clippy::derive_ord_xor_partial_ord)]
1804impl Ord for SvgNodeData {
1805 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
1806 self.partial_cmp(other).unwrap_or(core::cmp::Ordering::Equal)
1807 }
1808}
1809
1810impl Hash for SvgNodeData {
1811 fn hash<H: Hasher>(&self, state: &mut H) {
1812 mem::discriminant(self).hash(state);
1813 match self {
1814 Self::ImageClipMask(m) => m.hash(state),
1815 Self::Path(mp) => {
1816 for ring in mp.rings.as_ref() {
1817 for item in ring.items.as_ref() {
1818 match item {
1819 crate::svg::SvgPathElement::Line(l) => {
1820 0u8.hash(state);
1821 l.start.x.to_bits().hash(state);
1822 l.start.y.to_bits().hash(state);
1823 l.end.x.to_bits().hash(state);
1824 l.end.y.to_bits().hash(state);
1825 }
1826 crate::svg::SvgPathElement::QuadraticCurve(q) => {
1827 1u8.hash(state);
1828 q.start.x.to_bits().hash(state);
1829 q.start.y.to_bits().hash(state);
1830 q.ctrl.x.to_bits().hash(state);
1831 q.ctrl.y.to_bits().hash(state);
1832 q.end.x.to_bits().hash(state);
1833 q.end.y.to_bits().hash(state);
1834 }
1835 crate::svg::SvgPathElement::CubicCurve(c) => {
1836 2u8.hash(state);
1837 c.start.x.to_bits().hash(state);
1838 c.start.y.to_bits().hash(state);
1839 c.ctrl_1.x.to_bits().hash(state);
1840 c.ctrl_1.y.to_bits().hash(state);
1841 c.ctrl_2.x.to_bits().hash(state);
1842 c.ctrl_2.y.to_bits().hash(state);
1843 c.end.x.to_bits().hash(state);
1844 c.end.y.to_bits().hash(state);
1845 }
1846 }
1847 }
1848 }
1849 }
1850 Self::Circle { cx, cy, r } => {
1851 cx.to_bits().hash(state); cy.to_bits().hash(state); r.to_bits().hash(state);
1852 }
1853 Self::Rect { x, y, width, height, rx, ry } => {
1854 x.to_bits().hash(state); y.to_bits().hash(state);
1855 width.to_bits().hash(state); height.to_bits().hash(state);
1856 rx.to_bits().hash(state); ry.to_bits().hash(state);
1857 }
1858 Self::Ellipse { cx, cy, rx, ry } => {
1859 cx.to_bits().hash(state); cy.to_bits().hash(state);
1860 rx.to_bits().hash(state); ry.to_bits().hash(state);
1861 }
1862 Self::Line { x1, y1, x2, y2 } | Self::LinearGradient { x1, y1, x2, y2 } => {
1865 x1.to_bits().hash(state); y1.to_bits().hash(state);
1866 x2.to_bits().hash(state); y2.to_bits().hash(state);
1867 }
1868 Self::PointsList { points, closed } => {
1869 for p in points {
1870 p.x.to_bits().hash(state); p.y.to_bits().hash(state);
1871 }
1872 closed.hash(state);
1873 }
1874 Self::ViewBox { min_x, min_y, width, height } => {
1875 min_x.to_bits().hash(state); min_y.to_bits().hash(state);
1876 width.to_bits().hash(state); height.to_bits().hash(state);
1877 }
1878 Self::RadialGradient { cx, cy, r, fx, fy } => {
1879 cx.to_bits().hash(state); cy.to_bits().hash(state);
1880 r.to_bits().hash(state); fx.to_bits().hash(state);
1881 fy.to_bits().hash(state);
1882 }
1883 Self::GradientStop { offset } => {
1884 offset.to_bits().hash(state);
1885 }
1886 Self::Use { href, x, y } => {
1887 href.hash(state);
1888 x.to_bits().hash(state); y.to_bits().hash(state);
1889 }
1890 Self::SvgImageData { href, x, y, width, height } => {
1891 href.hash(state);
1892 x.to_bits().hash(state); y.to_bits().hash(state);
1893 width.to_bits().hash(state); height.to_bits().hash(state);
1894 }
1895 }
1896 }
1897}
1898
1899#[repr(C)]
1903#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1904pub struct NodeDataExt {
1905 pub attributes: AttributeTypeVec,
1909 pub virtual_view: Option<VirtualViewNode>,
1911 pub dataset: Option<RefAny>,
1913 pub svg_data: Option<SvgNodeData>,
1915 pub menu_bar: Option<Box<Menu>>,
1917 pub context_menu: Option<Box<Menu>>,
1919 pub key: Option<u64>,
1923 pub dataset_merge_callback: Option<DatasetMergeCallback>,
1926 pub component_origin: Option<ComponentOrigin>,
1932}
1933
1934#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1958#[repr(C)]
1959pub struct DatasetMergeCallback {
1960 pub cb: DatasetMergeCallbackType,
1963 pub callable: OptionRefAny,
1966}
1967
1968impl fmt::Debug for DatasetMergeCallback {
1969 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1970 f.debug_struct("DatasetMergeCallback")
1971 .field("cb", &(self.cb as usize))
1972 .field("callable", &self.callable)
1973 .finish()
1974 }
1975}
1976
1977impl From<DatasetMergeCallbackType> for DatasetMergeCallback {
1980 fn from(cb: DatasetMergeCallbackType) -> Self {
1981 Self {
1982 cb,
1983 callable: OptionRefAny::None,
1984 }
1985 }
1986}
1987
1988impl DatasetMergeCallback {
1989 #[must_use]
1993 pub fn from_ptr(cb: DatasetMergeCallbackType) -> Self {
1994 Self::from(cb)
1995 }
1996}
1997
1998impl_option!(
1999 DatasetMergeCallback,
2000 OptionDatasetMergeCallback,
2001 copy = false,
2002 [Debug, Clone]
2003);
2004
2005pub type DatasetMergeCallbackType = extern "C" fn(RefAny, RefAny) -> RefAny;
2014
2015impl Clone for NodeData {
2016 #[inline]
2017 fn clone(&self) -> Self {
2018 Self {
2019 node_type: self.node_type.to_library_owned_nodetype(),
2020 style: self.style.clone(),
2021 callbacks: self.callbacks.clone(),
2022 flags: self.flags,
2023 accessibility: self.accessibility.clone(),
2024 extra: self.extra.clone(),
2025 }
2026 }
2027}
2028
2029impl_vec!(NodeData, NodeDataVec, NodeDataVecDestructor, NodeDataVecDestructorType, NodeDataVecSlice, OptionNodeData);
2031impl_vec_clone!(NodeData, NodeDataVec, NodeDataVecDestructor);
2032impl_vec_mut!(NodeData, NodeDataVec);
2033impl_vec_debug!(NodeData, NodeDataVec);
2034impl_vec_partialord!(NodeData, NodeDataVec);
2035impl_vec_ord!(NodeData, NodeDataVec);
2036impl_vec_partialeq!(NodeData, NodeDataVec);
2037impl_vec_eq!(NodeData, NodeDataVec);
2038impl_vec_hash!(NodeData, NodeDataVec);
2039
2040impl NodeDataVec {
2041 #[inline]
2042 #[must_use] pub fn as_container(&self) -> NodeDataContainerRef<'_, NodeData> {
2043 NodeDataContainerRef {
2044 internal: self.as_ref(),
2045 }
2046 }
2047 #[inline]
2048 pub fn as_container_mut(&mut self) -> NodeDataContainerRefMut<'_, NodeData> {
2049 NodeDataContainerRefMut {
2050 internal: self.as_mut(),
2051 }
2052 }
2053}
2054
2055unsafe impl Send for NodeData {}
2059
2060#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2063#[repr(C, u8)]
2064#[derive(Default)]
2065pub enum TabIndex {
2066 #[default]
2072 Auto,
2073 OverrideInParent(u32),
2079 NoKeyboardFocus,
2082}
2083
2084impl_option!(
2085 TabIndex,
2086 OptionTabIndex,
2087 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
2088);
2089
2090impl TabIndex {
2091 #[allow(clippy::cast_possible_wrap)]
2095 #[must_use] pub const fn get_index(&self) -> isize {
2096 use self::TabIndex::{Auto, OverrideInParent, NoKeyboardFocus};
2097 match self {
2098 Auto => 0,
2099 OverrideInParent(x) => *x as isize,
2100 NoKeyboardFocus => -1,
2101 }
2102 }
2103}
2104
2105
2106#[repr(C)]
2118#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2119#[derive(Default)]
2120pub struct NodeFlags {
2121 pub inner: u32,
2122}
2123
2124
2125impl NodeFlags {
2126 const CONTENTEDITABLE_BIT: u32 = 1 << 31;
2127 const TAB_INDEX_MASK: u32 = 0b11 << 29;
2128 const ANONYMOUS_BIT: u32 = 1 << 28;
2129 const TAB_VALUE_MASK: u32 = (1 << 28) - 1;
2130
2131 const TAB_NONE: u32 = 0b00 << 29;
2132 const TAB_AUTO: u32 = 0b01 << 29;
2133 const TAB_OVERRIDE: u32 = 0b10 << 29;
2134 const TAB_NO_KEYBOARD: u32 = 0b11 << 29;
2135
2136 #[must_use] pub const fn new() -> Self {
2137 Self { inner: 0 }
2138 }
2139
2140 #[must_use] pub const fn is_contenteditable(&self) -> bool {
2141 (self.inner & Self::CONTENTEDITABLE_BIT) != 0
2142 }
2143
2144 #[must_use] pub const fn set_contenteditable(mut self, v: bool) -> Self {
2145 if v {
2146 self.inner |= Self::CONTENTEDITABLE_BIT;
2147 } else {
2148 self.inner &= !Self::CONTENTEDITABLE_BIT;
2149 }
2150 self
2151 }
2152
2153 pub const fn set_contenteditable_mut(&mut self, v: bool) {
2154 if v {
2155 self.inner |= Self::CONTENTEDITABLE_BIT;
2156 } else {
2157 self.inner &= !Self::CONTENTEDITABLE_BIT;
2158 }
2159 }
2160
2161 #[must_use] pub const fn get_tab_index(&self) -> Option<TabIndex> {
2162 match self.inner & Self::TAB_INDEX_MASK {
2163 x if x == Self::TAB_NONE => None,
2164 x if x == Self::TAB_AUTO => Some(TabIndex::Auto),
2165 x if x == Self::TAB_OVERRIDE => {
2166 let val = self.inner & Self::TAB_VALUE_MASK;
2167 Some(TabIndex::OverrideInParent(val))
2168 }
2169 x if x == Self::TAB_NO_KEYBOARD => Some(TabIndex::NoKeyboardFocus),
2170 _ => None,
2171 }
2172 }
2173
2174 #[must_use] pub const fn is_anonymous(&self) -> bool {
2176 (self.inner & Self::ANONYMOUS_BIT) != 0
2177 }
2178
2179 pub const fn set_anonymous(&mut self, v: bool) {
2180 if v {
2181 self.inner |= Self::ANONYMOUS_BIT;
2182 } else {
2183 self.inner &= !Self::ANONYMOUS_BIT;
2184 }
2185 }
2186
2187 pub const fn set_tab_index(&mut self, tab_index: Option<TabIndex>) {
2188 self.inner &= Self::CONTENTEDITABLE_BIT | Self::ANONYMOUS_BIT;
2191 match tab_index {
2192 None => { }
2193 Some(TabIndex::Auto) => {
2194 self.inner |= Self::TAB_AUTO;
2195 }
2196 Some(TabIndex::OverrideInParent(val)) => {
2197 self.inner |= Self::TAB_OVERRIDE | (val & Self::TAB_VALUE_MASK);
2198 }
2199 Some(TabIndex::NoKeyboardFocus) => {
2200 self.inner |= Self::TAB_NO_KEYBOARD;
2201 }
2202 }
2203 }
2204}
2205
2206impl Default for NodeData {
2207 fn default() -> Self {
2208 Self::create_node(NodeType::Div)
2209 }
2210}
2211
2212impl fmt::Display for NodeData {
2213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2214 let html_type = self.node_type.get_path();
2215 let attributes_string = node_data_to_string(self);
2216
2217 match self.node_type.format() {
2218 Some(content) => write!(
2219 f,
2220 "<{html_type}{attributes_string}>{content}</{html_type}>"
2221 ),
2222 None => write!(f, "<{html_type}{attributes_string}/>"),
2223 }
2224 }
2225}
2226
2227fn node_data_to_string(node_data: &NodeData) -> String {
2228 let mut id_string = String::new();
2229 let ids = node_data
2230 .attributes()
2231 .as_ref()
2232 .iter()
2233 .filter_map(|s| s.as_id())
2234 .collect::<Vec<_>>()
2235 .join(" ");
2236
2237 if !ids.is_empty() {
2238 id_string = format!(" id=\"{ids}\" ");
2239 }
2240
2241 let mut class_string = String::new();
2242 let classes = node_data
2243 .attributes()
2244 .as_ref()
2245 .iter()
2246 .filter_map(|s| s.as_class())
2247 .collect::<Vec<_>>()
2248 .join(" ");
2249
2250 if !classes.is_empty() {
2251 class_string = format!(" class=\"{classes}\" ");
2252 }
2253
2254 let mut tabindex_string = String::new();
2255 if let Some(tab_index) = node_data.get_tab_index() {
2256 tabindex_string = format!(" tabindex=\"{}\" ", tab_index.get_index());
2257 }
2258
2259 format!("{id_string}{class_string}{tabindex_string}")
2260}
2261
2262impl NodeData {
2263 #[inline]
2265 #[must_use] pub const fn create_node(node_type: NodeType) -> Self {
2266 Self {
2267 node_type,
2268 callbacks: CoreCallbackDataVec::from_const_slice(&[]),
2269 style: azul_css::css::Css {
2270 rules: azul_css::css::CssRuleBlockVec::from_const_slice(&[]),
2271 },
2272 flags: NodeFlags::new(),
2273 accessibility: None,
2274 extra: None,
2275 }
2276 }
2277
2278 #[inline]
2281 #[must_use] pub fn attributes(&self) -> &AttributeTypeVec {
2282 static EMPTY: AttributeTypeVec = AttributeTypeVec::from_const_slice(&[]);
2283 self.extra.as_ref().map_or(&EMPTY, |ext| &ext.attributes)
2284 }
2285
2286 #[inline]
2289 pub fn attributes_mut(&mut self) -> &mut AttributeTypeVec {
2290 &mut self.extra.get_or_insert_with(|| Box::new(NodeDataExt::default())).attributes
2291 }
2292
2293 #[inline]
2295 pub fn set_attributes(&mut self, attrs: AttributeTypeVec) {
2296 self.extra.get_or_insert_with(|| Box::new(NodeDataExt::default())).attributes = attrs;
2297 }
2298
2299 #[inline]
2301 #[must_use] pub const fn create_body() -> Self {
2302 Self::create_node(NodeType::Body)
2303 }
2304
2305 #[inline]
2307 #[must_use] pub const fn create_div() -> Self {
2308 Self::create_node(NodeType::Div)
2309 }
2310
2311 #[inline]
2313 #[must_use] pub const fn create_br() -> Self {
2314 Self::create_node(NodeType::Br)
2315 }
2316
2317 #[inline]
2319 pub fn create_text<S: Into<AzString>>(value: S) -> Self {
2320 Self::create_node(NodeType::Text(BoxOrStatic::heap(value.into())))
2321 }
2322
2323 #[inline]
2325 #[must_use] pub fn create_image(image: ImageRef) -> Self {
2326 Self::create_node(NodeType::Image(BoxOrStatic::heap(image)))
2327 }
2328
2329 #[inline]
2330 pub fn create_virtual_view(data: RefAny, callback: impl Into<VirtualViewCallback>) -> Self {
2331 let mut nd = Self::create_node(NodeType::VirtualView);
2332 let ext = nd.extra.get_or_insert_with(|| Box::new(NodeDataExt::default()));
2333 ext.virtual_view = Some(VirtualViewNode {
2334 callback: callback.into(),
2335 refany: data,
2336 });
2337 nd
2338 }
2339
2340 fn with_attribute(mut self, attr: AttributeType) -> Self {
2346 let mut v = self.attributes().clone().into_library_owned_vec();
2347 v.push(attr);
2348 self.set_attributes(v.into());
2349 self
2350 }
2351
2352 #[inline]
2354 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_button(aria: SmallAriaInfo) -> Self {
2356 let mut nd = Self::create_node(NodeType::Button);
2357 nd.set_accessibility_info(aria.to_full_info());
2358 nd
2359 }
2360
2361 #[inline]
2363 #[must_use] pub const fn create_button_no_a11y() -> Self {
2364 Self::create_node(NodeType::Button)
2365 }
2366
2367 #[inline]
2369 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_a(href: AzString, aria: SmallAriaInfo) -> Self {
2371 let mut nd = Self::create_node(NodeType::A).with_attribute(AttributeType::Href(href));
2372 nd.set_accessibility_info(aria.to_full_info());
2373 nd
2374 }
2375
2376 #[inline]
2378 #[must_use] pub fn create_a_no_a11y(href: AzString) -> Self {
2379 Self::create_node(NodeType::A).with_attribute(AttributeType::Href(href))
2380 }
2381
2382 #[inline]
2384 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_input(
2386 input_type: AzString,
2387 name: AzString,
2388 label: AzString,
2389 aria: SmallAriaInfo,
2390 ) -> Self {
2391 let mut nd = Self::create_node(NodeType::Input)
2392 .with_attribute(AttributeType::InputType(input_type))
2393 .with_attribute(AttributeType::Name(name))
2394 .with_attribute(AttributeType::AriaLabel(label));
2395 nd.set_accessibility_info(aria.to_full_info());
2396 nd
2397 }
2398
2399 #[inline]
2401 #[must_use] pub fn create_input_no_a11y(input_type: AzString, name: AzString, label: AzString) -> Self {
2402 Self::create_node(NodeType::Input)
2403 .with_attribute(AttributeType::InputType(input_type))
2404 .with_attribute(AttributeType::Name(name))
2405 .with_attribute(AttributeType::AriaLabel(label))
2406 }
2407
2408 #[inline]
2410 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_textarea(name: AzString, label: AzString, aria: SmallAriaInfo) -> Self {
2412 let mut nd = Self::create_node(NodeType::TextArea)
2413 .with_attribute(AttributeType::Name(name))
2414 .with_attribute(AttributeType::AriaLabel(label));
2415 nd.set_accessibility_info(aria.to_full_info());
2416 nd
2417 }
2418
2419 #[inline]
2421 #[must_use] pub fn create_textarea_no_a11y(name: AzString, label: AzString) -> Self {
2422 Self::create_node(NodeType::TextArea)
2423 .with_attribute(AttributeType::Name(name))
2424 .with_attribute(AttributeType::AriaLabel(label))
2425 }
2426
2427 #[inline]
2429 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_select(name: AzString, label: AzString, aria: SmallAriaInfo) -> Self {
2431 let mut nd = Self::create_node(NodeType::Select)
2432 .with_attribute(AttributeType::Name(name))
2433 .with_attribute(AttributeType::AriaLabel(label));
2434 nd.set_accessibility_info(aria.to_full_info());
2435 nd
2436 }
2437
2438 #[inline]
2440 #[must_use] pub fn create_select_no_a11y(name: AzString, label: AzString) -> Self {
2441 Self::create_node(NodeType::Select)
2442 .with_attribute(AttributeType::Name(name))
2443 .with_attribute(AttributeType::AriaLabel(label))
2444 }
2445
2446 #[inline]
2448 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_table(aria: SmallAriaInfo) -> Self {
2450 let mut nd = Self::create_node(NodeType::Table);
2451 nd.set_accessibility_info(aria.to_full_info());
2452 nd
2453 }
2454
2455 #[inline]
2457 #[must_use] pub const fn create_table_no_a11y() -> Self {
2458 Self::create_node(NodeType::Table)
2459 }
2460
2461 #[inline]
2464 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_label(for_id: AzString, aria: SmallAriaInfo) -> Self {
2466 let mut nd = Self::create_node(NodeType::Label).with_attribute(AttributeType::Custom(
2467 AttributeNameValue {
2468 attr_name: "for".into(),
2469 value: for_id,
2470 },
2471 ));
2472 nd.set_accessibility_info(aria.to_full_info());
2473 nd
2474 }
2475
2476 #[inline]
2479 #[must_use] pub fn create_label_no_a11y(for_id: AzString) -> Self {
2480 Self::create_node(NodeType::Label).with_attribute(AttributeType::Custom(AttributeNameValue {
2481 attr_name: "for".into(),
2482 value: for_id,
2483 }))
2484 }
2485
2486 #[inline]
2488 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn is_node_type(&self, searched_type: NodeType) -> bool {
2490 self.node_type == searched_type
2491 }
2492
2493 #[must_use] pub fn has_id(&self, id: &str) -> bool {
2495 self.attributes()
2496 .iter()
2497 .any(|attr| attr.as_id() == Some(id))
2498 }
2499
2500 #[must_use] pub fn has_class(&self, class: &str) -> bool {
2502 self.attributes()
2503 .iter()
2504 .any(|attr| attr.as_class() == Some(class))
2505 }
2506
2507 #[must_use] pub fn has_context_menu(&self) -> bool {
2508 self.extra
2509 .as_ref()
2510 .is_some_and(|m| m.context_menu.is_some())
2511 }
2512
2513 #[must_use] pub const fn is_text_node(&self) -> bool {
2514 matches!(self.node_type, NodeType::Text(_))
2515 }
2516
2517 #[must_use] pub const fn is_virtual_view_node(&self) -> bool {
2518 matches!(self.node_type, NodeType::VirtualView)
2519 }
2520
2521 #[inline]
2525 #[must_use] pub const fn get_node_type(&self) -> &NodeType {
2526 &self.node_type
2527 }
2528 #[inline]
2529 pub fn get_dataset_mut(&mut self) -> Option<&mut RefAny> {
2530 self.extra.as_mut().and_then(|e| e.dataset.as_mut())
2531 }
2532 #[inline]
2533 #[must_use] pub fn get_dataset(&self) -> Option<&RefAny> {
2534 self.extra.as_ref().and_then(|e| e.dataset.as_ref())
2535 }
2536 pub fn take_dataset(&mut self) -> Option<RefAny> {
2538 self.extra.as_mut().and_then(|e| e.dataset.take())
2539 }
2540 #[inline]
2543 #[must_use] pub fn get_ids_and_classes(&self) -> IdOrClassVec {
2544 let v: Vec<IdOrClass> = self.attributes().as_ref().iter().filter_map(|attr| {
2545 match attr {
2546 AttributeType::Id(s) => Some(IdOrClass::Id(s.clone())),
2547 AttributeType::Class(s) => Some(IdOrClass::Class(s.clone())),
2548 _ => None,
2549 }
2550 }).collect();
2551 v.into()
2552 }
2553 #[inline]
2554 #[must_use] pub const fn get_callbacks(&self) -> &CoreCallbackDataVec {
2555 &self.callbacks
2556 }
2557 #[inline]
2558 #[must_use] pub const fn get_style(&self) -> &azul_css::css::Css {
2559 &self.style
2560 }
2561
2562 #[inline]
2563 #[must_use] pub fn get_svg_data(&self) -> Option<&SvgNodeData> {
2564 self.extra.as_ref().and_then(|e| e.svg_data.as_ref())
2565 }
2566
2567 #[inline]
2569 #[must_use] pub fn get_image_clip_mask(&self) -> Option<&ImageMask> {
2570 match self.get_svg_data()? {
2571 SvgNodeData::ImageClipMask(m) => Some(m),
2572 _ => None,
2573 }
2574 }
2575 #[inline]
2576 #[must_use] pub const fn get_tab_index(&self) -> Option<TabIndex> {
2577 self.flags.get_tab_index()
2578 }
2579 #[inline]
2580 #[must_use] pub fn get_accessibility_info(&self) -> Option<&AccessibilityInfo> {
2581 self.accessibility.as_deref()
2582 }
2583 #[inline]
2584 #[must_use] pub fn get_menu_bar(&self) -> Option<&Menu> {
2585 self.extra.as_ref().and_then(|e| e.menu_bar.as_deref())
2586 }
2587 #[inline]
2588 #[must_use] pub fn get_context_menu(&self) -> Option<&Menu> {
2589 self.extra.as_ref().and_then(|e| e.context_menu.as_deref())
2590 }
2591
2592 #[inline]
2594 #[must_use] pub const fn is_anonymous(&self) -> bool {
2595 self.flags.is_anonymous()
2596 }
2597
2598 #[inline]
2599 pub fn set_node_type(&mut self, node_type: NodeType) {
2600 self.node_type = node_type;
2601 }
2602 #[inline]
2603 pub fn set_dataset(&mut self, data: OptionRefAny) {
2604 match data {
2605 OptionRefAny::None => {
2606 if let Some(ext) = self.extra.as_mut() {
2607 ext.dataset = None;
2608 }
2609 }
2610 OptionRefAny::Some(r) => {
2611 self.extra
2612 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2613 .dataset = Some(r);
2614 }
2615 }
2616 }
2617 #[inline]
2621 #[allow(clippy::needless_pass_by_value)] pub fn set_ids_and_classes(&mut self, ids_and_classes: IdOrClassVec) {
2623 let mut v: AttributeTypeVec = Vec::new().into();
2625 mem::swap(&mut v, self.attributes_mut());
2626 let mut v = v.into_library_owned_vec();
2627 v.retain(|a| !matches!(a, AttributeType::Id(_) | AttributeType::Class(_)));
2628 for ioc in ids_and_classes.as_ref() {
2630 match ioc {
2631 IdOrClass::Id(s) => v.push(AttributeType::Id(s.clone())),
2632 IdOrClass::Class(s) => v.push(AttributeType::Class(s.clone())),
2633 }
2634 }
2635 self.set_attributes(v.into());
2636 }
2637 #[inline]
2638 pub fn set_callbacks(&mut self, callbacks: CoreCallbackDataVec) {
2639 self.callbacks = callbacks;
2640 }
2641 #[inline]
2645 pub fn set_css_props(&mut self, css_props: CssPropertyWithConditionsVec) {
2646 self.style = css_props.into();
2647 }
2648 #[inline]
2651 pub fn set_style(&mut self, style: azul_css::css::Css) {
2652 self.style = style;
2653 }
2654 #[inline]
2655 pub fn set_clip_mask(&mut self, clip_mask: ImageMask) {
2656 self.extra
2657 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2658 .svg_data = Some(SvgNodeData::ImageClipMask(clip_mask));
2659 }
2660 #[inline]
2661 pub fn set_svg_data(&mut self, data: SvgNodeData) {
2662 self.extra
2663 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2664 .svg_data = Some(data);
2665 }
2666 #[inline]
2667 pub const fn set_tab_index(&mut self, tab_index: TabIndex) {
2668 self.flags.set_tab_index(Some(tab_index));
2669 }
2670 #[inline]
2671 pub const fn set_contenteditable(&mut self, contenteditable: bool) {
2672 self.flags.set_contenteditable_mut(contenteditable);
2673 }
2674 #[inline]
2675 #[must_use] pub const fn is_contenteditable(&self) -> bool {
2676 self.flags.is_contenteditable()
2677 }
2678 #[inline]
2679 pub fn set_accessibility_info(&mut self, accessibility_info: AccessibilityInfo) {
2680 self.accessibility = Some(Box::new(accessibility_info));
2681 }
2682
2683 #[inline]
2685 pub const fn set_anonymous(&mut self, is_anonymous: bool) {
2686 self.flags.set_anonymous(is_anonymous);
2687 }
2688 #[inline]
2689 pub fn set_menu_bar(&mut self, menu_bar: Menu) {
2690 self.extra
2691 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2692 .menu_bar = Some(Box::new(menu_bar));
2693 }
2694 #[inline]
2695 pub fn set_context_menu(&mut self, context_menu: Menu) {
2696 self.extra
2697 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2698 .context_menu = Some(Box::new(context_menu));
2699 }
2700
2701 #[inline]
2714 pub fn set_key<K: Hash>(&mut self, key: K) {
2715 use core::hash::Hasher;
2716 let mut hasher = crate::hash::DefaultHasher::new();
2717 key.hash(&mut hasher);
2718 self.extra
2719 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2720 .key = Some(hasher.finish());
2721 }
2722
2723 #[inline]
2725 #[must_use] pub fn get_key(&self) -> Option<u64> {
2726 self.extra.as_ref().and_then(|ext| ext.key)
2727 }
2728
2729 #[inline]
2762 pub fn set_merge_callback<C: Into<DatasetMergeCallback>>(&mut self, callback: C) {
2763 self.extra
2764 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2765 .dataset_merge_callback = Some(callback.into());
2766 }
2767
2768 #[inline]
2770 #[must_use] pub fn get_merge_callback(&self) -> Option<DatasetMergeCallback> {
2771 self.extra.as_ref().and_then(|ext| ext.dataset_merge_callback.clone())
2772 }
2773
2774 #[inline]
2779 pub fn set_component_origin(&mut self, origin: ComponentOrigin) {
2780 self.extra
2781 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2782 .component_origin = Some(origin);
2783 }
2784
2785 #[inline]
2787 #[must_use] pub fn get_component_origin(&self) -> Option<&ComponentOrigin> {
2788 self.extra.as_ref().and_then(|ext| ext.component_origin.as_ref())
2789 }
2790
2791 #[inline]
2792 #[must_use] pub fn with_menu_bar(mut self, menu_bar: Menu) -> Self {
2793 self.set_menu_bar(menu_bar);
2794 self
2795 }
2796
2797 #[inline]
2798 #[must_use] pub fn with_context_menu(mut self, context_menu: Menu) -> Self {
2799 self.set_context_menu(context_menu);
2800 self
2801 }
2802
2803 #[inline]
2804 pub fn add_callback<C: Into<CoreCallback>>(
2805 &mut self,
2806 event: EventFilter,
2807 data: RefAny,
2808 callback: C,
2809 ) {
2810 let callback = callback.into();
2811 let mut v: CoreCallbackDataVec = Vec::new().into();
2812 mem::swap(&mut v, &mut self.callbacks);
2813 let mut v = v.into_library_owned_vec();
2814 v.push(CoreCallbackData {
2815 event,
2816 refany: data,
2817 callback,
2818 });
2819 self.callbacks = v.into();
2820 }
2821
2822 #[inline]
2823 pub fn add_id(&mut self, s: AzString) {
2824 let mut v: AttributeTypeVec = Vec::new().into();
2825 mem::swap(&mut v, self.attributes_mut());
2826 let mut v = v.into_library_owned_vec();
2827 v.push(AttributeType::Id(s));
2828 self.set_attributes(v.into());
2829 }
2830 #[inline]
2831 pub fn add_class(&mut self, s: AzString) {
2832 let mut v: AttributeTypeVec = Vec::new().into();
2833 mem::swap(&mut v, self.attributes_mut());
2834 let mut v = v.into_library_owned_vec();
2835 v.push(AttributeType::Class(s));
2836 self.set_attributes(v.into());
2837 }
2838
2839 #[inline]
2844 pub fn add_css_property(&mut self, p: CssPropertyWithConditions) {
2845 use azul_css::css::{rule_priority, CssDeclaration, CssPath, CssRuleBlock};
2846 let rule = CssRuleBlock {
2847 path: CssPath { selectors: Vec::new().into() },
2848 declarations: vec![CssDeclaration::Static(p.property)].into(),
2849 conditions: p.apply_if,
2850 priority: rule_priority::INLINE,
2851 };
2852 let mut v: azul_css::css::CssRuleBlockVec = Vec::new().into();
2853 mem::swap(&mut v, &mut self.style.rules);
2854 let mut v = v.into_library_owned_vec();
2855 v.push(rule);
2856 self.style.rules = v.into();
2857 }
2858
2859 #[must_use] pub fn calculate_node_data_hash(&self) -> DomNodeHash {
2861 use core::hash::Hasher;
2862 let mut hasher = crate::hash::DefaultHasher::new();
2863 self.hash(&mut hasher);
2864 let h = hasher.finish();
2865 DomNodeHash { inner: h }
2866 }
2867
2868 #[must_use] pub fn calculate_structural_hash(&self) -> DomNodeHash {
2880 use core::hash::Hasher;
2881 use core::hash::Hasher as StdHasher;
2882
2883 let mut hasher = crate::hash::DefaultHasher::new();
2884
2885 mem::discriminant(&self.node_type).hash(&mut hasher);
2888
2889 if self.node_type == NodeType::VirtualView {
2891 if let Some(ext) = self.extra.as_ref() {
2892 if let Some(vv) = ext.virtual_view.as_ref() {
2893 vv.hash(&mut hasher);
2894 }
2895 }
2896 }
2897
2898 if let NodeType::Image(ref img_ref) = self.node_type {
2904 match img_ref.get_data() {
2905 crate::resources::DecodedImage::Callback(cb) => {
2906 cb.callback.cb.hash(&mut hasher);
2908 cb.refany.get_type_id().hash(&mut hasher);
2910 }
2911 _ => {
2912 img_ref.hash(&mut hasher);
2914 }
2915 }
2916 }
2917
2918 for attr in self.attributes().as_ref() {
2921 match attr {
2922 AttributeType::Id(s) => { 0u8.hash(&mut hasher); s.as_str().hash(&mut hasher); }
2923 AttributeType::Class(s) => { 1u8.hash(&mut hasher); s.as_str().hash(&mut hasher); }
2924 _ => {}
2925 }
2926 }
2927
2928 for attr in self.attributes().as_ref() {
2931 if !matches!(attr, AttributeType::ContentEditable(_) | AttributeType::Id(_) | AttributeType::Class(_)) {
2932 attr.hash(&mut hasher);
2933 }
2934 }
2935
2936 for callback in self.callbacks.as_ref() {
2938 callback.event.hash(&mut hasher);
2939 }
2940
2941 let h = hasher.finish();
2942 DomNodeHash { inner: h }
2943 }
2944
2945 #[inline]
2946 #[must_use] pub const fn with_tab_index(mut self, tab_index: TabIndex) -> Self {
2947 self.set_tab_index(tab_index);
2948 self
2949 }
2950 #[inline]
2951 #[must_use] pub const fn with_contenteditable(mut self, contenteditable: bool) -> Self {
2952 self.set_contenteditable(contenteditable);
2953 self
2954 }
2955 #[inline]
2956 #[must_use] pub fn with_node_type(mut self, node_type: NodeType) -> Self {
2957 self.set_node_type(node_type);
2958 self
2959 }
2960 #[inline]
2961 #[must_use]
2962 pub fn with_callback<C: Into<CoreCallback>>(
2963 mut self,
2964 event: EventFilter,
2965 data: RefAny,
2966 callback: C,
2967 ) -> Self {
2968 self.add_callback(event, data, callback);
2969 self
2970 }
2971 #[inline]
2972 #[must_use] pub fn with_dataset(mut self, data: OptionRefAny) -> Self {
2973 self.set_dataset(data);
2974 self
2975 }
2976 #[inline]
2977 #[must_use] pub fn with_ids_and_classes(mut self, ids_and_classes: IdOrClassVec) -> Self {
2978 self.set_ids_and_classes(ids_and_classes);
2979 self
2980 }
2981 #[inline]
2982 #[must_use] pub fn with_callbacks(mut self, callbacks: CoreCallbackDataVec) -> Self {
2983 self.callbacks = callbacks;
2984 self
2985 }
2986 #[inline]
2990 #[must_use] pub fn with_css_props(mut self, css_props: CssPropertyWithConditionsVec) -> Self {
2991 self.style = css_props.into();
2992 self
2993 }
2994 #[inline]
2996 #[must_use] pub fn with_style(mut self, style: azul_css::css::Css) -> Self {
2997 self.style = style;
2998 self
2999 }
3000
3001 #[inline]
3014 #[must_use]
3015 pub fn with_key<K: Hash>(mut self, key: K) -> Self {
3016 self.set_key(key);
3017 self
3018 }
3019
3020 #[inline]
3051 #[must_use]
3052 pub fn with_merge_callback<C: Into<DatasetMergeCallback>>(mut self, callback: C) -> Self {
3053 self.set_merge_callback(callback);
3054 self
3055 }
3056
3057 pub fn set_css(&mut self, style: &str) {
3075 let parsed = azul_css::css::Css::parse_inline(style);
3079 let mut current: azul_css::css::CssRuleBlockVec = Vec::new().into();
3080 mem::swap(&mut current, &mut self.style.rules);
3081 let mut v = current.into_library_owned_vec();
3082 v.extend(parsed.rules.into_library_owned_vec());
3083 self.style.rules = v.into();
3084 }
3085
3086 #[must_use] pub fn with_css(mut self, style: &str) -> Self {
3088 self.set_css(style);
3089 self
3090 }
3091
3092 #[inline]
3093 #[must_use]
3094 pub const fn swap_with_default(&mut self) -> Self {
3095 let mut s = Self::create_div();
3096 mem::swap(&mut s, self);
3097 s
3098 }
3099
3100 #[inline]
3101 #[must_use] pub fn copy_special(&self) -> Self {
3102 Self {
3103 node_type: self.node_type.to_library_owned_nodetype(),
3104 style: self.style.clone(),
3105 callbacks: self.callbacks.clone(),
3106 flags: self.flags,
3107 accessibility: self.accessibility.clone(),
3108 extra: self.extra.clone(),
3109 }
3110 }
3111
3112 pub(crate) fn copy_special_moving_complex(&mut self) -> Self {
3127 let taken_style = mem::take(&mut self.style);
3138 let taken_extra = self.extra.take();
3139 let taken_node_type = mem::replace(&mut self.node_type, NodeType::Div);
3140 let mut copy = self.copy_special();
3141 unsafe { core::ptr::write(&raw mut copy.node_type, taken_node_type); }
3149 copy.style = taken_style;
3150 copy.extra = taken_extra;
3151 copy
3152 }
3153
3154 #[must_use] pub fn is_focusable(&self) -> bool {
3155 if matches!(self.node_type,
3157 NodeType::A | NodeType::Button | NodeType::Input
3158 | NodeType::Select | NodeType::TextArea
3159 ) {
3160 return true;
3161 }
3162 if self.is_contenteditable() {
3164 return true;
3165 }
3166 self.get_tab_index().is_some()
3168 || self
3169 .get_callbacks()
3170 .iter()
3171 .any(|cb| cb.event.is_focus_callback())
3172 }
3173
3174 #[must_use] pub fn has_activation_behavior(&self) -> bool {
3187 use crate::events::{EventFilter, HoverEventFilter};
3188
3189 if matches!(self.node_type, NodeType::A | NodeType::Button) {
3191 return true;
3192 }
3193
3194 let has_click_callback = self
3197 .get_callbacks()
3198 .iter()
3199 .any(|cb| matches!(
3200 cb.event,
3201 EventFilter::Hover(HoverEventFilter::MouseUp | HoverEventFilter::LeftMouseUp)
3202 ));
3203
3204 if has_click_callback {
3205 return true;
3206 }
3207
3208 if let Some(ref accessibility) = self.accessibility {
3210 use crate::a11y::AccessibilityRole;
3211 match accessibility.role {
3212 AccessibilityRole::PushButton | AccessibilityRole::Link
3214 | AccessibilityRole::CheckButton | AccessibilityRole::RadioButton | AccessibilityRole::MenuItem
3217 | AccessibilityRole::PageTab => return true,
3219 _ => {}
3220 }
3221 }
3222
3223 false
3224 }
3225
3226 #[must_use] pub fn is_activatable(&self) -> bool {
3231 if !self.has_activation_behavior() {
3232 return false;
3233 }
3234
3235 if let Some(ref accessibility) = self.accessibility {
3237 if accessibility
3239 .states
3240 .as_ref()
3241 .iter()
3242 .any(|s| matches!(s, AccessibilityState::Unavailable))
3243 {
3244 return false;
3245 }
3246 }
3247
3248 true
3250 }
3251
3252 #[must_use] pub fn get_effective_tabindex(&self) -> Option<i32> {
3260 self.flags.get_tab_index().map_or_else(|| if self.get_callbacks().iter().any(|cb| cb.event.is_focus_callback()) {
3261 Some(0)
3262 } else {
3263 None
3264 }, |tab_idx| match tab_idx {
3265 TabIndex::Auto => Some(0),
3266 TabIndex::OverrideInParent(n) => Some(i32::try_from(n).unwrap_or(i32::MAX)),
3267 TabIndex::NoKeyboardFocus => Some(-1),
3268 })
3269 }
3270
3271 #[must_use] pub fn get_accessible_label(&self) -> Option<&str> {
3277 for attr in self.attributes().as_ref() {
3278 if let AttributeType::AriaLabel(s) = attr { return Some(s.as_str()) }
3279 }
3280 for attr in self.attributes().as_ref() {
3281 match attr {
3282 AttributeType::Alt(s) | AttributeType::Title(s) => return Some(s.as_str()),
3283 _ => {}
3284 }
3285 }
3286 None
3287 }
3288
3289 #[must_use] pub fn get_accessible_value(&self) -> Option<&str> {
3294 for attr in self.attributes().as_ref() {
3295 if let AttributeType::Value(s) = attr {
3296 return Some(s.as_str());
3297 }
3298 }
3299 None
3300 }
3301
3302 #[must_use] pub fn get_placeholder(&self) -> Option<&str> {
3304 for attr in self.attributes().as_ref() {
3305 if let AttributeType::Placeholder(s) = attr {
3306 return Some(s.as_str());
3307 }
3308 }
3309 None
3310 }
3311
3312 pub fn get_virtual_view_node(&mut self) -> Option<&mut VirtualViewNode> {
3313 self.extra.as_mut()?.virtual_view.as_mut()
3314 }
3315
3316 #[must_use] pub fn get_virtual_view_node_ref(&self) -> Option<&VirtualViewNode> {
3317 self.extra.as_ref()?.virtual_view.as_ref()
3318 }
3319
3320 pub fn get_render_image_callback_node(
3321 &mut self,
3322 ) -> Option<(&mut CoreImageCallback, ImageRefHash)> {
3323 match &mut self.node_type {
3324 NodeType::Image(ref mut img) => {
3325 let hash = image_ref_get_hash(img.as_ref());
3326 img.as_mut().get_image_callback_mut().map(|r| (r, hash))
3327 }
3328 _ => None,
3329 }
3330 }
3331
3332 pub fn debug_print_start(
3333 &self,
3334 css_cache: &CssPropertyCache,
3335 node_id: &NodeId,
3336 node_state: &StyledNodeState,
3337 ) -> String {
3338 let html_type = self.node_type.get_path();
3339 let attributes_string = node_data_to_string(self);
3340 let style = css_cache.get_computed_css_style_string(self, node_id, node_state);
3341 format!(
3342 "<{} data-az-node-id=\"{}\" {} {style}>",
3343 html_type,
3344 node_id.index(),
3345 attributes_string,
3346 style = if style.trim().is_empty() {
3347 String::new()
3348 } else {
3349 format!("style=\"{style}\"")
3350 }
3351 )
3352 }
3353
3354 #[must_use] pub fn debug_print_end(&self) -> String {
3355 let html_type = self.node_type.get_path();
3356 format!("</{html_type}>")
3357 }
3358}
3359
3360impl crate::events::ActivationBehavior for NodeData {
3361 fn has_activation_behavior(&self) -> bool {
3362 Self::has_activation_behavior(self)
3363 }
3364
3365 fn is_activatable(&self) -> bool {
3366 Self::is_activatable(self)
3367 }
3368}
3369
3370impl crate::events::Focusable for NodeData {
3371 fn get_tabindex(&self) -> Option<i32> {
3372 self.get_effective_tabindex()
3373 }
3374
3375 fn is_focusable(&self) -> bool {
3376 Self::is_focusable(self)
3377 }
3378
3379 fn is_naturally_focusable(&self) -> bool {
3380 matches!(
3381 self.node_type,
3382 NodeType::A
3383 | NodeType::Button
3384 | NodeType::Input
3385 | NodeType::Select
3386 | NodeType::TextArea
3387 )
3388 }
3389}
3390
3391#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
3393#[repr(C)]
3394pub struct DomId {
3395 pub inner: usize,
3396}
3397
3398impl fmt::Display for DomId {
3399 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3400 write!(f, "{}", self.inner)
3401 }
3402}
3403
3404impl DomId {
3405 pub const ROOT_ID: Self = Self { inner: 0 };
3406}
3407
3408impl Default for DomId {
3409 fn default() -> Self {
3410 Self::ROOT_ID
3411 }
3412}
3413
3414impl_option!(
3415 DomId,
3416 OptionDomId,
3417 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3418);
3419
3420impl_vec!(DomId, DomIdVec, DomIdVecDestructor, DomIdVecDestructorType, DomIdVecSlice, OptionDomId);
3421impl_vec_debug!(DomId, DomIdVec);
3422impl_vec_clone!(DomId, DomIdVec, DomIdVecDestructor);
3423impl_vec_partialeq!(DomId, DomIdVec);
3424impl_vec_partialord!(DomId, DomIdVec);
3425
3426#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3428#[repr(C)]
3429pub struct DomNodeId {
3430 pub dom: DomId,
3432 pub node: NodeHierarchyItemId,
3434}
3435
3436impl_option!(
3437 DomNodeId,
3438 OptionDomNodeId,
3439 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3440);
3441
3442impl DomNodeId {
3443 pub const ROOT: Self = Self {
3444 dom: DomId::ROOT_ID,
3445 node: NodeHierarchyItemId::NONE,
3446 };
3447}
3448
3449#[repr(C)]
3455#[derive(PartialEq, Clone)]
3456pub struct Dom {
3457 pub root: NodeData,
3459 pub children: DomVec,
3461 pub css: azul_css::css::CssVec,
3465 pub estimated_total_children: usize,
3478}
3479
3480#[repr(C)]
3486#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
3487pub struct CssWithNodeId {
3488 pub node_id: usize,
3490 pub css: azul_css::css::Css,
3492}
3493
3494impl_vec!(CssWithNodeId, CssWithNodeIdVec, CssWithNodeIdVecDestructor, CssWithNodeIdVecDestructorType, CssWithNodeIdVecSlice, OptionCssWithNodeId);
3495impl_option!(CssWithNodeId, OptionCssWithNodeId, copy = false, [Debug, Clone, PartialEq, Eq, PartialOrd]);
3496impl_vec_clone!(CssWithNodeId, CssWithNodeIdVec, CssWithNodeIdVecDestructor);
3497impl_vec_mut!(CssWithNodeId, CssWithNodeIdVec);
3498impl_vec_debug!(CssWithNodeId, CssWithNodeIdVec);
3499impl_vec_partialord!(CssWithNodeId, CssWithNodeIdVec);
3500impl_vec_partialeq!(CssWithNodeId, CssWithNodeIdVec);
3501
3502#[repr(C)]
3509#[derive(Debug, Clone, PartialEq, PartialOrd)]
3510pub struct FastDom {
3511 pub node_hierarchy: crate::styled_dom::NodeHierarchyItemVec,
3513 pub node_data: NodeDataVec,
3515 pub css: CssWithNodeIdVec,
3517}
3518
3519impl Eq for Dom {}
3522
3523impl Hash for Dom {
3524 fn hash<H: Hasher>(&self, state: &mut H) {
3525 self.root.hash(state);
3526 self.children.hash(state);
3527 self.estimated_total_children.hash(state);
3528 }
3529}
3530
3531impl PartialOrd for Dom {
3533 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
3534 Some(self.cmp(other))
3535 }
3536}
3537impl Ord for Dom {
3538 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
3539 self.root.cmp(&other.root)
3540 .then_with(|| self.children.cmp(&other.children))
3541 .then_with(|| self.estimated_total_children.cmp(&other.estimated_total_children))
3542 }
3543}
3544
3545impl_option!(
3546 Dom,
3547 OptionDom,
3548 copy = false,
3549 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3550);
3551
3552impl_vec!(Dom, DomVec, DomVecDestructor, DomVecDestructorType, DomVecSlice, OptionDom);
3553impl_vec_clone!(Dom, DomVec, DomVecDestructor);
3554impl_vec_mut!(Dom, DomVec);
3555impl_vec_debug!(Dom, DomVec);
3556impl_vec_partialord!(Dom, DomVec);
3557impl_vec_ord!(Dom, DomVec);
3558impl_vec_partialeq!(Dom, DomVec);
3559impl_vec_eq!(Dom, DomVec);
3560impl_vec_hash!(Dom, DomVec);
3561
3562impl Default for Dom {
3568 fn default() -> Self {
3569 Self::create_body()
3570 }
3571}
3572
3573impl Dom {
3574 #[inline]
3579 #[must_use] pub fn create_node(node_type: NodeType) -> Self {
3580 Self {
3581 root: NodeData::create_node(node_type),
3582 children: Vec::new().into(),
3583 css: Vec::new().into(),
3584 estimated_total_children: 0,
3585 }
3586 }
3587 #[inline]
3588 #[must_use] pub fn create_from_data(node_data: NodeData) -> Self {
3589 Self {
3590 root: node_data,
3591 children: Vec::new().into(),
3592 css: Vec::new().into(),
3593 estimated_total_children: 0,
3594 }
3595 }
3596
3597 #[inline]
3604 #[must_use] pub const fn create_html() -> Self {
3605 Self {
3606 root: NodeData::create_node(NodeType::Html),
3607 children: DomVec::from_const_slice(&[]),
3608 css: azul_css::css::CssVec::from_const_slice(&[]),
3609 estimated_total_children: 0,
3610 }
3611 }
3612
3613 #[inline]
3617 #[must_use] pub const fn create_head() -> Self {
3618 Self {
3619 root: NodeData::create_node(NodeType::Head),
3620 children: DomVec::from_const_slice(&[]),
3621 css: azul_css::css::CssVec::from_const_slice(&[]),
3622 estimated_total_children: 0,
3623 }
3624 }
3625
3626 #[inline]
3627 #[must_use] pub const fn create_body() -> Self {
3628 Self {
3629 root: NodeData::create_node(NodeType::Body),
3630 children: DomVec::from_const_slice(&[]),
3631 css: azul_css::css::CssVec::from_const_slice(&[]),
3632 estimated_total_children: 0,
3633 }
3634 }
3635
3636 #[inline]
3641 #[must_use] pub const fn create_div() -> Self {
3642 Self {
3643 root: NodeData::create_node(NodeType::Div),
3644 children: DomVec::from_const_slice(&[]),
3645 css: azul_css::css::CssVec::from_const_slice(&[]),
3646 estimated_total_children: 0,
3647 }
3648 }
3649
3650 #[inline]
3658 #[must_use] pub const fn create_article() -> Self {
3659 Self {
3660 root: NodeData::create_node(NodeType::Article),
3661 children: DomVec::from_const_slice(&[]),
3662 css: azul_css::css::CssVec::from_const_slice(&[]),
3663 estimated_total_children: 0,
3664 }
3665 }
3666
3667 #[inline]
3672 #[must_use] pub const fn create_section() -> Self {
3673 Self {
3674 root: NodeData::create_node(NodeType::Section),
3675 children: DomVec::from_const_slice(&[]),
3676 css: azul_css::css::CssVec::from_const_slice(&[]),
3677 estimated_total_children: 0,
3678 }
3679 }
3680
3681 #[inline]
3687 #[must_use] pub const fn create_nav() -> Self {
3688 Self {
3689 root: NodeData::create_node(NodeType::Nav),
3690 children: DomVec::from_const_slice(&[]),
3691 css: azul_css::css::CssVec::from_const_slice(&[]),
3692 estimated_total_children: 0,
3693 }
3694 }
3695
3696 #[inline]
3701 #[must_use] pub const fn create_aside() -> Self {
3702 Self {
3703 root: NodeData::create_node(NodeType::Aside),
3704 children: DomVec::from_const_slice(&[]),
3705 css: azul_css::css::CssVec::from_const_slice(&[]),
3706 estimated_total_children: 0,
3707 }
3708 }
3709
3710 #[inline]
3715 #[must_use] pub const fn create_header() -> Self {
3716 Self {
3717 root: NodeData::create_node(NodeType::Header),
3718 children: DomVec::from_const_slice(&[]),
3719 css: azul_css::css::CssVec::from_const_slice(&[]),
3720 estimated_total_children: 0,
3721 }
3722 }
3723
3724 #[inline]
3729 #[must_use] pub const fn create_footer() -> Self {
3730 Self {
3731 root: NodeData::create_node(NodeType::Footer),
3732 children: DomVec::from_const_slice(&[]),
3733 css: azul_css::css::CssVec::from_const_slice(&[]),
3734 estimated_total_children: 0,
3735 }
3736 }
3737
3738 #[inline]
3744 #[must_use] pub const fn create_main() -> Self {
3745 Self {
3746 root: NodeData::create_node(NodeType::Main),
3747 children: DomVec::from_const_slice(&[]),
3748 css: azul_css::css::CssVec::from_const_slice(&[]),
3749 estimated_total_children: 0,
3750 }
3751 }
3752
3753 #[inline]
3758 #[must_use] pub const fn create_figure() -> Self {
3759 Self {
3760 root: NodeData::create_node(NodeType::Figure),
3761 children: DomVec::from_const_slice(&[]),
3762 css: azul_css::css::CssVec::from_const_slice(&[]),
3763 estimated_total_children: 0,
3764 }
3765 }
3766
3767 #[inline]
3772 #[must_use] pub const fn create_figcaption() -> Self {
3773 Self {
3774 root: NodeData::create_node(NodeType::FigCaption),
3775 children: DomVec::from_const_slice(&[]),
3776 css: azul_css::css::CssVec::from_const_slice(&[]),
3777 estimated_total_children: 0,
3778 }
3779 }
3780
3781 #[inline]
3788 #[must_use] pub const fn create_details_no_a11y() -> Self {
3789 Self {
3790 root: NodeData::create_node(NodeType::Details),
3791 children: DomVec::from_const_slice(&[]),
3792 css: azul_css::css::CssVec::from_const_slice(&[]),
3793 estimated_total_children: 0,
3794 }
3795 }
3796
3797 #[inline]
3804 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_details(aria: SmallAriaInfo) -> Self {
3806 Self::create_details_no_a11y().with_accessibility_info(aria.to_full_info())
3807 }
3808
3809 #[inline]
3814 #[must_use] pub const fn create_summary_no_a11y() -> Self {
3815 Self {
3816 root: NodeData::create_node(NodeType::Summary),
3817 children: DomVec::from_const_slice(&[]),
3818 css: azul_css::css::CssVec::from_const_slice(&[]),
3819 estimated_total_children: 0,
3820 }
3821 }
3822
3823 #[inline]
3830 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_summary(aria: SmallAriaInfo) -> Self {
3832 Self::create_summary_no_a11y().with_accessibility_info(aria.to_full_info())
3833 }
3834
3835 #[inline]
3840 pub fn create_summary_with_text_no_a11y<S: Into<AzString>>(text: S) -> Self {
3841 Self::create_summary_no_a11y().with_child(Self::create_text(text))
3842 }
3843
3844 #[inline]
3851 #[allow(clippy::needless_pass_by_value)] pub fn create_summary_with_text<S: Into<AzString>>(text: S, aria: SmallAriaInfo) -> Self {
3853 Self::create_summary_with_text_no_a11y(text).with_accessibility_info(aria.to_full_info())
3854 }
3855
3856 #[inline]
3861 #[must_use] pub const fn create_dialog_no_a11y() -> Self {
3862 Self {
3863 root: NodeData::create_node(NodeType::Dialog),
3864 children: DomVec::from_const_slice(&[]),
3865 css: azul_css::css::CssVec::from_const_slice(&[]),
3866 estimated_total_children: 0,
3867 }
3868 }
3869
3870 #[inline]
3878 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_dialog(aria: DialogAriaInfo) -> Self {
3880 Self::create_dialog_no_a11y().with_accessibility_info(aria.to_full_info())
3881 }
3882
3883 #[inline]
3886 #[must_use] pub const fn create_br() -> Self {
3887 Self {
3888 root: NodeData::create_node(NodeType::Br),
3889 children: DomVec::from_const_slice(&[]),
3890 css: azul_css::css::CssVec::from_const_slice(&[]),
3891 estimated_total_children: 0,
3892 }
3893 }
3894 #[inline]
3895 pub fn create_text<S: Into<AzString>>(value: S) -> Self {
3896 Self::create_node(NodeType::Text(BoxOrStatic::heap(value.into())))
3897 }
3898 #[inline]
3899 #[must_use] pub fn create_image(image: ImageRef) -> Self {
3900 Self::create_node(NodeType::Image(BoxOrStatic::heap(image)))
3901 }
3902 #[inline]
3914 pub fn create_icon<S: Into<AzString>>(icon_name: S) -> Self {
3915 Self::create_node(NodeType::Icon(BoxOrStatic::heap(icon_name.into())))
3916 }
3917
3918 #[inline]
3919 pub fn create_virtual_view(data: RefAny, callback: impl Into<VirtualViewCallback>) -> Self {
3920 Self::create_from_data(NodeData::create_virtual_view(data, callback))
3921 }
3922
3923 #[inline]
3930 #[must_use] pub fn create_geolocation_probe(config: crate::geolocation::GeolocationProbeConfig) -> Self {
3931 Self::create_node(NodeType::GeolocationProbe(config))
3932 }
3933
3934 #[inline]
3940 #[must_use] pub const fn create_p() -> Self {
3941 Self {
3942 root: NodeData::create_node(NodeType::P),
3943 children: DomVec::from_const_slice(&[]),
3944 css: azul_css::css::CssVec::from_const_slice(&[]),
3945 estimated_total_children: 0,
3946 }
3947 }
3948
3949 #[inline]
3954 #[must_use] pub const fn create_h1() -> Self {
3955 Self {
3956 root: NodeData::create_node(NodeType::H1),
3957 children: DomVec::from_const_slice(&[]),
3958 css: azul_css::css::CssVec::from_const_slice(&[]),
3959 estimated_total_children: 0,
3960 }
3961 }
3962
3963 #[inline]
3971 pub fn create_h1_with_text<S: Into<AzString>>(text: S) -> Self {
3972 Self::create_h1().with_child(Self::create_text(text))
3973 }
3974
3975 #[inline]
3979 #[must_use] pub const fn create_h2() -> Self {
3980 Self {
3981 root: NodeData::create_node(NodeType::H2),
3982 children: DomVec::from_const_slice(&[]),
3983 css: azul_css::css::CssVec::from_const_slice(&[]),
3984 estimated_total_children: 0,
3985 }
3986 }
3987
3988 #[inline]
3995 pub fn create_h2_with_text<S: Into<AzString>>(text: S) -> Self {
3996 Self::create_h2().with_child(Self::create_text(text))
3997 }
3998
3999 #[inline]
4003 #[must_use] pub const fn create_h3() -> Self {
4004 Self {
4005 root: NodeData::create_node(NodeType::H3),
4006 children: DomVec::from_const_slice(&[]),
4007 css: azul_css::css::CssVec::from_const_slice(&[]),
4008 estimated_total_children: 0,
4009 }
4010 }
4011
4012 #[inline]
4019 pub fn create_h3_with_text<S: Into<AzString>>(text: S) -> Self {
4020 Self::create_h3().with_child(Self::create_text(text))
4021 }
4022
4023 #[inline]
4025 #[must_use] pub const fn create_h4() -> Self {
4026 Self {
4027 root: NodeData::create_node(NodeType::H4),
4028 children: DomVec::from_const_slice(&[]),
4029 css: azul_css::css::CssVec::from_const_slice(&[]),
4030 estimated_total_children: 0,
4031 }
4032 }
4033
4034 #[inline]
4039 pub fn create_h4_with_text<S: Into<AzString>>(text: S) -> Self {
4040 Self::create_h4().with_child(Self::create_text(text))
4041 }
4042
4043 #[inline]
4045 #[must_use] pub const fn create_h5() -> Self {
4046 Self {
4047 root: NodeData::create_node(NodeType::H5),
4048 children: DomVec::from_const_slice(&[]),
4049 css: azul_css::css::CssVec::from_const_slice(&[]),
4050 estimated_total_children: 0,
4051 }
4052 }
4053
4054 #[inline]
4059 pub fn create_h5_with_text<S: Into<AzString>>(text: S) -> Self {
4060 Self::create_h5().with_child(Self::create_text(text))
4061 }
4062
4063 #[inline]
4065 #[must_use] pub const fn create_h6() -> Self {
4066 Self {
4067 root: NodeData::create_node(NodeType::H6),
4068 children: DomVec::from_const_slice(&[]),
4069 css: azul_css::css::CssVec::from_const_slice(&[]),
4070 estimated_total_children: 0,
4071 }
4072 }
4073
4074 #[inline]
4079 pub fn create_h6_with_text<S: Into<AzString>>(text: S) -> Self {
4080 Self::create_h6().with_child(Self::create_text(text))
4081 }
4082
4083 #[inline]
4088 #[must_use] pub const fn create_span() -> Self {
4089 Self {
4090 root: NodeData::create_node(NodeType::Span),
4091 children: DomVec::from_const_slice(&[]),
4092 css: azul_css::css::CssVec::from_const_slice(&[]),
4093 estimated_total_children: 0,
4094 }
4095 }
4096
4097 #[inline]
4105 pub fn create_span_with_text<S: Into<AzString>>(text: S) -> Self {
4106 Self::create_span().with_child(Self::create_text(text))
4107 }
4108
4109 #[inline]
4113 #[must_use] pub const fn create_strong() -> Self {
4114 Self {
4115 root: NodeData::create_node(NodeType::Strong),
4116 children: DomVec::from_const_slice(&[]),
4117 css: azul_css::css::CssVec::from_const_slice(&[]),
4118 estimated_total_children: 0,
4119 }
4120 }
4121
4122 #[inline]
4130 pub fn create_strong_with_text<S: Into<AzString>>(text: S) -> Self {
4131 Self::create_strong().with_child(Self::create_text(text))
4132 }
4133
4134 #[inline]
4138 #[must_use] pub const fn create_em() -> Self {
4139 Self {
4140 root: NodeData::create_node(NodeType::Em),
4141 children: DomVec::from_const_slice(&[]),
4142 css: azul_css::css::CssVec::from_const_slice(&[]),
4143 estimated_total_children: 0,
4144 }
4145 }
4146
4147 #[inline]
4155 pub fn create_em_with_text<S: Into<AzString>>(text: S) -> Self {
4156 Self::create_em().with_child(Self::create_text(text))
4157 }
4158
4159 #[inline]
4163 #[must_use] pub fn create_code() -> Self {
4164 Self::create_node(NodeType::Code)
4165 }
4166
4167 #[inline]
4175 pub fn create_code_with_text<S: Into<AzString>>(code: S) -> Self {
4176 Self::create_code().with_child(Self::create_text(code))
4177 }
4178
4179 #[inline]
4183 #[must_use] pub fn create_pre() -> Self {
4184 Self::create_node(NodeType::Pre)
4185 }
4186
4187 #[inline]
4195 pub fn create_pre_with_text<S: Into<AzString>>(text: S) -> Self {
4196 Self::create_pre().with_child(Self::create_text(text))
4197 }
4198
4199 #[inline]
4203 #[must_use] pub fn create_blockquote() -> Self {
4204 Self::create_node(NodeType::BlockQuote)
4205 }
4206
4207 #[inline]
4215 pub fn create_blockquote_with_text<S: Into<AzString>>(text: S) -> Self {
4216 Self::create_blockquote().with_child(Self::create_text(text))
4217 }
4218
4219 #[inline]
4223 #[must_use] pub fn create_cite() -> Self {
4224 Self::create_node(NodeType::Cite)
4225 }
4226
4227 #[inline]
4235 pub fn create_cite_with_text<S: Into<AzString>>(text: S) -> Self {
4236 Self::create_cite().with_child(Self::create_text(text))
4237 }
4238
4239 #[inline]
4244 #[must_use] pub fn create_abbr() -> Self {
4245 Self::create_node(NodeType::Abbr)
4246 }
4247
4248 #[inline]
4257 #[must_use] pub fn create_abbr_with_title(abbr_text: AzString, title: AzString) -> Self {
4258 Self::create_node(NodeType::Abbr)
4259 .with_attribute(AttributeType::Title(title))
4260 .with_child(Self::create_text(abbr_text))
4261 }
4262
4263 #[inline]
4267 #[must_use] pub fn create_kbd() -> Self {
4268 Self::create_node(NodeType::Kbd)
4269 }
4270
4271 #[inline]
4279 pub fn create_kbd_with_text<S: Into<AzString>>(text: S) -> Self {
4280 Self::create_kbd().with_child(Self::create_text(text))
4281 }
4282
4283 #[inline]
4287 #[must_use] pub fn create_samp() -> Self {
4288 Self::create_node(NodeType::Samp)
4289 }
4290
4291 #[inline]
4298 pub fn create_samp_with_text<S: Into<AzString>>(text: S) -> Self {
4299 Self::create_samp().with_child(Self::create_text(text))
4300 }
4301
4302 #[inline]
4306 #[must_use] pub fn create_var() -> Self {
4307 Self::create_node(NodeType::Var)
4308 }
4309
4310 #[inline]
4317 pub fn create_var_with_text<S: Into<AzString>>(text: S) -> Self {
4318 Self::create_var().with_child(Self::create_text(text))
4319 }
4320
4321 #[inline]
4323 #[must_use] pub fn create_sub() -> Self {
4324 Self::create_node(NodeType::Sub)
4325 }
4326
4327 #[inline]
4334 pub fn create_sub_with_text<S: Into<AzString>>(text: S) -> Self {
4335 Self::create_sub().with_child(Self::create_text(text))
4336 }
4337
4338 #[inline]
4340 #[must_use] pub fn create_sup() -> Self {
4341 Self::create_node(NodeType::Sup)
4342 }
4343
4344 #[inline]
4351 pub fn create_sup_with_text<S: Into<AzString>>(text: S) -> Self {
4352 Self::create_sup().with_child(Self::create_text(text))
4353 }
4354
4355 #[inline]
4357 #[must_use] pub fn create_u() -> Self {
4358 Self::create_node(NodeType::U)
4359 }
4360
4361 #[inline]
4366 pub fn create_u_with_text<S: Into<AzString>>(text: S) -> Self {
4367 Self::create_u().with_child(Self::create_text(text))
4368 }
4369
4370 #[inline]
4372 #[must_use] pub fn create_s() -> Self {
4373 Self::create_node(NodeType::S)
4374 }
4375
4376 #[inline]
4381 pub fn create_s_with_text<S: Into<AzString>>(text: S) -> Self {
4382 Self::create_s().with_child(Self::create_text(text))
4383 }
4384
4385 #[inline]
4387 #[must_use] pub fn create_mark() -> Self {
4388 Self::create_node(NodeType::Mark)
4389 }
4390
4391 #[inline]
4396 pub fn create_mark_with_text<S: Into<AzString>>(text: S) -> Self {
4397 Self::create_mark().with_child(Self::create_text(text))
4398 }
4399
4400 #[inline]
4402 #[must_use] pub fn create_del() -> Self {
4403 Self::create_node(NodeType::Del)
4404 }
4405
4406 #[inline]
4411 pub fn create_del_with_text<S: Into<AzString>>(text: S) -> Self {
4412 Self::create_del().with_child(Self::create_text(text))
4413 }
4414
4415 #[inline]
4417 #[must_use] pub fn create_ins() -> Self {
4418 Self::create_node(NodeType::Ins)
4419 }
4420
4421 #[inline]
4426 pub fn create_ins_with_text<S: Into<AzString>>(text: S) -> Self {
4427 Self::create_ins().with_child(Self::create_text(text))
4428 }
4429
4430 #[inline]
4432 #[must_use] pub fn create_dfn() -> Self {
4433 Self::create_node(NodeType::Dfn)
4434 }
4435
4436 #[inline]
4441 pub fn create_dfn_with_text<S: Into<AzString>>(text: S) -> Self {
4442 Self::create_dfn().with_child(Self::create_text(text))
4443 }
4444
4445 #[inline]
4454 #[must_use] pub fn create_time(text: AzString, datetime: OptionString) -> Self {
4455 let mut element = Self::create_node(NodeType::Time).with_child(Self::create_text(text));
4456 if let OptionString::Some(dt) = datetime {
4457 element = element.with_attribute(AttributeType::Custom(AttributeNameValue {
4458 attr_name: "datetime".into(),
4459 value: dt,
4460 }));
4461 }
4462 element
4463 }
4464
4465 #[inline]
4469 #[must_use] pub fn create_bdo() -> Self {
4470 Self::create_node(NodeType::Bdo)
4471 }
4472
4473 #[inline]
4477 pub fn create_bdo_with_text<S: Into<AzString>>(text: S) -> Self {
4478 Self::create_bdo().with_child(Self::create_text(text))
4479 }
4480
4481 #[inline]
4487 #[must_use] pub fn create_b() -> Self {
4488 Self::create_node(NodeType::B)
4489 }
4490
4491 #[inline]
4498 pub fn create_b_with_text<S: Into<AzString>>(text: S) -> Self {
4499 Self::create_b().with_child(Self::create_text(text))
4500 }
4501
4502 #[inline]
4506 #[must_use] pub fn create_i() -> Self {
4507 Self::create_node(NodeType::I)
4508 }
4509
4510 #[inline]
4517 pub fn create_i_with_text<S: Into<AzString>>(text: S) -> Self {
4518 Self::create_i().with_child(Self::create_text(text))
4519 }
4520
4521 #[inline]
4525 #[must_use] pub fn create_small() -> Self {
4526 Self::create_node(NodeType::Small)
4527 }
4528
4529 #[inline]
4534 pub fn create_small_with_text<S: Into<AzString>>(text: S) -> Self {
4535 Self::create_small().with_child(Self::create_text(text))
4536 }
4537
4538 #[inline]
4542 #[must_use] pub fn create_big() -> Self {
4543 Self::create_node(NodeType::Big)
4544 }
4545
4546 #[inline]
4550 pub fn create_big_with_text<S: Into<AzString>>(text: S) -> Self {
4551 Self::create_big().with_child(Self::create_text(text))
4552 }
4553
4554 #[inline]
4559 #[must_use] pub fn create_bdi() -> Self {
4560 Self::create_node(NodeType::Bdi)
4561 }
4562
4563 #[inline]
4568 pub fn create_bdi_with_text<S: Into<AzString>>(text: S) -> Self {
4569 Self::create_bdi().with_child(Self::create_text(text))
4570 }
4571
4572 #[inline]
4577 #[must_use] pub fn create_wbr() -> Self {
4578 Self::create_node(NodeType::Wbr)
4579 }
4580
4581 #[inline]
4586 #[must_use] pub fn create_ruby() -> Self {
4587 Self::create_node(NodeType::Ruby)
4588 }
4589
4590 #[inline]
4594 #[must_use] pub fn create_rt() -> Self {
4595 Self::create_node(NodeType::Rt)
4596 }
4597
4598 #[inline]
4603 pub fn create_rt_with_text<S: Into<AzString>>(text: S) -> Self {
4604 Self::create_rt().with_child(Self::create_text(text))
4605 }
4606
4607 #[inline]
4611 #[must_use] pub fn create_rtc() -> Self {
4612 Self::create_node(NodeType::Rtc)
4613 }
4614
4615 #[inline]
4619 #[must_use] pub fn create_rp() -> Self {
4620 Self::create_node(NodeType::Rp)
4621 }
4622
4623 #[inline]
4628 pub fn create_rp_with_text<S: Into<AzString>>(text: S) -> Self {
4629 Self::create_rp().with_child(Self::create_text(text))
4630 }
4631
4632 #[inline]
4637 #[must_use] pub fn create_data(value: AzString) -> Self {
4638 Self::create_node(NodeType::Data).with_attribute(AttributeType::Value(value))
4639 }
4640
4641 #[inline]
4647 #[must_use] pub fn create_data_with_text(value: AzString, text: AzString) -> Self {
4648 Self::create_data(value).with_child(Self::create_text(text))
4649 }
4650
4651 #[inline]
4655 #[must_use] pub fn create_dir() -> Self {
4656 Self::create_node(NodeType::Dir)
4657 }
4658
4659 #[inline]
4663 #[must_use] pub fn create_svg() -> Self {
4664 Self::create_node(NodeType::Svg)
4665 }
4666
4667 #[inline]
4675 #[must_use] pub fn create_a_no_a11y(href: AzString, label: OptionString) -> Self {
4676 let mut link = Self::create_node(NodeType::A).with_attribute(AttributeType::Href(href));
4677 if let OptionString::Some(text) = label {
4678 link = link.with_child(Self::create_text(text));
4679 }
4680 link
4681 }
4682
4683 #[inline]
4691 #[must_use] pub fn create_button_no_a11y(text: AzString) -> Self {
4692 Self::create_node(NodeType::Button).with_child(Self::create_text(text))
4693 }
4694
4695 #[inline]
4703 #[must_use] pub fn create_label_no_a11y(for_id: AzString, text: AzString) -> Self {
4704 Self::create_node(NodeType::Label)
4705 .with_attribute(AttributeType::Custom(AttributeNameValue {
4706 attr_name: "for".into(),
4707 value: for_id,
4708 }))
4709 .with_child(Self::create_text(text))
4710 }
4711
4712 #[inline]
4722 #[must_use] pub fn create_input_no_a11y(input_type: AzString, name: AzString, label: AzString) -> Self {
4723 Self::create_node(NodeType::Input)
4724 .with_attribute(AttributeType::InputType(input_type))
4725 .with_attribute(AttributeType::Name(name))
4726 .with_attribute(AttributeType::AriaLabel(label))
4727 }
4728
4729 #[inline]
4738 #[must_use] pub fn create_textarea_no_a11y(name: AzString, label: AzString) -> Self {
4739 Self::create_node(NodeType::TextArea)
4740 .with_attribute(AttributeType::Name(name))
4741 .with_attribute(AttributeType::AriaLabel(label))
4742 }
4743
4744 #[inline]
4753 #[must_use] pub fn create_select_no_a11y(name: AzString, label: AzString) -> Self {
4754 Self::create_node(NodeType::Select)
4755 .with_attribute(AttributeType::Name(name))
4756 .with_attribute(AttributeType::AriaLabel(label))
4757 }
4758
4759 #[inline]
4765 #[must_use] pub fn create_option_no_a11y(value: AzString, text: AzString) -> Self {
4766 Self::create_node(NodeType::SelectOption)
4767 .with_attribute(AttributeType::Value(value))
4768 .with_child(Self::create_text(text))
4769 }
4770
4771 #[inline]
4780 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_option(value: AzString, text: AzString, aria: SmallAriaInfo) -> Self {
4782 Self::create_option_no_a11y(value, text).with_accessibility_info(aria.to_full_info())
4783 }
4784
4785 #[inline]
4790 #[must_use] pub fn create_ul() -> Self {
4791 Self::create_node(NodeType::Ul)
4792 }
4793
4794 #[inline]
4799 #[must_use] pub fn create_ol() -> Self {
4800 Self::create_node(NodeType::Ol)
4801 }
4802
4803 #[inline]
4808 #[must_use] pub fn create_li() -> Self {
4809 Self::create_node(NodeType::Li)
4810 }
4811
4812 #[inline]
4817 #[must_use] pub fn create_table_no_a11y() -> Self {
4818 Self::create_node(NodeType::Table)
4819 }
4820
4821 #[inline]
4825 #[must_use] pub fn create_caption() -> Self {
4826 Self::create_node(NodeType::Caption)
4827 }
4828
4829 #[inline]
4833 #[must_use] pub fn create_thead() -> Self {
4834 Self::create_node(NodeType::THead)
4835 }
4836
4837 #[inline]
4841 #[must_use] pub fn create_tbody() -> Self {
4842 Self::create_node(NodeType::TBody)
4843 }
4844
4845 #[inline]
4849 #[must_use] pub fn create_tfoot() -> Self {
4850 Self::create_node(NodeType::TFoot)
4851 }
4852
4853 #[inline]
4855 #[must_use] pub fn create_tr() -> Self {
4856 Self::create_node(NodeType::Tr)
4857 }
4858
4859 #[inline]
4864 #[must_use] pub fn create_th() -> Self {
4865 Self::create_node(NodeType::Th)
4866 }
4867
4868 #[inline]
4870 #[must_use] pub fn create_td() -> Self {
4871 Self::create_node(NodeType::Td)
4872 }
4873
4874 #[inline]
4878 #[must_use] pub fn create_form_no_a11y() -> Self {
4879 Self::create_node(NodeType::Form)
4880 }
4881
4882 #[inline]
4889 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_form(aria: SmallAriaInfo) -> Self {
4891 Self::create_form_no_a11y().with_accessibility_info(aria.to_full_info())
4892 }
4893
4894 #[inline]
4898 #[must_use] pub fn create_fieldset_no_a11y() -> Self {
4899 Self::create_node(NodeType::FieldSet)
4900 }
4901
4902 #[inline]
4910 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_fieldset(aria: SmallAriaInfo) -> Self {
4912 Self::create_fieldset_no_a11y().with_accessibility_info(aria.to_full_info())
4913 }
4914
4915 #[inline]
4919 #[must_use] pub fn create_legend_no_a11y() -> Self {
4920 Self::create_node(NodeType::Legend)
4921 }
4922
4923 #[inline]
4930 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_legend(aria: SmallAriaInfo) -> Self {
4932 Self::create_legend_no_a11y().with_accessibility_info(aria.to_full_info())
4933 }
4934
4935 #[inline]
4940 #[must_use] pub fn create_hr() -> Self {
4941 Self::create_node(NodeType::Hr)
4942 }
4943
4944 #[inline]
4951 #[must_use] pub const fn create_address() -> Self {
4952 Self {
4953 root: NodeData::create_node(NodeType::Address),
4954 children: DomVec::from_const_slice(&[]),
4955 css: azul_css::css::CssVec::from_const_slice(&[]),
4956 estimated_total_children: 0,
4957 }
4958 }
4959
4960 #[inline]
4964 #[must_use] pub const fn create_dl() -> Self {
4965 Self {
4966 root: NodeData::create_node(NodeType::Dl),
4967 children: DomVec::from_const_slice(&[]),
4968 css: azul_css::css::CssVec::from_const_slice(&[]),
4969 estimated_total_children: 0,
4970 }
4971 }
4972
4973 #[inline]
4977 #[must_use] pub const fn create_dt() -> Self {
4978 Self {
4979 root: NodeData::create_node(NodeType::Dt),
4980 children: DomVec::from_const_slice(&[]),
4981 css: azul_css::css::CssVec::from_const_slice(&[]),
4982 estimated_total_children: 0,
4983 }
4984 }
4985
4986 #[inline]
4990 #[must_use] pub const fn create_dd() -> Self {
4991 Self {
4992 root: NodeData::create_node(NodeType::Dd),
4993 children: DomVec::from_const_slice(&[]),
4994 css: azul_css::css::CssVec::from_const_slice(&[]),
4995 estimated_total_children: 0,
4996 }
4997 }
4998
4999 #[inline]
5001 #[must_use] pub const fn create_colgroup() -> Self {
5002 Self {
5003 root: NodeData::create_node(NodeType::ColGroup),
5004 children: DomVec::from_const_slice(&[]),
5005 css: azul_css::css::CssVec::from_const_slice(&[]),
5006 estimated_total_children: 0,
5007 }
5008 }
5009
5010 #[inline]
5012 #[must_use] pub fn create_col(span: i32) -> Self {
5013 Self::create_node(NodeType::Col).with_attribute(AttributeType::ColSpan(span))
5014 }
5015
5016 #[inline]
5023 #[must_use] pub fn create_optgroup_no_a11y(label: AzString) -> Self {
5024 Self::create_node(NodeType::OptGroup).with_attribute(AttributeType::AriaLabel(label))
5025 }
5026
5027 #[inline]
5035 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_optgroup(label: AzString, aria: SmallAriaInfo) -> Self {
5037 Self::create_optgroup_no_a11y(label).with_accessibility_info(aria.to_full_info())
5038 }
5039
5040 #[inline]
5044 #[must_use] pub const fn create_q() -> Self {
5045 Self {
5046 root: NodeData::create_node(NodeType::Q),
5047 children: DomVec::from_const_slice(&[]),
5048 css: azul_css::css::CssVec::from_const_slice(&[]),
5049 estimated_total_children: 0,
5050 }
5051 }
5052
5053 #[inline]
5057 #[must_use] pub const fn create_acronym() -> Self {
5058 Self {
5059 root: NodeData::create_node(NodeType::Acronym),
5060 children: DomVec::from_const_slice(&[]),
5061 css: azul_css::css::CssVec::from_const_slice(&[]),
5062 estimated_total_children: 0,
5063 }
5064 }
5065
5066 #[inline]
5070 pub fn create_acronym_with_text<S: Into<AzString>>(text: S) -> Self {
5071 Self::create_acronym().with_child(Self::create_text(text))
5072 }
5073
5074 #[inline]
5078 #[must_use] pub const fn create_menu_no_a11y() -> Self {
5079 Self {
5080 root: NodeData::create_node(NodeType::Menu),
5081 children: DomVec::from_const_slice(&[]),
5082 css: azul_css::css::CssVec::from_const_slice(&[]),
5083 estimated_total_children: 0,
5084 }
5085 }
5086
5087 #[inline]
5094 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_menu(aria: SmallAriaInfo) -> Self {
5096 Self::create_menu_no_a11y().with_accessibility_info(aria.to_full_info())
5097 }
5098
5099 #[inline]
5103 #[must_use] pub const fn create_menuitem_no_a11y() -> Self {
5104 Self {
5105 root: NodeData::create_node(NodeType::MenuItem),
5106 children: DomVec::from_const_slice(&[]),
5107 css: azul_css::css::CssVec::from_const_slice(&[]),
5108 estimated_total_children: 0,
5109 }
5110 }
5111
5112 #[inline]
5119 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_menuitem(aria: SmallAriaInfo) -> Self {
5121 Self::create_menuitem_no_a11y().with_accessibility_info(aria.to_full_info())
5122 }
5123
5124 #[inline]
5129 pub fn create_menuitem_with_text_no_a11y<S: Into<AzString>>(text: S) -> Self {
5130 Self::create_menuitem_no_a11y().with_child(Self::create_text(text))
5131 }
5132
5133 #[inline]
5140 #[allow(clippy::needless_pass_by_value)] pub fn create_menuitem_with_text<S: Into<AzString>>(text: S, aria: SmallAriaInfo) -> Self {
5142 Self::create_menuitem_with_text_no_a11y(text).with_accessibility_info(aria.to_full_info())
5143 }
5144
5145 #[inline]
5150 #[must_use] pub const fn create_output_no_a11y() -> Self {
5151 Self {
5152 root: NodeData::create_node(NodeType::Output),
5153 children: DomVec::from_const_slice(&[]),
5154 css: azul_css::css::CssVec::from_const_slice(&[]),
5155 estimated_total_children: 0,
5156 }
5157 }
5158
5159 #[inline]
5166 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_output(aria: SmallAriaInfo) -> Self {
5168 Self::create_output_no_a11y().with_accessibility_info(aria.to_full_info())
5169 }
5170
5171 #[inline]
5179 #[must_use] pub fn create_progress_no_a11y(value: f32, max: f32) -> Self {
5180 Self::create_node(NodeType::Progress)
5181 .with_attribute(AttributeType::Custom(AttributeNameValue {
5182 attr_name: "value".into(),
5183 value: value.to_string().into(),
5184 }))
5185 .with_attribute(AttributeType::Custom(AttributeNameValue {
5186 attr_name: "max".into(),
5187 value: max.to_string().into(),
5188 }))
5189 }
5190
5191 #[inline]
5199 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_progress(aria: ProgressAriaInfo) -> Self {
5201 let mut node = Self::create_node(NodeType::Progress);
5202 if !aria.indeterminate {
5203 if let azul_css::OptionF32::Some(v) = aria.current_value {
5204 node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
5205 attr_name: "value".into(),
5206 value: v.to_string().into(),
5207 }));
5208 }
5209 }
5210 if let azul_css::OptionF32::Some(m) = aria.max {
5211 node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
5212 attr_name: "max".into(),
5213 value: m.to_string().into(),
5214 }));
5215 }
5216 node.with_accessibility_info(aria.to_full_info())
5217 }
5218
5219 #[inline]
5228 #[must_use] pub fn create_meter_no_a11y(value: f32, min: f32, max: f32) -> Self {
5229 Self::create_node(NodeType::Meter)
5230 .with_attribute(AttributeType::Custom(AttributeNameValue {
5231 attr_name: "value".into(),
5232 value: value.to_string().into(),
5233 }))
5234 .with_attribute(AttributeType::Custom(AttributeNameValue {
5235 attr_name: "min".into(),
5236 value: min.to_string().into(),
5237 }))
5238 .with_attribute(AttributeType::Custom(AttributeNameValue {
5239 attr_name: "max".into(),
5240 value: max.to_string().into(),
5241 }))
5242 }
5243
5244 #[inline]
5252 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_meter(aria: MeterAriaInfo) -> Self {
5254 let mut node = Self::create_meter_no_a11y(aria.current_value, aria.min, aria.max);
5255 if let azul_css::OptionF32::Some(v) = aria.low {
5256 node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
5257 attr_name: "low".into(),
5258 value: v.to_string().into(),
5259 }));
5260 }
5261 if let azul_css::OptionF32::Some(v) = aria.high {
5262 node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
5263 attr_name: "high".into(),
5264 value: v.to_string().into(),
5265 }));
5266 }
5267 if let azul_css::OptionF32::Some(v) = aria.optimum {
5268 node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
5269 attr_name: "optimum".into(),
5270 value: v.to_string().into(),
5271 }));
5272 }
5273 node.with_accessibility_info(aria.to_full_info())
5274 }
5275
5276 #[inline]
5280 #[must_use] pub const fn create_datalist_no_a11y() -> Self {
5281 Self {
5282 root: NodeData::create_node(NodeType::DataList),
5283 children: DomVec::from_const_slice(&[]),
5284 css: azul_css::css::CssVec::from_const_slice(&[]),
5285 estimated_total_children: 0,
5286 }
5287 }
5288
5289 #[inline]
5296 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_datalist(aria: SmallAriaInfo) -> Self {
5298 Self::create_datalist_no_a11y().with_accessibility_info(aria.to_full_info())
5299 }
5300
5301 #[inline]
5308 #[must_use] pub const fn create_canvas_no_a11y() -> Self {
5309 Self {
5310 root: NodeData::create_node(NodeType::Canvas),
5311 children: DomVec::from_const_slice(&[]),
5312 css: azul_css::css::CssVec::from_const_slice(&[]),
5313 estimated_total_children: 0,
5314 }
5315 }
5316
5317 #[inline]
5325 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_canvas(aria: SmallAriaInfo) -> Self {
5327 Self::create_canvas_no_a11y().with_accessibility_info(aria.to_full_info())
5328 }
5329
5330 #[inline]
5334 #[must_use] pub const fn create_object() -> Self {
5335 Self {
5336 root: NodeData::create_node(NodeType::Object),
5337 children: DomVec::from_const_slice(&[]),
5338 css: azul_css::css::CssVec::from_const_slice(&[]),
5339 estimated_total_children: 0,
5340 }
5341 }
5342
5343 #[inline]
5349 #[must_use] pub fn create_param(name: AzString, value: AzString) -> Self {
5350 Self::create_node(NodeType::Param)
5351 .with_attribute(AttributeType::Name(name))
5352 .with_attribute(AttributeType::Value(value))
5353 }
5354
5355 #[inline]
5360 #[must_use] pub const fn create_embed() -> Self {
5361 Self {
5362 root: NodeData::create_node(NodeType::Embed),
5363 children: DomVec::from_const_slice(&[]),
5364 css: azul_css::css::CssVec::from_const_slice(&[]),
5365 estimated_total_children: 0,
5366 }
5367 }
5368
5369 #[inline]
5373 #[must_use] pub const fn create_audio_no_a11y() -> Self {
5374 Self {
5375 root: NodeData::create_node(NodeType::Audio),
5376 children: DomVec::from_const_slice(&[]),
5377 css: azul_css::css::CssVec::from_const_slice(&[]),
5378 estimated_total_children: 0,
5379 }
5380 }
5381
5382 #[inline]
5389 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_audio(aria: SmallAriaInfo) -> Self {
5391 Self::create_audio_no_a11y().with_accessibility_info(aria.to_full_info())
5392 }
5393
5394 #[inline]
5398 #[must_use] pub const fn create_video_no_a11y() -> Self {
5399 Self {
5400 root: NodeData::create_node(NodeType::Video),
5401 children: DomVec::from_const_slice(&[]),
5402 css: azul_css::css::CssVec::from_const_slice(&[]),
5403 estimated_total_children: 0,
5404 }
5405 }
5406
5407 #[inline]
5414 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_video(aria: SmallAriaInfo) -> Self {
5416 Self::create_video_no_a11y().with_accessibility_info(aria.to_full_info())
5417 }
5418
5419 #[inline]
5425 #[must_use] pub fn create_source(src: AzString, media_type: AzString) -> Self {
5426 Self::create_node(NodeType::Source)
5427 .with_attribute(AttributeType::Src(src))
5428 .with_attribute(AttributeType::Custom(AttributeNameValue {
5429 attr_name: "type".into(),
5430 value: media_type,
5431 }))
5432 }
5433
5434 #[inline]
5443 #[must_use] pub fn create_track(src: AzString, kind: AzString) -> Self {
5444 Self::create_node(NodeType::Track)
5445 .with_attribute(AttributeType::Src(src))
5446 .with_attribute(AttributeType::Custom(AttributeNameValue {
5447 attr_name: "kind".into(),
5448 value: kind,
5449 }))
5450 }
5451
5452 #[inline]
5456 #[must_use] pub const fn create_map() -> Self {
5457 Self {
5458 root: NodeData::create_node(NodeType::Map),
5459 children: DomVec::from_const_slice(&[]),
5460 css: azul_css::css::CssVec::from_const_slice(&[]),
5461 estimated_total_children: 0,
5462 }
5463 }
5464
5465 #[inline]
5469 #[must_use] pub const fn create_area_no_a11y() -> Self {
5470 Self {
5471 root: NodeData::create_node(NodeType::Area),
5472 children: DomVec::from_const_slice(&[]),
5473 css: azul_css::css::CssVec::from_const_slice(&[]),
5474 estimated_total_children: 0,
5475 }
5476 }
5477
5478 #[inline]
5485 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_area(aria: SmallAriaInfo) -> Self {
5487 Self::create_area_no_a11y().with_accessibility_info(aria.to_full_info())
5488 }
5489
5490 #[inline]
5496 #[must_use] pub fn create_title() -> Self {
5497 Self::create_node(NodeType::Title)
5498 }
5499
5500 #[inline]
5505 pub fn create_title_with_text<S: Into<AzString>>(text: S) -> Self {
5506 Self::create_title().with_child(Self::create_text(text))
5507 }
5508
5509 #[inline]
5513 #[must_use] pub const fn create_meta() -> Self {
5514 Self {
5515 root: NodeData::create_node(NodeType::Meta),
5516 children: DomVec::from_const_slice(&[]),
5517 css: azul_css::css::CssVec::from_const_slice(&[]),
5518 estimated_total_children: 0,
5519 }
5520 }
5521
5522 #[inline]
5527 #[must_use] pub const fn create_link() -> Self {
5528 Self {
5529 root: NodeData::create_node(NodeType::Link),
5530 children: DomVec::from_const_slice(&[]),
5531 css: azul_css::css::CssVec::from_const_slice(&[]),
5532 estimated_total_children: 0,
5533 }
5534 }
5535
5536 #[inline]
5541 #[must_use] pub const fn create_script() -> Self {
5542 Self {
5543 root: NodeData::create_node(NodeType::Script),
5544 children: DomVec::from_const_slice(&[]),
5545 css: azul_css::css::CssVec::from_const_slice(&[]),
5546 estimated_total_children: 0,
5547 }
5548 }
5549
5550 #[inline]
5555 #[must_use] pub const fn create_style() -> Self {
5556 Self {
5557 root: NodeData::create_node(NodeType::Style),
5558 children: DomVec::from_const_slice(&[]),
5559 css: azul_css::css::CssVec::from_const_slice(&[]),
5560 estimated_total_children: 0,
5561 }
5562 }
5563
5564 #[inline]
5569 pub fn create_style_with_text<S: Into<AzString>>(text: S) -> Self {
5570 Self::create_style().with_child(Self::create_text(text))
5571 }
5572
5573 #[inline]
5578 #[must_use] pub fn create_base(href: AzString) -> Self {
5579 Self::create_node(NodeType::Base).with_attribute(AttributeType::Href(href))
5580 }
5581
5582 #[inline]
5592 #[must_use] pub fn create_th_with_scope(scope: AzString, text: AzString) -> Self {
5593 Self::create_node(NodeType::Th)
5594 .with_attribute(AttributeType::Scope(scope))
5595 .with_child(Self::create_text(text))
5596 }
5597
5598 #[inline]
5603 pub fn create_td_with_text<S: Into<AzString>>(text: S) -> Self {
5604 Self::create_td().with_child(Self::create_text(text))
5605 }
5606
5607 #[inline]
5612 pub fn create_th_with_text<S: Into<AzString>>(text: S) -> Self {
5613 Self::create_th().with_child(Self::create_text(text))
5614 }
5615
5616 #[inline]
5621 pub fn create_li_with_text<S: Into<AzString>>(text: S) -> Self {
5622 Self::create_li().with_child(Self::create_text(text))
5623 }
5624
5625 #[inline]
5630 pub fn create_p_with_text<S: Into<AzString>>(text: S) -> Self {
5631 Self::create_p().with_child(Self::create_text(text))
5632 }
5633
5634 #[inline]
5646 #[allow(clippy::needless_pass_by_value)] pub fn create_button<S: Into<AzString>>(text: S, aria: SmallAriaInfo) -> Self {
5648 let mut btn = Self::create_button_no_a11y(text.into());
5649 btn.root.set_accessibility_info(aria.to_full_info());
5650 btn
5651 }
5652
5653 #[inline]
5663 #[allow(clippy::needless_pass_by_value)] pub fn create_a<S1: Into<AzString>, S2: Into<AzString>>(
5665 href: S1,
5666 text: S2,
5667 aria: SmallAriaInfo,
5668 ) -> Self {
5669 let mut link = Self::create_a_no_a11y(href.into(), OptionString::Some(text.into()));
5670 link.root.set_accessibility_info(aria.to_full_info());
5671 link
5672 }
5673
5674 #[inline]
5684 #[allow(clippy::needless_pass_by_value)] pub fn create_input<S1: Into<AzString>, S2: Into<AzString>, S3: Into<AzString>>(
5686 input_type: S1,
5687 name: S2,
5688 label: S3,
5689 aria: SmallAriaInfo,
5690 ) -> Self {
5691 let mut input = Self::create_input_no_a11y(input_type.into(), name.into(), label.into());
5692 input.root.set_accessibility_info(aria.to_full_info());
5693 input
5694 }
5695
5696 #[inline]
5705 #[allow(clippy::needless_pass_by_value)] pub fn create_textarea<S1: Into<AzString>, S2: Into<AzString>>(
5707 name: S1,
5708 label: S2,
5709 aria: SmallAriaInfo,
5710 ) -> Self {
5711 let mut textarea = Self::create_textarea_no_a11y(name.into(), label.into());
5712 textarea.root.set_accessibility_info(aria.to_full_info());
5713 textarea
5714 }
5715
5716 #[inline]
5725 #[allow(clippy::needless_pass_by_value)] pub fn create_select<S1: Into<AzString>, S2: Into<AzString>>(
5727 name: S1,
5728 label: S2,
5729 aria: SmallAriaInfo,
5730 ) -> Self {
5731 let mut select = Self::create_select_no_a11y(name.into(), label.into());
5732 select.root.set_accessibility_info(aria.to_full_info());
5733 select
5734 }
5735
5736 #[inline]
5745 #[allow(clippy::needless_pass_by_value)] pub fn create_table<S: Into<AzString>>(caption: S, aria: SmallAriaInfo) -> Self {
5747 let mut table = Self::create_table_no_a11y()
5748 .with_child(Self::create_caption().with_child(Self::create_text(caption)));
5749 table.root.set_accessibility_info(aria.to_full_info());
5750 table
5751 }
5752
5753 #[inline]
5762 #[allow(clippy::needless_pass_by_value)] pub fn create_label<S1: Into<AzString>, S2: Into<AzString>>(
5764 for_id: S1,
5765 text: S2,
5766 aria: SmallAriaInfo,
5767 ) -> Self {
5768 let mut label = Self::create_label_no_a11y(for_id.into(), text.into());
5769 label.root.set_accessibility_info(aria.to_full_info());
5770 label
5771 }
5772
5773 #[cfg(feature = "xml")]
5779 pub fn from_xml<S: AsRef<str>>(xml_str: S) -> Self {
5780 Self::create_text(format!(
5783 "XML content loaded ({} bytes)",
5784 xml_str.as_ref().len()
5785 ))
5786 }
5787
5788 #[cfg(not(feature = "xml"))]
5790 pub fn from_xml<S: AsRef<str>>(xml_str: S) -> Self {
5791 Self::create_text(format!(
5792 "XML parsing requires 'xml' feature ({} bytes)",
5793 xml_str.as_ref().len()
5794 ))
5795 }
5796
5797 #[inline]
5799 #[must_use]
5800 pub const fn swap_with_default(&mut self) -> Self {
5801 let mut s = Self {
5802 root: NodeData::create_div(),
5803 children: DomVec::from_const_slice(&[]),
5804 css: azul_css::css::CssVec::from_const_slice(&[]),
5805 estimated_total_children: 0,
5806 };
5807 mem::swap(&mut s, self);
5808 s
5809 }
5810
5811 #[must_use]
5818 pub fn recompute_estimated_total_children(&self) -> usize {
5819 self.children
5820 .iter()
5821 .map(|c| c.recompute_estimated_total_children() + 1)
5822 .sum()
5823 }
5824
5825 #[inline]
5826 pub fn add_child(&mut self, child: Self) {
5827 debug_assert_eq!(
5834 child.estimated_total_children,
5835 child
5836 .children
5837 .iter()
5838 .map(|c| c.estimated_total_children + 1)
5839 .sum::<usize>(),
5840 "Dom.estimated_total_children desynced for added child; call \
5841 fixup_children_estimated() after mutating `children` directly",
5842 );
5843 let estimated = child.estimated_total_children;
5844 let mut v: DomVec = Vec::new().into();
5845 mem::swap(&mut v, &mut self.children);
5846 let mut v = v.into_library_owned_vec();
5847 v.push(child);
5848 self.children = v.into();
5849 self.estimated_total_children += estimated + 1;
5850 }
5851
5852 #[inline]
5853 pub fn set_children(&mut self, children: DomVec) {
5854 debug_assert!(
5858 children.iter().all(|c| c.estimated_total_children
5859 == c
5860 .children
5861 .iter()
5862 .map(|g| g.estimated_total_children + 1)
5863 .sum::<usize>()),
5864 "Dom.estimated_total_children desynced in set_children; a child's own \
5865 estimate was stale — call fixup_children_estimated() first",
5866 );
5867 let children_estimated = children
5868 .iter()
5869 .map(|s| s.estimated_total_children + 1)
5870 .sum();
5871 self.children = children;
5872 self.estimated_total_children = children_estimated;
5873 }
5874
5875 #[must_use]
5876 pub fn copy_except_for_root(&mut self) -> Self {
5877 Self {
5878 root: self.root.copy_special(),
5879 children: self.children.clone(),
5880 css: self.css.clone(),
5881 estimated_total_children: self.estimated_total_children,
5882 }
5883 }
5884 #[must_use] pub const fn node_count(&self) -> usize {
5885 self.estimated_total_children.saturating_add(1)
5892 }
5893
5894 pub fn add_component_css(&mut self, css: azul_css::css::Css) {
5900 let mut v = Vec::new().into();
5901 mem::swap(&mut v, &mut self.css);
5902 let mut v: Vec<azul_css::css::Css> = v.into_library_owned_vec();
5903 v.push(css);
5904 self.css = v.into();
5905 }
5906
5907 pub fn set_component_css(&mut self, css: azul_css::css::CssVec) {
5911 self.css = css;
5912 }
5913 #[inline]
5914 #[must_use] pub fn with_children(mut self, children: DomVec) -> Self {
5915 self.set_children(children);
5916 self
5917 }
5918 #[inline]
5919 #[must_use] pub fn with_child(mut self, child: Self) -> Self {
5920 self.add_child(child);
5921 self
5922 }
5923 #[inline]
5924 #[must_use] pub fn with_node_type(mut self, node_type: NodeType) -> Self {
5925 self.root.set_node_type(node_type);
5926 self
5927 }
5928 #[inline]
5929 #[must_use] pub fn with_id(mut self, id: AzString) -> Self {
5930 self.root.add_id(id);
5931 self
5932 }
5933 #[inline]
5934 #[must_use] pub fn with_class(mut self, class: AzString) -> Self {
5935 self.root.add_class(class);
5936 self
5937 }
5938 #[inline]
5939 #[must_use]
5940 pub fn with_callback<C: Into<CoreCallback>>(
5941 mut self,
5942 event: EventFilter,
5943 data: RefAny,
5944 callback: C,
5945 ) -> Self {
5946 self.root.add_callback(event, data, callback);
5947 self
5948 }
5949 #[inline]
5951 #[must_use] pub fn with_css_property(mut self, prop: CssPropertyWithConditions) -> Self {
5952 self.root.add_css_property(prop);
5953 self
5954 }
5955 #[inline]
5957 pub fn add_css_property(&mut self, prop: CssPropertyWithConditions) {
5958 self.root.add_css_property(prop);
5959 }
5960 #[inline]
5961 pub fn add_class(&mut self, class: AzString) {
5962 self.root.add_class(class);
5963 }
5964 #[inline]
5965 pub fn add_callback<C: Into<CoreCallback>>(
5966 &mut self,
5967 event: EventFilter,
5968 data: RefAny,
5969 callback: C,
5970 ) {
5971 self.root.add_callback(event, data, callback);
5972 }
5973 #[inline]
5974 pub const fn set_tab_index(&mut self, tab_index: TabIndex) {
5975 self.root.set_tab_index(tab_index);
5976 }
5977 #[inline]
5978 pub const fn set_contenteditable(&mut self, contenteditable: bool) {
5979 self.root.set_contenteditable(contenteditable);
5980 }
5981 #[inline]
5982 #[must_use] pub const fn with_tab_index(mut self, tab_index: TabIndex) -> Self {
5983 self.root.set_tab_index(tab_index);
5984 self
5985 }
5986 #[inline]
5987 #[must_use] pub const fn with_contenteditable(mut self, contenteditable: bool) -> Self {
5988 self.root.set_contenteditable(contenteditable);
5989 self
5990 }
5991 #[inline]
5992 #[must_use] pub fn with_dataset(mut self, data: OptionRefAny) -> Self {
5993 self.root.set_dataset(data);
5994 self
5995 }
5996 #[inline]
5997 #[must_use] pub fn with_ids_and_classes(mut self, ids_and_classes: IdOrClassVec) -> Self {
5998 self.root.set_ids_and_classes(ids_and_classes);
5999 self
6000 }
6001
6002 #[inline]
6004 #[must_use] pub fn with_attribute(mut self, attr: AttributeType) -> Self {
6005 let mut attrs = self.root.attributes().clone();
6006 let mut v = attrs.into_library_owned_vec();
6007 v.push(attr);
6008 self.root.set_attributes(v.into());
6009 self
6010 }
6011
6012 #[inline]
6014 #[must_use] pub fn with_attributes(mut self, attributes: AttributeTypeVec) -> Self {
6015 self.root.set_attributes(attributes);
6016 self
6017 }
6018
6019 #[inline]
6020 #[must_use] pub fn with_callbacks(mut self, callbacks: CoreCallbackDataVec) -> Self {
6021 self.root.callbacks = callbacks;
6022 self
6023 }
6024 #[inline]
6027 #[must_use] pub fn with_css_props(mut self, css_props: CssPropertyWithConditionsVec) -> Self {
6028 self.root.style = css_props.into();
6029 self
6030 }
6031 #[inline]
6033 #[must_use] pub fn with_style(mut self, style: azul_css::css::Css) -> Self {
6034 self.root.style = style;
6035 self
6036 }
6037
6038 #[inline]
6050 #[must_use]
6051 pub fn with_key<K: Hash>(mut self, key: K) -> Self {
6052 self.root.set_key(key);
6053 self
6054 }
6055
6056 #[inline]
6065 #[must_use]
6066 pub fn with_merge_callback<C: Into<DatasetMergeCallback>>(mut self, callback: C) -> Self {
6067 self.root.set_merge_callback(callback);
6068 self
6069 }
6070
6071 pub fn set_css(&mut self, style: &str) {
6100 self.add_component_css(azul_css::css::Css::parse_inline(style));
6108 }
6109
6110 #[must_use] pub fn with_css(mut self, style: &str) -> Self {
6112 self.set_css(style);
6113 self
6114 }
6115
6116 #[inline]
6118 pub fn set_context_menu(&mut self, context_menu: Menu) {
6119 self.root.set_context_menu(context_menu);
6120 }
6121
6122 #[inline]
6123 #[must_use] pub fn with_context_menu(mut self, context_menu: Menu) -> Self {
6124 self.set_context_menu(context_menu);
6125 self
6126 }
6127
6128 #[inline]
6130 pub fn set_menu_bar(&mut self, menu_bar: Menu) {
6131 self.root.set_menu_bar(menu_bar);
6132 }
6133
6134 #[inline]
6135 #[must_use] pub fn with_menu_bar(mut self, menu_bar: Menu) -> Self {
6136 self.set_menu_bar(menu_bar);
6137 self
6138 }
6139
6140 #[inline]
6141 #[must_use] pub fn with_clip_mask(mut self, clip_mask: ImageMask) -> Self {
6142 self.root.set_clip_mask(clip_mask);
6143 self
6144 }
6145
6146 #[inline]
6147 #[must_use] pub fn with_svg_clip_path(mut self, clip: crate::svg::SvgMultiPolygon) -> Self {
6148 self.root.set_svg_data(SvgNodeData::Path(clip));
6149 self
6150 }
6151
6152 #[inline]
6153 #[must_use] pub fn with_svg_data(mut self, data: SvgNodeData) -> Self {
6154 self.root.set_svg_data(data);
6155 self
6156 }
6157
6158 #[inline]
6159 #[must_use] pub fn with_accessibility_info(mut self, accessibility_info: AccessibilityInfo) -> Self {
6160 self.root.set_accessibility_info(accessibility_info);
6161 self
6162 }
6163
6164 pub fn fixup_children_estimated(&mut self) -> usize {
6165 if self.children.is_empty() {
6166 self.estimated_total_children = 0;
6167 } else {
6168 self.estimated_total_children = self
6169 .children
6170 .iter_mut()
6171 .map(|s| s.fixup_children_estimated() + 1)
6172 .sum();
6173 }
6174 self.estimated_total_children
6175 }
6176}
6177
6178impl core::iter::FromIterator<Self> for Dom {
6179 fn from_iter<I: IntoIterator<Item = Self>>(iter: I) -> Self {
6180 let mut estimated_total_children = 0;
6181 let children = iter
6182 .into_iter()
6183 .inspect(|c| {
6184 estimated_total_children += c.estimated_total_children + 1;
6185 })
6186 .collect::<Vec<Self>>();
6187
6188 Self {
6189 root: NodeData::create_div(),
6190 children: children.into(),
6191 css: azul_css::css::CssVec::from_const_slice(&[]),
6192 estimated_total_children,
6193 }
6194 }
6195}
6196
6197impl fmt::Debug for Dom {
6198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6199 fn print_dom(d: &Dom, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6200 write!(f, "Dom {{\r\n")?;
6201 write!(f, "\troot: {:#?}\r\n", d.root)?;
6202 write!(
6203 f,
6204 "\testimated_total_children: {:#?}\r\n",
6205 d.estimated_total_children
6206 )?;
6207 write!(f, "\tchildren: [\r\n")?;
6208 for c in &d.children {
6209 print_dom(c, f)?;
6210 }
6211 write!(f, "\t]\r\n")?;
6212 write!(f, "}}\r\n")?;
6213 Ok(())
6214 }
6215
6216 print_dom(self, f)
6217 }
6218}
6219
6220#[cfg(test)]
6221mod audit_tests {
6222 use super::*;
6223
6224 #[test]
6225 fn node_count_matches_recompute() {
6226 let dom = Dom::create_div()
6228 .with_child(Dom::create_div().with_child(Dom::create_div()))
6229 .with_child(Dom::create_div());
6230 assert_eq!(
6231 dom.estimated_total_children,
6232 dom.recompute_estimated_total_children()
6233 );
6234 assert_eq!(dom.estimated_total_children, 3);
6235 assert_eq!(dom.node_count(), 4);
6236 }
6237
6238 #[test]
6239 fn single_node_dom_node_count() {
6240 let dom = Dom::create_div();
6241 assert_eq!(dom.estimated_total_children, 0);
6242 assert_eq!(dom.node_count(), 1);
6243 assert_eq!(dom.recompute_estimated_total_children(), 0);
6244 }
6245
6246 #[test]
6247 fn fixup_repairs_desynced_estimate() {
6248 let mut dom = Dom::create_div().with_child(Dom::create_div());
6249 dom.estimated_total_children = 999;
6251 let repaired = dom.fixup_children_estimated();
6252 assert_eq!(repaired, 1);
6253 assert_eq!(
6254 dom.estimated_total_children,
6255 dom.recompute_estimated_total_children()
6256 );
6257 }
6258
6259 #[cfg(debug_assertions)]
6261 #[test]
6262 #[should_panic(expected = "desynced")]
6263 fn add_child_with_stale_estimate_panics_in_debug() {
6264 let mut child = Dom::create_div().with_child(Dom::create_div());
6265 child.estimated_total_children = 0; let mut parent = Dom::create_div();
6267 parent.add_child(child);
6268 }
6269
6270 #[test]
6274 fn node_data_is_send() {
6275 fn assert_send<T: Send>() {}
6276 assert_send::<NodeData>();
6277 }
6278
6279 #[test]
6285 fn copy_special_moving_complex_moves_text_node_type() {
6286 let mut nd = NodeData::create_text("hello").with_css("color: red;");
6287 assert!(!nd.style.rules.is_empty(), "precondition: style set");
6288
6289 let copy = nd.copy_special_moving_complex();
6290
6291 match copy.get_node_type() {
6293 NodeType::Text(s) => assert_eq!(s.as_ref().as_str(), "hello"),
6294 other => panic!("expected Text node_type on copy, got {other:?}"),
6295 }
6296 assert!(matches!(nd.get_node_type(), NodeType::Div));
6298 assert!(nd.style.rules.is_empty());
6300 assert!(!copy.style.rules.is_empty());
6301 }
6302
6303 #[test]
6305 fn copy_special_moving_complex_moves_div_node_type() {
6306 let mut nd = NodeData::create_div();
6307 let copy = nd.copy_special_moving_complex();
6308 assert!(matches!(copy.get_node_type(), NodeType::Div));
6309 assert!(matches!(nd.get_node_type(), NodeType::Div));
6310 }
6311}
6312
6313#[cfg(test)]
6314#[allow(clippy::cast_possible_wrap, clippy::too_many_lines)]
6315mod autotest_generated {
6316 use super::*;
6317
6318 fn hash_of<T: Hash>(t: &T) -> u64 {
6323 let mut h = crate::hash::DefaultHasher::new();
6324 t.hash(&mut h);
6325 h.finish()
6326 }
6327
6328 extern "C" fn merge_cb_a(new_data: RefAny, _old: RefAny) -> RefAny {
6329 new_data
6330 }
6331
6332 extern "C" fn merge_cb_b(_new: RefAny, old_data: RefAny) -> RefAny {
6333 old_data
6334 }
6335
6336 extern "C" fn virtual_view_cb(
6339 _data: RefAny,
6340 _info: crate::callbacks::VirtualViewCallbackInfo,
6341 ) -> crate::callbacks::VirtualViewReturn {
6342 unreachable!("virtual view callback is never invoked by these tests")
6343 }
6344
6345 fn virtual_view_callback() -> VirtualViewCallback {
6346 VirtualViewCallback {
6347 cb: virtual_view_cb,
6348 ctx: OptionRefAny::None,
6349 }
6350 }
6351
6352 fn huge_unicode_string() -> String {
6354 "ä🎉本".repeat(25_000)
6355 }
6356
6357 fn all_attribute_variants() -> Vec<AttributeType> {
6359 let nv = || AttributeNameValue {
6360 attr_name: "data-x".into(),
6361 value: "v".into(),
6362 };
6363 vec![
6364 AttributeType::Id("i".into()),
6365 AttributeType::Class("c".into()),
6366 AttributeType::AriaLabel("l".into()),
6367 AttributeType::AriaLabelledBy("lb".into()),
6368 AttributeType::AriaDescribedBy("db".into()),
6369 AttributeType::AriaRole("r".into()),
6370 AttributeType::AriaState(nv()),
6371 AttributeType::AriaProperty(nv()),
6372 AttributeType::Href("h".into()),
6373 AttributeType::Rel("rel".into()),
6374 AttributeType::Target("t".into()),
6375 AttributeType::Src("s".into()),
6376 AttributeType::Alt("a".into()),
6377 AttributeType::Title("ti".into()),
6378 AttributeType::Name("n".into()),
6379 AttributeType::Value("v".into()),
6380 AttributeType::InputType("text".into()),
6381 AttributeType::Placeholder("p".into()),
6382 AttributeType::Required,
6383 AttributeType::Disabled,
6384 AttributeType::Readonly,
6385 AttributeType::CheckedTrue,
6386 AttributeType::CheckedFalse,
6387 AttributeType::Selected,
6388 AttributeType::Max("10".into()),
6389 AttributeType::Min("0".into()),
6390 AttributeType::Step("1".into()),
6391 AttributeType::Pattern(".*".into()),
6392 AttributeType::MinLength(i32::MIN),
6393 AttributeType::MaxLength(i32::MAX),
6394 AttributeType::Autocomplete("off".into()),
6395 AttributeType::Scope("row".into()),
6396 AttributeType::ColSpan(-1),
6397 AttributeType::RowSpan(0),
6398 AttributeType::TabIndex(i32::MIN),
6399 AttributeType::Focusable,
6400 AttributeType::Lang("en".into()),
6401 AttributeType::Dir("rtl".into()),
6402 AttributeType::ContentEditable(true),
6403 AttributeType::Draggable(false),
6404 AttributeType::Hidden,
6405 AttributeType::Data(nv()),
6406 AttributeType::Custom(nv()),
6407 ]
6408 }
6409
6410 fn representative_node_types() -> Vec<NodeType> {
6412 vec![
6413 NodeType::Html,
6414 NodeType::Body,
6415 NodeType::Div,
6416 NodeType::Br,
6417 NodeType::Button,
6418 NodeType::Input,
6419 NodeType::TextArea,
6420 NodeType::Select,
6421 NodeType::A,
6422 NodeType::H1,
6423 NodeType::H6,
6424 NodeType::Table,
6425 NodeType::Td,
6426 NodeType::Svg,
6427 NodeType::SvgPath,
6428 NodeType::SvgText("svg text".into()),
6429 NodeType::SvgImage(ImageRef::null_image(
6430 1,
6431 1,
6432 crate::resources::RawImageFormat::R8,
6433 Vec::new(),
6434 )),
6435 NodeType::Before,
6436 NodeType::After,
6437 NodeType::Marker,
6438 NodeType::Placeholder,
6439 NodeType::Text(BoxOrStatic::heap(AzString::from("hello"))),
6440 NodeType::Image(BoxOrStatic::heap(ImageRef::null_image(
6441 2,
6442 2,
6443 crate::resources::RawImageFormat::RGBA8,
6444 Vec::new(),
6445 ))),
6446 NodeType::VirtualView,
6447 NodeType::Icon(BoxOrStatic::heap(AzString::from("home"))),
6448 NodeType::GeolocationProbe(crate::geolocation::GeolocationProbeConfig::default()),
6449 ]
6450 }
6451
6452 #[test]
6457 fn node_flags_new_is_empty_and_matches_default() {
6458 let f = NodeFlags::new();
6459 assert_eq!(f.inner, 0);
6460 assert_eq!(f, NodeFlags::default());
6461 assert!(!f.is_contenteditable());
6462 assert!(!f.is_anonymous());
6463 assert_eq!(f.get_tab_index(), None);
6464 }
6465
6466 #[test]
6467 fn node_flags_tab_index_round_trips_for_all_variants() {
6468 for ti in [
6469 None,
6470 Some(TabIndex::Auto),
6471 Some(TabIndex::NoKeyboardFocus),
6472 Some(TabIndex::OverrideInParent(0)),
6473 Some(TabIndex::OverrideInParent(1)),
6474 Some(TabIndex::OverrideInParent(1_000)),
6475 ] {
6476 let mut f = NodeFlags::new();
6477 f.set_tab_index(ti);
6478 assert_eq!(f.get_tab_index(), ti, "round-trip failed for {ti:?}");
6479 }
6480 }
6481
6482 #[test]
6483 fn node_flags_tab_index_round_trips_at_the_28_bit_boundary() {
6484 const MAX_EXACT: u32 = (1 << 28) - 1;
6487 let mut f = NodeFlags::new();
6488 f.set_tab_index(Some(TabIndex::OverrideInParent(MAX_EXACT)));
6489 assert_eq!(
6490 f.get_tab_index(),
6491 Some(TabIndex::OverrideInParent(MAX_EXACT))
6492 );
6493 }
6494
6495 #[test]
6496 fn node_flags_tab_index_above_28_bits_truncates_without_corrupting_other_flags() {
6497 const OVERFLOW: u32 = 1 << 28;
6503 let mut f = NodeFlags::new();
6504 f.set_tab_index(Some(TabIndex::OverrideInParent(OVERFLOW)));
6505 assert_eq!(
6506 f.get_tab_index(),
6507 Some(TabIndex::OverrideInParent(0)),
6508 "2^28 truncates to 0 (documented lossiness)"
6509 );
6510 assert!(!f.is_anonymous(), "overflow bit must not set ANONYMOUS");
6511 assert!(!f.is_contenteditable());
6512
6513 let mut f = NodeFlags::new();
6514 f.set_tab_index(Some(TabIndex::OverrideInParent(u32::MAX)));
6515 assert_eq!(
6516 f.get_tab_index(),
6517 Some(TabIndex::OverrideInParent((1 << 28) - 1)),
6518 "u32::MAX truncates to the 28-bit mask"
6519 );
6520 assert!(!f.is_anonymous(), "u32::MAX must not set ANONYMOUS");
6521 assert!(!f.is_contenteditable(), "u32::MAX must not set CONTENTEDITABLE");
6522 }
6523
6524 #[test]
6525 fn node_flags_set_tab_index_preserves_contenteditable_and_anonymous() {
6526 let mut f = NodeFlags::new();
6527 f.set_contenteditable_mut(true);
6528 f.set_anonymous(true);
6529
6530 for ti in [
6531 None,
6532 Some(TabIndex::Auto),
6533 Some(TabIndex::NoKeyboardFocus),
6534 Some(TabIndex::OverrideInParent((1 << 28) - 1)),
6539 Some(TabIndex::OverrideInParent(7)),
6540 ] {
6541 f.set_tab_index(ti);
6542 assert!(f.is_contenteditable(), "contenteditable lost for {ti:?}");
6543 assert!(f.is_anonymous(), "anonymous lost for {ti:?}");
6544 assert_eq!(f.get_tab_index(), ti);
6545 }
6546 }
6547
6548 #[test]
6549 fn node_flags_set_contenteditable_preserves_tab_index_and_anonymous() {
6550 let mut f = NodeFlags::new();
6551 f.set_anonymous(true);
6552 f.set_tab_index(Some(TabIndex::OverrideInParent(12_345)));
6553
6554 f.set_contenteditable_mut(true);
6555 assert!(f.is_contenteditable());
6556 assert!(f.is_anonymous());
6557 assert_eq!(f.get_tab_index(), Some(TabIndex::OverrideInParent(12_345)));
6558
6559 f.set_contenteditable_mut(false);
6560 assert!(!f.is_contenteditable());
6561 assert!(f.is_anonymous());
6562 assert_eq!(f.get_tab_index(), Some(TabIndex::OverrideInParent(12_345)));
6563 }
6564
6565 #[test]
6566 fn node_flags_set_anonymous_preserves_tab_index_and_contenteditable() {
6567 let mut f = NodeFlags::new();
6568 f.set_contenteditable_mut(true);
6569 f.set_tab_index(Some(TabIndex::NoKeyboardFocus));
6570
6571 f.set_anonymous(true);
6572 assert!(f.is_anonymous());
6573 assert!(f.is_contenteditable());
6574 assert_eq!(f.get_tab_index(), Some(TabIndex::NoKeyboardFocus));
6575
6576 f.set_anonymous(false);
6577 assert!(!f.is_anonymous());
6578 assert!(f.is_contenteditable());
6579 assert_eq!(f.get_tab_index(), Some(TabIndex::NoKeyboardFocus));
6580 }
6581
6582 #[test]
6583 fn node_flags_consecutive_set_contenteditable_is_idempotent() {
6584 let mut f = NodeFlags::new();
6585 f.set_contenteditable_mut(true);
6586 let once = f;
6587 f.set_contenteditable_mut(true);
6588 assert_eq!(f, once, "setting twice must not toggle");
6589 }
6590
6591 #[test]
6592 fn node_flags_builder_and_mut_setter_agree() {
6593 for v in [true, false] {
6594 let builder = NodeFlags::new().set_contenteditable(v);
6595 let mut mutated = NodeFlags::new();
6596 mutated.set_contenteditable_mut(v);
6597 assert_eq!(builder, mutated, "builder/mut disagree for {v}");
6598 }
6599 }
6600
6601 #[test]
6602 fn node_flags_all_bits_set_decodes_without_panicking() {
6603 let f = NodeFlags { inner: u32::MAX };
6607 assert!(f.is_contenteditable());
6608 assert!(f.is_anonymous());
6609 assert_eq!(f.get_tab_index(), Some(TabIndex::NoKeyboardFocus));
6611 }
6612
6613 #[test]
6614 fn node_flags_get_tab_index_is_total_over_the_tag_bits() {
6615 for tag in 0u32..4 {
6618 for extra in [0u32, u32::MAX] {
6619 let inner = (tag << 29) | (extra & !(0b11 << 29));
6620 let f = NodeFlags { inner };
6621 let decoded = f.get_tab_index();
6622 match tag {
6623 0 => assert_eq!(decoded, None),
6624 1 => assert_eq!(decoded, Some(TabIndex::Auto)),
6625 2 => assert!(matches!(decoded, Some(TabIndex::OverrideInParent(_)))),
6626 _ => assert_eq!(decoded, Some(TabIndex::NoKeyboardFocus)),
6627 }
6628 }
6629 }
6630 }
6631
6632 #[test]
6637 fn tab_index_default_is_auto_with_index_zero() {
6638 assert_eq!(TabIndex::default(), TabIndex::Auto);
6639 assert_eq!(TabIndex::default().get_index(), 0);
6640 }
6641
6642 #[test]
6643 fn tab_index_get_index_at_numeric_limits() {
6644 assert_eq!(TabIndex::Auto.get_index(), 0);
6645 assert_eq!(TabIndex::NoKeyboardFocus.get_index(), -1);
6646 assert_eq!(TabIndex::OverrideInParent(0).get_index(), 0);
6647 let max = TabIndex::OverrideInParent(u32::MAX).get_index();
6650 assert_eq!(max, u32::MAX as isize);
6651 assert!(max > 0, "u32::MAX must not wrap to a negative isize");
6652 }
6653
6654 #[test]
6655 fn get_effective_tabindex_saturates_into_i32() {
6656 let nd = NodeData::create_div().with_tab_index(TabIndex::OverrideInParent(u32::MAX));
6660 assert_eq!(nd.get_effective_tabindex(), Some((1 << 28) - 1));
6661
6662 assert_eq!(
6663 NodeData::create_div()
6664 .with_tab_index(TabIndex::Auto)
6665 .get_effective_tabindex(),
6666 Some(0)
6667 );
6668 assert_eq!(
6669 NodeData::create_div()
6670 .with_tab_index(TabIndex::NoKeyboardFocus)
6671 .get_effective_tabindex(),
6672 Some(-1)
6673 );
6674 assert_eq!(NodeData::create_div().get_effective_tabindex(), None);
6675 }
6676
6677 #[test]
6678 fn get_effective_tabindex_falls_back_to_zero_for_focus_callbacks() {
6679 let nd = NodeData::create_div().with_callback(
6680 EventFilter::Focus(FocusEventFilter::MouseDown),
6681 RefAny::new(0u32),
6682 0usize,
6683 );
6684 assert_eq!(nd.get_effective_tabindex(), Some(0));
6685 }
6686
6687 #[test]
6692 fn tag_id_unique_never_returns_zero_and_never_repeats() {
6693 let ids: Vec<TagId> = (0..512).map(|_| TagId::unique()).collect();
6696 for id in &ids {
6697 assert_ne!(id.inner, 0, "TagId 0 is reserved for 'no tag'");
6698 }
6699 let mut sorted: Vec<u64> = ids.iter().map(|t| t.inner).collect();
6700 sorted.sort_unstable();
6701 sorted.dedup();
6702 assert_eq!(sorted.len(), 512, "TagId::unique() handed out a duplicate");
6703 }
6704
6705 #[test]
6706 fn tag_id_crate_internal_conversions_are_identity_at_limits() {
6707 for inner in [0u64, 1, u64::MAX, u64::MAX - 1] {
6708 let t = TagId { inner };
6709 assert_eq!(t.into_crate_internal(), t);
6710 assert_eq!(TagId::from_crate_internal(t), t);
6711 assert_eq!(
6713 TagId::from_crate_internal(t.into_crate_internal()).inner,
6714 inner
6715 );
6716 }
6717 }
6718
6719 #[test]
6720 fn tag_id_display_is_non_empty_at_numeric_limits() {
6721 for inner in [0u64, 1, u64::MAX] {
6722 let s = format!("{}", TagId { inner });
6723 assert!(!s.is_empty());
6724 assert!(s.contains(&inner.to_string()), "{s} should contain {inner}");
6725 }
6726 }
6727
6728 #[test]
6729 fn scroll_tag_id_unique_is_distinct_and_debug_matches_display() {
6730 let a = ScrollTagId::unique();
6731 let b = ScrollTagId::unique();
6732 assert_ne!(a, b);
6733 assert_ne!(a.inner.inner, 0);
6734
6735 let s = ScrollTagId {
6736 inner: TagId { inner: u64::MAX },
6737 };
6738 assert_eq!(format!("{s:?}"), format!("{s}"));
6739 assert!(!format!("{s}").is_empty());
6740 }
6741
6742 #[test]
6747 fn attribute_boolean_attrs_always_have_an_empty_value() {
6748 for attr in all_attribute_variants() {
6751 if attr.is_boolean() {
6752 assert_eq!(
6753 attr.value().as_str(),
6754 "",
6755 "boolean attr {} must have an empty value",
6756 attr.name()
6757 );
6758 }
6759 }
6760 }
6761
6762 #[test]
6763 fn attribute_name_and_value_never_panic_for_any_variant() {
6764 for attr in all_attribute_variants() {
6765 let name = attr.name();
6766 let value = attr.value();
6767 assert!(!name.is_empty(), "empty name for {attr:?}");
6770 let _ = value.as_str();
6771 }
6772 }
6773
6774 #[test]
6775 fn attribute_custom_with_empty_name_returns_empty_name_without_panicking() {
6776 let attr = AttributeType::Custom(AttributeNameValue {
6777 attr_name: "".into(),
6778 value: "".into(),
6779 });
6780 assert_eq!(attr.name(), "");
6781 assert_eq!(attr.value().as_str(), "");
6782 assert!(!attr.is_boolean());
6783 }
6784
6785 #[test]
6786 fn attribute_as_id_and_as_class_are_mutually_exclusive() {
6787 for attr in all_attribute_variants() {
6788 match &attr {
6789 AttributeType::Id(s) => {
6790 assert_eq!(attr.as_id(), Some(s.as_str()));
6791 assert_eq!(attr.as_class(), None);
6792 }
6793 AttributeType::Class(s) => {
6794 assert_eq!(attr.as_class(), Some(s.as_str()));
6795 assert_eq!(attr.as_id(), None);
6796 }
6797 _ => {
6798 assert_eq!(attr.as_id(), None, "as_id must be None for {attr:?}");
6799 assert_eq!(attr.as_class(), None, "as_class must be None for {attr:?}");
6800 }
6801 }
6802 }
6803 }
6804
6805 #[test]
6806 fn attribute_numeric_values_serialize_at_i32_limits() {
6807 assert_eq!(
6808 AttributeType::MinLength(i32::MIN).value().as_str(),
6809 "-2147483648"
6810 );
6811 assert_eq!(
6812 AttributeType::MaxLength(i32::MAX).value().as_str(),
6813 "2147483647"
6814 );
6815 assert_eq!(AttributeType::ColSpan(0).value().as_str(), "0");
6816 assert_eq!(AttributeType::RowSpan(-1).value().as_str(), "-1");
6817 assert_eq!(
6818 AttributeType::TabIndex(i32::MIN).value().as_str(),
6819 "-2147483648"
6820 );
6821 }
6822
6823 #[test]
6824 fn attribute_focusable_is_tabindex_zero_and_not_boolean() {
6825 let f = AttributeType::Focusable;
6828 assert_eq!(f.name(), "tabindex");
6829 assert_eq!(f.value().as_str(), "0");
6830 assert!(!f.is_boolean());
6831 assert_eq!(AttributeType::TabIndex(0).name(), "tabindex");
6832 }
6833
6834 #[test]
6835 fn attribute_checked_true_and_false_are_both_boolean_and_share_a_name() {
6836 assert!(AttributeType::CheckedTrue.is_boolean());
6840 assert!(AttributeType::CheckedFalse.is_boolean());
6841 assert_eq!(AttributeType::CheckedTrue.name(), "checked");
6842 assert_eq!(AttributeType::CheckedFalse.name(), "checked");
6843 assert_eq!(AttributeType::CheckedFalse.value().as_str(), "");
6844 assert_ne!(AttributeType::CheckedTrue, AttributeType::CheckedFalse);
6846 }
6847
6848 #[test]
6849 fn attribute_content_editable_and_draggable_stringify_bools() {
6850 assert_eq!(AttributeType::ContentEditable(true).value().as_str(), "true");
6851 assert_eq!(
6852 AttributeType::ContentEditable(false).value().as_str(),
6853 "false"
6854 );
6855 assert_eq!(AttributeType::Draggable(true).value().as_str(), "true");
6856 assert_eq!(AttributeType::Draggable(false).value().as_str(), "false");
6857 assert!(!AttributeType::ContentEditable(false).is_boolean());
6858 }
6859
6860 #[test]
6861 fn attribute_round_trips_huge_unicode_values() {
6862 let big = huge_unicode_string();
6863 let attr = AttributeType::Value(big.clone().into());
6864 assert_eq!(attr.value().as_str(), big.as_str());
6865 assert_eq!(attr.name(), "value");
6866
6867 let id = AttributeType::Id(big.clone().into());
6868 assert_eq!(id.as_id(), Some(big.as_str()));
6869 }
6870
6871 #[test]
6872 fn id_or_class_accessors_are_mutually_exclusive() {
6873 let id = IdOrClass::Id("my-id".into());
6874 let class = IdOrClass::Class("my-class".into());
6875 assert_eq!(id.as_id(), Some("my-id"));
6876 assert_eq!(id.as_class(), None);
6877 assert_eq!(class.as_class(), Some("my-class"));
6878 assert_eq!(class.as_id(), None);
6879
6880 assert_eq!(IdOrClass::Id("".into()).as_id(), Some(""));
6882 assert_eq!(IdOrClass::Class("".into()).as_class(), Some(""));
6883 }
6884
6885 #[test]
6890 fn input_type_as_str_is_non_empty_and_unique_per_variant() {
6891 let all = [
6892 InputType::Text,
6893 InputType::Button,
6894 InputType::Checkbox,
6895 InputType::Color,
6896 InputType::Date,
6897 InputType::Datetime,
6898 InputType::DatetimeLocal,
6899 InputType::Email,
6900 InputType::File,
6901 InputType::Hidden,
6902 InputType::Image,
6903 InputType::Month,
6904 InputType::Number,
6905 InputType::Password,
6906 InputType::Radio,
6907 InputType::Range,
6908 InputType::Reset,
6909 InputType::Search,
6910 InputType::Submit,
6911 InputType::Tel,
6912 InputType::Time,
6913 InputType::Url,
6914 InputType::Week,
6915 ];
6916 let mut seen: Vec<&str> = all.iter().map(InputType::as_str).collect();
6917 for s in &seen {
6918 assert!(!s.is_empty());
6919 assert!(
6920 !s.contains(char::is_whitespace),
6921 "{s} is not a valid HTML attribute value"
6922 );
6923 }
6924 let len = seen.len();
6925 seen.sort_unstable();
6926 seen.dedup();
6927 assert_eq!(seen.len(), len, "two InputType variants share an as_str()");
6928
6929 assert_eq!(InputType::DatetimeLocal.as_str(), "datetime-local");
6930 assert_eq!(InputType::Text.as_str(), "text");
6931 }
6932
6933 #[test]
6938 fn node_type_to_library_owned_round_trips_every_variant() {
6939 for nt in representative_node_types() {
6942 let owned = nt.to_library_owned_nodetype();
6943 assert_eq!(owned, nt, "to_library_owned_nodetype lost data for {nt:?}");
6944 assert_eq!(owned.get_path(), nt.get_path());
6945 }
6946 }
6947
6948 #[test]
6949 fn node_type_get_path_and_format_never_panic() {
6950 for nt in representative_node_types() {
6951 let _tag = nt.get_path();
6952 let _fmt = nt.format();
6953 let _semantic = nt.is_semantic_for_accessibility();
6954 }
6955 }
6956
6957 #[test]
6958 fn node_type_format_returns_content_only_for_content_variants() {
6959 assert_eq!(NodeType::Div.format(), None);
6960 assert_eq!(NodeType::Br.format(), None);
6961 assert_eq!(NodeType::Button.format(), None);
6962
6963 assert_eq!(
6964 NodeType::Text(BoxOrStatic::heap(AzString::from("hi"))).format(),
6965 Some("hi".to_string())
6966 );
6967 assert_eq!(
6968 NodeType::VirtualView.format(),
6969 Some("virtualized-view".to_string())
6970 );
6971 assert_eq!(
6972 NodeType::Icon(BoxOrStatic::heap(AzString::from("home"))).format(),
6973 Some("icon(home)".to_string())
6974 );
6975 }
6976
6977 #[test]
6978 fn node_type_format_handles_empty_and_unicode_text() {
6979 assert_eq!(
6980 NodeType::Text(BoxOrStatic::heap(AzString::from(""))).format(),
6981 Some(String::new())
6982 );
6983 let unicode = "日本語 🎉 ünïcødé";
6984 assert_eq!(
6985 NodeType::Text(BoxOrStatic::heap(AzString::from(unicode))).format(),
6986 Some(unicode.to_string())
6987 );
6988 }
6989
6990 #[test]
6991 fn node_type_format_of_geolocation_probe_survives_nan_and_infinity() {
6992 for max_accuracy_m in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY, -0.0, f32::MAX] {
6995 let cfg = crate::geolocation::GeolocationProbeConfig {
6996 high_accuracy: true,
6997 background: true,
6998 max_accuracy_m,
6999 min_interval_ms: u32::MAX,
7000 };
7001 let out = NodeType::GeolocationProbe(cfg)
7002 .format()
7003 .expect("GeolocationProbe always formats");
7004 assert!(out.starts_with("geolocation-probe("));
7005 assert!(out.contains("4294967295"), "min_interval_ms must be printed");
7006 }
7007 }
7008
7009 #[test]
7010 fn geolocation_probe_nan_is_self_equal_and_hash_consistent() {
7011 let cfg = crate::geolocation::GeolocationProbeConfig {
7014 max_accuracy_m: f32::NAN,
7015 ..Default::default()
7016 };
7017 let a = NodeType::GeolocationProbe(cfg);
7018 let b = NodeType::GeolocationProbe(cfg);
7019 assert_eq!(a, b, "bitwise-NaN configs must compare equal");
7020 assert_eq!(
7021 hash_of(&a),
7022 hash_of(&b),
7023 "Eq == true but hashes differ: violates the Hash/Eq contract"
7024 );
7025 assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
7026 }
7027
7028 #[test]
7029 fn node_type_is_semantic_for_accessibility_known_true_and_false() {
7030 for nt in [
7031 NodeType::Button,
7032 NodeType::Input,
7033 NodeType::TextArea,
7034 NodeType::Select,
7035 NodeType::A,
7036 NodeType::H1,
7037 NodeType::H6,
7038 NodeType::Article,
7039 NodeType::Nav,
7040 NodeType::Main,
7041 ] {
7042 assert!(
7043 nt.is_semantic_for_accessibility(),
7044 "{nt:?} should be semantic"
7045 );
7046 }
7047 for nt in [
7048 NodeType::Div,
7049 NodeType::Span,
7050 NodeType::Br,
7051 NodeType::VirtualView,
7052 NodeType::Text(BoxOrStatic::heap(AzString::from("x"))),
7053 ] {
7054 assert!(
7055 !nt.is_semantic_for_accessibility(),
7056 "{nt:?} should not be semantic"
7057 );
7058 }
7059 }
7060
7061 #[test]
7062 fn node_type_text_variants_are_content_sensitive() {
7063 let a = NodeType::Text(BoxOrStatic::heap(AzString::from("a")));
7064 let b = NodeType::Text(BoxOrStatic::heap(AzString::from("b")));
7065 assert_ne!(a, b);
7066 assert_eq!(a.get_path(), b.get_path(), "same tag, different content");
7067 }
7068
7069 #[test]
7074 fn node_data_default_has_no_attributes_and_no_extra_state() {
7075 let nd = NodeData::default();
7076 assert!(nd.is_node_type(NodeType::Div));
7077 assert!(nd.attributes().as_ref().is_empty());
7078 assert!(nd.get_ids_and_classes().as_ref().is_empty());
7079 assert!(nd.get_dataset().is_none());
7080 assert!(nd.get_key().is_none());
7081 assert!(nd.get_menu_bar().is_none());
7082 assert!(nd.get_context_menu().is_none());
7083 assert!(nd.get_svg_data().is_none());
7084 assert!(nd.get_image_clip_mask().is_none());
7085 assert!(nd.get_accessibility_info().is_none());
7086 assert!(nd.get_merge_callback().is_none());
7087 assert!(nd.get_component_origin().is_none());
7088 assert!(!nd.has_context_menu());
7089 assert!(!nd.is_contenteditable());
7090 assert!(!nd.is_anonymous());
7091 assert_eq!(nd.get_tab_index(), None);
7092 }
7093
7094 #[test]
7095 fn attributes_mut_lazily_allocates_but_stays_empty() {
7096 let mut nd = NodeData::create_div();
7097 assert!(nd.attributes().as_ref().is_empty());
7098 let _ = nd.attributes_mut(); assert!(
7100 nd.attributes().as_ref().is_empty(),
7101 "lazy alloc must not invent attributes"
7102 );
7103 nd.add_id("x".into());
7104 assert_eq!(nd.attributes().as_ref().len(), 1);
7105 }
7106
7107 #[test]
7108 fn has_id_and_has_class_match_exactly_not_by_prefix() {
7109 let mut nd = NodeData::create_div();
7110 nd.add_id("header".into());
7111 nd.add_class("btn".into());
7112
7113 assert!(nd.has_id("header"));
7114 assert!(nd.has_class("btn"));
7115 assert!(!nd.has_id("head"));
7117 assert!(!nd.has_id("header2"));
7118 assert!(!nd.has_class("bt"));
7119 assert!(!nd.has_class(""));
7120 assert!(!nd.has_class("header"));
7122 assert!(!nd.has_id("btn"));
7123 }
7124
7125 #[test]
7126 fn has_id_matches_the_empty_string_id() {
7127 let mut nd = NodeData::create_div();
7128 assert!(!nd.has_id(""), "no ids at all => empty id must not match");
7129 nd.add_id("".into());
7130 assert!(nd.has_id(""), "an explicitly-added empty id must match");
7131 assert!(!nd.has_id("x"));
7132 }
7133
7134 #[test]
7135 fn has_id_and_has_class_handle_unicode_and_huge_strings() {
7136 let unicode = "日本語-🎉-ünïcødé";
7137 let big = huge_unicode_string();
7138
7139 let mut nd = NodeData::create_div();
7140 nd.add_id(unicode.into());
7141 nd.add_class(big.clone().into());
7142
7143 assert!(nd.has_id(unicode));
7144 assert!(nd.has_class(big.as_str()));
7145 assert!(!nd.has_id("日本語"));
7147 }
7148
7149 #[test]
7150 fn duplicate_ids_are_kept_and_still_match() {
7151 let mut nd = NodeData::create_div();
7152 nd.add_id("dup".into());
7153 nd.add_id("dup".into());
7154 assert!(nd.has_id("dup"));
7155 assert_eq!(
7156 nd.get_ids_and_classes().as_ref().len(),
7157 2,
7158 "add_id does not deduplicate"
7159 );
7160 }
7161
7162 #[test]
7163 fn get_ids_and_classes_preserves_insertion_order_and_kind() {
7164 let mut nd = NodeData::create_div();
7165 nd.add_id("i1".into());
7166 nd.add_class("c1".into());
7167 nd.add_id("i2".into());
7168
7169 let v = nd.get_ids_and_classes();
7170 let v = v.as_ref();
7171 assert_eq!(v.len(), 3);
7172 assert_eq!(v[0], IdOrClass::Id("i1".into()));
7173 assert_eq!(v[1], IdOrClass::Class("c1".into()));
7174 assert_eq!(v[2], IdOrClass::Id("i2".into()));
7175 }
7176
7177 #[test]
7178 fn get_ids_and_classes_ignores_non_id_class_attributes() {
7179 let mut nd = NodeData::create_div();
7180 nd.set_attributes(
7181 vec![
7182 AttributeType::Href("/x".into()),
7183 AttributeType::Id("i".into()),
7184 AttributeType::Disabled,
7185 AttributeType::Class("c".into()),
7186 ]
7187 .into(),
7188 );
7189 let v = nd.get_ids_and_classes();
7190 assert_eq!(v.as_ref().len(), 2);
7191 }
7192
7193 #[test]
7194 fn set_ids_and_classes_replaces_ids_but_preserves_other_attributes() {
7195 let mut nd = NodeData::create_div();
7198 nd.set_attributes(
7199 vec![
7200 AttributeType::Href("/old".into()),
7201 AttributeType::Id("old-id".into()),
7202 AttributeType::Class("old-class".into()),
7203 AttributeType::Disabled,
7204 ]
7205 .into(),
7206 );
7207
7208 nd.set_ids_and_classes(vec![IdOrClass::Class("new-class".into())].into());
7209
7210 assert!(!nd.has_id("old-id"), "old id must be dropped");
7211 assert!(!nd.has_class("old-class"), "old class must be dropped");
7212 assert!(nd.has_class("new-class"));
7213 let attrs = nd.attributes().as_ref();
7215 assert!(attrs.contains(&AttributeType::Href("/old".into())));
7216 assert!(attrs.contains(&AttributeType::Disabled));
7217 assert_eq!(attrs.len(), 3);
7218 }
7219
7220 #[test]
7221 fn set_ids_and_classes_with_an_empty_vec_clears_all_ids_and_classes() {
7222 let mut nd = NodeData::create_div();
7223 nd.add_id("i".into());
7224 nd.add_class("c".into());
7225 nd.set_ids_and_classes(Vec::new().into());
7226 assert!(nd.get_ids_and_classes().as_ref().is_empty());
7227 assert!(!nd.has_id("i"));
7228 assert!(!nd.has_class("c"));
7229 }
7230
7231 #[test]
7232 fn set_ids_and_classes_is_idempotent_when_reapplied() {
7233 let mut nd = NodeData::create_div();
7234 let ids: IdOrClassVec = vec![
7235 IdOrClass::Id("i".into()),
7236 IdOrClass::Class("c".into()),
7237 ]
7238 .into();
7239 nd.set_ids_and_classes(ids.clone());
7240 let after_first = nd.attributes().clone();
7241 nd.set_ids_and_classes(ids);
7242 assert_eq!(
7243 nd.attributes().as_ref(),
7244 after_first.as_ref(),
7245 "re-applying the same ids/classes must not duplicate them"
7246 );
7247 }
7248
7249 #[test]
7250 fn with_attribute_appends_without_dropping_existing_attributes() {
7251 let nd = NodeData::create_div()
7254 .with_attribute(AttributeType::Href("/a".into()))
7255 .with_attribute(AttributeType::Alt("alt".into()));
7256 let attrs = nd.attributes().as_ref();
7257 assert_eq!(attrs.len(), 2);
7258 assert_eq!(attrs[0], AttributeType::Href("/a".into()));
7259 assert_eq!(attrs[1], AttributeType::Alt("alt".into()));
7260 }
7261
7262 #[test]
7267 fn create_node_shorthands_produce_the_right_node_type() {
7268 assert!(NodeData::create_body().is_node_type(NodeType::Body));
7269 assert!(NodeData::create_div().is_node_type(NodeType::Div));
7270 assert!(NodeData::create_br().is_node_type(NodeType::Br));
7271 assert!(NodeData::create_button_no_a11y().is_node_type(NodeType::Button));
7272 assert!(NodeData::create_table_no_a11y().is_node_type(NodeType::Table));
7273 }
7274
7275 #[test]
7276 fn create_text_accepts_empty_unicode_and_huge_input() {
7277 for s in ["", "x", "日本語 🎉"] {
7278 let nd = NodeData::create_text(s);
7279 assert!(nd.is_text_node());
7280 assert_eq!(nd.get_node_type().format(), Some(s.to_string()));
7281 }
7282 let big = huge_unicode_string();
7283 let nd = NodeData::create_text(big.clone());
7284 assert!(nd.is_text_node());
7285 assert_eq!(nd.get_node_type().format(), Some(big));
7286 }
7287
7288 #[test]
7289 fn create_a_stores_href_and_accessibility_name() {
7290 let nd = NodeData::create_a("/home".into(), SmallAriaInfo::label("Home"));
7291 assert!(nd.is_node_type(NodeType::A));
7292 assert!(nd
7293 .attributes()
7294 .as_ref()
7295 .contains(&AttributeType::Href("/home".into())));
7296 let info = nd
7297 .get_accessibility_info()
7298 .expect("create_a must set accessibility info");
7299 assert_eq!(info.accessibility_name, OptionString::Some("Home".into()));
7300 }
7301
7302 #[test]
7303 fn create_a_no_a11y_has_href_but_no_accessibility_info() {
7304 let nd = NodeData::create_a_no_a11y("/x".into());
7305 assert!(nd
7306 .attributes()
7307 .as_ref()
7308 .contains(&AttributeType::Href("/x".into())));
7309 assert!(nd.get_accessibility_info().is_none());
7310 }
7311
7312 #[test]
7313 fn create_a_accepts_an_empty_href() {
7314 let nd = NodeData::create_a_no_a11y("".into());
7315 assert_eq!(
7316 nd.attributes().as_ref()[0],
7317 AttributeType::Href("".into()),
7318 "empty href is stored verbatim, not dropped"
7319 );
7320 }
7321
7322 #[test]
7323 fn create_input_stores_all_three_attributes_in_order() {
7324 let nd = NodeData::create_input_no_a11y("text".into(), "user".into(), "Username".into());
7325 assert!(nd.is_node_type(NodeType::Input));
7326 let attrs = nd.attributes().as_ref();
7327 assert_eq!(attrs.len(), 3);
7328 assert_eq!(attrs[0], AttributeType::InputType("text".into()));
7329 assert_eq!(attrs[1], AttributeType::Name("user".into()));
7330 assert_eq!(attrs[2], AttributeType::AriaLabel("Username".into()));
7331 }
7332
7333 #[test]
7334 fn create_input_with_a11y_sets_both_attributes_and_accessibility_info() {
7335 let nd = NodeData::create_input(
7336 "password".into(),
7337 "pw".into(),
7338 "Password".into(),
7339 SmallAriaInfo::label("Password").with_role(AccessibilityRole::Text),
7340 );
7341 assert_eq!(nd.attributes().as_ref().len(), 3);
7342 let info = nd.get_accessibility_info().expect("a11y info");
7343 assert_eq!(info.role, AccessibilityRole::Text);
7344 }
7345
7346 #[test]
7347 fn create_textarea_and_select_store_name_and_label() {
7348 let ta = NodeData::create_textarea_no_a11y("body".into(), "Body".into());
7349 assert!(ta.is_node_type(NodeType::TextArea));
7350 assert_eq!(ta.attributes().as_ref().len(), 2);
7351
7352 let sel = NodeData::create_select_no_a11y("country".into(), "Country".into());
7353 assert!(sel.is_node_type(NodeType::Select));
7354 assert_eq!(
7355 sel.attributes().as_ref()[0],
7356 AttributeType::Name("country".into())
7357 );
7358 }
7359
7360 #[test]
7361 fn create_label_uses_a_custom_for_attribute() {
7362 let nd = NodeData::create_label_no_a11y("email-input".into());
7363 assert!(nd.is_node_type(NodeType::Label));
7364 assert_eq!(
7365 nd.attributes().as_ref()[0],
7366 AttributeType::Custom(AttributeNameValue {
7367 attr_name: "for".into(),
7368 value: "email-input".into(),
7369 })
7370 );
7371 assert_eq!(nd.attributes().as_ref()[0].name(), "for");
7372 assert_eq!(nd.attributes().as_ref()[0].value().as_str(), "email-input");
7373 }
7374
7375 #[test]
7376 fn create_button_and_table_with_aria_set_accessibility_info() {
7377 let btn = NodeData::create_button(
7378 SmallAriaInfo::label("Save").with_role(AccessibilityRole::PushButton),
7379 );
7380 let info = btn.get_accessibility_info().expect("a11y info");
7381 assert_eq!(info.role, AccessibilityRole::PushButton);
7382 assert_eq!(info.accessibility_name, OptionString::Some("Save".into()));
7383
7384 let table = NodeData::create_table(SmallAriaInfo::label("Results"));
7385 assert!(table.is_node_type(NodeType::Table));
7386 assert!(table.get_accessibility_info().is_some());
7387 }
7388
7389 #[test]
7390 fn a11y_constructors_accept_empty_aria_labels() {
7391 let btn = NodeData::create_button(SmallAriaInfo::label(""));
7392 let info = btn.get_accessibility_info().expect("a11y info");
7393 assert_eq!(info.accessibility_name, OptionString::Some("".into()));
7394 assert_eq!(info.role, AccessibilityRole::Unknown);
7396 }
7397
7398 #[test]
7399 fn create_image_and_is_node_type_round_trip() {
7400 let img = ImageRef::null_image(4, 4, crate::resources::RawImageFormat::RGBA8, Vec::new());
7401 let nd = NodeData::create_image(img.clone());
7402 assert!(!nd.is_text_node());
7403 assert_eq!(nd.get_node_type().get_path(), NodeTypeTag::Img);
7404 assert!(nd.is_node_type(NodeType::Image(BoxOrStatic::heap(img))));
7405 }
7406
7407 #[test]
7412 fn is_node_type_is_content_sensitive_for_text() {
7413 let nd = NodeData::create_text("a");
7414 assert!(nd.is_node_type(NodeType::Text(BoxOrStatic::heap(AzString::from("a")))));
7415 assert!(
7416 !nd.is_node_type(NodeType::Text(BoxOrStatic::heap(AzString::from("b")))),
7417 "is_node_type compares payloads, not just the discriminant"
7418 );
7419 assert!(!nd.is_node_type(NodeType::Div));
7420 }
7421
7422 #[test]
7423 fn is_text_node_and_is_virtual_view_node() {
7424 assert!(NodeData::create_text("x").is_text_node());
7425 assert!(!NodeData::create_div().is_text_node());
7426
7427 let vv = NodeData::create_virtual_view(RefAny::new(1u32), virtual_view_callback());
7428 assert!(vv.is_virtual_view_node());
7429 assert!(!vv.is_text_node());
7430 assert!(vv.get_virtual_view_node_ref().is_some());
7431 assert!(!NodeData::create_div().is_virtual_view_node());
7432 assert!(NodeData::create_div().get_virtual_view_node_ref().is_none());
7433 }
7434
7435 #[test]
7436 fn has_context_menu_flips_only_after_set_context_menu() {
7437 let mut nd = NodeData::create_div();
7438 assert!(!nd.has_context_menu());
7439 nd.set_menu_bar(Menu::create(Vec::new().into()));
7441 assert!(
7442 !nd.has_context_menu(),
7443 "menu_bar must not satisfy has_context_menu"
7444 );
7445 assert!(nd.get_menu_bar().is_some());
7446
7447 nd.set_context_menu(Menu::create(Vec::new().into()));
7448 assert!(nd.has_context_menu());
7449 assert!(nd.get_context_menu().is_some());
7450 }
7451
7452 #[test]
7453 fn with_menu_bar_and_with_context_menu_are_independent_slots() {
7454 let nd = NodeData::create_div()
7455 .with_menu_bar(Menu::create(Vec::new().into()))
7456 .with_context_menu(Menu::create(Vec::new().into()));
7457 assert!(nd.get_menu_bar().is_some());
7458 assert!(nd.get_context_menu().is_some());
7459 assert!(nd.has_context_menu());
7460 }
7461
7462 #[test]
7463 fn is_focusable_for_naturally_focusable_and_opted_in_nodes() {
7464 for nt in [
7465 NodeType::A,
7466 NodeType::Button,
7467 NodeType::Input,
7468 NodeType::Select,
7469 NodeType::TextArea,
7470 ] {
7471 assert!(
7472 NodeData::create_node(nt.clone()).is_focusable(),
7473 "{nt:?} is naturally focusable"
7474 );
7475 }
7476 assert!(!NodeData::create_div().is_focusable());
7477 assert!(NodeData::create_div()
7478 .with_contenteditable(true)
7479 .is_focusable());
7480 assert!(NodeData::create_div()
7481 .with_tab_index(TabIndex::NoKeyboardFocus)
7482 .is_focusable());
7483 assert!(NodeData::create_div()
7484 .with_callback(
7485 EventFilter::Focus(FocusEventFilter::MouseDown),
7486 RefAny::new(0u32),
7487 0usize,
7488 )
7489 .is_focusable());
7490 assert!(!NodeData::create_div()
7492 .with_callback(
7493 EventFilter::Hover(HoverEventFilter::MouseOver),
7494 RefAny::new(0u32),
7495 0usize,
7496 )
7497 .is_focusable());
7498 }
7499
7500 #[test]
7501 fn has_activation_behavior_for_elements_callbacks_and_roles() {
7502 assert!(NodeData::create_node(NodeType::A).has_activation_behavior());
7503 assert!(NodeData::create_button_no_a11y().has_activation_behavior());
7504 assert!(!NodeData::create_div().has_activation_behavior());
7505
7506 for f in [HoverEventFilter::MouseUp, HoverEventFilter::LeftMouseUp] {
7507 assert!(NodeData::create_div()
7508 .with_callback(EventFilter::Hover(f), RefAny::new(0u32), 0usize)
7509 .has_activation_behavior());
7510 }
7511 assert!(!NodeData::create_div()
7513 .with_callback(
7514 EventFilter::Hover(HoverEventFilter::MouseDown),
7515 RefAny::new(0u32),
7516 0usize,
7517 )
7518 .has_activation_behavior());
7519
7520 let mut nd = NodeData::create_div();
7521 nd.set_accessibility_info(
7522 SmallAriaInfo::label("x")
7523 .with_role(AccessibilityRole::PushButton)
7524 .to_full_info(),
7525 );
7526 assert!(nd.has_activation_behavior(), "role=PushButton activates");
7527 }
7528
7529 #[test]
7530 fn is_activatable_is_false_for_unavailable_elements() {
7531 let mut nd = NodeData::create_button_no_a11y();
7532 assert!(nd.is_activatable());
7533
7534 let mut info = SmallAriaInfo::label("Save")
7535 .with_role(AccessibilityRole::PushButton)
7536 .to_full_info();
7537 info.states = vec![AccessibilityState::Unavailable].into();
7538 nd.set_accessibility_info(info);
7539
7540 assert!(nd.has_activation_behavior());
7541 assert!(
7542 !nd.is_activatable(),
7543 "an Unavailable (disabled) button must not be activatable"
7544 );
7545
7546 assert!(!NodeData::create_div().is_activatable());
7548 }
7549
7550 #[test]
7555 fn get_accessible_label_prefers_aria_label_over_alt_and_title() {
7556 let mut nd = NodeData::create_div();
7557 nd.set_attributes(
7558 vec![
7559 AttributeType::Title("title".into()),
7560 AttributeType::Alt("alt".into()),
7561 AttributeType::AriaLabel("aria".into()),
7562 ]
7563 .into(),
7564 );
7565 assert_eq!(
7566 nd.get_accessible_label(),
7567 Some("aria"),
7568 "aria-label wins regardless of attribute order"
7569 );
7570 }
7571
7572 #[test]
7573 fn get_accessible_label_alt_vs_title_is_order_dependent() {
7574 let mut title_first = NodeData::create_div();
7580 title_first.set_attributes(
7581 vec![
7582 AttributeType::Title("title".into()),
7583 AttributeType::Alt("alt".into()),
7584 ]
7585 .into(),
7586 );
7587 assert_eq!(title_first.get_accessible_label(), Some("title"));
7588
7589 let mut alt_first = NodeData::create_div();
7590 alt_first.set_attributes(
7591 vec![
7592 AttributeType::Alt("alt".into()),
7593 AttributeType::Title("title".into()),
7594 ]
7595 .into(),
7596 );
7597 assert_eq!(alt_first.get_accessible_label(), Some("alt"));
7598 }
7599
7600 #[test]
7601 fn get_accessible_label_value_and_placeholder_default_to_none() {
7602 let nd = NodeData::create_div();
7603 assert_eq!(nd.get_accessible_label(), None);
7604 assert_eq!(nd.get_accessible_value(), None);
7605 assert_eq!(nd.get_placeholder(), None);
7606 }
7607
7608 #[test]
7609 fn get_accessible_value_and_placeholder_return_the_first_match() {
7610 let mut nd = NodeData::create_div();
7611 nd.set_attributes(
7612 vec![
7613 AttributeType::Value("first".into()),
7614 AttributeType::Value("second".into()),
7615 AttributeType::Placeholder("ph".into()),
7616 ]
7617 .into(),
7618 );
7619 assert_eq!(nd.get_accessible_value(), Some("first"));
7620 assert_eq!(nd.get_placeholder(), Some("ph"));
7621 }
7622
7623 #[test]
7624 fn get_accessible_label_returns_empty_string_not_none_for_empty_aria_label() {
7625 let mut nd = NodeData::create_div();
7627 nd.set_attributes(vec![AttributeType::AriaLabel("".into())].into());
7628 assert_eq!(nd.get_accessible_label(), Some(""));
7629 }
7630
7631 #[test]
7636 fn dataset_set_get_take_round_trip() {
7637 let mut nd = NodeData::create_div();
7638 assert!(nd.get_dataset().is_none());
7639 assert!(nd.take_dataset().is_none(), "take on empty must be None");
7640
7641 nd.set_dataset(OptionRefAny::Some(RefAny::new(42u32)));
7642 assert!(nd.get_dataset().is_some());
7643 assert!(nd.get_dataset_mut().is_some());
7644
7645 let mut taken = nd.take_dataset().expect("dataset was set");
7646 assert_eq!(taken.downcast_ref::<u32>().map(|r| *r), Some(42));
7647 assert!(nd.get_dataset().is_none(), "take must clear the slot");
7648 assert!(nd.take_dataset().is_none(), "double-take must be None");
7649 }
7650
7651 #[test]
7652 fn set_dataset_none_clears_without_allocating_extra() {
7653 let mut nd = NodeData::create_div();
7654 nd.set_dataset(OptionRefAny::None);
7656 assert!(nd.get_dataset().is_none());
7657
7658 nd.set_dataset(OptionRefAny::Some(RefAny::new(1u8)));
7659 nd.set_dataset(OptionRefAny::None);
7660 assert!(nd.get_dataset().is_none());
7661 }
7662
7663 #[test]
7664 fn set_key_is_deterministic_and_input_sensitive() {
7665 let mut a = NodeData::create_div();
7666 let mut b = NodeData::create_div();
7667 a.set_key("user-123");
7668 b.set_key("user-123");
7669 assert_eq!(a.get_key(), b.get_key(), "same key input => same hash");
7670 assert!(a.get_key().is_some());
7671
7672 let mut c = NodeData::create_div();
7673 c.set_key("user-124");
7674 assert_ne!(a.get_key(), c.get_key(), "different inputs => different keys");
7675 }
7676
7677 #[test]
7678 fn set_key_hashes_str_and_string_identically() {
7679 let mut a = NodeData::create_div();
7680 let mut b = NodeData::create_div();
7681 a.set_key("x");
7682 b.set_key(String::from("x"));
7683 assert_eq!(
7684 a.get_key(),
7685 b.get_key(),
7686 "&str and String must hash the same (Hash for str)"
7687 );
7688 }
7689
7690 #[test]
7691 fn set_key_accepts_extreme_inputs() {
7692 for nd in [
7693 NodeData::create_div().with_key(""),
7694 NodeData::create_div().with_key(u64::MAX),
7695 NodeData::create_div().with_key(i64::MIN),
7696 NodeData::create_div().with_key(huge_unicode_string()),
7697 ] {
7698 assert!(nd.get_key().is_some());
7699 }
7700 }
7701
7702 #[test]
7703 fn set_key_overwrites_rather_than_accumulating() {
7704 let mut nd = NodeData::create_div();
7705 nd.set_key("a");
7706 let first = nd.get_key();
7707 nd.set_key("b");
7708 assert_ne!(nd.get_key(), first, "the last set_key wins");
7709 }
7710
7711 #[test]
7712 fn merge_callback_round_trips_the_function_pointer() {
7713 let mut nd = NodeData::create_div();
7714 assert!(nd.get_merge_callback().is_none());
7715
7716 nd.set_merge_callback(merge_cb_a as DatasetMergeCallbackType);
7717 let cb = nd.get_merge_callback().expect("merge callback was set");
7718 assert_eq!(cb.cb as usize, merge_cb_a as usize);
7719 assert_eq!(cb.callable, OptionRefAny::None);
7720
7721 nd.set_merge_callback(merge_cb_b as DatasetMergeCallbackType);
7723 let cb = nd.get_merge_callback().expect("merge callback was replaced");
7724 assert_eq!(cb.cb as usize, merge_cb_b as usize);
7725 }
7726
7727 #[test]
7728 fn dataset_merge_callback_from_ptr_matches_the_from_impl() {
7729 let via_ptr = DatasetMergeCallback::from_ptr(merge_cb_a);
7730 let via_from = DatasetMergeCallback::from(merge_cb_a as DatasetMergeCallbackType);
7731 assert_eq!(via_ptr, via_from);
7732 assert_eq!(via_ptr.cb as usize, merge_cb_a as usize);
7733 assert_eq!(via_ptr.callable, OptionRefAny::None);
7734
7735 assert_ne!(via_ptr, DatasetMergeCallback::from_ptr(merge_cb_b));
7737 }
7738
7739 #[test]
7740 fn dataset_merge_callback_debug_is_non_empty_and_names_the_type() {
7741 let cb = DatasetMergeCallback::from_ptr(merge_cb_a);
7742 let s = format!("{cb:?}");
7743 assert!(s.contains("DatasetMergeCallback"));
7744 assert!(s.contains("cb"));
7745 }
7746
7747 #[test]
7748 fn merge_callback_is_actually_callable_through_the_stored_pointer() {
7749 let cb = DatasetMergeCallback::from_ptr(merge_cb_b);
7750 let mut out = (cb.cb)(RefAny::new(1u32), RefAny::new(2u32));
7751 assert_eq!(
7752 out.downcast_ref::<u32>().map(|r| *r),
7753 Some(2),
7754 "merge_cb_b returns the OLD data"
7755 );
7756 }
7757
7758 #[test]
7759 fn component_origin_round_trips_and_defaults_to_none() {
7760 let mut nd = NodeData::create_div();
7761 assert!(nd.get_component_origin().is_none());
7762
7763 nd.set_component_origin(ComponentOrigin {
7764 component_id: "shadcn:card".into(),
7765 data_model_json: crate::json::Json::null(),
7766 });
7767 let origin = nd.get_component_origin().expect("origin was set");
7768 assert_eq!(origin.component_id.as_str(), "shadcn:card");
7769
7770 let d = ComponentOrigin::default();
7772 assert_eq!(d.component_id.as_str(), "");
7773 assert_eq!(hash_of(&d), hash_of(&ComponentOrigin::default()));
7774 }
7775
7776 #[test]
7781 fn get_image_clip_mask_returns_none_for_non_mask_svg_data() {
7782 let mut nd = NodeData::create_div();
7783 assert!(nd.get_image_clip_mask().is_none());
7784
7785 nd.set_svg_data(SvgNodeData::Circle {
7786 cx: 1.0,
7787 cy: 2.0,
7788 r: 3.0,
7789 });
7790 assert!(nd.get_svg_data().is_some());
7791 assert!(
7792 nd.get_image_clip_mask().is_none(),
7793 "a Circle is not an ImageClipMask"
7794 );
7795 }
7796
7797 #[test]
7798 fn set_clip_mask_is_readable_through_get_image_clip_mask() {
7799 let mask = ImageMask {
7800 image: ImageRef::null_image(2, 2, crate::resources::RawImageFormat::R8, Vec::new()),
7801 rect: crate::geom::LogicalRect::new(
7802 LogicalPosition { x: 0.0, y: 0.0 },
7803 crate::geom::LogicalSize {
7804 width: 2.0,
7805 height: 2.0,
7806 },
7807 ),
7808 repeat: false,
7809 };
7810 let mut nd = NodeData::create_div();
7811 nd.set_clip_mask(mask.clone());
7812 assert_eq!(nd.get_image_clip_mask(), Some(&mask));
7813 assert!(matches!(
7815 nd.get_svg_data(),
7816 Some(SvgNodeData::ImageClipMask(_))
7817 ));
7818 }
7819
7820 #[test]
7821 fn svg_node_data_with_nan_coords_is_self_equal_and_hash_consistent() {
7822 let a = SvgNodeData::Rect {
7826 x: f32::NAN,
7827 y: f32::INFINITY,
7828 width: f32::NEG_INFINITY,
7829 height: -0.0,
7830 rx: 0.0,
7831 ry: f32::MAX,
7832 };
7833 let b = a.clone();
7834 assert_eq!(a, b);
7835 assert_eq!(hash_of(&a), hash_of(&b));
7836 assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
7837 }
7838
7839 #[test]
7840 fn svg_node_data_line_and_linear_gradient_are_distinct_despite_a_shared_hash_body() {
7841 let line = SvgNodeData::Line {
7844 x1: 1.0,
7845 y1: 2.0,
7846 x2: 3.0,
7847 y2: 4.0,
7848 };
7849 let grad = SvgNodeData::LinearGradient {
7850 x1: 1.0,
7851 y1: 2.0,
7852 x2: 3.0,
7853 y2: 4.0,
7854 };
7855 assert_ne!(line, grad, "same field values, different variants");
7856 }
7857
7858 #[test]
7863 fn calculate_node_data_hash_is_deterministic_and_equal_for_equal_nodes() {
7864 let a = NodeData::create_div().with_key("k").with_contenteditable(true);
7865 let b = a.clone();
7866 assert_eq!(a, b);
7867 assert_eq!(a.calculate_node_data_hash(), b.calculate_node_data_hash());
7868 assert_eq!(
7869 a.calculate_node_data_hash(),
7870 a.calculate_node_data_hash(),
7871 "hashing must not depend on call count"
7872 );
7873 }
7874
7875 #[test]
7876 fn structural_hash_ignores_text_content_but_data_hash_does_not() {
7877 let a = NodeData::create_text("Hello");
7880 let b = NodeData::create_text("Hello World");
7881
7882 assert_eq!(
7883 a.calculate_structural_hash(),
7884 b.calculate_structural_hash(),
7885 "structural hash must ignore text content"
7886 );
7887 assert_ne!(
7888 a.calculate_node_data_hash(),
7889 b.calculate_node_data_hash(),
7890 "the full data hash must NOT ignore text content"
7891 );
7892 }
7893
7894 #[test]
7895 fn structural_hash_ignores_contenteditable_but_data_hash_does_not() {
7896 let plain = NodeData::create_div();
7897 let editable = NodeData::create_div().with_contenteditable(true);
7898
7899 assert_eq!(
7900 plain.calculate_structural_hash(),
7901 editable.calculate_structural_hash(),
7902 "contenteditable flips with focus; it must not move the structural hash"
7903 );
7904 assert_ne!(
7905 plain.calculate_node_data_hash(),
7906 editable.calculate_node_data_hash(),
7907 "flags ARE part of the full data hash"
7908 );
7909 }
7910
7911 #[test]
7912 fn structural_hash_is_sensitive_to_ids_classes_and_node_type() {
7913 let mut a = NodeData::create_div();
7914 a.add_id("a".into());
7915 let mut b = NodeData::create_div();
7916 b.add_id("b".into());
7917 assert_ne!(a.calculate_structural_hash(), b.calculate_structural_hash());
7918
7919 let mut c = NodeData::create_div();
7920 c.add_class("a".into());
7921 assert_ne!(
7922 a.calculate_structural_hash(),
7923 c.calculate_structural_hash(),
7924 "id=\"a\" and class=\"a\" must not collide"
7925 );
7926
7927 assert_ne!(
7928 NodeData::create_div().calculate_structural_hash(),
7929 NodeData::create_br().calculate_structural_hash()
7930 );
7931 }
7932
7933 #[test]
7934 fn node_data_eq_implies_equal_hash_for_a_richly_populated_node() {
7935 let mut a = NodeData::create_div();
7936 a.add_id("id".into());
7937 a.add_class("cls".into());
7938 a.set_tab_index(TabIndex::OverrideInParent(9));
7939 a.set_contenteditable(true);
7940 a.set_anonymous(true);
7941 a.set_key("key");
7942 a.set_dataset(OptionRefAny::Some(RefAny::new(7u64)));
7943 a.set_svg_data(SvgNodeData::GradientStop { offset: 0.5 });
7944 a.set_context_menu(Menu::create(Vec::new().into()));
7945 a.set_merge_callback(merge_cb_a as DatasetMergeCallbackType);
7946 a.set_css("color: red;");
7947
7948 let b = a.clone();
7949 assert_eq!(a, b, "clone must be value-equal");
7950 assert_eq!(
7951 hash_of(&a),
7952 hash_of(&b),
7953 "Eq == true but hashes differ: Hash/Eq contract violated"
7954 );
7955 assert_eq!(a.calculate_node_data_hash(), b.calculate_node_data_hash());
7956 assert_eq!(a.copy_special(), b);
7958 }
7959
7960 #[test]
7965 fn node_data_to_string_is_empty_for_a_bare_node() {
7966 assert_eq!(node_data_to_string(&NodeData::create_div()), "");
7968 }
7969
7970 #[test]
7971 fn node_data_to_string_emits_ids_classes_and_tabindex() {
7972 let mut nd = NodeData::create_div();
7973 nd.add_id("i1".into());
7974 nd.add_id("i2".into());
7975 nd.add_class("c1".into());
7976 nd.set_tab_index(TabIndex::NoKeyboardFocus);
7977
7978 let s = node_data_to_string(&nd);
7979 assert!(s.contains(r#"id="i1 i2""#), "ids are space-joined: {s}");
7980 assert!(s.contains(r#"class="c1""#), "{s}");
7981 assert!(s.contains(r#"tabindex="-1""#), "{s}");
7982 }
7983
7984 #[test]
7985 fn node_data_display_is_self_closing_without_content() {
7986 let s = format!("{}", NodeData::create_div());
7987 assert!(s.starts_with('<'), "{s}");
7988 assert!(s.ends_with("/>"), "content-less nodes self-close: {s}");
7989 }
7990
7991 #[test]
7992 fn node_data_display_wraps_text_content_in_a_tag_pair() {
7993 let s = format!("{}", NodeData::create_text("hello"));
7994 assert!(s.starts_with('<'));
7995 assert!(s.ends_with('>'));
7996 assert!(s.contains("hello"), "{s}");
7997 assert!(!s.ends_with("/>"), "a node with content must not self-close");
7998 }
7999
8000 #[test]
8001 fn node_data_display_does_not_panic_on_hostile_text() {
8002 for text in [
8006 "",
8007 "<script>alert(1)</script>",
8008 "\" onload=\"x",
8009 "日本語 🎉",
8010 "line\nbreak\ttab",
8011 ] {
8012 let s = format!("{}", NodeData::create_text(text));
8013 assert!(s.contains(text), "Display dropped content for {text:?}");
8014 }
8015 }
8016
8017 #[test]
8018 fn node_data_display_survives_a_huge_text_payload() {
8019 let big = huge_unicode_string();
8020 let s = format!("{}", NodeData::create_text(big.clone()));
8021 assert!(s.len() > big.len());
8022 }
8023
8024 #[test]
8025 fn debug_print_end_matches_the_node_tag() {
8026 let s = NodeData::create_div().debug_print_end();
8027 assert!(s.starts_with("</"));
8028 assert!(s.ends_with('>'));
8029 }
8030
8031 #[test]
8036 fn set_node_type_replaces_the_type_and_keeps_the_attributes() {
8037 let mut nd = NodeData::create_div();
8038 nd.add_id("keep".into());
8039 nd.set_node_type(NodeType::Span);
8040 assert!(nd.is_node_type(NodeType::Span));
8041 assert!(nd.has_id("keep"), "changing the tag must not drop attributes");
8042 }
8043
8044 #[test]
8045 fn add_callback_appends_and_get_callbacks_reflects_it() {
8046 let mut nd = NodeData::create_div();
8047 assert!(nd.get_callbacks().as_ref().is_empty());
8048
8049 nd.add_callback(
8050 EventFilter::Hover(HoverEventFilter::MouseUp),
8051 RefAny::new(1u32),
8052 0usize,
8053 );
8054 nd.add_callback(
8055 EventFilter::Focus(FocusEventFilter::MouseDown),
8056 RefAny::new(2u32),
8057 1usize,
8058 );
8059 assert_eq!(nd.get_callbacks().as_ref().len(), 2);
8060 assert_eq!(
8061 nd.get_callbacks().as_ref()[0].event,
8062 EventFilter::Hover(HoverEventFilter::MouseUp)
8063 );
8064 }
8065
8066 #[test]
8067 fn add_css_property_appends_an_inline_rule() {
8068 use azul_css::props::property::{CssProperty, CssPropertyType};
8069
8070 let mut nd = NodeData::create_div();
8071 assert!(nd.get_style().rules.as_ref().is_empty());
8072
8073 nd.add_css_property(CssPropertyWithConditions {
8074 property: CssProperty::const_none(CssPropertyType::Display),
8075 apply_if: Vec::new().into(),
8076 });
8077 assert_eq!(nd.get_style().rules.as_ref().len(), 1);
8078
8079 nd.add_css_property(CssPropertyWithConditions {
8080 property: CssProperty::const_none(CssPropertyType::Display),
8081 apply_if: Vec::new().into(),
8082 });
8083 assert_eq!(
8084 nd.get_style().rules.as_ref().len(),
8085 2,
8086 "add_css_property appends, it does not replace"
8087 );
8088 }
8089
8090 #[test]
8091 fn set_style_replaces_whereas_set_css_appends() {
8092 let mut nd = NodeData::create_div();
8093 nd.set_css("color: red;");
8094 let after_first = nd.get_style().rules.as_ref().len();
8095 assert!(after_first > 0);
8096
8097 nd.set_css("color: blue;");
8098 assert!(
8099 nd.get_style().rules.as_ref().len() > after_first,
8100 "set_css appends to the existing inline style"
8101 );
8102
8103 nd.set_style(azul_css::css::Css {
8104 rules: Vec::new().into(),
8105 });
8106 assert!(
8107 nd.get_style().rules.as_ref().is_empty(),
8108 "set_style replaces wholesale"
8109 );
8110 }
8111
8112 #[test]
8113 fn set_css_with_empty_and_malformed_input_does_not_panic() {
8114 for style in [
8115 "",
8116 " ",
8117 ";;;;",
8118 "color",
8119 "color:",
8120 ":",
8121 "}",
8122 "{",
8123 "color: ;",
8124 "not-a-property: not-a-value;",
8125 ":hover {",
8126 "@os {",
8127 "color: red", "\u{0}color: red;", "color: 日本語;",
8130 ] {
8131 let nd = NodeData::create_div().with_css(style);
8132 let _ = nd.get_style().rules.as_ref().len();
8135 }
8136 }
8137
8138 #[test]
8139 fn swap_with_default_returns_the_original_and_leaves_a_div() {
8140 let mut nd = NodeData::create_text("payload");
8141 let taken = nd.swap_with_default();
8142 assert!(taken.is_text_node());
8143 assert!(nd.is_node_type(NodeType::Div), "the slot becomes a fresh div");
8144 assert!(nd.attributes().as_ref().is_empty());
8145 }
8146
8147 #[test]
8148 fn node_data_builders_are_equivalent_to_their_setters() {
8149 let built = NodeData::create_div()
8150 .with_tab_index(TabIndex::Auto)
8151 .with_contenteditable(true)
8152 .with_node_type(NodeType::Span);
8153
8154 let mut set = NodeData::create_div();
8155 set.set_tab_index(TabIndex::Auto);
8156 set.set_contenteditable(true);
8157 set.set_node_type(NodeType::Span);
8158
8159 assert_eq!(built, set);
8160 }
8161
8162 #[test]
8167 fn node_data_vec_as_container_is_empty_for_an_empty_vec() {
8168 let v: NodeDataVec = Vec::new().into();
8169 assert_eq!(v.as_container().internal.len(), 0);
8170 }
8171
8172 #[test]
8173 fn node_data_vec_containers_expose_and_mutate_the_backing_slice() {
8174 let mut v: NodeDataVec = vec![
8175 NodeData::create_div(),
8176 NodeData::create_br(),
8177 NodeData::create_text("t"),
8178 ]
8179 .into();
8180 assert_eq!(v.as_container().internal.len(), 3);
8181 assert!(v.as_container().internal[2].is_text_node());
8182
8183 v.as_container_mut().internal[0].set_node_type(NodeType::Span);
8184 assert!(v.as_container().internal[0].is_node_type(NodeType::Span));
8185 }
8186
8187 #[test]
8192 fn dom_default_is_an_empty_body() {
8193 let d = Dom::default();
8194 assert!(d.root.is_node_type(NodeType::Body));
8195 assert_eq!(d.estimated_total_children, 0);
8196 assert_eq!(d.node_count(), 1);
8197 }
8198
8199 #[test]
8200 fn dom_set_children_recomputes_the_estimate_from_scratch() {
8201 let child = Dom::create_div().with_child(Dom::create_div());
8202 let mut parent = Dom::create_div();
8203 parent.add_child(Dom::create_div());
8204 assert_eq!(parent.estimated_total_children, 1);
8205
8206 parent.set_children(vec![child].into());
8208 assert_eq!(parent.estimated_total_children, 2);
8209 assert_eq!(
8210 parent.estimated_total_children,
8211 parent.recompute_estimated_total_children()
8212 );
8213 }
8214
8215 #[test]
8216 fn dom_set_children_with_an_empty_vec_zeroes_the_estimate() {
8217 let mut d = Dom::create_div().with_child(Dom::create_div().with_child(Dom::create_div()));
8218 assert_eq!(d.estimated_total_children, 2);
8219 d.set_children(Vec::new().into());
8220 assert_eq!(d.estimated_total_children, 0);
8221 assert_eq!(d.node_count(), 1);
8222 }
8223
8224 #[test]
8225 fn dom_deeply_nested_chain_keeps_an_exact_estimate() {
8226 const DEPTH: usize = 256;
8228 let mut d = Dom::create_div();
8229 for _ in 0..DEPTH {
8230 d = Dom::create_div().with_child(d);
8231 }
8232 assert_eq!(d.estimated_total_children, DEPTH);
8233 assert_eq!(d.node_count(), DEPTH + 1);
8234 assert_eq!(d.recompute_estimated_total_children(), DEPTH);
8235 }
8236
8237 #[test]
8238 fn dom_very_wide_child_list_keeps_an_exact_estimate() {
8239 const WIDTH: usize = 5_000;
8240 let children: Vec<Dom> = (0..WIDTH).map(|_| Dom::create_div()).collect();
8241 let d = Dom::create_div().with_children(children.into());
8242 assert_eq!(d.estimated_total_children, WIDTH);
8243 assert_eq!(d.node_count(), WIDTH + 1);
8244 }
8245
8246 #[test]
8247 fn dom_from_iterator_counts_nested_grandchildren() {
8248 let empty: Dom = Vec::new().into_iter().collect();
8249 assert_eq!(empty.estimated_total_children, 0);
8250 assert!(empty.root.is_node_type(NodeType::Div));
8251
8252 let d: Dom = vec![
8254 Dom::create_div().with_child(Dom::create_div()),
8255 Dom::create_div(),
8256 ]
8257 .into_iter()
8258 .collect();
8259 assert_eq!(d.estimated_total_children, 3);
8260 assert_eq!(d.estimated_total_children, d.recompute_estimated_total_children());
8261 assert_eq!(d.node_count(), 4);
8262 }
8263
8264 #[test]
8265 fn dom_fixup_repairs_a_corrupted_estimate_at_every_depth() {
8266 let mut d = Dom::create_div()
8267 .with_child(Dom::create_div().with_child(Dom::create_div()))
8268 .with_child(Dom::create_div());
8269
8270 d.estimated_total_children = 0;
8273 d.children.as_mut()[0].estimated_total_children = 99;
8274
8275 let repaired = d.fixup_children_estimated();
8276 assert_eq!(repaired, 3);
8277 assert_eq!(d.children.as_ref()[0].estimated_total_children, 1);
8278 assert_eq!(
8279 d.estimated_total_children,
8280 d.recompute_estimated_total_children()
8281 );
8282 }
8283
8284 #[test]
8285 fn dom_fixup_on_a_leaf_zeroes_a_bogus_estimate() {
8286 let mut d = Dom::create_div();
8287 d.estimated_total_children = usize::MAX;
8288 assert_eq!(d.fixup_children_estimated(), 0);
8289 assert_eq!(d.node_count(), 1, "node_count is safe again after fixup");
8290 }
8291
8292 #[test]
8299 fn dom_node_count_saturates_on_a_corrupted_max_estimate() {
8300 let mut d = Dom::create_div();
8301 d.estimated_total_children = usize::MAX;
8302 assert_eq!(d.node_count(), usize::MAX);
8303 assert_ne!(
8304 d.node_count(),
8305 0,
8306 "wrapping to 0 would claim an empty DOM — the one answer callers \
8307 act on without checking"
8308 );
8309 }
8310
8311 #[test]
8312 fn dom_swap_with_default_returns_the_original_tree() {
8313 let mut d = Dom::create_div().with_child(Dom::create_div());
8314 let taken = d.swap_with_default();
8315 assert_eq!(taken.estimated_total_children, 1);
8316 assert_eq!(d.estimated_total_children, 0, "the slot is reset");
8317 assert!(d.root.is_node_type(NodeType::Div));
8318 }
8319
8320 #[test]
8325 fn dom_with_id_and_with_class_apply_to_the_root() {
8326 let d = Dom::create_div()
8327 .with_id("root".into())
8328 .with_class("card".into());
8329 assert!(d.root.has_id("root"));
8330 assert!(d.root.has_class("card"));
8331 }
8332
8333 #[test]
8334 fn dom_with_attribute_appends_and_with_attributes_replaces() {
8335 let d = Dom::create_div()
8336 .with_attribute(AttributeType::Href("/a".into()))
8337 .with_attribute(AttributeType::Alt("alt".into()));
8338 assert_eq!(d.root.attributes().as_ref().len(), 2);
8339
8340 let d = d.with_attributes(vec![AttributeType::Disabled].into());
8341 assert_eq!(
8342 d.root.attributes().as_ref().len(),
8343 1,
8344 "with_attributes replaces wholesale"
8345 );
8346 assert_eq!(d.root.attributes().as_ref()[0], AttributeType::Disabled);
8347 }
8348
8349 #[test]
8350 fn dom_add_component_css_stacks_stylesheets() {
8351 let mut d = Dom::create_div();
8352 assert!(d.css.as_ref().is_empty());
8353 d.set_css("color: red;");
8354 d.set_css("color: blue;");
8355 assert_eq!(d.css.as_ref().len(), 2, "each set_css pushes a stylesheet");
8356
8357 d.set_component_css(Vec::new().into());
8358 assert!(d.css.as_ref().is_empty(), "set_component_css replaces");
8359 }
8360
8361 #[test]
8362 fn dom_with_css_does_not_panic_on_malformed_input() {
8363 for style in ["", "}}}", "@os {", "color:", "\u{0}"] {
8364 let d = Dom::create_div().with_css(style);
8365 assert_eq!(d.css.as_ref().len(), 1, "a Css is pushed even if it parses empty");
8366 }
8367 }
8368
8369 #[test]
8370 fn dom_text_helpers_produce_a_text_child() {
8371 let d = Dom::create_h1_with_text("Title");
8372 assert!(d.root.is_node_type(NodeType::H1));
8373 assert_eq!(d.estimated_total_children, 1);
8374 assert!(d.children.as_ref()[0].root.is_text_node());
8375 }
8376
8377 #[test]
8378 fn dom_create_geolocation_probe_carries_its_config() {
8379 let cfg = crate::geolocation::GeolocationProbeConfig {
8380 high_accuracy: true,
8381 background: false,
8382 max_accuracy_m: 25.0,
8383 min_interval_ms: 1_000,
8384 };
8385 let d = Dom::create_geolocation_probe(cfg);
8386 match d.root.get_node_type() {
8387 NodeType::GeolocationProbe(c) => {
8388 assert!(c.high_accuracy);
8389 assert_eq!(c.min_interval_ms, 1_000);
8390 }
8391 other => panic!("expected GeolocationProbe, got {other:?}"),
8392 }
8393 }
8394
8395 #[test]
8396 fn dom_clone_and_eq_agree_on_a_nested_tree() {
8397 let d = Dom::create_div()
8398 .with_id("r".into())
8399 .with_child(Dom::create_text("a"))
8400 .with_child(Dom::create_div().with_child(Dom::create_text("b")));
8401 let c = d.clone();
8402 assert_eq!(d, c);
8403 assert_eq!(hash_of(&d), hash_of(&c));
8404 assert_eq!(c.estimated_total_children, 3);
8406 assert_eq!(c.node_count(), 4);
8407 }
8408
8409 #[test]
8410 fn dom_debug_does_not_panic_on_a_nested_tree() {
8411 let d = Dom::create_div()
8412 .with_child(Dom::create_text("日本語 🎉"))
8413 .with_child(Dom::create_div().with_child(Dom::create_br()));
8414 let s = format!("{d:?}");
8415 assert!(s.contains("Dom"));
8416 assert!(s.contains("estimated_total_children"));
8417 }
8418
8419 #[test]
8424 fn dom_id_root_is_zero_and_is_the_default() {
8425 assert_eq!(DomId::ROOT_ID.inner, 0);
8426 assert_eq!(DomId::default(), DomId::ROOT_ID);
8427 assert_eq!(format!("{}", DomId::ROOT_ID), "0");
8428 assert_eq!(format!("{}", DomId { inner: usize::MAX }), usize::MAX.to_string());
8429 }
8430
8431 #[test]
8432 fn dom_node_id_root_points_at_the_root_dom_and_no_node() {
8433 assert_eq!(DomNodeId::ROOT.dom, DomId::ROOT_ID);
8434 assert_eq!(DomNodeId::ROOT.node, NodeHierarchyItemId::NONE);
8435 }
8436}