gpui_router/
route.rs

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