gpui_router/
route.rs

1use crate::{Layout, RouterState};
2use gpui::*;
3use matchit::Router as MatchitRouter;
4use smallvec::SmallVec;
5use std::fmt::{Debug, Display};
6
7pub fn route() -> impl IntoElement {
8  Route::new()
9}
10
11/// Configures an element to render when a pattern matches the current path.
12/// It must be rendered within a [`Routes`](crate::Routes) element.
13#[derive(IntoElement, Default)]
14pub struct Route {
15  basename: SharedString,
16  path: Option<SharedString>,
17  pub(crate) element: Option<AnyElement>,
18  pub(crate) routes: SmallVec<[Box<Route>; 1]>,
19  pub(crate) layout: Option<Box<dyn Layout>>,
20}
21
22impl Debug for Route {
23  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24    f.debug_struct("Route")
25      .field("basename", &self.basename)
26      .field("path", &self.path)
27      .field("layout", &self.layout.is_some())
28      .field("element", &self.element.is_some())
29      .field("routes", &self.routes.len())
30      .finish()
31  }
32}
33
34impl Display for Route {
35  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36    write!(f, "Route")
37  }
38}
39
40impl Route {
41  pub fn new() -> Self {
42    Self::default()
43  }
44
45  pub(crate) fn basename(mut self, basename: impl Into<SharedString>) -> Self {
46    self.basename = basename.into();
47    self
48  }
49
50  /// The path to match against the current location.
51  pub fn path(mut self, path: impl Into<SharedString>) -> Self {
52    self.path = Some(path.into());
53    self
54  }
55
56  pub fn element<E: IntoElement>(mut self, element: E) -> Self {
57    if cfg!(debug_assertions) && self.layout.is_some() {
58      panic!("Route element and layout cannot be set at the same time");
59    }
60
61    self.element = Some(element.into_any_element());
62    self
63  }
64
65  pub fn layout(mut self, layout: impl Layout + 'static) -> Self {
66    if cfg!(debug_assertions) && self.element.is_some() {
67      panic!("Route element and layout cannot be set at the same time");
68    }
69
70    self.layout = Some(Box::new(layout));
71    self
72  }
73
74  pub fn index(self) -> Self {
75    if cfg!(debug_assertions) && self.path.is_some() {
76      panic!("Route index and path cannot be set at the same time");
77    }
78    self.path("")
79  }
80
81  /// Adds a `Route` as a child to the `Route`.
82  pub fn child(mut self, child: Route) -> Self {
83    self.routes.push(Box::new(child));
84    self
85  }
86
87  /// Adds multiple `Route`s as children to the `Route`.
88  pub fn children(mut self, children: impl IntoIterator<Item = Route>) -> Self {
89    for child in children.into_iter() {
90      self = self.child(child);
91    }
92    self
93  }
94
95  pub(crate) fn build_route_map(&self, basename: &str) -> MatchitRouter<()> {
96    let basename = basename.trim_end_matches('/');
97    let mut router_map = MatchitRouter::new();
98
99    let path = match self.path {
100      Some(ref path) => format!("{}/{}", basename, path),
101      None => basename.to_string(),
102    };
103
104    let path = if path != "/" { path.trim_end_matches('/') } else { &path };
105
106    if self.element.is_some() {
107      router_map.insert(path, ()).unwrap();
108      return router_map;
109    }
110
111    // Recursively build the route map
112    for route in self.routes.iter() {
113      router_map.merge(route.build_route_map(path)).unwrap();
114    }
115
116    router_map
117  }
118
119  pub(crate) fn in_pattern(&self, basename: &str, path: &str) -> bool {
120    self.build_route_map(basename).at(path).is_ok()
121  }
122}
123
124impl RenderOnce for Route {
125  fn render(mut self, window: &mut Window, cx: &mut App) -> impl IntoElement {
126    if let Some(element) = self.element {
127      return element;
128    }
129
130    if let Some(mut layout) = self.layout {
131      let pathname = cx.global::<RouterState>().location.pathname.clone();
132      let basename = self.basename.trim_end_matches('/');
133      let basename = match self.path {
134        Some(ref path) => format!("{}/{}", basename, path),
135        None => basename.to_string(),
136      };
137      let routes = std::mem::take(&mut self.routes);
138      let route = routes.into_iter().find(|route| route.in_pattern(&basename, &pathname));
139      if let Some(route) = route {
140        layout.outlet(route.basename(basename).render(window, cx).into_any_element());
141      }
142      return layout.render_layout(window, cx).into_any_element();
143    }
144    Empty {}.into_any_element()
145  }
146}