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 PageBreak,
656}
657
658pub type BoxOrStaticImageRef = BoxOrStatic<ImageRef>;
660
661impl_option!(NodeType, OptionNodeType, copy = false, [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]);
662
663impl NodeType {
664 #[allow(clippy::too_many_lines)] fn to_library_owned_nodetype(&self) -> Self {
666 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};
667 match self {
668 Html => Html,
669 Head => Head,
670 Body => Body,
671 Div => Div,
672 P => P,
673 Article => Article,
674 Section => Section,
675 Nav => Nav,
676 Aside => Aside,
677 Header => Header,
678 Footer => Footer,
679 Main => Main,
680 Figure => Figure,
681 FigCaption => FigCaption,
682 H1 => H1,
683 H2 => H2,
684 H3 => H3,
685 H4 => H4,
686 H5 => H5,
687 H6 => H6,
688 Br => Br,
689 Hr => Hr,
690 Pre => Pre,
691 BlockQuote => BlockQuote,
692 Address => Address,
693 Details => Details,
694 Summary => Summary,
695 Dialog => Dialog,
696 Ul => Ul,
697 Ol => Ol,
698 Li => Li,
699 Dl => Dl,
700 Dt => Dt,
701 Dd => Dd,
702 Menu => Menu,
703 MenuItem => MenuItem,
704 Dir => Dir,
705 Table => Table,
706 Caption => Caption,
707 THead => THead,
708 TBody => TBody,
709 TFoot => TFoot,
710 Tr => Tr,
711 Th => Th,
712 Td => Td,
713 ColGroup => ColGroup,
714 Col => Col,
715 Form => Form,
716 FieldSet => FieldSet,
717 Legend => Legend,
718 Label => Label,
719 Input => Input,
720 Button => Button,
721 Select => Select,
722 OptGroup => OptGroup,
723 SelectOption => SelectOption,
724 TextArea => TextArea,
725 Output => Output,
726 Progress => Progress,
727 Meter => Meter,
728 DataList => DataList,
729 Span => Span,
730 A => A,
731 Em => Em,
732 Strong => Strong,
733 B => B,
734 I => I,
735 U => U,
736 S => S,
737 Mark => Mark,
738 Del => Del,
739 Ins => Ins,
740 Code => Code,
741 Samp => Samp,
742 Kbd => Kbd,
743 Var => Var,
744 Cite => Cite,
745 Dfn => Dfn,
746 Abbr => Abbr,
747 Acronym => Acronym,
748 Q => Q,
749 Time => Time,
750 Sub => Sub,
751 Sup => Sup,
752 Small => Small,
753 Big => Big,
754 Bdo => Bdo,
755 Bdi => Bdi,
756 Wbr => Wbr,
757 Ruby => Ruby,
758 Rt => Rt,
759 Rtc => Rtc,
760 Rp => Rp,
761 Data => Data,
762 Canvas => Canvas,
763 Object => Object,
764 Param => Param,
765 Embed => Embed,
766 Audio => Audio,
767 Video => Video,
768 Source => Source,
769 Track => Track,
770 Map => Map,
771 Area => Area,
772 Svg => Svg, SvgG => SvgG, SvgDefs => SvgDefs, SvgSymbol => SvgSymbol,
774 SvgUse => SvgUse, SvgSwitch => SvgSwitch,
775 SvgPath => SvgPath, SvgCircle => SvgCircle, SvgRect => SvgRect,
777 SvgEllipse => SvgEllipse, SvgLine => SvgLine,
778 SvgPolygon => SvgPolygon, SvgPolyline => SvgPolyline,
779 SvgText(s) => SvgText(s.clone_self()),
781 SvgTspan => SvgTspan, SvgTextPath => SvgTextPath,
782 SvgLinearGradient => SvgLinearGradient, SvgRadialGradient => SvgRadialGradient,
784 SvgStop => SvgStop, SvgPattern => SvgPattern,
785 SvgClipPathElement => SvgClipPathElement, SvgMask => SvgMask,
787 SvgFilter => SvgFilter, SvgFeBlend => SvgFeBlend,
789 SvgFeColorMatrix => SvgFeColorMatrix,
790 SvgFeComponentTransfer => SvgFeComponentTransfer,
791 SvgFeComposite => SvgFeComposite, SvgFeConvolveMatrix => SvgFeConvolveMatrix,
792 SvgFeDiffuseLighting => SvgFeDiffuseLighting,
793 SvgFeDisplacementMap => SvgFeDisplacementMap,
794 SvgFeDistantLight => SvgFeDistantLight, SvgFeDropShadow => SvgFeDropShadow,
795 SvgFeFlood => SvgFeFlood,
796 SvgFeFuncR => SvgFeFuncR, SvgFeFuncG => SvgFeFuncG,
797 SvgFeFuncB => SvgFeFuncB, SvgFeFuncA => SvgFeFuncA,
798 SvgFeGaussianBlur => SvgFeGaussianBlur, SvgFeImage => SvgFeImage,
799 SvgFeMerge => SvgFeMerge, SvgFeMergeNode => SvgFeMergeNode,
800 SvgFeMorphology => SvgFeMorphology, SvgFeOffset => SvgFeOffset,
801 SvgFePointLight => SvgFePointLight,
802 SvgFeSpecularLighting => SvgFeSpecularLighting,
803 SvgFeSpotLight => SvgFeSpotLight,
804 SvgFeTile => SvgFeTile, SvgFeTurbulence => SvgFeTurbulence,
805 SvgMarker => SvgMarker,
807 SvgImage(i) => SvgImage(i.clone()),
808 SvgForeignObject => SvgForeignObject,
809 SvgTitle => SvgTitle, SvgDesc => SvgDesc, SvgMetadata => SvgMetadata,
811 SvgA => SvgA, SvgView => SvgView,
812 SvgStyle => SvgStyle, SvgScript => SvgScript,
813 SvgAnimate => SvgAnimate, SvgAnimateMotion => SvgAnimateMotion,
815 SvgAnimateTransform => SvgAnimateTransform,
816 SvgSet => SvgSet, SvgMpath => SvgMpath,
817 Title => Title,
819 Meta => Meta,
820 Link => Link,
821 Script => Script,
822 Style => Style,
823 Base => Base,
824 Before => Before,
825 After => After,
826 Marker => Marker,
827 Placeholder => Placeholder,
828
829 Text(s) => Text(BoxOrStatic::heap(s.clone_self())),
830 Image(i) => Image(i.clone()),
831 VirtualView => VirtualView,
832 Icon(s) => Icon(BoxOrStatic::heap(s.clone_self())),
833 GeolocationProbe(cfg) => GeolocationProbe(*cfg),
834 Self::PageBreak => Self::PageBreak,
835 }
836 }
837
838 #[must_use] pub fn format(&self) -> Option<String> {
839 use self::NodeType::{Text, Image, VirtualView, Icon, GeolocationProbe};
840 match self {
841 Text(s) => Some(format!("{s}")),
842 Image(id) => Some(format!("image({id:?})")),
843 VirtualView => Some("virtualized-view".to_string()),
844 Icon(s) => Some(format!("icon({s})")),
845 GeolocationProbe(cfg) => Some(format!(
846 "geolocation-probe(hi={}, bg={}, max={}m, every={}ms)",
847 cfg.high_accuracy, cfg.background, cfg.max_accuracy_m, cfg.min_interval_ms
848 )),
849 _ => None,
850 }
851 }
852
853 #[allow(clippy::too_many_lines)] #[must_use] pub const fn get_path(&self) -> NodeTypeTag {
856 match self {
857 Self::Html => NodeTypeTag::Html,
858 Self::Head => NodeTypeTag::Head,
859 Self::Body => NodeTypeTag::Body,
860 Self::Div => NodeTypeTag::Div,
861 Self::P => NodeTypeTag::P,
862 Self::Article => NodeTypeTag::Article,
863 Self::Section => NodeTypeTag::Section,
864 Self::Nav => NodeTypeTag::Nav,
865 Self::Aside => NodeTypeTag::Aside,
866 Self::Header => NodeTypeTag::Header,
867 Self::Footer => NodeTypeTag::Footer,
868 Self::Main => NodeTypeTag::Main,
869 Self::Figure => NodeTypeTag::Figure,
870 Self::FigCaption => NodeTypeTag::FigCaption,
871 Self::H1 => NodeTypeTag::H1,
872 Self::H2 => NodeTypeTag::H2,
873 Self::H3 => NodeTypeTag::H3,
874 Self::H4 => NodeTypeTag::H4,
875 Self::H5 => NodeTypeTag::H5,
876 Self::H6 => NodeTypeTag::H6,
877 Self::Br => NodeTypeTag::Br,
878 Self::Hr => NodeTypeTag::Hr,
879 Self::Pre => NodeTypeTag::Pre,
880 Self::BlockQuote => NodeTypeTag::BlockQuote,
881 Self::Address => NodeTypeTag::Address,
882 Self::Details => NodeTypeTag::Details,
883 Self::Summary => NodeTypeTag::Summary,
884 Self::Dialog => NodeTypeTag::Dialog,
885 Self::Ul => NodeTypeTag::Ul,
886 Self::Ol => NodeTypeTag::Ol,
887 Self::Li => NodeTypeTag::Li,
888 Self::Dl => NodeTypeTag::Dl,
889 Self::Dt => NodeTypeTag::Dt,
890 Self::Dd => NodeTypeTag::Dd,
891 Self::Menu => NodeTypeTag::Menu,
892 Self::MenuItem => NodeTypeTag::MenuItem,
893 Self::Dir => NodeTypeTag::Dir,
894 Self::Table => NodeTypeTag::Table,
895 Self::Caption => NodeTypeTag::Caption,
896 Self::THead => NodeTypeTag::THead,
897 Self::TBody => NodeTypeTag::TBody,
898 Self::TFoot => NodeTypeTag::TFoot,
899 Self::Tr => NodeTypeTag::Tr,
900 Self::Th => NodeTypeTag::Th,
901 Self::Td => NodeTypeTag::Td,
902 Self::ColGroup => NodeTypeTag::ColGroup,
903 Self::Col => NodeTypeTag::Col,
904 Self::Form => NodeTypeTag::Form,
905 Self::FieldSet => NodeTypeTag::FieldSet,
906 Self::Legend => NodeTypeTag::Legend,
907 Self::Label => NodeTypeTag::Label,
908 Self::Input => NodeTypeTag::Input,
909 Self::Button => NodeTypeTag::Button,
910 Self::Select => NodeTypeTag::Select,
911 Self::OptGroup => NodeTypeTag::OptGroup,
912 Self::SelectOption => NodeTypeTag::SelectOption,
913 Self::TextArea => NodeTypeTag::TextArea,
914 Self::Output => NodeTypeTag::Output,
915 Self::Progress => NodeTypeTag::Progress,
916 Self::Meter => NodeTypeTag::Meter,
917 Self::DataList => NodeTypeTag::DataList,
918 Self::Span => NodeTypeTag::Span,
919 Self::A => NodeTypeTag::A,
920 Self::Em => NodeTypeTag::Em,
921 Self::Strong => NodeTypeTag::Strong,
922 Self::B => NodeTypeTag::B,
923 Self::I => NodeTypeTag::I,
924 Self::U => NodeTypeTag::U,
925 Self::S => NodeTypeTag::S,
926 Self::Mark => NodeTypeTag::Mark,
927 Self::Del => NodeTypeTag::Del,
928 Self::Ins => NodeTypeTag::Ins,
929 Self::Code => NodeTypeTag::Code,
930 Self::Samp => NodeTypeTag::Samp,
931 Self::Kbd => NodeTypeTag::Kbd,
932 Self::Var => NodeTypeTag::Var,
933 Self::Cite => NodeTypeTag::Cite,
934 Self::Dfn => NodeTypeTag::Dfn,
935 Self::Abbr => NodeTypeTag::Abbr,
936 Self::Acronym => NodeTypeTag::Acronym,
937 Self::Q => NodeTypeTag::Q,
938 Self::Time => NodeTypeTag::Time,
939 Self::Sub => NodeTypeTag::Sub,
940 Self::Sup => NodeTypeTag::Sup,
941 Self::Small => NodeTypeTag::Small,
942 Self::Big => NodeTypeTag::Big,
943 Self::Bdo => NodeTypeTag::Bdo,
944 Self::Bdi => NodeTypeTag::Bdi,
945 Self::Wbr => NodeTypeTag::Wbr,
946 Self::Ruby => NodeTypeTag::Ruby,
947 Self::Rt => NodeTypeTag::Rt,
948 Self::Rtc => NodeTypeTag::Rtc,
949 Self::Rp => NodeTypeTag::Rp,
950 Self::Data => NodeTypeTag::Data,
951 Self::Canvas => NodeTypeTag::Canvas,
952 Self::Object => NodeTypeTag::Object,
953 Self::Param => NodeTypeTag::Param,
954 Self::Embed => NodeTypeTag::Embed,
955 Self::Audio => NodeTypeTag::Audio,
956 Self::Video => NodeTypeTag::Video,
957 Self::Source => NodeTypeTag::Source,
958 Self::Track => NodeTypeTag::Track,
959 Self::Map => NodeTypeTag::Map,
960 Self::Area => NodeTypeTag::Area,
961 Self::Svg => NodeTypeTag::Svg,
963 Self::SvgG => NodeTypeTag::SvgG,
964 Self::SvgDefs => NodeTypeTag::SvgDefs,
965 Self::SvgSymbol => NodeTypeTag::SvgSymbol,
966 Self::SvgUse => NodeTypeTag::SvgUse,
967 Self::SvgSwitch => NodeTypeTag::SvgSwitch,
968 Self::SvgPath => NodeTypeTag::SvgPath,
969 Self::SvgCircle => NodeTypeTag::SvgCircle,
970 Self::SvgRect => NodeTypeTag::SvgRect,
971 Self::SvgEllipse => NodeTypeTag::SvgEllipse,
972 Self::SvgLine => NodeTypeTag::SvgLine,
973 Self::SvgPolygon => NodeTypeTag::SvgPolygon,
974 Self::SvgPolyline => NodeTypeTag::SvgPolyline,
975 Self::SvgText(_) => NodeTypeTag::SvgText,
976 Self::SvgTspan => NodeTypeTag::SvgTspan,
977 Self::SvgTextPath => NodeTypeTag::SvgTextPath,
978 Self::SvgLinearGradient => NodeTypeTag::SvgLinearGradient,
979 Self::SvgRadialGradient => NodeTypeTag::SvgRadialGradient,
980 Self::SvgStop => NodeTypeTag::SvgStop,
981 Self::SvgPattern => NodeTypeTag::SvgPattern,
982 Self::SvgClipPathElement => NodeTypeTag::SvgClipPathElement,
983 Self::SvgMask => NodeTypeTag::SvgMask,
984 Self::SvgFilter => NodeTypeTag::SvgFilter,
985 Self::SvgFeBlend => NodeTypeTag::SvgFeBlend,
986 Self::SvgFeColorMatrix => NodeTypeTag::SvgFeColorMatrix,
987 Self::SvgFeComponentTransfer => NodeTypeTag::SvgFeComponentTransfer,
988 Self::SvgFeComposite => NodeTypeTag::SvgFeComposite,
989 Self::SvgFeConvolveMatrix => NodeTypeTag::SvgFeConvolveMatrix,
990 Self::SvgFeDiffuseLighting => NodeTypeTag::SvgFeDiffuseLighting,
991 Self::SvgFeDisplacementMap => NodeTypeTag::SvgFeDisplacementMap,
992 Self::SvgFeDistantLight => NodeTypeTag::SvgFeDistantLight,
993 Self::SvgFeDropShadow => NodeTypeTag::SvgFeDropShadow,
994 Self::SvgFeFlood => NodeTypeTag::SvgFeFlood,
995 Self::SvgFeFuncR => NodeTypeTag::SvgFeFuncR,
996 Self::SvgFeFuncG => NodeTypeTag::SvgFeFuncG,
997 Self::SvgFeFuncB => NodeTypeTag::SvgFeFuncB,
998 Self::SvgFeFuncA => NodeTypeTag::SvgFeFuncA,
999 Self::SvgFeGaussianBlur => NodeTypeTag::SvgFeGaussianBlur,
1000 Self::SvgFeImage => NodeTypeTag::SvgFeImage,
1001 Self::SvgFeMerge => NodeTypeTag::SvgFeMerge,
1002 Self::SvgFeMergeNode => NodeTypeTag::SvgFeMergeNode,
1003 Self::SvgFeMorphology => NodeTypeTag::SvgFeMorphology,
1004 Self::SvgFeOffset => NodeTypeTag::SvgFeOffset,
1005 Self::SvgFePointLight => NodeTypeTag::SvgFePointLight,
1006 Self::SvgFeSpecularLighting => NodeTypeTag::SvgFeSpecularLighting,
1007 Self::SvgFeSpotLight => NodeTypeTag::SvgFeSpotLight,
1008 Self::SvgFeTile => NodeTypeTag::SvgFeTile,
1009 Self::SvgFeTurbulence => NodeTypeTag::SvgFeTurbulence,
1010 Self::SvgMarker => NodeTypeTag::SvgMarker,
1011 Self::SvgImage(_) => NodeTypeTag::SvgImage,
1012 Self::SvgForeignObject => NodeTypeTag::SvgForeignObject,
1013 Self::SvgTitle => NodeTypeTag::SvgTitle,
1014 Self::SvgDesc => NodeTypeTag::SvgDesc,
1015 Self::SvgMetadata => NodeTypeTag::SvgMetadata,
1016 Self::SvgA => NodeTypeTag::SvgA,
1017 Self::SvgView => NodeTypeTag::SvgView,
1018 Self::SvgStyle => NodeTypeTag::SvgStyle,
1019 Self::SvgScript => NodeTypeTag::SvgScript,
1020 Self::SvgAnimate => NodeTypeTag::SvgAnimate,
1021 Self::SvgAnimateMotion => NodeTypeTag::SvgAnimateMotion,
1022 Self::SvgAnimateTransform => NodeTypeTag::SvgAnimateTransform,
1023 Self::SvgSet => NodeTypeTag::SvgSet,
1024 Self::SvgMpath => NodeTypeTag::SvgMpath,
1025 Self::Title => NodeTypeTag::Title,
1027 Self::Meta => NodeTypeTag::Meta,
1028 Self::Link => NodeTypeTag::Link,
1029 Self::Script => NodeTypeTag::Script,
1030 Self::Style => NodeTypeTag::Style,
1031 Self::Base => NodeTypeTag::Base,
1032 Self::Text(_) => NodeTypeTag::Text,
1033 Self::Image(_) => NodeTypeTag::Img,
1034 Self::VirtualView => NodeTypeTag::VirtualView,
1035 Self::Icon(_) => NodeTypeTag::Icon,
1036 Self::GeolocationProbe(_) => NodeTypeTag::GeolocationProbe,
1037 Self::PageBreak => NodeTypeTag::PageBreak,
1038 Self::Before => NodeTypeTag::Before,
1039 Self::After => NodeTypeTag::After,
1040 Self::Marker => NodeTypeTag::Marker,
1041 Self::Placeholder => NodeTypeTag::Placeholder,
1042 }
1043 }
1044
1045 #[must_use] pub const fn is_semantic_for_accessibility(&self) -> bool {
1051 matches!(
1052 self,
1053 Self::Button
1054 | Self::Input
1055 | Self::TextArea
1056 | Self::Select
1057 | Self::A
1058 | Self::H1
1059 | Self::H2
1060 | Self::H3
1061 | Self::H4
1062 | Self::H5
1063 | Self::H6
1064 | Self::Article
1065 | Self::Section
1066 | Self::Nav
1067 | Self::Main
1068 | Self::Header
1069 | Self::Footer
1070 | Self::Aside
1071 )
1072 }
1073}
1074
1075#[derive(Clone, Copy, PartialEq, Eq)]
1077#[repr(C, u8)]
1084pub enum FormattingContext {
1086 Block {
1088 establishes_new_context: bool,
1090 },
1091 Inline,
1093 InlineBlock,
1095 Flex,
1097 Float(LayoutFloat),
1099 OutOfFlow(LayoutPosition),
1101 Table,
1103 TableRowGroup,
1105 TableRow,
1107 TableCell,
1109 TableColumnGroup,
1111 TableCaption,
1113 Grid,
1115 Contents,
1117 None,
1119}
1120
1121impl fmt::Debug for FormattingContext {
1122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1123 match self {
1124 Self::Block {
1125 establishes_new_context,
1126 } => write!(
1127 f,
1128 "Block {{ establishes_new_context: {establishes_new_context:?} }}"
1129 ),
1130 Self::Inline => write!(f, "Inline"),
1131 Self::InlineBlock => write!(f, "InlineBlock"),
1132 Self::Flex => write!(f, "Flex"),
1133 Self::Float(layout_float) => write!(f, "Float({layout_float:?})"),
1134 Self::OutOfFlow(layout_position) => {
1135 write!(f, "OutOfFlow({layout_position:?})")
1136 }
1137 Self::Grid => write!(f, "Grid"),
1138 Self::None => write!(f, "None"),
1139 Self::Table => write!(f, "Table"),
1140 Self::TableRowGroup => write!(f, "TableRowGroup"),
1141 Self::TableRow => write!(f, "TableRow"),
1142 Self::TableCell => write!(f, "TableCell"),
1143 Self::TableColumnGroup => write!(f, "TableColumnGroup"),
1144 Self::TableCaption => write!(f, "TableCaption"),
1145 Self::Contents => write!(f, "Contents"),
1146 }
1147 }
1148}
1149
1150impl Default for FormattingContext {
1151 fn default() -> Self {
1152 Self::Block {
1153 establishes_new_context: false,
1154 }
1155 }
1156}
1157
1158#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1160#[repr(C)]
1161pub enum On {
1162 MouseOver,
1164 MouseDown,
1167 LeftMouseDown,
1170 MiddleMouseDown,
1173 RightMouseDown,
1176 MouseUp,
1178 LeftMouseUp,
1181 MiddleMouseUp,
1184 RightMouseUp,
1187 MouseEnter,
1189 MouseLeave,
1191 Scroll,
1193 TextInput,
1196 VirtualKeyDown,
1204 VirtualKeyUp,
1206 HoveredFile,
1208 DroppedFile,
1210 HoveredFileCancelled,
1212 FocusReceived,
1214 FocusLost,
1216
1217 Default,
1220 Collapse,
1222 Expand,
1224 Increment,
1226 Decrement,
1228 DocumentEdit,
1234}
1235
1236#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1238#[repr(C)]
1239pub struct VirtualViewNode {
1240 pub callback: VirtualViewCallback,
1242 pub refany: RefAny,
1244}
1245
1246#[repr(C, u8)]
1248#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1249pub enum IdOrClass {
1250 Id(AzString),
1251 Class(AzString),
1252}
1253
1254impl_option!(
1255 IdOrClass,
1256 OptionIdOrClass,
1257 copy = false,
1258 [Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord]
1259);
1260
1261impl_vec!(IdOrClass, IdOrClassVec, IdOrClassVecDestructor, IdOrClassVecDestructorType, IdOrClassVecSlice, OptionIdOrClass);
1262impl_vec_debug!(IdOrClass, IdOrClassVec);
1263impl_vec_partialord!(IdOrClass, IdOrClassVec);
1264impl_vec_ord!(IdOrClass, IdOrClassVec);
1265impl_vec_clone!(IdOrClass, IdOrClassVec, IdOrClassVecDestructor);
1266impl_vec_partialeq!(IdOrClass, IdOrClassVec);
1267impl_vec_eq!(IdOrClass, IdOrClassVec);
1268impl_vec_hash!(IdOrClass, IdOrClassVec);
1269
1270impl IdOrClass {
1271 #[must_use] pub fn as_id(&self) -> Option<&str> {
1272 match self {
1273 Self::Id(s) => Some(s.as_str()),
1274 Self::Class(_) => None,
1275 }
1276 }
1277 #[must_use] pub fn as_class(&self) -> Option<&str> {
1278 match self {
1279 Self::Class(s) => Some(s.as_str()),
1280 Self::Id(_) => None,
1281 }
1282 }
1283}
1284
1285#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1287#[repr(C)]
1288pub struct AttributeNameValue {
1289 pub attr_name: AzString,
1290 pub value: AzString,
1291}
1292
1293#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1299#[repr(C, u8)]
1300pub enum AttributeType {
1301 Id(AzString),
1303 Class(AzString),
1305 AriaLabel(AzString),
1307 AriaLabelledBy(AzString),
1309 AriaDescribedBy(AzString),
1311 AriaRole(AzString),
1313 AriaState(AttributeNameValue),
1315 AriaProperty(AttributeNameValue),
1317
1318 Href(AzString),
1320 Rel(AzString),
1322 Target(AzString),
1324
1325 Src(AzString),
1327 Alt(AzString),
1329 Title(AzString),
1331
1332 Name(AzString),
1334 Value(AzString),
1336 InputType(AzString),
1338 Placeholder(AzString),
1340 Required,
1342 Disabled,
1344 Readonly,
1346 CheckedTrue,
1348 CheckedFalse,
1350 Selected,
1352 Max(AzString),
1354 Min(AzString),
1356 Step(AzString),
1358 Pattern(AzString),
1360 MinLength(i32),
1362 MaxLength(i32),
1364 Autocomplete(AzString),
1366
1367 Scope(AzString),
1369 ColSpan(i32),
1371 RowSpan(i32),
1373
1374 TabIndex(i32),
1376 Focusable,
1378
1379 Lang(AzString),
1381 Dir(AzString),
1383
1384 ContentEditable(bool),
1386 Draggable(bool),
1388 Hidden,
1390
1391 Data(AttributeNameValue),
1393 Custom(AttributeNameValue),
1395}
1396
1397impl_option!(
1398 AttributeType,
1399 OptionAttributeType,
1400 copy = false,
1401 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1402);
1403
1404impl_vec!(AttributeType, AttributeTypeVec, AttributeTypeVecDestructor, AttributeTypeVecDestructorType, AttributeTypeVecSlice, OptionAttributeType);
1405impl_vec_debug!(AttributeType, AttributeTypeVec);
1406impl_vec_partialord!(AttributeType, AttributeTypeVec);
1407impl_vec_ord!(AttributeType, AttributeTypeVec);
1408impl_vec_clone!(AttributeType, AttributeTypeVec, AttributeTypeVecDestructor);
1409impl_vec_partialeq!(AttributeType, AttributeTypeVec);
1410impl_vec_eq!(AttributeType, AttributeTypeVec);
1411impl_vec_hash!(AttributeType, AttributeTypeVec);
1412
1413impl AttributeType {
1414 #[must_use] pub fn as_id(&self) -> Option<&str> {
1416 match self {
1417 Self::Id(s) => Some(s.as_str()),
1418 _ => None,
1419 }
1420 }
1421 #[must_use] pub fn as_class(&self) -> Option<&str> {
1423 match self {
1424 Self::Class(s) => Some(s.as_str()),
1425 _ => None,
1426 }
1427 }
1428 #[must_use] pub fn name(&self) -> &str {
1430 match self {
1431 Self::Id(_) => "id",
1432 Self::Class(_) => "class",
1433 Self::AriaLabel(_) => "aria-label",
1434 Self::AriaLabelledBy(_) => "aria-labelledby",
1435 Self::AriaDescribedBy(_) => "aria-describedby",
1436 Self::AriaRole(_) => "role",
1437 Self::AriaState(nv)
1438 | Self::AriaProperty(nv)
1439 | Self::Data(nv)
1440 | Self::Custom(nv) => nv.attr_name.as_str(),
1441 Self::Href(_) => "href",
1442 Self::Rel(_) => "rel",
1443 Self::Target(_) => "target",
1444 Self::Src(_) => "src",
1445 Self::Alt(_) => "alt",
1446 Self::Title(_) => "title",
1447 Self::Name(_) => "name",
1448 Self::Value(_) => "value",
1449 Self::InputType(_) => "type",
1450 Self::Placeholder(_) => "placeholder",
1451 Self::Required => "required",
1452 Self::Disabled => "disabled",
1453 Self::Readonly => "readonly",
1454 Self::CheckedTrue | Self::CheckedFalse => "checked",
1455 Self::Selected => "selected",
1456 Self::Max(_) => "max",
1457 Self::Min(_) => "min",
1458 Self::Step(_) => "step",
1459 Self::Pattern(_) => "pattern",
1460 Self::MinLength(_) => "minlength",
1461 Self::MaxLength(_) => "maxlength",
1462 Self::Autocomplete(_) => "autocomplete",
1463 Self::Scope(_) => "scope",
1464 Self::ColSpan(_) => "colspan",
1465 Self::RowSpan(_) => "rowspan",
1466 Self::TabIndex(_) | Self::Focusable => "tabindex",
1467 Self::Lang(_) => "lang",
1468 Self::Dir(_) => "dir",
1469 Self::ContentEditable(_) => "contenteditable",
1470 Self::Draggable(_) => "draggable",
1471 Self::Hidden => "hidden",
1472 }
1473 }
1474
1475 #[must_use] pub fn value(&self) -> AzString {
1477 match self {
1478 Self::Id(v)
1479 | Self::Class(v)
1480 | Self::AriaLabel(v)
1481 | Self::AriaLabelledBy(v)
1482 | Self::AriaDescribedBy(v)
1483 | Self::AriaRole(v)
1484 | Self::Href(v)
1485 | Self::Rel(v)
1486 | Self::Target(v)
1487 | Self::Src(v)
1488 | Self::Alt(v)
1489 | Self::Title(v)
1490 | Self::Name(v)
1491 | Self::Value(v)
1492 | Self::InputType(v)
1493 | Self::Placeholder(v)
1494 | Self::Max(v)
1495 | Self::Min(v)
1496 | Self::Step(v)
1497 | Self::Pattern(v)
1498 | Self::Autocomplete(v)
1499 | Self::Scope(v)
1500 | Self::Lang(v)
1501 | Self::Dir(v) => v.clone(),
1502
1503 Self::AriaState(nv)
1504 | Self::AriaProperty(nv)
1505 | Self::Data(nv)
1506 | Self::Custom(nv) => nv.value.clone(),
1507
1508 Self::MinLength(n)
1509 | Self::MaxLength(n)
1510 | Self::ColSpan(n)
1511 | Self::RowSpan(n)
1512 | Self::TabIndex(n) => n.to_string().into(),
1513
1514 Self::Focusable => "0".into(),
1515 Self::ContentEditable(b) | Self::Draggable(b) => {
1516 if *b {
1517 "true".into()
1518 } else {
1519 "false".into()
1520 }
1521 }
1522
1523 Self::Required
1524 | Self::Disabled
1525 | Self::Readonly
1526 | Self::CheckedTrue
1527 | Self::CheckedFalse
1528 | Self::Selected
1529 | Self::Hidden => "".into(), }
1531 }
1532
1533 #[must_use] pub const fn is_boolean(&self) -> bool {
1535 matches!(
1536 self,
1537 Self::Required
1538 | Self::Disabled
1539 | Self::Readonly
1540 | Self::CheckedTrue
1541 | Self::CheckedFalse
1542 | Self::Selected
1543 | Self::Hidden
1544 )
1545 }
1546}
1547
1548#[repr(C)]
1551#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
1552pub struct NodeData {
1553 pub node_type: NodeType,
1555 pub callbacks: CoreCallbackDataVec,
1559 pub style: azul_css::css::Css,
1566 pub flags: NodeFlags,
1568 pub accessibility: Option<Box<AccessibilityInfo>>,
1571 extra: Option<Box<NodeDataExt>>,
1576}
1577
1578impl_option!(
1579 NodeData,
1580 OptionNodeData,
1581 copy = false,
1582 [Debug, PartialEq, Eq, PartialOrd, Ord]
1583);
1584
1585impl Hash for NodeData {
1586 fn hash<H: Hasher>(&self, state: &mut H) {
1587 self.node_type.hash(state);
1588 self.attributes().as_ref().hash(state);
1589 self.flags.hash(state);
1590
1591 for callback in self.callbacks.as_ref() {
1594 callback.event.hash(state);
1595 callback.callback.hash(state);
1596 callback.refany.get_type_id().hash(state);
1597 }
1598
1599 for (prop, _conds) in self.style.iter_inline_properties() {
1603 mem::discriminant(prop).hash(state);
1604 }
1605 if let Some(ext) = self.extra.as_ref() {
1606 if let Some(ds) = ext.dataset.as_ref() {
1607 ds.hash(state);
1608 }
1609 if let Some(c) = ext.svg_data.as_ref() {
1610 c.hash(state);
1611 }
1612 if let Some(c) = ext.menu_bar.as_ref() {
1613 c.hash(state);
1614 }
1615 if let Some(c) = ext.context_menu.as_ref() {
1616 c.hash(state);
1617 }
1618 if let Some(vv) = ext.virtual_view.as_ref() {
1619 vv.hash(state);
1620 }
1621 }
1622 }
1623}
1624
1625#[derive(Debug, Clone, PartialEq)]
1633pub struct ComponentOrigin {
1634 pub component_id: AzString,
1636 pub data_model_json: crate::json::Json,
1640}
1641
1642impl Eq for ComponentOrigin {}
1645
1646impl PartialOrd for ComponentOrigin {
1647 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
1648 Some(self.cmp(other))
1649 }
1650}
1651
1652impl Ord for ComponentOrigin {
1653 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
1654 self.component_id.cmp(&other.component_id)
1655 .then_with(|| {
1656 let a = alloc::format!("{}", self.data_model_json);
1657 let b = alloc::format!("{}", other.data_model_json);
1658 a.cmp(&b)
1659 })
1660 }
1661}
1662
1663impl Hash for ComponentOrigin {
1664 fn hash<H: Hasher>(&self, state: &mut H) {
1665 self.component_id.hash(state);
1666 alloc::format!("{}", self.data_model_json).hash(state);
1667 }
1668}
1669
1670impl Default for ComponentOrigin {
1671 fn default() -> Self {
1672 Self {
1673 component_id: AzString::from_const_str(""),
1674 data_model_json: crate::json::Json::null(),
1675 }
1676 }
1677}
1678
1679#[derive(Debug, Clone, PartialOrd)]
1684pub enum SvgNodeData {
1685 ImageClipMask(ImageMask),
1687 Path(crate::svg::SvgMultiPolygon),
1689 Circle { cx: f32, cy: f32, r: f32 },
1691 Rect { x: f32, y: f32, width: f32, height: f32, rx: f32, ry: f32 },
1693 Ellipse { cx: f32, cy: f32, rx: f32, ry: f32 },
1695 Line { x1: f32, y1: f32, x2: f32, y2: f32 },
1697 PointsList { points: alloc::vec::Vec<azul_css::props::basic::SvgPoint>, closed: bool },
1699 ViewBox { min_x: f32, min_y: f32, width: f32, height: f32 },
1701 LinearGradient { x1: f32, y1: f32, x2: f32, y2: f32 },
1703 RadialGradient { cx: f32, cy: f32, r: f32, fx: f32, fy: f32 },
1705 GradientStop { offset: f32 },
1707 Use { href: AzString, x: f32, y: f32 },
1709 SvgImageData { href: AzString, x: f32, y: f32, width: f32, height: f32 },
1711}
1712
1713impl PartialEq for SvgNodeData {
1718 #[allow(clippy::match_same_arms, clippy::similar_names)] fn eq(&self, other: &Self) -> bool {
1720 const fn fb(a: f32, b: f32) -> bool {
1722 a.to_bits() == b.to_bits()
1723 }
1724 use self::SvgNodeData::{
1725 Circle, Ellipse, GradientStop, ImageClipMask, Line, LinearGradient, Path,
1726 PointsList, RadialGradient, Rect, SvgImageData, Use, ViewBox,
1727 };
1728 match (self, other) {
1729 (ImageClipMask(a), ImageClipMask(b)) => a == b,
1730 (Path(a), Path(b)) => {
1731 let ra = a.rings.as_ref();
1732 let rb = b.rings.as_ref();
1733 ra.len() == rb.len()
1734 && ra.iter().zip(rb.iter()).all(|(x, y)| {
1735 let ia = x.items.as_ref();
1736 let ib = y.items.as_ref();
1737 ia.len() == ib.len()
1738 && ia.iter().zip(ib.iter()).all(|(p, q)| svg_path_element_bits_eq(p, q))
1739 })
1740 }
1741 (Circle { cx, cy, r }, Circle { cx: cx2, cy: cy2, r: r2 }) => {
1742 fb(*cx, *cx2) && fb(*cy, *cy2) && fb(*r, *r2)
1743 }
1744 (
1745 Rect { x, y, width, height, rx, ry },
1746 Rect { x: x2, y: y2, width: w2, height: h2, rx: rx2, ry: ry2 },
1747 ) => {
1748 fb(*x, *x2) && fb(*y, *y2) && fb(*width, *w2)
1749 && fb(*height, *h2) && fb(*rx, *rx2) && fb(*ry, *ry2)
1750 }
1751 (Ellipse { cx, cy, rx, ry }, Ellipse { cx: cx2, cy: cy2, rx: rx2, ry: ry2 }) => {
1752 fb(*cx, *cx2) && fb(*cy, *cy2) && fb(*rx, *rx2) && fb(*ry, *ry2)
1753 }
1754 (Line { x1, y1, x2, y2 }, Line { x1: a1, y1: b1, x2: a2, y2: b2 })
1755 | (LinearGradient { x1, y1, x2, y2 }, LinearGradient { x1: a1, y1: b1, x2: a2, y2: b2 }) => {
1756 fb(*x1, *a1) && fb(*y1, *b1) && fb(*x2, *a2) && fb(*y2, *b2)
1757 }
1758 (PointsList { points: pa, closed: ca }, PointsList { points: pb, closed: cb }) => {
1759 ca == cb
1760 && pa.len() == pb.len()
1761 && pa.iter().zip(pb.iter()).all(|(p, q)| fb(p.x, q.x) && fb(p.y, q.y))
1762 }
1763 (
1764 ViewBox { min_x, min_y, width, height },
1765 ViewBox { min_x: a, min_y: b, width: w, height: h },
1766 ) => fb(*min_x, *a) && fb(*min_y, *b) && fb(*width, *w) && fb(*height, *h),
1767 (
1768 RadialGradient { cx, cy, r, fx, fy },
1769 RadialGradient { cx: cx2, cy: cy2, r: r2, fx: fx2, fy: fy2 },
1770 ) => {
1771 fb(*cx, *cx2) && fb(*cy, *cy2) && fb(*r, *r2) && fb(*fx, *fx2) && fb(*fy, *fy2)
1772 }
1773 (GradientStop { offset: a }, GradientStop { offset: b }) => fb(*a, *b),
1774 (Use { href, x, y }, Use { href: h2, x: x2, y: y2 }) => {
1775 href == h2 && fb(*x, *x2) && fb(*y, *y2)
1776 }
1777 (
1778 SvgImageData { href, x, y, width, height },
1779 SvgImageData { href: h2, x: x2, y: y2, width: w2, height: hh2 },
1780 ) => {
1781 href == h2 && fb(*x, *x2) && fb(*y, *y2) && fb(*width, *w2) && fb(*height, *hh2)
1782 }
1783 _ => false,
1785 }
1786 }
1787}
1788
1789const fn svg_path_element_bits_eq(
1792 a: &crate::svg::SvgPathElement,
1793 b: &crate::svg::SvgPathElement,
1794) -> bool {
1795 use crate::svg::SvgPathElement::{CubicCurve, Line, QuadraticCurve};
1796 const fn pb(a: azul_css::props::basic::SvgPoint, b: azul_css::props::basic::SvgPoint) -> bool {
1797 a.x.to_bits() == b.x.to_bits() && a.y.to_bits() == b.y.to_bits()
1798 }
1799 match (a, b) {
1800 (Line(a), Line(b)) => pb(a.start, b.start) && pb(a.end, b.end),
1801 (QuadraticCurve(a), QuadraticCurve(b)) => {
1802 pb(a.start, b.start) && pb(a.ctrl, b.ctrl) && pb(a.end, b.end)
1803 }
1804 (CubicCurve(a), CubicCurve(b)) => {
1805 pb(a.start, b.start) && pb(a.ctrl_1, b.ctrl_1)
1806 && pb(a.ctrl_2, b.ctrl_2) && pb(a.end, b.end)
1807 }
1808 _ => false,
1809 }
1810}
1811
1812impl Eq for SvgNodeData {}
1813
1814#[allow(clippy::derive_ord_xor_partial_ord)]
1818impl Ord for SvgNodeData {
1819 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
1820 self.partial_cmp(other).unwrap_or(core::cmp::Ordering::Equal)
1821 }
1822}
1823
1824impl Hash for SvgNodeData {
1825 fn hash<H: Hasher>(&self, state: &mut H) {
1826 mem::discriminant(self).hash(state);
1827 match self {
1828 Self::ImageClipMask(m) => m.hash(state),
1829 Self::Path(mp) => {
1830 for ring in mp.rings.as_ref() {
1831 for item in ring.items.as_ref() {
1832 match item {
1833 crate::svg::SvgPathElement::Line(l) => {
1834 0u8.hash(state);
1835 l.start.x.to_bits().hash(state);
1836 l.start.y.to_bits().hash(state);
1837 l.end.x.to_bits().hash(state);
1838 l.end.y.to_bits().hash(state);
1839 }
1840 crate::svg::SvgPathElement::QuadraticCurve(q) => {
1841 1u8.hash(state);
1842 q.start.x.to_bits().hash(state);
1843 q.start.y.to_bits().hash(state);
1844 q.ctrl.x.to_bits().hash(state);
1845 q.ctrl.y.to_bits().hash(state);
1846 q.end.x.to_bits().hash(state);
1847 q.end.y.to_bits().hash(state);
1848 }
1849 crate::svg::SvgPathElement::CubicCurve(c) => {
1850 2u8.hash(state);
1851 c.start.x.to_bits().hash(state);
1852 c.start.y.to_bits().hash(state);
1853 c.ctrl_1.x.to_bits().hash(state);
1854 c.ctrl_1.y.to_bits().hash(state);
1855 c.ctrl_2.x.to_bits().hash(state);
1856 c.ctrl_2.y.to_bits().hash(state);
1857 c.end.x.to_bits().hash(state);
1858 c.end.y.to_bits().hash(state);
1859 }
1860 }
1861 }
1862 }
1863 }
1864 Self::Circle { cx, cy, r } => {
1865 cx.to_bits().hash(state); cy.to_bits().hash(state); r.to_bits().hash(state);
1866 }
1867 Self::Rect { x, y, width, height, rx, ry } => {
1868 x.to_bits().hash(state); y.to_bits().hash(state);
1869 width.to_bits().hash(state); height.to_bits().hash(state);
1870 rx.to_bits().hash(state); ry.to_bits().hash(state);
1871 }
1872 Self::Ellipse { cx, cy, rx, ry } => {
1873 cx.to_bits().hash(state); cy.to_bits().hash(state);
1874 rx.to_bits().hash(state); ry.to_bits().hash(state);
1875 }
1876 Self::Line { x1, y1, x2, y2 } | Self::LinearGradient { x1, y1, x2, y2 } => {
1879 x1.to_bits().hash(state); y1.to_bits().hash(state);
1880 x2.to_bits().hash(state); y2.to_bits().hash(state);
1881 }
1882 Self::PointsList { points, closed } => {
1883 for p in points {
1884 p.x.to_bits().hash(state); p.y.to_bits().hash(state);
1885 }
1886 closed.hash(state);
1887 }
1888 Self::ViewBox { min_x, min_y, width, height } => {
1889 min_x.to_bits().hash(state); min_y.to_bits().hash(state);
1890 width.to_bits().hash(state); height.to_bits().hash(state);
1891 }
1892 Self::RadialGradient { cx, cy, r, fx, fy } => {
1893 cx.to_bits().hash(state); cy.to_bits().hash(state);
1894 r.to_bits().hash(state); fx.to_bits().hash(state);
1895 fy.to_bits().hash(state);
1896 }
1897 Self::GradientStop { offset } => {
1898 offset.to_bits().hash(state);
1899 }
1900 Self::Use { href, x, y } => {
1901 href.hash(state);
1902 x.to_bits().hash(state); y.to_bits().hash(state);
1903 }
1904 Self::SvgImageData { href, x, y, width, height } => {
1905 href.hash(state);
1906 x.to_bits().hash(state); y.to_bits().hash(state);
1907 width.to_bits().hash(state); height.to_bits().hash(state);
1908 }
1909 }
1910 }
1911}
1912
1913#[repr(C)]
1917#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1918pub struct NodeDataExt {
1919 pub attributes: AttributeTypeVec,
1923 pub virtual_view: Option<VirtualViewNode>,
1925 pub dataset: Option<RefAny>,
1927 pub svg_data: Option<SvgNodeData>,
1929 pub menu_bar: Option<Box<Menu>>,
1931 pub context_menu: Option<Box<Menu>>,
1933 pub key: Option<u64>,
1937 pub dataset_merge_callback: Option<DatasetMergeCallback>,
1940 pub component_origin: Option<ComponentOrigin>,
1946}
1947
1948#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1972#[repr(C)]
1973pub struct DatasetMergeCallback {
1974 pub cb: DatasetMergeCallbackType,
1977 pub callable: OptionRefAny,
1980}
1981
1982impl fmt::Debug for DatasetMergeCallback {
1983 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1984 f.debug_struct("DatasetMergeCallback")
1985 .field("cb", &(self.cb as usize))
1986 .field("callable", &self.callable)
1987 .finish()
1988 }
1989}
1990
1991impl From<DatasetMergeCallbackType> for DatasetMergeCallback {
1994 fn from(cb: DatasetMergeCallbackType) -> Self {
1995 Self {
1996 cb,
1997 callable: OptionRefAny::None,
1998 }
1999 }
2000}
2001
2002impl DatasetMergeCallback {
2003 #[must_use]
2007 pub fn from_ptr(cb: DatasetMergeCallbackType) -> Self {
2008 Self::from(cb)
2009 }
2010}
2011
2012impl_option!(
2013 DatasetMergeCallback,
2014 OptionDatasetMergeCallback,
2015 copy = false,
2016 [Debug, Clone]
2017);
2018
2019pub type DatasetMergeCallbackType = extern "C" fn(RefAny, RefAny) -> RefAny;
2028
2029impl Clone for NodeData {
2030 #[inline]
2031 fn clone(&self) -> Self {
2032 Self {
2033 node_type: self.node_type.to_library_owned_nodetype(),
2034 style: self.style.clone(),
2035 callbacks: self.callbacks.clone(),
2036 flags: self.flags,
2037 accessibility: self.accessibility.clone(),
2038 extra: self.extra.clone(),
2039 }
2040 }
2041}
2042
2043impl_vec!(NodeData, NodeDataVec, NodeDataVecDestructor, NodeDataVecDestructorType, NodeDataVecSlice, OptionNodeData);
2045impl_vec_clone!(NodeData, NodeDataVec, NodeDataVecDestructor);
2046impl_vec_mut!(NodeData, NodeDataVec);
2047impl_vec_debug!(NodeData, NodeDataVec);
2048impl_vec_partialord!(NodeData, NodeDataVec);
2049impl_vec_ord!(NodeData, NodeDataVec);
2050impl_vec_partialeq!(NodeData, NodeDataVec);
2051impl_vec_eq!(NodeData, NodeDataVec);
2052impl_vec_hash!(NodeData, NodeDataVec);
2053
2054impl NodeDataVec {
2055 #[inline]
2056 #[must_use] pub fn as_container(&self) -> NodeDataContainerRef<'_, NodeData> {
2057 NodeDataContainerRef {
2058 internal: self.as_ref(),
2059 }
2060 }
2061 #[inline]
2062 pub fn as_container_mut(&mut self) -> NodeDataContainerRefMut<'_, NodeData> {
2063 NodeDataContainerRefMut {
2064 internal: self.as_mut(),
2065 }
2066 }
2067}
2068
2069unsafe impl Send for NodeData {}
2073
2074#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2077#[repr(C, u8)]
2078#[derive(Default)]
2079pub enum TabIndex {
2080 #[default]
2086 Auto,
2087 OverrideInParent(u32),
2093 NoKeyboardFocus,
2096}
2097
2098impl_option!(
2099 TabIndex,
2100 OptionTabIndex,
2101 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
2102);
2103
2104impl TabIndex {
2105 #[allow(clippy::cast_possible_wrap)]
2109 #[must_use] pub const fn get_index(&self) -> isize {
2110 use self::TabIndex::{Auto, OverrideInParent, NoKeyboardFocus};
2111 match self {
2112 Auto => 0,
2113 OverrideInParent(x) => *x as isize,
2114 NoKeyboardFocus => -1,
2115 }
2116 }
2117}
2118
2119
2120#[repr(C)]
2132#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2133#[derive(Default)]
2134pub struct NodeFlags {
2135 pub inner: u32,
2136}
2137
2138
2139impl NodeFlags {
2140 const CONTENTEDITABLE_BIT: u32 = 1 << 31;
2141 const TAB_INDEX_MASK: u32 = 0b11 << 29;
2142 const ANONYMOUS_BIT: u32 = 1 << 28;
2143 const TAB_VALUE_MASK: u32 = (1 << 28) - 1;
2144
2145 const TAB_NONE: u32 = 0b00 << 29;
2146 const TAB_AUTO: u32 = 0b01 << 29;
2147 const TAB_OVERRIDE: u32 = 0b10 << 29;
2148 const TAB_NO_KEYBOARD: u32 = 0b11 << 29;
2149
2150 #[must_use] pub const fn new() -> Self {
2151 Self { inner: 0 }
2152 }
2153
2154 #[must_use] pub const fn is_contenteditable(&self) -> bool {
2155 (self.inner & Self::CONTENTEDITABLE_BIT) != 0
2156 }
2157
2158 #[must_use] pub const fn set_contenteditable(mut self, v: bool) -> Self {
2159 if v {
2160 self.inner |= Self::CONTENTEDITABLE_BIT;
2161 } else {
2162 self.inner &= !Self::CONTENTEDITABLE_BIT;
2163 }
2164 self
2165 }
2166
2167 pub const fn set_contenteditable_mut(&mut self, v: bool) {
2168 if v {
2169 self.inner |= Self::CONTENTEDITABLE_BIT;
2170 } else {
2171 self.inner &= !Self::CONTENTEDITABLE_BIT;
2172 }
2173 }
2174
2175 #[must_use] pub const fn get_tab_index(&self) -> Option<TabIndex> {
2176 match self.inner & Self::TAB_INDEX_MASK {
2177 x if x == Self::TAB_NONE => None,
2178 x if x == Self::TAB_AUTO => Some(TabIndex::Auto),
2179 x if x == Self::TAB_OVERRIDE => {
2180 let val = self.inner & Self::TAB_VALUE_MASK;
2181 Some(TabIndex::OverrideInParent(val))
2182 }
2183 x if x == Self::TAB_NO_KEYBOARD => Some(TabIndex::NoKeyboardFocus),
2184 _ => None,
2185 }
2186 }
2187
2188 #[must_use] pub const fn is_anonymous(&self) -> bool {
2190 (self.inner & Self::ANONYMOUS_BIT) != 0
2191 }
2192
2193 pub const fn set_anonymous(&mut self, v: bool) {
2194 if v {
2195 self.inner |= Self::ANONYMOUS_BIT;
2196 } else {
2197 self.inner &= !Self::ANONYMOUS_BIT;
2198 }
2199 }
2200
2201 pub const fn set_tab_index(&mut self, tab_index: Option<TabIndex>) {
2202 self.inner &= Self::CONTENTEDITABLE_BIT | Self::ANONYMOUS_BIT;
2205 match tab_index {
2206 None => { }
2207 Some(TabIndex::Auto) => {
2208 self.inner |= Self::TAB_AUTO;
2209 }
2210 Some(TabIndex::OverrideInParent(val)) => {
2211 self.inner |= Self::TAB_OVERRIDE | (val & Self::TAB_VALUE_MASK);
2212 }
2213 Some(TabIndex::NoKeyboardFocus) => {
2214 self.inner |= Self::TAB_NO_KEYBOARD;
2215 }
2216 }
2217 }
2218}
2219
2220impl Default for NodeData {
2221 fn default() -> Self {
2222 Self::create_node(NodeType::Div)
2223 }
2224}
2225
2226impl fmt::Display for NodeData {
2227 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2228 let html_type = self.node_type.get_path();
2229 let attributes_string = node_data_to_string(self);
2230
2231 match self.node_type.format() {
2232 Some(content) => write!(
2233 f,
2234 "<{html_type}{attributes_string}>{content}</{html_type}>"
2235 ),
2236 None => write!(f, "<{html_type}{attributes_string}/>"),
2237 }
2238 }
2239}
2240
2241fn node_data_to_string(node_data: &NodeData) -> String {
2242 let mut id_string = String::new();
2243 let ids = node_data
2244 .attributes()
2245 .as_ref()
2246 .iter()
2247 .filter_map(|s| s.as_id())
2248 .collect::<Vec<_>>()
2249 .join(" ");
2250
2251 if !ids.is_empty() {
2252 id_string = format!(" id=\"{ids}\" ");
2253 }
2254
2255 let mut class_string = String::new();
2256 let classes = node_data
2257 .attributes()
2258 .as_ref()
2259 .iter()
2260 .filter_map(|s| s.as_class())
2261 .collect::<Vec<_>>()
2262 .join(" ");
2263
2264 if !classes.is_empty() {
2265 class_string = format!(" class=\"{classes}\" ");
2266 }
2267
2268 let mut tabindex_string = String::new();
2269 if let Some(tab_index) = node_data.get_tab_index() {
2270 tabindex_string = format!(" tabindex=\"{}\" ", tab_index.get_index());
2271 }
2272
2273 format!("{id_string}{class_string}{tabindex_string}")
2274}
2275
2276impl NodeData {
2277 #[inline]
2279 #[must_use] pub const fn create_node(node_type: NodeType) -> Self {
2280 Self {
2281 node_type,
2282 callbacks: CoreCallbackDataVec::from_const_slice(&[]),
2283 style: azul_css::css::Css {
2284 rules: azul_css::css::CssRuleBlockVec::from_const_slice(&[]),
2285 },
2286 flags: NodeFlags::new(),
2287 accessibility: None,
2288 extra: None,
2289 }
2290 }
2291
2292 #[inline]
2295 #[must_use] pub fn attributes(&self) -> &AttributeTypeVec {
2296 static EMPTY: AttributeTypeVec = AttributeTypeVec::from_const_slice(&[]);
2297 self.extra.as_ref().map_or(&EMPTY, |ext| &ext.attributes)
2298 }
2299
2300 #[inline]
2303 pub fn attributes_mut(&mut self) -> &mut AttributeTypeVec {
2304 &mut self.extra.get_or_insert_with(|| Box::new(NodeDataExt::default())).attributes
2305 }
2306
2307 #[inline]
2309 pub fn set_attributes(&mut self, attrs: AttributeTypeVec) {
2310 self.extra.get_or_insert_with(|| Box::new(NodeDataExt::default())).attributes = attrs;
2311 }
2312
2313 #[inline]
2315 #[must_use] pub const fn create_body() -> Self {
2316 Self::create_node(NodeType::Body)
2317 }
2318
2319 #[inline]
2321 #[must_use] pub const fn create_div() -> Self {
2322 Self::create_node(NodeType::Div)
2323 }
2324
2325 #[inline]
2327 #[must_use] pub const fn create_br() -> Self {
2328 Self::create_node(NodeType::Br)
2329 }
2330
2331 #[inline]
2333 pub fn create_text<S: Into<AzString>>(value: S) -> Self {
2334 Self::create_node(NodeType::Text(BoxOrStatic::heap(value.into())))
2335 }
2336
2337 #[inline]
2339 #[must_use] pub fn create_image(image: ImageRef) -> Self {
2340 Self::create_node(NodeType::Image(BoxOrStatic::heap(image)))
2341 }
2342
2343 #[inline]
2344 pub fn create_virtual_view(data: RefAny, callback: impl Into<VirtualViewCallback>) -> Self {
2345 let mut nd = Self::create_node(NodeType::VirtualView);
2346 let ext = nd.extra.get_or_insert_with(|| Box::new(NodeDataExt::default()));
2347 ext.virtual_view = Some(VirtualViewNode {
2348 callback: callback.into(),
2349 refany: data,
2350 });
2351 nd
2352 }
2353
2354 fn with_attribute(mut self, attr: AttributeType) -> Self {
2360 let mut v = self.attributes().clone().into_library_owned_vec();
2361 v.push(attr);
2362 self.set_attributes(v.into());
2363 self
2364 }
2365
2366 #[inline]
2368 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_button(aria: SmallAriaInfo) -> Self {
2370 let mut nd = Self::create_node(NodeType::Button);
2371 nd.set_accessibility_info(aria.to_full_info());
2372 nd
2373 }
2374
2375 #[inline]
2377 #[must_use] pub const fn create_button_no_a11y() -> Self {
2378 Self::create_node(NodeType::Button)
2379 }
2380
2381 #[inline]
2383 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_a(href: AzString, aria: SmallAriaInfo) -> Self {
2385 let mut nd = Self::create_node(NodeType::A).with_attribute(AttributeType::Href(href));
2386 nd.set_accessibility_info(aria.to_full_info());
2387 nd
2388 }
2389
2390 #[inline]
2392 #[must_use] pub fn create_a_no_a11y(href: AzString) -> Self {
2393 Self::create_node(NodeType::A).with_attribute(AttributeType::Href(href))
2394 }
2395
2396 #[inline]
2398 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_input(
2400 input_type: AzString,
2401 name: AzString,
2402 label: AzString,
2403 aria: SmallAriaInfo,
2404 ) -> Self {
2405 let mut nd = Self::create_node(NodeType::Input)
2406 .with_attribute(AttributeType::InputType(input_type))
2407 .with_attribute(AttributeType::Name(name))
2408 .with_attribute(AttributeType::AriaLabel(label));
2409 nd.set_accessibility_info(aria.to_full_info());
2410 nd
2411 }
2412
2413 #[inline]
2415 #[must_use] pub fn create_input_no_a11y(input_type: AzString, name: AzString, label: AzString) -> Self {
2416 Self::create_node(NodeType::Input)
2417 .with_attribute(AttributeType::InputType(input_type))
2418 .with_attribute(AttributeType::Name(name))
2419 .with_attribute(AttributeType::AriaLabel(label))
2420 }
2421
2422 #[inline]
2424 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_textarea(name: AzString, label: AzString, aria: SmallAriaInfo) -> Self {
2426 let mut nd = Self::create_node(NodeType::TextArea)
2427 .with_attribute(AttributeType::Name(name))
2428 .with_attribute(AttributeType::AriaLabel(label));
2429 nd.set_accessibility_info(aria.to_full_info());
2430 nd
2431 }
2432
2433 #[inline]
2435 #[must_use] pub fn create_textarea_no_a11y(name: AzString, label: AzString) -> Self {
2436 Self::create_node(NodeType::TextArea)
2437 .with_attribute(AttributeType::Name(name))
2438 .with_attribute(AttributeType::AriaLabel(label))
2439 }
2440
2441 #[inline]
2443 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_select(name: AzString, label: AzString, aria: SmallAriaInfo) -> Self {
2445 let mut nd = Self::create_node(NodeType::Select)
2446 .with_attribute(AttributeType::Name(name))
2447 .with_attribute(AttributeType::AriaLabel(label));
2448 nd.set_accessibility_info(aria.to_full_info());
2449 nd
2450 }
2451
2452 #[inline]
2454 #[must_use] pub fn create_select_no_a11y(name: AzString, label: AzString) -> Self {
2455 Self::create_node(NodeType::Select)
2456 .with_attribute(AttributeType::Name(name))
2457 .with_attribute(AttributeType::AriaLabel(label))
2458 }
2459
2460 #[inline]
2462 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_table(aria: SmallAriaInfo) -> Self {
2464 let mut nd = Self::create_node(NodeType::Table);
2465 nd.set_accessibility_info(aria.to_full_info());
2466 nd
2467 }
2468
2469 #[inline]
2471 #[must_use] pub const fn create_table_no_a11y() -> Self {
2472 Self::create_node(NodeType::Table)
2473 }
2474
2475 #[inline]
2478 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_label(for_id: AzString, aria: SmallAriaInfo) -> Self {
2480 let mut nd = Self::create_node(NodeType::Label).with_attribute(AttributeType::Custom(
2481 AttributeNameValue {
2482 attr_name: "for".into(),
2483 value: for_id,
2484 },
2485 ));
2486 nd.set_accessibility_info(aria.to_full_info());
2487 nd
2488 }
2489
2490 #[inline]
2493 #[must_use] pub fn create_label_no_a11y(for_id: AzString) -> Self {
2494 Self::create_node(NodeType::Label).with_attribute(AttributeType::Custom(AttributeNameValue {
2495 attr_name: "for".into(),
2496 value: for_id,
2497 }))
2498 }
2499
2500 #[inline]
2502 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn is_node_type(&self, searched_type: NodeType) -> bool {
2504 self.node_type == searched_type
2505 }
2506
2507 #[must_use] pub fn has_id(&self, id: &str) -> bool {
2509 self.attributes()
2510 .iter()
2511 .any(|attr| attr.as_id() == Some(id))
2512 }
2513
2514 #[must_use] pub fn has_class(&self, class: &str) -> bool {
2516 self.attributes()
2517 .iter()
2518 .any(|attr| attr.as_class() == Some(class))
2519 }
2520
2521 #[must_use] pub fn has_context_menu(&self) -> bool {
2522 self.extra
2523 .as_ref()
2524 .is_some_and(|m| m.context_menu.is_some())
2525 }
2526
2527 #[must_use] pub const fn is_text_node(&self) -> bool {
2528 matches!(self.node_type, NodeType::Text(_))
2529 }
2530
2531 #[must_use] pub const fn is_virtual_view_node(&self) -> bool {
2532 matches!(self.node_type, NodeType::VirtualView)
2533 }
2534
2535 #[inline]
2539 #[must_use] pub const fn get_node_type(&self) -> &NodeType {
2540 &self.node_type
2541 }
2542 #[inline]
2543 pub fn get_dataset_mut(&mut self) -> Option<&mut RefAny> {
2544 self.extra.as_mut().and_then(|e| e.dataset.as_mut())
2545 }
2546 #[inline]
2547 #[must_use] pub fn get_dataset(&self) -> Option<&RefAny> {
2548 self.extra.as_ref().and_then(|e| e.dataset.as_ref())
2549 }
2550 pub fn take_dataset(&mut self) -> Option<RefAny> {
2552 self.extra.as_mut().and_then(|e| e.dataset.take())
2553 }
2554 #[inline]
2557 #[must_use] pub fn get_ids_and_classes(&self) -> IdOrClassVec {
2558 let v: Vec<IdOrClass> = self.attributes().as_ref().iter().filter_map(|attr| {
2559 match attr {
2560 AttributeType::Id(s) => Some(IdOrClass::Id(s.clone())),
2561 AttributeType::Class(s) => Some(IdOrClass::Class(s.clone())),
2562 _ => None,
2563 }
2564 }).collect();
2565 v.into()
2566 }
2567 #[inline]
2568 #[must_use] pub const fn get_callbacks(&self) -> &CoreCallbackDataVec {
2569 &self.callbacks
2570 }
2571 #[inline]
2572 #[must_use] pub const fn get_style(&self) -> &azul_css::css::Css {
2573 &self.style
2574 }
2575
2576 #[inline]
2577 #[must_use] pub fn get_svg_data(&self) -> Option<&SvgNodeData> {
2578 self.extra.as_ref().and_then(|e| e.svg_data.as_ref())
2579 }
2580
2581 #[inline]
2583 #[must_use] pub fn get_image_clip_mask(&self) -> Option<&ImageMask> {
2584 match self.get_svg_data()? {
2585 SvgNodeData::ImageClipMask(m) => Some(m),
2586 _ => None,
2587 }
2588 }
2589 #[inline]
2590 #[must_use] pub const fn get_tab_index(&self) -> Option<TabIndex> {
2591 self.flags.get_tab_index()
2592 }
2593 #[inline]
2594 #[must_use] pub fn get_accessibility_info(&self) -> Option<&AccessibilityInfo> {
2595 self.accessibility.as_deref()
2596 }
2597 #[inline]
2598 #[must_use] pub fn get_menu_bar(&self) -> Option<&Menu> {
2599 self.extra.as_ref().and_then(|e| e.menu_bar.as_deref())
2600 }
2601 #[inline]
2602 #[must_use] pub fn get_context_menu(&self) -> Option<&Menu> {
2603 self.extra.as_ref().and_then(|e| e.context_menu.as_deref())
2604 }
2605
2606 #[inline]
2608 #[must_use] pub const fn is_anonymous(&self) -> bool {
2609 self.flags.is_anonymous()
2610 }
2611
2612 #[inline]
2613 pub fn set_node_type(&mut self, node_type: NodeType) {
2614 self.node_type = node_type;
2615 }
2616 #[inline]
2617 pub fn set_dataset(&mut self, data: OptionRefAny) {
2618 match data {
2619 OptionRefAny::None => {
2620 if let Some(ext) = self.extra.as_mut() {
2621 ext.dataset = None;
2622 }
2623 }
2624 OptionRefAny::Some(r) => {
2625 self.extra
2626 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2627 .dataset = Some(r);
2628 }
2629 }
2630 }
2631 #[inline]
2635 #[allow(clippy::needless_pass_by_value)] pub fn set_ids_and_classes(&mut self, ids_and_classes: IdOrClassVec) {
2637 let mut v: AttributeTypeVec = Vec::new().into();
2639 mem::swap(&mut v, self.attributes_mut());
2640 let mut v = v.into_library_owned_vec();
2641 v.retain(|a| !matches!(a, AttributeType::Id(_) | AttributeType::Class(_)));
2642 for ioc in ids_and_classes.as_ref() {
2644 match ioc {
2645 IdOrClass::Id(s) => v.push(AttributeType::Id(s.clone())),
2646 IdOrClass::Class(s) => v.push(AttributeType::Class(s.clone())),
2647 }
2648 }
2649 self.set_attributes(v.into());
2650 }
2651 #[inline]
2652 pub fn set_callbacks(&mut self, callbacks: CoreCallbackDataVec) {
2653 self.callbacks = callbacks;
2654 }
2655 #[inline]
2659 pub fn set_css_props(&mut self, css_props: CssPropertyWithConditionsVec) {
2660 self.style = css_props.into();
2661 }
2662 pub fn upsert_inline_css_property(&mut self, prop: azul_css::props::property::CssProperty) {
2677 use azul_css::css::{rule_priority, CssDeclaration, CssPath, CssRuleBlock};
2678
2679 let ty = prop.get_type();
2680 let mut rules = mem::take(&mut self.style.rules).into_library_owned_vec();
2681 for rule in &mut rules {
2682 if !rule.conditions.as_ref().is_empty() {
2683 continue;
2684 }
2685 let mut decls = mem::take(&mut rule.declarations).into_library_owned_vec();
2686 decls.retain(|d| match d {
2687 CssDeclaration::Static(p) => p.get_type() != ty,
2688 CssDeclaration::Dynamic(_) => true,
2689 });
2690 rule.declarations = decls.into();
2691 }
2692 rules.retain(|r| !r.declarations.as_ref().is_empty());
2694 rules.push(CssRuleBlock {
2695 path: CssPath {
2696 selectors: Vec::new().into(),
2697 },
2698 declarations: alloc::vec![CssDeclaration::Static(prop)].into(),
2699 conditions: Vec::new().into(),
2700 priority: rule_priority::INLINE,
2701 });
2702 self.style.rules = rules.into();
2703 }
2704 #[inline]
2707 pub fn set_style(&mut self, style: azul_css::css::Css) {
2708 self.style = style;
2709 }
2710 #[inline]
2711 pub fn set_clip_mask(&mut self, clip_mask: ImageMask) {
2712 self.extra
2713 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2714 .svg_data = Some(SvgNodeData::ImageClipMask(clip_mask));
2715 }
2716 #[inline]
2717 pub fn set_svg_data(&mut self, data: SvgNodeData) {
2718 self.extra
2719 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2720 .svg_data = Some(data);
2721 }
2722 #[inline]
2723 pub const fn set_tab_index(&mut self, tab_index: TabIndex) {
2724 self.flags.set_tab_index(Some(tab_index));
2725 }
2726 #[inline]
2727 pub const fn set_contenteditable(&mut self, contenteditable: bool) {
2728 self.flags.set_contenteditable_mut(contenteditable);
2729 }
2730 #[inline]
2731 #[must_use] pub const fn is_contenteditable(&self) -> bool {
2732 self.flags.is_contenteditable()
2733 }
2734 #[inline]
2735 pub fn set_accessibility_info(&mut self, accessibility_info: AccessibilityInfo) {
2736 self.accessibility = Some(Box::new(accessibility_info));
2737 }
2738
2739 #[inline]
2741 pub const fn set_anonymous(&mut self, is_anonymous: bool) {
2742 self.flags.set_anonymous(is_anonymous);
2743 }
2744 #[inline]
2745 pub fn set_menu_bar(&mut self, menu_bar: Menu) {
2746 self.extra
2747 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2748 .menu_bar = Some(Box::new(menu_bar));
2749 }
2750 #[inline]
2751 pub fn set_context_menu(&mut self, context_menu: Menu) {
2752 self.extra
2753 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2754 .context_menu = Some(Box::new(context_menu));
2755 }
2756
2757 #[inline]
2770 pub fn set_key<K: Hash>(&mut self, key: K) {
2771 use core::hash::Hasher;
2772 let mut hasher = crate::hash::DefaultHasher::new();
2773 key.hash(&mut hasher);
2774 self.extra
2775 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2776 .key = Some(hasher.finish());
2777 }
2778
2779 #[inline]
2781 #[must_use] pub fn get_key(&self) -> Option<u64> {
2782 self.extra.as_ref().and_then(|ext| ext.key)
2783 }
2784
2785 #[inline]
2818 pub fn set_merge_callback<C: Into<DatasetMergeCallback>>(&mut self, callback: C) {
2819 self.extra
2820 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2821 .dataset_merge_callback = Some(callback.into());
2822 }
2823
2824 #[inline]
2826 #[must_use] pub fn get_merge_callback(&self) -> Option<DatasetMergeCallback> {
2827 self.extra.as_ref().and_then(|ext| ext.dataset_merge_callback.clone())
2828 }
2829
2830 #[inline]
2835 pub fn set_component_origin(&mut self, origin: ComponentOrigin) {
2836 self.extra
2837 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2838 .component_origin = Some(origin);
2839 }
2840
2841 #[inline]
2843 #[must_use] pub fn get_component_origin(&self) -> Option<&ComponentOrigin> {
2844 self.extra.as_ref().and_then(|ext| ext.component_origin.as_ref())
2845 }
2846
2847 #[inline]
2848 #[must_use] pub fn with_menu_bar(mut self, menu_bar: Menu) -> Self {
2849 self.set_menu_bar(menu_bar);
2850 self
2851 }
2852
2853 #[inline]
2854 #[must_use] pub fn with_context_menu(mut self, context_menu: Menu) -> Self {
2855 self.set_context_menu(context_menu);
2856 self
2857 }
2858
2859 #[inline]
2860 pub fn add_callback<C: Into<CoreCallback>>(
2861 &mut self,
2862 event: EventFilter,
2863 data: RefAny,
2864 callback: C,
2865 ) {
2866 let callback = callback.into();
2867 let mut v: CoreCallbackDataVec = Vec::new().into();
2868 mem::swap(&mut v, &mut self.callbacks);
2869 let mut v = v.into_library_owned_vec();
2870 v.push(CoreCallbackData {
2871 event,
2872 refany: data,
2873 callback,
2874 });
2875 self.callbacks = v.into();
2876 }
2877
2878 #[inline]
2879 pub fn add_id(&mut self, s: AzString) {
2880 let mut v: AttributeTypeVec = Vec::new().into();
2881 mem::swap(&mut v, self.attributes_mut());
2882 let mut v = v.into_library_owned_vec();
2883 v.push(AttributeType::Id(s));
2884 self.set_attributes(v.into());
2885 }
2886 #[inline]
2887 pub fn add_class(&mut self, s: AzString) {
2888 let mut v: AttributeTypeVec = Vec::new().into();
2889 mem::swap(&mut v, self.attributes_mut());
2890 let mut v = v.into_library_owned_vec();
2891 v.push(AttributeType::Class(s));
2892 self.set_attributes(v.into());
2893 }
2894
2895 #[inline]
2900 pub fn add_css_property(&mut self, p: CssPropertyWithConditions) {
2901 use azul_css::css::{rule_priority, CssDeclaration, CssPath, CssRuleBlock};
2902 let rule = CssRuleBlock {
2903 path: CssPath { selectors: Vec::new().into() },
2904 declarations: vec![CssDeclaration::Static(p.property)].into(),
2905 conditions: p.apply_if,
2906 priority: rule_priority::INLINE,
2907 };
2908 let mut v: azul_css::css::CssRuleBlockVec = Vec::new().into();
2909 mem::swap(&mut v, &mut self.style.rules);
2910 let mut v = v.into_library_owned_vec();
2911 v.push(rule);
2912 self.style.rules = v.into();
2913 }
2914
2915 #[must_use] pub fn calculate_node_data_hash(&self) -> DomNodeHash {
2917 use core::hash::Hasher;
2918 let mut hasher = crate::hash::DefaultHasher::new();
2919 self.hash(&mut hasher);
2920 let h = hasher.finish();
2921 DomNodeHash { inner: h }
2922 }
2923
2924 #[must_use] pub fn calculate_structural_hash(&self) -> DomNodeHash {
2936 use core::hash::Hasher;
2937 use core::hash::Hasher as StdHasher;
2938
2939 let mut hasher = crate::hash::DefaultHasher::new();
2940
2941 mem::discriminant(&self.node_type).hash(&mut hasher);
2944
2945 if self.node_type == NodeType::VirtualView {
2947 if let Some(ext) = self.extra.as_ref() {
2948 if let Some(vv) = ext.virtual_view.as_ref() {
2949 vv.hash(&mut hasher);
2950 }
2951 }
2952 }
2953
2954 if let NodeType::Image(ref img_ref) = self.node_type {
2960 match img_ref.get_data() {
2961 crate::resources::DecodedImage::Callback(cb) => {
2962 cb.callback.cb.hash(&mut hasher);
2964 cb.refany.get_type_id().hash(&mut hasher);
2966 }
2967 _ => {
2968 img_ref.hash(&mut hasher);
2970 }
2971 }
2972 }
2973
2974 for attr in self.attributes().as_ref() {
2977 match attr {
2978 AttributeType::Id(s) => { 0u8.hash(&mut hasher); s.as_str().hash(&mut hasher); }
2979 AttributeType::Class(s) => { 1u8.hash(&mut hasher); s.as_str().hash(&mut hasher); }
2980 _ => {}
2981 }
2982 }
2983
2984 for attr in self.attributes().as_ref() {
2987 if !matches!(attr, AttributeType::ContentEditable(_) | AttributeType::Id(_) | AttributeType::Class(_)) {
2988 attr.hash(&mut hasher);
2989 }
2990 }
2991
2992 for callback in self.callbacks.as_ref() {
2994 callback.event.hash(&mut hasher);
2995 }
2996
2997 let h = hasher.finish();
2998 DomNodeHash { inner: h }
2999 }
3000
3001 #[inline]
3002 #[must_use] pub const fn with_tab_index(mut self, tab_index: TabIndex) -> Self {
3003 self.set_tab_index(tab_index);
3004 self
3005 }
3006 #[inline]
3007 #[must_use] pub const fn with_contenteditable(mut self, contenteditable: bool) -> Self {
3008 self.set_contenteditable(contenteditable);
3009 self
3010 }
3011 #[inline]
3012 #[must_use] pub fn with_node_type(mut self, node_type: NodeType) -> Self {
3013 self.set_node_type(node_type);
3014 self
3015 }
3016 #[inline]
3017 #[must_use]
3018 pub fn with_callback<C: Into<CoreCallback>>(
3019 mut self,
3020 event: EventFilter,
3021 data: RefAny,
3022 callback: C,
3023 ) -> Self {
3024 self.add_callback(event, data, callback);
3025 self
3026 }
3027 #[inline]
3028 #[must_use] pub fn with_dataset(mut self, data: OptionRefAny) -> Self {
3029 self.set_dataset(data);
3030 self
3031 }
3032 #[inline]
3033 #[must_use] pub fn with_ids_and_classes(mut self, ids_and_classes: IdOrClassVec) -> Self {
3034 self.set_ids_and_classes(ids_and_classes);
3035 self
3036 }
3037 #[inline]
3038 #[must_use] pub fn with_callbacks(mut self, callbacks: CoreCallbackDataVec) -> Self {
3039 self.callbacks = callbacks;
3040 self
3041 }
3042 #[inline]
3046 #[must_use] pub fn with_css_props(mut self, css_props: CssPropertyWithConditionsVec) -> Self {
3047 self.style = css_props.into();
3048 self
3049 }
3050 #[inline]
3052 #[must_use] pub fn with_style(mut self, style: azul_css::css::Css) -> Self {
3053 self.style = style;
3054 self
3055 }
3056
3057 #[inline]
3070 #[must_use]
3071 pub fn with_key<K: Hash>(mut self, key: K) -> Self {
3072 self.set_key(key);
3073 self
3074 }
3075
3076 #[inline]
3107 #[must_use]
3108 pub fn with_merge_callback<C: Into<DatasetMergeCallback>>(mut self, callback: C) -> Self {
3109 self.set_merge_callback(callback);
3110 self
3111 }
3112
3113 pub fn set_css(&mut self, style: &str) {
3131 let parsed = azul_css::css::Css::parse_inline(style);
3135 let mut current: azul_css::css::CssRuleBlockVec = Vec::new().into();
3136 mem::swap(&mut current, &mut self.style.rules);
3137 let mut v = current.into_library_owned_vec();
3138 v.extend(parsed.rules.into_library_owned_vec());
3139 self.style.rules = v.into();
3140 }
3141
3142 #[must_use] pub fn with_css(mut self, style: &str) -> Self {
3144 self.set_css(style);
3145 self
3146 }
3147
3148 #[inline]
3149 #[must_use]
3150 pub const fn swap_with_default(&mut self) -> Self {
3151 let mut s = Self::create_div();
3152 mem::swap(&mut s, self);
3153 s
3154 }
3155
3156 #[inline]
3157 #[must_use] pub fn copy_special(&self) -> Self {
3158 Self {
3159 node_type: self.node_type.to_library_owned_nodetype(),
3160 style: self.style.clone(),
3161 callbacks: self.callbacks.clone(),
3162 flags: self.flags,
3163 accessibility: self.accessibility.clone(),
3164 extra: self.extra.clone(),
3165 }
3166 }
3167
3168 pub(crate) fn copy_special_moving_complex(&mut self) -> Self {
3183 let taken_style = mem::take(&mut self.style);
3194 let taken_extra = self.extra.take();
3195 let taken_node_type = mem::replace(&mut self.node_type, NodeType::Div);
3196 let mut copy = self.copy_special();
3197 unsafe { core::ptr::write(&raw mut copy.node_type, taken_node_type); }
3205 copy.style = taken_style;
3206 copy.extra = taken_extra;
3207 copy
3208 }
3209
3210 #[must_use] pub fn is_focusable(&self) -> bool {
3211 if matches!(self.node_type,
3213 NodeType::A | NodeType::Button | NodeType::Input
3214 | NodeType::Select | NodeType::TextArea
3215 ) {
3216 return true;
3217 }
3218 if self.is_contenteditable() {
3220 return true;
3221 }
3222 self.get_tab_index().is_some()
3224 || self
3225 .get_callbacks()
3226 .iter()
3227 .any(|cb| cb.event.is_focus_callback())
3228 }
3229
3230 #[must_use] pub fn has_activation_behavior(&self) -> bool {
3243 use crate::events::{EventFilter, HoverEventFilter};
3244
3245 if matches!(self.node_type, NodeType::A | NodeType::Button) {
3247 return true;
3248 }
3249
3250 let has_click_callback = self
3253 .get_callbacks()
3254 .iter()
3255 .any(|cb| matches!(
3256 cb.event,
3257 EventFilter::Hover(HoverEventFilter::MouseUp | HoverEventFilter::LeftMouseUp)
3258 ));
3259
3260 if has_click_callback {
3261 return true;
3262 }
3263
3264 if let Some(ref accessibility) = self.accessibility {
3266 use crate::a11y::AccessibilityRole;
3267 match accessibility.role {
3268 AccessibilityRole::PushButton | AccessibilityRole::Link
3270 | AccessibilityRole::CheckButton | AccessibilityRole::RadioButton | AccessibilityRole::MenuItem
3273 | AccessibilityRole::PageTab => return true,
3275 _ => {}
3276 }
3277 }
3278
3279 false
3280 }
3281
3282 #[must_use] pub fn is_activatable(&self) -> bool {
3287 if !self.has_activation_behavior() {
3288 return false;
3289 }
3290
3291 if let Some(ref accessibility) = self.accessibility {
3293 if accessibility
3295 .states
3296 .as_ref()
3297 .iter()
3298 .any(|s| matches!(s, AccessibilityState::Unavailable))
3299 {
3300 return false;
3301 }
3302 }
3303
3304 true
3306 }
3307
3308 #[must_use] pub fn get_effective_tabindex(&self) -> Option<i32> {
3316 self.flags.get_tab_index().map_or_else(|| if self.get_callbacks().iter().any(|cb| cb.event.is_focus_callback()) {
3317 Some(0)
3318 } else {
3319 None
3320 }, |tab_idx| match tab_idx {
3321 TabIndex::Auto => Some(0),
3322 TabIndex::OverrideInParent(n) => Some(i32::try_from(n).unwrap_or(i32::MAX)),
3323 TabIndex::NoKeyboardFocus => Some(-1),
3324 })
3325 }
3326
3327 #[must_use] pub fn get_accessible_label(&self) -> Option<&str> {
3333 for attr in self.attributes().as_ref() {
3334 if let AttributeType::AriaLabel(s) = attr { return Some(s.as_str()) }
3335 }
3336 for attr in self.attributes().as_ref() {
3337 match attr {
3338 AttributeType::Alt(s) | AttributeType::Title(s) => return Some(s.as_str()),
3339 _ => {}
3340 }
3341 }
3342 None
3343 }
3344
3345 #[must_use] pub fn get_accessible_value(&self) -> Option<&str> {
3350 for attr in self.attributes().as_ref() {
3351 if let AttributeType::Value(s) = attr {
3352 return Some(s.as_str());
3353 }
3354 }
3355 None
3356 }
3357
3358 #[must_use] pub fn get_placeholder(&self) -> Option<&str> {
3360 for attr in self.attributes().as_ref() {
3361 if let AttributeType::Placeholder(s) = attr {
3362 return Some(s.as_str());
3363 }
3364 }
3365 None
3366 }
3367
3368 pub fn get_virtual_view_node(&mut self) -> Option<&mut VirtualViewNode> {
3369 self.extra.as_mut()?.virtual_view.as_mut()
3370 }
3371
3372 #[must_use] pub fn get_virtual_view_node_ref(&self) -> Option<&VirtualViewNode> {
3373 self.extra.as_ref()?.virtual_view.as_ref()
3374 }
3375
3376 pub fn get_render_image_callback_node(
3377 &mut self,
3378 ) -> Option<(&mut CoreImageCallback, ImageRefHash)> {
3379 match &mut self.node_type {
3380 NodeType::Image(ref mut img) => {
3381 let hash = image_ref_get_hash(img.as_ref());
3382 img.as_mut().get_image_callback_mut().map(|r| (r, hash))
3383 }
3384 _ => None,
3385 }
3386 }
3387
3388 pub fn debug_print_start(
3389 &self,
3390 css_cache: &CssPropertyCache,
3391 node_id: &NodeId,
3392 node_state: &StyledNodeState,
3393 ) -> String {
3394 let html_type = self.node_type.get_path();
3395 let attributes_string = node_data_to_string(self);
3396 let style = css_cache.get_computed_css_style_string(self, node_id, node_state);
3397 format!(
3398 "<{} data-az-node-id=\"{}\" {} {style}>",
3399 html_type,
3400 node_id.index(),
3401 attributes_string,
3402 style = if style.trim().is_empty() {
3403 String::new()
3404 } else {
3405 format!("style=\"{style}\"")
3406 }
3407 )
3408 }
3409
3410 #[must_use] pub fn debug_print_end(&self) -> String {
3411 let html_type = self.node_type.get_path();
3412 format!("</{html_type}>")
3413 }
3414}
3415
3416impl crate::events::ActivationBehavior for NodeData {
3417 fn has_activation_behavior(&self) -> bool {
3418 Self::has_activation_behavior(self)
3419 }
3420
3421 fn is_activatable(&self) -> bool {
3422 Self::is_activatable(self)
3423 }
3424}
3425
3426impl crate::events::Focusable for NodeData {
3427 fn get_tabindex(&self) -> Option<i32> {
3428 self.get_effective_tabindex()
3429 }
3430
3431 fn is_focusable(&self) -> bool {
3432 Self::is_focusable(self)
3433 }
3434
3435 fn is_naturally_focusable(&self) -> bool {
3436 matches!(
3437 self.node_type,
3438 NodeType::A
3439 | NodeType::Button
3440 | NodeType::Input
3441 | NodeType::Select
3442 | NodeType::TextArea
3443 )
3444 }
3445}
3446
3447#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
3449#[repr(C)]
3450pub struct DomId {
3451 pub inner: usize,
3452}
3453
3454impl fmt::Display for DomId {
3455 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3456 write!(f, "{}", self.inner)
3457 }
3458}
3459
3460impl DomId {
3461 pub const ROOT_ID: Self = Self { inner: 0 };
3462}
3463
3464impl Default for DomId {
3465 fn default() -> Self {
3466 Self::ROOT_ID
3467 }
3468}
3469
3470impl_option!(
3471 DomId,
3472 OptionDomId,
3473 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3474);
3475
3476impl_vec!(DomId, DomIdVec, DomIdVecDestructor, DomIdVecDestructorType, DomIdVecSlice, OptionDomId);
3477impl_vec_debug!(DomId, DomIdVec);
3478impl_vec_clone!(DomId, DomIdVec, DomIdVecDestructor);
3479impl_vec_partialeq!(DomId, DomIdVec);
3480impl_vec_partialord!(DomId, DomIdVec);
3481
3482#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3484#[repr(C)]
3485pub struct DomNodeId {
3486 pub dom: DomId,
3488 pub node: NodeHierarchyItemId,
3490}
3491
3492impl_option!(
3493 DomNodeId,
3494 OptionDomNodeId,
3495 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3496);
3497
3498impl DomNodeId {
3499 pub const ROOT: Self = Self {
3500 dom: DomId::ROOT_ID,
3501 node: NodeHierarchyItemId::NONE,
3502 };
3503}
3504
3505#[repr(C)]
3511#[derive(PartialEq, Clone)]
3512pub struct Dom {
3513 pub root: NodeData,
3515 pub children: DomVec,
3517 pub css: azul_css::css::CssVec,
3521 pub estimated_total_children: usize,
3534}
3535
3536#[repr(C)]
3542#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
3543pub struct CssWithNodeId {
3544 pub node_id: usize,
3546 pub css: azul_css::css::Css,
3548}
3549
3550impl_vec!(CssWithNodeId, CssWithNodeIdVec, CssWithNodeIdVecDestructor, CssWithNodeIdVecDestructorType, CssWithNodeIdVecSlice, OptionCssWithNodeId);
3551impl_option!(CssWithNodeId, OptionCssWithNodeId, copy = false, [Debug, Clone, PartialEq, Eq, PartialOrd]);
3552impl_vec_clone!(CssWithNodeId, CssWithNodeIdVec, CssWithNodeIdVecDestructor);
3553impl_vec_mut!(CssWithNodeId, CssWithNodeIdVec);
3554impl_vec_debug!(CssWithNodeId, CssWithNodeIdVec);
3555impl_vec_partialord!(CssWithNodeId, CssWithNodeIdVec);
3556impl_vec_partialeq!(CssWithNodeId, CssWithNodeIdVec);
3557
3558#[repr(C)]
3565#[derive(Debug, Clone, PartialEq, PartialOrd)]
3566pub struct FastDom {
3567 pub node_hierarchy: crate::styled_dom::NodeHierarchyItemVec,
3569 pub node_data: NodeDataVec,
3571 pub css: CssWithNodeIdVec,
3573}
3574
3575impl Eq for Dom {}
3578
3579impl Hash for Dom {
3580 fn hash<H: Hasher>(&self, state: &mut H) {
3581 self.root.hash(state);
3582 self.children.hash(state);
3583 self.estimated_total_children.hash(state);
3584 }
3585}
3586
3587impl PartialOrd for Dom {
3589 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
3590 Some(self.cmp(other))
3591 }
3592}
3593impl Ord for Dom {
3594 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
3595 self.root.cmp(&other.root)
3596 .then_with(|| self.children.cmp(&other.children))
3597 .then_with(|| self.estimated_total_children.cmp(&other.estimated_total_children))
3598 }
3599}
3600
3601impl_option!(
3602 Dom,
3603 OptionDom,
3604 copy = false,
3605 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3606);
3607
3608impl_vec!(Dom, DomVec, DomVecDestructor, DomVecDestructorType, DomVecSlice, OptionDom);
3609impl_vec_clone!(Dom, DomVec, DomVecDestructor);
3610impl_vec_mut!(Dom, DomVec);
3611impl_vec_debug!(Dom, DomVec);
3612impl_vec_partialord!(Dom, DomVec);
3613impl_vec_ord!(Dom, DomVec);
3614impl_vec_partialeq!(Dom, DomVec);
3615impl_vec_eq!(Dom, DomVec);
3616impl_vec_hash!(Dom, DomVec);
3617
3618impl Default for Dom {
3624 fn default() -> Self {
3625 Self::create_body()
3626 }
3627}
3628
3629impl Dom {
3630 #[inline]
3635 #[must_use] pub fn create_node(node_type: NodeType) -> Self {
3636 Self {
3637 root: NodeData::create_node(node_type),
3638 children: Vec::new().into(),
3639 css: Vec::new().into(),
3640 estimated_total_children: 0,
3641 }
3642 }
3643 #[inline]
3644 #[must_use] pub fn create_from_data(node_data: NodeData) -> Self {
3645 Self {
3646 root: node_data,
3647 children: Vec::new().into(),
3648 css: Vec::new().into(),
3649 estimated_total_children: 0,
3650 }
3651 }
3652
3653 #[inline]
3660 #[must_use] pub const fn create_html() -> Self {
3661 Self {
3662 root: NodeData::create_node(NodeType::Html),
3663 children: DomVec::from_const_slice(&[]),
3664 css: azul_css::css::CssVec::from_const_slice(&[]),
3665 estimated_total_children: 0,
3666 }
3667 }
3668
3669 #[inline]
3673 #[must_use] pub const fn create_head() -> Self {
3674 Self {
3675 root: NodeData::create_node(NodeType::Head),
3676 children: DomVec::from_const_slice(&[]),
3677 css: azul_css::css::CssVec::from_const_slice(&[]),
3678 estimated_total_children: 0,
3679 }
3680 }
3681
3682 #[inline]
3683 #[must_use] pub const fn create_body() -> Self {
3684 Self {
3685 root: NodeData::create_node(NodeType::Body),
3686 children: DomVec::from_const_slice(&[]),
3687 css: azul_css::css::CssVec::from_const_slice(&[]),
3688 estimated_total_children: 0,
3689 }
3690 }
3691
3692 #[inline]
3697 #[must_use] pub const fn create_div() -> Self {
3698 Self {
3699 root: NodeData::create_node(NodeType::Div),
3700 children: DomVec::from_const_slice(&[]),
3701 css: azul_css::css::CssVec::from_const_slice(&[]),
3702 estimated_total_children: 0,
3703 }
3704 }
3705
3706 #[inline]
3714 #[must_use] pub const fn create_article() -> Self {
3715 Self {
3716 root: NodeData::create_node(NodeType::Article),
3717 children: DomVec::from_const_slice(&[]),
3718 css: azul_css::css::CssVec::from_const_slice(&[]),
3719 estimated_total_children: 0,
3720 }
3721 }
3722
3723 #[inline]
3728 #[must_use] pub const fn create_section() -> Self {
3729 Self {
3730 root: NodeData::create_node(NodeType::Section),
3731 children: DomVec::from_const_slice(&[]),
3732 css: azul_css::css::CssVec::from_const_slice(&[]),
3733 estimated_total_children: 0,
3734 }
3735 }
3736
3737 #[inline]
3743 #[must_use] pub const fn create_nav() -> Self {
3744 Self {
3745 root: NodeData::create_node(NodeType::Nav),
3746 children: DomVec::from_const_slice(&[]),
3747 css: azul_css::css::CssVec::from_const_slice(&[]),
3748 estimated_total_children: 0,
3749 }
3750 }
3751
3752 #[inline]
3757 #[must_use] pub const fn create_aside() -> Self {
3758 Self {
3759 root: NodeData::create_node(NodeType::Aside),
3760 children: DomVec::from_const_slice(&[]),
3761 css: azul_css::css::CssVec::from_const_slice(&[]),
3762 estimated_total_children: 0,
3763 }
3764 }
3765
3766 #[inline]
3771 #[must_use] pub const fn create_header() -> Self {
3772 Self {
3773 root: NodeData::create_node(NodeType::Header),
3774 children: DomVec::from_const_slice(&[]),
3775 css: azul_css::css::CssVec::from_const_slice(&[]),
3776 estimated_total_children: 0,
3777 }
3778 }
3779
3780 #[inline]
3785 #[must_use] pub const fn create_footer() -> Self {
3786 Self {
3787 root: NodeData::create_node(NodeType::Footer),
3788 children: DomVec::from_const_slice(&[]),
3789 css: azul_css::css::CssVec::from_const_slice(&[]),
3790 estimated_total_children: 0,
3791 }
3792 }
3793
3794 #[inline]
3800 #[must_use] pub const fn create_main() -> Self {
3801 Self {
3802 root: NodeData::create_node(NodeType::Main),
3803 children: DomVec::from_const_slice(&[]),
3804 css: azul_css::css::CssVec::from_const_slice(&[]),
3805 estimated_total_children: 0,
3806 }
3807 }
3808
3809 #[inline]
3814 #[must_use] pub const fn create_figure() -> Self {
3815 Self {
3816 root: NodeData::create_node(NodeType::Figure),
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]
3828 #[must_use] pub const fn create_figcaption() -> Self {
3829 Self {
3830 root: NodeData::create_node(NodeType::FigCaption),
3831 children: DomVec::from_const_slice(&[]),
3832 css: azul_css::css::CssVec::from_const_slice(&[]),
3833 estimated_total_children: 0,
3834 }
3835 }
3836
3837 #[inline]
3844 #[must_use] pub const fn create_details_no_a11y() -> Self {
3845 Self {
3846 root: NodeData::create_node(NodeType::Details),
3847 children: DomVec::from_const_slice(&[]),
3848 css: azul_css::css::CssVec::from_const_slice(&[]),
3849 estimated_total_children: 0,
3850 }
3851 }
3852
3853 #[inline]
3860 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_details(aria: SmallAriaInfo) -> Self {
3862 Self::create_details_no_a11y().with_accessibility_info(aria.to_full_info())
3863 }
3864
3865 #[inline]
3870 #[must_use] pub const fn create_summary_no_a11y() -> Self {
3871 Self {
3872 root: NodeData::create_node(NodeType::Summary),
3873 children: DomVec::from_const_slice(&[]),
3874 css: azul_css::css::CssVec::from_const_slice(&[]),
3875 estimated_total_children: 0,
3876 }
3877 }
3878
3879 #[inline]
3886 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_summary(aria: SmallAriaInfo) -> Self {
3888 Self::create_summary_no_a11y().with_accessibility_info(aria.to_full_info())
3889 }
3890
3891 #[inline]
3896 pub fn create_summary_with_text_no_a11y<S: Into<AzString>>(text: S) -> Self {
3897 Self::create_summary_no_a11y().with_child(Self::create_text(text))
3898 }
3899
3900 #[inline]
3907 #[allow(clippy::needless_pass_by_value)] pub fn create_summary_with_text<S: Into<AzString>>(text: S, aria: SmallAriaInfo) -> Self {
3909 Self::create_summary_with_text_no_a11y(text).with_accessibility_info(aria.to_full_info())
3910 }
3911
3912 #[inline]
3917 #[must_use] pub const fn create_dialog_no_a11y() -> Self {
3918 Self {
3919 root: NodeData::create_node(NodeType::Dialog),
3920 children: DomVec::from_const_slice(&[]),
3921 css: azul_css::css::CssVec::from_const_slice(&[]),
3922 estimated_total_children: 0,
3923 }
3924 }
3925
3926 #[inline]
3934 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_dialog(aria: DialogAriaInfo) -> Self {
3936 Self::create_dialog_no_a11y().with_accessibility_info(aria.to_full_info())
3937 }
3938
3939 #[inline]
3942 #[must_use] pub const fn create_br() -> Self {
3943 Self {
3944 root: NodeData::create_node(NodeType::Br),
3945 children: DomVec::from_const_slice(&[]),
3946 css: azul_css::css::CssVec::from_const_slice(&[]),
3947 estimated_total_children: 0,
3948 }
3949 }
3950 #[inline]
3951 pub fn create_text<S: Into<AzString>>(value: S) -> Self {
3952 Self::create_node(NodeType::Text(BoxOrStatic::heap(value.into())))
3953 }
3954 #[inline]
3955 #[must_use] pub fn create_image(image: ImageRef) -> Self {
3956 Self::create_node(NodeType::Image(BoxOrStatic::heap(image)))
3957 }
3958 #[inline]
3970 pub fn create_icon<S: Into<AzString>>(icon_name: S) -> Self {
3971 Self::create_node(NodeType::Icon(BoxOrStatic::heap(icon_name.into())))
3972 }
3973
3974 #[inline]
3975 pub fn create_virtual_view(data: RefAny, callback: impl Into<VirtualViewCallback>) -> Self {
3976 Self::create_from_data(NodeData::create_virtual_view(data, callback))
3977 }
3978
3979 #[inline]
3986 #[must_use] pub fn create_geolocation_probe(config: crate::geolocation::GeolocationProbeConfig) -> Self {
3987 Self::create_node(NodeType::GeolocationProbe(config))
3988 }
3989
3990 #[inline]
3996 #[must_use] pub const fn create_p() -> Self {
3997 Self {
3998 root: NodeData::create_node(NodeType::P),
3999 children: DomVec::from_const_slice(&[]),
4000 css: azul_css::css::CssVec::from_const_slice(&[]),
4001 estimated_total_children: 0,
4002 }
4003 }
4004
4005 #[inline]
4010 #[must_use] pub const fn create_h1() -> Self {
4011 Self {
4012 root: NodeData::create_node(NodeType::H1),
4013 children: DomVec::from_const_slice(&[]),
4014 css: azul_css::css::CssVec::from_const_slice(&[]),
4015 estimated_total_children: 0,
4016 }
4017 }
4018
4019 #[inline]
4027 pub fn create_h1_with_text<S: Into<AzString>>(text: S) -> Self {
4028 Self::create_h1().with_child(Self::create_text(text))
4029 }
4030
4031 #[inline]
4035 #[must_use] pub const fn create_h2() -> Self {
4036 Self {
4037 root: NodeData::create_node(NodeType::H2),
4038 children: DomVec::from_const_slice(&[]),
4039 css: azul_css::css::CssVec::from_const_slice(&[]),
4040 estimated_total_children: 0,
4041 }
4042 }
4043
4044 #[inline]
4051 pub fn create_h2_with_text<S: Into<AzString>>(text: S) -> Self {
4052 Self::create_h2().with_child(Self::create_text(text))
4053 }
4054
4055 #[inline]
4059 #[must_use] pub const fn create_h3() -> Self {
4060 Self {
4061 root: NodeData::create_node(NodeType::H3),
4062 children: DomVec::from_const_slice(&[]),
4063 css: azul_css::css::CssVec::from_const_slice(&[]),
4064 estimated_total_children: 0,
4065 }
4066 }
4067
4068 #[inline]
4075 pub fn create_h3_with_text<S: Into<AzString>>(text: S) -> Self {
4076 Self::create_h3().with_child(Self::create_text(text))
4077 }
4078
4079 #[inline]
4081 #[must_use] pub const fn create_h4() -> Self {
4082 Self {
4083 root: NodeData::create_node(NodeType::H4),
4084 children: DomVec::from_const_slice(&[]),
4085 css: azul_css::css::CssVec::from_const_slice(&[]),
4086 estimated_total_children: 0,
4087 }
4088 }
4089
4090 #[inline]
4095 pub fn create_h4_with_text<S: Into<AzString>>(text: S) -> Self {
4096 Self::create_h4().with_child(Self::create_text(text))
4097 }
4098
4099 #[inline]
4101 #[must_use] pub const fn create_h5() -> Self {
4102 Self {
4103 root: NodeData::create_node(NodeType::H5),
4104 children: DomVec::from_const_slice(&[]),
4105 css: azul_css::css::CssVec::from_const_slice(&[]),
4106 estimated_total_children: 0,
4107 }
4108 }
4109
4110 #[inline]
4115 pub fn create_h5_with_text<S: Into<AzString>>(text: S) -> Self {
4116 Self::create_h5().with_child(Self::create_text(text))
4117 }
4118
4119 #[inline]
4121 #[must_use] pub const fn create_h6() -> Self {
4122 Self {
4123 root: NodeData::create_node(NodeType::H6),
4124 children: DomVec::from_const_slice(&[]),
4125 css: azul_css::css::CssVec::from_const_slice(&[]),
4126 estimated_total_children: 0,
4127 }
4128 }
4129
4130 #[inline]
4135 pub fn create_h6_with_text<S: Into<AzString>>(text: S) -> Self {
4136 Self::create_h6().with_child(Self::create_text(text))
4137 }
4138
4139 #[inline]
4144 #[must_use] pub const fn create_span() -> Self {
4145 Self {
4146 root: NodeData::create_node(NodeType::Span),
4147 children: DomVec::from_const_slice(&[]),
4148 css: azul_css::css::CssVec::from_const_slice(&[]),
4149 estimated_total_children: 0,
4150 }
4151 }
4152
4153 #[inline]
4161 pub fn create_span_with_text<S: Into<AzString>>(text: S) -> Self {
4162 Self::create_span().with_child(Self::create_text(text))
4163 }
4164
4165 #[inline]
4169 #[must_use] pub const fn create_strong() -> Self {
4170 Self {
4171 root: NodeData::create_node(NodeType::Strong),
4172 children: DomVec::from_const_slice(&[]),
4173 css: azul_css::css::CssVec::from_const_slice(&[]),
4174 estimated_total_children: 0,
4175 }
4176 }
4177
4178 #[inline]
4186 pub fn create_strong_with_text<S: Into<AzString>>(text: S) -> Self {
4187 Self::create_strong().with_child(Self::create_text(text))
4188 }
4189
4190 #[inline]
4194 #[must_use] pub const fn create_em() -> Self {
4195 Self {
4196 root: NodeData::create_node(NodeType::Em),
4197 children: DomVec::from_const_slice(&[]),
4198 css: azul_css::css::CssVec::from_const_slice(&[]),
4199 estimated_total_children: 0,
4200 }
4201 }
4202
4203 #[inline]
4211 pub fn create_em_with_text<S: Into<AzString>>(text: S) -> Self {
4212 Self::create_em().with_child(Self::create_text(text))
4213 }
4214
4215 #[inline]
4219 #[must_use] pub fn create_code() -> Self {
4220 Self::create_node(NodeType::Code)
4221 }
4222
4223 #[inline]
4231 pub fn create_code_with_text<S: Into<AzString>>(code: S) -> Self {
4232 Self::create_code().with_child(Self::create_text(code))
4233 }
4234
4235 #[inline]
4239 #[must_use] pub fn create_pre() -> Self {
4240 Self::create_node(NodeType::Pre)
4241 }
4242
4243 #[inline]
4251 pub fn create_pre_with_text<S: Into<AzString>>(text: S) -> Self {
4252 Self::create_pre().with_child(Self::create_text(text))
4253 }
4254
4255 #[inline]
4259 #[must_use] pub fn create_blockquote() -> Self {
4260 Self::create_node(NodeType::BlockQuote)
4261 }
4262
4263 #[inline]
4271 pub fn create_blockquote_with_text<S: Into<AzString>>(text: S) -> Self {
4272 Self::create_blockquote().with_child(Self::create_text(text))
4273 }
4274
4275 #[inline]
4279 #[must_use] pub fn create_cite() -> Self {
4280 Self::create_node(NodeType::Cite)
4281 }
4282
4283 #[inline]
4291 pub fn create_cite_with_text<S: Into<AzString>>(text: S) -> Self {
4292 Self::create_cite().with_child(Self::create_text(text))
4293 }
4294
4295 #[inline]
4300 #[must_use] pub fn create_abbr() -> Self {
4301 Self::create_node(NodeType::Abbr)
4302 }
4303
4304 #[inline]
4313 #[must_use] pub fn create_abbr_with_title(abbr_text: AzString, title: AzString) -> Self {
4314 Self::create_node(NodeType::Abbr)
4315 .with_attribute(AttributeType::Title(title))
4316 .with_child(Self::create_text(abbr_text))
4317 }
4318
4319 #[inline]
4323 #[must_use] pub fn create_kbd() -> Self {
4324 Self::create_node(NodeType::Kbd)
4325 }
4326
4327 #[inline]
4335 pub fn create_kbd_with_text<S: Into<AzString>>(text: S) -> Self {
4336 Self::create_kbd().with_child(Self::create_text(text))
4337 }
4338
4339 #[inline]
4343 #[must_use] pub fn create_samp() -> Self {
4344 Self::create_node(NodeType::Samp)
4345 }
4346
4347 #[inline]
4354 pub fn create_samp_with_text<S: Into<AzString>>(text: S) -> Self {
4355 Self::create_samp().with_child(Self::create_text(text))
4356 }
4357
4358 #[inline]
4362 #[must_use] pub fn create_var() -> Self {
4363 Self::create_node(NodeType::Var)
4364 }
4365
4366 #[inline]
4373 pub fn create_var_with_text<S: Into<AzString>>(text: S) -> Self {
4374 Self::create_var().with_child(Self::create_text(text))
4375 }
4376
4377 #[inline]
4379 #[must_use] pub fn create_sub() -> Self {
4380 Self::create_node(NodeType::Sub)
4381 }
4382
4383 #[inline]
4390 pub fn create_sub_with_text<S: Into<AzString>>(text: S) -> Self {
4391 Self::create_sub().with_child(Self::create_text(text))
4392 }
4393
4394 #[inline]
4396 #[must_use] pub fn create_sup() -> Self {
4397 Self::create_node(NodeType::Sup)
4398 }
4399
4400 #[inline]
4407 pub fn create_sup_with_text<S: Into<AzString>>(text: S) -> Self {
4408 Self::create_sup().with_child(Self::create_text(text))
4409 }
4410
4411 #[inline]
4413 #[must_use] pub fn create_u() -> Self {
4414 Self::create_node(NodeType::U)
4415 }
4416
4417 #[inline]
4422 pub fn create_u_with_text<S: Into<AzString>>(text: S) -> Self {
4423 Self::create_u().with_child(Self::create_text(text))
4424 }
4425
4426 #[inline]
4428 #[must_use] pub fn create_s() -> Self {
4429 Self::create_node(NodeType::S)
4430 }
4431
4432 #[inline]
4437 pub fn create_s_with_text<S: Into<AzString>>(text: S) -> Self {
4438 Self::create_s().with_child(Self::create_text(text))
4439 }
4440
4441 #[inline]
4443 #[must_use] pub fn create_mark() -> Self {
4444 Self::create_node(NodeType::Mark)
4445 }
4446
4447 #[inline]
4452 pub fn create_mark_with_text<S: Into<AzString>>(text: S) -> Self {
4453 Self::create_mark().with_child(Self::create_text(text))
4454 }
4455
4456 #[inline]
4458 #[must_use] pub fn create_del() -> Self {
4459 Self::create_node(NodeType::Del)
4460 }
4461
4462 #[inline]
4467 pub fn create_del_with_text<S: Into<AzString>>(text: S) -> Self {
4468 Self::create_del().with_child(Self::create_text(text))
4469 }
4470
4471 #[inline]
4473 #[must_use] pub fn create_ins() -> Self {
4474 Self::create_node(NodeType::Ins)
4475 }
4476
4477 #[inline]
4482 pub fn create_ins_with_text<S: Into<AzString>>(text: S) -> Self {
4483 Self::create_ins().with_child(Self::create_text(text))
4484 }
4485
4486 #[inline]
4488 #[must_use] pub fn create_dfn() -> Self {
4489 Self::create_node(NodeType::Dfn)
4490 }
4491
4492 #[inline]
4497 pub fn create_dfn_with_text<S: Into<AzString>>(text: S) -> Self {
4498 Self::create_dfn().with_child(Self::create_text(text))
4499 }
4500
4501 #[inline]
4510 #[must_use] pub fn create_time(text: AzString, datetime: OptionString) -> Self {
4511 let mut element = Self::create_node(NodeType::Time).with_child(Self::create_text(text));
4512 if let OptionString::Some(dt) = datetime {
4513 element = element.with_attribute(AttributeType::Custom(AttributeNameValue {
4514 attr_name: "datetime".into(),
4515 value: dt,
4516 }));
4517 }
4518 element
4519 }
4520
4521 #[inline]
4525 #[must_use] pub fn create_bdo() -> Self {
4526 Self::create_node(NodeType::Bdo)
4527 }
4528
4529 #[inline]
4533 pub fn create_bdo_with_text<S: Into<AzString>>(text: S) -> Self {
4534 Self::create_bdo().with_child(Self::create_text(text))
4535 }
4536
4537 #[inline]
4543 #[must_use] pub fn create_b() -> Self {
4544 Self::create_node(NodeType::B)
4545 }
4546
4547 #[inline]
4554 pub fn create_b_with_text<S: Into<AzString>>(text: S) -> Self {
4555 Self::create_b().with_child(Self::create_text(text))
4556 }
4557
4558 #[inline]
4562 #[must_use] pub fn create_i() -> Self {
4563 Self::create_node(NodeType::I)
4564 }
4565
4566 #[inline]
4573 pub fn create_i_with_text<S: Into<AzString>>(text: S) -> Self {
4574 Self::create_i().with_child(Self::create_text(text))
4575 }
4576
4577 #[inline]
4581 #[must_use] pub fn create_small() -> Self {
4582 Self::create_node(NodeType::Small)
4583 }
4584
4585 #[inline]
4590 pub fn create_small_with_text<S: Into<AzString>>(text: S) -> Self {
4591 Self::create_small().with_child(Self::create_text(text))
4592 }
4593
4594 #[inline]
4598 #[must_use] pub fn create_big() -> Self {
4599 Self::create_node(NodeType::Big)
4600 }
4601
4602 #[inline]
4606 pub fn create_big_with_text<S: Into<AzString>>(text: S) -> Self {
4607 Self::create_big().with_child(Self::create_text(text))
4608 }
4609
4610 #[inline]
4615 #[must_use] pub fn create_bdi() -> Self {
4616 Self::create_node(NodeType::Bdi)
4617 }
4618
4619 #[inline]
4624 pub fn create_bdi_with_text<S: Into<AzString>>(text: S) -> Self {
4625 Self::create_bdi().with_child(Self::create_text(text))
4626 }
4627
4628 #[inline]
4633 #[must_use] pub fn create_wbr() -> Self {
4634 Self::create_node(NodeType::Wbr)
4635 }
4636
4637 #[inline]
4642 #[must_use] pub fn create_ruby() -> Self {
4643 Self::create_node(NodeType::Ruby)
4644 }
4645
4646 #[inline]
4650 #[must_use] pub fn create_rt() -> Self {
4651 Self::create_node(NodeType::Rt)
4652 }
4653
4654 #[inline]
4659 pub fn create_rt_with_text<S: Into<AzString>>(text: S) -> Self {
4660 Self::create_rt().with_child(Self::create_text(text))
4661 }
4662
4663 #[inline]
4667 #[must_use] pub fn create_rtc() -> Self {
4668 Self::create_node(NodeType::Rtc)
4669 }
4670
4671 #[inline]
4675 #[must_use] pub fn create_rp() -> Self {
4676 Self::create_node(NodeType::Rp)
4677 }
4678
4679 #[inline]
4684 pub fn create_rp_with_text<S: Into<AzString>>(text: S) -> Self {
4685 Self::create_rp().with_child(Self::create_text(text))
4686 }
4687
4688 #[inline]
4693 #[must_use] pub fn create_data(value: AzString) -> Self {
4694 Self::create_node(NodeType::Data).with_attribute(AttributeType::Value(value))
4695 }
4696
4697 #[inline]
4703 #[must_use] pub fn create_data_with_text(value: AzString, text: AzString) -> Self {
4704 Self::create_data(value).with_child(Self::create_text(text))
4705 }
4706
4707 #[inline]
4711 #[must_use] pub fn create_dir() -> Self {
4712 Self::create_node(NodeType::Dir)
4713 }
4714
4715 #[inline]
4719 #[must_use] pub fn create_svg() -> Self {
4720 Self::create_node(NodeType::Svg)
4721 }
4722
4723 #[inline]
4731 #[must_use] pub fn create_a_no_a11y(href: AzString, label: OptionString) -> Self {
4732 let mut link = Self::create_node(NodeType::A).with_attribute(AttributeType::Href(href));
4733 if let OptionString::Some(text) = label {
4734 link = link.with_child(Self::create_text(text));
4735 }
4736 link
4737 }
4738
4739 #[inline]
4747 #[must_use] pub fn create_button_no_a11y(text: AzString) -> Self {
4748 Self::create_node(NodeType::Button).with_child(Self::create_text(text))
4749 }
4750
4751 #[inline]
4759 #[must_use] pub fn create_label_no_a11y(for_id: AzString, text: AzString) -> Self {
4760 Self::create_node(NodeType::Label)
4761 .with_attribute(AttributeType::Custom(AttributeNameValue {
4762 attr_name: "for".into(),
4763 value: for_id,
4764 }))
4765 .with_child(Self::create_text(text))
4766 }
4767
4768 #[inline]
4778 #[must_use] pub fn create_input_no_a11y(input_type: AzString, name: AzString, label: AzString) -> Self {
4779 Self::create_node(NodeType::Input)
4780 .with_attribute(AttributeType::InputType(input_type))
4781 .with_attribute(AttributeType::Name(name))
4782 .with_attribute(AttributeType::AriaLabel(label))
4783 }
4784
4785 #[inline]
4794 #[must_use] pub fn create_textarea_no_a11y(name: AzString, label: AzString) -> Self {
4795 Self::create_node(NodeType::TextArea)
4796 .with_attribute(AttributeType::Name(name))
4797 .with_attribute(AttributeType::AriaLabel(label))
4798 }
4799
4800 #[inline]
4809 #[must_use] pub fn create_select_no_a11y(name: AzString, label: AzString) -> Self {
4810 Self::create_node(NodeType::Select)
4811 .with_attribute(AttributeType::Name(name))
4812 .with_attribute(AttributeType::AriaLabel(label))
4813 }
4814
4815 #[inline]
4821 #[must_use] pub fn create_option_no_a11y(value: AzString, text: AzString) -> Self {
4822 Self::create_node(NodeType::SelectOption)
4823 .with_attribute(AttributeType::Value(value))
4824 .with_child(Self::create_text(text))
4825 }
4826
4827 #[inline]
4836 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_option(value: AzString, text: AzString, aria: SmallAriaInfo) -> Self {
4838 Self::create_option_no_a11y(value, text).with_accessibility_info(aria.to_full_info())
4839 }
4840
4841 #[inline]
4846 #[must_use] pub fn create_ul() -> Self {
4847 Self::create_node(NodeType::Ul)
4848 }
4849
4850 #[inline]
4855 #[must_use] pub fn create_ol() -> Self {
4856 Self::create_node(NodeType::Ol)
4857 }
4858
4859 #[inline]
4864 #[must_use] pub fn create_li() -> Self {
4865 Self::create_node(NodeType::Li)
4866 }
4867
4868 #[inline]
4873 #[must_use] pub fn create_table_no_a11y() -> Self {
4874 Self::create_node(NodeType::Table)
4875 }
4876
4877 #[inline]
4881 #[must_use] pub fn create_caption() -> Self {
4882 Self::create_node(NodeType::Caption)
4883 }
4884
4885 #[inline]
4889 #[must_use] pub fn create_thead() -> Self {
4890 Self::create_node(NodeType::THead)
4891 }
4892
4893 #[inline]
4897 #[must_use] pub fn create_tbody() -> Self {
4898 Self::create_node(NodeType::TBody)
4899 }
4900
4901 #[inline]
4905 #[must_use] pub fn create_tfoot() -> Self {
4906 Self::create_node(NodeType::TFoot)
4907 }
4908
4909 #[inline]
4911 #[must_use] pub fn create_tr() -> Self {
4912 Self::create_node(NodeType::Tr)
4913 }
4914
4915 #[inline]
4920 #[must_use] pub fn create_th() -> Self {
4921 Self::create_node(NodeType::Th)
4922 }
4923
4924 #[inline]
4926 #[must_use] pub fn create_td() -> Self {
4927 Self::create_node(NodeType::Td)
4928 }
4929
4930 #[inline]
4934 #[must_use] pub fn create_form_no_a11y() -> Self {
4935 Self::create_node(NodeType::Form)
4936 }
4937
4938 #[inline]
4945 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_form(aria: SmallAriaInfo) -> Self {
4947 Self::create_form_no_a11y().with_accessibility_info(aria.to_full_info())
4948 }
4949
4950 #[inline]
4954 #[must_use] pub fn create_fieldset_no_a11y() -> Self {
4955 Self::create_node(NodeType::FieldSet)
4956 }
4957
4958 #[inline]
4966 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_fieldset(aria: SmallAriaInfo) -> Self {
4968 Self::create_fieldset_no_a11y().with_accessibility_info(aria.to_full_info())
4969 }
4970
4971 #[inline]
4975 #[must_use] pub fn create_legend_no_a11y() -> Self {
4976 Self::create_node(NodeType::Legend)
4977 }
4978
4979 #[inline]
4986 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_legend(aria: SmallAriaInfo) -> Self {
4988 Self::create_legend_no_a11y().with_accessibility_info(aria.to_full_info())
4989 }
4990
4991 #[inline]
4996 #[must_use] pub fn create_hr() -> Self {
4997 Self::create_node(NodeType::Hr)
4998 }
4999
5000 #[must_use] pub fn create_page_break() -> Self {
5014 Self::create_node(NodeType::PageBreak)
5015 }
5016
5017 #[inline]
5024 #[must_use] pub const fn create_address() -> Self {
5025 Self {
5026 root: NodeData::create_node(NodeType::Address),
5027 children: DomVec::from_const_slice(&[]),
5028 css: azul_css::css::CssVec::from_const_slice(&[]),
5029 estimated_total_children: 0,
5030 }
5031 }
5032
5033 #[inline]
5037 #[must_use] pub const fn create_dl() -> Self {
5038 Self {
5039 root: NodeData::create_node(NodeType::Dl),
5040 children: DomVec::from_const_slice(&[]),
5041 css: azul_css::css::CssVec::from_const_slice(&[]),
5042 estimated_total_children: 0,
5043 }
5044 }
5045
5046 #[inline]
5050 #[must_use] pub const fn create_dt() -> Self {
5051 Self {
5052 root: NodeData::create_node(NodeType::Dt),
5053 children: DomVec::from_const_slice(&[]),
5054 css: azul_css::css::CssVec::from_const_slice(&[]),
5055 estimated_total_children: 0,
5056 }
5057 }
5058
5059 #[inline]
5063 #[must_use] pub const fn create_dd() -> Self {
5064 Self {
5065 root: NodeData::create_node(NodeType::Dd),
5066 children: DomVec::from_const_slice(&[]),
5067 css: azul_css::css::CssVec::from_const_slice(&[]),
5068 estimated_total_children: 0,
5069 }
5070 }
5071
5072 #[inline]
5074 #[must_use] pub const fn create_colgroup() -> Self {
5075 Self {
5076 root: NodeData::create_node(NodeType::ColGroup),
5077 children: DomVec::from_const_slice(&[]),
5078 css: azul_css::css::CssVec::from_const_slice(&[]),
5079 estimated_total_children: 0,
5080 }
5081 }
5082
5083 #[inline]
5085 #[must_use] pub fn create_col(span: i32) -> Self {
5086 Self::create_node(NodeType::Col).with_attribute(AttributeType::ColSpan(span))
5087 }
5088
5089 #[inline]
5096 #[must_use] pub fn create_optgroup_no_a11y(label: AzString) -> Self {
5097 Self::create_node(NodeType::OptGroup).with_attribute(AttributeType::AriaLabel(label))
5098 }
5099
5100 #[inline]
5108 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_optgroup(label: AzString, aria: SmallAriaInfo) -> Self {
5110 Self::create_optgroup_no_a11y(label).with_accessibility_info(aria.to_full_info())
5111 }
5112
5113 #[inline]
5117 #[must_use] pub const fn create_q() -> Self {
5118 Self {
5119 root: NodeData::create_node(NodeType::Q),
5120 children: DomVec::from_const_slice(&[]),
5121 css: azul_css::css::CssVec::from_const_slice(&[]),
5122 estimated_total_children: 0,
5123 }
5124 }
5125
5126 #[inline]
5130 #[must_use] pub const fn create_acronym() -> Self {
5131 Self {
5132 root: NodeData::create_node(NodeType::Acronym),
5133 children: DomVec::from_const_slice(&[]),
5134 css: azul_css::css::CssVec::from_const_slice(&[]),
5135 estimated_total_children: 0,
5136 }
5137 }
5138
5139 #[inline]
5143 pub fn create_acronym_with_text<S: Into<AzString>>(text: S) -> Self {
5144 Self::create_acronym().with_child(Self::create_text(text))
5145 }
5146
5147 #[inline]
5151 #[must_use] pub const fn create_menu_no_a11y() -> Self {
5152 Self {
5153 root: NodeData::create_node(NodeType::Menu),
5154 children: DomVec::from_const_slice(&[]),
5155 css: azul_css::css::CssVec::from_const_slice(&[]),
5156 estimated_total_children: 0,
5157 }
5158 }
5159
5160 #[inline]
5167 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_menu(aria: SmallAriaInfo) -> Self {
5169 Self::create_menu_no_a11y().with_accessibility_info(aria.to_full_info())
5170 }
5171
5172 #[inline]
5176 #[must_use] pub const fn create_menuitem_no_a11y() -> Self {
5177 Self {
5178 root: NodeData::create_node(NodeType::MenuItem),
5179 children: DomVec::from_const_slice(&[]),
5180 css: azul_css::css::CssVec::from_const_slice(&[]),
5181 estimated_total_children: 0,
5182 }
5183 }
5184
5185 #[inline]
5192 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_menuitem(aria: SmallAriaInfo) -> Self {
5194 Self::create_menuitem_no_a11y().with_accessibility_info(aria.to_full_info())
5195 }
5196
5197 #[inline]
5202 pub fn create_menuitem_with_text_no_a11y<S: Into<AzString>>(text: S) -> Self {
5203 Self::create_menuitem_no_a11y().with_child(Self::create_text(text))
5204 }
5205
5206 #[inline]
5213 #[allow(clippy::needless_pass_by_value)] pub fn create_menuitem_with_text<S: Into<AzString>>(text: S, aria: SmallAriaInfo) -> Self {
5215 Self::create_menuitem_with_text_no_a11y(text).with_accessibility_info(aria.to_full_info())
5216 }
5217
5218 #[inline]
5223 #[must_use] pub const fn create_output_no_a11y() -> Self {
5224 Self {
5225 root: NodeData::create_node(NodeType::Output),
5226 children: DomVec::from_const_slice(&[]),
5227 css: azul_css::css::CssVec::from_const_slice(&[]),
5228 estimated_total_children: 0,
5229 }
5230 }
5231
5232 #[inline]
5239 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_output(aria: SmallAriaInfo) -> Self {
5241 Self::create_output_no_a11y().with_accessibility_info(aria.to_full_info())
5242 }
5243
5244 #[inline]
5252 #[must_use] pub fn create_progress_no_a11y(value: f32, max: f32) -> Self {
5253 Self::create_node(NodeType::Progress)
5254 .with_attribute(AttributeType::Custom(AttributeNameValue {
5255 attr_name: "value".into(),
5256 value: value.to_string().into(),
5257 }))
5258 .with_attribute(AttributeType::Custom(AttributeNameValue {
5259 attr_name: "max".into(),
5260 value: max.to_string().into(),
5261 }))
5262 }
5263
5264 #[inline]
5272 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_progress(aria: ProgressAriaInfo) -> Self {
5274 let mut node = Self::create_node(NodeType::Progress);
5275 if !aria.indeterminate {
5276 if let azul_css::OptionF32::Some(v) = aria.current_value {
5277 node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
5278 attr_name: "value".into(),
5279 value: v.to_string().into(),
5280 }));
5281 }
5282 }
5283 if let azul_css::OptionF32::Some(m) = aria.max {
5284 node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
5285 attr_name: "max".into(),
5286 value: m.to_string().into(),
5287 }));
5288 }
5289 node.with_accessibility_info(aria.to_full_info())
5290 }
5291
5292 #[inline]
5301 #[must_use] pub fn create_meter_no_a11y(value: f32, min: f32, max: f32) -> Self {
5302 Self::create_node(NodeType::Meter)
5303 .with_attribute(AttributeType::Custom(AttributeNameValue {
5304 attr_name: "value".into(),
5305 value: value.to_string().into(),
5306 }))
5307 .with_attribute(AttributeType::Custom(AttributeNameValue {
5308 attr_name: "min".into(),
5309 value: min.to_string().into(),
5310 }))
5311 .with_attribute(AttributeType::Custom(AttributeNameValue {
5312 attr_name: "max".into(),
5313 value: max.to_string().into(),
5314 }))
5315 }
5316
5317 #[inline]
5325 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_meter(aria: MeterAriaInfo) -> Self {
5327 let mut node = Self::create_meter_no_a11y(aria.current_value, aria.min, aria.max);
5328 if let azul_css::OptionF32::Some(v) = aria.low {
5329 node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
5330 attr_name: "low".into(),
5331 value: v.to_string().into(),
5332 }));
5333 }
5334 if let azul_css::OptionF32::Some(v) = aria.high {
5335 node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
5336 attr_name: "high".into(),
5337 value: v.to_string().into(),
5338 }));
5339 }
5340 if let azul_css::OptionF32::Some(v) = aria.optimum {
5341 node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
5342 attr_name: "optimum".into(),
5343 value: v.to_string().into(),
5344 }));
5345 }
5346 node.with_accessibility_info(aria.to_full_info())
5347 }
5348
5349 #[inline]
5353 #[must_use] pub const fn create_datalist_no_a11y() -> Self {
5354 Self {
5355 root: NodeData::create_node(NodeType::DataList),
5356 children: DomVec::from_const_slice(&[]),
5357 css: azul_css::css::CssVec::from_const_slice(&[]),
5358 estimated_total_children: 0,
5359 }
5360 }
5361
5362 #[inline]
5369 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_datalist(aria: SmallAriaInfo) -> Self {
5371 Self::create_datalist_no_a11y().with_accessibility_info(aria.to_full_info())
5372 }
5373
5374 #[inline]
5381 #[must_use] pub const fn create_canvas_no_a11y() -> Self {
5382 Self {
5383 root: NodeData::create_node(NodeType::Canvas),
5384 children: DomVec::from_const_slice(&[]),
5385 css: azul_css::css::CssVec::from_const_slice(&[]),
5386 estimated_total_children: 0,
5387 }
5388 }
5389
5390 #[inline]
5398 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_canvas(aria: SmallAriaInfo) -> Self {
5400 Self::create_canvas_no_a11y().with_accessibility_info(aria.to_full_info())
5401 }
5402
5403 #[inline]
5407 #[must_use] pub const fn create_object() -> Self {
5408 Self {
5409 root: NodeData::create_node(NodeType::Object),
5410 children: DomVec::from_const_slice(&[]),
5411 css: azul_css::css::CssVec::from_const_slice(&[]),
5412 estimated_total_children: 0,
5413 }
5414 }
5415
5416 #[inline]
5422 #[must_use] pub fn create_param(name: AzString, value: AzString) -> Self {
5423 Self::create_node(NodeType::Param)
5424 .with_attribute(AttributeType::Name(name))
5425 .with_attribute(AttributeType::Value(value))
5426 }
5427
5428 #[inline]
5433 #[must_use] pub const fn create_embed() -> Self {
5434 Self {
5435 root: NodeData::create_node(NodeType::Embed),
5436 children: DomVec::from_const_slice(&[]),
5437 css: azul_css::css::CssVec::from_const_slice(&[]),
5438 estimated_total_children: 0,
5439 }
5440 }
5441
5442 #[inline]
5446 #[must_use] pub const fn create_audio_no_a11y() -> Self {
5447 Self {
5448 root: NodeData::create_node(NodeType::Audio),
5449 children: DomVec::from_const_slice(&[]),
5450 css: azul_css::css::CssVec::from_const_slice(&[]),
5451 estimated_total_children: 0,
5452 }
5453 }
5454
5455 #[inline]
5462 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_audio(aria: SmallAriaInfo) -> Self {
5464 Self::create_audio_no_a11y().with_accessibility_info(aria.to_full_info())
5465 }
5466
5467 #[inline]
5471 #[must_use] pub const fn create_video_no_a11y() -> Self {
5472 Self {
5473 root: NodeData::create_node(NodeType::Video),
5474 children: DomVec::from_const_slice(&[]),
5475 css: azul_css::css::CssVec::from_const_slice(&[]),
5476 estimated_total_children: 0,
5477 }
5478 }
5479
5480 #[inline]
5487 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_video(aria: SmallAriaInfo) -> Self {
5489 Self::create_video_no_a11y().with_accessibility_info(aria.to_full_info())
5490 }
5491
5492 #[inline]
5498 #[must_use] pub fn create_source(src: AzString, media_type: AzString) -> Self {
5499 Self::create_node(NodeType::Source)
5500 .with_attribute(AttributeType::Src(src))
5501 .with_attribute(AttributeType::Custom(AttributeNameValue {
5502 attr_name: "type".into(),
5503 value: media_type,
5504 }))
5505 }
5506
5507 #[inline]
5516 #[must_use] pub fn create_track(src: AzString, kind: AzString) -> Self {
5517 Self::create_node(NodeType::Track)
5518 .with_attribute(AttributeType::Src(src))
5519 .with_attribute(AttributeType::Custom(AttributeNameValue {
5520 attr_name: "kind".into(),
5521 value: kind,
5522 }))
5523 }
5524
5525 #[inline]
5529 #[must_use] pub const fn create_map() -> Self {
5530 Self {
5531 root: NodeData::create_node(NodeType::Map),
5532 children: DomVec::from_const_slice(&[]),
5533 css: azul_css::css::CssVec::from_const_slice(&[]),
5534 estimated_total_children: 0,
5535 }
5536 }
5537
5538 #[inline]
5542 #[must_use] pub const fn create_area_no_a11y() -> Self {
5543 Self {
5544 root: NodeData::create_node(NodeType::Area),
5545 children: DomVec::from_const_slice(&[]),
5546 css: azul_css::css::CssVec::from_const_slice(&[]),
5547 estimated_total_children: 0,
5548 }
5549 }
5550
5551 #[inline]
5558 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_area(aria: SmallAriaInfo) -> Self {
5560 Self::create_area_no_a11y().with_accessibility_info(aria.to_full_info())
5561 }
5562
5563 #[inline]
5569 #[must_use] pub fn create_title() -> Self {
5570 Self::create_node(NodeType::Title)
5571 }
5572
5573 #[inline]
5578 pub fn create_title_with_text<S: Into<AzString>>(text: S) -> Self {
5579 Self::create_title().with_child(Self::create_text(text))
5580 }
5581
5582 #[inline]
5586 #[must_use] pub const fn create_meta() -> Self {
5587 Self {
5588 root: NodeData::create_node(NodeType::Meta),
5589 children: DomVec::from_const_slice(&[]),
5590 css: azul_css::css::CssVec::from_const_slice(&[]),
5591 estimated_total_children: 0,
5592 }
5593 }
5594
5595 #[inline]
5600 #[must_use] pub const fn create_link() -> Self {
5601 Self {
5602 root: NodeData::create_node(NodeType::Link),
5603 children: DomVec::from_const_slice(&[]),
5604 css: azul_css::css::CssVec::from_const_slice(&[]),
5605 estimated_total_children: 0,
5606 }
5607 }
5608
5609 #[inline]
5614 #[must_use] pub const fn create_script() -> Self {
5615 Self {
5616 root: NodeData::create_node(NodeType::Script),
5617 children: DomVec::from_const_slice(&[]),
5618 css: azul_css::css::CssVec::from_const_slice(&[]),
5619 estimated_total_children: 0,
5620 }
5621 }
5622
5623 #[inline]
5628 #[must_use] pub const fn create_style() -> Self {
5629 Self {
5630 root: NodeData::create_node(NodeType::Style),
5631 children: DomVec::from_const_slice(&[]),
5632 css: azul_css::css::CssVec::from_const_slice(&[]),
5633 estimated_total_children: 0,
5634 }
5635 }
5636
5637 #[inline]
5642 pub fn create_style_with_text<S: Into<AzString>>(text: S) -> Self {
5643 Self::create_style().with_child(Self::create_text(text))
5644 }
5645
5646 #[inline]
5651 #[must_use] pub fn create_base(href: AzString) -> Self {
5652 Self::create_node(NodeType::Base).with_attribute(AttributeType::Href(href))
5653 }
5654
5655 #[inline]
5665 #[must_use] pub fn create_th_with_scope(scope: AzString, text: AzString) -> Self {
5666 Self::create_node(NodeType::Th)
5667 .with_attribute(AttributeType::Scope(scope))
5668 .with_child(Self::create_text(text))
5669 }
5670
5671 #[inline]
5676 pub fn create_td_with_text<S: Into<AzString>>(text: S) -> Self {
5677 Self::create_td().with_child(Self::create_text(text))
5678 }
5679
5680 #[inline]
5685 pub fn create_th_with_text<S: Into<AzString>>(text: S) -> Self {
5686 Self::create_th().with_child(Self::create_text(text))
5687 }
5688
5689 #[inline]
5694 pub fn create_li_with_text<S: Into<AzString>>(text: S) -> Self {
5695 Self::create_li().with_child(Self::create_text(text))
5696 }
5697
5698 #[inline]
5703 pub fn create_p_with_text<S: Into<AzString>>(text: S) -> Self {
5704 Self::create_p().with_child(Self::create_text(text))
5705 }
5706
5707 #[inline]
5719 #[allow(clippy::needless_pass_by_value)] pub fn create_button<S: Into<AzString>>(text: S, aria: SmallAriaInfo) -> Self {
5721 let mut btn = Self::create_button_no_a11y(text.into());
5722 btn.root.set_accessibility_info(aria.to_full_info());
5723 btn
5724 }
5725
5726 #[inline]
5736 #[allow(clippy::needless_pass_by_value)] pub fn create_a<S1: Into<AzString>, S2: Into<AzString>>(
5738 href: S1,
5739 text: S2,
5740 aria: SmallAriaInfo,
5741 ) -> Self {
5742 let mut link = Self::create_a_no_a11y(href.into(), OptionString::Some(text.into()));
5743 link.root.set_accessibility_info(aria.to_full_info());
5744 link
5745 }
5746
5747 #[inline]
5757 #[allow(clippy::needless_pass_by_value)] pub fn create_input<S1: Into<AzString>, S2: Into<AzString>, S3: Into<AzString>>(
5759 input_type: S1,
5760 name: S2,
5761 label: S3,
5762 aria: SmallAriaInfo,
5763 ) -> Self {
5764 let mut input = Self::create_input_no_a11y(input_type.into(), name.into(), label.into());
5765 input.root.set_accessibility_info(aria.to_full_info());
5766 input
5767 }
5768
5769 #[inline]
5778 #[allow(clippy::needless_pass_by_value)] pub fn create_textarea<S1: Into<AzString>, S2: Into<AzString>>(
5780 name: S1,
5781 label: S2,
5782 aria: SmallAriaInfo,
5783 ) -> Self {
5784 let mut textarea = Self::create_textarea_no_a11y(name.into(), label.into());
5785 textarea.root.set_accessibility_info(aria.to_full_info());
5786 textarea
5787 }
5788
5789 #[inline]
5798 #[allow(clippy::needless_pass_by_value)] pub fn create_select<S1: Into<AzString>, S2: Into<AzString>>(
5800 name: S1,
5801 label: S2,
5802 aria: SmallAriaInfo,
5803 ) -> Self {
5804 let mut select = Self::create_select_no_a11y(name.into(), label.into());
5805 select.root.set_accessibility_info(aria.to_full_info());
5806 select
5807 }
5808
5809 #[inline]
5818 #[allow(clippy::needless_pass_by_value)] pub fn create_table<S: Into<AzString>>(caption: S, aria: SmallAriaInfo) -> Self {
5820 let mut table = Self::create_table_no_a11y()
5821 .with_child(Self::create_caption().with_child(Self::create_text(caption)));
5822 table.root.set_accessibility_info(aria.to_full_info());
5823 table
5824 }
5825
5826 #[inline]
5835 #[allow(clippy::needless_pass_by_value)] pub fn create_label<S1: Into<AzString>, S2: Into<AzString>>(
5837 for_id: S1,
5838 text: S2,
5839 aria: SmallAriaInfo,
5840 ) -> Self {
5841 let mut label = Self::create_label_no_a11y(for_id.into(), text.into());
5842 label.root.set_accessibility_info(aria.to_full_info());
5843 label
5844 }
5845
5846 #[cfg(feature = "xml")]
5852 pub fn from_xml<S: AsRef<str>>(xml_str: S) -> Self {
5853 Self::create_text(format!(
5856 "XML content loaded ({} bytes)",
5857 xml_str.as_ref().len()
5858 ))
5859 }
5860
5861 #[cfg(not(feature = "xml"))]
5863 pub fn from_xml<S: AsRef<str>>(xml_str: S) -> Self {
5864 Self::create_text(format!(
5865 "XML parsing requires 'xml' feature ({} bytes)",
5866 xml_str.as_ref().len()
5867 ))
5868 }
5869
5870 #[inline]
5872 #[must_use]
5873 pub const fn swap_with_default(&mut self) -> Self {
5874 let mut s = Self {
5875 root: NodeData::create_div(),
5876 children: DomVec::from_const_slice(&[]),
5877 css: azul_css::css::CssVec::from_const_slice(&[]),
5878 estimated_total_children: 0,
5879 };
5880 mem::swap(&mut s, self);
5881 s
5882 }
5883
5884 #[must_use]
5891 pub fn recompute_estimated_total_children(&self) -> usize {
5892 self.children
5893 .iter()
5894 .map(|c| c.recompute_estimated_total_children() + 1)
5895 .sum()
5896 }
5897
5898 #[inline]
5899 pub fn add_child(&mut self, child: Self) {
5900 debug_assert_eq!(
5907 child.estimated_total_children,
5908 child
5909 .children
5910 .iter()
5911 .map(|c| c.estimated_total_children + 1)
5912 .sum::<usize>(),
5913 "Dom.estimated_total_children desynced for added child; call \
5914 fixup_children_estimated() after mutating `children` directly",
5915 );
5916 let estimated = child.estimated_total_children;
5917 let mut v: DomVec = Vec::new().into();
5918 mem::swap(&mut v, &mut self.children);
5919 let mut v = v.into_library_owned_vec();
5920 v.push(child);
5921 self.children = v.into();
5922 self.estimated_total_children += estimated + 1;
5923 }
5924
5925 #[inline]
5926 pub fn set_children(&mut self, children: DomVec) {
5927 debug_assert!(
5931 children.iter().all(|c| c.estimated_total_children
5932 == c
5933 .children
5934 .iter()
5935 .map(|g| g.estimated_total_children + 1)
5936 .sum::<usize>()),
5937 "Dom.estimated_total_children desynced in set_children; a child's own \
5938 estimate was stale — call fixup_children_estimated() first",
5939 );
5940 let children_estimated = children
5941 .iter()
5942 .map(|s| s.estimated_total_children + 1)
5943 .sum();
5944 self.children = children;
5945 self.estimated_total_children = children_estimated;
5946 }
5947
5948 #[must_use]
5949 pub fn copy_except_for_root(&mut self) -> Self {
5950 Self {
5951 root: self.root.copy_special(),
5952 children: self.children.clone(),
5953 css: self.css.clone(),
5954 estimated_total_children: self.estimated_total_children,
5955 }
5956 }
5957 #[must_use] pub const fn node_count(&self) -> usize {
5958 self.estimated_total_children.saturating_add(1)
5965 }
5966
5967 pub fn add_component_css(&mut self, css: azul_css::css::Css) {
5973 let mut v = Vec::new().into();
5974 mem::swap(&mut v, &mut self.css);
5975 let mut v: Vec<azul_css::css::Css> = v.into_library_owned_vec();
5976 v.push(css);
5977 self.css = v.into();
5978 }
5979
5980 pub fn set_component_css(&mut self, css: azul_css::css::CssVec) {
5984 self.css = css;
5985 }
5986 #[inline]
5987 #[must_use] pub fn with_children(mut self, children: DomVec) -> Self {
5988 self.set_children(children);
5989 self
5990 }
5991 #[inline]
5992 #[must_use] pub fn with_child(mut self, child: Self) -> Self {
5993 self.add_child(child);
5994 self
5995 }
5996 #[inline]
5997 #[must_use] pub fn with_node_type(mut self, node_type: NodeType) -> Self {
5998 self.root.set_node_type(node_type);
5999 self
6000 }
6001 #[inline]
6002 #[must_use] pub fn with_id(mut self, id: AzString) -> Self {
6003 self.root.add_id(id);
6004 self
6005 }
6006 #[inline]
6007 #[must_use] pub fn with_class(mut self, class: AzString) -> Self {
6008 self.root.add_class(class);
6009 self
6010 }
6011 #[inline]
6012 #[must_use]
6013 pub fn with_callback<C: Into<CoreCallback>>(
6014 mut self,
6015 event: EventFilter,
6016 data: RefAny,
6017 callback: C,
6018 ) -> Self {
6019 self.root.add_callback(event, data, callback);
6020 self
6021 }
6022 #[inline]
6024 #[must_use] pub fn with_css_property(mut self, prop: CssPropertyWithConditions) -> Self {
6025 self.root.add_css_property(prop);
6026 self
6027 }
6028 #[inline]
6030 pub fn add_css_property(&mut self, prop: CssPropertyWithConditions) {
6031 self.root.add_css_property(prop);
6032 }
6033 #[inline]
6034 pub fn add_class(&mut self, class: AzString) {
6035 self.root.add_class(class);
6036 }
6037 #[inline]
6038 pub fn add_callback<C: Into<CoreCallback>>(
6039 &mut self,
6040 event: EventFilter,
6041 data: RefAny,
6042 callback: C,
6043 ) {
6044 self.root.add_callback(event, data, callback);
6045 }
6046 #[inline]
6047 pub const fn set_tab_index(&mut self, tab_index: TabIndex) {
6048 self.root.set_tab_index(tab_index);
6049 }
6050 #[inline]
6051 pub const fn set_contenteditable(&mut self, contenteditable: bool) {
6052 self.root.set_contenteditable(contenteditable);
6053 }
6054 #[inline]
6055 #[must_use] pub const fn with_tab_index(mut self, tab_index: TabIndex) -> Self {
6056 self.root.set_tab_index(tab_index);
6057 self
6058 }
6059 #[inline]
6060 #[must_use] pub const fn with_contenteditable(mut self, contenteditable: bool) -> Self {
6061 self.root.set_contenteditable(contenteditable);
6062 self
6063 }
6064 #[inline]
6065 #[must_use] pub fn with_dataset(mut self, data: OptionRefAny) -> Self {
6066 self.root.set_dataset(data);
6067 self
6068 }
6069 #[inline]
6070 #[must_use] pub fn with_ids_and_classes(mut self, ids_and_classes: IdOrClassVec) -> Self {
6071 self.root.set_ids_and_classes(ids_and_classes);
6072 self
6073 }
6074
6075 #[inline]
6077 #[must_use] pub fn with_attribute(mut self, attr: AttributeType) -> Self {
6078 let mut attrs = self.root.attributes().clone();
6079 let mut v = attrs.into_library_owned_vec();
6080 v.push(attr);
6081 self.root.set_attributes(v.into());
6082 self
6083 }
6084
6085 #[inline]
6087 #[must_use] pub fn with_attributes(mut self, attributes: AttributeTypeVec) -> Self {
6088 self.root.set_attributes(attributes);
6089 self
6090 }
6091
6092 #[inline]
6093 #[must_use] pub fn with_callbacks(mut self, callbacks: CoreCallbackDataVec) -> Self {
6094 self.root.callbacks = callbacks;
6095 self
6096 }
6097 #[inline]
6100 #[must_use] pub fn with_css_props(mut self, css_props: CssPropertyWithConditionsVec) -> Self {
6101 self.root.style = css_props.into();
6102 self
6103 }
6104 #[inline]
6106 #[must_use] pub fn with_style(mut self, style: azul_css::css::Css) -> Self {
6107 self.root.style = style;
6108 self
6109 }
6110
6111 #[inline]
6123 #[must_use]
6124 pub fn with_key<K: Hash>(mut self, key: K) -> Self {
6125 self.root.set_key(key);
6126 self
6127 }
6128
6129 #[inline]
6138 #[must_use]
6139 pub fn with_merge_callback<C: Into<DatasetMergeCallback>>(mut self, callback: C) -> Self {
6140 self.root.set_merge_callback(callback);
6141 self
6142 }
6143
6144 pub fn set_css(&mut self, style: &str) {
6173 self.add_component_css(azul_css::css::Css::parse_inline(style));
6181 }
6182
6183 #[must_use] pub fn with_css(mut self, style: &str) -> Self {
6185 self.set_css(style);
6186 self
6187 }
6188
6189 #[inline]
6191 pub fn set_context_menu(&mut self, context_menu: Menu) {
6192 self.root.set_context_menu(context_menu);
6193 }
6194
6195 #[inline]
6196 #[must_use] pub fn with_context_menu(mut self, context_menu: Menu) -> Self {
6197 self.set_context_menu(context_menu);
6198 self
6199 }
6200
6201 #[inline]
6203 pub fn set_menu_bar(&mut self, menu_bar: Menu) {
6204 self.root.set_menu_bar(menu_bar);
6205 }
6206
6207 #[inline]
6208 #[must_use] pub fn with_menu_bar(mut self, menu_bar: Menu) -> Self {
6209 self.set_menu_bar(menu_bar);
6210 self
6211 }
6212
6213 #[inline]
6214 #[must_use] pub fn with_clip_mask(mut self, clip_mask: ImageMask) -> Self {
6215 self.root.set_clip_mask(clip_mask);
6216 self
6217 }
6218
6219 #[inline]
6220 #[must_use] pub fn with_svg_clip_path(mut self, clip: crate::svg::SvgMultiPolygon) -> Self {
6221 self.root.set_svg_data(SvgNodeData::Path(clip));
6222 self
6223 }
6224
6225 #[inline]
6226 #[must_use] pub fn with_svg_data(mut self, data: SvgNodeData) -> Self {
6227 self.root.set_svg_data(data);
6228 self
6229 }
6230
6231 #[inline]
6232 #[must_use] pub fn with_accessibility_info(mut self, accessibility_info: AccessibilityInfo) -> Self {
6233 self.root.set_accessibility_info(accessibility_info);
6234 self
6235 }
6236
6237 pub fn fixup_children_estimated(&mut self) -> usize {
6238 if self.children.is_empty() {
6239 self.estimated_total_children = 0;
6240 } else {
6241 self.estimated_total_children = self
6242 .children
6243 .iter_mut()
6244 .map(|s| s.fixup_children_estimated() + 1)
6245 .sum();
6246 }
6247 self.estimated_total_children
6248 }
6249}
6250
6251impl core::iter::FromIterator<Self> for Dom {
6252 fn from_iter<I: IntoIterator<Item = Self>>(iter: I) -> Self {
6253 let mut estimated_total_children = 0;
6254 let children = iter
6255 .into_iter()
6256 .inspect(|c| {
6257 estimated_total_children += c.estimated_total_children + 1;
6258 })
6259 .collect::<Vec<Self>>();
6260
6261 Self {
6262 root: NodeData::create_div(),
6263 children: children.into(),
6264 css: azul_css::css::CssVec::from_const_slice(&[]),
6265 estimated_total_children,
6266 }
6267 }
6268}
6269
6270impl fmt::Debug for Dom {
6271 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6272 fn print_dom(d: &Dom, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6273 write!(f, "Dom {{\r\n")?;
6274 write!(f, "\troot: {:#?}\r\n", d.root)?;
6275 write!(
6276 f,
6277 "\testimated_total_children: {:#?}\r\n",
6278 d.estimated_total_children
6279 )?;
6280 write!(f, "\tchildren: [\r\n")?;
6281 for c in &d.children {
6282 print_dom(c, f)?;
6283 }
6284 write!(f, "\t]\r\n")?;
6285 write!(f, "}}\r\n")?;
6286 Ok(())
6287 }
6288
6289 print_dom(self, f)
6290 }
6291}
6292
6293#[cfg(test)]
6294mod audit_tests {
6295 use super::*;
6296
6297 #[test]
6298 fn node_count_matches_recompute() {
6299 let dom = Dom::create_div()
6301 .with_child(Dom::create_div().with_child(Dom::create_div()))
6302 .with_child(Dom::create_div());
6303 assert_eq!(
6304 dom.estimated_total_children,
6305 dom.recompute_estimated_total_children()
6306 );
6307 assert_eq!(dom.estimated_total_children, 3);
6308 assert_eq!(dom.node_count(), 4);
6309 }
6310
6311 #[test]
6312 fn single_node_dom_node_count() {
6313 let dom = Dom::create_div();
6314 assert_eq!(dom.estimated_total_children, 0);
6315 assert_eq!(dom.node_count(), 1);
6316 assert_eq!(dom.recompute_estimated_total_children(), 0);
6317 }
6318
6319 #[test]
6320 fn fixup_repairs_desynced_estimate() {
6321 let mut dom = Dom::create_div().with_child(Dom::create_div());
6322 dom.estimated_total_children = 999;
6324 let repaired = dom.fixup_children_estimated();
6325 assert_eq!(repaired, 1);
6326 assert_eq!(
6327 dom.estimated_total_children,
6328 dom.recompute_estimated_total_children()
6329 );
6330 }
6331
6332 #[cfg(debug_assertions)]
6334 #[test]
6335 #[should_panic(expected = "desynced")]
6336 fn add_child_with_stale_estimate_panics_in_debug() {
6337 let mut child = Dom::create_div().with_child(Dom::create_div());
6338 child.estimated_total_children = 0; let mut parent = Dom::create_div();
6340 parent.add_child(child);
6341 }
6342
6343 #[test]
6347 fn node_data_is_send() {
6348 fn assert_send<T: Send>() {}
6349 assert_send::<NodeData>();
6350 }
6351
6352 #[test]
6358 fn copy_special_moving_complex_moves_text_node_type() {
6359 let mut nd = NodeData::create_text("hello").with_css("color: red;");
6360 assert!(!nd.style.rules.is_empty(), "precondition: style set");
6361
6362 let copy = nd.copy_special_moving_complex();
6363
6364 match copy.get_node_type() {
6366 NodeType::Text(s) => assert_eq!(s.as_ref().as_str(), "hello"),
6367 other => panic!("expected Text node_type on copy, got {other:?}"),
6368 }
6369 assert!(matches!(nd.get_node_type(), NodeType::Div));
6371 assert!(nd.style.rules.is_empty());
6373 assert!(!copy.style.rules.is_empty());
6374 }
6375
6376 #[test]
6378 fn copy_special_moving_complex_moves_div_node_type() {
6379 let mut nd = NodeData::create_div();
6380 let copy = nd.copy_special_moving_complex();
6381 assert!(matches!(copy.get_node_type(), NodeType::Div));
6382 assert!(matches!(nd.get_node_type(), NodeType::Div));
6383 }
6384}
6385
6386#[cfg(test)]
6387#[allow(clippy::cast_possible_wrap, clippy::too_many_lines)]
6388mod autotest_generated {
6389 use super::*;
6390
6391 #[test]
6401 fn upsert_inline_css_property_replaces_only_the_unconditional_same_type() {
6402 use azul_css::dynamic_selector::{
6403 CssPropertyWithConditions, DynamicSelector, PseudoStateType,
6404 };
6405 use azul_css::props::layout::display::LayoutDisplay;
6406 use azul_css::props::layout::position::LayoutPosition;
6407 use azul_css::props::property::{CssProperty, CssPropertyType};
6408
6409 let mut node = NodeData::create_div();
6410 node.set_css_props(
6411 vec![
6412 CssPropertyWithConditions::simple(CssProperty::const_position(
6413 LayoutPosition::Absolute,
6414 )),
6415 CssPropertyWithConditions::simple(CssProperty::const_display(
6416 LayoutDisplay::None,
6417 )),
6418 CssPropertyWithConditions {
6420 property: CssProperty::const_display(LayoutDisplay::Block),
6421 apply_if: vec![DynamicSelector::PseudoState(PseudoStateType::Hover)]
6422 .into(),
6423 },
6424 ]
6425 .into(),
6426 );
6427
6428 node.upsert_inline_css_property(CssProperty::const_display(LayoutDisplay::Flex));
6429
6430 let collect = |node: &NodeData| -> Vec<(CssProperty, bool)> {
6431 node.style
6432 .iter_inline_properties()
6433 .map(|(p, conds)| (p.clone(), conds.as_ref().is_empty()))
6434 .collect()
6435 };
6436
6437 let props = collect(&node);
6438 let unconditional_displays: Vec<&CssProperty> = props
6439 .iter()
6440 .filter(|(p, uncond)| *uncond && p.get_type() == CssPropertyType::Display)
6441 .map(|(p, _)| p)
6442 .collect();
6443 assert_eq!(
6444 unconditional_displays,
6445 vec![&CssProperty::const_display(LayoutDisplay::Flex)],
6446 "exactly ONE unconditional display remains: the patched value"
6447 );
6448 assert!(
6449 props.iter().any(|(p, uncond)| *uncond
6450 && *p == CssProperty::const_position(LayoutPosition::Absolute)),
6451 "unrelated inline properties must survive the patch"
6452 );
6453 assert!(
6454 props.iter().any(|(p, uncond)| !*uncond
6455 && *p == CssProperty::const_display(LayoutDisplay::Block)),
6456 "conditional (hover) declarations must survive the patch"
6457 );
6458
6459 let len_before = node.style.rules.as_ref().len();
6461 for i in 0..20 {
6462 let v = if i % 2 == 0 { LayoutDisplay::None } else { LayoutDisplay::Flex };
6463 node.upsert_inline_css_property(CssProperty::const_display(v));
6464 }
6465 assert_eq!(
6466 node.style.rules.as_ref().len(),
6467 len_before,
6468 "repeated upserts of the same type must not grow the inline style"
6469 );
6470 }
6471
6472 fn hash_of<T: Hash>(t: &T) -> u64 {
6477 let mut h = crate::hash::DefaultHasher::new();
6478 t.hash(&mut h);
6479 h.finish()
6480 }
6481
6482 extern "C" fn merge_cb_a(new_data: RefAny, _old: RefAny) -> RefAny {
6483 new_data
6484 }
6485
6486 extern "C" fn merge_cb_b(_new: RefAny, old_data: RefAny) -> RefAny {
6487 old_data
6488 }
6489
6490 extern "C" fn virtual_view_cb(
6493 _data: RefAny,
6494 _info: crate::callbacks::VirtualViewCallbackInfo,
6495 ) -> crate::callbacks::VirtualViewReturn {
6496 unreachable!("virtual view callback is never invoked by these tests")
6497 }
6498
6499 fn virtual_view_callback() -> VirtualViewCallback {
6500 VirtualViewCallback {
6501 cb: virtual_view_cb,
6502 ctx: OptionRefAny::None,
6503 }
6504 }
6505
6506 fn huge_unicode_string() -> String {
6508 "ä🎉本".repeat(25_000)
6509 }
6510
6511 fn all_attribute_variants() -> Vec<AttributeType> {
6513 let nv = || AttributeNameValue {
6514 attr_name: "data-x".into(),
6515 value: "v".into(),
6516 };
6517 vec![
6518 AttributeType::Id("i".into()),
6519 AttributeType::Class("c".into()),
6520 AttributeType::AriaLabel("l".into()),
6521 AttributeType::AriaLabelledBy("lb".into()),
6522 AttributeType::AriaDescribedBy("db".into()),
6523 AttributeType::AriaRole("r".into()),
6524 AttributeType::AriaState(nv()),
6525 AttributeType::AriaProperty(nv()),
6526 AttributeType::Href("h".into()),
6527 AttributeType::Rel("rel".into()),
6528 AttributeType::Target("t".into()),
6529 AttributeType::Src("s".into()),
6530 AttributeType::Alt("a".into()),
6531 AttributeType::Title("ti".into()),
6532 AttributeType::Name("n".into()),
6533 AttributeType::Value("v".into()),
6534 AttributeType::InputType("text".into()),
6535 AttributeType::Placeholder("p".into()),
6536 AttributeType::Required,
6537 AttributeType::Disabled,
6538 AttributeType::Readonly,
6539 AttributeType::CheckedTrue,
6540 AttributeType::CheckedFalse,
6541 AttributeType::Selected,
6542 AttributeType::Max("10".into()),
6543 AttributeType::Min("0".into()),
6544 AttributeType::Step("1".into()),
6545 AttributeType::Pattern(".*".into()),
6546 AttributeType::MinLength(i32::MIN),
6547 AttributeType::MaxLength(i32::MAX),
6548 AttributeType::Autocomplete("off".into()),
6549 AttributeType::Scope("row".into()),
6550 AttributeType::ColSpan(-1),
6551 AttributeType::RowSpan(0),
6552 AttributeType::TabIndex(i32::MIN),
6553 AttributeType::Focusable,
6554 AttributeType::Lang("en".into()),
6555 AttributeType::Dir("rtl".into()),
6556 AttributeType::ContentEditable(true),
6557 AttributeType::Draggable(false),
6558 AttributeType::Hidden,
6559 AttributeType::Data(nv()),
6560 AttributeType::Custom(nv()),
6561 ]
6562 }
6563
6564 fn representative_node_types() -> Vec<NodeType> {
6566 vec![
6567 NodeType::Html,
6568 NodeType::Body,
6569 NodeType::Div,
6570 NodeType::Br,
6571 NodeType::Button,
6572 NodeType::Input,
6573 NodeType::TextArea,
6574 NodeType::Select,
6575 NodeType::A,
6576 NodeType::H1,
6577 NodeType::H6,
6578 NodeType::Table,
6579 NodeType::Td,
6580 NodeType::Svg,
6581 NodeType::SvgPath,
6582 NodeType::SvgText("svg text".into()),
6583 NodeType::SvgImage(ImageRef::null_image(
6584 1,
6585 1,
6586 crate::resources::RawImageFormat::R8,
6587 Vec::new(),
6588 )),
6589 NodeType::Before,
6590 NodeType::After,
6591 NodeType::Marker,
6592 NodeType::Placeholder,
6593 NodeType::Text(BoxOrStatic::heap(AzString::from("hello"))),
6594 NodeType::Image(BoxOrStatic::heap(ImageRef::null_image(
6595 2,
6596 2,
6597 crate::resources::RawImageFormat::RGBA8,
6598 Vec::new(),
6599 ))),
6600 NodeType::VirtualView,
6601 NodeType::Icon(BoxOrStatic::heap(AzString::from("home"))),
6602 NodeType::GeolocationProbe(crate::geolocation::GeolocationProbeConfig::default()),
6603 ]
6604 }
6605
6606 #[test]
6611 fn node_flags_new_is_empty_and_matches_default() {
6612 let f = NodeFlags::new();
6613 assert_eq!(f.inner, 0);
6614 assert_eq!(f, NodeFlags::default());
6615 assert!(!f.is_contenteditable());
6616 assert!(!f.is_anonymous());
6617 assert_eq!(f.get_tab_index(), None);
6618 }
6619
6620 #[test]
6621 fn node_flags_tab_index_round_trips_for_all_variants() {
6622 for ti in [
6623 None,
6624 Some(TabIndex::Auto),
6625 Some(TabIndex::NoKeyboardFocus),
6626 Some(TabIndex::OverrideInParent(0)),
6627 Some(TabIndex::OverrideInParent(1)),
6628 Some(TabIndex::OverrideInParent(1_000)),
6629 ] {
6630 let mut f = NodeFlags::new();
6631 f.set_tab_index(ti);
6632 assert_eq!(f.get_tab_index(), ti, "round-trip failed for {ti:?}");
6633 }
6634 }
6635
6636 #[test]
6637 fn node_flags_tab_index_round_trips_at_the_28_bit_boundary() {
6638 const MAX_EXACT: u32 = (1 << 28) - 1;
6641 let mut f = NodeFlags::new();
6642 f.set_tab_index(Some(TabIndex::OverrideInParent(MAX_EXACT)));
6643 assert_eq!(
6644 f.get_tab_index(),
6645 Some(TabIndex::OverrideInParent(MAX_EXACT))
6646 );
6647 }
6648
6649 #[test]
6650 fn node_flags_tab_index_above_28_bits_truncates_without_corrupting_other_flags() {
6651 const OVERFLOW: u32 = 1 << 28;
6657 let mut f = NodeFlags::new();
6658 f.set_tab_index(Some(TabIndex::OverrideInParent(OVERFLOW)));
6659 assert_eq!(
6660 f.get_tab_index(),
6661 Some(TabIndex::OverrideInParent(0)),
6662 "2^28 truncates to 0 (documented lossiness)"
6663 );
6664 assert!(!f.is_anonymous(), "overflow bit must not set ANONYMOUS");
6665 assert!(!f.is_contenteditable());
6666
6667 let mut f = NodeFlags::new();
6668 f.set_tab_index(Some(TabIndex::OverrideInParent(u32::MAX)));
6669 assert_eq!(
6670 f.get_tab_index(),
6671 Some(TabIndex::OverrideInParent((1 << 28) - 1)),
6672 "u32::MAX truncates to the 28-bit mask"
6673 );
6674 assert!(!f.is_anonymous(), "u32::MAX must not set ANONYMOUS");
6675 assert!(!f.is_contenteditable(), "u32::MAX must not set CONTENTEDITABLE");
6676 }
6677
6678 #[test]
6679 fn node_flags_set_tab_index_preserves_contenteditable_and_anonymous() {
6680 let mut f = NodeFlags::new();
6681 f.set_contenteditable_mut(true);
6682 f.set_anonymous(true);
6683
6684 for ti in [
6685 None,
6686 Some(TabIndex::Auto),
6687 Some(TabIndex::NoKeyboardFocus),
6688 Some(TabIndex::OverrideInParent((1 << 28) - 1)),
6693 Some(TabIndex::OverrideInParent(7)),
6694 ] {
6695 f.set_tab_index(ti);
6696 assert!(f.is_contenteditable(), "contenteditable lost for {ti:?}");
6697 assert!(f.is_anonymous(), "anonymous lost for {ti:?}");
6698 assert_eq!(f.get_tab_index(), ti);
6699 }
6700 }
6701
6702 #[test]
6703 fn node_flags_set_contenteditable_preserves_tab_index_and_anonymous() {
6704 let mut f = NodeFlags::new();
6705 f.set_anonymous(true);
6706 f.set_tab_index(Some(TabIndex::OverrideInParent(12_345)));
6707
6708 f.set_contenteditable_mut(true);
6709 assert!(f.is_contenteditable());
6710 assert!(f.is_anonymous());
6711 assert_eq!(f.get_tab_index(), Some(TabIndex::OverrideInParent(12_345)));
6712
6713 f.set_contenteditable_mut(false);
6714 assert!(!f.is_contenteditable());
6715 assert!(f.is_anonymous());
6716 assert_eq!(f.get_tab_index(), Some(TabIndex::OverrideInParent(12_345)));
6717 }
6718
6719 #[test]
6720 fn node_flags_set_anonymous_preserves_tab_index_and_contenteditable() {
6721 let mut f = NodeFlags::new();
6722 f.set_contenteditable_mut(true);
6723 f.set_tab_index(Some(TabIndex::NoKeyboardFocus));
6724
6725 f.set_anonymous(true);
6726 assert!(f.is_anonymous());
6727 assert!(f.is_contenteditable());
6728 assert_eq!(f.get_tab_index(), Some(TabIndex::NoKeyboardFocus));
6729
6730 f.set_anonymous(false);
6731 assert!(!f.is_anonymous());
6732 assert!(f.is_contenteditable());
6733 assert_eq!(f.get_tab_index(), Some(TabIndex::NoKeyboardFocus));
6734 }
6735
6736 #[test]
6737 fn node_flags_consecutive_set_contenteditable_is_idempotent() {
6738 let mut f = NodeFlags::new();
6739 f.set_contenteditable_mut(true);
6740 let once = f;
6741 f.set_contenteditable_mut(true);
6742 assert_eq!(f, once, "setting twice must not toggle");
6743 }
6744
6745 #[test]
6746 fn node_flags_builder_and_mut_setter_agree() {
6747 for v in [true, false] {
6748 let builder = NodeFlags::new().set_contenteditable(v);
6749 let mut mutated = NodeFlags::new();
6750 mutated.set_contenteditable_mut(v);
6751 assert_eq!(builder, mutated, "builder/mut disagree for {v}");
6752 }
6753 }
6754
6755 #[test]
6756 fn node_flags_all_bits_set_decodes_without_panicking() {
6757 let f = NodeFlags { inner: u32::MAX };
6761 assert!(f.is_contenteditable());
6762 assert!(f.is_anonymous());
6763 assert_eq!(f.get_tab_index(), Some(TabIndex::NoKeyboardFocus));
6765 }
6766
6767 #[test]
6768 fn node_flags_get_tab_index_is_total_over_the_tag_bits() {
6769 for tag in 0u32..4 {
6772 for extra in [0u32, u32::MAX] {
6773 let inner = (tag << 29) | (extra & !(0b11 << 29));
6774 let f = NodeFlags { inner };
6775 let decoded = f.get_tab_index();
6776 match tag {
6777 0 => assert_eq!(decoded, None),
6778 1 => assert_eq!(decoded, Some(TabIndex::Auto)),
6779 2 => assert!(matches!(decoded, Some(TabIndex::OverrideInParent(_)))),
6780 _ => assert_eq!(decoded, Some(TabIndex::NoKeyboardFocus)),
6781 }
6782 }
6783 }
6784 }
6785
6786 #[test]
6791 fn tab_index_default_is_auto_with_index_zero() {
6792 assert_eq!(TabIndex::default(), TabIndex::Auto);
6793 assert_eq!(TabIndex::default().get_index(), 0);
6794 }
6795
6796 #[test]
6797 fn tab_index_get_index_at_numeric_limits() {
6798 assert_eq!(TabIndex::Auto.get_index(), 0);
6799 assert_eq!(TabIndex::NoKeyboardFocus.get_index(), -1);
6800 assert_eq!(TabIndex::OverrideInParent(0).get_index(), 0);
6801 let max = TabIndex::OverrideInParent(u32::MAX).get_index();
6804 assert_eq!(max, u32::MAX as isize);
6805 assert!(max > 0, "u32::MAX must not wrap to a negative isize");
6806 }
6807
6808 #[test]
6809 fn get_effective_tabindex_saturates_into_i32() {
6810 let nd = NodeData::create_div().with_tab_index(TabIndex::OverrideInParent(u32::MAX));
6814 assert_eq!(nd.get_effective_tabindex(), Some((1 << 28) - 1));
6815
6816 assert_eq!(
6817 NodeData::create_div()
6818 .with_tab_index(TabIndex::Auto)
6819 .get_effective_tabindex(),
6820 Some(0)
6821 );
6822 assert_eq!(
6823 NodeData::create_div()
6824 .with_tab_index(TabIndex::NoKeyboardFocus)
6825 .get_effective_tabindex(),
6826 Some(-1)
6827 );
6828 assert_eq!(NodeData::create_div().get_effective_tabindex(), None);
6829 }
6830
6831 #[test]
6832 fn get_effective_tabindex_falls_back_to_zero_for_focus_callbacks() {
6833 let nd = NodeData::create_div().with_callback(
6834 EventFilter::Focus(FocusEventFilter::MouseDown),
6835 RefAny::new(0u32),
6836 0usize,
6837 );
6838 assert_eq!(nd.get_effective_tabindex(), Some(0));
6839 }
6840
6841 #[test]
6846 fn tag_id_unique_never_returns_zero_and_never_repeats() {
6847 let ids: Vec<TagId> = (0..512).map(|_| TagId::unique()).collect();
6850 for id in &ids {
6851 assert_ne!(id.inner, 0, "TagId 0 is reserved for 'no tag'");
6852 }
6853 let mut sorted: Vec<u64> = ids.iter().map(|t| t.inner).collect();
6854 sorted.sort_unstable();
6855 sorted.dedup();
6856 assert_eq!(sorted.len(), 512, "TagId::unique() handed out a duplicate");
6857 }
6858
6859 #[test]
6860 fn tag_id_crate_internal_conversions_are_identity_at_limits() {
6861 for inner in [0u64, 1, u64::MAX, u64::MAX - 1] {
6862 let t = TagId { inner };
6863 assert_eq!(t.into_crate_internal(), t);
6864 assert_eq!(TagId::from_crate_internal(t), t);
6865 assert_eq!(
6867 TagId::from_crate_internal(t.into_crate_internal()).inner,
6868 inner
6869 );
6870 }
6871 }
6872
6873 #[test]
6874 fn tag_id_display_is_non_empty_at_numeric_limits() {
6875 for inner in [0u64, 1, u64::MAX] {
6876 let s = format!("{}", TagId { inner });
6877 assert!(!s.is_empty());
6878 assert!(s.contains(&inner.to_string()), "{s} should contain {inner}");
6879 }
6880 }
6881
6882 #[test]
6883 fn scroll_tag_id_unique_is_distinct_and_debug_matches_display() {
6884 let a = ScrollTagId::unique();
6885 let b = ScrollTagId::unique();
6886 assert_ne!(a, b);
6887 assert_ne!(a.inner.inner, 0);
6888
6889 let s = ScrollTagId {
6890 inner: TagId { inner: u64::MAX },
6891 };
6892 assert_eq!(format!("{s:?}"), format!("{s}"));
6893 assert!(!format!("{s}").is_empty());
6894 }
6895
6896 #[test]
6901 fn attribute_boolean_attrs_always_have_an_empty_value() {
6902 for attr in all_attribute_variants() {
6905 if attr.is_boolean() {
6906 assert_eq!(
6907 attr.value().as_str(),
6908 "",
6909 "boolean attr {} must have an empty value",
6910 attr.name()
6911 );
6912 }
6913 }
6914 }
6915
6916 #[test]
6917 fn attribute_name_and_value_never_panic_for_any_variant() {
6918 for attr in all_attribute_variants() {
6919 let name = attr.name();
6920 let value = attr.value();
6921 assert!(!name.is_empty(), "empty name for {attr:?}");
6924 let _ = value.as_str();
6925 }
6926 }
6927
6928 #[test]
6929 fn attribute_custom_with_empty_name_returns_empty_name_without_panicking() {
6930 let attr = AttributeType::Custom(AttributeNameValue {
6931 attr_name: "".into(),
6932 value: "".into(),
6933 });
6934 assert_eq!(attr.name(), "");
6935 assert_eq!(attr.value().as_str(), "");
6936 assert!(!attr.is_boolean());
6937 }
6938
6939 #[test]
6940 fn attribute_as_id_and_as_class_are_mutually_exclusive() {
6941 for attr in all_attribute_variants() {
6942 match &attr {
6943 AttributeType::Id(s) => {
6944 assert_eq!(attr.as_id(), Some(s.as_str()));
6945 assert_eq!(attr.as_class(), None);
6946 }
6947 AttributeType::Class(s) => {
6948 assert_eq!(attr.as_class(), Some(s.as_str()));
6949 assert_eq!(attr.as_id(), None);
6950 }
6951 _ => {
6952 assert_eq!(attr.as_id(), None, "as_id must be None for {attr:?}");
6953 assert_eq!(attr.as_class(), None, "as_class must be None for {attr:?}");
6954 }
6955 }
6956 }
6957 }
6958
6959 #[test]
6960 fn attribute_numeric_values_serialize_at_i32_limits() {
6961 assert_eq!(
6962 AttributeType::MinLength(i32::MIN).value().as_str(),
6963 "-2147483648"
6964 );
6965 assert_eq!(
6966 AttributeType::MaxLength(i32::MAX).value().as_str(),
6967 "2147483647"
6968 );
6969 assert_eq!(AttributeType::ColSpan(0).value().as_str(), "0");
6970 assert_eq!(AttributeType::RowSpan(-1).value().as_str(), "-1");
6971 assert_eq!(
6972 AttributeType::TabIndex(i32::MIN).value().as_str(),
6973 "-2147483648"
6974 );
6975 }
6976
6977 #[test]
6978 fn attribute_focusable_is_tabindex_zero_and_not_boolean() {
6979 let f = AttributeType::Focusable;
6982 assert_eq!(f.name(), "tabindex");
6983 assert_eq!(f.value().as_str(), "0");
6984 assert!(!f.is_boolean());
6985 assert_eq!(AttributeType::TabIndex(0).name(), "tabindex");
6986 }
6987
6988 #[test]
6989 fn attribute_checked_true_and_false_are_both_boolean_and_share_a_name() {
6990 assert!(AttributeType::CheckedTrue.is_boolean());
6994 assert!(AttributeType::CheckedFalse.is_boolean());
6995 assert_eq!(AttributeType::CheckedTrue.name(), "checked");
6996 assert_eq!(AttributeType::CheckedFalse.name(), "checked");
6997 assert_eq!(AttributeType::CheckedFalse.value().as_str(), "");
6998 assert_ne!(AttributeType::CheckedTrue, AttributeType::CheckedFalse);
7000 }
7001
7002 #[test]
7003 fn attribute_content_editable_and_draggable_stringify_bools() {
7004 assert_eq!(AttributeType::ContentEditable(true).value().as_str(), "true");
7005 assert_eq!(
7006 AttributeType::ContentEditable(false).value().as_str(),
7007 "false"
7008 );
7009 assert_eq!(AttributeType::Draggable(true).value().as_str(), "true");
7010 assert_eq!(AttributeType::Draggable(false).value().as_str(), "false");
7011 assert!(!AttributeType::ContentEditable(false).is_boolean());
7012 }
7013
7014 #[test]
7015 fn attribute_round_trips_huge_unicode_values() {
7016 let big = huge_unicode_string();
7017 let attr = AttributeType::Value(big.clone().into());
7018 assert_eq!(attr.value().as_str(), big.as_str());
7019 assert_eq!(attr.name(), "value");
7020
7021 let id = AttributeType::Id(big.clone().into());
7022 assert_eq!(id.as_id(), Some(big.as_str()));
7023 }
7024
7025 #[test]
7026 fn id_or_class_accessors_are_mutually_exclusive() {
7027 let id = IdOrClass::Id("my-id".into());
7028 let class = IdOrClass::Class("my-class".into());
7029 assert_eq!(id.as_id(), Some("my-id"));
7030 assert_eq!(id.as_class(), None);
7031 assert_eq!(class.as_class(), Some("my-class"));
7032 assert_eq!(class.as_id(), None);
7033
7034 assert_eq!(IdOrClass::Id("".into()).as_id(), Some(""));
7036 assert_eq!(IdOrClass::Class("".into()).as_class(), Some(""));
7037 }
7038
7039 #[test]
7044 fn input_type_as_str_is_non_empty_and_unique_per_variant() {
7045 let all = [
7046 InputType::Text,
7047 InputType::Button,
7048 InputType::Checkbox,
7049 InputType::Color,
7050 InputType::Date,
7051 InputType::Datetime,
7052 InputType::DatetimeLocal,
7053 InputType::Email,
7054 InputType::File,
7055 InputType::Hidden,
7056 InputType::Image,
7057 InputType::Month,
7058 InputType::Number,
7059 InputType::Password,
7060 InputType::Radio,
7061 InputType::Range,
7062 InputType::Reset,
7063 InputType::Search,
7064 InputType::Submit,
7065 InputType::Tel,
7066 InputType::Time,
7067 InputType::Url,
7068 InputType::Week,
7069 ];
7070 let mut seen: Vec<&str> = all.iter().map(InputType::as_str).collect();
7071 for s in &seen {
7072 assert!(!s.is_empty());
7073 assert!(
7074 !s.contains(char::is_whitespace),
7075 "{s} is not a valid HTML attribute value"
7076 );
7077 }
7078 let len = seen.len();
7079 seen.sort_unstable();
7080 seen.dedup();
7081 assert_eq!(seen.len(), len, "two InputType variants share an as_str()");
7082
7083 assert_eq!(InputType::DatetimeLocal.as_str(), "datetime-local");
7084 assert_eq!(InputType::Text.as_str(), "text");
7085 }
7086
7087 #[test]
7092 fn node_type_to_library_owned_round_trips_every_variant() {
7093 for nt in representative_node_types() {
7096 let owned = nt.to_library_owned_nodetype();
7097 assert_eq!(owned, nt, "to_library_owned_nodetype lost data for {nt:?}");
7098 assert_eq!(owned.get_path(), nt.get_path());
7099 }
7100 }
7101
7102 #[test]
7103 fn node_type_get_path_and_format_never_panic() {
7104 for nt in representative_node_types() {
7105 let _tag = nt.get_path();
7106 let _fmt = nt.format();
7107 let _semantic = nt.is_semantic_for_accessibility();
7108 }
7109 }
7110
7111 #[test]
7112 fn node_type_format_returns_content_only_for_content_variants() {
7113 assert_eq!(NodeType::Div.format(), None);
7114 assert_eq!(NodeType::Br.format(), None);
7115 assert_eq!(NodeType::Button.format(), None);
7116
7117 assert_eq!(
7118 NodeType::Text(BoxOrStatic::heap(AzString::from("hi"))).format(),
7119 Some("hi".to_string())
7120 );
7121 assert_eq!(
7122 NodeType::VirtualView.format(),
7123 Some("virtualized-view".to_string())
7124 );
7125 assert_eq!(
7126 NodeType::Icon(BoxOrStatic::heap(AzString::from("home"))).format(),
7127 Some("icon(home)".to_string())
7128 );
7129 }
7130
7131 #[test]
7132 fn node_type_format_handles_empty_and_unicode_text() {
7133 assert_eq!(
7134 NodeType::Text(BoxOrStatic::heap(AzString::from(""))).format(),
7135 Some(String::new())
7136 );
7137 let unicode = "日本語 🎉 ünïcødé";
7138 assert_eq!(
7139 NodeType::Text(BoxOrStatic::heap(AzString::from(unicode))).format(),
7140 Some(unicode.to_string())
7141 );
7142 }
7143
7144 #[test]
7145 fn node_type_format_of_geolocation_probe_survives_nan_and_infinity() {
7146 for max_accuracy_m in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY, -0.0, f32::MAX] {
7149 let cfg = crate::geolocation::GeolocationProbeConfig {
7150 high_accuracy: true,
7151 background: true,
7152 max_accuracy_m,
7153 min_interval_ms: u32::MAX,
7154 };
7155 let out = NodeType::GeolocationProbe(cfg)
7156 .format()
7157 .expect("GeolocationProbe always formats");
7158 assert!(out.starts_with("geolocation-probe("));
7159 assert!(out.contains("4294967295"), "min_interval_ms must be printed");
7160 }
7161 }
7162
7163 #[test]
7164 fn geolocation_probe_nan_is_self_equal_and_hash_consistent() {
7165 let cfg = crate::geolocation::GeolocationProbeConfig {
7168 max_accuracy_m: f32::NAN,
7169 ..Default::default()
7170 };
7171 let a = NodeType::GeolocationProbe(cfg);
7172 let b = NodeType::GeolocationProbe(cfg);
7173 assert_eq!(a, b, "bitwise-NaN configs must compare equal");
7174 assert_eq!(
7175 hash_of(&a),
7176 hash_of(&b),
7177 "Eq == true but hashes differ: violates the Hash/Eq contract"
7178 );
7179 assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
7180 }
7181
7182 #[test]
7183 fn node_type_is_semantic_for_accessibility_known_true_and_false() {
7184 for nt in [
7185 NodeType::Button,
7186 NodeType::Input,
7187 NodeType::TextArea,
7188 NodeType::Select,
7189 NodeType::A,
7190 NodeType::H1,
7191 NodeType::H6,
7192 NodeType::Article,
7193 NodeType::Nav,
7194 NodeType::Main,
7195 ] {
7196 assert!(
7197 nt.is_semantic_for_accessibility(),
7198 "{nt:?} should be semantic"
7199 );
7200 }
7201 for nt in [
7202 NodeType::Div,
7203 NodeType::Span,
7204 NodeType::Br,
7205 NodeType::VirtualView,
7206 NodeType::Text(BoxOrStatic::heap(AzString::from("x"))),
7207 ] {
7208 assert!(
7209 !nt.is_semantic_for_accessibility(),
7210 "{nt:?} should not be semantic"
7211 );
7212 }
7213 }
7214
7215 #[test]
7216 fn node_type_text_variants_are_content_sensitive() {
7217 let a = NodeType::Text(BoxOrStatic::heap(AzString::from("a")));
7218 let b = NodeType::Text(BoxOrStatic::heap(AzString::from("b")));
7219 assert_ne!(a, b);
7220 assert_eq!(a.get_path(), b.get_path(), "same tag, different content");
7221 }
7222
7223 #[test]
7228 fn node_data_default_has_no_attributes_and_no_extra_state() {
7229 let nd = NodeData::default();
7230 assert!(nd.is_node_type(NodeType::Div));
7231 assert!(nd.attributes().as_ref().is_empty());
7232 assert!(nd.get_ids_and_classes().as_ref().is_empty());
7233 assert!(nd.get_dataset().is_none());
7234 assert!(nd.get_key().is_none());
7235 assert!(nd.get_menu_bar().is_none());
7236 assert!(nd.get_context_menu().is_none());
7237 assert!(nd.get_svg_data().is_none());
7238 assert!(nd.get_image_clip_mask().is_none());
7239 assert!(nd.get_accessibility_info().is_none());
7240 assert!(nd.get_merge_callback().is_none());
7241 assert!(nd.get_component_origin().is_none());
7242 assert!(!nd.has_context_menu());
7243 assert!(!nd.is_contenteditable());
7244 assert!(!nd.is_anonymous());
7245 assert_eq!(nd.get_tab_index(), None);
7246 }
7247
7248 #[test]
7249 fn attributes_mut_lazily_allocates_but_stays_empty() {
7250 let mut nd = NodeData::create_div();
7251 assert!(nd.attributes().as_ref().is_empty());
7252 let _ = nd.attributes_mut(); assert!(
7254 nd.attributes().as_ref().is_empty(),
7255 "lazy alloc must not invent attributes"
7256 );
7257 nd.add_id("x".into());
7258 assert_eq!(nd.attributes().as_ref().len(), 1);
7259 }
7260
7261 #[test]
7262 fn has_id_and_has_class_match_exactly_not_by_prefix() {
7263 let mut nd = NodeData::create_div();
7264 nd.add_id("header".into());
7265 nd.add_class("btn".into());
7266
7267 assert!(nd.has_id("header"));
7268 assert!(nd.has_class("btn"));
7269 assert!(!nd.has_id("head"));
7271 assert!(!nd.has_id("header2"));
7272 assert!(!nd.has_class("bt"));
7273 assert!(!nd.has_class(""));
7274 assert!(!nd.has_class("header"));
7276 assert!(!nd.has_id("btn"));
7277 }
7278
7279 #[test]
7280 fn has_id_matches_the_empty_string_id() {
7281 let mut nd = NodeData::create_div();
7282 assert!(!nd.has_id(""), "no ids at all => empty id must not match");
7283 nd.add_id("".into());
7284 assert!(nd.has_id(""), "an explicitly-added empty id must match");
7285 assert!(!nd.has_id("x"));
7286 }
7287
7288 #[test]
7289 fn has_id_and_has_class_handle_unicode_and_huge_strings() {
7290 let unicode = "日本語-🎉-ünïcødé";
7291 let big = huge_unicode_string();
7292
7293 let mut nd = NodeData::create_div();
7294 nd.add_id(unicode.into());
7295 nd.add_class(big.clone().into());
7296
7297 assert!(nd.has_id(unicode));
7298 assert!(nd.has_class(big.as_str()));
7299 assert!(!nd.has_id("日本語"));
7301 }
7302
7303 #[test]
7304 fn duplicate_ids_are_kept_and_still_match() {
7305 let mut nd = NodeData::create_div();
7306 nd.add_id("dup".into());
7307 nd.add_id("dup".into());
7308 assert!(nd.has_id("dup"));
7309 assert_eq!(
7310 nd.get_ids_and_classes().as_ref().len(),
7311 2,
7312 "add_id does not deduplicate"
7313 );
7314 }
7315
7316 #[test]
7317 fn get_ids_and_classes_preserves_insertion_order_and_kind() {
7318 let mut nd = NodeData::create_div();
7319 nd.add_id("i1".into());
7320 nd.add_class("c1".into());
7321 nd.add_id("i2".into());
7322
7323 let v = nd.get_ids_and_classes();
7324 let v = v.as_ref();
7325 assert_eq!(v.len(), 3);
7326 assert_eq!(v[0], IdOrClass::Id("i1".into()));
7327 assert_eq!(v[1], IdOrClass::Class("c1".into()));
7328 assert_eq!(v[2], IdOrClass::Id("i2".into()));
7329 }
7330
7331 #[test]
7332 fn get_ids_and_classes_ignores_non_id_class_attributes() {
7333 let mut nd = NodeData::create_div();
7334 nd.set_attributes(
7335 vec![
7336 AttributeType::Href("/x".into()),
7337 AttributeType::Id("i".into()),
7338 AttributeType::Disabled,
7339 AttributeType::Class("c".into()),
7340 ]
7341 .into(),
7342 );
7343 let v = nd.get_ids_and_classes();
7344 assert_eq!(v.as_ref().len(), 2);
7345 }
7346
7347 #[test]
7348 fn set_ids_and_classes_replaces_ids_but_preserves_other_attributes() {
7349 let mut nd = NodeData::create_div();
7352 nd.set_attributes(
7353 vec![
7354 AttributeType::Href("/old".into()),
7355 AttributeType::Id("old-id".into()),
7356 AttributeType::Class("old-class".into()),
7357 AttributeType::Disabled,
7358 ]
7359 .into(),
7360 );
7361
7362 nd.set_ids_and_classes(vec![IdOrClass::Class("new-class".into())].into());
7363
7364 assert!(!nd.has_id("old-id"), "old id must be dropped");
7365 assert!(!nd.has_class("old-class"), "old class must be dropped");
7366 assert!(nd.has_class("new-class"));
7367 let attrs = nd.attributes().as_ref();
7369 assert!(attrs.contains(&AttributeType::Href("/old".into())));
7370 assert!(attrs.contains(&AttributeType::Disabled));
7371 assert_eq!(attrs.len(), 3);
7372 }
7373
7374 #[test]
7375 fn set_ids_and_classes_with_an_empty_vec_clears_all_ids_and_classes() {
7376 let mut nd = NodeData::create_div();
7377 nd.add_id("i".into());
7378 nd.add_class("c".into());
7379 nd.set_ids_and_classes(Vec::new().into());
7380 assert!(nd.get_ids_and_classes().as_ref().is_empty());
7381 assert!(!nd.has_id("i"));
7382 assert!(!nd.has_class("c"));
7383 }
7384
7385 #[test]
7386 fn set_ids_and_classes_is_idempotent_when_reapplied() {
7387 let mut nd = NodeData::create_div();
7388 let ids: IdOrClassVec = vec![
7389 IdOrClass::Id("i".into()),
7390 IdOrClass::Class("c".into()),
7391 ]
7392 .into();
7393 nd.set_ids_and_classes(ids.clone());
7394 let after_first = nd.attributes().clone();
7395 nd.set_ids_and_classes(ids);
7396 assert_eq!(
7397 nd.attributes().as_ref(),
7398 after_first.as_ref(),
7399 "re-applying the same ids/classes must not duplicate them"
7400 );
7401 }
7402
7403 #[test]
7404 fn with_attribute_appends_without_dropping_existing_attributes() {
7405 let nd = NodeData::create_div()
7408 .with_attribute(AttributeType::Href("/a".into()))
7409 .with_attribute(AttributeType::Alt("alt".into()));
7410 let attrs = nd.attributes().as_ref();
7411 assert_eq!(attrs.len(), 2);
7412 assert_eq!(attrs[0], AttributeType::Href("/a".into()));
7413 assert_eq!(attrs[1], AttributeType::Alt("alt".into()));
7414 }
7415
7416 #[test]
7421 fn create_node_shorthands_produce_the_right_node_type() {
7422 assert!(NodeData::create_body().is_node_type(NodeType::Body));
7423 assert!(NodeData::create_div().is_node_type(NodeType::Div));
7424 assert!(NodeData::create_br().is_node_type(NodeType::Br));
7425 assert!(NodeData::create_button_no_a11y().is_node_type(NodeType::Button));
7426 assert!(NodeData::create_table_no_a11y().is_node_type(NodeType::Table));
7427 }
7428
7429 #[test]
7430 fn create_text_accepts_empty_unicode_and_huge_input() {
7431 for s in ["", "x", "日本語 🎉"] {
7432 let nd = NodeData::create_text(s);
7433 assert!(nd.is_text_node());
7434 assert_eq!(nd.get_node_type().format(), Some(s.to_string()));
7435 }
7436 let big = huge_unicode_string();
7437 let nd = NodeData::create_text(big.clone());
7438 assert!(nd.is_text_node());
7439 assert_eq!(nd.get_node_type().format(), Some(big));
7440 }
7441
7442 #[test]
7443 fn create_a_stores_href_and_accessibility_name() {
7444 let nd = NodeData::create_a("/home".into(), SmallAriaInfo::label("Home"));
7445 assert!(nd.is_node_type(NodeType::A));
7446 assert!(nd
7447 .attributes()
7448 .as_ref()
7449 .contains(&AttributeType::Href("/home".into())));
7450 let info = nd
7451 .get_accessibility_info()
7452 .expect("create_a must set accessibility info");
7453 assert_eq!(info.accessibility_name, OptionString::Some("Home".into()));
7454 }
7455
7456 #[test]
7457 fn create_a_no_a11y_has_href_but_no_accessibility_info() {
7458 let nd = NodeData::create_a_no_a11y("/x".into());
7459 assert!(nd
7460 .attributes()
7461 .as_ref()
7462 .contains(&AttributeType::Href("/x".into())));
7463 assert!(nd.get_accessibility_info().is_none());
7464 }
7465
7466 #[test]
7467 fn create_a_accepts_an_empty_href() {
7468 let nd = NodeData::create_a_no_a11y("".into());
7469 assert_eq!(
7470 nd.attributes().as_ref()[0],
7471 AttributeType::Href("".into()),
7472 "empty href is stored verbatim, not dropped"
7473 );
7474 }
7475
7476 #[test]
7477 fn create_input_stores_all_three_attributes_in_order() {
7478 let nd = NodeData::create_input_no_a11y("text".into(), "user".into(), "Username".into());
7479 assert!(nd.is_node_type(NodeType::Input));
7480 let attrs = nd.attributes().as_ref();
7481 assert_eq!(attrs.len(), 3);
7482 assert_eq!(attrs[0], AttributeType::InputType("text".into()));
7483 assert_eq!(attrs[1], AttributeType::Name("user".into()));
7484 assert_eq!(attrs[2], AttributeType::AriaLabel("Username".into()));
7485 }
7486
7487 #[test]
7488 fn create_input_with_a11y_sets_both_attributes_and_accessibility_info() {
7489 let nd = NodeData::create_input(
7490 "password".into(),
7491 "pw".into(),
7492 "Password".into(),
7493 SmallAriaInfo::label("Password").with_role(AccessibilityRole::Text),
7494 );
7495 assert_eq!(nd.attributes().as_ref().len(), 3);
7496 let info = nd.get_accessibility_info().expect("a11y info");
7497 assert_eq!(info.role, AccessibilityRole::Text);
7498 }
7499
7500 #[test]
7501 fn create_textarea_and_select_store_name_and_label() {
7502 let ta = NodeData::create_textarea_no_a11y("body".into(), "Body".into());
7503 assert!(ta.is_node_type(NodeType::TextArea));
7504 assert_eq!(ta.attributes().as_ref().len(), 2);
7505
7506 let sel = NodeData::create_select_no_a11y("country".into(), "Country".into());
7507 assert!(sel.is_node_type(NodeType::Select));
7508 assert_eq!(
7509 sel.attributes().as_ref()[0],
7510 AttributeType::Name("country".into())
7511 );
7512 }
7513
7514 #[test]
7515 fn create_label_uses_a_custom_for_attribute() {
7516 let nd = NodeData::create_label_no_a11y("email-input".into());
7517 assert!(nd.is_node_type(NodeType::Label));
7518 assert_eq!(
7519 nd.attributes().as_ref()[0],
7520 AttributeType::Custom(AttributeNameValue {
7521 attr_name: "for".into(),
7522 value: "email-input".into(),
7523 })
7524 );
7525 assert_eq!(nd.attributes().as_ref()[0].name(), "for");
7526 assert_eq!(nd.attributes().as_ref()[0].value().as_str(), "email-input");
7527 }
7528
7529 #[test]
7530 fn create_button_and_table_with_aria_set_accessibility_info() {
7531 let btn = NodeData::create_button(
7532 SmallAriaInfo::label("Save").with_role(AccessibilityRole::PushButton),
7533 );
7534 let info = btn.get_accessibility_info().expect("a11y info");
7535 assert_eq!(info.role, AccessibilityRole::PushButton);
7536 assert_eq!(info.accessibility_name, OptionString::Some("Save".into()));
7537
7538 let table = NodeData::create_table(SmallAriaInfo::label("Results"));
7539 assert!(table.is_node_type(NodeType::Table));
7540 assert!(table.get_accessibility_info().is_some());
7541 }
7542
7543 #[test]
7544 fn a11y_constructors_accept_empty_aria_labels() {
7545 let btn = NodeData::create_button(SmallAriaInfo::label(""));
7546 let info = btn.get_accessibility_info().expect("a11y info");
7547 assert_eq!(info.accessibility_name, OptionString::Some("".into()));
7548 assert_eq!(info.role, AccessibilityRole::Unknown);
7550 }
7551
7552 #[test]
7553 fn create_image_and_is_node_type_round_trip() {
7554 let img = ImageRef::null_image(4, 4, crate::resources::RawImageFormat::RGBA8, Vec::new());
7555 let nd = NodeData::create_image(img.clone());
7556 assert!(!nd.is_text_node());
7557 assert_eq!(nd.get_node_type().get_path(), NodeTypeTag::Img);
7558 assert!(nd.is_node_type(NodeType::Image(BoxOrStatic::heap(img))));
7559 }
7560
7561 #[test]
7566 fn is_node_type_is_content_sensitive_for_text() {
7567 let nd = NodeData::create_text("a");
7568 assert!(nd.is_node_type(NodeType::Text(BoxOrStatic::heap(AzString::from("a")))));
7569 assert!(
7570 !nd.is_node_type(NodeType::Text(BoxOrStatic::heap(AzString::from("b")))),
7571 "is_node_type compares payloads, not just the discriminant"
7572 );
7573 assert!(!nd.is_node_type(NodeType::Div));
7574 }
7575
7576 #[test]
7577 fn is_text_node_and_is_virtual_view_node() {
7578 assert!(NodeData::create_text("x").is_text_node());
7579 assert!(!NodeData::create_div().is_text_node());
7580
7581 let vv = NodeData::create_virtual_view(RefAny::new(1u32), virtual_view_callback());
7582 assert!(vv.is_virtual_view_node());
7583 assert!(!vv.is_text_node());
7584 assert!(vv.get_virtual_view_node_ref().is_some());
7585 assert!(!NodeData::create_div().is_virtual_view_node());
7586 assert!(NodeData::create_div().get_virtual_view_node_ref().is_none());
7587 }
7588
7589 #[test]
7590 fn has_context_menu_flips_only_after_set_context_menu() {
7591 let mut nd = NodeData::create_div();
7592 assert!(!nd.has_context_menu());
7593 nd.set_menu_bar(Menu::create(Vec::new().into()));
7595 assert!(
7596 !nd.has_context_menu(),
7597 "menu_bar must not satisfy has_context_menu"
7598 );
7599 assert!(nd.get_menu_bar().is_some());
7600
7601 nd.set_context_menu(Menu::create(Vec::new().into()));
7602 assert!(nd.has_context_menu());
7603 assert!(nd.get_context_menu().is_some());
7604 }
7605
7606 #[test]
7607 fn with_menu_bar_and_with_context_menu_are_independent_slots() {
7608 let nd = NodeData::create_div()
7609 .with_menu_bar(Menu::create(Vec::new().into()))
7610 .with_context_menu(Menu::create(Vec::new().into()));
7611 assert!(nd.get_menu_bar().is_some());
7612 assert!(nd.get_context_menu().is_some());
7613 assert!(nd.has_context_menu());
7614 }
7615
7616 #[test]
7617 fn is_focusable_for_naturally_focusable_and_opted_in_nodes() {
7618 for nt in [
7619 NodeType::A,
7620 NodeType::Button,
7621 NodeType::Input,
7622 NodeType::Select,
7623 NodeType::TextArea,
7624 ] {
7625 assert!(
7626 NodeData::create_node(nt.clone()).is_focusable(),
7627 "{nt:?} is naturally focusable"
7628 );
7629 }
7630 assert!(!NodeData::create_div().is_focusable());
7631 assert!(NodeData::create_div()
7632 .with_contenteditable(true)
7633 .is_focusable());
7634 assert!(NodeData::create_div()
7635 .with_tab_index(TabIndex::NoKeyboardFocus)
7636 .is_focusable());
7637 assert!(NodeData::create_div()
7638 .with_callback(
7639 EventFilter::Focus(FocusEventFilter::MouseDown),
7640 RefAny::new(0u32),
7641 0usize,
7642 )
7643 .is_focusable());
7644 assert!(!NodeData::create_div()
7646 .with_callback(
7647 EventFilter::Hover(HoverEventFilter::MouseOver),
7648 RefAny::new(0u32),
7649 0usize,
7650 )
7651 .is_focusable());
7652 }
7653
7654 #[test]
7655 fn has_activation_behavior_for_elements_callbacks_and_roles() {
7656 assert!(NodeData::create_node(NodeType::A).has_activation_behavior());
7657 assert!(NodeData::create_button_no_a11y().has_activation_behavior());
7658 assert!(!NodeData::create_div().has_activation_behavior());
7659
7660 for f in [HoverEventFilter::MouseUp, HoverEventFilter::LeftMouseUp] {
7661 assert!(NodeData::create_div()
7662 .with_callback(EventFilter::Hover(f), RefAny::new(0u32), 0usize)
7663 .has_activation_behavior());
7664 }
7665 assert!(!NodeData::create_div()
7667 .with_callback(
7668 EventFilter::Hover(HoverEventFilter::MouseDown),
7669 RefAny::new(0u32),
7670 0usize,
7671 )
7672 .has_activation_behavior());
7673
7674 let mut nd = NodeData::create_div();
7675 nd.set_accessibility_info(
7676 SmallAriaInfo::label("x")
7677 .with_role(AccessibilityRole::PushButton)
7678 .to_full_info(),
7679 );
7680 assert!(nd.has_activation_behavior(), "role=PushButton activates");
7681 }
7682
7683 #[test]
7684 fn is_activatable_is_false_for_unavailable_elements() {
7685 let mut nd = NodeData::create_button_no_a11y();
7686 assert!(nd.is_activatable());
7687
7688 let mut info = SmallAriaInfo::label("Save")
7689 .with_role(AccessibilityRole::PushButton)
7690 .to_full_info();
7691 info.states = vec![AccessibilityState::Unavailable].into();
7692 nd.set_accessibility_info(info);
7693
7694 assert!(nd.has_activation_behavior());
7695 assert!(
7696 !nd.is_activatable(),
7697 "an Unavailable (disabled) button must not be activatable"
7698 );
7699
7700 assert!(!NodeData::create_div().is_activatable());
7702 }
7703
7704 #[test]
7709 fn get_accessible_label_prefers_aria_label_over_alt_and_title() {
7710 let mut nd = NodeData::create_div();
7711 nd.set_attributes(
7712 vec![
7713 AttributeType::Title("title".into()),
7714 AttributeType::Alt("alt".into()),
7715 AttributeType::AriaLabel("aria".into()),
7716 ]
7717 .into(),
7718 );
7719 assert_eq!(
7720 nd.get_accessible_label(),
7721 Some("aria"),
7722 "aria-label wins regardless of attribute order"
7723 );
7724 }
7725
7726 #[test]
7727 fn get_accessible_label_alt_vs_title_is_order_dependent() {
7728 let mut title_first = NodeData::create_div();
7734 title_first.set_attributes(
7735 vec![
7736 AttributeType::Title("title".into()),
7737 AttributeType::Alt("alt".into()),
7738 ]
7739 .into(),
7740 );
7741 assert_eq!(title_first.get_accessible_label(), Some("title"));
7742
7743 let mut alt_first = NodeData::create_div();
7744 alt_first.set_attributes(
7745 vec![
7746 AttributeType::Alt("alt".into()),
7747 AttributeType::Title("title".into()),
7748 ]
7749 .into(),
7750 );
7751 assert_eq!(alt_first.get_accessible_label(), Some("alt"));
7752 }
7753
7754 #[test]
7755 fn get_accessible_label_value_and_placeholder_default_to_none() {
7756 let nd = NodeData::create_div();
7757 assert_eq!(nd.get_accessible_label(), None);
7758 assert_eq!(nd.get_accessible_value(), None);
7759 assert_eq!(nd.get_placeholder(), None);
7760 }
7761
7762 #[test]
7763 fn get_accessible_value_and_placeholder_return_the_first_match() {
7764 let mut nd = NodeData::create_div();
7765 nd.set_attributes(
7766 vec![
7767 AttributeType::Value("first".into()),
7768 AttributeType::Value("second".into()),
7769 AttributeType::Placeholder("ph".into()),
7770 ]
7771 .into(),
7772 );
7773 assert_eq!(nd.get_accessible_value(), Some("first"));
7774 assert_eq!(nd.get_placeholder(), Some("ph"));
7775 }
7776
7777 #[test]
7778 fn get_accessible_label_returns_empty_string_not_none_for_empty_aria_label() {
7779 let mut nd = NodeData::create_div();
7781 nd.set_attributes(vec![AttributeType::AriaLabel("".into())].into());
7782 assert_eq!(nd.get_accessible_label(), Some(""));
7783 }
7784
7785 #[test]
7790 fn dataset_set_get_take_round_trip() {
7791 let mut nd = NodeData::create_div();
7792 assert!(nd.get_dataset().is_none());
7793 assert!(nd.take_dataset().is_none(), "take on empty must be None");
7794
7795 nd.set_dataset(OptionRefAny::Some(RefAny::new(42u32)));
7796 assert!(nd.get_dataset().is_some());
7797 assert!(nd.get_dataset_mut().is_some());
7798
7799 let mut taken = nd.take_dataset().expect("dataset was set");
7800 assert_eq!(taken.downcast_ref::<u32>().map(|r| *r), Some(42));
7801 assert!(nd.get_dataset().is_none(), "take must clear the slot");
7802 assert!(nd.take_dataset().is_none(), "double-take must be None");
7803 }
7804
7805 #[test]
7806 fn set_dataset_none_clears_without_allocating_extra() {
7807 let mut nd = NodeData::create_div();
7808 nd.set_dataset(OptionRefAny::None);
7810 assert!(nd.get_dataset().is_none());
7811
7812 nd.set_dataset(OptionRefAny::Some(RefAny::new(1u8)));
7813 nd.set_dataset(OptionRefAny::None);
7814 assert!(nd.get_dataset().is_none());
7815 }
7816
7817 #[test]
7818 fn set_key_is_deterministic_and_input_sensitive() {
7819 let mut a = NodeData::create_div();
7820 let mut b = NodeData::create_div();
7821 a.set_key("user-123");
7822 b.set_key("user-123");
7823 assert_eq!(a.get_key(), b.get_key(), "same key input => same hash");
7824 assert!(a.get_key().is_some());
7825
7826 let mut c = NodeData::create_div();
7827 c.set_key("user-124");
7828 assert_ne!(a.get_key(), c.get_key(), "different inputs => different keys");
7829 }
7830
7831 #[test]
7832 fn set_key_hashes_str_and_string_identically() {
7833 let mut a = NodeData::create_div();
7834 let mut b = NodeData::create_div();
7835 a.set_key("x");
7836 b.set_key(String::from("x"));
7837 assert_eq!(
7838 a.get_key(),
7839 b.get_key(),
7840 "&str and String must hash the same (Hash for str)"
7841 );
7842 }
7843
7844 #[test]
7845 fn set_key_accepts_extreme_inputs() {
7846 for nd in [
7847 NodeData::create_div().with_key(""),
7848 NodeData::create_div().with_key(u64::MAX),
7849 NodeData::create_div().with_key(i64::MIN),
7850 NodeData::create_div().with_key(huge_unicode_string()),
7851 ] {
7852 assert!(nd.get_key().is_some());
7853 }
7854 }
7855
7856 #[test]
7857 fn set_key_overwrites_rather_than_accumulating() {
7858 let mut nd = NodeData::create_div();
7859 nd.set_key("a");
7860 let first = nd.get_key();
7861 nd.set_key("b");
7862 assert_ne!(nd.get_key(), first, "the last set_key wins");
7863 }
7864
7865 #[test]
7866 fn merge_callback_round_trips_the_function_pointer() {
7867 let mut nd = NodeData::create_div();
7868 assert!(nd.get_merge_callback().is_none());
7869
7870 nd.set_merge_callback(merge_cb_a as DatasetMergeCallbackType);
7871 let cb = nd.get_merge_callback().expect("merge callback was set");
7872 assert_eq!(cb.cb as usize, merge_cb_a as usize);
7873 assert_eq!(cb.callable, OptionRefAny::None);
7874
7875 nd.set_merge_callback(merge_cb_b as DatasetMergeCallbackType);
7877 let cb = nd.get_merge_callback().expect("merge callback was replaced");
7878 assert_eq!(cb.cb as usize, merge_cb_b as usize);
7879 }
7880
7881 #[test]
7882 fn dataset_merge_callback_from_ptr_matches_the_from_impl() {
7883 let via_ptr = DatasetMergeCallback::from_ptr(merge_cb_a);
7884 let via_from = DatasetMergeCallback::from(merge_cb_a as DatasetMergeCallbackType);
7885 assert_eq!(via_ptr, via_from);
7886 assert_eq!(via_ptr.cb as usize, merge_cb_a as usize);
7887 assert_eq!(via_ptr.callable, OptionRefAny::None);
7888
7889 assert_ne!(via_ptr, DatasetMergeCallback::from_ptr(merge_cb_b));
7891 }
7892
7893 #[test]
7894 fn dataset_merge_callback_debug_is_non_empty_and_names_the_type() {
7895 let cb = DatasetMergeCallback::from_ptr(merge_cb_a);
7896 let s = format!("{cb:?}");
7897 assert!(s.contains("DatasetMergeCallback"));
7898 assert!(s.contains("cb"));
7899 }
7900
7901 #[test]
7902 fn merge_callback_is_actually_callable_through_the_stored_pointer() {
7903 let cb = DatasetMergeCallback::from_ptr(merge_cb_b);
7904 let mut out = (cb.cb)(RefAny::new(1u32), RefAny::new(2u32));
7905 assert_eq!(
7906 out.downcast_ref::<u32>().map(|r| *r),
7907 Some(2),
7908 "merge_cb_b returns the OLD data"
7909 );
7910 }
7911
7912 #[test]
7913 fn component_origin_round_trips_and_defaults_to_none() {
7914 let mut nd = NodeData::create_div();
7915 assert!(nd.get_component_origin().is_none());
7916
7917 nd.set_component_origin(ComponentOrigin {
7918 component_id: "shadcn:card".into(),
7919 data_model_json: crate::json::Json::null(),
7920 });
7921 let origin = nd.get_component_origin().expect("origin was set");
7922 assert_eq!(origin.component_id.as_str(), "shadcn:card");
7923
7924 let d = ComponentOrigin::default();
7926 assert_eq!(d.component_id.as_str(), "");
7927 assert_eq!(hash_of(&d), hash_of(&ComponentOrigin::default()));
7928 }
7929
7930 #[test]
7935 fn get_image_clip_mask_returns_none_for_non_mask_svg_data() {
7936 let mut nd = NodeData::create_div();
7937 assert!(nd.get_image_clip_mask().is_none());
7938
7939 nd.set_svg_data(SvgNodeData::Circle {
7940 cx: 1.0,
7941 cy: 2.0,
7942 r: 3.0,
7943 });
7944 assert!(nd.get_svg_data().is_some());
7945 assert!(
7946 nd.get_image_clip_mask().is_none(),
7947 "a Circle is not an ImageClipMask"
7948 );
7949 }
7950
7951 #[test]
7952 fn set_clip_mask_is_readable_through_get_image_clip_mask() {
7953 let mask = ImageMask {
7954 image: ImageRef::null_image(2, 2, crate::resources::RawImageFormat::R8, Vec::new()),
7955 rect: crate::geom::LogicalRect::new(
7956 LogicalPosition { x: 0.0, y: 0.0 },
7957 crate::geom::LogicalSize {
7958 width: 2.0,
7959 height: 2.0,
7960 },
7961 ),
7962 repeat: false,
7963 };
7964 let mut nd = NodeData::create_div();
7965 nd.set_clip_mask(mask.clone());
7966 assert_eq!(nd.get_image_clip_mask(), Some(&mask));
7967 assert!(matches!(
7969 nd.get_svg_data(),
7970 Some(SvgNodeData::ImageClipMask(_))
7971 ));
7972 }
7973
7974 #[test]
7975 fn svg_node_data_with_nan_coords_is_self_equal_and_hash_consistent() {
7976 let a = SvgNodeData::Rect {
7980 x: f32::NAN,
7981 y: f32::INFINITY,
7982 width: f32::NEG_INFINITY,
7983 height: -0.0,
7984 rx: 0.0,
7985 ry: f32::MAX,
7986 };
7987 let b = a.clone();
7988 assert_eq!(a, b);
7989 assert_eq!(hash_of(&a), hash_of(&b));
7990 assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
7991 }
7992
7993 #[test]
7994 fn svg_node_data_line_and_linear_gradient_are_distinct_despite_a_shared_hash_body() {
7995 let line = SvgNodeData::Line {
7998 x1: 1.0,
7999 y1: 2.0,
8000 x2: 3.0,
8001 y2: 4.0,
8002 };
8003 let grad = SvgNodeData::LinearGradient {
8004 x1: 1.0,
8005 y1: 2.0,
8006 x2: 3.0,
8007 y2: 4.0,
8008 };
8009 assert_ne!(line, grad, "same field values, different variants");
8010 }
8011
8012 #[test]
8017 fn calculate_node_data_hash_is_deterministic_and_equal_for_equal_nodes() {
8018 let a = NodeData::create_div().with_key("k").with_contenteditable(true);
8019 let b = a.clone();
8020 assert_eq!(a, b);
8021 assert_eq!(a.calculate_node_data_hash(), b.calculate_node_data_hash());
8022 assert_eq!(
8023 a.calculate_node_data_hash(),
8024 a.calculate_node_data_hash(),
8025 "hashing must not depend on call count"
8026 );
8027 }
8028
8029 #[test]
8030 fn structural_hash_ignores_text_content_but_data_hash_does_not() {
8031 let a = NodeData::create_text("Hello");
8034 let b = NodeData::create_text("Hello World");
8035
8036 assert_eq!(
8037 a.calculate_structural_hash(),
8038 b.calculate_structural_hash(),
8039 "structural hash must ignore text content"
8040 );
8041 assert_ne!(
8042 a.calculate_node_data_hash(),
8043 b.calculate_node_data_hash(),
8044 "the full data hash must NOT ignore text content"
8045 );
8046 }
8047
8048 #[test]
8049 fn structural_hash_ignores_contenteditable_but_data_hash_does_not() {
8050 let plain = NodeData::create_div();
8051 let editable = NodeData::create_div().with_contenteditable(true);
8052
8053 assert_eq!(
8054 plain.calculate_structural_hash(),
8055 editable.calculate_structural_hash(),
8056 "contenteditable flips with focus; it must not move the structural hash"
8057 );
8058 assert_ne!(
8059 plain.calculate_node_data_hash(),
8060 editable.calculate_node_data_hash(),
8061 "flags ARE part of the full data hash"
8062 );
8063 }
8064
8065 #[test]
8066 fn structural_hash_is_sensitive_to_ids_classes_and_node_type() {
8067 let mut a = NodeData::create_div();
8068 a.add_id("a".into());
8069 let mut b = NodeData::create_div();
8070 b.add_id("b".into());
8071 assert_ne!(a.calculate_structural_hash(), b.calculate_structural_hash());
8072
8073 let mut c = NodeData::create_div();
8074 c.add_class("a".into());
8075 assert_ne!(
8076 a.calculate_structural_hash(),
8077 c.calculate_structural_hash(),
8078 "id=\"a\" and class=\"a\" must not collide"
8079 );
8080
8081 assert_ne!(
8082 NodeData::create_div().calculate_structural_hash(),
8083 NodeData::create_br().calculate_structural_hash()
8084 );
8085 }
8086
8087 #[test]
8088 fn node_data_eq_implies_equal_hash_for_a_richly_populated_node() {
8089 let mut a = NodeData::create_div();
8090 a.add_id("id".into());
8091 a.add_class("cls".into());
8092 a.set_tab_index(TabIndex::OverrideInParent(9));
8093 a.set_contenteditable(true);
8094 a.set_anonymous(true);
8095 a.set_key("key");
8096 a.set_dataset(OptionRefAny::Some(RefAny::new(7u64)));
8097 a.set_svg_data(SvgNodeData::GradientStop { offset: 0.5 });
8098 a.set_context_menu(Menu::create(Vec::new().into()));
8099 a.set_merge_callback(merge_cb_a as DatasetMergeCallbackType);
8100 a.set_css("color: red;");
8101
8102 let b = a.clone();
8103 assert_eq!(a, b, "clone must be value-equal");
8104 assert_eq!(
8105 hash_of(&a),
8106 hash_of(&b),
8107 "Eq == true but hashes differ: Hash/Eq contract violated"
8108 );
8109 assert_eq!(a.calculate_node_data_hash(), b.calculate_node_data_hash());
8110 assert_eq!(a.copy_special(), b);
8112 }
8113
8114 #[test]
8119 fn node_data_to_string_is_empty_for_a_bare_node() {
8120 assert_eq!(node_data_to_string(&NodeData::create_div()), "");
8122 }
8123
8124 #[test]
8125 fn node_data_to_string_emits_ids_classes_and_tabindex() {
8126 let mut nd = NodeData::create_div();
8127 nd.add_id("i1".into());
8128 nd.add_id("i2".into());
8129 nd.add_class("c1".into());
8130 nd.set_tab_index(TabIndex::NoKeyboardFocus);
8131
8132 let s = node_data_to_string(&nd);
8133 assert!(s.contains(r#"id="i1 i2""#), "ids are space-joined: {s}");
8134 assert!(s.contains(r#"class="c1""#), "{s}");
8135 assert!(s.contains(r#"tabindex="-1""#), "{s}");
8136 }
8137
8138 #[test]
8139 fn node_data_display_is_self_closing_without_content() {
8140 let s = format!("{}", NodeData::create_div());
8141 assert!(s.starts_with('<'), "{s}");
8142 assert!(s.ends_with("/>"), "content-less nodes self-close: {s}");
8143 }
8144
8145 #[test]
8146 fn node_data_display_wraps_text_content_in_a_tag_pair() {
8147 let s = format!("{}", NodeData::create_text("hello"));
8148 assert!(s.starts_with('<'));
8149 assert!(s.ends_with('>'));
8150 assert!(s.contains("hello"), "{s}");
8151 assert!(!s.ends_with("/>"), "a node with content must not self-close");
8152 }
8153
8154 #[test]
8155 fn node_data_display_does_not_panic_on_hostile_text() {
8156 for text in [
8160 "",
8161 "<script>alert(1)</script>",
8162 "\" onload=\"x",
8163 "日本語 🎉",
8164 "line\nbreak\ttab",
8165 ] {
8166 let s = format!("{}", NodeData::create_text(text));
8167 assert!(s.contains(text), "Display dropped content for {text:?}");
8168 }
8169 }
8170
8171 #[test]
8172 fn node_data_display_survives_a_huge_text_payload() {
8173 let big = huge_unicode_string();
8174 let s = format!("{}", NodeData::create_text(big.clone()));
8175 assert!(s.len() > big.len());
8176 }
8177
8178 #[test]
8179 fn debug_print_end_matches_the_node_tag() {
8180 let s = NodeData::create_div().debug_print_end();
8181 assert!(s.starts_with("</"));
8182 assert!(s.ends_with('>'));
8183 }
8184
8185 #[test]
8190 fn set_node_type_replaces_the_type_and_keeps_the_attributes() {
8191 let mut nd = NodeData::create_div();
8192 nd.add_id("keep".into());
8193 nd.set_node_type(NodeType::Span);
8194 assert!(nd.is_node_type(NodeType::Span));
8195 assert!(nd.has_id("keep"), "changing the tag must not drop attributes");
8196 }
8197
8198 #[test]
8199 fn add_callback_appends_and_get_callbacks_reflects_it() {
8200 let mut nd = NodeData::create_div();
8201 assert!(nd.get_callbacks().as_ref().is_empty());
8202
8203 nd.add_callback(
8204 EventFilter::Hover(HoverEventFilter::MouseUp),
8205 RefAny::new(1u32),
8206 0usize,
8207 );
8208 nd.add_callback(
8209 EventFilter::Focus(FocusEventFilter::MouseDown),
8210 RefAny::new(2u32),
8211 1usize,
8212 );
8213 assert_eq!(nd.get_callbacks().as_ref().len(), 2);
8214 assert_eq!(
8215 nd.get_callbacks().as_ref()[0].event,
8216 EventFilter::Hover(HoverEventFilter::MouseUp)
8217 );
8218 }
8219
8220 #[test]
8221 fn add_css_property_appends_an_inline_rule() {
8222 use azul_css::props::property::{CssProperty, CssPropertyType};
8223
8224 let mut nd = NodeData::create_div();
8225 assert!(nd.get_style().rules.as_ref().is_empty());
8226
8227 nd.add_css_property(CssPropertyWithConditions {
8228 property: CssProperty::const_none(CssPropertyType::Display),
8229 apply_if: Vec::new().into(),
8230 });
8231 assert_eq!(nd.get_style().rules.as_ref().len(), 1);
8232
8233 nd.add_css_property(CssPropertyWithConditions {
8234 property: CssProperty::const_none(CssPropertyType::Display),
8235 apply_if: Vec::new().into(),
8236 });
8237 assert_eq!(
8238 nd.get_style().rules.as_ref().len(),
8239 2,
8240 "add_css_property appends, it does not replace"
8241 );
8242 }
8243
8244 #[test]
8245 fn set_style_replaces_whereas_set_css_appends() {
8246 let mut nd = NodeData::create_div();
8247 nd.set_css("color: red;");
8248 let after_first = nd.get_style().rules.as_ref().len();
8249 assert!(after_first > 0);
8250
8251 nd.set_css("color: blue;");
8252 assert!(
8253 nd.get_style().rules.as_ref().len() > after_first,
8254 "set_css appends to the existing inline style"
8255 );
8256
8257 nd.set_style(azul_css::css::Css {
8258 rules: Vec::new().into(),
8259 });
8260 assert!(
8261 nd.get_style().rules.as_ref().is_empty(),
8262 "set_style replaces wholesale"
8263 );
8264 }
8265
8266 #[test]
8267 fn set_css_with_empty_and_malformed_input_does_not_panic() {
8268 for style in [
8269 "",
8270 " ",
8271 ";;;;",
8272 "color",
8273 "color:",
8274 ":",
8275 "}",
8276 "{",
8277 "color: ;",
8278 "not-a-property: not-a-value;",
8279 ":hover {",
8280 "@os {",
8281 "color: red", "\u{0}color: red;", "color: 日本語;",
8284 ] {
8285 let nd = NodeData::create_div().with_css(style);
8286 let _ = nd.get_style().rules.as_ref().len();
8289 }
8290 }
8291
8292 #[test]
8293 fn swap_with_default_returns_the_original_and_leaves_a_div() {
8294 let mut nd = NodeData::create_text("payload");
8295 let taken = nd.swap_with_default();
8296 assert!(taken.is_text_node());
8297 assert!(nd.is_node_type(NodeType::Div), "the slot becomes a fresh div");
8298 assert!(nd.attributes().as_ref().is_empty());
8299 }
8300
8301 #[test]
8302 fn node_data_builders_are_equivalent_to_their_setters() {
8303 let built = NodeData::create_div()
8304 .with_tab_index(TabIndex::Auto)
8305 .with_contenteditable(true)
8306 .with_node_type(NodeType::Span);
8307
8308 let mut set = NodeData::create_div();
8309 set.set_tab_index(TabIndex::Auto);
8310 set.set_contenteditable(true);
8311 set.set_node_type(NodeType::Span);
8312
8313 assert_eq!(built, set);
8314 }
8315
8316 #[test]
8321 fn node_data_vec_as_container_is_empty_for_an_empty_vec() {
8322 let v: NodeDataVec = Vec::new().into();
8323 assert_eq!(v.as_container().internal.len(), 0);
8324 }
8325
8326 #[test]
8327 fn node_data_vec_containers_expose_and_mutate_the_backing_slice() {
8328 let mut v: NodeDataVec = vec![
8329 NodeData::create_div(),
8330 NodeData::create_br(),
8331 NodeData::create_text("t"),
8332 ]
8333 .into();
8334 assert_eq!(v.as_container().internal.len(), 3);
8335 assert!(v.as_container().internal[2].is_text_node());
8336
8337 v.as_container_mut().internal[0].set_node_type(NodeType::Span);
8338 assert!(v.as_container().internal[0].is_node_type(NodeType::Span));
8339 }
8340
8341 #[test]
8346 fn dom_default_is_an_empty_body() {
8347 let d = Dom::default();
8348 assert!(d.root.is_node_type(NodeType::Body));
8349 assert_eq!(d.estimated_total_children, 0);
8350 assert_eq!(d.node_count(), 1);
8351 }
8352
8353 #[test]
8354 fn dom_set_children_recomputes_the_estimate_from_scratch() {
8355 let child = Dom::create_div().with_child(Dom::create_div());
8356 let mut parent = Dom::create_div();
8357 parent.add_child(Dom::create_div());
8358 assert_eq!(parent.estimated_total_children, 1);
8359
8360 parent.set_children(vec![child].into());
8362 assert_eq!(parent.estimated_total_children, 2);
8363 assert_eq!(
8364 parent.estimated_total_children,
8365 parent.recompute_estimated_total_children()
8366 );
8367 }
8368
8369 #[test]
8370 fn dom_set_children_with_an_empty_vec_zeroes_the_estimate() {
8371 let mut d = Dom::create_div().with_child(Dom::create_div().with_child(Dom::create_div()));
8372 assert_eq!(d.estimated_total_children, 2);
8373 d.set_children(Vec::new().into());
8374 assert_eq!(d.estimated_total_children, 0);
8375 assert_eq!(d.node_count(), 1);
8376 }
8377
8378 #[test]
8379 fn dom_deeply_nested_chain_keeps_an_exact_estimate() {
8380 const DEPTH: usize = 256;
8382 let mut d = Dom::create_div();
8383 for _ in 0..DEPTH {
8384 d = Dom::create_div().with_child(d);
8385 }
8386 assert_eq!(d.estimated_total_children, DEPTH);
8387 assert_eq!(d.node_count(), DEPTH + 1);
8388 assert_eq!(d.recompute_estimated_total_children(), DEPTH);
8389 }
8390
8391 #[test]
8392 fn dom_very_wide_child_list_keeps_an_exact_estimate() {
8393 const WIDTH: usize = 5_000;
8394 let children: Vec<Dom> = (0..WIDTH).map(|_| Dom::create_div()).collect();
8395 let d = Dom::create_div().with_children(children.into());
8396 assert_eq!(d.estimated_total_children, WIDTH);
8397 assert_eq!(d.node_count(), WIDTH + 1);
8398 }
8399
8400 #[test]
8401 fn dom_from_iterator_counts_nested_grandchildren() {
8402 let empty: Dom = Vec::new().into_iter().collect();
8403 assert_eq!(empty.estimated_total_children, 0);
8404 assert!(empty.root.is_node_type(NodeType::Div));
8405
8406 let d: Dom = vec![
8408 Dom::create_div().with_child(Dom::create_div()),
8409 Dom::create_div(),
8410 ]
8411 .into_iter()
8412 .collect();
8413 assert_eq!(d.estimated_total_children, 3);
8414 assert_eq!(d.estimated_total_children, d.recompute_estimated_total_children());
8415 assert_eq!(d.node_count(), 4);
8416 }
8417
8418 #[test]
8419 fn dom_fixup_repairs_a_corrupted_estimate_at_every_depth() {
8420 let mut d = Dom::create_div()
8421 .with_child(Dom::create_div().with_child(Dom::create_div()))
8422 .with_child(Dom::create_div());
8423
8424 d.estimated_total_children = 0;
8427 d.children.as_mut()[0].estimated_total_children = 99;
8428
8429 let repaired = d.fixup_children_estimated();
8430 assert_eq!(repaired, 3);
8431 assert_eq!(d.children.as_ref()[0].estimated_total_children, 1);
8432 assert_eq!(
8433 d.estimated_total_children,
8434 d.recompute_estimated_total_children()
8435 );
8436 }
8437
8438 #[test]
8439 fn dom_fixup_on_a_leaf_zeroes_a_bogus_estimate() {
8440 let mut d = Dom::create_div();
8441 d.estimated_total_children = usize::MAX;
8442 assert_eq!(d.fixup_children_estimated(), 0);
8443 assert_eq!(d.node_count(), 1, "node_count is safe again after fixup");
8444 }
8445
8446 #[test]
8453 fn dom_node_count_saturates_on_a_corrupted_max_estimate() {
8454 let mut d = Dom::create_div();
8455 d.estimated_total_children = usize::MAX;
8456 assert_eq!(d.node_count(), usize::MAX);
8457 assert_ne!(
8458 d.node_count(),
8459 0,
8460 "wrapping to 0 would claim an empty DOM — the one answer callers \
8461 act on without checking"
8462 );
8463 }
8464
8465 #[test]
8466 fn dom_swap_with_default_returns_the_original_tree() {
8467 let mut d = Dom::create_div().with_child(Dom::create_div());
8468 let taken = d.swap_with_default();
8469 assert_eq!(taken.estimated_total_children, 1);
8470 assert_eq!(d.estimated_total_children, 0, "the slot is reset");
8471 assert!(d.root.is_node_type(NodeType::Div));
8472 }
8473
8474 #[test]
8479 fn dom_with_id_and_with_class_apply_to_the_root() {
8480 let d = Dom::create_div()
8481 .with_id("root".into())
8482 .with_class("card".into());
8483 assert!(d.root.has_id("root"));
8484 assert!(d.root.has_class("card"));
8485 }
8486
8487 #[test]
8488 fn dom_with_attribute_appends_and_with_attributes_replaces() {
8489 let d = Dom::create_div()
8490 .with_attribute(AttributeType::Href("/a".into()))
8491 .with_attribute(AttributeType::Alt("alt".into()));
8492 assert_eq!(d.root.attributes().as_ref().len(), 2);
8493
8494 let d = d.with_attributes(vec![AttributeType::Disabled].into());
8495 assert_eq!(
8496 d.root.attributes().as_ref().len(),
8497 1,
8498 "with_attributes replaces wholesale"
8499 );
8500 assert_eq!(d.root.attributes().as_ref()[0], AttributeType::Disabled);
8501 }
8502
8503 #[test]
8504 fn dom_add_component_css_stacks_stylesheets() {
8505 let mut d = Dom::create_div();
8506 assert!(d.css.as_ref().is_empty());
8507 d.set_css("color: red;");
8508 d.set_css("color: blue;");
8509 assert_eq!(d.css.as_ref().len(), 2, "each set_css pushes a stylesheet");
8510
8511 d.set_component_css(Vec::new().into());
8512 assert!(d.css.as_ref().is_empty(), "set_component_css replaces");
8513 }
8514
8515 #[test]
8516 fn dom_with_css_does_not_panic_on_malformed_input() {
8517 for style in ["", "}}}", "@os {", "color:", "\u{0}"] {
8518 let d = Dom::create_div().with_css(style);
8519 assert_eq!(d.css.as_ref().len(), 1, "a Css is pushed even if it parses empty");
8520 }
8521 }
8522
8523 #[test]
8524 fn dom_text_helpers_produce_a_text_child() {
8525 let d = Dom::create_h1_with_text("Title");
8526 assert!(d.root.is_node_type(NodeType::H1));
8527 assert_eq!(d.estimated_total_children, 1);
8528 assert!(d.children.as_ref()[0].root.is_text_node());
8529 }
8530
8531 #[test]
8532 fn dom_create_geolocation_probe_carries_its_config() {
8533 let cfg = crate::geolocation::GeolocationProbeConfig {
8534 high_accuracy: true,
8535 background: false,
8536 max_accuracy_m: 25.0,
8537 min_interval_ms: 1_000,
8538 };
8539 let d = Dom::create_geolocation_probe(cfg);
8540 match d.root.get_node_type() {
8541 NodeType::GeolocationProbe(c) => {
8542 assert!(c.high_accuracy);
8543 assert_eq!(c.min_interval_ms, 1_000);
8544 }
8545 other => panic!("expected GeolocationProbe, got {other:?}"),
8546 }
8547 }
8548
8549 #[test]
8550 fn dom_clone_and_eq_agree_on_a_nested_tree() {
8551 let d = Dom::create_div()
8552 .with_id("r".into())
8553 .with_child(Dom::create_text("a"))
8554 .with_child(Dom::create_div().with_child(Dom::create_text("b")));
8555 let c = d.clone();
8556 assert_eq!(d, c);
8557 assert_eq!(hash_of(&d), hash_of(&c));
8558 assert_eq!(c.estimated_total_children, 3);
8560 assert_eq!(c.node_count(), 4);
8561 }
8562
8563 #[test]
8564 fn dom_debug_does_not_panic_on_a_nested_tree() {
8565 let d = Dom::create_div()
8566 .with_child(Dom::create_text("日本語 🎉"))
8567 .with_child(Dom::create_div().with_child(Dom::create_br()));
8568 let s = format!("{d:?}");
8569 assert!(s.contains("Dom"));
8570 assert!(s.contains("estimated_total_children"));
8571 }
8572
8573 #[test]
8578 fn dom_id_root_is_zero_and_is_the_default() {
8579 assert_eq!(DomId::ROOT_ID.inner, 0);
8580 assert_eq!(DomId::default(), DomId::ROOT_ID);
8581 assert_eq!(format!("{}", DomId::ROOT_ID), "0");
8582 assert_eq!(format!("{}", DomId { inner: usize::MAX }), usize::MAX.to_string());
8583 }
8584
8585 #[test]
8586 fn dom_node_id_root_points_at_the_root_dom_and_no_node() {
8587 assert_eq!(DomNodeId::ROOT.dom, DomId::ROOT_ID);
8588 assert_eq!(DomNodeId::ROOT.node, NodeHierarchyItemId::NONE);
8589 }
8590}