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