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, PartialEq, 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 Eq for SvgNodeData {}
1700
1701#[allow(clippy::derive_ord_xor_partial_ord)]
1705impl Ord for SvgNodeData {
1706 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
1707 self.partial_cmp(other).unwrap_or(core::cmp::Ordering::Equal)
1708 }
1709}
1710
1711impl Hash for SvgNodeData {
1712 fn hash<H: Hasher>(&self, state: &mut H) {
1713 mem::discriminant(self).hash(state);
1714 match self {
1715 Self::ImageClipMask(m) => m.hash(state),
1716 Self::Path(mp) => {
1717 for ring in mp.rings.as_ref() {
1718 for item in ring.items.as_ref() {
1719 match item {
1720 crate::svg::SvgPathElement::Line(l) => {
1721 0u8.hash(state);
1722 l.start.x.to_bits().hash(state);
1723 l.start.y.to_bits().hash(state);
1724 l.end.x.to_bits().hash(state);
1725 l.end.y.to_bits().hash(state);
1726 }
1727 crate::svg::SvgPathElement::QuadraticCurve(q) => {
1728 1u8.hash(state);
1729 q.start.x.to_bits().hash(state);
1730 q.start.y.to_bits().hash(state);
1731 q.ctrl.x.to_bits().hash(state);
1732 q.ctrl.y.to_bits().hash(state);
1733 q.end.x.to_bits().hash(state);
1734 q.end.y.to_bits().hash(state);
1735 }
1736 crate::svg::SvgPathElement::CubicCurve(c) => {
1737 2u8.hash(state);
1738 c.start.x.to_bits().hash(state);
1739 c.start.y.to_bits().hash(state);
1740 c.ctrl_1.x.to_bits().hash(state);
1741 c.ctrl_1.y.to_bits().hash(state);
1742 c.ctrl_2.x.to_bits().hash(state);
1743 c.ctrl_2.y.to_bits().hash(state);
1744 c.end.x.to_bits().hash(state);
1745 c.end.y.to_bits().hash(state);
1746 }
1747 }
1748 }
1749 }
1750 }
1751 Self::Circle { cx, cy, r } => {
1752 cx.to_bits().hash(state); cy.to_bits().hash(state); r.to_bits().hash(state);
1753 }
1754 Self::Rect { x, y, width, height, rx, ry } => {
1755 x.to_bits().hash(state); y.to_bits().hash(state);
1756 width.to_bits().hash(state); height.to_bits().hash(state);
1757 rx.to_bits().hash(state); ry.to_bits().hash(state);
1758 }
1759 Self::Ellipse { cx, cy, rx, ry } => {
1760 cx.to_bits().hash(state); cy.to_bits().hash(state);
1761 rx.to_bits().hash(state); ry.to_bits().hash(state);
1762 }
1763 Self::Line { x1, y1, x2, y2 } | Self::LinearGradient { x1, y1, x2, y2 } => {
1766 x1.to_bits().hash(state); y1.to_bits().hash(state);
1767 x2.to_bits().hash(state); y2.to_bits().hash(state);
1768 }
1769 Self::PointsList { points, closed } => {
1770 for p in points {
1771 p.x.to_bits().hash(state); p.y.to_bits().hash(state);
1772 }
1773 closed.hash(state);
1774 }
1775 Self::ViewBox { min_x, min_y, width, height } => {
1776 min_x.to_bits().hash(state); min_y.to_bits().hash(state);
1777 width.to_bits().hash(state); height.to_bits().hash(state);
1778 }
1779 Self::RadialGradient { cx, cy, r, fx, fy } => {
1780 cx.to_bits().hash(state); cy.to_bits().hash(state);
1781 r.to_bits().hash(state); fx.to_bits().hash(state);
1782 fy.to_bits().hash(state);
1783 }
1784 Self::GradientStop { offset } => {
1785 offset.to_bits().hash(state);
1786 }
1787 Self::Use { href, x, y } => {
1788 href.hash(state);
1789 x.to_bits().hash(state); y.to_bits().hash(state);
1790 }
1791 Self::SvgImageData { href, x, y, width, height } => {
1792 href.hash(state);
1793 x.to_bits().hash(state); y.to_bits().hash(state);
1794 width.to_bits().hash(state); height.to_bits().hash(state);
1795 }
1796 }
1797 }
1798}
1799
1800#[repr(C)]
1804#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1805pub struct NodeDataExt {
1806 pub attributes: AttributeTypeVec,
1810 pub virtual_view: Option<VirtualViewNode>,
1812 pub dataset: Option<RefAny>,
1814 pub svg_data: Option<SvgNodeData>,
1816 pub menu_bar: Option<Box<Menu>>,
1818 pub context_menu: Option<Box<Menu>>,
1820 pub key: Option<u64>,
1824 pub dataset_merge_callback: Option<DatasetMergeCallback>,
1827 pub component_origin: Option<ComponentOrigin>,
1833}
1834
1835#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1859#[repr(C)]
1860pub struct DatasetMergeCallback {
1861 pub cb: DatasetMergeCallbackType,
1864 pub callable: OptionRefAny,
1867}
1868
1869impl fmt::Debug for DatasetMergeCallback {
1870 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1871 f.debug_struct("DatasetMergeCallback")
1872 .field("cb", &(self.cb as usize))
1873 .field("callable", &self.callable)
1874 .finish()
1875 }
1876}
1877
1878impl From<DatasetMergeCallbackType> for DatasetMergeCallback {
1881 fn from(cb: DatasetMergeCallbackType) -> Self {
1882 Self {
1883 cb,
1884 callable: OptionRefAny::None,
1885 }
1886 }
1887}
1888
1889impl DatasetMergeCallback {
1890 #[must_use]
1894 pub fn from_ptr(cb: DatasetMergeCallbackType) -> Self {
1895 Self::from(cb)
1896 }
1897}
1898
1899impl_option!(
1900 DatasetMergeCallback,
1901 OptionDatasetMergeCallback,
1902 copy = false,
1903 [Debug, Clone]
1904);
1905
1906pub type DatasetMergeCallbackType = extern "C" fn(RefAny, RefAny) -> RefAny;
1915
1916impl Clone for NodeData {
1917 #[inline]
1918 fn clone(&self) -> Self {
1919 Self {
1920 node_type: self.node_type.to_library_owned_nodetype(),
1921 style: self.style.clone(),
1922 callbacks: self.callbacks.clone(),
1923 flags: self.flags,
1924 accessibility: self.accessibility.clone(),
1925 extra: self.extra.clone(),
1926 }
1927 }
1928}
1929
1930impl_vec!(NodeData, NodeDataVec, NodeDataVecDestructor, NodeDataVecDestructorType, NodeDataVecSlice, OptionNodeData);
1932impl_vec_clone!(NodeData, NodeDataVec, NodeDataVecDestructor);
1933impl_vec_mut!(NodeData, NodeDataVec);
1934impl_vec_debug!(NodeData, NodeDataVec);
1935impl_vec_partialord!(NodeData, NodeDataVec);
1936impl_vec_ord!(NodeData, NodeDataVec);
1937impl_vec_partialeq!(NodeData, NodeDataVec);
1938impl_vec_eq!(NodeData, NodeDataVec);
1939impl_vec_hash!(NodeData, NodeDataVec);
1940
1941impl NodeDataVec {
1942 #[inline]
1943 #[must_use] pub fn as_container(&self) -> NodeDataContainerRef<'_, NodeData> {
1944 NodeDataContainerRef {
1945 internal: self.as_ref(),
1946 }
1947 }
1948 #[inline]
1949 pub fn as_container_mut(&mut self) -> NodeDataContainerRefMut<'_, NodeData> {
1950 NodeDataContainerRefMut {
1951 internal: self.as_mut(),
1952 }
1953 }
1954}
1955
1956unsafe impl Send for NodeData {}
1960
1961#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
1964#[repr(C, u8)]
1965#[derive(Default)]
1966pub enum TabIndex {
1967 #[default]
1973 Auto,
1974 OverrideInParent(u32),
1980 NoKeyboardFocus,
1983}
1984
1985impl_option!(
1986 TabIndex,
1987 OptionTabIndex,
1988 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1989);
1990
1991impl TabIndex {
1992 #[allow(clippy::cast_possible_wrap)]
1996 #[must_use] pub const fn get_index(&self) -> isize {
1997 use self::TabIndex::{Auto, OverrideInParent, NoKeyboardFocus};
1998 match self {
1999 Auto => 0,
2000 OverrideInParent(x) => *x as isize,
2001 NoKeyboardFocus => -1,
2002 }
2003 }
2004}
2005
2006
2007#[repr(C)]
2019#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2020#[derive(Default)]
2021pub struct NodeFlags {
2022 pub inner: u32,
2023}
2024
2025
2026impl NodeFlags {
2027 const CONTENTEDITABLE_BIT: u32 = 1 << 31;
2028 const TAB_INDEX_MASK: u32 = 0b11 << 29;
2029 const ANONYMOUS_BIT: u32 = 1 << 28;
2030 const TAB_VALUE_MASK: u32 = (1 << 28) - 1;
2031
2032 const TAB_NONE: u32 = 0b00 << 29;
2033 const TAB_AUTO: u32 = 0b01 << 29;
2034 const TAB_OVERRIDE: u32 = 0b10 << 29;
2035 const TAB_NO_KEYBOARD: u32 = 0b11 << 29;
2036
2037 #[must_use] pub const fn new() -> Self {
2038 Self { inner: 0 }
2039 }
2040
2041 #[must_use] pub const fn is_contenteditable(&self) -> bool {
2042 (self.inner & Self::CONTENTEDITABLE_BIT) != 0
2043 }
2044
2045 #[must_use] pub const fn set_contenteditable(mut self, v: bool) -> Self {
2046 if v {
2047 self.inner |= Self::CONTENTEDITABLE_BIT;
2048 } else {
2049 self.inner &= !Self::CONTENTEDITABLE_BIT;
2050 }
2051 self
2052 }
2053
2054 pub const fn set_contenteditable_mut(&mut self, v: bool) {
2055 if v {
2056 self.inner |= Self::CONTENTEDITABLE_BIT;
2057 } else {
2058 self.inner &= !Self::CONTENTEDITABLE_BIT;
2059 }
2060 }
2061
2062 #[must_use] pub const fn get_tab_index(&self) -> Option<TabIndex> {
2063 match self.inner & Self::TAB_INDEX_MASK {
2064 x if x == Self::TAB_NONE => None,
2065 x if x == Self::TAB_AUTO => Some(TabIndex::Auto),
2066 x if x == Self::TAB_OVERRIDE => {
2067 let val = self.inner & Self::TAB_VALUE_MASK;
2068 Some(TabIndex::OverrideInParent(val))
2069 }
2070 x if x == Self::TAB_NO_KEYBOARD => Some(TabIndex::NoKeyboardFocus),
2071 _ => None,
2072 }
2073 }
2074
2075 #[must_use] pub const fn is_anonymous(&self) -> bool {
2077 (self.inner & Self::ANONYMOUS_BIT) != 0
2078 }
2079
2080 pub const fn set_anonymous(&mut self, v: bool) {
2081 if v {
2082 self.inner |= Self::ANONYMOUS_BIT;
2083 } else {
2084 self.inner &= !Self::ANONYMOUS_BIT;
2085 }
2086 }
2087
2088 pub const fn set_tab_index(&mut self, tab_index: Option<TabIndex>) {
2089 self.inner &= Self::CONTENTEDITABLE_BIT | Self::ANONYMOUS_BIT;
2092 match tab_index {
2093 None => { }
2094 Some(TabIndex::Auto) => {
2095 self.inner |= Self::TAB_AUTO;
2096 }
2097 Some(TabIndex::OverrideInParent(val)) => {
2098 self.inner |= Self::TAB_OVERRIDE | (val & Self::TAB_VALUE_MASK);
2099 }
2100 Some(TabIndex::NoKeyboardFocus) => {
2101 self.inner |= Self::TAB_NO_KEYBOARD;
2102 }
2103 }
2104 }
2105}
2106
2107impl Default for NodeData {
2108 fn default() -> Self {
2109 Self::create_node(NodeType::Div)
2110 }
2111}
2112
2113impl fmt::Display for NodeData {
2114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2115 let html_type = self.node_type.get_path();
2116 let attributes_string = node_data_to_string(self);
2117
2118 match self.node_type.format() {
2119 Some(content) => write!(
2120 f,
2121 "<{html_type}{attributes_string}>{content}</{html_type}>"
2122 ),
2123 None => write!(f, "<{html_type}{attributes_string}/>"),
2124 }
2125 }
2126}
2127
2128fn node_data_to_string(node_data: &NodeData) -> String {
2129 let mut id_string = String::new();
2130 let ids = node_data
2131 .attributes()
2132 .as_ref()
2133 .iter()
2134 .filter_map(|s| s.as_id())
2135 .collect::<Vec<_>>()
2136 .join(" ");
2137
2138 if !ids.is_empty() {
2139 id_string = format!(" id=\"{ids}\" ");
2140 }
2141
2142 let mut class_string = String::new();
2143 let classes = node_data
2144 .attributes()
2145 .as_ref()
2146 .iter()
2147 .filter_map(|s| s.as_class())
2148 .collect::<Vec<_>>()
2149 .join(" ");
2150
2151 if !classes.is_empty() {
2152 class_string = format!(" class=\"{classes}\" ");
2153 }
2154
2155 let mut tabindex_string = String::new();
2156 if let Some(tab_index) = node_data.get_tab_index() {
2157 tabindex_string = format!(" tabindex=\"{}\" ", tab_index.get_index());
2158 }
2159
2160 format!("{id_string}{class_string}{tabindex_string}")
2161}
2162
2163impl NodeData {
2164 #[inline]
2166 #[must_use] pub const fn create_node(node_type: NodeType) -> Self {
2167 Self {
2168 node_type,
2169 callbacks: CoreCallbackDataVec::from_const_slice(&[]),
2170 style: azul_css::css::Css {
2171 rules: azul_css::css::CssRuleBlockVec::from_const_slice(&[]),
2172 },
2173 flags: NodeFlags::new(),
2174 accessibility: None,
2175 extra: None,
2176 }
2177 }
2178
2179 #[inline]
2182 #[must_use] pub fn attributes(&self) -> &AttributeTypeVec {
2183 static EMPTY: AttributeTypeVec = AttributeTypeVec::from_const_slice(&[]);
2184 self.extra.as_ref().map_or(&EMPTY, |ext| &ext.attributes)
2185 }
2186
2187 #[inline]
2190 pub fn attributes_mut(&mut self) -> &mut AttributeTypeVec {
2191 &mut self.extra.get_or_insert_with(|| Box::new(NodeDataExt::default())).attributes
2192 }
2193
2194 #[inline]
2196 pub fn set_attributes(&mut self, attrs: AttributeTypeVec) {
2197 self.extra.get_or_insert_with(|| Box::new(NodeDataExt::default())).attributes = attrs;
2198 }
2199
2200 #[inline]
2202 #[must_use] pub const fn create_body() -> Self {
2203 Self::create_node(NodeType::Body)
2204 }
2205
2206 #[inline]
2208 #[must_use] pub const fn create_div() -> Self {
2209 Self::create_node(NodeType::Div)
2210 }
2211
2212 #[inline]
2214 #[must_use] pub const fn create_br() -> Self {
2215 Self::create_node(NodeType::Br)
2216 }
2217
2218 #[inline]
2220 pub fn create_text<S: Into<AzString>>(value: S) -> Self {
2221 Self::create_node(NodeType::Text(BoxOrStatic::heap(value.into())))
2222 }
2223
2224 #[inline]
2226 #[must_use] pub fn create_image(image: ImageRef) -> Self {
2227 Self::create_node(NodeType::Image(BoxOrStatic::heap(image)))
2228 }
2229
2230 #[inline]
2231 pub fn create_virtual_view(data: RefAny, callback: impl Into<VirtualViewCallback>) -> Self {
2232 let mut nd = Self::create_node(NodeType::VirtualView);
2233 let ext = nd.extra.get_or_insert_with(|| Box::new(NodeDataExt::default()));
2234 ext.virtual_view = Some(VirtualViewNode {
2235 callback: callback.into(),
2236 refany: data,
2237 });
2238 nd
2239 }
2240
2241 fn with_attribute(mut self, attr: AttributeType) -> Self {
2247 let mut v = self.attributes().clone().into_library_owned_vec();
2248 v.push(attr);
2249 self.set_attributes(v.into());
2250 self
2251 }
2252
2253 #[inline]
2255 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_button(aria: SmallAriaInfo) -> Self {
2257 let mut nd = Self::create_node(NodeType::Button);
2258 nd.set_accessibility_info(aria.to_full_info());
2259 nd
2260 }
2261
2262 #[inline]
2264 #[must_use] pub const fn create_button_no_a11y() -> Self {
2265 Self::create_node(NodeType::Button)
2266 }
2267
2268 #[inline]
2270 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_a(href: AzString, aria: SmallAriaInfo) -> Self {
2272 let mut nd = Self::create_node(NodeType::A).with_attribute(AttributeType::Href(href));
2273 nd.set_accessibility_info(aria.to_full_info());
2274 nd
2275 }
2276
2277 #[inline]
2279 #[must_use] pub fn create_a_no_a11y(href: AzString) -> Self {
2280 Self::create_node(NodeType::A).with_attribute(AttributeType::Href(href))
2281 }
2282
2283 #[inline]
2285 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_input(
2287 input_type: AzString,
2288 name: AzString,
2289 label: AzString,
2290 aria: SmallAriaInfo,
2291 ) -> Self {
2292 let mut nd = Self::create_node(NodeType::Input)
2293 .with_attribute(AttributeType::InputType(input_type))
2294 .with_attribute(AttributeType::Name(name))
2295 .with_attribute(AttributeType::AriaLabel(label));
2296 nd.set_accessibility_info(aria.to_full_info());
2297 nd
2298 }
2299
2300 #[inline]
2302 #[must_use] pub fn create_input_no_a11y(input_type: AzString, name: AzString, label: AzString) -> Self {
2303 Self::create_node(NodeType::Input)
2304 .with_attribute(AttributeType::InputType(input_type))
2305 .with_attribute(AttributeType::Name(name))
2306 .with_attribute(AttributeType::AriaLabel(label))
2307 }
2308
2309 #[inline]
2311 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_textarea(name: AzString, label: AzString, aria: SmallAriaInfo) -> Self {
2313 let mut nd = Self::create_node(NodeType::TextArea)
2314 .with_attribute(AttributeType::Name(name))
2315 .with_attribute(AttributeType::AriaLabel(label));
2316 nd.set_accessibility_info(aria.to_full_info());
2317 nd
2318 }
2319
2320 #[inline]
2322 #[must_use] pub fn create_textarea_no_a11y(name: AzString, label: AzString) -> Self {
2323 Self::create_node(NodeType::TextArea)
2324 .with_attribute(AttributeType::Name(name))
2325 .with_attribute(AttributeType::AriaLabel(label))
2326 }
2327
2328 #[inline]
2330 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_select(name: AzString, label: AzString, aria: SmallAriaInfo) -> Self {
2332 let mut nd = Self::create_node(NodeType::Select)
2333 .with_attribute(AttributeType::Name(name))
2334 .with_attribute(AttributeType::AriaLabel(label));
2335 nd.set_accessibility_info(aria.to_full_info());
2336 nd
2337 }
2338
2339 #[inline]
2341 #[must_use] pub fn create_select_no_a11y(name: AzString, label: AzString) -> Self {
2342 Self::create_node(NodeType::Select)
2343 .with_attribute(AttributeType::Name(name))
2344 .with_attribute(AttributeType::AriaLabel(label))
2345 }
2346
2347 #[inline]
2349 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_table(aria: SmallAriaInfo) -> Self {
2351 let mut nd = Self::create_node(NodeType::Table);
2352 nd.set_accessibility_info(aria.to_full_info());
2353 nd
2354 }
2355
2356 #[inline]
2358 #[must_use] pub const fn create_table_no_a11y() -> Self {
2359 Self::create_node(NodeType::Table)
2360 }
2361
2362 #[inline]
2365 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_label(for_id: AzString, aria: SmallAriaInfo) -> Self {
2367 let mut nd = Self::create_node(NodeType::Label).with_attribute(AttributeType::Custom(
2368 AttributeNameValue {
2369 attr_name: "for".into(),
2370 value: for_id,
2371 },
2372 ));
2373 nd.set_accessibility_info(aria.to_full_info());
2374 nd
2375 }
2376
2377 #[inline]
2380 #[must_use] pub fn create_label_no_a11y(for_id: AzString) -> Self {
2381 Self::create_node(NodeType::Label).with_attribute(AttributeType::Custom(AttributeNameValue {
2382 attr_name: "for".into(),
2383 value: for_id,
2384 }))
2385 }
2386
2387 #[inline]
2389 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn is_node_type(&self, searched_type: NodeType) -> bool {
2391 self.node_type == searched_type
2392 }
2393
2394 #[must_use] pub fn has_id(&self, id: &str) -> bool {
2396 self.attributes()
2397 .iter()
2398 .any(|attr| attr.as_id() == Some(id))
2399 }
2400
2401 #[must_use] pub fn has_class(&self, class: &str) -> bool {
2403 self.attributes()
2404 .iter()
2405 .any(|attr| attr.as_class() == Some(class))
2406 }
2407
2408 #[must_use] pub fn has_context_menu(&self) -> bool {
2409 self.extra
2410 .as_ref()
2411 .is_some_and(|m| m.context_menu.is_some())
2412 }
2413
2414 #[must_use] pub const fn is_text_node(&self) -> bool {
2415 matches!(self.node_type, NodeType::Text(_))
2416 }
2417
2418 #[must_use] pub const fn is_virtual_view_node(&self) -> bool {
2419 matches!(self.node_type, NodeType::VirtualView)
2420 }
2421
2422 #[inline]
2426 #[must_use] pub const fn get_node_type(&self) -> &NodeType {
2427 &self.node_type
2428 }
2429 #[inline]
2430 pub fn get_dataset_mut(&mut self) -> Option<&mut RefAny> {
2431 self.extra.as_mut().and_then(|e| e.dataset.as_mut())
2432 }
2433 #[inline]
2434 #[must_use] pub fn get_dataset(&self) -> Option<&RefAny> {
2435 self.extra.as_ref().and_then(|e| e.dataset.as_ref())
2436 }
2437 pub fn take_dataset(&mut self) -> Option<RefAny> {
2439 self.extra.as_mut().and_then(|e| e.dataset.take())
2440 }
2441 #[inline]
2444 #[must_use] pub fn get_ids_and_classes(&self) -> IdOrClassVec {
2445 let v: Vec<IdOrClass> = self.attributes().as_ref().iter().filter_map(|attr| {
2446 match attr {
2447 AttributeType::Id(s) => Some(IdOrClass::Id(s.clone())),
2448 AttributeType::Class(s) => Some(IdOrClass::Class(s.clone())),
2449 _ => None,
2450 }
2451 }).collect();
2452 v.into()
2453 }
2454 #[inline]
2455 #[must_use] pub const fn get_callbacks(&self) -> &CoreCallbackDataVec {
2456 &self.callbacks
2457 }
2458 #[inline]
2459 #[must_use] pub const fn get_style(&self) -> &azul_css::css::Css {
2460 &self.style
2461 }
2462
2463 #[inline]
2464 #[must_use] pub fn get_svg_data(&self) -> Option<&SvgNodeData> {
2465 self.extra.as_ref().and_then(|e| e.svg_data.as_ref())
2466 }
2467
2468 #[inline]
2470 #[must_use] pub fn get_image_clip_mask(&self) -> Option<&ImageMask> {
2471 match self.get_svg_data()? {
2472 SvgNodeData::ImageClipMask(m) => Some(m),
2473 _ => None,
2474 }
2475 }
2476 #[inline]
2477 #[must_use] pub const fn get_tab_index(&self) -> Option<TabIndex> {
2478 self.flags.get_tab_index()
2479 }
2480 #[inline]
2481 #[must_use] pub fn get_accessibility_info(&self) -> Option<&AccessibilityInfo> {
2482 self.accessibility.as_deref()
2483 }
2484 #[inline]
2485 #[must_use] pub fn get_menu_bar(&self) -> Option<&Menu> {
2486 self.extra.as_ref().and_then(|e| e.menu_bar.as_deref())
2487 }
2488 #[inline]
2489 #[must_use] pub fn get_context_menu(&self) -> Option<&Menu> {
2490 self.extra.as_ref().and_then(|e| e.context_menu.as_deref())
2491 }
2492
2493 #[inline]
2495 #[must_use] pub const fn is_anonymous(&self) -> bool {
2496 self.flags.is_anonymous()
2497 }
2498
2499 #[inline]
2500 pub fn set_node_type(&mut self, node_type: NodeType) {
2501 self.node_type = node_type;
2502 }
2503 #[inline]
2504 pub fn set_dataset(&mut self, data: OptionRefAny) {
2505 match data {
2506 OptionRefAny::None => {
2507 if let Some(ext) = self.extra.as_mut() {
2508 ext.dataset = None;
2509 }
2510 }
2511 OptionRefAny::Some(r) => {
2512 self.extra
2513 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2514 .dataset = Some(r);
2515 }
2516 }
2517 }
2518 #[inline]
2522 #[allow(clippy::needless_pass_by_value)] pub fn set_ids_and_classes(&mut self, ids_and_classes: IdOrClassVec) {
2524 let mut v: AttributeTypeVec = Vec::new().into();
2526 mem::swap(&mut v, self.attributes_mut());
2527 let mut v = v.into_library_owned_vec();
2528 v.retain(|a| !matches!(a, AttributeType::Id(_) | AttributeType::Class(_)));
2529 for ioc in ids_and_classes.as_ref() {
2531 match ioc {
2532 IdOrClass::Id(s) => v.push(AttributeType::Id(s.clone())),
2533 IdOrClass::Class(s) => v.push(AttributeType::Class(s.clone())),
2534 }
2535 }
2536 self.set_attributes(v.into());
2537 }
2538 #[inline]
2539 pub fn set_callbacks(&mut self, callbacks: CoreCallbackDataVec) {
2540 self.callbacks = callbacks;
2541 }
2542 #[inline]
2546 pub fn set_css_props(&mut self, css_props: CssPropertyWithConditionsVec) {
2547 self.style = css_props.into();
2548 }
2549 #[inline]
2552 pub fn set_style(&mut self, style: azul_css::css::Css) {
2553 self.style = style;
2554 }
2555 #[inline]
2556 pub fn set_clip_mask(&mut self, clip_mask: ImageMask) {
2557 self.extra
2558 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2559 .svg_data = Some(SvgNodeData::ImageClipMask(clip_mask));
2560 }
2561 #[inline]
2562 pub fn set_svg_data(&mut self, data: SvgNodeData) {
2563 self.extra
2564 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2565 .svg_data = Some(data);
2566 }
2567 #[inline]
2568 pub const fn set_tab_index(&mut self, tab_index: TabIndex) {
2569 self.flags.set_tab_index(Some(tab_index));
2570 }
2571 #[inline]
2572 pub const fn set_contenteditable(&mut self, contenteditable: bool) {
2573 self.flags.set_contenteditable_mut(contenteditable);
2574 }
2575 #[inline]
2576 #[must_use] pub const fn is_contenteditable(&self) -> bool {
2577 self.flags.is_contenteditable()
2578 }
2579 #[inline]
2580 pub fn set_accessibility_info(&mut self, accessibility_info: AccessibilityInfo) {
2581 self.accessibility = Some(Box::new(accessibility_info));
2582 }
2583
2584 #[inline]
2586 pub const fn set_anonymous(&mut self, is_anonymous: bool) {
2587 self.flags.set_anonymous(is_anonymous);
2588 }
2589 #[inline]
2590 pub fn set_menu_bar(&mut self, menu_bar: Menu) {
2591 self.extra
2592 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2593 .menu_bar = Some(Box::new(menu_bar));
2594 }
2595 #[inline]
2596 pub fn set_context_menu(&mut self, context_menu: Menu) {
2597 self.extra
2598 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2599 .context_menu = Some(Box::new(context_menu));
2600 }
2601
2602 #[inline]
2615 pub fn set_key<K: Hash>(&mut self, key: K) {
2616 use core::hash::Hasher;
2617 let mut hasher = crate::hash::DefaultHasher::new();
2618 key.hash(&mut hasher);
2619 self.extra
2620 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2621 .key = Some(hasher.finish());
2622 }
2623
2624 #[inline]
2626 #[must_use] pub fn get_key(&self) -> Option<u64> {
2627 self.extra.as_ref().and_then(|ext| ext.key)
2628 }
2629
2630 #[inline]
2663 pub fn set_merge_callback<C: Into<DatasetMergeCallback>>(&mut self, callback: C) {
2664 self.extra
2665 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2666 .dataset_merge_callback = Some(callback.into());
2667 }
2668
2669 #[inline]
2671 #[must_use] pub fn get_merge_callback(&self) -> Option<DatasetMergeCallback> {
2672 self.extra.as_ref().and_then(|ext| ext.dataset_merge_callback.clone())
2673 }
2674
2675 #[inline]
2680 pub fn set_component_origin(&mut self, origin: ComponentOrigin) {
2681 self.extra
2682 .get_or_insert_with(|| Box::new(NodeDataExt::default()))
2683 .component_origin = Some(origin);
2684 }
2685
2686 #[inline]
2688 #[must_use] pub fn get_component_origin(&self) -> Option<&ComponentOrigin> {
2689 self.extra.as_ref().and_then(|ext| ext.component_origin.as_ref())
2690 }
2691
2692 #[inline]
2693 #[must_use] pub fn with_menu_bar(mut self, menu_bar: Menu) -> Self {
2694 self.set_menu_bar(menu_bar);
2695 self
2696 }
2697
2698 #[inline]
2699 #[must_use] pub fn with_context_menu(mut self, context_menu: Menu) -> Self {
2700 self.set_context_menu(context_menu);
2701 self
2702 }
2703
2704 #[inline]
2705 pub fn add_callback<C: Into<CoreCallback>>(
2706 &mut self,
2707 event: EventFilter,
2708 data: RefAny,
2709 callback: C,
2710 ) {
2711 let callback = callback.into();
2712 let mut v: CoreCallbackDataVec = Vec::new().into();
2713 mem::swap(&mut v, &mut self.callbacks);
2714 let mut v = v.into_library_owned_vec();
2715 v.push(CoreCallbackData {
2716 event,
2717 refany: data,
2718 callback,
2719 });
2720 self.callbacks = v.into();
2721 }
2722
2723 #[inline]
2724 pub fn add_id(&mut self, s: AzString) {
2725 let mut v: AttributeTypeVec = Vec::new().into();
2726 mem::swap(&mut v, self.attributes_mut());
2727 let mut v = v.into_library_owned_vec();
2728 v.push(AttributeType::Id(s));
2729 self.set_attributes(v.into());
2730 }
2731 #[inline]
2732 pub fn add_class(&mut self, s: AzString) {
2733 let mut v: AttributeTypeVec = Vec::new().into();
2734 mem::swap(&mut v, self.attributes_mut());
2735 let mut v = v.into_library_owned_vec();
2736 v.push(AttributeType::Class(s));
2737 self.set_attributes(v.into());
2738 }
2739
2740 #[inline]
2745 pub fn add_css_property(&mut self, p: CssPropertyWithConditions) {
2746 use azul_css::css::{rule_priority, CssDeclaration, CssPath, CssRuleBlock};
2747 let rule = CssRuleBlock {
2748 path: CssPath { selectors: Vec::new().into() },
2749 declarations: vec![CssDeclaration::Static(p.property)].into(),
2750 conditions: p.apply_if,
2751 priority: rule_priority::INLINE,
2752 };
2753 let mut v: azul_css::css::CssRuleBlockVec = Vec::new().into();
2754 mem::swap(&mut v, &mut self.style.rules);
2755 let mut v = v.into_library_owned_vec();
2756 v.push(rule);
2757 self.style.rules = v.into();
2758 }
2759
2760 #[must_use] pub fn calculate_node_data_hash(&self) -> DomNodeHash {
2762 use core::hash::Hasher;
2763 let mut hasher = crate::hash::DefaultHasher::new();
2764 self.hash(&mut hasher);
2765 let h = hasher.finish();
2766 DomNodeHash { inner: h }
2767 }
2768
2769 #[must_use] pub fn calculate_structural_hash(&self) -> DomNodeHash {
2781 use core::hash::Hasher;
2782 use core::hash::Hasher as StdHasher;
2783
2784 let mut hasher = crate::hash::DefaultHasher::new();
2785
2786 mem::discriminant(&self.node_type).hash(&mut hasher);
2789
2790 if self.node_type == NodeType::VirtualView {
2792 if let Some(ext) = self.extra.as_ref() {
2793 if let Some(vv) = ext.virtual_view.as_ref() {
2794 vv.hash(&mut hasher);
2795 }
2796 }
2797 }
2798
2799 if let NodeType::Image(ref img_ref) = self.node_type {
2805 match img_ref.get_data() {
2806 crate::resources::DecodedImage::Callback(cb) => {
2807 cb.callback.cb.hash(&mut hasher);
2809 cb.refany.get_type_id().hash(&mut hasher);
2811 }
2812 _ => {
2813 img_ref.hash(&mut hasher);
2815 }
2816 }
2817 }
2818
2819 for attr in self.attributes().as_ref() {
2822 match attr {
2823 AttributeType::Id(s) => { 0u8.hash(&mut hasher); s.as_str().hash(&mut hasher); }
2824 AttributeType::Class(s) => { 1u8.hash(&mut hasher); s.as_str().hash(&mut hasher); }
2825 _ => {}
2826 }
2827 }
2828
2829 for attr in self.attributes().as_ref() {
2832 if !matches!(attr, AttributeType::ContentEditable(_) | AttributeType::Id(_) | AttributeType::Class(_)) {
2833 attr.hash(&mut hasher);
2834 }
2835 }
2836
2837 for callback in self.callbacks.as_ref() {
2839 callback.event.hash(&mut hasher);
2840 }
2841
2842 let h = hasher.finish();
2843 DomNodeHash { inner: h }
2844 }
2845
2846 #[inline]
2847 #[must_use] pub const fn with_tab_index(mut self, tab_index: TabIndex) -> Self {
2848 self.set_tab_index(tab_index);
2849 self
2850 }
2851 #[inline]
2852 #[must_use] pub const fn with_contenteditable(mut self, contenteditable: bool) -> Self {
2853 self.set_contenteditable(contenteditable);
2854 self
2855 }
2856 #[inline]
2857 #[must_use] pub fn with_node_type(mut self, node_type: NodeType) -> Self {
2858 self.set_node_type(node_type);
2859 self
2860 }
2861 #[inline]
2862 #[must_use]
2863 pub fn with_callback<C: Into<CoreCallback>>(
2864 mut self,
2865 event: EventFilter,
2866 data: RefAny,
2867 callback: C,
2868 ) -> Self {
2869 self.add_callback(event, data, callback);
2870 self
2871 }
2872 #[inline]
2873 #[must_use] pub fn with_dataset(mut self, data: OptionRefAny) -> Self {
2874 self.set_dataset(data);
2875 self
2876 }
2877 #[inline]
2878 #[must_use] pub fn with_ids_and_classes(mut self, ids_and_classes: IdOrClassVec) -> Self {
2879 self.set_ids_and_classes(ids_and_classes);
2880 self
2881 }
2882 #[inline]
2883 #[must_use] pub fn with_callbacks(mut self, callbacks: CoreCallbackDataVec) -> Self {
2884 self.callbacks = callbacks;
2885 self
2886 }
2887 #[inline]
2891 #[must_use] pub fn with_css_props(mut self, css_props: CssPropertyWithConditionsVec) -> Self {
2892 self.style = css_props.into();
2893 self
2894 }
2895 #[inline]
2897 #[must_use] pub fn with_style(mut self, style: azul_css::css::Css) -> Self {
2898 self.style = style;
2899 self
2900 }
2901
2902 #[inline]
2915 #[must_use]
2916 pub fn with_key<K: Hash>(mut self, key: K) -> Self {
2917 self.set_key(key);
2918 self
2919 }
2920
2921 #[inline]
2952 #[must_use]
2953 pub fn with_merge_callback<C: Into<DatasetMergeCallback>>(mut self, callback: C) -> Self {
2954 self.set_merge_callback(callback);
2955 self
2956 }
2957
2958 pub fn set_css(&mut self, style: &str) {
2976 let parsed = azul_css::css::Css::parse_inline(style);
2980 let mut current: azul_css::css::CssRuleBlockVec = Vec::new().into();
2981 mem::swap(&mut current, &mut self.style.rules);
2982 let mut v = current.into_library_owned_vec();
2983 v.extend(parsed.rules.into_library_owned_vec());
2984 self.style.rules = v.into();
2985 }
2986
2987 #[must_use] pub fn with_css(mut self, style: &str) -> Self {
2989 self.set_css(style);
2990 self
2991 }
2992
2993 #[inline]
2994 #[must_use]
2995 pub const fn swap_with_default(&mut self) -> Self {
2996 let mut s = Self::create_div();
2997 mem::swap(&mut s, self);
2998 s
2999 }
3000
3001 #[inline]
3002 #[must_use] pub fn copy_special(&self) -> Self {
3003 Self {
3004 node_type: self.node_type.to_library_owned_nodetype(),
3005 style: self.style.clone(),
3006 callbacks: self.callbacks.clone(),
3007 flags: self.flags,
3008 accessibility: self.accessibility.clone(),
3009 extra: self.extra.clone(),
3010 }
3011 }
3012
3013 pub(crate) fn copy_special_moving_complex(&mut self) -> Self {
3028 let taken_style = mem::take(&mut self.style);
3039 let taken_extra = self.extra.take();
3040 let taken_node_type = mem::replace(&mut self.node_type, NodeType::Div);
3041 let mut copy = self.copy_special();
3042 unsafe { core::ptr::write(&raw mut copy.node_type, taken_node_type); }
3050 copy.style = taken_style;
3051 copy.extra = taken_extra;
3052 copy
3053 }
3054
3055 #[must_use] pub fn is_focusable(&self) -> bool {
3056 if matches!(self.node_type,
3058 NodeType::A | NodeType::Button | NodeType::Input
3059 | NodeType::Select | NodeType::TextArea
3060 ) {
3061 return true;
3062 }
3063 if self.is_contenteditable() {
3065 return true;
3066 }
3067 self.get_tab_index().is_some()
3069 || self
3070 .get_callbacks()
3071 .iter()
3072 .any(|cb| cb.event.is_focus_callback())
3073 }
3074
3075 #[must_use] pub fn has_activation_behavior(&self) -> bool {
3088 use crate::events::{EventFilter, HoverEventFilter};
3089
3090 if matches!(self.node_type, NodeType::A | NodeType::Button) {
3092 return true;
3093 }
3094
3095 let has_click_callback = self
3098 .get_callbacks()
3099 .iter()
3100 .any(|cb| matches!(
3101 cb.event,
3102 EventFilter::Hover(HoverEventFilter::MouseUp | HoverEventFilter::LeftMouseUp)
3103 ));
3104
3105 if has_click_callback {
3106 return true;
3107 }
3108
3109 if let Some(ref accessibility) = self.accessibility {
3111 use crate::a11y::AccessibilityRole;
3112 match accessibility.role {
3113 AccessibilityRole::PushButton | AccessibilityRole::Link
3115 | AccessibilityRole::CheckButton | AccessibilityRole::RadioButton | AccessibilityRole::MenuItem
3118 | AccessibilityRole::PageTab => return true,
3120 _ => {}
3121 }
3122 }
3123
3124 false
3125 }
3126
3127 #[must_use] pub fn is_activatable(&self) -> bool {
3132 if !self.has_activation_behavior() {
3133 return false;
3134 }
3135
3136 if let Some(ref accessibility) = self.accessibility {
3138 if accessibility
3140 .states
3141 .as_ref()
3142 .iter()
3143 .any(|s| matches!(s, AccessibilityState::Unavailable))
3144 {
3145 return false;
3146 }
3147 }
3148
3149 true
3151 }
3152
3153 #[must_use] pub fn get_effective_tabindex(&self) -> Option<i32> {
3161 self.flags.get_tab_index().map_or_else(|| if self.get_callbacks().iter().any(|cb| cb.event.is_focus_callback()) {
3162 Some(0)
3163 } else {
3164 None
3165 }, |tab_idx| match tab_idx {
3166 TabIndex::Auto => Some(0),
3167 TabIndex::OverrideInParent(n) => Some(i32::try_from(n).unwrap_or(i32::MAX)),
3168 TabIndex::NoKeyboardFocus => Some(-1),
3169 })
3170 }
3171
3172 #[must_use] pub fn get_accessible_label(&self) -> Option<&str> {
3178 for attr in self.attributes().as_ref() {
3179 if let AttributeType::AriaLabel(s) = attr { return Some(s.as_str()) }
3180 }
3181 for attr in self.attributes().as_ref() {
3182 match attr {
3183 AttributeType::Alt(s) | AttributeType::Title(s) => return Some(s.as_str()),
3184 _ => {}
3185 }
3186 }
3187 None
3188 }
3189
3190 #[must_use] pub fn get_accessible_value(&self) -> Option<&str> {
3195 for attr in self.attributes().as_ref() {
3196 if let AttributeType::Value(s) = attr {
3197 return Some(s.as_str());
3198 }
3199 }
3200 None
3201 }
3202
3203 #[must_use] pub fn get_placeholder(&self) -> Option<&str> {
3205 for attr in self.attributes().as_ref() {
3206 if let AttributeType::Placeholder(s) = attr {
3207 return Some(s.as_str());
3208 }
3209 }
3210 None
3211 }
3212
3213 pub fn get_virtual_view_node(&mut self) -> Option<&mut VirtualViewNode> {
3214 self.extra.as_mut()?.virtual_view.as_mut()
3215 }
3216
3217 #[must_use] pub fn get_virtual_view_node_ref(&self) -> Option<&VirtualViewNode> {
3218 self.extra.as_ref()?.virtual_view.as_ref()
3219 }
3220
3221 pub fn get_render_image_callback_node(
3222 &mut self,
3223 ) -> Option<(&mut CoreImageCallback, ImageRefHash)> {
3224 match &mut self.node_type {
3225 NodeType::Image(ref mut img) => {
3226 let hash = image_ref_get_hash(img.as_ref());
3227 img.as_mut().get_image_callback_mut().map(|r| (r, hash))
3228 }
3229 _ => None,
3230 }
3231 }
3232
3233 pub fn debug_print_start(
3234 &self,
3235 css_cache: &CssPropertyCache,
3236 node_id: &NodeId,
3237 node_state: &StyledNodeState,
3238 ) -> String {
3239 let html_type = self.node_type.get_path();
3240 let attributes_string = node_data_to_string(self);
3241 let style = css_cache.get_computed_css_style_string(self, node_id, node_state);
3242 format!(
3243 "<{} data-az-node-id=\"{}\" {} {style}>",
3244 html_type,
3245 node_id.index(),
3246 attributes_string,
3247 style = if style.trim().is_empty() {
3248 String::new()
3249 } else {
3250 format!("style=\"{style}\"")
3251 }
3252 )
3253 }
3254
3255 #[must_use] pub fn debug_print_end(&self) -> String {
3256 let html_type = self.node_type.get_path();
3257 format!("</{html_type}>")
3258 }
3259}
3260
3261impl crate::events::ActivationBehavior for NodeData {
3262 fn has_activation_behavior(&self) -> bool {
3263 Self::has_activation_behavior(self)
3264 }
3265
3266 fn is_activatable(&self) -> bool {
3267 Self::is_activatable(self)
3268 }
3269}
3270
3271impl crate::events::Focusable for NodeData {
3272 fn get_tabindex(&self) -> Option<i32> {
3273 self.get_effective_tabindex()
3274 }
3275
3276 fn is_focusable(&self) -> bool {
3277 Self::is_focusable(self)
3278 }
3279
3280 fn is_naturally_focusable(&self) -> bool {
3281 matches!(
3282 self.node_type,
3283 NodeType::A
3284 | NodeType::Button
3285 | NodeType::Input
3286 | NodeType::Select
3287 | NodeType::TextArea
3288 )
3289 }
3290}
3291
3292#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
3294#[repr(C)]
3295pub struct DomId {
3296 pub inner: usize,
3297}
3298
3299impl fmt::Display for DomId {
3300 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3301 write!(f, "{}", self.inner)
3302 }
3303}
3304
3305impl DomId {
3306 pub const ROOT_ID: Self = Self { inner: 0 };
3307}
3308
3309impl Default for DomId {
3310 fn default() -> Self {
3311 Self::ROOT_ID
3312 }
3313}
3314
3315impl_option!(
3316 DomId,
3317 OptionDomId,
3318 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3319);
3320
3321impl_vec!(DomId, DomIdVec, DomIdVecDestructor, DomIdVecDestructorType, DomIdVecSlice, OptionDomId);
3322impl_vec_debug!(DomId, DomIdVec);
3323impl_vec_clone!(DomId, DomIdVec, DomIdVecDestructor);
3324impl_vec_partialeq!(DomId, DomIdVec);
3325impl_vec_partialord!(DomId, DomIdVec);
3326
3327#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3329#[repr(C)]
3330pub struct DomNodeId {
3331 pub dom: DomId,
3333 pub node: NodeHierarchyItemId,
3335}
3336
3337impl_option!(
3338 DomNodeId,
3339 OptionDomNodeId,
3340 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3341);
3342
3343impl DomNodeId {
3344 pub const ROOT: Self = Self {
3345 dom: DomId::ROOT_ID,
3346 node: NodeHierarchyItemId::NONE,
3347 };
3348}
3349
3350#[repr(C)]
3356#[derive(PartialEq, Clone)]
3357pub struct Dom {
3358 pub root: NodeData,
3360 pub children: DomVec,
3362 pub css: azul_css::css::CssVec,
3366 pub estimated_total_children: usize,
3379}
3380
3381#[repr(C)]
3387#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
3388pub struct CssWithNodeId {
3389 pub node_id: usize,
3391 pub css: azul_css::css::Css,
3393}
3394
3395impl_vec!(CssWithNodeId, CssWithNodeIdVec, CssWithNodeIdVecDestructor, CssWithNodeIdVecDestructorType, CssWithNodeIdVecSlice, OptionCssWithNodeId);
3396impl_option!(CssWithNodeId, OptionCssWithNodeId, copy = false, [Debug, Clone, PartialEq, Eq, PartialOrd]);
3397impl_vec_clone!(CssWithNodeId, CssWithNodeIdVec, CssWithNodeIdVecDestructor);
3398impl_vec_mut!(CssWithNodeId, CssWithNodeIdVec);
3399impl_vec_debug!(CssWithNodeId, CssWithNodeIdVec);
3400impl_vec_partialord!(CssWithNodeId, CssWithNodeIdVec);
3401impl_vec_partialeq!(CssWithNodeId, CssWithNodeIdVec);
3402
3403#[repr(C)]
3410#[derive(Debug, Clone, PartialEq, PartialOrd)]
3411pub struct FastDom {
3412 pub node_hierarchy: crate::styled_dom::NodeHierarchyItemVec,
3414 pub node_data: NodeDataVec,
3416 pub css: CssWithNodeIdVec,
3418}
3419
3420impl Eq for Dom {}
3423
3424impl Hash for Dom {
3425 fn hash<H: Hasher>(&self, state: &mut H) {
3426 self.root.hash(state);
3427 self.children.hash(state);
3428 self.estimated_total_children.hash(state);
3429 }
3430}
3431
3432impl PartialOrd for Dom {
3434 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
3435 Some(self.cmp(other))
3436 }
3437}
3438impl Ord for Dom {
3439 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
3440 self.root.cmp(&other.root)
3441 .then_with(|| self.children.cmp(&other.children))
3442 .then_with(|| self.estimated_total_children.cmp(&other.estimated_total_children))
3443 }
3444}
3445
3446impl_option!(
3447 Dom,
3448 OptionDom,
3449 copy = false,
3450 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3451);
3452
3453impl_vec!(Dom, DomVec, DomVecDestructor, DomVecDestructorType, DomVecSlice, OptionDom);
3454impl_vec_clone!(Dom, DomVec, DomVecDestructor);
3455impl_vec_mut!(Dom, DomVec);
3456impl_vec_debug!(Dom, DomVec);
3457impl_vec_partialord!(Dom, DomVec);
3458impl_vec_ord!(Dom, DomVec);
3459impl_vec_partialeq!(Dom, DomVec);
3460impl_vec_eq!(Dom, DomVec);
3461impl_vec_hash!(Dom, DomVec);
3462
3463impl Default for Dom {
3469 fn default() -> Self {
3470 Self::create_body()
3471 }
3472}
3473
3474impl Dom {
3475 #[inline]
3480 #[must_use] pub fn create_node(node_type: NodeType) -> Self {
3481 Self {
3482 root: NodeData::create_node(node_type),
3483 children: Vec::new().into(),
3484 css: Vec::new().into(),
3485 estimated_total_children: 0,
3486 }
3487 }
3488 #[inline]
3489 #[must_use] pub fn create_from_data(node_data: NodeData) -> Self {
3490 Self {
3491 root: node_data,
3492 children: Vec::new().into(),
3493 css: Vec::new().into(),
3494 estimated_total_children: 0,
3495 }
3496 }
3497
3498 #[inline]
3505 #[must_use] pub const fn create_html() -> Self {
3506 Self {
3507 root: NodeData::create_node(NodeType::Html),
3508 children: DomVec::from_const_slice(&[]),
3509 css: azul_css::css::CssVec::from_const_slice(&[]),
3510 estimated_total_children: 0,
3511 }
3512 }
3513
3514 #[inline]
3518 #[must_use] pub const fn create_head() -> Self {
3519 Self {
3520 root: NodeData::create_node(NodeType::Head),
3521 children: DomVec::from_const_slice(&[]),
3522 css: azul_css::css::CssVec::from_const_slice(&[]),
3523 estimated_total_children: 0,
3524 }
3525 }
3526
3527 #[inline]
3528 #[must_use] pub const fn create_body() -> Self {
3529 Self {
3530 root: NodeData::create_node(NodeType::Body),
3531 children: DomVec::from_const_slice(&[]),
3532 css: azul_css::css::CssVec::from_const_slice(&[]),
3533 estimated_total_children: 0,
3534 }
3535 }
3536
3537 #[inline]
3542 #[must_use] pub const fn create_div() -> Self {
3543 Self {
3544 root: NodeData::create_node(NodeType::Div),
3545 children: DomVec::from_const_slice(&[]),
3546 css: azul_css::css::CssVec::from_const_slice(&[]),
3547 estimated_total_children: 0,
3548 }
3549 }
3550
3551 #[inline]
3559 #[must_use] pub const fn create_article() -> Self {
3560 Self {
3561 root: NodeData::create_node(NodeType::Article),
3562 children: DomVec::from_const_slice(&[]),
3563 css: azul_css::css::CssVec::from_const_slice(&[]),
3564 estimated_total_children: 0,
3565 }
3566 }
3567
3568 #[inline]
3573 #[must_use] pub const fn create_section() -> Self {
3574 Self {
3575 root: NodeData::create_node(NodeType::Section),
3576 children: DomVec::from_const_slice(&[]),
3577 css: azul_css::css::CssVec::from_const_slice(&[]),
3578 estimated_total_children: 0,
3579 }
3580 }
3581
3582 #[inline]
3588 #[must_use] pub const fn create_nav() -> Self {
3589 Self {
3590 root: NodeData::create_node(NodeType::Nav),
3591 children: DomVec::from_const_slice(&[]),
3592 css: azul_css::css::CssVec::from_const_slice(&[]),
3593 estimated_total_children: 0,
3594 }
3595 }
3596
3597 #[inline]
3602 #[must_use] pub const fn create_aside() -> Self {
3603 Self {
3604 root: NodeData::create_node(NodeType::Aside),
3605 children: DomVec::from_const_slice(&[]),
3606 css: azul_css::css::CssVec::from_const_slice(&[]),
3607 estimated_total_children: 0,
3608 }
3609 }
3610
3611 #[inline]
3616 #[must_use] pub const fn create_header() -> Self {
3617 Self {
3618 root: NodeData::create_node(NodeType::Header),
3619 children: DomVec::from_const_slice(&[]),
3620 css: azul_css::css::CssVec::from_const_slice(&[]),
3621 estimated_total_children: 0,
3622 }
3623 }
3624
3625 #[inline]
3630 #[must_use] pub const fn create_footer() -> Self {
3631 Self {
3632 root: NodeData::create_node(NodeType::Footer),
3633 children: DomVec::from_const_slice(&[]),
3634 css: azul_css::css::CssVec::from_const_slice(&[]),
3635 estimated_total_children: 0,
3636 }
3637 }
3638
3639 #[inline]
3645 #[must_use] pub const fn create_main() -> Self {
3646 Self {
3647 root: NodeData::create_node(NodeType::Main),
3648 children: DomVec::from_const_slice(&[]),
3649 css: azul_css::css::CssVec::from_const_slice(&[]),
3650 estimated_total_children: 0,
3651 }
3652 }
3653
3654 #[inline]
3659 #[must_use] pub const fn create_figure() -> Self {
3660 Self {
3661 root: NodeData::create_node(NodeType::Figure),
3662 children: DomVec::from_const_slice(&[]),
3663 css: azul_css::css::CssVec::from_const_slice(&[]),
3664 estimated_total_children: 0,
3665 }
3666 }
3667
3668 #[inline]
3673 #[must_use] pub const fn create_figcaption() -> Self {
3674 Self {
3675 root: NodeData::create_node(NodeType::FigCaption),
3676 children: DomVec::from_const_slice(&[]),
3677 css: azul_css::css::CssVec::from_const_slice(&[]),
3678 estimated_total_children: 0,
3679 }
3680 }
3681
3682 #[inline]
3689 #[must_use] pub const fn create_details_no_a11y() -> Self {
3690 Self {
3691 root: NodeData::create_node(NodeType::Details),
3692 children: DomVec::from_const_slice(&[]),
3693 css: azul_css::css::CssVec::from_const_slice(&[]),
3694 estimated_total_children: 0,
3695 }
3696 }
3697
3698 #[inline]
3705 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_details(aria: SmallAriaInfo) -> Self {
3707 Self::create_details_no_a11y().with_accessibility_info(aria.to_full_info())
3708 }
3709
3710 #[inline]
3715 #[must_use] pub const fn create_summary_no_a11y() -> Self {
3716 Self {
3717 root: NodeData::create_node(NodeType::Summary),
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]
3731 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_summary(aria: SmallAriaInfo) -> Self {
3733 Self::create_summary_no_a11y().with_accessibility_info(aria.to_full_info())
3734 }
3735
3736 #[inline]
3741 pub fn create_summary_with_text_no_a11y<S: Into<AzString>>(text: S) -> Self {
3742 Self::create_summary_no_a11y().with_child(Self::create_text(text))
3743 }
3744
3745 #[inline]
3752 #[allow(clippy::needless_pass_by_value)] pub fn create_summary_with_text<S: Into<AzString>>(text: S, aria: SmallAriaInfo) -> Self {
3754 Self::create_summary_with_text_no_a11y(text).with_accessibility_info(aria.to_full_info())
3755 }
3756
3757 #[inline]
3762 #[must_use] pub const fn create_dialog_no_a11y() -> Self {
3763 Self {
3764 root: NodeData::create_node(NodeType::Dialog),
3765 children: DomVec::from_const_slice(&[]),
3766 css: azul_css::css::CssVec::from_const_slice(&[]),
3767 estimated_total_children: 0,
3768 }
3769 }
3770
3771 #[inline]
3779 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_dialog(aria: DialogAriaInfo) -> Self {
3781 Self::create_dialog_no_a11y().with_accessibility_info(aria.to_full_info())
3782 }
3783
3784 #[inline]
3787 #[must_use] pub const fn create_br() -> Self {
3788 Self {
3789 root: NodeData::create_node(NodeType::Br),
3790 children: DomVec::from_const_slice(&[]),
3791 css: azul_css::css::CssVec::from_const_slice(&[]),
3792 estimated_total_children: 0,
3793 }
3794 }
3795 #[inline]
3796 pub fn create_text<S: Into<AzString>>(value: S) -> Self {
3797 Self::create_node(NodeType::Text(BoxOrStatic::heap(value.into())))
3798 }
3799 #[inline]
3800 #[must_use] pub fn create_image(image: ImageRef) -> Self {
3801 Self::create_node(NodeType::Image(BoxOrStatic::heap(image)))
3802 }
3803 #[inline]
3815 pub fn create_icon<S: Into<AzString>>(icon_name: S) -> Self {
3816 Self::create_node(NodeType::Icon(BoxOrStatic::heap(icon_name.into())))
3817 }
3818
3819 #[inline]
3820 pub fn create_virtual_view(data: RefAny, callback: impl Into<VirtualViewCallback>) -> Self {
3821 Self::create_from_data(NodeData::create_virtual_view(data, callback))
3822 }
3823
3824 #[inline]
3831 #[must_use] pub fn create_geolocation_probe(config: crate::geolocation::GeolocationProbeConfig) -> Self {
3832 Self::create_node(NodeType::GeolocationProbe(config))
3833 }
3834
3835 #[inline]
3841 #[must_use] pub const fn create_p() -> Self {
3842 Self {
3843 root: NodeData::create_node(NodeType::P),
3844 children: DomVec::from_const_slice(&[]),
3845 css: azul_css::css::CssVec::from_const_slice(&[]),
3846 estimated_total_children: 0,
3847 }
3848 }
3849
3850 #[inline]
3855 #[must_use] pub const fn create_h1() -> Self {
3856 Self {
3857 root: NodeData::create_node(NodeType::H1),
3858 children: DomVec::from_const_slice(&[]),
3859 css: azul_css::css::CssVec::from_const_slice(&[]),
3860 estimated_total_children: 0,
3861 }
3862 }
3863
3864 #[inline]
3872 pub fn create_h1_with_text<S: Into<AzString>>(text: S) -> Self {
3873 Self::create_h1().with_child(Self::create_text(text))
3874 }
3875
3876 #[inline]
3880 #[must_use] pub const fn create_h2() -> Self {
3881 Self {
3882 root: NodeData::create_node(NodeType::H2),
3883 children: DomVec::from_const_slice(&[]),
3884 css: azul_css::css::CssVec::from_const_slice(&[]),
3885 estimated_total_children: 0,
3886 }
3887 }
3888
3889 #[inline]
3896 pub fn create_h2_with_text<S: Into<AzString>>(text: S) -> Self {
3897 Self::create_h2().with_child(Self::create_text(text))
3898 }
3899
3900 #[inline]
3904 #[must_use] pub const fn create_h3() -> Self {
3905 Self {
3906 root: NodeData::create_node(NodeType::H3),
3907 children: DomVec::from_const_slice(&[]),
3908 css: azul_css::css::CssVec::from_const_slice(&[]),
3909 estimated_total_children: 0,
3910 }
3911 }
3912
3913 #[inline]
3920 pub fn create_h3_with_text<S: Into<AzString>>(text: S) -> Self {
3921 Self::create_h3().with_child(Self::create_text(text))
3922 }
3923
3924 #[inline]
3926 #[must_use] pub const fn create_h4() -> Self {
3927 Self {
3928 root: NodeData::create_node(NodeType::H4),
3929 children: DomVec::from_const_slice(&[]),
3930 css: azul_css::css::CssVec::from_const_slice(&[]),
3931 estimated_total_children: 0,
3932 }
3933 }
3934
3935 #[inline]
3940 pub fn create_h4_with_text<S: Into<AzString>>(text: S) -> Self {
3941 Self::create_h4().with_child(Self::create_text(text))
3942 }
3943
3944 #[inline]
3946 #[must_use] pub const fn create_h5() -> Self {
3947 Self {
3948 root: NodeData::create_node(NodeType::H5),
3949 children: DomVec::from_const_slice(&[]),
3950 css: azul_css::css::CssVec::from_const_slice(&[]),
3951 estimated_total_children: 0,
3952 }
3953 }
3954
3955 #[inline]
3960 pub fn create_h5_with_text<S: Into<AzString>>(text: S) -> Self {
3961 Self::create_h5().with_child(Self::create_text(text))
3962 }
3963
3964 #[inline]
3966 #[must_use] pub const fn create_h6() -> Self {
3967 Self {
3968 root: NodeData::create_node(NodeType::H6),
3969 children: DomVec::from_const_slice(&[]),
3970 css: azul_css::css::CssVec::from_const_slice(&[]),
3971 estimated_total_children: 0,
3972 }
3973 }
3974
3975 #[inline]
3980 pub fn create_h6_with_text<S: Into<AzString>>(text: S) -> Self {
3981 Self::create_h6().with_child(Self::create_text(text))
3982 }
3983
3984 #[inline]
3989 #[must_use] pub const fn create_span() -> Self {
3990 Self {
3991 root: NodeData::create_node(NodeType::Span),
3992 children: DomVec::from_const_slice(&[]),
3993 css: azul_css::css::CssVec::from_const_slice(&[]),
3994 estimated_total_children: 0,
3995 }
3996 }
3997
3998 #[inline]
4006 pub fn create_span_with_text<S: Into<AzString>>(text: S) -> Self {
4007 Self::create_span().with_child(Self::create_text(text))
4008 }
4009
4010 #[inline]
4014 #[must_use] pub const fn create_strong() -> Self {
4015 Self {
4016 root: NodeData::create_node(NodeType::Strong),
4017 children: DomVec::from_const_slice(&[]),
4018 css: azul_css::css::CssVec::from_const_slice(&[]),
4019 estimated_total_children: 0,
4020 }
4021 }
4022
4023 #[inline]
4031 pub fn create_strong_with_text<S: Into<AzString>>(text: S) -> Self {
4032 Self::create_strong().with_child(Self::create_text(text))
4033 }
4034
4035 #[inline]
4039 #[must_use] pub const fn create_em() -> Self {
4040 Self {
4041 root: NodeData::create_node(NodeType::Em),
4042 children: DomVec::from_const_slice(&[]),
4043 css: azul_css::css::CssVec::from_const_slice(&[]),
4044 estimated_total_children: 0,
4045 }
4046 }
4047
4048 #[inline]
4056 pub fn create_em_with_text<S: Into<AzString>>(text: S) -> Self {
4057 Self::create_em().with_child(Self::create_text(text))
4058 }
4059
4060 #[inline]
4064 #[must_use] pub fn create_code() -> Self {
4065 Self::create_node(NodeType::Code)
4066 }
4067
4068 #[inline]
4076 pub fn create_code_with_text<S: Into<AzString>>(code: S) -> Self {
4077 Self::create_code().with_child(Self::create_text(code))
4078 }
4079
4080 #[inline]
4084 #[must_use] pub fn create_pre() -> Self {
4085 Self::create_node(NodeType::Pre)
4086 }
4087
4088 #[inline]
4096 pub fn create_pre_with_text<S: Into<AzString>>(text: S) -> Self {
4097 Self::create_pre().with_child(Self::create_text(text))
4098 }
4099
4100 #[inline]
4104 #[must_use] pub fn create_blockquote() -> Self {
4105 Self::create_node(NodeType::BlockQuote)
4106 }
4107
4108 #[inline]
4116 pub fn create_blockquote_with_text<S: Into<AzString>>(text: S) -> Self {
4117 Self::create_blockquote().with_child(Self::create_text(text))
4118 }
4119
4120 #[inline]
4124 #[must_use] pub fn create_cite() -> Self {
4125 Self::create_node(NodeType::Cite)
4126 }
4127
4128 #[inline]
4136 pub fn create_cite_with_text<S: Into<AzString>>(text: S) -> Self {
4137 Self::create_cite().with_child(Self::create_text(text))
4138 }
4139
4140 #[inline]
4145 #[must_use] pub fn create_abbr() -> Self {
4146 Self::create_node(NodeType::Abbr)
4147 }
4148
4149 #[inline]
4158 #[must_use] pub fn create_abbr_with_title(abbr_text: AzString, title: AzString) -> Self {
4159 Self::create_node(NodeType::Abbr)
4160 .with_attribute(AttributeType::Title(title))
4161 .with_child(Self::create_text(abbr_text))
4162 }
4163
4164 #[inline]
4168 #[must_use] pub fn create_kbd() -> Self {
4169 Self::create_node(NodeType::Kbd)
4170 }
4171
4172 #[inline]
4180 pub fn create_kbd_with_text<S: Into<AzString>>(text: S) -> Self {
4181 Self::create_kbd().with_child(Self::create_text(text))
4182 }
4183
4184 #[inline]
4188 #[must_use] pub fn create_samp() -> Self {
4189 Self::create_node(NodeType::Samp)
4190 }
4191
4192 #[inline]
4199 pub fn create_samp_with_text<S: Into<AzString>>(text: S) -> Self {
4200 Self::create_samp().with_child(Self::create_text(text))
4201 }
4202
4203 #[inline]
4207 #[must_use] pub fn create_var() -> Self {
4208 Self::create_node(NodeType::Var)
4209 }
4210
4211 #[inline]
4218 pub fn create_var_with_text<S: Into<AzString>>(text: S) -> Self {
4219 Self::create_var().with_child(Self::create_text(text))
4220 }
4221
4222 #[inline]
4224 #[must_use] pub fn create_sub() -> Self {
4225 Self::create_node(NodeType::Sub)
4226 }
4227
4228 #[inline]
4235 pub fn create_sub_with_text<S: Into<AzString>>(text: S) -> Self {
4236 Self::create_sub().with_child(Self::create_text(text))
4237 }
4238
4239 #[inline]
4241 #[must_use] pub fn create_sup() -> Self {
4242 Self::create_node(NodeType::Sup)
4243 }
4244
4245 #[inline]
4252 pub fn create_sup_with_text<S: Into<AzString>>(text: S) -> Self {
4253 Self::create_sup().with_child(Self::create_text(text))
4254 }
4255
4256 #[inline]
4258 #[must_use] pub fn create_u() -> Self {
4259 Self::create_node(NodeType::U)
4260 }
4261
4262 #[inline]
4267 pub fn create_u_with_text<S: Into<AzString>>(text: S) -> Self {
4268 Self::create_u().with_child(Self::create_text(text))
4269 }
4270
4271 #[inline]
4273 #[must_use] pub fn create_s() -> Self {
4274 Self::create_node(NodeType::S)
4275 }
4276
4277 #[inline]
4282 pub fn create_s_with_text<S: Into<AzString>>(text: S) -> Self {
4283 Self::create_s().with_child(Self::create_text(text))
4284 }
4285
4286 #[inline]
4288 #[must_use] pub fn create_mark() -> Self {
4289 Self::create_node(NodeType::Mark)
4290 }
4291
4292 #[inline]
4297 pub fn create_mark_with_text<S: Into<AzString>>(text: S) -> Self {
4298 Self::create_mark().with_child(Self::create_text(text))
4299 }
4300
4301 #[inline]
4303 #[must_use] pub fn create_del() -> Self {
4304 Self::create_node(NodeType::Del)
4305 }
4306
4307 #[inline]
4312 pub fn create_del_with_text<S: Into<AzString>>(text: S) -> Self {
4313 Self::create_del().with_child(Self::create_text(text))
4314 }
4315
4316 #[inline]
4318 #[must_use] pub fn create_ins() -> Self {
4319 Self::create_node(NodeType::Ins)
4320 }
4321
4322 #[inline]
4327 pub fn create_ins_with_text<S: Into<AzString>>(text: S) -> Self {
4328 Self::create_ins().with_child(Self::create_text(text))
4329 }
4330
4331 #[inline]
4333 #[must_use] pub fn create_dfn() -> Self {
4334 Self::create_node(NodeType::Dfn)
4335 }
4336
4337 #[inline]
4342 pub fn create_dfn_with_text<S: Into<AzString>>(text: S) -> Self {
4343 Self::create_dfn().with_child(Self::create_text(text))
4344 }
4345
4346 #[inline]
4355 #[must_use] pub fn create_time(text: AzString, datetime: OptionString) -> Self {
4356 let mut element = Self::create_node(NodeType::Time).with_child(Self::create_text(text));
4357 if let OptionString::Some(dt) = datetime {
4358 element = element.with_attribute(AttributeType::Custom(AttributeNameValue {
4359 attr_name: "datetime".into(),
4360 value: dt,
4361 }));
4362 }
4363 element
4364 }
4365
4366 #[inline]
4370 #[must_use] pub fn create_bdo() -> Self {
4371 Self::create_node(NodeType::Bdo)
4372 }
4373
4374 #[inline]
4378 pub fn create_bdo_with_text<S: Into<AzString>>(text: S) -> Self {
4379 Self::create_bdo().with_child(Self::create_text(text))
4380 }
4381
4382 #[inline]
4388 #[must_use] pub fn create_b() -> Self {
4389 Self::create_node(NodeType::B)
4390 }
4391
4392 #[inline]
4399 pub fn create_b_with_text<S: Into<AzString>>(text: S) -> Self {
4400 Self::create_b().with_child(Self::create_text(text))
4401 }
4402
4403 #[inline]
4407 #[must_use] pub fn create_i() -> Self {
4408 Self::create_node(NodeType::I)
4409 }
4410
4411 #[inline]
4418 pub fn create_i_with_text<S: Into<AzString>>(text: S) -> Self {
4419 Self::create_i().with_child(Self::create_text(text))
4420 }
4421
4422 #[inline]
4426 #[must_use] pub fn create_small() -> Self {
4427 Self::create_node(NodeType::Small)
4428 }
4429
4430 #[inline]
4435 pub fn create_small_with_text<S: Into<AzString>>(text: S) -> Self {
4436 Self::create_small().with_child(Self::create_text(text))
4437 }
4438
4439 #[inline]
4443 #[must_use] pub fn create_big() -> Self {
4444 Self::create_node(NodeType::Big)
4445 }
4446
4447 #[inline]
4451 pub fn create_big_with_text<S: Into<AzString>>(text: S) -> Self {
4452 Self::create_big().with_child(Self::create_text(text))
4453 }
4454
4455 #[inline]
4460 #[must_use] pub fn create_bdi() -> Self {
4461 Self::create_node(NodeType::Bdi)
4462 }
4463
4464 #[inline]
4469 pub fn create_bdi_with_text<S: Into<AzString>>(text: S) -> Self {
4470 Self::create_bdi().with_child(Self::create_text(text))
4471 }
4472
4473 #[inline]
4478 #[must_use] pub fn create_wbr() -> Self {
4479 Self::create_node(NodeType::Wbr)
4480 }
4481
4482 #[inline]
4487 #[must_use] pub fn create_ruby() -> Self {
4488 Self::create_node(NodeType::Ruby)
4489 }
4490
4491 #[inline]
4495 #[must_use] pub fn create_rt() -> Self {
4496 Self::create_node(NodeType::Rt)
4497 }
4498
4499 #[inline]
4504 pub fn create_rt_with_text<S: Into<AzString>>(text: S) -> Self {
4505 Self::create_rt().with_child(Self::create_text(text))
4506 }
4507
4508 #[inline]
4512 #[must_use] pub fn create_rtc() -> Self {
4513 Self::create_node(NodeType::Rtc)
4514 }
4515
4516 #[inline]
4520 #[must_use] pub fn create_rp() -> Self {
4521 Self::create_node(NodeType::Rp)
4522 }
4523
4524 #[inline]
4529 pub fn create_rp_with_text<S: Into<AzString>>(text: S) -> Self {
4530 Self::create_rp().with_child(Self::create_text(text))
4531 }
4532
4533 #[inline]
4538 #[must_use] pub fn create_data(value: AzString) -> Self {
4539 Self::create_node(NodeType::Data).with_attribute(AttributeType::Value(value))
4540 }
4541
4542 #[inline]
4548 #[must_use] pub fn create_data_with_text(value: AzString, text: AzString) -> Self {
4549 Self::create_data(value).with_child(Self::create_text(text))
4550 }
4551
4552 #[inline]
4556 #[must_use] pub fn create_dir() -> Self {
4557 Self::create_node(NodeType::Dir)
4558 }
4559
4560 #[inline]
4564 #[must_use] pub fn create_svg() -> Self {
4565 Self::create_node(NodeType::Svg)
4566 }
4567
4568 #[inline]
4576 #[must_use] pub fn create_a_no_a11y(href: AzString, label: OptionString) -> Self {
4577 let mut link = Self::create_node(NodeType::A).with_attribute(AttributeType::Href(href));
4578 if let OptionString::Some(text) = label {
4579 link = link.with_child(Self::create_text(text));
4580 }
4581 link
4582 }
4583
4584 #[inline]
4592 #[must_use] pub fn create_button_no_a11y(text: AzString) -> Self {
4593 Self::create_node(NodeType::Button).with_child(Self::create_text(text))
4594 }
4595
4596 #[inline]
4604 #[must_use] pub fn create_label_no_a11y(for_id: AzString, text: AzString) -> Self {
4605 Self::create_node(NodeType::Label)
4606 .with_attribute(AttributeType::Custom(AttributeNameValue {
4607 attr_name: "for".into(),
4608 value: for_id,
4609 }))
4610 .with_child(Self::create_text(text))
4611 }
4612
4613 #[inline]
4623 #[must_use] pub fn create_input_no_a11y(input_type: AzString, name: AzString, label: AzString) -> Self {
4624 Self::create_node(NodeType::Input)
4625 .with_attribute(AttributeType::InputType(input_type))
4626 .with_attribute(AttributeType::Name(name))
4627 .with_attribute(AttributeType::AriaLabel(label))
4628 }
4629
4630 #[inline]
4639 #[must_use] pub fn create_textarea_no_a11y(name: AzString, label: AzString) -> Self {
4640 Self::create_node(NodeType::TextArea)
4641 .with_attribute(AttributeType::Name(name))
4642 .with_attribute(AttributeType::AriaLabel(label))
4643 }
4644
4645 #[inline]
4654 #[must_use] pub fn create_select_no_a11y(name: AzString, label: AzString) -> Self {
4655 Self::create_node(NodeType::Select)
4656 .with_attribute(AttributeType::Name(name))
4657 .with_attribute(AttributeType::AriaLabel(label))
4658 }
4659
4660 #[inline]
4666 #[must_use] pub fn create_option_no_a11y(value: AzString, text: AzString) -> Self {
4667 Self::create_node(NodeType::SelectOption)
4668 .with_attribute(AttributeType::Value(value))
4669 .with_child(Self::create_text(text))
4670 }
4671
4672 #[inline]
4681 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_option(value: AzString, text: AzString, aria: SmallAriaInfo) -> Self {
4683 Self::create_option_no_a11y(value, text).with_accessibility_info(aria.to_full_info())
4684 }
4685
4686 #[inline]
4691 #[must_use] pub fn create_ul() -> Self {
4692 Self::create_node(NodeType::Ul)
4693 }
4694
4695 #[inline]
4700 #[must_use] pub fn create_ol() -> Self {
4701 Self::create_node(NodeType::Ol)
4702 }
4703
4704 #[inline]
4709 #[must_use] pub fn create_li() -> Self {
4710 Self::create_node(NodeType::Li)
4711 }
4712
4713 #[inline]
4718 #[must_use] pub fn create_table_no_a11y() -> Self {
4719 Self::create_node(NodeType::Table)
4720 }
4721
4722 #[inline]
4726 #[must_use] pub fn create_caption() -> Self {
4727 Self::create_node(NodeType::Caption)
4728 }
4729
4730 #[inline]
4734 #[must_use] pub fn create_thead() -> Self {
4735 Self::create_node(NodeType::THead)
4736 }
4737
4738 #[inline]
4742 #[must_use] pub fn create_tbody() -> Self {
4743 Self::create_node(NodeType::TBody)
4744 }
4745
4746 #[inline]
4750 #[must_use] pub fn create_tfoot() -> Self {
4751 Self::create_node(NodeType::TFoot)
4752 }
4753
4754 #[inline]
4756 #[must_use] pub fn create_tr() -> Self {
4757 Self::create_node(NodeType::Tr)
4758 }
4759
4760 #[inline]
4765 #[must_use] pub fn create_th() -> Self {
4766 Self::create_node(NodeType::Th)
4767 }
4768
4769 #[inline]
4771 #[must_use] pub fn create_td() -> Self {
4772 Self::create_node(NodeType::Td)
4773 }
4774
4775 #[inline]
4779 #[must_use] pub fn create_form_no_a11y() -> Self {
4780 Self::create_node(NodeType::Form)
4781 }
4782
4783 #[inline]
4790 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_form(aria: SmallAriaInfo) -> Self {
4792 Self::create_form_no_a11y().with_accessibility_info(aria.to_full_info())
4793 }
4794
4795 #[inline]
4799 #[must_use] pub fn create_fieldset_no_a11y() -> Self {
4800 Self::create_node(NodeType::FieldSet)
4801 }
4802
4803 #[inline]
4811 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_fieldset(aria: SmallAriaInfo) -> Self {
4813 Self::create_fieldset_no_a11y().with_accessibility_info(aria.to_full_info())
4814 }
4815
4816 #[inline]
4820 #[must_use] pub fn create_legend_no_a11y() -> Self {
4821 Self::create_node(NodeType::Legend)
4822 }
4823
4824 #[inline]
4831 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_legend(aria: SmallAriaInfo) -> Self {
4833 Self::create_legend_no_a11y().with_accessibility_info(aria.to_full_info())
4834 }
4835
4836 #[inline]
4841 #[must_use] pub fn create_hr() -> Self {
4842 Self::create_node(NodeType::Hr)
4843 }
4844
4845 #[inline]
4852 #[must_use] pub const fn create_address() -> Self {
4853 Self {
4854 root: NodeData::create_node(NodeType::Address),
4855 children: DomVec::from_const_slice(&[]),
4856 css: azul_css::css::CssVec::from_const_slice(&[]),
4857 estimated_total_children: 0,
4858 }
4859 }
4860
4861 #[inline]
4865 #[must_use] pub const fn create_dl() -> Self {
4866 Self {
4867 root: NodeData::create_node(NodeType::Dl),
4868 children: DomVec::from_const_slice(&[]),
4869 css: azul_css::css::CssVec::from_const_slice(&[]),
4870 estimated_total_children: 0,
4871 }
4872 }
4873
4874 #[inline]
4878 #[must_use] pub const fn create_dt() -> Self {
4879 Self {
4880 root: NodeData::create_node(NodeType::Dt),
4881 children: DomVec::from_const_slice(&[]),
4882 css: azul_css::css::CssVec::from_const_slice(&[]),
4883 estimated_total_children: 0,
4884 }
4885 }
4886
4887 #[inline]
4891 #[must_use] pub const fn create_dd() -> Self {
4892 Self {
4893 root: NodeData::create_node(NodeType::Dd),
4894 children: DomVec::from_const_slice(&[]),
4895 css: azul_css::css::CssVec::from_const_slice(&[]),
4896 estimated_total_children: 0,
4897 }
4898 }
4899
4900 #[inline]
4902 #[must_use] pub const fn create_colgroup() -> Self {
4903 Self {
4904 root: NodeData::create_node(NodeType::ColGroup),
4905 children: DomVec::from_const_slice(&[]),
4906 css: azul_css::css::CssVec::from_const_slice(&[]),
4907 estimated_total_children: 0,
4908 }
4909 }
4910
4911 #[inline]
4913 #[must_use] pub fn create_col(span: i32) -> Self {
4914 Self::create_node(NodeType::Col).with_attribute(AttributeType::ColSpan(span))
4915 }
4916
4917 #[inline]
4924 #[must_use] pub fn create_optgroup_no_a11y(label: AzString) -> Self {
4925 Self::create_node(NodeType::OptGroup).with_attribute(AttributeType::AriaLabel(label))
4926 }
4927
4928 #[inline]
4936 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_optgroup(label: AzString, aria: SmallAriaInfo) -> Self {
4938 Self::create_optgroup_no_a11y(label).with_accessibility_info(aria.to_full_info())
4939 }
4940
4941 #[inline]
4945 #[must_use] pub const fn create_q() -> Self {
4946 Self {
4947 root: NodeData::create_node(NodeType::Q),
4948 children: DomVec::from_const_slice(&[]),
4949 css: azul_css::css::CssVec::from_const_slice(&[]),
4950 estimated_total_children: 0,
4951 }
4952 }
4953
4954 #[inline]
4958 #[must_use] pub const fn create_acronym() -> Self {
4959 Self {
4960 root: NodeData::create_node(NodeType::Acronym),
4961 children: DomVec::from_const_slice(&[]),
4962 css: azul_css::css::CssVec::from_const_slice(&[]),
4963 estimated_total_children: 0,
4964 }
4965 }
4966
4967 #[inline]
4971 pub fn create_acronym_with_text<S: Into<AzString>>(text: S) -> Self {
4972 Self::create_acronym().with_child(Self::create_text(text))
4973 }
4974
4975 #[inline]
4979 #[must_use] pub const fn create_menu_no_a11y() -> Self {
4980 Self {
4981 root: NodeData::create_node(NodeType::Menu),
4982 children: DomVec::from_const_slice(&[]),
4983 css: azul_css::css::CssVec::from_const_slice(&[]),
4984 estimated_total_children: 0,
4985 }
4986 }
4987
4988 #[inline]
4995 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_menu(aria: SmallAriaInfo) -> Self {
4997 Self::create_menu_no_a11y().with_accessibility_info(aria.to_full_info())
4998 }
4999
5000 #[inline]
5004 #[must_use] pub const fn create_menuitem_no_a11y() -> Self {
5005 Self {
5006 root: NodeData::create_node(NodeType::MenuItem),
5007 children: DomVec::from_const_slice(&[]),
5008 css: azul_css::css::CssVec::from_const_slice(&[]),
5009 estimated_total_children: 0,
5010 }
5011 }
5012
5013 #[inline]
5020 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_menuitem(aria: SmallAriaInfo) -> Self {
5022 Self::create_menuitem_no_a11y().with_accessibility_info(aria.to_full_info())
5023 }
5024
5025 #[inline]
5030 pub fn create_menuitem_with_text_no_a11y<S: Into<AzString>>(text: S) -> Self {
5031 Self::create_menuitem_no_a11y().with_child(Self::create_text(text))
5032 }
5033
5034 #[inline]
5041 #[allow(clippy::needless_pass_by_value)] pub fn create_menuitem_with_text<S: Into<AzString>>(text: S, aria: SmallAriaInfo) -> Self {
5043 Self::create_menuitem_with_text_no_a11y(text).with_accessibility_info(aria.to_full_info())
5044 }
5045
5046 #[inline]
5051 #[must_use] pub const fn create_output_no_a11y() -> Self {
5052 Self {
5053 root: NodeData::create_node(NodeType::Output),
5054 children: DomVec::from_const_slice(&[]),
5055 css: azul_css::css::CssVec::from_const_slice(&[]),
5056 estimated_total_children: 0,
5057 }
5058 }
5059
5060 #[inline]
5067 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_output(aria: SmallAriaInfo) -> Self {
5069 Self::create_output_no_a11y().with_accessibility_info(aria.to_full_info())
5070 }
5071
5072 #[inline]
5080 #[must_use] pub fn create_progress_no_a11y(value: f32, max: f32) -> Self {
5081 Self::create_node(NodeType::Progress)
5082 .with_attribute(AttributeType::Custom(AttributeNameValue {
5083 attr_name: "value".into(),
5084 value: value.to_string().into(),
5085 }))
5086 .with_attribute(AttributeType::Custom(AttributeNameValue {
5087 attr_name: "max".into(),
5088 value: max.to_string().into(),
5089 }))
5090 }
5091
5092 #[inline]
5100 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_progress(aria: ProgressAriaInfo) -> Self {
5102 let mut node = Self::create_node(NodeType::Progress);
5103 if !aria.indeterminate {
5104 if let azul_css::OptionF32::Some(v) = aria.current_value {
5105 node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
5106 attr_name: "value".into(),
5107 value: v.to_string().into(),
5108 }));
5109 }
5110 }
5111 if let azul_css::OptionF32::Some(m) = aria.max {
5112 node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
5113 attr_name: "max".into(),
5114 value: m.to_string().into(),
5115 }));
5116 }
5117 node.with_accessibility_info(aria.to_full_info())
5118 }
5119
5120 #[inline]
5129 #[must_use] pub fn create_meter_no_a11y(value: f32, min: f32, max: f32) -> Self {
5130 Self::create_node(NodeType::Meter)
5131 .with_attribute(AttributeType::Custom(AttributeNameValue {
5132 attr_name: "value".into(),
5133 value: value.to_string().into(),
5134 }))
5135 .with_attribute(AttributeType::Custom(AttributeNameValue {
5136 attr_name: "min".into(),
5137 value: min.to_string().into(),
5138 }))
5139 .with_attribute(AttributeType::Custom(AttributeNameValue {
5140 attr_name: "max".into(),
5141 value: max.to_string().into(),
5142 }))
5143 }
5144
5145 #[inline]
5153 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_meter(aria: MeterAriaInfo) -> Self {
5155 let mut node = Self::create_meter_no_a11y(aria.current_value, aria.min, aria.max);
5156 if let azul_css::OptionF32::Some(v) = aria.low {
5157 node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
5158 attr_name: "low".into(),
5159 value: v.to_string().into(),
5160 }));
5161 }
5162 if let azul_css::OptionF32::Some(v) = aria.high {
5163 node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
5164 attr_name: "high".into(),
5165 value: v.to_string().into(),
5166 }));
5167 }
5168 if let azul_css::OptionF32::Some(v) = aria.optimum {
5169 node = node.with_attribute(AttributeType::Custom(AttributeNameValue {
5170 attr_name: "optimum".into(),
5171 value: v.to_string().into(),
5172 }));
5173 }
5174 node.with_accessibility_info(aria.to_full_info())
5175 }
5176
5177 #[inline]
5181 #[must_use] pub const fn create_datalist_no_a11y() -> Self {
5182 Self {
5183 root: NodeData::create_node(NodeType::DataList),
5184 children: DomVec::from_const_slice(&[]),
5185 css: azul_css::css::CssVec::from_const_slice(&[]),
5186 estimated_total_children: 0,
5187 }
5188 }
5189
5190 #[inline]
5197 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_datalist(aria: SmallAriaInfo) -> Self {
5199 Self::create_datalist_no_a11y().with_accessibility_info(aria.to_full_info())
5200 }
5201
5202 #[inline]
5209 #[must_use] pub const fn create_canvas_no_a11y() -> Self {
5210 Self {
5211 root: NodeData::create_node(NodeType::Canvas),
5212 children: DomVec::from_const_slice(&[]),
5213 css: azul_css::css::CssVec::from_const_slice(&[]),
5214 estimated_total_children: 0,
5215 }
5216 }
5217
5218 #[inline]
5226 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_canvas(aria: SmallAriaInfo) -> Self {
5228 Self::create_canvas_no_a11y().with_accessibility_info(aria.to_full_info())
5229 }
5230
5231 #[inline]
5235 #[must_use] pub const fn create_object() -> Self {
5236 Self {
5237 root: NodeData::create_node(NodeType::Object),
5238 children: DomVec::from_const_slice(&[]),
5239 css: azul_css::css::CssVec::from_const_slice(&[]),
5240 estimated_total_children: 0,
5241 }
5242 }
5243
5244 #[inline]
5250 #[must_use] pub fn create_param(name: AzString, value: AzString) -> Self {
5251 Self::create_node(NodeType::Param)
5252 .with_attribute(AttributeType::Name(name))
5253 .with_attribute(AttributeType::Value(value))
5254 }
5255
5256 #[inline]
5261 #[must_use] pub const fn create_embed() -> Self {
5262 Self {
5263 root: NodeData::create_node(NodeType::Embed),
5264 children: DomVec::from_const_slice(&[]),
5265 css: azul_css::css::CssVec::from_const_slice(&[]),
5266 estimated_total_children: 0,
5267 }
5268 }
5269
5270 #[inline]
5274 #[must_use] pub const fn create_audio_no_a11y() -> Self {
5275 Self {
5276 root: NodeData::create_node(NodeType::Audio),
5277 children: DomVec::from_const_slice(&[]),
5278 css: azul_css::css::CssVec::from_const_slice(&[]),
5279 estimated_total_children: 0,
5280 }
5281 }
5282
5283 #[inline]
5290 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_audio(aria: SmallAriaInfo) -> Self {
5292 Self::create_audio_no_a11y().with_accessibility_info(aria.to_full_info())
5293 }
5294
5295 #[inline]
5299 #[must_use] pub const fn create_video_no_a11y() -> Self {
5300 Self {
5301 root: NodeData::create_node(NodeType::Video),
5302 children: DomVec::from_const_slice(&[]),
5303 css: azul_css::css::CssVec::from_const_slice(&[]),
5304 estimated_total_children: 0,
5305 }
5306 }
5307
5308 #[inline]
5315 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_video(aria: SmallAriaInfo) -> Self {
5317 Self::create_video_no_a11y().with_accessibility_info(aria.to_full_info())
5318 }
5319
5320 #[inline]
5326 #[must_use] pub fn create_source(src: AzString, media_type: AzString) -> Self {
5327 Self::create_node(NodeType::Source)
5328 .with_attribute(AttributeType::Src(src))
5329 .with_attribute(AttributeType::Custom(AttributeNameValue {
5330 attr_name: "type".into(),
5331 value: media_type,
5332 }))
5333 }
5334
5335 #[inline]
5344 #[must_use] pub fn create_track(src: AzString, kind: AzString) -> Self {
5345 Self::create_node(NodeType::Track)
5346 .with_attribute(AttributeType::Src(src))
5347 .with_attribute(AttributeType::Custom(AttributeNameValue {
5348 attr_name: "kind".into(),
5349 value: kind,
5350 }))
5351 }
5352
5353 #[inline]
5357 #[must_use] pub const fn create_map() -> Self {
5358 Self {
5359 root: NodeData::create_node(NodeType::Map),
5360 children: DomVec::from_const_slice(&[]),
5361 css: azul_css::css::CssVec::from_const_slice(&[]),
5362 estimated_total_children: 0,
5363 }
5364 }
5365
5366 #[inline]
5370 #[must_use] pub const fn create_area_no_a11y() -> Self {
5371 Self {
5372 root: NodeData::create_node(NodeType::Area),
5373 children: DomVec::from_const_slice(&[]),
5374 css: azul_css::css::CssVec::from_const_slice(&[]),
5375 estimated_total_children: 0,
5376 }
5377 }
5378
5379 #[inline]
5386 #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn create_area(aria: SmallAriaInfo) -> Self {
5388 Self::create_area_no_a11y().with_accessibility_info(aria.to_full_info())
5389 }
5390
5391 #[inline]
5397 #[must_use] pub fn create_title() -> Self {
5398 Self::create_node(NodeType::Title)
5399 }
5400
5401 #[inline]
5406 pub fn create_title_with_text<S: Into<AzString>>(text: S) -> Self {
5407 Self::create_title().with_child(Self::create_text(text))
5408 }
5409
5410 #[inline]
5414 #[must_use] pub const fn create_meta() -> Self {
5415 Self {
5416 root: NodeData::create_node(NodeType::Meta),
5417 children: DomVec::from_const_slice(&[]),
5418 css: azul_css::css::CssVec::from_const_slice(&[]),
5419 estimated_total_children: 0,
5420 }
5421 }
5422
5423 #[inline]
5428 #[must_use] pub const fn create_link() -> Self {
5429 Self {
5430 root: NodeData::create_node(NodeType::Link),
5431 children: DomVec::from_const_slice(&[]),
5432 css: azul_css::css::CssVec::from_const_slice(&[]),
5433 estimated_total_children: 0,
5434 }
5435 }
5436
5437 #[inline]
5442 #[must_use] pub const fn create_script() -> Self {
5443 Self {
5444 root: NodeData::create_node(NodeType::Script),
5445 children: DomVec::from_const_slice(&[]),
5446 css: azul_css::css::CssVec::from_const_slice(&[]),
5447 estimated_total_children: 0,
5448 }
5449 }
5450
5451 #[inline]
5456 #[must_use] pub const fn create_style() -> Self {
5457 Self {
5458 root: NodeData::create_node(NodeType::Style),
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]
5470 pub fn create_style_with_text<S: Into<AzString>>(text: S) -> Self {
5471 Self::create_style().with_child(Self::create_text(text))
5472 }
5473
5474 #[inline]
5479 #[must_use] pub fn create_base(href: AzString) -> Self {
5480 Self::create_node(NodeType::Base).with_attribute(AttributeType::Href(href))
5481 }
5482
5483 #[inline]
5493 #[must_use] pub fn create_th_with_scope(scope: AzString, text: AzString) -> Self {
5494 Self::create_node(NodeType::Th)
5495 .with_attribute(AttributeType::Scope(scope))
5496 .with_child(Self::create_text(text))
5497 }
5498
5499 #[inline]
5504 pub fn create_td_with_text<S: Into<AzString>>(text: S) -> Self {
5505 Self::create_td().with_child(Self::create_text(text))
5506 }
5507
5508 #[inline]
5513 pub fn create_th_with_text<S: Into<AzString>>(text: S) -> Self {
5514 Self::create_th().with_child(Self::create_text(text))
5515 }
5516
5517 #[inline]
5522 pub fn create_li_with_text<S: Into<AzString>>(text: S) -> Self {
5523 Self::create_li().with_child(Self::create_text(text))
5524 }
5525
5526 #[inline]
5531 pub fn create_p_with_text<S: Into<AzString>>(text: S) -> Self {
5532 Self::create_p().with_child(Self::create_text(text))
5533 }
5534
5535 #[inline]
5547 #[allow(clippy::needless_pass_by_value)] pub fn create_button<S: Into<AzString>>(text: S, aria: SmallAriaInfo) -> Self {
5549 let mut btn = Self::create_button_no_a11y(text.into());
5550 btn.root.set_accessibility_info(aria.to_full_info());
5551 btn
5552 }
5553
5554 #[inline]
5564 #[allow(clippy::needless_pass_by_value)] pub fn create_a<S1: Into<AzString>, S2: Into<AzString>>(
5566 href: S1,
5567 text: S2,
5568 aria: SmallAriaInfo,
5569 ) -> Self {
5570 let mut link = Self::create_a_no_a11y(href.into(), OptionString::Some(text.into()));
5571 link.root.set_accessibility_info(aria.to_full_info());
5572 link
5573 }
5574
5575 #[inline]
5585 #[allow(clippy::needless_pass_by_value)] pub fn create_input<S1: Into<AzString>, S2: Into<AzString>, S3: Into<AzString>>(
5587 input_type: S1,
5588 name: S2,
5589 label: S3,
5590 aria: SmallAriaInfo,
5591 ) -> Self {
5592 let mut input = Self::create_input_no_a11y(input_type.into(), name.into(), label.into());
5593 input.root.set_accessibility_info(aria.to_full_info());
5594 input
5595 }
5596
5597 #[inline]
5606 #[allow(clippy::needless_pass_by_value)] pub fn create_textarea<S1: Into<AzString>, S2: Into<AzString>>(
5608 name: S1,
5609 label: S2,
5610 aria: SmallAriaInfo,
5611 ) -> Self {
5612 let mut textarea = Self::create_textarea_no_a11y(name.into(), label.into());
5613 textarea.root.set_accessibility_info(aria.to_full_info());
5614 textarea
5615 }
5616
5617 #[inline]
5626 #[allow(clippy::needless_pass_by_value)] pub fn create_select<S1: Into<AzString>, S2: Into<AzString>>(
5628 name: S1,
5629 label: S2,
5630 aria: SmallAriaInfo,
5631 ) -> Self {
5632 let mut select = Self::create_select_no_a11y(name.into(), label.into());
5633 select.root.set_accessibility_info(aria.to_full_info());
5634 select
5635 }
5636
5637 #[inline]
5646 #[allow(clippy::needless_pass_by_value)] pub fn create_table<S: Into<AzString>>(caption: S, aria: SmallAriaInfo) -> Self {
5648 let mut table = Self::create_table_no_a11y()
5649 .with_child(Self::create_caption().with_child(Self::create_text(caption)));
5650 table.root.set_accessibility_info(aria.to_full_info());
5651 table
5652 }
5653
5654 #[inline]
5663 #[allow(clippy::needless_pass_by_value)] pub fn create_label<S1: Into<AzString>, S2: Into<AzString>>(
5665 for_id: S1,
5666 text: S2,
5667 aria: SmallAriaInfo,
5668 ) -> Self {
5669 let mut label = Self::create_label_no_a11y(for_id.into(), text.into());
5670 label.root.set_accessibility_info(aria.to_full_info());
5671 label
5672 }
5673
5674 #[cfg(feature = "xml")]
5680 pub fn from_xml<S: AsRef<str>>(xml_str: S) -> Self {
5681 Self::create_text(format!(
5684 "XML content loaded ({} bytes)",
5685 xml_str.as_ref().len()
5686 ))
5687 }
5688
5689 #[cfg(not(feature = "xml"))]
5691 pub fn from_xml<S: AsRef<str>>(xml_str: S) -> Self {
5692 Self::create_text(format!(
5693 "XML parsing requires 'xml' feature ({} bytes)",
5694 xml_str.as_ref().len()
5695 ))
5696 }
5697
5698 #[inline]
5700 #[must_use]
5701 pub const fn swap_with_default(&mut self) -> Self {
5702 let mut s = Self {
5703 root: NodeData::create_div(),
5704 children: DomVec::from_const_slice(&[]),
5705 css: azul_css::css::CssVec::from_const_slice(&[]),
5706 estimated_total_children: 0,
5707 };
5708 mem::swap(&mut s, self);
5709 s
5710 }
5711
5712 #[must_use]
5719 pub fn recompute_estimated_total_children(&self) -> usize {
5720 self.children
5721 .iter()
5722 .map(|c| c.recompute_estimated_total_children() + 1)
5723 .sum()
5724 }
5725
5726 #[inline]
5727 pub fn add_child(&mut self, child: Self) {
5728 debug_assert_eq!(
5735 child.estimated_total_children,
5736 child
5737 .children
5738 .iter()
5739 .map(|c| c.estimated_total_children + 1)
5740 .sum::<usize>(),
5741 "Dom.estimated_total_children desynced for added child; call \
5742 fixup_children_estimated() after mutating `children` directly",
5743 );
5744 let estimated = child.estimated_total_children;
5745 let mut v: DomVec = Vec::new().into();
5746 mem::swap(&mut v, &mut self.children);
5747 let mut v = v.into_library_owned_vec();
5748 v.push(child);
5749 self.children = v.into();
5750 self.estimated_total_children += estimated + 1;
5751 }
5752
5753 #[inline]
5754 pub fn set_children(&mut self, children: DomVec) {
5755 debug_assert!(
5759 children.iter().all(|c| c.estimated_total_children
5760 == c
5761 .children
5762 .iter()
5763 .map(|g| g.estimated_total_children + 1)
5764 .sum::<usize>()),
5765 "Dom.estimated_total_children desynced in set_children; a child's own \
5766 estimate was stale — call fixup_children_estimated() first",
5767 );
5768 let children_estimated = children
5769 .iter()
5770 .map(|s| s.estimated_total_children + 1)
5771 .sum();
5772 self.children = children;
5773 self.estimated_total_children = children_estimated;
5774 }
5775
5776 #[must_use]
5777 pub fn copy_except_for_root(&mut self) -> Self {
5778 Self {
5779 root: self.root.copy_special(),
5780 children: self.children.clone(),
5781 css: self.css.clone(),
5782 estimated_total_children: self.estimated_total_children,
5783 }
5784 }
5785 #[must_use] pub const fn node_count(&self) -> usize {
5786 self.estimated_total_children + 1
5787 }
5788
5789 pub fn add_component_css(&mut self, css: azul_css::css::Css) {
5795 let mut v = Vec::new().into();
5796 mem::swap(&mut v, &mut self.css);
5797 let mut v: Vec<azul_css::css::Css> = v.into_library_owned_vec();
5798 v.push(css);
5799 self.css = v.into();
5800 }
5801
5802 pub fn set_component_css(&mut self, css: azul_css::css::CssVec) {
5806 self.css = css;
5807 }
5808 #[inline]
5809 #[must_use] pub fn with_children(mut self, children: DomVec) -> Self {
5810 self.set_children(children);
5811 self
5812 }
5813 #[inline]
5814 #[must_use] pub fn with_child(mut self, child: Self) -> Self {
5815 self.add_child(child);
5816 self
5817 }
5818 #[inline]
5819 #[must_use] pub fn with_node_type(mut self, node_type: NodeType) -> Self {
5820 self.root.set_node_type(node_type);
5821 self
5822 }
5823 #[inline]
5824 #[must_use] pub fn with_id(mut self, id: AzString) -> Self {
5825 self.root.add_id(id);
5826 self
5827 }
5828 #[inline]
5829 #[must_use] pub fn with_class(mut self, class: AzString) -> Self {
5830 self.root.add_class(class);
5831 self
5832 }
5833 #[inline]
5834 #[must_use]
5835 pub fn with_callback<C: Into<CoreCallback>>(
5836 mut self,
5837 event: EventFilter,
5838 data: RefAny,
5839 callback: C,
5840 ) -> Self {
5841 self.root.add_callback(event, data, callback);
5842 self
5843 }
5844 #[inline]
5846 #[must_use] pub fn with_css_property(mut self, prop: CssPropertyWithConditions) -> Self {
5847 self.root.add_css_property(prop);
5848 self
5849 }
5850 #[inline]
5852 pub fn add_css_property(&mut self, prop: CssPropertyWithConditions) {
5853 self.root.add_css_property(prop);
5854 }
5855 #[inline]
5856 pub fn add_class(&mut self, class: AzString) {
5857 self.root.add_class(class);
5858 }
5859 #[inline]
5860 pub fn add_callback<C: Into<CoreCallback>>(
5861 &mut self,
5862 event: EventFilter,
5863 data: RefAny,
5864 callback: C,
5865 ) {
5866 self.root.add_callback(event, data, callback);
5867 }
5868 #[inline]
5869 pub const fn set_tab_index(&mut self, tab_index: TabIndex) {
5870 self.root.set_tab_index(tab_index);
5871 }
5872 #[inline]
5873 pub const fn set_contenteditable(&mut self, contenteditable: bool) {
5874 self.root.set_contenteditable(contenteditable);
5875 }
5876 #[inline]
5877 #[must_use] pub const fn with_tab_index(mut self, tab_index: TabIndex) -> Self {
5878 self.root.set_tab_index(tab_index);
5879 self
5880 }
5881 #[inline]
5882 #[must_use] pub const fn with_contenteditable(mut self, contenteditable: bool) -> Self {
5883 self.root.set_contenteditable(contenteditable);
5884 self
5885 }
5886 #[inline]
5887 #[must_use] pub fn with_dataset(mut self, data: OptionRefAny) -> Self {
5888 self.root.set_dataset(data);
5889 self
5890 }
5891 #[inline]
5892 #[must_use] pub fn with_ids_and_classes(mut self, ids_and_classes: IdOrClassVec) -> Self {
5893 self.root.set_ids_and_classes(ids_and_classes);
5894 self
5895 }
5896
5897 #[inline]
5899 #[must_use] pub fn with_attribute(mut self, attr: AttributeType) -> Self {
5900 let mut attrs = self.root.attributes().clone();
5901 let mut v = attrs.into_library_owned_vec();
5902 v.push(attr);
5903 self.root.set_attributes(v.into());
5904 self
5905 }
5906
5907 #[inline]
5909 #[must_use] pub fn with_attributes(mut self, attributes: AttributeTypeVec) -> Self {
5910 self.root.set_attributes(attributes);
5911 self
5912 }
5913
5914 #[inline]
5915 #[must_use] pub fn with_callbacks(mut self, callbacks: CoreCallbackDataVec) -> Self {
5916 self.root.callbacks = callbacks;
5917 self
5918 }
5919 #[inline]
5922 #[must_use] pub fn with_css_props(mut self, css_props: CssPropertyWithConditionsVec) -> Self {
5923 self.root.style = css_props.into();
5924 self
5925 }
5926 #[inline]
5928 #[must_use] pub fn with_style(mut self, style: azul_css::css::Css) -> Self {
5929 self.root.style = style;
5930 self
5931 }
5932
5933 #[inline]
5945 #[must_use]
5946 pub fn with_key<K: Hash>(mut self, key: K) -> Self {
5947 self.root.set_key(key);
5948 self
5949 }
5950
5951 #[inline]
5960 #[must_use]
5961 pub fn with_merge_callback<C: Into<DatasetMergeCallback>>(mut self, callback: C) -> Self {
5962 self.root.set_merge_callback(callback);
5963 self
5964 }
5965
5966 pub fn set_css(&mut self, style: &str) {
5995 self.add_component_css(azul_css::css::Css::parse_inline(style));
6003 }
6004
6005 #[must_use] pub fn with_css(mut self, style: &str) -> Self {
6007 self.set_css(style);
6008 self
6009 }
6010
6011 #[inline]
6013 pub fn set_context_menu(&mut self, context_menu: Menu) {
6014 self.root.set_context_menu(context_menu);
6015 }
6016
6017 #[inline]
6018 #[must_use] pub fn with_context_menu(mut self, context_menu: Menu) -> Self {
6019 self.set_context_menu(context_menu);
6020 self
6021 }
6022
6023 #[inline]
6025 pub fn set_menu_bar(&mut self, menu_bar: Menu) {
6026 self.root.set_menu_bar(menu_bar);
6027 }
6028
6029 #[inline]
6030 #[must_use] pub fn with_menu_bar(mut self, menu_bar: Menu) -> Self {
6031 self.set_menu_bar(menu_bar);
6032 self
6033 }
6034
6035 #[inline]
6036 #[must_use] pub fn with_clip_mask(mut self, clip_mask: ImageMask) -> Self {
6037 self.root.set_clip_mask(clip_mask);
6038 self
6039 }
6040
6041 #[inline]
6042 #[must_use] pub fn with_svg_clip_path(mut self, clip: crate::svg::SvgMultiPolygon) -> Self {
6043 self.root.set_svg_data(SvgNodeData::Path(clip));
6044 self
6045 }
6046
6047 #[inline]
6048 #[must_use] pub fn with_svg_data(mut self, data: SvgNodeData) -> Self {
6049 self.root.set_svg_data(data);
6050 self
6051 }
6052
6053 #[inline]
6054 #[must_use] pub fn with_accessibility_info(mut self, accessibility_info: AccessibilityInfo) -> Self {
6055 self.root.set_accessibility_info(accessibility_info);
6056 self
6057 }
6058
6059 pub fn fixup_children_estimated(&mut self) -> usize {
6060 if self.children.is_empty() {
6061 self.estimated_total_children = 0;
6062 } else {
6063 self.estimated_total_children = self
6064 .children
6065 .iter_mut()
6066 .map(|s| s.fixup_children_estimated() + 1)
6067 .sum();
6068 }
6069 self.estimated_total_children
6070 }
6071}
6072
6073impl core::iter::FromIterator<Self> for Dom {
6074 fn from_iter<I: IntoIterator<Item = Self>>(iter: I) -> Self {
6075 let mut estimated_total_children = 0;
6076 let children = iter
6077 .into_iter()
6078 .inspect(|c| {
6079 estimated_total_children += c.estimated_total_children + 1;
6080 })
6081 .collect::<Vec<Self>>();
6082
6083 Self {
6084 root: NodeData::create_div(),
6085 children: children.into(),
6086 css: azul_css::css::CssVec::from_const_slice(&[]),
6087 estimated_total_children,
6088 }
6089 }
6090}
6091
6092impl fmt::Debug for Dom {
6093 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6094 fn print_dom(d: &Dom, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6095 write!(f, "Dom {{\r\n")?;
6096 write!(f, "\troot: {:#?}\r\n", d.root)?;
6097 write!(
6098 f,
6099 "\testimated_total_children: {:#?}\r\n",
6100 d.estimated_total_children
6101 )?;
6102 write!(f, "\tchildren: [\r\n")?;
6103 for c in &d.children {
6104 print_dom(c, f)?;
6105 }
6106 write!(f, "\t]\r\n")?;
6107 write!(f, "}}\r\n")?;
6108 Ok(())
6109 }
6110
6111 print_dom(self, f)
6112 }
6113}
6114
6115#[cfg(test)]
6116mod audit_tests {
6117 use super::*;
6118
6119 #[test]
6120 fn node_count_matches_recompute() {
6121 let dom = Dom::create_div()
6123 .with_child(Dom::create_div().with_child(Dom::create_div()))
6124 .with_child(Dom::create_div());
6125 assert_eq!(
6126 dom.estimated_total_children,
6127 dom.recompute_estimated_total_children()
6128 );
6129 assert_eq!(dom.estimated_total_children, 3);
6130 assert_eq!(dom.node_count(), 4);
6131 }
6132
6133 #[test]
6134 fn single_node_dom_node_count() {
6135 let dom = Dom::create_div();
6136 assert_eq!(dom.estimated_total_children, 0);
6137 assert_eq!(dom.node_count(), 1);
6138 assert_eq!(dom.recompute_estimated_total_children(), 0);
6139 }
6140
6141 #[test]
6142 fn fixup_repairs_desynced_estimate() {
6143 let mut dom = Dom::create_div().with_child(Dom::create_div());
6144 dom.estimated_total_children = 999;
6146 let repaired = dom.fixup_children_estimated();
6147 assert_eq!(repaired, 1);
6148 assert_eq!(
6149 dom.estimated_total_children,
6150 dom.recompute_estimated_total_children()
6151 );
6152 }
6153
6154 #[cfg(debug_assertions)]
6156 #[test]
6157 #[should_panic(expected = "desynced")]
6158 fn add_child_with_stale_estimate_panics_in_debug() {
6159 let mut child = Dom::create_div().with_child(Dom::create_div());
6160 child.estimated_total_children = 0; let mut parent = Dom::create_div();
6162 parent.add_child(child);
6163 }
6164
6165 #[test]
6169 fn node_data_is_send() {
6170 fn assert_send<T: Send>() {}
6171 assert_send::<NodeData>();
6172 }
6173
6174 #[test]
6180 fn copy_special_moving_complex_moves_text_node_type() {
6181 let mut nd = NodeData::create_text("hello").with_css("color: red;");
6182 assert!(!nd.style.rules.is_empty(), "precondition: style set");
6183
6184 let copy = nd.copy_special_moving_complex();
6185
6186 match copy.get_node_type() {
6188 NodeType::Text(s) => assert_eq!(s.as_ref().as_str(), "hello"),
6189 other => panic!("expected Text node_type on copy, got {other:?}"),
6190 }
6191 assert!(matches!(nd.get_node_type(), NodeType::Div));
6193 assert!(nd.style.rules.is_empty());
6195 assert!(!copy.style.rules.is_empty());
6196 }
6197
6198 #[test]
6200 fn copy_special_moving_complex_moves_div_node_type() {
6201 let mut nd = NodeData::create_div();
6202 let copy = nd.copy_special_moving_complex();
6203 assert!(matches!(copy.get_node_type(), NodeType::Div));
6204 assert!(matches!(nd.get_node_type(), NodeType::Div));
6205 }
6206}
6207
6208#[cfg(test)]
6209#[allow(clippy::cast_possible_wrap, clippy::too_many_lines)]
6210mod autotest_generated {
6211 use super::*;
6212
6213 fn hash_of<T: Hash>(t: &T) -> u64 {
6218 let mut h = crate::hash::DefaultHasher::new();
6219 t.hash(&mut h);
6220 h.finish()
6221 }
6222
6223 extern "C" fn merge_cb_a(new_data: RefAny, _old: RefAny) -> RefAny {
6224 new_data
6225 }
6226
6227 extern "C" fn merge_cb_b(_new: RefAny, old_data: RefAny) -> RefAny {
6228 old_data
6229 }
6230
6231 extern "C" fn virtual_view_cb(
6234 _data: RefAny,
6235 _info: crate::callbacks::VirtualViewCallbackInfo,
6236 ) -> crate::callbacks::VirtualViewReturn {
6237 unreachable!("virtual view callback is never invoked by these tests")
6238 }
6239
6240 fn virtual_view_callback() -> VirtualViewCallback {
6241 VirtualViewCallback {
6242 cb: virtual_view_cb,
6243 ctx: OptionRefAny::None,
6244 }
6245 }
6246
6247 fn huge_unicode_string() -> String {
6249 "ä🎉本".repeat(25_000)
6250 }
6251
6252 fn all_attribute_variants() -> Vec<AttributeType> {
6254 let nv = || AttributeNameValue {
6255 attr_name: "data-x".into(),
6256 value: "v".into(),
6257 };
6258 vec![
6259 AttributeType::Id("i".into()),
6260 AttributeType::Class("c".into()),
6261 AttributeType::AriaLabel("l".into()),
6262 AttributeType::AriaLabelledBy("lb".into()),
6263 AttributeType::AriaDescribedBy("db".into()),
6264 AttributeType::AriaRole("r".into()),
6265 AttributeType::AriaState(nv()),
6266 AttributeType::AriaProperty(nv()),
6267 AttributeType::Href("h".into()),
6268 AttributeType::Rel("rel".into()),
6269 AttributeType::Target("t".into()),
6270 AttributeType::Src("s".into()),
6271 AttributeType::Alt("a".into()),
6272 AttributeType::Title("ti".into()),
6273 AttributeType::Name("n".into()),
6274 AttributeType::Value("v".into()),
6275 AttributeType::InputType("text".into()),
6276 AttributeType::Placeholder("p".into()),
6277 AttributeType::Required,
6278 AttributeType::Disabled,
6279 AttributeType::Readonly,
6280 AttributeType::CheckedTrue,
6281 AttributeType::CheckedFalse,
6282 AttributeType::Selected,
6283 AttributeType::Max("10".into()),
6284 AttributeType::Min("0".into()),
6285 AttributeType::Step("1".into()),
6286 AttributeType::Pattern(".*".into()),
6287 AttributeType::MinLength(i32::MIN),
6288 AttributeType::MaxLength(i32::MAX),
6289 AttributeType::Autocomplete("off".into()),
6290 AttributeType::Scope("row".into()),
6291 AttributeType::ColSpan(-1),
6292 AttributeType::RowSpan(0),
6293 AttributeType::TabIndex(i32::MIN),
6294 AttributeType::Focusable,
6295 AttributeType::Lang("en".into()),
6296 AttributeType::Dir("rtl".into()),
6297 AttributeType::ContentEditable(true),
6298 AttributeType::Draggable(false),
6299 AttributeType::Hidden,
6300 AttributeType::Data(nv()),
6301 AttributeType::Custom(nv()),
6302 ]
6303 }
6304
6305 fn representative_node_types() -> Vec<NodeType> {
6307 vec![
6308 NodeType::Html,
6309 NodeType::Body,
6310 NodeType::Div,
6311 NodeType::Br,
6312 NodeType::Button,
6313 NodeType::Input,
6314 NodeType::TextArea,
6315 NodeType::Select,
6316 NodeType::A,
6317 NodeType::H1,
6318 NodeType::H6,
6319 NodeType::Table,
6320 NodeType::Td,
6321 NodeType::Svg,
6322 NodeType::SvgPath,
6323 NodeType::SvgText("svg text".into()),
6324 NodeType::SvgImage(ImageRef::null_image(
6325 1,
6326 1,
6327 crate::resources::RawImageFormat::R8,
6328 Vec::new(),
6329 )),
6330 NodeType::Before,
6331 NodeType::After,
6332 NodeType::Marker,
6333 NodeType::Placeholder,
6334 NodeType::Text(BoxOrStatic::heap(AzString::from("hello"))),
6335 NodeType::Image(BoxOrStatic::heap(ImageRef::null_image(
6336 2,
6337 2,
6338 crate::resources::RawImageFormat::RGBA8,
6339 Vec::new(),
6340 ))),
6341 NodeType::VirtualView,
6342 NodeType::Icon(BoxOrStatic::heap(AzString::from("home"))),
6343 NodeType::GeolocationProbe(crate::geolocation::GeolocationProbeConfig::default()),
6344 ]
6345 }
6346
6347 #[test]
6352 fn node_flags_new_is_empty_and_matches_default() {
6353 let f = NodeFlags::new();
6354 assert_eq!(f.inner, 0);
6355 assert_eq!(f, NodeFlags::default());
6356 assert!(!f.is_contenteditable());
6357 assert!(!f.is_anonymous());
6358 assert_eq!(f.get_tab_index(), None);
6359 }
6360
6361 #[test]
6362 fn node_flags_tab_index_round_trips_for_all_variants() {
6363 for ti in [
6364 None,
6365 Some(TabIndex::Auto),
6366 Some(TabIndex::NoKeyboardFocus),
6367 Some(TabIndex::OverrideInParent(0)),
6368 Some(TabIndex::OverrideInParent(1)),
6369 Some(TabIndex::OverrideInParent(1_000)),
6370 ] {
6371 let mut f = NodeFlags::new();
6372 f.set_tab_index(ti);
6373 assert_eq!(f.get_tab_index(), ti, "round-trip failed for {ti:?}");
6374 }
6375 }
6376
6377 #[test]
6378 fn node_flags_tab_index_round_trips_at_the_28_bit_boundary() {
6379 const MAX_EXACT: u32 = (1 << 28) - 1;
6382 let mut f = NodeFlags::new();
6383 f.set_tab_index(Some(TabIndex::OverrideInParent(MAX_EXACT)));
6384 assert_eq!(
6385 f.get_tab_index(),
6386 Some(TabIndex::OverrideInParent(MAX_EXACT))
6387 );
6388 }
6389
6390 #[test]
6391 fn node_flags_tab_index_above_28_bits_truncates_without_corrupting_other_flags() {
6392 const OVERFLOW: u32 = 1 << 28;
6398 let mut f = NodeFlags::new();
6399 f.set_tab_index(Some(TabIndex::OverrideInParent(OVERFLOW)));
6400 assert_eq!(
6401 f.get_tab_index(),
6402 Some(TabIndex::OverrideInParent(0)),
6403 "2^28 truncates to 0 (documented lossiness)"
6404 );
6405 assert!(!f.is_anonymous(), "overflow bit must not set ANONYMOUS");
6406 assert!(!f.is_contenteditable());
6407
6408 let mut f = NodeFlags::new();
6409 f.set_tab_index(Some(TabIndex::OverrideInParent(u32::MAX)));
6410 assert_eq!(
6411 f.get_tab_index(),
6412 Some(TabIndex::OverrideInParent((1 << 28) - 1)),
6413 "u32::MAX truncates to the 28-bit mask"
6414 );
6415 assert!(!f.is_anonymous(), "u32::MAX must not set ANONYMOUS");
6416 assert!(!f.is_contenteditable(), "u32::MAX must not set CONTENTEDITABLE");
6417 }
6418
6419 #[test]
6420 fn node_flags_set_tab_index_preserves_contenteditable_and_anonymous() {
6421 let mut f = NodeFlags::new();
6422 f.set_contenteditable_mut(true);
6423 f.set_anonymous(true);
6424
6425 for ti in [
6426 None,
6427 Some(TabIndex::Auto),
6428 Some(TabIndex::NoKeyboardFocus),
6429 Some(TabIndex::OverrideInParent(u32::MAX)),
6430 Some(TabIndex::OverrideInParent(7)),
6431 ] {
6432 f.set_tab_index(ti);
6433 assert!(f.is_contenteditable(), "contenteditable lost for {ti:?}");
6434 assert!(f.is_anonymous(), "anonymous lost for {ti:?}");
6435 assert_eq!(f.get_tab_index(), ti);
6436 }
6437 }
6438
6439 #[test]
6440 fn node_flags_set_contenteditable_preserves_tab_index_and_anonymous() {
6441 let mut f = NodeFlags::new();
6442 f.set_anonymous(true);
6443 f.set_tab_index(Some(TabIndex::OverrideInParent(12_345)));
6444
6445 f.set_contenteditable_mut(true);
6446 assert!(f.is_contenteditable());
6447 assert!(f.is_anonymous());
6448 assert_eq!(f.get_tab_index(), Some(TabIndex::OverrideInParent(12_345)));
6449
6450 f.set_contenteditable_mut(false);
6451 assert!(!f.is_contenteditable());
6452 assert!(f.is_anonymous());
6453 assert_eq!(f.get_tab_index(), Some(TabIndex::OverrideInParent(12_345)));
6454 }
6455
6456 #[test]
6457 fn node_flags_set_anonymous_preserves_tab_index_and_contenteditable() {
6458 let mut f = NodeFlags::new();
6459 f.set_contenteditable_mut(true);
6460 f.set_tab_index(Some(TabIndex::NoKeyboardFocus));
6461
6462 f.set_anonymous(true);
6463 assert!(f.is_anonymous());
6464 assert!(f.is_contenteditable());
6465 assert_eq!(f.get_tab_index(), Some(TabIndex::NoKeyboardFocus));
6466
6467 f.set_anonymous(false);
6468 assert!(!f.is_anonymous());
6469 assert!(f.is_contenteditable());
6470 assert_eq!(f.get_tab_index(), Some(TabIndex::NoKeyboardFocus));
6471 }
6472
6473 #[test]
6474 fn node_flags_consecutive_set_contenteditable_is_idempotent() {
6475 let mut f = NodeFlags::new();
6476 f.set_contenteditable_mut(true);
6477 let once = f;
6478 f.set_contenteditable_mut(true);
6479 assert_eq!(f, once, "setting twice must not toggle");
6480 }
6481
6482 #[test]
6483 fn node_flags_builder_and_mut_setter_agree() {
6484 for v in [true, false] {
6485 let builder = NodeFlags::new().set_contenteditable(v);
6486 let mut mutated = NodeFlags::new();
6487 mutated.set_contenteditable_mut(v);
6488 assert_eq!(builder, mutated, "builder/mut disagree for {v}");
6489 }
6490 }
6491
6492 #[test]
6493 fn node_flags_all_bits_set_decodes_without_panicking() {
6494 let f = NodeFlags { inner: u32::MAX };
6498 assert!(f.is_contenteditable());
6499 assert!(f.is_anonymous());
6500 assert_eq!(f.get_tab_index(), Some(TabIndex::NoKeyboardFocus));
6502 }
6503
6504 #[test]
6505 fn node_flags_get_tab_index_is_total_over_the_tag_bits() {
6506 for tag in 0u32..4 {
6509 for extra in [0u32, u32::MAX] {
6510 let inner = (tag << 29) | (extra & !(0b11 << 29));
6511 let f = NodeFlags { inner };
6512 let decoded = f.get_tab_index();
6513 match tag {
6514 0 => assert_eq!(decoded, None),
6515 1 => assert_eq!(decoded, Some(TabIndex::Auto)),
6516 2 => assert!(matches!(decoded, Some(TabIndex::OverrideInParent(_)))),
6517 _ => assert_eq!(decoded, Some(TabIndex::NoKeyboardFocus)),
6518 }
6519 }
6520 }
6521 }
6522
6523 #[test]
6528 fn tab_index_default_is_auto_with_index_zero() {
6529 assert_eq!(TabIndex::default(), TabIndex::Auto);
6530 assert_eq!(TabIndex::default().get_index(), 0);
6531 }
6532
6533 #[test]
6534 fn tab_index_get_index_at_numeric_limits() {
6535 assert_eq!(TabIndex::Auto.get_index(), 0);
6536 assert_eq!(TabIndex::NoKeyboardFocus.get_index(), -1);
6537 assert_eq!(TabIndex::OverrideInParent(0).get_index(), 0);
6538 let max = TabIndex::OverrideInParent(u32::MAX).get_index();
6541 assert_eq!(max, u32::MAX as isize);
6542 assert!(max > 0, "u32::MAX must not wrap to a negative isize");
6543 }
6544
6545 #[test]
6546 fn get_effective_tabindex_saturates_into_i32() {
6547 let nd = NodeData::create_div().with_tab_index(TabIndex::OverrideInParent(u32::MAX));
6551 assert_eq!(nd.get_effective_tabindex(), Some((1 << 28) - 1));
6552
6553 assert_eq!(
6554 NodeData::create_div()
6555 .with_tab_index(TabIndex::Auto)
6556 .get_effective_tabindex(),
6557 Some(0)
6558 );
6559 assert_eq!(
6560 NodeData::create_div()
6561 .with_tab_index(TabIndex::NoKeyboardFocus)
6562 .get_effective_tabindex(),
6563 Some(-1)
6564 );
6565 assert_eq!(NodeData::create_div().get_effective_tabindex(), None);
6566 }
6567
6568 #[test]
6569 fn get_effective_tabindex_falls_back_to_zero_for_focus_callbacks() {
6570 let nd = NodeData::create_div().with_callback(
6571 EventFilter::Focus(FocusEventFilter::MouseDown),
6572 RefAny::new(0u32),
6573 0usize,
6574 );
6575 assert_eq!(nd.get_effective_tabindex(), Some(0));
6576 }
6577
6578 #[test]
6583 fn tag_id_unique_never_returns_zero_and_never_repeats() {
6584 let ids: Vec<TagId> = (0..512).map(|_| TagId::unique()).collect();
6587 for id in &ids {
6588 assert_ne!(id.inner, 0, "TagId 0 is reserved for 'no tag'");
6589 }
6590 let mut sorted: Vec<u64> = ids.iter().map(|t| t.inner).collect();
6591 sorted.sort_unstable();
6592 sorted.dedup();
6593 assert_eq!(sorted.len(), 512, "TagId::unique() handed out a duplicate");
6594 }
6595
6596 #[test]
6597 fn tag_id_crate_internal_conversions_are_identity_at_limits() {
6598 for inner in [0u64, 1, u64::MAX, u64::MAX - 1] {
6599 let t = TagId { inner };
6600 assert_eq!(t.into_crate_internal(), t);
6601 assert_eq!(TagId::from_crate_internal(t), t);
6602 assert_eq!(
6604 TagId::from_crate_internal(t.into_crate_internal()).inner,
6605 inner
6606 );
6607 }
6608 }
6609
6610 #[test]
6611 fn tag_id_display_is_non_empty_at_numeric_limits() {
6612 for inner in [0u64, 1, u64::MAX] {
6613 let s = format!("{}", TagId { inner });
6614 assert!(!s.is_empty());
6615 assert!(s.contains(&inner.to_string()), "{s} should contain {inner}");
6616 }
6617 }
6618
6619 #[test]
6620 fn scroll_tag_id_unique_is_distinct_and_debug_matches_display() {
6621 let a = ScrollTagId::unique();
6622 let b = ScrollTagId::unique();
6623 assert_ne!(a, b);
6624 assert_ne!(a.inner.inner, 0);
6625
6626 let s = ScrollTagId {
6627 inner: TagId { inner: u64::MAX },
6628 };
6629 assert_eq!(format!("{s:?}"), format!("{s}"));
6630 assert!(!format!("{s}").is_empty());
6631 }
6632
6633 #[test]
6638 fn attribute_boolean_attrs_always_have_an_empty_value() {
6639 for attr in all_attribute_variants() {
6642 if attr.is_boolean() {
6643 assert_eq!(
6644 attr.value().as_str(),
6645 "",
6646 "boolean attr {} must have an empty value",
6647 attr.name()
6648 );
6649 }
6650 }
6651 }
6652
6653 #[test]
6654 fn attribute_name_and_value_never_panic_for_any_variant() {
6655 for attr in all_attribute_variants() {
6656 let name = attr.name();
6657 let value = attr.value();
6658 assert!(!name.is_empty(), "empty name for {attr:?}");
6661 let _ = value.as_str();
6662 }
6663 }
6664
6665 #[test]
6666 fn attribute_custom_with_empty_name_returns_empty_name_without_panicking() {
6667 let attr = AttributeType::Custom(AttributeNameValue {
6668 attr_name: "".into(),
6669 value: "".into(),
6670 });
6671 assert_eq!(attr.name(), "");
6672 assert_eq!(attr.value().as_str(), "");
6673 assert!(!attr.is_boolean());
6674 }
6675
6676 #[test]
6677 fn attribute_as_id_and_as_class_are_mutually_exclusive() {
6678 for attr in all_attribute_variants() {
6679 match &attr {
6680 AttributeType::Id(s) => {
6681 assert_eq!(attr.as_id(), Some(s.as_str()));
6682 assert_eq!(attr.as_class(), None);
6683 }
6684 AttributeType::Class(s) => {
6685 assert_eq!(attr.as_class(), Some(s.as_str()));
6686 assert_eq!(attr.as_id(), None);
6687 }
6688 _ => {
6689 assert_eq!(attr.as_id(), None, "as_id must be None for {attr:?}");
6690 assert_eq!(attr.as_class(), None, "as_class must be None for {attr:?}");
6691 }
6692 }
6693 }
6694 }
6695
6696 #[test]
6697 fn attribute_numeric_values_serialize_at_i32_limits() {
6698 assert_eq!(
6699 AttributeType::MinLength(i32::MIN).value().as_str(),
6700 "-2147483648"
6701 );
6702 assert_eq!(
6703 AttributeType::MaxLength(i32::MAX).value().as_str(),
6704 "2147483647"
6705 );
6706 assert_eq!(AttributeType::ColSpan(0).value().as_str(), "0");
6707 assert_eq!(AttributeType::RowSpan(-1).value().as_str(), "-1");
6708 assert_eq!(
6709 AttributeType::TabIndex(i32::MIN).value().as_str(),
6710 "-2147483648"
6711 );
6712 }
6713
6714 #[test]
6715 fn attribute_focusable_is_tabindex_zero_and_not_boolean() {
6716 let f = AttributeType::Focusable;
6719 assert_eq!(f.name(), "tabindex");
6720 assert_eq!(f.value().as_str(), "0");
6721 assert!(!f.is_boolean());
6722 assert_eq!(AttributeType::TabIndex(0).name(), "tabindex");
6723 }
6724
6725 #[test]
6726 fn attribute_checked_true_and_false_are_both_boolean_and_share_a_name() {
6727 assert!(AttributeType::CheckedTrue.is_boolean());
6731 assert!(AttributeType::CheckedFalse.is_boolean());
6732 assert_eq!(AttributeType::CheckedTrue.name(), "checked");
6733 assert_eq!(AttributeType::CheckedFalse.name(), "checked");
6734 assert_eq!(AttributeType::CheckedFalse.value().as_str(), "");
6735 assert_ne!(AttributeType::CheckedTrue, AttributeType::CheckedFalse);
6737 }
6738
6739 #[test]
6740 fn attribute_content_editable_and_draggable_stringify_bools() {
6741 assert_eq!(AttributeType::ContentEditable(true).value().as_str(), "true");
6742 assert_eq!(
6743 AttributeType::ContentEditable(false).value().as_str(),
6744 "false"
6745 );
6746 assert_eq!(AttributeType::Draggable(true).value().as_str(), "true");
6747 assert_eq!(AttributeType::Draggable(false).value().as_str(), "false");
6748 assert!(!AttributeType::ContentEditable(false).is_boolean());
6749 }
6750
6751 #[test]
6752 fn attribute_round_trips_huge_unicode_values() {
6753 let big = huge_unicode_string();
6754 let attr = AttributeType::Value(big.clone().into());
6755 assert_eq!(attr.value().as_str(), big.as_str());
6756 assert_eq!(attr.name(), "value");
6757
6758 let id = AttributeType::Id(big.clone().into());
6759 assert_eq!(id.as_id(), Some(big.as_str()));
6760 }
6761
6762 #[test]
6763 fn id_or_class_accessors_are_mutually_exclusive() {
6764 let id = IdOrClass::Id("my-id".into());
6765 let class = IdOrClass::Class("my-class".into());
6766 assert_eq!(id.as_id(), Some("my-id"));
6767 assert_eq!(id.as_class(), None);
6768 assert_eq!(class.as_class(), Some("my-class"));
6769 assert_eq!(class.as_id(), None);
6770
6771 assert_eq!(IdOrClass::Id("".into()).as_id(), Some(""));
6773 assert_eq!(IdOrClass::Class("".into()).as_class(), Some(""));
6774 }
6775
6776 #[test]
6781 fn input_type_as_str_is_non_empty_and_unique_per_variant() {
6782 let all = [
6783 InputType::Text,
6784 InputType::Button,
6785 InputType::Checkbox,
6786 InputType::Color,
6787 InputType::Date,
6788 InputType::Datetime,
6789 InputType::DatetimeLocal,
6790 InputType::Email,
6791 InputType::File,
6792 InputType::Hidden,
6793 InputType::Image,
6794 InputType::Month,
6795 InputType::Number,
6796 InputType::Password,
6797 InputType::Radio,
6798 InputType::Range,
6799 InputType::Reset,
6800 InputType::Search,
6801 InputType::Submit,
6802 InputType::Tel,
6803 InputType::Time,
6804 InputType::Url,
6805 InputType::Week,
6806 ];
6807 let mut seen: Vec<&str> = all.iter().map(InputType::as_str).collect();
6808 for s in &seen {
6809 assert!(!s.is_empty());
6810 assert!(
6811 !s.contains(char::is_whitespace),
6812 "{s} is not a valid HTML attribute value"
6813 );
6814 }
6815 let len = seen.len();
6816 seen.sort_unstable();
6817 seen.dedup();
6818 assert_eq!(seen.len(), len, "two InputType variants share an as_str()");
6819
6820 assert_eq!(InputType::DatetimeLocal.as_str(), "datetime-local");
6821 assert_eq!(InputType::Text.as_str(), "text");
6822 }
6823
6824 #[test]
6829 fn node_type_to_library_owned_round_trips_every_variant() {
6830 for nt in representative_node_types() {
6833 let owned = nt.to_library_owned_nodetype();
6834 assert_eq!(owned, nt, "to_library_owned_nodetype lost data for {nt:?}");
6835 assert_eq!(owned.get_path(), nt.get_path());
6836 }
6837 }
6838
6839 #[test]
6840 fn node_type_get_path_and_format_never_panic() {
6841 for nt in representative_node_types() {
6842 let _tag = nt.get_path();
6843 let _fmt = nt.format();
6844 let _semantic = nt.is_semantic_for_accessibility();
6845 }
6846 }
6847
6848 #[test]
6849 fn node_type_format_returns_content_only_for_content_variants() {
6850 assert_eq!(NodeType::Div.format(), None);
6851 assert_eq!(NodeType::Br.format(), None);
6852 assert_eq!(NodeType::Button.format(), None);
6853
6854 assert_eq!(
6855 NodeType::Text(BoxOrStatic::heap(AzString::from("hi"))).format(),
6856 Some("hi".to_string())
6857 );
6858 assert_eq!(
6859 NodeType::VirtualView.format(),
6860 Some("virtualized-view".to_string())
6861 );
6862 assert_eq!(
6863 NodeType::Icon(BoxOrStatic::heap(AzString::from("home"))).format(),
6864 Some("icon(home)".to_string())
6865 );
6866 }
6867
6868 #[test]
6869 fn node_type_format_handles_empty_and_unicode_text() {
6870 assert_eq!(
6871 NodeType::Text(BoxOrStatic::heap(AzString::from(""))).format(),
6872 Some(String::new())
6873 );
6874 let unicode = "日本語 🎉 ünïcødé";
6875 assert_eq!(
6876 NodeType::Text(BoxOrStatic::heap(AzString::from(unicode))).format(),
6877 Some(unicode.to_string())
6878 );
6879 }
6880
6881 #[test]
6882 fn node_type_format_of_geolocation_probe_survives_nan_and_infinity() {
6883 for max_accuracy_m in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY, -0.0, f32::MAX] {
6886 let cfg = crate::geolocation::GeolocationProbeConfig {
6887 high_accuracy: true,
6888 background: true,
6889 max_accuracy_m,
6890 min_interval_ms: u32::MAX,
6891 };
6892 let out = NodeType::GeolocationProbe(cfg)
6893 .format()
6894 .expect("GeolocationProbe always formats");
6895 assert!(out.starts_with("geolocation-probe("));
6896 assert!(out.contains("4294967295"), "min_interval_ms must be printed");
6897 }
6898 }
6899
6900 #[test]
6901 fn geolocation_probe_nan_is_self_equal_and_hash_consistent() {
6902 let cfg = crate::geolocation::GeolocationProbeConfig {
6905 max_accuracy_m: f32::NAN,
6906 ..Default::default()
6907 };
6908 let a = NodeType::GeolocationProbe(cfg);
6909 let b = NodeType::GeolocationProbe(cfg);
6910 assert_eq!(a, b, "bitwise-NaN configs must compare equal");
6911 assert_eq!(
6912 hash_of(&a),
6913 hash_of(&b),
6914 "Eq == true but hashes differ: violates the Hash/Eq contract"
6915 );
6916 assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
6917 }
6918
6919 #[test]
6920 fn node_type_is_semantic_for_accessibility_known_true_and_false() {
6921 for nt in [
6922 NodeType::Button,
6923 NodeType::Input,
6924 NodeType::TextArea,
6925 NodeType::Select,
6926 NodeType::A,
6927 NodeType::H1,
6928 NodeType::H6,
6929 NodeType::Article,
6930 NodeType::Nav,
6931 NodeType::Main,
6932 ] {
6933 assert!(
6934 nt.is_semantic_for_accessibility(),
6935 "{nt:?} should be semantic"
6936 );
6937 }
6938 for nt in [
6939 NodeType::Div,
6940 NodeType::Span,
6941 NodeType::Br,
6942 NodeType::VirtualView,
6943 NodeType::Text(BoxOrStatic::heap(AzString::from("x"))),
6944 ] {
6945 assert!(
6946 !nt.is_semantic_for_accessibility(),
6947 "{nt:?} should not be semantic"
6948 );
6949 }
6950 }
6951
6952 #[test]
6953 fn node_type_text_variants_are_content_sensitive() {
6954 let a = NodeType::Text(BoxOrStatic::heap(AzString::from("a")));
6955 let b = NodeType::Text(BoxOrStatic::heap(AzString::from("b")));
6956 assert_ne!(a, b);
6957 assert_eq!(a.get_path(), b.get_path(), "same tag, different content");
6958 }
6959
6960 #[test]
6965 fn node_data_default_has_no_attributes_and_no_extra_state() {
6966 let nd = NodeData::default();
6967 assert!(nd.is_node_type(NodeType::Div));
6968 assert!(nd.attributes().as_ref().is_empty());
6969 assert!(nd.get_ids_and_classes().as_ref().is_empty());
6970 assert!(nd.get_dataset().is_none());
6971 assert!(nd.get_key().is_none());
6972 assert!(nd.get_menu_bar().is_none());
6973 assert!(nd.get_context_menu().is_none());
6974 assert!(nd.get_svg_data().is_none());
6975 assert!(nd.get_image_clip_mask().is_none());
6976 assert!(nd.get_accessibility_info().is_none());
6977 assert!(nd.get_merge_callback().is_none());
6978 assert!(nd.get_component_origin().is_none());
6979 assert!(!nd.has_context_menu());
6980 assert!(!nd.is_contenteditable());
6981 assert!(!nd.is_anonymous());
6982 assert_eq!(nd.get_tab_index(), None);
6983 }
6984
6985 #[test]
6986 fn attributes_mut_lazily_allocates_but_stays_empty() {
6987 let mut nd = NodeData::create_div();
6988 assert!(nd.attributes().as_ref().is_empty());
6989 let _ = nd.attributes_mut(); assert!(
6991 nd.attributes().as_ref().is_empty(),
6992 "lazy alloc must not invent attributes"
6993 );
6994 nd.add_id("x".into());
6995 assert_eq!(nd.attributes().as_ref().len(), 1);
6996 }
6997
6998 #[test]
6999 fn has_id_and_has_class_match_exactly_not_by_prefix() {
7000 let mut nd = NodeData::create_div();
7001 nd.add_id("header".into());
7002 nd.add_class("btn".into());
7003
7004 assert!(nd.has_id("header"));
7005 assert!(nd.has_class("btn"));
7006 assert!(!nd.has_id("head"));
7008 assert!(!nd.has_id("header2"));
7009 assert!(!nd.has_class("bt"));
7010 assert!(!nd.has_class(""));
7011 assert!(!nd.has_class("header"));
7013 assert!(!nd.has_id("btn"));
7014 }
7015
7016 #[test]
7017 fn has_id_matches_the_empty_string_id() {
7018 let mut nd = NodeData::create_div();
7019 assert!(!nd.has_id(""), "no ids at all => empty id must not match");
7020 nd.add_id("".into());
7021 assert!(nd.has_id(""), "an explicitly-added empty id must match");
7022 assert!(!nd.has_id("x"));
7023 }
7024
7025 #[test]
7026 fn has_id_and_has_class_handle_unicode_and_huge_strings() {
7027 let unicode = "日本語-🎉-ünïcødé";
7028 let big = huge_unicode_string();
7029
7030 let mut nd = NodeData::create_div();
7031 nd.add_id(unicode.into());
7032 nd.add_class(big.clone().into());
7033
7034 assert!(nd.has_id(unicode));
7035 assert!(nd.has_class(big.as_str()));
7036 assert!(!nd.has_id("日本語"));
7038 }
7039
7040 #[test]
7041 fn duplicate_ids_are_kept_and_still_match() {
7042 let mut nd = NodeData::create_div();
7043 nd.add_id("dup".into());
7044 nd.add_id("dup".into());
7045 assert!(nd.has_id("dup"));
7046 assert_eq!(
7047 nd.get_ids_and_classes().as_ref().len(),
7048 2,
7049 "add_id does not deduplicate"
7050 );
7051 }
7052
7053 #[test]
7054 fn get_ids_and_classes_preserves_insertion_order_and_kind() {
7055 let mut nd = NodeData::create_div();
7056 nd.add_id("i1".into());
7057 nd.add_class("c1".into());
7058 nd.add_id("i2".into());
7059
7060 let v = nd.get_ids_and_classes();
7061 let v = v.as_ref();
7062 assert_eq!(v.len(), 3);
7063 assert_eq!(v[0], IdOrClass::Id("i1".into()));
7064 assert_eq!(v[1], IdOrClass::Class("c1".into()));
7065 assert_eq!(v[2], IdOrClass::Id("i2".into()));
7066 }
7067
7068 #[test]
7069 fn get_ids_and_classes_ignores_non_id_class_attributes() {
7070 let mut nd = NodeData::create_div();
7071 nd.set_attributes(
7072 vec![
7073 AttributeType::Href("/x".into()),
7074 AttributeType::Id("i".into()),
7075 AttributeType::Disabled,
7076 AttributeType::Class("c".into()),
7077 ]
7078 .into(),
7079 );
7080 let v = nd.get_ids_and_classes();
7081 assert_eq!(v.as_ref().len(), 2);
7082 }
7083
7084 #[test]
7085 fn set_ids_and_classes_replaces_ids_but_preserves_other_attributes() {
7086 let mut nd = NodeData::create_div();
7089 nd.set_attributes(
7090 vec![
7091 AttributeType::Href("/old".into()),
7092 AttributeType::Id("old-id".into()),
7093 AttributeType::Class("old-class".into()),
7094 AttributeType::Disabled,
7095 ]
7096 .into(),
7097 );
7098
7099 nd.set_ids_and_classes(vec![IdOrClass::Class("new-class".into())].into());
7100
7101 assert!(!nd.has_id("old-id"), "old id must be dropped");
7102 assert!(!nd.has_class("old-class"), "old class must be dropped");
7103 assert!(nd.has_class("new-class"));
7104 let attrs = nd.attributes().as_ref();
7106 assert!(attrs.contains(&AttributeType::Href("/old".into())));
7107 assert!(attrs.contains(&AttributeType::Disabled));
7108 assert_eq!(attrs.len(), 3);
7109 }
7110
7111 #[test]
7112 fn set_ids_and_classes_with_an_empty_vec_clears_all_ids_and_classes() {
7113 let mut nd = NodeData::create_div();
7114 nd.add_id("i".into());
7115 nd.add_class("c".into());
7116 nd.set_ids_and_classes(Vec::new().into());
7117 assert!(nd.get_ids_and_classes().as_ref().is_empty());
7118 assert!(!nd.has_id("i"));
7119 assert!(!nd.has_class("c"));
7120 }
7121
7122 #[test]
7123 fn set_ids_and_classes_is_idempotent_when_reapplied() {
7124 let mut nd = NodeData::create_div();
7125 let ids: IdOrClassVec = vec![
7126 IdOrClass::Id("i".into()),
7127 IdOrClass::Class("c".into()),
7128 ]
7129 .into();
7130 nd.set_ids_and_classes(ids.clone());
7131 let after_first = nd.attributes().clone();
7132 nd.set_ids_and_classes(ids);
7133 assert_eq!(
7134 nd.attributes().as_ref(),
7135 after_first.as_ref(),
7136 "re-applying the same ids/classes must not duplicate them"
7137 );
7138 }
7139
7140 #[test]
7141 fn with_attribute_appends_without_dropping_existing_attributes() {
7142 let nd = NodeData::create_div()
7145 .with_attribute(AttributeType::Href("/a".into()))
7146 .with_attribute(AttributeType::Alt("alt".into()));
7147 let attrs = nd.attributes().as_ref();
7148 assert_eq!(attrs.len(), 2);
7149 assert_eq!(attrs[0], AttributeType::Href("/a".into()));
7150 assert_eq!(attrs[1], AttributeType::Alt("alt".into()));
7151 }
7152
7153 #[test]
7158 fn create_node_shorthands_produce_the_right_node_type() {
7159 assert!(NodeData::create_body().is_node_type(NodeType::Body));
7160 assert!(NodeData::create_div().is_node_type(NodeType::Div));
7161 assert!(NodeData::create_br().is_node_type(NodeType::Br));
7162 assert!(NodeData::create_button_no_a11y().is_node_type(NodeType::Button));
7163 assert!(NodeData::create_table_no_a11y().is_node_type(NodeType::Table));
7164 }
7165
7166 #[test]
7167 fn create_text_accepts_empty_unicode_and_huge_input() {
7168 for s in ["", "x", "日本語 🎉"] {
7169 let nd = NodeData::create_text(s);
7170 assert!(nd.is_text_node());
7171 assert_eq!(nd.get_node_type().format(), Some(s.to_string()));
7172 }
7173 let big = huge_unicode_string();
7174 let nd = NodeData::create_text(big.clone());
7175 assert!(nd.is_text_node());
7176 assert_eq!(nd.get_node_type().format(), Some(big));
7177 }
7178
7179 #[test]
7180 fn create_a_stores_href_and_accessibility_name() {
7181 let nd = NodeData::create_a("/home".into(), SmallAriaInfo::label("Home"));
7182 assert!(nd.is_node_type(NodeType::A));
7183 assert!(nd
7184 .attributes()
7185 .as_ref()
7186 .contains(&AttributeType::Href("/home".into())));
7187 let info = nd
7188 .get_accessibility_info()
7189 .expect("create_a must set accessibility info");
7190 assert_eq!(info.accessibility_name, OptionString::Some("Home".into()));
7191 }
7192
7193 #[test]
7194 fn create_a_no_a11y_has_href_but_no_accessibility_info() {
7195 let nd = NodeData::create_a_no_a11y("/x".into());
7196 assert!(nd
7197 .attributes()
7198 .as_ref()
7199 .contains(&AttributeType::Href("/x".into())));
7200 assert!(nd.get_accessibility_info().is_none());
7201 }
7202
7203 #[test]
7204 fn create_a_accepts_an_empty_href() {
7205 let nd = NodeData::create_a_no_a11y("".into());
7206 assert_eq!(
7207 nd.attributes().as_ref()[0],
7208 AttributeType::Href("".into()),
7209 "empty href is stored verbatim, not dropped"
7210 );
7211 }
7212
7213 #[test]
7214 fn create_input_stores_all_three_attributes_in_order() {
7215 let nd = NodeData::create_input_no_a11y("text".into(), "user".into(), "Username".into());
7216 assert!(nd.is_node_type(NodeType::Input));
7217 let attrs = nd.attributes().as_ref();
7218 assert_eq!(attrs.len(), 3);
7219 assert_eq!(attrs[0], AttributeType::InputType("text".into()));
7220 assert_eq!(attrs[1], AttributeType::Name("user".into()));
7221 assert_eq!(attrs[2], AttributeType::AriaLabel("Username".into()));
7222 }
7223
7224 #[test]
7225 fn create_input_with_a11y_sets_both_attributes_and_accessibility_info() {
7226 let nd = NodeData::create_input(
7227 "password".into(),
7228 "pw".into(),
7229 "Password".into(),
7230 SmallAriaInfo::label("Password").with_role(AccessibilityRole::Text),
7231 );
7232 assert_eq!(nd.attributes().as_ref().len(), 3);
7233 let info = nd.get_accessibility_info().expect("a11y info");
7234 assert_eq!(info.role, AccessibilityRole::Text);
7235 }
7236
7237 #[test]
7238 fn create_textarea_and_select_store_name_and_label() {
7239 let ta = NodeData::create_textarea_no_a11y("body".into(), "Body".into());
7240 assert!(ta.is_node_type(NodeType::TextArea));
7241 assert_eq!(ta.attributes().as_ref().len(), 2);
7242
7243 let sel = NodeData::create_select_no_a11y("country".into(), "Country".into());
7244 assert!(sel.is_node_type(NodeType::Select));
7245 assert_eq!(
7246 sel.attributes().as_ref()[0],
7247 AttributeType::Name("country".into())
7248 );
7249 }
7250
7251 #[test]
7252 fn create_label_uses_a_custom_for_attribute() {
7253 let nd = NodeData::create_label_no_a11y("email-input".into());
7254 assert!(nd.is_node_type(NodeType::Label));
7255 assert_eq!(
7256 nd.attributes().as_ref()[0],
7257 AttributeType::Custom(AttributeNameValue {
7258 attr_name: "for".into(),
7259 value: "email-input".into(),
7260 })
7261 );
7262 assert_eq!(nd.attributes().as_ref()[0].name(), "for");
7263 assert_eq!(nd.attributes().as_ref()[0].value().as_str(), "email-input");
7264 }
7265
7266 #[test]
7267 fn create_button_and_table_with_aria_set_accessibility_info() {
7268 let btn = NodeData::create_button(
7269 SmallAriaInfo::label("Save").with_role(AccessibilityRole::PushButton),
7270 );
7271 let info = btn.get_accessibility_info().expect("a11y info");
7272 assert_eq!(info.role, AccessibilityRole::PushButton);
7273 assert_eq!(info.accessibility_name, OptionString::Some("Save".into()));
7274
7275 let table = NodeData::create_table(SmallAriaInfo::label("Results"));
7276 assert!(table.is_node_type(NodeType::Table));
7277 assert!(table.get_accessibility_info().is_some());
7278 }
7279
7280 #[test]
7281 fn a11y_constructors_accept_empty_aria_labels() {
7282 let btn = NodeData::create_button(SmallAriaInfo::label(""));
7283 let info = btn.get_accessibility_info().expect("a11y info");
7284 assert_eq!(info.accessibility_name, OptionString::Some("".into()));
7285 assert_eq!(info.role, AccessibilityRole::Unknown);
7287 }
7288
7289 #[test]
7290 fn create_image_and_is_node_type_round_trip() {
7291 let img = ImageRef::null_image(4, 4, crate::resources::RawImageFormat::RGBA8, Vec::new());
7292 let nd = NodeData::create_image(img.clone());
7293 assert!(!nd.is_text_node());
7294 assert_eq!(nd.get_node_type().get_path(), NodeTypeTag::Img);
7295 assert!(nd.is_node_type(NodeType::Image(BoxOrStatic::heap(img))));
7296 }
7297
7298 #[test]
7303 fn is_node_type_is_content_sensitive_for_text() {
7304 let nd = NodeData::create_text("a");
7305 assert!(nd.is_node_type(NodeType::Text(BoxOrStatic::heap(AzString::from("a")))));
7306 assert!(
7307 !nd.is_node_type(NodeType::Text(BoxOrStatic::heap(AzString::from("b")))),
7308 "is_node_type compares payloads, not just the discriminant"
7309 );
7310 assert!(!nd.is_node_type(NodeType::Div));
7311 }
7312
7313 #[test]
7314 fn is_text_node_and_is_virtual_view_node() {
7315 assert!(NodeData::create_text("x").is_text_node());
7316 assert!(!NodeData::create_div().is_text_node());
7317
7318 let vv = NodeData::create_virtual_view(RefAny::new(1u32), virtual_view_callback());
7319 assert!(vv.is_virtual_view_node());
7320 assert!(!vv.is_text_node());
7321 assert!(vv.get_virtual_view_node_ref().is_some());
7322 assert!(!NodeData::create_div().is_virtual_view_node());
7323 assert!(NodeData::create_div().get_virtual_view_node_ref().is_none());
7324 }
7325
7326 #[test]
7327 fn has_context_menu_flips_only_after_set_context_menu() {
7328 let mut nd = NodeData::create_div();
7329 assert!(!nd.has_context_menu());
7330 nd.set_menu_bar(Menu::create(Vec::new().into()));
7332 assert!(
7333 !nd.has_context_menu(),
7334 "menu_bar must not satisfy has_context_menu"
7335 );
7336 assert!(nd.get_menu_bar().is_some());
7337
7338 nd.set_context_menu(Menu::create(Vec::new().into()));
7339 assert!(nd.has_context_menu());
7340 assert!(nd.get_context_menu().is_some());
7341 }
7342
7343 #[test]
7344 fn with_menu_bar_and_with_context_menu_are_independent_slots() {
7345 let nd = NodeData::create_div()
7346 .with_menu_bar(Menu::create(Vec::new().into()))
7347 .with_context_menu(Menu::create(Vec::new().into()));
7348 assert!(nd.get_menu_bar().is_some());
7349 assert!(nd.get_context_menu().is_some());
7350 assert!(nd.has_context_menu());
7351 }
7352
7353 #[test]
7354 fn is_focusable_for_naturally_focusable_and_opted_in_nodes() {
7355 for nt in [
7356 NodeType::A,
7357 NodeType::Button,
7358 NodeType::Input,
7359 NodeType::Select,
7360 NodeType::TextArea,
7361 ] {
7362 assert!(
7363 NodeData::create_node(nt.clone()).is_focusable(),
7364 "{nt:?} is naturally focusable"
7365 );
7366 }
7367 assert!(!NodeData::create_div().is_focusable());
7368 assert!(NodeData::create_div()
7369 .with_contenteditable(true)
7370 .is_focusable());
7371 assert!(NodeData::create_div()
7372 .with_tab_index(TabIndex::NoKeyboardFocus)
7373 .is_focusable());
7374 assert!(NodeData::create_div()
7375 .with_callback(
7376 EventFilter::Focus(FocusEventFilter::MouseDown),
7377 RefAny::new(0u32),
7378 0usize,
7379 )
7380 .is_focusable());
7381 assert!(!NodeData::create_div()
7383 .with_callback(
7384 EventFilter::Hover(HoverEventFilter::MouseOver),
7385 RefAny::new(0u32),
7386 0usize,
7387 )
7388 .is_focusable());
7389 }
7390
7391 #[test]
7392 fn has_activation_behavior_for_elements_callbacks_and_roles() {
7393 assert!(NodeData::create_node(NodeType::A).has_activation_behavior());
7394 assert!(NodeData::create_button_no_a11y().has_activation_behavior());
7395 assert!(!NodeData::create_div().has_activation_behavior());
7396
7397 for f in [HoverEventFilter::MouseUp, HoverEventFilter::LeftMouseUp] {
7398 assert!(NodeData::create_div()
7399 .with_callback(EventFilter::Hover(f), RefAny::new(0u32), 0usize)
7400 .has_activation_behavior());
7401 }
7402 assert!(!NodeData::create_div()
7404 .with_callback(
7405 EventFilter::Hover(HoverEventFilter::MouseDown),
7406 RefAny::new(0u32),
7407 0usize,
7408 )
7409 .has_activation_behavior());
7410
7411 let mut nd = NodeData::create_div();
7412 nd.set_accessibility_info(
7413 SmallAriaInfo::label("x")
7414 .with_role(AccessibilityRole::PushButton)
7415 .to_full_info(),
7416 );
7417 assert!(nd.has_activation_behavior(), "role=PushButton activates");
7418 }
7419
7420 #[test]
7421 fn is_activatable_is_false_for_unavailable_elements() {
7422 let mut nd = NodeData::create_button_no_a11y();
7423 assert!(nd.is_activatable());
7424
7425 let mut info = SmallAriaInfo::label("Save")
7426 .with_role(AccessibilityRole::PushButton)
7427 .to_full_info();
7428 info.states = vec![AccessibilityState::Unavailable].into();
7429 nd.set_accessibility_info(info);
7430
7431 assert!(nd.has_activation_behavior());
7432 assert!(
7433 !nd.is_activatable(),
7434 "an Unavailable (disabled) button must not be activatable"
7435 );
7436
7437 assert!(!NodeData::create_div().is_activatable());
7439 }
7440
7441 #[test]
7446 fn get_accessible_label_prefers_aria_label_over_alt_and_title() {
7447 let mut nd = NodeData::create_div();
7448 nd.set_attributes(
7449 vec![
7450 AttributeType::Title("title".into()),
7451 AttributeType::Alt("alt".into()),
7452 AttributeType::AriaLabel("aria".into()),
7453 ]
7454 .into(),
7455 );
7456 assert_eq!(
7457 nd.get_accessible_label(),
7458 Some("aria"),
7459 "aria-label wins regardless of attribute order"
7460 );
7461 }
7462
7463 #[test]
7464 fn get_accessible_label_alt_vs_title_is_order_dependent() {
7465 let mut title_first = NodeData::create_div();
7471 title_first.set_attributes(
7472 vec![
7473 AttributeType::Title("title".into()),
7474 AttributeType::Alt("alt".into()),
7475 ]
7476 .into(),
7477 );
7478 assert_eq!(title_first.get_accessible_label(), Some("title"));
7479
7480 let mut alt_first = NodeData::create_div();
7481 alt_first.set_attributes(
7482 vec![
7483 AttributeType::Alt("alt".into()),
7484 AttributeType::Title("title".into()),
7485 ]
7486 .into(),
7487 );
7488 assert_eq!(alt_first.get_accessible_label(), Some("alt"));
7489 }
7490
7491 #[test]
7492 fn get_accessible_label_value_and_placeholder_default_to_none() {
7493 let nd = NodeData::create_div();
7494 assert_eq!(nd.get_accessible_label(), None);
7495 assert_eq!(nd.get_accessible_value(), None);
7496 assert_eq!(nd.get_placeholder(), None);
7497 }
7498
7499 #[test]
7500 fn get_accessible_value_and_placeholder_return_the_first_match() {
7501 let mut nd = NodeData::create_div();
7502 nd.set_attributes(
7503 vec![
7504 AttributeType::Value("first".into()),
7505 AttributeType::Value("second".into()),
7506 AttributeType::Placeholder("ph".into()),
7507 ]
7508 .into(),
7509 );
7510 assert_eq!(nd.get_accessible_value(), Some("first"));
7511 assert_eq!(nd.get_placeholder(), Some("ph"));
7512 }
7513
7514 #[test]
7515 fn get_accessible_label_returns_empty_string_not_none_for_empty_aria_label() {
7516 let mut nd = NodeData::create_div();
7518 nd.set_attributes(vec![AttributeType::AriaLabel("".into())].into());
7519 assert_eq!(nd.get_accessible_label(), Some(""));
7520 }
7521
7522 #[test]
7527 fn dataset_set_get_take_round_trip() {
7528 let mut nd = NodeData::create_div();
7529 assert!(nd.get_dataset().is_none());
7530 assert!(nd.take_dataset().is_none(), "take on empty must be None");
7531
7532 nd.set_dataset(OptionRefAny::Some(RefAny::new(42u32)));
7533 assert!(nd.get_dataset().is_some());
7534 assert!(nd.get_dataset_mut().is_some());
7535
7536 let mut taken = nd.take_dataset().expect("dataset was set");
7537 assert_eq!(taken.downcast_ref::<u32>().map(|r| *r), Some(42));
7538 assert!(nd.get_dataset().is_none(), "take must clear the slot");
7539 assert!(nd.take_dataset().is_none(), "double-take must be None");
7540 }
7541
7542 #[test]
7543 fn set_dataset_none_clears_without_allocating_extra() {
7544 let mut nd = NodeData::create_div();
7545 nd.set_dataset(OptionRefAny::None);
7547 assert!(nd.get_dataset().is_none());
7548
7549 nd.set_dataset(OptionRefAny::Some(RefAny::new(1u8)));
7550 nd.set_dataset(OptionRefAny::None);
7551 assert!(nd.get_dataset().is_none());
7552 }
7553
7554 #[test]
7555 fn set_key_is_deterministic_and_input_sensitive() {
7556 let mut a = NodeData::create_div();
7557 let mut b = NodeData::create_div();
7558 a.set_key("user-123");
7559 b.set_key("user-123");
7560 assert_eq!(a.get_key(), b.get_key(), "same key input => same hash");
7561 assert!(a.get_key().is_some());
7562
7563 let mut c = NodeData::create_div();
7564 c.set_key("user-124");
7565 assert_ne!(a.get_key(), c.get_key(), "different inputs => different keys");
7566 }
7567
7568 #[test]
7569 fn set_key_hashes_str_and_string_identically() {
7570 let mut a = NodeData::create_div();
7571 let mut b = NodeData::create_div();
7572 a.set_key("x");
7573 b.set_key(String::from("x"));
7574 assert_eq!(
7575 a.get_key(),
7576 b.get_key(),
7577 "&str and String must hash the same (Hash for str)"
7578 );
7579 }
7580
7581 #[test]
7582 fn set_key_accepts_extreme_inputs() {
7583 for nd in [
7584 NodeData::create_div().with_key(""),
7585 NodeData::create_div().with_key(u64::MAX),
7586 NodeData::create_div().with_key(i64::MIN),
7587 NodeData::create_div().with_key(huge_unicode_string()),
7588 ] {
7589 assert!(nd.get_key().is_some());
7590 }
7591 }
7592
7593 #[test]
7594 fn set_key_overwrites_rather_than_accumulating() {
7595 let mut nd = NodeData::create_div();
7596 nd.set_key("a");
7597 let first = nd.get_key();
7598 nd.set_key("b");
7599 assert_ne!(nd.get_key(), first, "the last set_key wins");
7600 }
7601
7602 #[test]
7603 fn merge_callback_round_trips_the_function_pointer() {
7604 let mut nd = NodeData::create_div();
7605 assert!(nd.get_merge_callback().is_none());
7606
7607 nd.set_merge_callback(merge_cb_a as DatasetMergeCallbackType);
7608 let cb = nd.get_merge_callback().expect("merge callback was set");
7609 assert_eq!(cb.cb as usize, merge_cb_a as usize);
7610 assert_eq!(cb.callable, OptionRefAny::None);
7611
7612 nd.set_merge_callback(merge_cb_b as DatasetMergeCallbackType);
7614 let cb = nd.get_merge_callback().expect("merge callback was replaced");
7615 assert_eq!(cb.cb as usize, merge_cb_b as usize);
7616 }
7617
7618 #[test]
7619 fn dataset_merge_callback_from_ptr_matches_the_from_impl() {
7620 let via_ptr = DatasetMergeCallback::from_ptr(merge_cb_a);
7621 let via_from = DatasetMergeCallback::from(merge_cb_a as DatasetMergeCallbackType);
7622 assert_eq!(via_ptr, via_from);
7623 assert_eq!(via_ptr.cb as usize, merge_cb_a as usize);
7624 assert_eq!(via_ptr.callable, OptionRefAny::None);
7625
7626 assert_ne!(via_ptr, DatasetMergeCallback::from_ptr(merge_cb_b));
7628 }
7629
7630 #[test]
7631 fn dataset_merge_callback_debug_is_non_empty_and_names_the_type() {
7632 let cb = DatasetMergeCallback::from_ptr(merge_cb_a);
7633 let s = format!("{cb:?}");
7634 assert!(s.contains("DatasetMergeCallback"));
7635 assert!(s.contains("cb"));
7636 }
7637
7638 #[test]
7639 fn merge_callback_is_actually_callable_through_the_stored_pointer() {
7640 let cb = DatasetMergeCallback::from_ptr(merge_cb_b);
7641 let mut out = (cb.cb)(RefAny::new(1u32), RefAny::new(2u32));
7642 assert_eq!(
7643 out.downcast_ref::<u32>().map(|r| *r),
7644 Some(2),
7645 "merge_cb_b returns the OLD data"
7646 );
7647 }
7648
7649 #[test]
7650 fn component_origin_round_trips_and_defaults_to_none() {
7651 let mut nd = NodeData::create_div();
7652 assert!(nd.get_component_origin().is_none());
7653
7654 nd.set_component_origin(ComponentOrigin {
7655 component_id: "shadcn:card".into(),
7656 data_model_json: crate::json::Json::null(),
7657 });
7658 let origin = nd.get_component_origin().expect("origin was set");
7659 assert_eq!(origin.component_id.as_str(), "shadcn:card");
7660
7661 let d = ComponentOrigin::default();
7663 assert_eq!(d.component_id.as_str(), "");
7664 assert_eq!(hash_of(&d), hash_of(&ComponentOrigin::default()));
7665 }
7666
7667 #[test]
7672 fn get_image_clip_mask_returns_none_for_non_mask_svg_data() {
7673 let mut nd = NodeData::create_div();
7674 assert!(nd.get_image_clip_mask().is_none());
7675
7676 nd.set_svg_data(SvgNodeData::Circle {
7677 cx: 1.0,
7678 cy: 2.0,
7679 r: 3.0,
7680 });
7681 assert!(nd.get_svg_data().is_some());
7682 assert!(
7683 nd.get_image_clip_mask().is_none(),
7684 "a Circle is not an ImageClipMask"
7685 );
7686 }
7687
7688 #[test]
7689 fn set_clip_mask_is_readable_through_get_image_clip_mask() {
7690 let mask = ImageMask {
7691 image: ImageRef::null_image(2, 2, crate::resources::RawImageFormat::R8, Vec::new()),
7692 rect: crate::geom::LogicalRect::new(
7693 LogicalPosition { x: 0.0, y: 0.0 },
7694 crate::geom::LogicalSize {
7695 width: 2.0,
7696 height: 2.0,
7697 },
7698 ),
7699 repeat: false,
7700 };
7701 let mut nd = NodeData::create_div();
7702 nd.set_clip_mask(mask.clone());
7703 assert_eq!(nd.get_image_clip_mask(), Some(&mask));
7704 assert!(matches!(
7706 nd.get_svg_data(),
7707 Some(SvgNodeData::ImageClipMask(_))
7708 ));
7709 }
7710
7711 #[test]
7712 fn svg_node_data_with_nan_coords_is_self_equal_and_hash_consistent() {
7713 let a = SvgNodeData::Rect {
7717 x: f32::NAN,
7718 y: f32::INFINITY,
7719 width: f32::NEG_INFINITY,
7720 height: -0.0,
7721 rx: 0.0,
7722 ry: f32::MAX,
7723 };
7724 let b = a.clone();
7725 assert_eq!(a, b);
7726 assert_eq!(hash_of(&a), hash_of(&b));
7727 assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
7728 }
7729
7730 #[test]
7731 fn svg_node_data_line_and_linear_gradient_are_distinct_despite_a_shared_hash_body() {
7732 let line = SvgNodeData::Line {
7735 x1: 1.0,
7736 y1: 2.0,
7737 x2: 3.0,
7738 y2: 4.0,
7739 };
7740 let grad = SvgNodeData::LinearGradient {
7741 x1: 1.0,
7742 y1: 2.0,
7743 x2: 3.0,
7744 y2: 4.0,
7745 };
7746 assert_ne!(line, grad, "same field values, different variants");
7747 }
7748
7749 #[test]
7754 fn calculate_node_data_hash_is_deterministic_and_equal_for_equal_nodes() {
7755 let a = NodeData::create_div().with_key("k").with_contenteditable(true);
7756 let b = a.clone();
7757 assert_eq!(a, b);
7758 assert_eq!(a.calculate_node_data_hash(), b.calculate_node_data_hash());
7759 assert_eq!(
7760 a.calculate_node_data_hash(),
7761 a.calculate_node_data_hash(),
7762 "hashing must not depend on call count"
7763 );
7764 }
7765
7766 #[test]
7767 fn structural_hash_ignores_text_content_but_data_hash_does_not() {
7768 let a = NodeData::create_text("Hello");
7771 let b = NodeData::create_text("Hello World");
7772
7773 assert_eq!(
7774 a.calculate_structural_hash(),
7775 b.calculate_structural_hash(),
7776 "structural hash must ignore text content"
7777 );
7778 assert_ne!(
7779 a.calculate_node_data_hash(),
7780 b.calculate_node_data_hash(),
7781 "the full data hash must NOT ignore text content"
7782 );
7783 }
7784
7785 #[test]
7786 fn structural_hash_ignores_contenteditable_but_data_hash_does_not() {
7787 let plain = NodeData::create_div();
7788 let editable = NodeData::create_div().with_contenteditable(true);
7789
7790 assert_eq!(
7791 plain.calculate_structural_hash(),
7792 editable.calculate_structural_hash(),
7793 "contenteditable flips with focus; it must not move the structural hash"
7794 );
7795 assert_ne!(
7796 plain.calculate_node_data_hash(),
7797 editable.calculate_node_data_hash(),
7798 "flags ARE part of the full data hash"
7799 );
7800 }
7801
7802 #[test]
7803 fn structural_hash_is_sensitive_to_ids_classes_and_node_type() {
7804 let mut a = NodeData::create_div();
7805 a.add_id("a".into());
7806 let mut b = NodeData::create_div();
7807 b.add_id("b".into());
7808 assert_ne!(a.calculate_structural_hash(), b.calculate_structural_hash());
7809
7810 let mut c = NodeData::create_div();
7811 c.add_class("a".into());
7812 assert_ne!(
7813 a.calculate_structural_hash(),
7814 c.calculate_structural_hash(),
7815 "id=\"a\" and class=\"a\" must not collide"
7816 );
7817
7818 assert_ne!(
7819 NodeData::create_div().calculate_structural_hash(),
7820 NodeData::create_br().calculate_structural_hash()
7821 );
7822 }
7823
7824 #[test]
7825 fn node_data_eq_implies_equal_hash_for_a_richly_populated_node() {
7826 let mut a = NodeData::create_div();
7827 a.add_id("id".into());
7828 a.add_class("cls".into());
7829 a.set_tab_index(TabIndex::OverrideInParent(9));
7830 a.set_contenteditable(true);
7831 a.set_anonymous(true);
7832 a.set_key("key");
7833 a.set_dataset(OptionRefAny::Some(RefAny::new(7u64)));
7834 a.set_svg_data(SvgNodeData::GradientStop { offset: 0.5 });
7835 a.set_context_menu(Menu::create(Vec::new().into()));
7836 a.set_merge_callback(merge_cb_a as DatasetMergeCallbackType);
7837 a.set_css("color: red;");
7838
7839 let b = a.clone();
7840 assert_eq!(a, b, "clone must be value-equal");
7841 assert_eq!(
7842 hash_of(&a),
7843 hash_of(&b),
7844 "Eq == true but hashes differ: Hash/Eq contract violated"
7845 );
7846 assert_eq!(a.calculate_node_data_hash(), b.calculate_node_data_hash());
7847 assert_eq!(a.copy_special(), b);
7849 }
7850
7851 #[test]
7856 fn node_data_to_string_is_empty_for_a_bare_node() {
7857 assert_eq!(node_data_to_string(&NodeData::create_div()), "");
7859 }
7860
7861 #[test]
7862 fn node_data_to_string_emits_ids_classes_and_tabindex() {
7863 let mut nd = NodeData::create_div();
7864 nd.add_id("i1".into());
7865 nd.add_id("i2".into());
7866 nd.add_class("c1".into());
7867 nd.set_tab_index(TabIndex::NoKeyboardFocus);
7868
7869 let s = node_data_to_string(&nd);
7870 assert!(s.contains(r#"id="i1 i2""#), "ids are space-joined: {s}");
7871 assert!(s.contains(r#"class="c1""#), "{s}");
7872 assert!(s.contains(r#"tabindex="-1""#), "{s}");
7873 }
7874
7875 #[test]
7876 fn node_data_display_is_self_closing_without_content() {
7877 let s = format!("{}", NodeData::create_div());
7878 assert!(s.starts_with('<'), "{s}");
7879 assert!(s.ends_with("/>"), "content-less nodes self-close: {s}");
7880 }
7881
7882 #[test]
7883 fn node_data_display_wraps_text_content_in_a_tag_pair() {
7884 let s = format!("{}", NodeData::create_text("hello"));
7885 assert!(s.starts_with('<'));
7886 assert!(s.ends_with('>'));
7887 assert!(s.contains("hello"), "{s}");
7888 assert!(!s.ends_with("/>"), "a node with content must not self-close");
7889 }
7890
7891 #[test]
7892 fn node_data_display_does_not_panic_on_hostile_text() {
7893 for text in [
7897 "",
7898 "<script>alert(1)</script>",
7899 "\" onload=\"x",
7900 "日本語 🎉",
7901 "line\nbreak\ttab",
7902 ] {
7903 let s = format!("{}", NodeData::create_text(text));
7904 assert!(s.contains(text), "Display dropped content for {text:?}");
7905 }
7906 }
7907
7908 #[test]
7909 fn node_data_display_survives_a_huge_text_payload() {
7910 let big = huge_unicode_string();
7911 let s = format!("{}", NodeData::create_text(big.clone()));
7912 assert!(s.len() > big.len());
7913 }
7914
7915 #[test]
7916 fn debug_print_end_matches_the_node_tag() {
7917 let s = NodeData::create_div().debug_print_end();
7918 assert!(s.starts_with("</"));
7919 assert!(s.ends_with('>'));
7920 }
7921
7922 #[test]
7927 fn set_node_type_replaces_the_type_and_keeps_the_attributes() {
7928 let mut nd = NodeData::create_div();
7929 nd.add_id("keep".into());
7930 nd.set_node_type(NodeType::Span);
7931 assert!(nd.is_node_type(NodeType::Span));
7932 assert!(nd.has_id("keep"), "changing the tag must not drop attributes");
7933 }
7934
7935 #[test]
7936 fn add_callback_appends_and_get_callbacks_reflects_it() {
7937 let mut nd = NodeData::create_div();
7938 assert!(nd.get_callbacks().as_ref().is_empty());
7939
7940 nd.add_callback(
7941 EventFilter::Hover(HoverEventFilter::MouseUp),
7942 RefAny::new(1u32),
7943 0usize,
7944 );
7945 nd.add_callback(
7946 EventFilter::Focus(FocusEventFilter::MouseDown),
7947 RefAny::new(2u32),
7948 1usize,
7949 );
7950 assert_eq!(nd.get_callbacks().as_ref().len(), 2);
7951 assert_eq!(
7952 nd.get_callbacks().as_ref()[0].event,
7953 EventFilter::Hover(HoverEventFilter::MouseUp)
7954 );
7955 }
7956
7957 #[test]
7958 fn add_css_property_appends_an_inline_rule() {
7959 use azul_css::props::property::{CssProperty, CssPropertyType};
7960
7961 let mut nd = NodeData::create_div();
7962 assert!(nd.get_style().rules.as_ref().is_empty());
7963
7964 nd.add_css_property(CssPropertyWithConditions {
7965 property: CssProperty::const_none(CssPropertyType::Display),
7966 apply_if: Vec::new().into(),
7967 });
7968 assert_eq!(nd.get_style().rules.as_ref().len(), 1);
7969
7970 nd.add_css_property(CssPropertyWithConditions {
7971 property: CssProperty::const_none(CssPropertyType::Display),
7972 apply_if: Vec::new().into(),
7973 });
7974 assert_eq!(
7975 nd.get_style().rules.as_ref().len(),
7976 2,
7977 "add_css_property appends, it does not replace"
7978 );
7979 }
7980
7981 #[test]
7982 fn set_style_replaces_whereas_set_css_appends() {
7983 let mut nd = NodeData::create_div();
7984 nd.set_css("color: red;");
7985 let after_first = nd.get_style().rules.as_ref().len();
7986 assert!(after_first > 0);
7987
7988 nd.set_css("color: blue;");
7989 assert!(
7990 nd.get_style().rules.as_ref().len() > after_first,
7991 "set_css appends to the existing inline style"
7992 );
7993
7994 nd.set_style(azul_css::css::Css {
7995 rules: Vec::new().into(),
7996 });
7997 assert!(
7998 nd.get_style().rules.as_ref().is_empty(),
7999 "set_style replaces wholesale"
8000 );
8001 }
8002
8003 #[test]
8004 fn set_css_with_empty_and_malformed_input_does_not_panic() {
8005 for style in [
8006 "",
8007 " ",
8008 ";;;;",
8009 "color",
8010 "color:",
8011 ":",
8012 "}",
8013 "{",
8014 "color: ;",
8015 "not-a-property: not-a-value;",
8016 ":hover {",
8017 "@os {",
8018 "color: red", "\u{0}color: red;", "color: 日本語;",
8021 ] {
8022 let nd = NodeData::create_div().with_css(style);
8023 let _ = nd.get_style().rules.as_ref().len();
8026 }
8027 }
8028
8029 #[test]
8030 fn swap_with_default_returns_the_original_and_leaves_a_div() {
8031 let mut nd = NodeData::create_text("payload");
8032 let taken = nd.swap_with_default();
8033 assert!(taken.is_text_node());
8034 assert!(nd.is_node_type(NodeType::Div), "the slot becomes a fresh div");
8035 assert!(nd.attributes().as_ref().is_empty());
8036 }
8037
8038 #[test]
8039 fn node_data_builders_are_equivalent_to_their_setters() {
8040 let built = NodeData::create_div()
8041 .with_tab_index(TabIndex::Auto)
8042 .with_contenteditable(true)
8043 .with_node_type(NodeType::Span);
8044
8045 let mut set = NodeData::create_div();
8046 set.set_tab_index(TabIndex::Auto);
8047 set.set_contenteditable(true);
8048 set.set_node_type(NodeType::Span);
8049
8050 assert_eq!(built, set);
8051 }
8052
8053 #[test]
8058 fn node_data_vec_as_container_is_empty_for_an_empty_vec() {
8059 let v: NodeDataVec = Vec::new().into();
8060 assert_eq!(v.as_container().internal.len(), 0);
8061 }
8062
8063 #[test]
8064 fn node_data_vec_containers_expose_and_mutate_the_backing_slice() {
8065 let mut v: NodeDataVec = vec![
8066 NodeData::create_div(),
8067 NodeData::create_br(),
8068 NodeData::create_text("t"),
8069 ]
8070 .into();
8071 assert_eq!(v.as_container().internal.len(), 3);
8072 assert!(v.as_container().internal[2].is_text_node());
8073
8074 v.as_container_mut().internal[0].set_node_type(NodeType::Span);
8075 assert!(v.as_container().internal[0].is_node_type(NodeType::Span));
8076 }
8077
8078 #[test]
8083 fn dom_default_is_an_empty_body() {
8084 let d = Dom::default();
8085 assert!(d.root.is_node_type(NodeType::Body));
8086 assert_eq!(d.estimated_total_children, 0);
8087 assert_eq!(d.node_count(), 1);
8088 }
8089
8090 #[test]
8091 fn dom_set_children_recomputes_the_estimate_from_scratch() {
8092 let child = Dom::create_div().with_child(Dom::create_div());
8093 let mut parent = Dom::create_div();
8094 parent.add_child(Dom::create_div());
8095 assert_eq!(parent.estimated_total_children, 1);
8096
8097 parent.set_children(vec![child].into());
8099 assert_eq!(parent.estimated_total_children, 2);
8100 assert_eq!(
8101 parent.estimated_total_children,
8102 parent.recompute_estimated_total_children()
8103 );
8104 }
8105
8106 #[test]
8107 fn dom_set_children_with_an_empty_vec_zeroes_the_estimate() {
8108 let mut d = Dom::create_div().with_child(Dom::create_div().with_child(Dom::create_div()));
8109 assert_eq!(d.estimated_total_children, 2);
8110 d.set_children(Vec::new().into());
8111 assert_eq!(d.estimated_total_children, 0);
8112 assert_eq!(d.node_count(), 1);
8113 }
8114
8115 #[test]
8116 fn dom_deeply_nested_chain_keeps_an_exact_estimate() {
8117 const DEPTH: usize = 256;
8119 let mut d = Dom::create_div();
8120 for _ in 0..DEPTH {
8121 d = Dom::create_div().with_child(d);
8122 }
8123 assert_eq!(d.estimated_total_children, DEPTH);
8124 assert_eq!(d.node_count(), DEPTH + 1);
8125 assert_eq!(d.recompute_estimated_total_children(), DEPTH);
8126 }
8127
8128 #[test]
8129 fn dom_very_wide_child_list_keeps_an_exact_estimate() {
8130 const WIDTH: usize = 5_000;
8131 let children: Vec<Dom> = (0..WIDTH).map(|_| Dom::create_div()).collect();
8132 let d = Dom::create_div().with_children(children.into());
8133 assert_eq!(d.estimated_total_children, WIDTH);
8134 assert_eq!(d.node_count(), WIDTH + 1);
8135 }
8136
8137 #[test]
8138 fn dom_from_iterator_counts_nested_grandchildren() {
8139 let empty: Dom = Vec::new().into_iter().collect();
8140 assert_eq!(empty.estimated_total_children, 0);
8141 assert!(empty.root.is_node_type(NodeType::Div));
8142
8143 let d: Dom = vec![
8145 Dom::create_div().with_child(Dom::create_div()),
8146 Dom::create_div(),
8147 ]
8148 .into_iter()
8149 .collect();
8150 assert_eq!(d.estimated_total_children, 3);
8151 assert_eq!(d.estimated_total_children, d.recompute_estimated_total_children());
8152 assert_eq!(d.node_count(), 4);
8153 }
8154
8155 #[test]
8156 fn dom_fixup_repairs_a_corrupted_estimate_at_every_depth() {
8157 let mut d = Dom::create_div()
8158 .with_child(Dom::create_div().with_child(Dom::create_div()))
8159 .with_child(Dom::create_div());
8160
8161 d.estimated_total_children = 0;
8164 d.children.as_mut()[0].estimated_total_children = 99;
8165
8166 let repaired = d.fixup_children_estimated();
8167 assert_eq!(repaired, 3);
8168 assert_eq!(d.children.as_ref()[0].estimated_total_children, 1);
8169 assert_eq!(
8170 d.estimated_total_children,
8171 d.recompute_estimated_total_children()
8172 );
8173 }
8174
8175 #[test]
8176 fn dom_fixup_on_a_leaf_zeroes_a_bogus_estimate() {
8177 let mut d = Dom::create_div();
8178 d.estimated_total_children = usize::MAX;
8179 assert_eq!(d.fixup_children_estimated(), 0);
8180 assert_eq!(d.node_count(), 1, "node_count is safe again after fixup");
8181 }
8182
8183 #[cfg(debug_assertions)]
8188 #[test]
8189 #[should_panic(expected = "overflow")]
8190 fn dom_node_count_overflows_on_a_corrupted_max_estimate() {
8191 let mut d = Dom::create_div();
8192 d.estimated_total_children = usize::MAX;
8193 let _ = d.node_count();
8194 }
8195
8196 #[test]
8197 fn dom_swap_with_default_returns_the_original_tree() {
8198 let mut d = Dom::create_div().with_child(Dom::create_div());
8199 let taken = d.swap_with_default();
8200 assert_eq!(taken.estimated_total_children, 1);
8201 assert_eq!(d.estimated_total_children, 0, "the slot is reset");
8202 assert!(d.root.is_node_type(NodeType::Div));
8203 }
8204
8205 #[test]
8210 fn dom_with_id_and_with_class_apply_to_the_root() {
8211 let d = Dom::create_div()
8212 .with_id("root".into())
8213 .with_class("card".into());
8214 assert!(d.root.has_id("root"));
8215 assert!(d.root.has_class("card"));
8216 }
8217
8218 #[test]
8219 fn dom_with_attribute_appends_and_with_attributes_replaces() {
8220 let d = Dom::create_div()
8221 .with_attribute(AttributeType::Href("/a".into()))
8222 .with_attribute(AttributeType::Alt("alt".into()));
8223 assert_eq!(d.root.attributes().as_ref().len(), 2);
8224
8225 let d = d.with_attributes(vec![AttributeType::Disabled].into());
8226 assert_eq!(
8227 d.root.attributes().as_ref().len(),
8228 1,
8229 "with_attributes replaces wholesale"
8230 );
8231 assert_eq!(d.root.attributes().as_ref()[0], AttributeType::Disabled);
8232 }
8233
8234 #[test]
8235 fn dom_add_component_css_stacks_stylesheets() {
8236 let mut d = Dom::create_div();
8237 assert!(d.css.as_ref().is_empty());
8238 d.set_css("color: red;");
8239 d.set_css("color: blue;");
8240 assert_eq!(d.css.as_ref().len(), 2, "each set_css pushes a stylesheet");
8241
8242 d.set_component_css(Vec::new().into());
8243 assert!(d.css.as_ref().is_empty(), "set_component_css replaces");
8244 }
8245
8246 #[test]
8247 fn dom_with_css_does_not_panic_on_malformed_input() {
8248 for style in ["", "}}}", "@os {", "color:", "\u{0}"] {
8249 let d = Dom::create_div().with_css(style);
8250 assert_eq!(d.css.as_ref().len(), 1, "a Css is pushed even if it parses empty");
8251 }
8252 }
8253
8254 #[test]
8255 fn dom_text_helpers_produce_a_text_child() {
8256 let d = Dom::create_h1_with_text("Title");
8257 assert!(d.root.is_node_type(NodeType::H1));
8258 assert_eq!(d.estimated_total_children, 1);
8259 assert!(d.children.as_ref()[0].root.is_text_node());
8260 }
8261
8262 #[test]
8263 fn dom_create_geolocation_probe_carries_its_config() {
8264 let cfg = crate::geolocation::GeolocationProbeConfig {
8265 high_accuracy: true,
8266 background: false,
8267 max_accuracy_m: 25.0,
8268 min_interval_ms: 1_000,
8269 };
8270 let d = Dom::create_geolocation_probe(cfg);
8271 match d.root.get_node_type() {
8272 NodeType::GeolocationProbe(c) => {
8273 assert!(c.high_accuracy);
8274 assert_eq!(c.min_interval_ms, 1_000);
8275 }
8276 other => panic!("expected GeolocationProbe, got {other:?}"),
8277 }
8278 }
8279
8280 #[test]
8281 fn dom_clone_and_eq_agree_on_a_nested_tree() {
8282 let d = Dom::create_div()
8283 .with_id("r".into())
8284 .with_child(Dom::create_text("a"))
8285 .with_child(Dom::create_div().with_child(Dom::create_text("b")));
8286 let c = d.clone();
8287 assert_eq!(d, c);
8288 assert_eq!(hash_of(&d), hash_of(&c));
8289 assert_eq!(c.estimated_total_children, 3);
8291 assert_eq!(c.node_count(), 4);
8292 }
8293
8294 #[test]
8295 fn dom_debug_does_not_panic_on_a_nested_tree() {
8296 let d = Dom::create_div()
8297 .with_child(Dom::create_text("日本語 🎉"))
8298 .with_child(Dom::create_div().with_child(Dom::create_br()));
8299 let s = format!("{d:?}");
8300 assert!(s.contains("Dom"));
8301 assert!(s.contains("estimated_total_children"));
8302 }
8303
8304 #[test]
8309 fn dom_id_root_is_zero_and_is_the_default() {
8310 assert_eq!(DomId::ROOT_ID.inner, 0);
8311 assert_eq!(DomId::default(), DomId::ROOT_ID);
8312 assert_eq!(format!("{}", DomId::ROOT_ID), "0");
8313 assert_eq!(format!("{}", DomId { inner: usize::MAX }), usize::MAX.to_string());
8314 }
8315
8316 #[test]
8317 fn dom_node_id_root_points_at_the_root_dom_and_no_node() {
8318 assert_eq!(DomNodeId::ROOT.dom, DomId::ROOT_ID);
8319 assert_eq!(DomNodeId::ROOT.node, NodeHierarchyItemId::NONE);
8320 }
8321}