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    application::Application,
11    props::PropsData,
12    widget::{
13        context::{WidgetContext, WidgetMountOrChangeContext, WidgetUnmountContext},
14        node::WidgetNode,
15    },
16    Prefab, PropsData,
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            .cloned()
322            .map(move |(key, meta)| &self.id[key.start..meta.end])
323    }
324
325    #[inline]
326    pub fn parts_key_meta(&self) -> impl Iterator<Item = (&str, &str)> + '_ {
327        self.parts
328            .iter()
329            .cloned()
330            .map(move |(key, meta)| (&self.id[key], &self.id[meta]))
331    }
332
333    #[inline]
334    pub fn rparts(&self) -> impl Iterator<Item = &str> + '_ {
335        self.parts
336            .iter()
337            .rev()
338            .cloned()
339            .map(move |(key, meta)| &self.id[key.start..meta.end])
340    }
341
342    #[inline]
343    pub fn rparts_key_meta(&self) -> impl Iterator<Item = (&str, &str)> + '_ {
344        self.parts
345            .iter()
346            .rev()
347            .cloned()
348            .map(move |(key, meta)| (&self.id[key], &self.id[meta]))
349    }
350
351    pub fn hashed_value(&self) -> u64 {
352        let mut hasher = DefaultHasher::new();
353        self.hash(&mut hasher);
354        hasher.finish()
355    }
356}
357
358impl Hash for WidgetId {
359    fn hash<H: Hasher>(&self, state: &mut H) {
360        self.id.hash(state);
361    }
362}
363
364impl PartialEq for WidgetId {
365    fn eq(&self, other: &Self) -> bool {
366        self.id == other.id
367    }
368}
369
370impl Eq for WidgetId {}
371
372impl Deref for WidgetId {
373    type Target = str;
374
375    fn deref(&self) -> &Self::Target {
376        &self.id
377    }
378}
379
380impl AsRef<str> for WidgetId {
381    fn as_ref(&self) -> &str {
382        &self.id
383    }
384}
385
386impl FromStr for WidgetId {
387    type Err = ();
388
389    fn from_str(s: &str) -> Result<Self, Self::Err> {
390        if let Some(index) = s.find(':') {
391            let type_name = s[..index].to_owned();
392            let rest = &s[(index + b":".len())..];
393            let path = rest.split('/').map(Cow::Borrowed).collect::<Vec<_>>();
394            Ok(Self::new(&type_name, &path))
395        } else {
396            Err(())
397        }
398    }
399}
400
401impl std::fmt::Debug for WidgetId {
402    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
403        f.debug_struct("WidgetId")
404            .field("id", &self.id)
405            .field("type_name", &&self.id[self.type_name.clone()])
406            .field(
407                "parts",
408                &self
409                    .parts
410                    .iter()
411                    .map(|(key, meta)| (&self.id[key.to_owned()], &self.id[meta.to_owned()]))
412                    .collect::<Vec<_>>(),
413            )
414            .finish()
415    }
416}
417
418impl std::fmt::Display for WidgetId {
419    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
420        f.write_str(self.as_ref())
421    }
422}
423
424impl TryFrom<WidgetIdDef> for WidgetId {
425    type Error = String;
426
427    fn try_from(id: WidgetIdDef) -> Result<Self, Self::Error> {
428        match Self::from_str(&id.0) {
429            Ok(id) => Ok(id),
430            Err(_) => Err(format!("Could not parse id: `{}`", id.0)),
431        }
432    }
433}
434
435#[derive(Debug, Clone, PartialEq, Eq)]
436pub struct WidgetIdCommon {
437    id: Option<WidgetId>,
438    count: usize,
439}
440
441impl Default for WidgetIdCommon {
442    fn default() -> Self {
443        Self {
444            id: None,
445            count: usize::MAX,
446        }
447    }
448}
449
450impl WidgetIdCommon {
451    pub fn new(id: WidgetId) -> Self {
452        Self {
453            count: id.depth(),
454            id: Some(id),
455        }
456    }
457
458    pub fn include(&mut self, id: &WidgetId) -> &mut Self {
459        if self.id.is_none() {
460            self.id = Some(id.to_owned());
461            self.count = id.depth();
462            return self;
463        }
464        if let Some(source) = self.id.as_ref() {
465            for index in 0..self.count.min(id.depth()) {
466                if source.part(index) != id.part(index) {
467                    self.count = index;
468                    return self;
469                }
470            }
471        }
472        self
473    }
474
475    pub fn include_other(&mut self, other: &Self) -> &mut Self {
476        if let Some(id) = other.id.as_ref() {
477            if self.id.is_none() {
478                self.id = Some(id.to_owned());
479                self.count = other.count;
480                return self;
481            }
482            if let Some(source) = self.id.as_ref() {
483                for index in 0..self.count.min(other.count) {
484                    if source.part(index) != id.part(index) {
485                        self.count = index;
486                        return self;
487                    }
488                }
489            }
490        }
491        self
492    }
493
494    pub fn is_valid(&self) -> bool {
495        self.id.is_some()
496    }
497
498    pub fn parts(&self) -> Option<impl Iterator<Item = &str>> {
499        self.id
500            .as_ref()
501            .map(|id| (0..self.count).map_while(move |index| id.part(index)))
502    }
503
504    pub fn path(&self) -> Option<&str> {
505        self.id
506            .as_ref()
507            .map(|id| id.range(0, self.count))
508            .filter(|id| !id.is_empty())
509    }
510}
511
512impl<'a> FromIterator<&'a WidgetId> for WidgetIdCommon {
513    fn from_iter<T: IntoIterator<Item = &'a WidgetId>>(iter: T) -> Self {
514        let mut result = Self::default();
515        for id in iter {
516            result.include(id);
517        }
518        result
519    }
520}
521
522#[derive(Debug, Default, Clone, Serialize, Deserialize)]
523pub struct WidgetRefDef(pub Option<WidgetId>);
524
525impl From<WidgetRef> for WidgetRefDef {
526    fn from(data: WidgetRef) -> Self {
527        match data.0.read() {
528            Ok(data) => Self(data.clone()),
529            Err(_) => Default::default(),
530        }
531    }
532}
533
534#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]
535#[serde(from = "WidgetRefDef")]
536#[serde(into = "WidgetRefDef")]
537pub struct WidgetRef(#[serde(skip)] Arc<RwLock<Option<WidgetId>>>);
538
539impl WidgetRef {
540    pub fn new(id: WidgetId) -> Self {
541        Self(Arc::new(RwLock::new(Some(id))))
542    }
543
544    pub(crate) fn write(&mut self, id: WidgetId) {
545        if let Ok(mut data) = self.0.write() {
546            *data = Some(id);
547        }
548    }
549
550    pub fn read(&self) -> Option<WidgetId> {
551        if let Ok(data) = self.0.read() {
552            data.clone()
553        } else {
554            None
555        }
556    }
557
558    pub fn exists(&self) -> bool {
559        self.0
560            .read()
561            .ok()
562            .map(|data| data.is_some())
563            .unwrap_or_default()
564    }
565}
566
567impl PartialEq for WidgetRef {
568    fn eq(&self, other: &Self) -> bool {
569        Arc::ptr_eq(&self.0, &other.0)
570    }
571}
572
573impl From<WidgetRefDef> for WidgetRef {
574    fn from(data: WidgetRefDef) -> Self {
575        WidgetRef(Arc::new(RwLock::new(data.0)))
576    }
577}
578
579impl std::fmt::Display for WidgetRef {
580    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
581        if let Ok(id) = self.0.read() {
582            if let Some(id) = id.as_ref() {
583                write!(f, "{}", id)
584            } else {
585                Ok(())
586            }
587        } else {
588            Ok(())
589        }
590    }
591}
592
593#[derive(PropsData, Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
594pub enum WidgetIdOrRef {
595    #[default]
596    None,
597    Id(WidgetId),
598    Ref(WidgetRef),
599}
600
601impl WidgetIdOrRef {
602    #[inline]
603    pub fn new_ref() -> Self {
604        Self::Ref(WidgetRef::default())
605    }
606
607    #[inline]
608    pub fn is_none(&self) -> bool {
609        matches!(self, Self::None)
610    }
611
612    #[inline]
613    pub fn is_some(&self) -> bool {
614        !self.is_none()
615    }
616
617    pub fn read(&self) -> Option<WidgetId> {
618        match self {
619            Self::None => None,
620            Self::Id(id) => Some(id.to_owned()),
621            Self::Ref(idref) => idref.read(),
622        }
623    }
624}
625
626impl From<()> for WidgetIdOrRef {
627    fn from(_: ()) -> Self {
628        Self::None
629    }
630}
631
632impl From<WidgetId> for WidgetIdOrRef {
633    fn from(v: WidgetId) -> Self {
634        Self::Id(v)
635    }
636}
637
638impl From<WidgetRef> for WidgetIdOrRef {
639    fn from(v: WidgetRef) -> Self {
640        Self::Ref(v)
641    }
642}
643
644impl std::fmt::Display for WidgetIdOrRef {
645    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
646        match self {
647            Self::None => Ok(()),
648            Self::Id(id) => write!(f, "{}", id),
649            Self::Ref(id) => write!(f, "{}", id),
650        }
651    }
652}
653
654#[derive(Clone)]
655pub enum FnWidget {
656    Pointer(fn(WidgetContext) -> WidgetNode),
657    Closure(Arc<dyn Fn(WidgetContext) -> WidgetNode + Send + Sync>),
658}
659
660impl FnWidget {
661    pub fn pointer(value: fn(WidgetContext) -> WidgetNode) -> Self {
662        Self::Pointer(value)
663    }
664
665    pub fn closure(value: impl Fn(WidgetContext) -> WidgetNode + Send + Sync + 'static) -> Self {
666        Self::Closure(Arc::new(value))
667    }
668
669    pub fn call(&self, context: WidgetContext) -> WidgetNode {
670        match self {
671            Self::Pointer(value) => value(context),
672            Self::Closure(value) => value(context),
673        }
674    }
675}
676
677#[derive(Default)]
678pub struct WidgetLifeCycle {
679    #[allow(clippy::type_complexity)]
680    mount: Vec<Box<dyn FnMut(WidgetMountOrChangeContext) + Send + Sync>>,
681    #[allow(clippy::type_complexity)]
682    change: Vec<Box<dyn FnMut(WidgetMountOrChangeContext) + Send + Sync>>,
683    #[allow(clippy::type_complexity)]
684    unmount: Vec<Box<dyn FnMut(WidgetUnmountContext) + Send + Sync>>,
685}
686
687impl WidgetLifeCycle {
688    pub fn mount<F>(&mut self, f: F)
689    where
690        F: 'static + FnMut(WidgetMountOrChangeContext) + Send + Sync,
691    {
692        self.mount.push(Box::new(f));
693    }
694
695    pub fn change<F>(&mut self, f: F)
696    where
697        F: 'static + FnMut(WidgetMountOrChangeContext) + Send + Sync,
698    {
699        self.change.push(Box::new(f));
700    }
701
702    pub fn unmount<F>(&mut self, f: F)
703    where
704        F: 'static + FnMut(WidgetUnmountContext) + Send + Sync,
705    {
706        self.unmount.push(Box::new(f));
707    }
708
709    #[allow(clippy::type_complexity)]
710    pub fn unwrap(
711        self,
712    ) -> (
713        Vec<Box<dyn FnMut(WidgetMountOrChangeContext) + Send + Sync>>,
714        Vec<Box<dyn FnMut(WidgetMountOrChangeContext) + Send + Sync>>,
715        Vec<Box<dyn FnMut(WidgetUnmountContext) + Send + Sync>>,
716    ) {
717        let Self {
718            mount,
719            change,
720            unmount,
721        } = self;
722        (mount, change, unmount)
723    }
724}
725
726pub fn setup(app: &mut Application) {
727    app.register_props::<()>("()");
728    app.register_props::<i8>("i8");
729    app.register_props::<i16>("i16");
730    app.register_props::<i32>("i32");
731    app.register_props::<i64>("i64");
732    app.register_props::<i128>("i128");
733    app.register_props::<u8>("u8");
734    app.register_props::<u16>("u16");
735    app.register_props::<u32>("u32");
736    app.register_props::<u64>("u64");
737    app.register_props::<u128>("u128");
738    app.register_props::<f32>("f32");
739    app.register_props::<f64>("f64");
740    app.register_props::<bool>("bool");
741    app.register_props::<String>("String");
742    app.register_props::<component::containers::anchor_box::AnchorProps>("AnchorProps");
743    app.register_props::<component::containers::anchor_box::PivotBoxProps>("PivotBoxProps");
744    app.register_props::<component::containers::content_box::ContentBoxProps>("ContentBoxProps");
745    app.register_props::<component::containers::flex_box::FlexBoxProps>("FlexBoxProps");
746    app.register_props::<component::containers::grid_box::GridBoxProps>("GridBoxProps");
747    app.register_props::<component::containers::horizontal_box::HorizontalBoxProps>(
748        "HorizontalBoxProps",
749    );
750    app.register_props::<component::containers::hidden_box::HiddenBoxProps>("HiddenBoxProps");
751    app.register_props::<component::containers::scroll_box::ScrollBoxOwner>("ScrollBoxOwner");
752    app.register_props::<component::containers::scroll_box::SideScrollbarsProps>(
753        "SideScrollbarsProps",
754    );
755    app.register_props::<component::containers::scroll_box::SideScrollbarsState>(
756        "SideScrollbarsState",
757    );
758    app.register_props::<component::containers::portal_box::PortalsContainer>("PortalsContainer");
759    app.register_props::<component::containers::size_box::SizeBoxProps>("SizeBoxProps");
760    app.register_props::<component::containers::switch_box::SwitchBoxProps>("SwitchBoxProps");
761    app.register_props::<component::containers::tabs_box::TabsBoxProps>("TabsBoxProps");
762    app.register_props::<component::containers::tabs_box::TabPlateProps>("TabPlateProps");
763    app.register_props::<component::containers::tooltip_box::TooltipState>("TooltipState");
764    app.register_props::<component::containers::variant_box::VariantBoxProps>("VariantBoxProps");
765    app.register_props::<component::containers::vertical_box::VerticalBoxProps>("VerticalBoxProps");
766    app.register_props::<component::containers::wrap_box::WrapBoxProps>("WrapBoxProps");
767    app.register_props::<component::image_box::ImageBoxProps>("ImageBoxProps");
768    app.register_props::<component::interactive::button::ButtonProps>("ButtonProps");
769    app.register_props::<component::interactive::button::ButtonNotifyProps>("ButtonNotifyProps");
770    app.register_props::<component::interactive::input_field::TextInputMode>("TextInputMode");
771    app.register_props::<component::interactive::input_field::TextInputProps>("TextInputProps");
772    app.register_props::<component::interactive::input_field::TextInputState>("TextInputState");
773    app.register_props::<component::interactive::input_field::TextInputNotifyProps>(
774        "TextInputNotifyProps",
775    );
776    app.register_props::<component::interactive::input_field::TextInputControlNotifyProps>(
777        "TextInputControlNotifyProps",
778    );
779    app.register_props::<component::interactive::options_view::OptionsViewMode>("OptionsViewMode");
780    app.register_props::<component::interactive::options_view::OptionsViewProps>(
781        "OptionsViewProps",
782    );
783    app.register_props::<component::interactive::slider_view::SliderViewProps>("SliderViewProps");
784    app.register_props::<component::interactive::navigation::NavItemActive>("NavItemActive");
785    app.register_props::<component::interactive::navigation::NavTrackingActive>(
786        "NavTrackingActive",
787    );
788    app.register_props::<component::interactive::navigation::NavContainerActive>(
789        "NavContainerActive",
790    );
791    app.register_props::<component::interactive::navigation::NavJumpLooped>("NavJumpLooped");
792    app.register_props::<component::interactive::navigation::NavJumpMapProps>("NavJumpMapProps");
793    app.register_props::<component::interactive::scroll_view::ScrollViewState>("ScrollViewState");
794    app.register_props::<component::interactive::scroll_view::ScrollViewRange>("ScrollViewRange");
795    app.register_props::<component::interactive::scroll_view::ScrollViewNotifyProps>(
796        "ScrollViewNotifyProps",
797    );
798    app.register_props::<component::MessageForwardProps>("MessageForwardProps");
799    app.register_props::<component::WidgetAlpha>("WidgetAlpha");
800    app.register_props::<component::space_box::SpaceBoxProps>("SpaceBoxProps");
801    app.register_props::<component::text_box::TextBoxProps>("TextBoxProps");
802    app.register_props::<unit::content::ContentBoxItemLayout>("ContentBoxItemLayout");
803    app.register_props::<unit::flex::FlexBoxItemLayout>("FlexBoxItemLayout");
804    app.register_props::<unit::grid::GridBoxItemLayout>("GridBoxItemLayout");
805
806    app.register_component(
807        "area_box",
808        FnWidget::pointer(component::containers::area_box::area_box),
809    );
810    app.register_component(
811        "anchor_box",
812        FnWidget::pointer(component::containers::anchor_box::anchor_box),
813    );
814    app.register_component(
815        "pivot_box",
816        FnWidget::pointer(component::containers::anchor_box::pivot_box),
817    );
818    app.register_component(
819        "nav_content_box",
820        FnWidget::pointer(component::containers::content_box::nav_content_box),
821    );
822    app.register_component(
823        "content_box",
824        FnWidget::pointer(component::containers::content_box::content_box),
825    );
826    app.register_component(
827        "nav_flex_box",
828        FnWidget::pointer(component::containers::flex_box::nav_flex_box),
829    );
830    app.register_component(
831        "flex_box",
832        FnWidget::pointer(component::containers::flex_box::flex_box),
833    );
834    app.register_component(
835        "nav_grid_box",
836        FnWidget::pointer(component::containers::grid_box::nav_grid_box),
837    );
838    app.register_component(
839        "grid_box",
840        FnWidget::pointer(component::containers::grid_box::grid_box),
841    );
842    app.register_component(
843        "nav_horizontal_box",
844        FnWidget::pointer(component::containers::horizontal_box::nav_horizontal_box),
845    );
846    app.register_component(
847        "horizontal_box",
848        FnWidget::pointer(component::containers::horizontal_box::horizontal_box),
849    );
850    app.register_component(
851        "nav_scroll_box",
852        FnWidget::pointer(component::containers::scroll_box::nav_scroll_box),
853    );
854    app.register_component(
855        "nav_scroll_box_side_scrollbars",
856        FnWidget::pointer(component::containers::scroll_box::nav_scroll_box_side_scrollbars),
857    );
858    app.register_component(
859        "portal_box",
860        FnWidget::pointer(component::containers::portal_box::portal_box),
861    );
862    app.register_component(
863        "size_box",
864        FnWidget::pointer(component::containers::size_box::size_box),
865    );
866    app.register_component(
867        "nav_switch_box",
868        FnWidget::pointer(component::containers::switch_box::nav_switch_box),
869    );
870    app.register_component(
871        "switch_box",
872        FnWidget::pointer(component::containers::switch_box::switch_box),
873    );
874    app.register_component(
875        "nav_tabs_box",
876        FnWidget::pointer(component::containers::tabs_box::nav_tabs_box),
877    );
878    app.register_component(
879        "tooltip_box",
880        FnWidget::pointer(component::containers::tooltip_box::tooltip_box),
881    );
882    app.register_component(
883        "portals_tooltip_box",
884        FnWidget::pointer(component::containers::tooltip_box::portals_tooltip_box),
885    );
886    app.register_component(
887        "variant_box",
888        FnWidget::pointer(component::containers::variant_box::variant_box),
889    );
890    app.register_component(
891        "nav_vertical_box",
892        FnWidget::pointer(component::containers::vertical_box::nav_vertical_box),
893    );
894    app.register_component(
895        "vertical_box",
896        FnWidget::pointer(component::containers::vertical_box::vertical_box),
897    );
898    app.register_component(
899        "wrap_box",
900        FnWidget::pointer(component::containers::wrap_box::wrap_box),
901    );
902    app.register_component(
903        "button",
904        FnWidget::pointer(component::interactive::button::button),
905    );
906    app.register_component(
907        "tracked_button",
908        FnWidget::pointer(component::interactive::button::tracked_button),
909    );
910    app.register_component(
911        "self_tracked_button",
912        FnWidget::pointer(component::interactive::button::self_tracked_button),
913    );
914    app.register_component(
915        "text_input",
916        FnWidget::pointer(component::interactive::input_field::text_input),
917    );
918    app.register_component(
919        "input_field",
920        FnWidget::pointer(component::interactive::input_field::input_field),
921    );
922    app.register_component(
923        "options_view",
924        FnWidget::pointer(component::interactive::options_view::options_view),
925    );
926    app.register_component(
927        "slider_view",
928        FnWidget::pointer(component::interactive::slider_view::slider_view),
929    );
930    app.register_component(
931        "navigation_barrier",
932        FnWidget::pointer(component::interactive::navigation::navigation_barrier),
933    );
934    app.register_component(
935        "tracking",
936        FnWidget::pointer(component::interactive::navigation::tracking),
937    );
938    app.register_component(
939        "self_tracking",
940        FnWidget::pointer(component::interactive::navigation::self_tracking),
941    );
942    app.register_component(
943        "space_box",
944        FnWidget::pointer(component::space_box::space_box),
945    );
946    app.register_component(
947        "image_box",
948        FnWidget::pointer(component::image_box::image_box),
949    );
950    app.register_component("text_box", FnWidget::pointer(component::text_box::text_box));
951}
952
953/// Helper to manually create a [`WidgetComponent`][crate::widget::component::WidgetComponent]
954/// struct from a function.
955///
956/// Users will not usually need this macro, but it can be useful in some advanced cases or where you
957/// don't want to use the [`widget`] macro.
958///
959/// # Example
960///
961/// ```
962/// # use raui_core::prelude::*;
963/// let component: WidgetComponent = make_widget!(my_component);
964///
965/// fn my_component(context: WidgetContext) -> WidgetNode {
966///     todo!("Make an awesome widget")
967/// }
968/// ```
969#[macro_export]
970macro_rules! make_widget {
971    ($type_id:path) => {{
972        let processor = $type_id;
973        let type_name = stringify!($type_id);
974        $crate::widget::component::WidgetComponent::new(
975            $crate::widget::FnWidget::pointer(processor),
976            type_name,
977        )
978    }};
979}
980
981/// A helper for getting the named children out of a widget context
982///
983/// # Example
984///
985/// ```
986/// # use raui_core::prelude::*;
987/// fn my_component(context: WidgetContext) -> WidgetNode {
988///     // Destructure our context to get our named slots
989///     let WidgetContext {
990///         named_slots,
991///         ..
992///     } = context;
993///
994///     // Unpack our named `body` slot
995///     unpack_named_slots!(named_slots => body);
996///
997///     make_widget!(content_box).named_slot("content", body).into()
998/// }
999/// ```
1000///
1001/// You can also unpack multiple slots at a time like this:
1002///
1003/// ```
1004/// # use raui_core::prelude::*;
1005/// # fn my_component(context: WidgetContext) -> WidgetNode {
1006/// #    let WidgetContext {
1007/// #        named_slots,
1008/// #        ..
1009/// #    } = context;
1010///      // Unpack the `header`, `body`, and `footer` slots
1011///      unpack_named_slots!(named_slots => { header, body, footer });
1012/// #    Default::default()
1013/// # }
1014/// ```
1015#[macro_export]
1016macro_rules! unpack_named_slots {
1017    ($map:expr => $name:ident) => {
1018        #[allow(unused_mut)]
1019        let mut $name = {
1020            let mut map = $map;
1021            match map.remove(stringify!($name)) {
1022                Some(widget) => widget,
1023                None => $crate::widget::node::WidgetNode::None,
1024            }
1025        };
1026    };
1027    ($map:expr => { $($name:ident),+ }) => {
1028        #[allow(unused_mut)]
1029        let ( $( mut $name ),+ ) = {
1030            let mut map = $map;
1031            (
1032                $(
1033                    {
1034                        match map.remove(stringify!($name)) {
1035                            Some(widget) => widget,
1036                            None => $crate::widget::node::WidgetNode::None,
1037                        }
1038                    }
1039                ),+
1040            )
1041        };
1042    };
1043}
1044
1045#[cfg(test)]
1046mod tests {
1047    use super::*;
1048
1049    #[test]
1050    fn test_widget_id() {
1051        let id = WidgetId::empty();
1052        assert_eq!(id.type_name(), "");
1053        assert_eq!(id.path(), "");
1054        assert_eq!(id.depth(), 0);
1055
1056        let id = WidgetId::new("type", &["parent".into(), "me".into()]);
1057        assert_eq!(id.to_string(), "type:parent/me".to_owned());
1058        assert_eq!(id.type_name(), "type");
1059        assert_eq!(id.parts().next().unwrap(), "parent");
1060        assert_eq!(id.key(), "me");
1061        assert_eq!(id.clone(), id);
1062
1063        let a = WidgetId::from_str("a:root/a").unwrap();
1064        let b = WidgetId::from_str("b:root/b").unwrap();
1065        let mut common = WidgetIdCommon::default();
1066        assert_eq!(common.path(), None);
1067        common.include(&a);
1068        assert_eq!(common.path(), Some("root/a"));
1069        let mut common = WidgetIdCommon::default();
1070        common.include(&b);
1071        assert_eq!(common.path(), Some("root/b"));
1072        common.include(&a);
1073        assert_eq!(common.path(), Some("root"));
1074
1075        let id = WidgetId::from_str("type:parent/me").unwrap();
1076        assert_eq!(&*id, "type:parent/me");
1077        assert_eq!(id.path(), "parent/me");
1078        let id = id.pop();
1079        assert_eq!(&*id, "type:parent");
1080        assert_eq!(id.path(), "parent");
1081        let id = id.pop();
1082        assert_eq!(&*id, "type:");
1083        assert_eq!(id.path(), "");
1084        let id = id.push("parent");
1085        assert_eq!(&*id, "type:parent");
1086        assert_eq!(id.path(), "parent");
1087        let id = id.push("me");
1088        assert_eq!(&*id, "type:parent/me");
1089        assert_eq!(id.path(), "parent/me");
1090        assert_eq!(id.key(), "me");
1091        assert_eq!(id.meta(), "");
1092        let id = id.push("with?meta");
1093        assert_eq!(&*id, "type:parent/me/with?meta");
1094        assert_eq!(id.path(), "parent/me/with?meta");
1095        assert_eq!(id.key(), "with");
1096        assert_eq!(id.meta(), "meta");
1097
1098        let a = WidgetId::from_str("a:root/a/b/c").unwrap();
1099        let b = WidgetId::from_str("b:root/a/b/c").unwrap();
1100        assert_eq!(a.distance_to(&b), Ok(0));
1101        assert!(!a.is_subset_of(&b));
1102        assert!(!a.is_superset_of(&b));
1103        let a = WidgetId::from_str("a:root/a/b").unwrap();
1104        let b = WidgetId::from_str("b:root/a/b/c").unwrap();
1105        assert_eq!(a.distance_to(&b), Ok(-1));
1106        assert!(a.is_subset_of(&b));
1107        assert!(!a.is_superset_of(&b));
1108        let a = WidgetId::from_str("a:root/a").unwrap();
1109        let b = WidgetId::from_str("b:root/a/b/c").unwrap();
1110        assert_eq!(a.distance_to(&b), Ok(-2));
1111        assert!(a.is_subset_of(&b));
1112        assert!(!a.is_superset_of(&b));
1113        let a = WidgetId::from_str("a:root").unwrap();
1114        let b = WidgetId::from_str("b:root/a/b/c").unwrap();
1115        assert_eq!(a.distance_to(&b), Ok(-3));
1116        assert!(a.is_subset_of(&b));
1117        assert!(!a.is_superset_of(&b));
1118        let a = WidgetId::from_str("a:root/a/b/c").unwrap();
1119        let b = WidgetId::from_str("b:root/a/b").unwrap();
1120        assert_eq!(a.distance_to(&b), Ok(1));
1121        assert!(!a.is_subset_of(&b));
1122        assert!(a.is_superset_of(&b));
1123        let a = WidgetId::from_str("a:root/a/b/c").unwrap();
1124        let b = WidgetId::from_str("b:root/a").unwrap();
1125        assert_eq!(a.distance_to(&b), Ok(2));
1126        assert!(!a.is_subset_of(&b));
1127        assert!(a.is_superset_of(&b));
1128        let a = WidgetId::from_str("a:root/a/b/c").unwrap();
1129        let b = WidgetId::from_str("b:root").unwrap();
1130        assert_eq!(a.distance_to(&b), Ok(3));
1131        assert!(!a.is_subset_of(&b));
1132        assert!(a.is_superset_of(&b));
1133        let a = WidgetId::from_str("a:root/a/b/x").unwrap();
1134        let b = WidgetId::from_str("b:root/a/b/c").unwrap();
1135        assert_eq!(a.distance_to(&b), Err(-1));
1136        assert!(!a.is_subset_of(&b));
1137        assert!(!a.is_superset_of(&b));
1138        let a = WidgetId::from_str("a:root/a/x/y").unwrap();
1139        let b = WidgetId::from_str("b:root/a/b/c").unwrap();
1140        assert_eq!(a.distance_to(&b), Err(-2));
1141        assert!(!a.is_subset_of(&b));
1142        assert!(!a.is_superset_of(&b));
1143        let a = WidgetId::from_str("a:root/x/y/z").unwrap();
1144        let b = WidgetId::from_str("b:root/a/b/c").unwrap();
1145        assert_eq!(a.distance_to(&b), Err(-3));
1146        assert!(!a.is_subset_of(&b));
1147        assert!(!a.is_superset_of(&b));
1148    }
1149}