raui_core/widget/
mod.rs

1//! Widget types and the core component collection
2
3pub mod component;
4pub mod context;
5pub mod node;
6pub mod unit;
7pub mod utils;
8
9use crate::{
10    Prefab, PropsData,
11    application::Application,
12    props::PropsData,
13    widget::{
14        context::{WidgetContext, WidgetMountOrChangeContext, WidgetUnmountContext},
15        node::WidgetNode,
16    },
17};
18use serde::{Deserialize, Serialize};
19use std::{
20    borrow::Cow,
21    collections::hash_map::DefaultHasher,
22    convert::TryFrom,
23    hash::{Hash, Hasher},
24    ops::{Deref, Range},
25    str::FromStr,
26    sync::{Arc, RwLock},
27};
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct WidgetIdDef(pub String);
31
32impl From<WidgetId> for WidgetIdDef {
33    fn from(data: WidgetId) -> Self {
34        Self(data.to_string())
35    }
36}
37
38#[derive(Debug, Clone, Copy)]
39pub struct WidgetIdMetaParam<'a> {
40    pub name: &'a str,
41    pub value: Option<&'a str>,
42}
43
44impl WidgetIdMetaParam<'_> {
45    pub fn is_flag(&self) -> bool {
46        self.value.is_none()
47    }
48
49    pub fn has_value(&self) -> bool {
50        self.value.is_some()
51    }
52}
53
54#[derive(Debug, Clone, Copy)]
55pub struct WidgetIdMetaParams<'a>(&'a str);
56
57impl<'a> WidgetIdMetaParams<'a> {
58    pub fn new(meta: &'a str) -> Self {
59        Self(meta)
60    }
61
62    pub fn iter(&'_ self) -> impl Iterator<Item = WidgetIdMetaParam<'_>> {
63        self.0.split('&').filter_map(|part| {
64            if let Some(index) = part.find('=') {
65                let name = &part[0..index];
66                let value = &part[(index + b"=".len())..];
67                if name.is_empty() {
68                    None
69                } else {
70                    Some(WidgetIdMetaParam {
71                        name,
72                        value: Some(value),
73                    })
74                }
75            } else if part.is_empty() {
76                None
77            } else {
78                Some(WidgetIdMetaParam {
79                    name: part,
80                    value: None,
81                })
82            }
83        })
84    }
85
86    pub fn find(&'_ self, name: &str) -> Option<WidgetIdMetaParam<'_>> {
87        self.iter().find(|param| param.name == name)
88    }
89
90    pub fn has_flag(&self, name: &str) -> bool {
91        self.iter()
92            .any(|param| param.name == name && param.is_flag())
93    }
94
95    pub fn find_value(&self, name: &str) -> Option<&str> {
96        self.iter().find_map(|param| {
97            if param.name == name {
98                param.value
99            } else {
100                None
101            }
102        })
103    }
104}
105
106#[derive(PropsData, Default, Clone, Serialize, Deserialize)]
107#[serde(try_from = "WidgetIdDef")]
108#[serde(into = "WidgetIdDef")]
109pub struct WidgetId {
110    id: String,
111    type_name: Range<usize>,
112    /// [(key range, meta range)]
113    parts: Vec<(Range<usize>, Range<usize>)>,
114}
115
116impl WidgetId {
117    pub fn empty() -> Self {
118        Self {
119            id: ":".to_owned(),
120            type_name: 0..0,
121            parts: Default::default(),
122        }
123    }
124
125    pub fn new(type_name: &str, path: &[Cow<'_, str>]) -> Self {
126        if path.is_empty() {
127            return Self {
128                id: format!("{type_name}:"),
129                type_name: 0..type_name.len(),
130                parts: Default::default(),
131            };
132        }
133        let count = type_name.len()
134            + b":".len()
135            + path.iter().map(|part| part.len()).sum::<usize>()
136            + path.len().saturating_sub(1) * b"/".len();
137        let mut result = String::with_capacity(count);
138        let mut position = result.len();
139        result.push_str(type_name);
140        let type_name = 0..result.len();
141        result.push(':');
142        let parts = path
143            .iter()
144            .enumerate()
145            .map(|(index, part)| {
146                if index > 0 {
147                    result.push('/');
148                }
149                position = result.len();
150                result.push_str(part);
151                let range = position..result.len();
152                if let Some(index) = part.find('?') {
153                    let key = range.start..(range.start + index);
154                    let meta = (range.start + index + b"?".len())..range.end;
155                    (key, meta)
156                } else {
157                    let meta = range.end..range.end;
158                    (range, meta)
159                }
160            })
161            .collect::<Vec<_>>();
162        Self {
163            id: result,
164            type_name,
165            parts,
166        }
167    }
168
169    pub fn push(&self, part: &str) -> Self {
170        let count = self.id.len() + b"/".len();
171        let mut result = String::with_capacity(count);
172        result.push_str(&self.id);
173        if self.depth() > 0 {
174            result.push('/');
175        }
176        let position = result.len();
177        result.push_str(part);
178        let range = position..result.len();
179        let (key, meta) = if let Some(index) = part.find('?') {
180            let key = range.start..(range.start + index);
181            let meta = (range.start + index + b"?".len())..range.end;
182            (key, meta)
183        } else {
184            let meta = range.end..range.end;
185            (range, meta)
186        };
187        let parts = self
188            .parts
189            .iter()
190            .cloned()
191            .chain(std::iter::once((key, meta)))
192            .collect();
193        Self {
194            id: result,
195            type_name: self.type_name.to_owned(),
196            parts,
197        }
198    }
199
200    pub fn pop(&self) -> Self {
201        let parts = self.parts[0..(self.parts.len().saturating_sub(1))].to_owned();
202        let result = if let Some(range) = parts.last() {
203            self.id[0..range.1.end].to_owned()
204        } else {
205            format!("{}:", self.type_name())
206        };
207        Self {
208            id: result,
209            type_name: self.type_name.to_owned(),
210            parts,
211        }
212    }
213
214    #[inline]
215    pub fn is_valid(&self) -> bool {
216        !self.id.is_empty()
217    }
218
219    #[inline]
220    pub fn depth(&self) -> usize {
221        self.parts.len()
222    }
223
224    #[inline]
225    pub fn type_name(&self) -> &str {
226        &self.id.as_str()[self.type_name.clone()]
227    }
228
229    #[inline]
230    pub fn path(&self) -> &str {
231        if self.parts.is_empty() {
232            &self.id.as_str()[0..0]
233        } else {
234            &self.id.as_str()[self.parts.first().unwrap().0.start..self.parts.last().unwrap().1.end]
235        }
236    }
237
238    #[inline]
239    pub fn key(&self) -> &str {
240        if self.parts.is_empty() {
241            &self.id.as_str()[0..0]
242        } else {
243            &self.id[self.parts.last().cloned().unwrap().0]
244        }
245    }
246
247    #[inline]
248    pub fn meta(&self) -> &str {
249        if self.parts.is_empty() {
250            &self.id.as_str()[0..0]
251        } else {
252            &self.id[self.parts.last().cloned().unwrap().1]
253        }
254    }
255
256    #[inline]
257    pub fn part(&self, index: usize) -> Option<&str> {
258        self.parts
259            .get(index)
260            .cloned()
261            .map(|(key, meta)| &self.id[key.start..meta.end])
262    }
263
264    #[inline]
265    pub fn part_key_meta(&self, index: usize) -> Option<(&str, &str)> {
266        self.parts
267            .get(index)
268            .cloned()
269            .map(|(key, meta)| (&self.id[key], &self.id[meta]))
270    }
271
272    pub fn range(&self, from_inclusive: usize, to_exclusive: usize) -> &str {
273        if self.parts.is_empty() {
274            return &self.id[0..0];
275        }
276        let start = from_inclusive.min(self.parts.len().saturating_sub(1));
277        let end = to_exclusive
278            .saturating_sub(1)
279            .max(start)
280            .min(self.parts.len().saturating_sub(1));
281        let start = self.parts[start].0.start;
282        let end = self.parts[end].1.end;
283        &self.id[start..end]
284    }
285
286    pub fn is_subset_of(&self, other: &Self) -> bool {
287        match self.distance_to(other) {
288            Ok(v) => v < 0,
289            _ => false,
290        }
291    }
292
293    pub fn is_superset_of(&self, other: &Self) -> bool {
294        match self.distance_to(other) {
295            Ok(v) => v > 0,
296            _ => false,
297        }
298    }
299
300    pub fn distance_to(&self, other: &Self) -> Result<isize, isize> {
301        for index in 0..self.depth().max(other.depth()) {
302            match (self.part(index), other.part(index)) {
303                (None, None) => return Ok(0),
304                (None, Some(_)) | (Some(_), None) => {
305                    return Ok(self.depth() as isize - other.depth() as isize);
306                }
307                (Some(a), Some(b)) => {
308                    if a != b {
309                        return Err(index as isize - other.depth() as isize);
310                    }
311                }
312            }
313        }
314        Ok(0)
315    }
316
317    #[inline]
318    pub fn parts(&self) -> impl Iterator<Item = &str> + '_ {
319        self.parts
320            .iter()
321            .map(move |(key, meta)| &self.id[key.start..meta.end])
322    }
323
324    #[inline]
325    pub fn parts_key_meta(&self) -> impl Iterator<Item = (&str, &str)> + '_ {
326        self.parts
327            .iter()
328            .cloned()
329            .map(move |(key, meta)| (&self.id[key], &self.id[meta]))
330    }
331
332    #[inline]
333    pub fn rparts(&self) -> impl Iterator<Item = &str> + '_ {
334        self.parts
335            .iter()
336            .rev()
337            .map(move |(key, meta)| &self.id[key.start..meta.end])
338    }
339
340    #[inline]
341    pub fn rparts_key_meta(&self) -> impl Iterator<Item = (&str, &str)> + '_ {
342        self.parts
343            .iter()
344            .rev()
345            .cloned()
346            .map(move |(key, meta)| (&self.id[key], &self.id[meta]))
347    }
348
349    pub fn hashed_value(&self) -> u64 {
350        let mut hasher = DefaultHasher::new();
351        self.hash(&mut hasher);
352        hasher.finish()
353    }
354}
355
356impl Hash for WidgetId {
357    fn hash<H: Hasher>(&self, state: &mut H) {
358        self.id.hash(state);
359    }
360}
361
362impl PartialEq for WidgetId {
363    fn eq(&self, other: &Self) -> bool {
364        self.id == other.id
365    }
366}
367
368impl Eq for WidgetId {}
369
370impl Deref for WidgetId {
371    type Target = str;
372
373    fn deref(&self) -> &Self::Target {
374        &self.id
375    }
376}
377
378impl AsRef<str> for WidgetId {
379    fn as_ref(&self) -> &str {
380        &self.id
381    }
382}
383
384impl FromStr for WidgetId {
385    type Err = ();
386
387    fn from_str(s: &str) -> Result<Self, Self::Err> {
388        if let Some(index) = s.find(':') {
389            let type_name = s[..index].to_owned();
390            let rest = &s[(index + b":".len())..];
391            let path = rest.split('/').map(Cow::Borrowed).collect::<Vec<_>>();
392            Ok(Self::new(&type_name, &path))
393        } else {
394            Err(())
395        }
396    }
397}
398
399impl std::fmt::Debug for WidgetId {
400    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
401        f.debug_struct("WidgetId")
402            .field("id", &self.id)
403            .field("type_name", &&self.id[self.type_name.clone()])
404            .field(
405                "parts",
406                &self
407                    .parts
408                    .iter()
409                    .map(|(key, meta)| (&self.id[key.to_owned()], &self.id[meta.to_owned()]))
410                    .collect::<Vec<_>>(),
411            )
412            .finish()
413    }
414}
415
416impl std::fmt::Display for WidgetId {
417    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
418        f.write_str(self.as_ref())
419    }
420}
421
422impl TryFrom<WidgetIdDef> for WidgetId {
423    type Error = String;
424
425    fn try_from(id: WidgetIdDef) -> Result<Self, Self::Error> {
426        match Self::from_str(&id.0) {
427            Ok(id) => Ok(id),
428            Err(_) => Err(format!("Could not parse id: `{}`", id.0)),
429        }
430    }
431}
432
433#[derive(Debug, Clone, PartialEq, Eq)]
434pub struct WidgetIdCommon {
435    id: Option<WidgetId>,
436    count: usize,
437}
438
439impl Default for WidgetIdCommon {
440    fn default() -> Self {
441        Self {
442            id: None,
443            count: usize::MAX,
444        }
445    }
446}
447
448impl WidgetIdCommon {
449    pub fn new(id: WidgetId) -> Self {
450        Self {
451            count: id.depth(),
452            id: Some(id),
453        }
454    }
455
456    pub fn include(&mut self, id: &WidgetId) -> &mut Self {
457        if self.id.is_none() {
458            self.id = Some(id.to_owned());
459            self.count = id.depth();
460            return self;
461        }
462        if let Some(source) = self.id.as_ref() {
463            for index in 0..self.count.min(id.depth()) {
464                if source.part(index) != id.part(index) {
465                    self.count = index;
466                    return self;
467                }
468            }
469        }
470        self
471    }
472
473    pub fn include_other(&mut self, other: &Self) -> &mut Self {
474        if let Some(id) = other.id.as_ref() {
475            if self.id.is_none() {
476                self.id = Some(id.to_owned());
477                self.count = other.count;
478                return self;
479            }
480            if let Some(source) = self.id.as_ref() {
481                for index in 0..self.count.min(other.count) {
482                    if source.part(index) != id.part(index) {
483                        self.count = index;
484                        return self;
485                    }
486                }
487            }
488        }
489        self
490    }
491
492    pub fn is_valid(&self) -> bool {
493        self.id.is_some()
494    }
495
496    pub fn parts(&self) -> Option<impl Iterator<Item = &str>> {
497        self.id
498            .as_ref()
499            .map(|id| (0..self.count).map_while(move |index| id.part(index)))
500    }
501
502    pub fn path(&self) -> Option<&str> {
503        self.id
504            .as_ref()
505            .map(|id| id.range(0, self.count))
506            .filter(|id| !id.is_empty())
507    }
508}
509
510impl<'a> FromIterator<&'a WidgetId> for WidgetIdCommon {
511    fn from_iter<T: IntoIterator<Item = &'a WidgetId>>(iter: T) -> Self {
512        let mut result = Self::default();
513        for id in iter {
514            result.include(id);
515        }
516        result
517    }
518}
519
520#[derive(Debug, Default, Clone, Serialize, Deserialize)]
521pub struct WidgetRefDef(pub Option<WidgetId>);
522
523impl From<WidgetRef> for WidgetRefDef {
524    fn from(data: WidgetRef) -> Self {
525        match data.0.read() {
526            Ok(data) => Self(data.clone()),
527            Err(_) => Default::default(),
528        }
529    }
530}
531
532#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]
533#[serde(from = "WidgetRefDef")]
534#[serde(into = "WidgetRefDef")]
535pub struct WidgetRef(#[serde(skip)] Arc<RwLock<Option<WidgetId>>>);
536
537impl WidgetRef {
538    pub fn new(id: WidgetId) -> Self {
539        Self(Arc::new(RwLock::new(Some(id))))
540    }
541
542    pub(crate) fn write(&mut self, id: WidgetId) {
543        if let Ok(mut data) = self.0.write() {
544            *data = Some(id);
545        }
546    }
547
548    pub fn read(&self) -> Option<WidgetId> {
549        if let Ok(data) = self.0.read() {
550            data.clone()
551        } else {
552            None
553        }
554    }
555
556    pub fn exists(&self) -> bool {
557        self.0
558            .read()
559            .ok()
560            .map(|data| data.is_some())
561            .unwrap_or_default()
562    }
563}
564
565impl PartialEq for WidgetRef {
566    fn eq(&self, other: &Self) -> bool {
567        Arc::ptr_eq(&self.0, &other.0)
568    }
569}
570
571impl From<WidgetRefDef> for WidgetRef {
572    fn from(data: WidgetRefDef) -> Self {
573        WidgetRef(Arc::new(RwLock::new(data.0)))
574    }
575}
576
577impl std::fmt::Display for WidgetRef {
578    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
579        if let Ok(id) = self.0.read() {
580            if let Some(id) = id.as_ref() {
581                write!(f, "{id}")
582            } else {
583                Ok(())
584            }
585        } else {
586            Ok(())
587        }
588    }
589}
590
591#[derive(PropsData, Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
592pub enum WidgetIdOrRef {
593    #[default]
594    None,
595    Id(WidgetId),
596    Ref(WidgetRef),
597}
598
599impl WidgetIdOrRef {
600    #[inline]
601    pub fn new_ref() -> Self {
602        Self::Ref(WidgetRef::default())
603    }
604
605    #[inline]
606    pub fn is_none(&self) -> bool {
607        matches!(self, Self::None)
608    }
609
610    #[inline]
611    pub fn is_some(&self) -> bool {
612        !self.is_none()
613    }
614
615    pub fn read(&self) -> Option<WidgetId> {
616        match self {
617            Self::None => None,
618            Self::Id(id) => Some(id.to_owned()),
619            Self::Ref(idref) => idref.read(),
620        }
621    }
622}
623
624impl From<()> for WidgetIdOrRef {
625    fn from(_: ()) -> Self {
626        Self::None
627    }
628}
629
630impl From<WidgetId> for WidgetIdOrRef {
631    fn from(v: WidgetId) -> Self {
632        Self::Id(v)
633    }
634}
635
636impl From<WidgetRef> for WidgetIdOrRef {
637    fn from(v: WidgetRef) -> Self {
638        Self::Ref(v)
639    }
640}
641
642impl std::fmt::Display for WidgetIdOrRef {
643    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
644        match self {
645            Self::None => Ok(()),
646            Self::Id(id) => write!(f, "{id}"),
647            Self::Ref(id) => write!(f, "{id}"),
648        }
649    }
650}
651
652#[derive(Clone)]
653pub enum FnWidget {
654    Pointer(fn(WidgetContext) -> WidgetNode),
655    Closure(Arc<dyn Fn(WidgetContext) -> WidgetNode + Send + Sync>),
656}
657
658impl FnWidget {
659    pub fn pointer(value: fn(WidgetContext) -> WidgetNode) -> Self {
660        Self::Pointer(value)
661    }
662
663    pub fn closure(value: impl Fn(WidgetContext) -> WidgetNode + Send + Sync + 'static) -> Self {
664        Self::Closure(Arc::new(value))
665    }
666
667    pub fn call(&self, context: WidgetContext) -> WidgetNode {
668        match self {
669            Self::Pointer(value) => value(context),
670            Self::Closure(value) => value(context),
671        }
672    }
673}
674
675#[derive(Default)]
676pub struct WidgetLifeCycle {
677    #[allow(clippy::type_complexity)]
678    mount: Vec<Box<dyn FnMut(WidgetMountOrChangeContext) + Send + Sync>>,
679    #[allow(clippy::type_complexity)]
680    change: Vec<Box<dyn FnMut(WidgetMountOrChangeContext) + Send + Sync>>,
681    #[allow(clippy::type_complexity)]
682    unmount: Vec<Box<dyn FnMut(WidgetUnmountContext) + Send + Sync>>,
683}
684
685impl WidgetLifeCycle {
686    pub fn mount<F>(&mut self, f: F)
687    where
688        F: 'static + FnMut(WidgetMountOrChangeContext) + Send + Sync,
689    {
690        self.mount.push(Box::new(f));
691    }
692
693    pub fn change<F>(&mut self, f: F)
694    where
695        F: 'static + FnMut(WidgetMountOrChangeContext) + Send + Sync,
696    {
697        self.change.push(Box::new(f));
698    }
699
700    pub fn unmount<F>(&mut self, f: F)
701    where
702        F: 'static + FnMut(WidgetUnmountContext) + Send + Sync,
703    {
704        self.unmount.push(Box::new(f));
705    }
706
707    #[allow(clippy::type_complexity)]
708    pub fn unwrap(
709        self,
710    ) -> (
711        Vec<Box<dyn FnMut(WidgetMountOrChangeContext) + Send + Sync>>,
712        Vec<Box<dyn FnMut(WidgetMountOrChangeContext) + Send + Sync>>,
713        Vec<Box<dyn FnMut(WidgetUnmountContext) + Send + Sync>>,
714    ) {
715        let Self {
716            mount,
717            change,
718            unmount,
719        } = self;
720        (mount, change, unmount)
721    }
722}
723
724pub fn none_widget(_: WidgetContext) -> WidgetNode {
725    Default::default()
726}
727
728pub fn setup(app: &mut Application) {
729    app.register_props::<()>("()");
730    app.register_props::<i8>("i8");
731    app.register_props::<i16>("i16");
732    app.register_props::<i32>("i32");
733    app.register_props::<i64>("i64");
734    app.register_props::<i128>("i128");
735    app.register_props::<u8>("u8");
736    app.register_props::<u16>("u16");
737    app.register_props::<u32>("u32");
738    app.register_props::<u64>("u64");
739    app.register_props::<u128>("u128");
740    app.register_props::<f32>("f32");
741    app.register_props::<f64>("f64");
742    app.register_props::<bool>("bool");
743    app.register_props::<String>("String");
744    app.register_props::<component::containers::anchor_box::AnchorProps>("AnchorProps");
745    app.register_props::<component::containers::anchor_box::PivotBoxProps>("PivotBoxProps");
746    app.register_props::<component::containers::content_box::ContentBoxProps>("ContentBoxProps");
747    app.register_props::<component::containers::flex_box::FlexBoxProps>("FlexBoxProps");
748    app.register_props::<component::containers::float_box::FloatBoxProps>("FloatBoxProps");
749    app.register_props::<component::containers::float_box::FloatBoxNotifyProps>(
750        "FloatBoxNotifyProps",
751    );
752    app.register_props::<component::containers::float_box::FloatBoxState>("FloatBoxState");
753    app.register_props::<component::containers::grid_box::GridBoxProps>("GridBoxProps");
754    app.register_props::<component::containers::horizontal_box::HorizontalBoxProps>(
755        "HorizontalBoxProps",
756    );
757    app.register_props::<component::containers::hidden_box::HiddenBoxProps>("HiddenBoxProps");
758    app.register_props::<component::containers::scroll_box::ScrollBoxOwner>("ScrollBoxOwner");
759    app.register_props::<component::containers::scroll_box::SideScrollbarsProps>(
760        "SideScrollbarsProps",
761    );
762    app.register_props::<component::containers::scroll_box::SideScrollbarsState>(
763        "SideScrollbarsState",
764    );
765    app.register_props::<component::containers::portal_box::PortalsContainer>("PortalsContainer");
766    app.register_props::<component::containers::responsive_box::MediaQueryExpression>(
767        "MediaQueryExpression",
768    );
769    app.register_props::<component::containers::responsive_box::ResponsiveBoxState>(
770        "ResponsiveBoxState",
771    );
772    app.register_props::<component::containers::size_box::SizeBoxProps>("SizeBoxProps");
773    app.register_props::<component::containers::switch_box::SwitchBoxProps>("SwitchBoxProps");
774    app.register_props::<component::containers::tabs_box::TabsBoxProps>("TabsBoxProps");
775    app.register_props::<component::containers::tabs_box::TabPlateProps>("TabPlateProps");
776    app.register_props::<component::containers::tooltip_box::TooltipState>("TooltipState");
777    app.register_props::<component::containers::variant_box::VariantBoxProps>("VariantBoxProps");
778    app.register_props::<component::containers::vertical_box::VerticalBoxProps>("VerticalBoxProps");
779    app.register_props::<component::containers::wrap_box::WrapBoxProps>("WrapBoxProps");
780    app.register_props::<component::image_box::ImageBoxProps>("ImageBoxProps");
781    app.register_props::<component::interactive::button::ButtonProps>("ButtonProps");
782    app.register_props::<component::interactive::button::ButtonNotifyProps>("ButtonNotifyProps");
783    app.register_props::<component::interactive::input_field::TextInputMode>("TextInputMode");
784    app.register_props::<component::interactive::input_field::TextInputProps>("TextInputProps");
785    app.register_props::<component::interactive::input_field::TextInputState>("TextInputState");
786    app.register_props::<component::interactive::input_field::TextInputNotifyProps>(
787        "TextInputNotifyProps",
788    );
789    app.register_props::<component::interactive::input_field::TextInputControlNotifyProps>(
790        "TextInputControlNotifyProps",
791    );
792    app.register_props::<component::interactive::options_view::OptionsViewMode>("OptionsViewMode");
793    app.register_props::<component::interactive::options_view::OptionsViewProps>(
794        "OptionsViewProps",
795    );
796    app.register_props::<component::interactive::slider_view::SliderViewProps>("SliderViewProps");
797    app.register_props::<component::interactive::navigation::NavItemActive>("NavItemActive");
798    app.register_props::<component::interactive::navigation::NavTrackingActive>(
799        "NavTrackingActive",
800    );
801    app.register_props::<component::interactive::navigation::NavContainerActive>(
802        "NavContainerActive",
803    );
804    app.register_props::<component::interactive::navigation::NavJumpLooped>("NavJumpLooped");
805    app.register_props::<component::interactive::navigation::NavJumpMapProps>("NavJumpMapProps");
806    app.register_props::<component::interactive::scroll_view::ScrollViewState>("ScrollViewState");
807    app.register_props::<component::interactive::scroll_view::ScrollViewRange>("ScrollViewRange");
808    app.register_props::<component::interactive::scroll_view::ScrollViewNotifyProps>(
809        "ScrollViewNotifyProps",
810    );
811    app.register_props::<component::MessageForwardProps>("MessageForwardProps");
812    app.register_props::<component::WidgetAlpha>("WidgetAlpha");
813    app.register_props::<component::space_box::SpaceBoxProps>("SpaceBoxProps");
814    app.register_props::<component::text_box::TextBoxProps>("TextBoxProps");
815    app.register_props::<unit::content::ContentBoxItemLayout>("ContentBoxItemLayout");
816    app.register_props::<unit::flex::FlexBoxItemLayout>("FlexBoxItemLayout");
817    app.register_props::<unit::grid::GridBoxItemLayout>("GridBoxItemLayout");
818
819    app.register_component("none_widget", FnWidget::pointer(none_widget));
820    app.register_component(
821        "area_box",
822        FnWidget::pointer(component::containers::area_box::area_box),
823    );
824    app.register_component(
825        "anchor_box",
826        FnWidget::pointer(component::containers::anchor_box::anchor_box),
827    );
828    app.register_component(
829        "pivot_box",
830        FnWidget::pointer(component::containers::anchor_box::pivot_box),
831    );
832    app.register_component(
833        "nav_content_box",
834        FnWidget::pointer(component::containers::content_box::nav_content_box),
835    );
836    app.register_component(
837        "content_box",
838        FnWidget::pointer(component::containers::content_box::content_box),
839    );
840    app.register_component(
841        "nav_flex_box",
842        FnWidget::pointer(component::containers::flex_box::nav_flex_box),
843    );
844    app.register_component(
845        "flex_box",
846        FnWidget::pointer(component::containers::flex_box::flex_box),
847    );
848    app.register_component(
849        "float_box",
850        FnWidget::pointer(component::containers::float_box::float_box),
851    );
852    app.register_component(
853        "nav_grid_box",
854        FnWidget::pointer(component::containers::grid_box::nav_grid_box),
855    );
856    app.register_component(
857        "grid_box",
858        FnWidget::pointer(component::containers::grid_box::grid_box),
859    );
860    app.register_component(
861        "nav_horizontal_box",
862        FnWidget::pointer(component::containers::horizontal_box::nav_horizontal_box),
863    );
864    app.register_component(
865        "horizontal_box",
866        FnWidget::pointer(component::containers::horizontal_box::horizontal_box),
867    );
868    app.register_component(
869        "nav_scroll_box",
870        FnWidget::pointer(component::containers::scroll_box::nav_scroll_box),
871    );
872    app.register_component(
873        "nav_scroll_box_side_scrollbars",
874        FnWidget::pointer(component::containers::scroll_box::nav_scroll_box_side_scrollbars),
875    );
876    app.register_component(
877        "portal_box",
878        FnWidget::pointer(component::containers::portal_box::portal_box),
879    );
880    app.register_component(
881        "responsive_box",
882        FnWidget::pointer(component::containers::responsive_box::responsive_box),
883    );
884    app.register_component(
885        "responsive_props_box",
886        FnWidget::pointer(component::containers::responsive_box::responsive_props_box),
887    );
888    app.register_component(
889        "size_box",
890        FnWidget::pointer(component::containers::size_box::size_box),
891    );
892    app.register_component(
893        "nav_switch_box",
894        FnWidget::pointer(component::containers::switch_box::nav_switch_box),
895    );
896    app.register_component(
897        "switch_box",
898        FnWidget::pointer(component::containers::switch_box::switch_box),
899    );
900    app.register_component(
901        "nav_tabs_box",
902        FnWidget::pointer(component::containers::tabs_box::nav_tabs_box),
903    );
904    app.register_component(
905        "tooltip_box",
906        FnWidget::pointer(component::containers::tooltip_box::tooltip_box),
907    );
908    app.register_component(
909        "portals_tooltip_box",
910        FnWidget::pointer(component::containers::tooltip_box::portals_tooltip_box),
911    );
912    app.register_component(
913        "variant_box",
914        FnWidget::pointer(component::containers::variant_box::variant_box),
915    );
916    app.register_component(
917        "nav_vertical_box",
918        FnWidget::pointer(component::containers::vertical_box::nav_vertical_box),
919    );
920    app.register_component(
921        "vertical_box",
922        FnWidget::pointer(component::containers::vertical_box::vertical_box),
923    );
924    app.register_component(
925        "wrap_box",
926        FnWidget::pointer(component::containers::wrap_box::wrap_box),
927    );
928    app.register_component(
929        "button",
930        FnWidget::pointer(component::interactive::button::button),
931    );
932    app.register_component(
933        "tracked_button",
934        FnWidget::pointer(component::interactive::button::tracked_button),
935    );
936    app.register_component(
937        "self_tracked_button",
938        FnWidget::pointer(component::interactive::button::self_tracked_button),
939    );
940    app.register_component(
941        "text_input",
942        FnWidget::pointer(component::interactive::input_field::text_input),
943    );
944    app.register_component(
945        "input_field",
946        FnWidget::pointer(component::interactive::input_field::input_field),
947    );
948    app.register_component(
949        "options_view",
950        FnWidget::pointer(component::interactive::options_view::options_view),
951    );
952    app.register_component(
953        "slider_view",
954        FnWidget::pointer(component::interactive::slider_view::slider_view),
955    );
956    app.register_component(
957        "navigation_barrier",
958        FnWidget::pointer(component::interactive::navigation::navigation_barrier),
959    );
960    app.register_component(
961        "tracking",
962        FnWidget::pointer(component::interactive::navigation::tracking),
963    );
964    app.register_component(
965        "self_tracking",
966        FnWidget::pointer(component::interactive::navigation::self_tracking),
967    );
968    app.register_component(
969        "space_box",
970        FnWidget::pointer(component::space_box::space_box),
971    );
972    app.register_component(
973        "image_box",
974        FnWidget::pointer(component::image_box::image_box),
975    );
976    app.register_component("text_box", FnWidget::pointer(component::text_box::text_box));
977}
978
979/// Helper to manually create a [`WidgetComponent`][crate::widget::component::WidgetComponent]
980/// struct from a function.
981///
982/// Users will not usually need this macro, but it can be useful in some advanced cases or where you
983/// don't want to use the [`widget`] macro.
984/// ```
985#[macro_export]
986macro_rules! make_widget {
987    ($type_id:path) => {{
988        let processor = $type_id;
989        let type_name = stringify!($type_id);
990        $crate::widget::component::WidgetComponent::new(
991            $crate::widget::FnWidget::pointer(processor),
992            type_name,
993        )
994    }};
995}
996
997/// A helper for getting the named children out of a widget context.
998#[macro_export]
999macro_rules! unpack_named_slots {
1000    ($map:expr => $name:ident) => {
1001        #[allow(unused_mut)]
1002        let mut $name = {
1003            let mut map = $map;
1004            match map.remove(stringify!($name)) {
1005                Some(widget) => widget,
1006                None => $crate::widget::node::WidgetNode::None,
1007            }
1008        };
1009    };
1010    ($map:expr => { $($name:ident),+ }) => {
1011        #[allow(unused_mut)]
1012        let ( $( mut $name ),+ ) = {
1013            let mut map = $map;
1014            (
1015                $(
1016                    {
1017                        match map.remove(stringify!($name)) {
1018                            Some(widget) => widget,
1019                            None => $crate::widget::node::WidgetNode::None,
1020                        }
1021                    }
1022                ),+
1023            )
1024        };
1025    };
1026}
1027
1028#[cfg(test)]
1029mod tests {
1030    use super::*;
1031
1032    #[test]
1033    fn test_widget_id() {
1034        let id = WidgetId::empty();
1035        assert_eq!(id.type_name(), "");
1036        assert_eq!(id.path(), "");
1037        assert_eq!(id.depth(), 0);
1038
1039        let id = WidgetId::new("type", &["parent".into(), "me".into()]);
1040        assert_eq!(id.to_string(), "type:parent/me".to_owned());
1041        assert_eq!(id.type_name(), "type");
1042        assert_eq!(id.parts().next().unwrap(), "parent");
1043        assert_eq!(id.key(), "me");
1044        assert_eq!(id.clone(), id);
1045
1046        let a = WidgetId::from_str("a:root/a").unwrap();
1047        let b = WidgetId::from_str("b:root/b").unwrap();
1048        let mut common = WidgetIdCommon::default();
1049        assert_eq!(common.path(), None);
1050        common.include(&a);
1051        assert_eq!(common.path(), Some("root/a"));
1052        let mut common = WidgetIdCommon::default();
1053        common.include(&b);
1054        assert_eq!(common.path(), Some("root/b"));
1055        common.include(&a);
1056        assert_eq!(common.path(), Some("root"));
1057
1058        let id = WidgetId::from_str("type:parent/me").unwrap();
1059        assert_eq!(&*id, "type:parent/me");
1060        assert_eq!(id.path(), "parent/me");
1061        let id = id.pop();
1062        assert_eq!(&*id, "type:parent");
1063        assert_eq!(id.path(), "parent");
1064        let id = id.pop();
1065        assert_eq!(&*id, "type:");
1066        assert_eq!(id.path(), "");
1067        let id = id.push("parent");
1068        assert_eq!(&*id, "type:parent");
1069        assert_eq!(id.path(), "parent");
1070        let id = id.push("me");
1071        assert_eq!(&*id, "type:parent/me");
1072        assert_eq!(id.path(), "parent/me");
1073        assert_eq!(id.key(), "me");
1074        assert_eq!(id.meta(), "");
1075        let id = id.push("with?meta");
1076        assert_eq!(&*id, "type:parent/me/with?meta");
1077        assert_eq!(id.path(), "parent/me/with?meta");
1078        assert_eq!(id.key(), "with");
1079        assert_eq!(id.meta(), "meta");
1080
1081        let a = WidgetId::from_str("a:root/a/b/c").unwrap();
1082        let b = WidgetId::from_str("b:root/a/b/c").unwrap();
1083        assert_eq!(a.distance_to(&b), Ok(0));
1084        assert!(!a.is_subset_of(&b));
1085        assert!(!a.is_superset_of(&b));
1086        let a = WidgetId::from_str("a:root/a/b").unwrap();
1087        let b = WidgetId::from_str("b:root/a/b/c").unwrap();
1088        assert_eq!(a.distance_to(&b), Ok(-1));
1089        assert!(a.is_subset_of(&b));
1090        assert!(!a.is_superset_of(&b));
1091        let a = WidgetId::from_str("a:root/a").unwrap();
1092        let b = WidgetId::from_str("b:root/a/b/c").unwrap();
1093        assert_eq!(a.distance_to(&b), Ok(-2));
1094        assert!(a.is_subset_of(&b));
1095        assert!(!a.is_superset_of(&b));
1096        let a = WidgetId::from_str("a:root").unwrap();
1097        let b = WidgetId::from_str("b:root/a/b/c").unwrap();
1098        assert_eq!(a.distance_to(&b), Ok(-3));
1099        assert!(a.is_subset_of(&b));
1100        assert!(!a.is_superset_of(&b));
1101        let a = WidgetId::from_str("a:root/a/b/c").unwrap();
1102        let b = WidgetId::from_str("b:root/a/b").unwrap();
1103        assert_eq!(a.distance_to(&b), Ok(1));
1104        assert!(!a.is_subset_of(&b));
1105        assert!(a.is_superset_of(&b));
1106        let a = WidgetId::from_str("a:root/a/b/c").unwrap();
1107        let b = WidgetId::from_str("b:root/a").unwrap();
1108        assert_eq!(a.distance_to(&b), Ok(2));
1109        assert!(!a.is_subset_of(&b));
1110        assert!(a.is_superset_of(&b));
1111        let a = WidgetId::from_str("a:root/a/b/c").unwrap();
1112        let b = WidgetId::from_str("b:root").unwrap();
1113        assert_eq!(a.distance_to(&b), Ok(3));
1114        assert!(!a.is_subset_of(&b));
1115        assert!(a.is_superset_of(&b));
1116        let a = WidgetId::from_str("a:root/a/b/x").unwrap();
1117        let b = WidgetId::from_str("b:root/a/b/c").unwrap();
1118        assert_eq!(a.distance_to(&b), Err(-1));
1119        assert!(!a.is_subset_of(&b));
1120        assert!(!a.is_superset_of(&b));
1121        let a = WidgetId::from_str("a:root/a/x/y").unwrap();
1122        let b = WidgetId::from_str("b:root/a/b/c").unwrap();
1123        assert_eq!(a.distance_to(&b), Err(-2));
1124        assert!(!a.is_subset_of(&b));
1125        assert!(!a.is_superset_of(&b));
1126        let a = WidgetId::from_str("a:root/x/y/z").unwrap();
1127        let b = WidgetId::from_str("b:root/a/b/c").unwrap();
1128        assert_eq!(a.distance_to(&b), Err(-3));
1129        assert!(!a.is_subset_of(&b));
1130        assert!(!a.is_superset_of(&b));
1131    }
1132}