1use crate::{Layout, RouterState};
2use gpui::*;
3use matchit::Router as MatchitRouter;
4use smallvec::SmallVec;
5use std::fmt::{Debug, Display};
6
7type RouteElementFactory = Box<dyn Fn(&mut Window, &mut App) -> AnyElement>;
8
9pub fn route() -> impl IntoElement {
11 Route::new()
12}
13
14#[derive(IntoElement)]
17pub struct Route {
18 basename: SharedString,
19 path: Option<SharedString>,
20 pub(crate) element: Option<RouteElementFactory>,
21 pub(crate) routes: SmallVec<[Box<Route>; 1]>,
22 pub(crate) layout: Option<Box<dyn Layout>>,
23}
24
25impl Default for Route {
26 fn default() -> Self {
27 Self {
28 basename: SharedString::default(),
29 path: None,
30 element: None,
31 routes: SmallVec::new(),
32 layout: None,
33 }
34 }
35}
36
37impl Debug for Route {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 f.debug_struct("Route")
40 .field("basename", &self.basename)
41 .field("path", &self.path)
42 .field("layout", &self.layout.is_some())
43 .field("element", &self.element.is_some())
44 .field("routes", &self.routes.len())
45 .finish()
46 }
47}
48
49impl Display for Route {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 write!(f, "Route")
52 }
53}
54
55impl Route {
56 pub fn new() -> Self {
57 Self::default()
58 }
59
60 pub(crate) fn basename(mut self, basename: impl Into<SharedString>) -> Self {
61 self.basename = basename.into();
62 self
63 }
64
65 pub fn path(mut self, path: impl Into<SharedString>) -> Self {
67 self.path = Some(path.into());
68 self
69 }
70
71 pub fn element<F, E>(mut self, element_fn: F) -> Self
81 where
82 F: Fn(&mut Window, &mut App) -> E + 'static,
83 E: IntoElement,
84 {
85 if cfg!(debug_assertions) && self.layout.is_some() {
86 panic!("Route element and layout cannot be set at the same time");
87 }
88
89 self.element = Some(Box::new(move |window, cx| element_fn(window, cx).into_any_element()));
90 self
91 }
92
93 pub fn layout(mut self, layout: impl Layout + 'static) -> Self {
96 if cfg!(debug_assertions) && self.element.is_some() {
97 panic!("Route element and layout cannot be set at the same time");
98 }
99
100 self.layout = Some(Box::new(layout));
101 self
102 }
103
104 pub fn index(self) -> Self {
107 if cfg!(debug_assertions) && self.path.is_some() {
108 panic!("Route index and path cannot be set at the same time");
109 }
110 self.path("")
111 }
112
113 pub fn child(mut self, child: Route) -> Self {
115 self.routes.push(Box::new(child));
116 self
117 }
118
119 pub fn children(mut self, children: impl IntoIterator<Item = Route>) -> Self {
121 for child in children.into_iter() {
122 self = self.child(child);
123 }
124 self
125 }
126
127 pub(crate) fn build_route_map(&self, basename: &str) -> MatchitRouter<()> {
128 let basename = basename.trim_end_matches('/');
129 let mut router_map = MatchitRouter::new();
130
131 let path = match self.path {
132 Some(ref path) => format!("{}/{}", basename, path),
133 None => basename.to_string(),
134 };
135
136 let path = if path != "/" { path.trim_end_matches('/') } else { &path };
137
138 if self.element.is_some() {
139 router_map.insert(path, ()).unwrap();
140 return router_map;
141 }
142
143 for route in self.routes.iter() {
145 router_map.merge(route.build_route_map(path)).unwrap();
146 }
147
148 router_map
149 }
150
151 pub(crate) fn in_pattern(&self, basename: &str, path: &str) -> bool {
152 self.build_route_map(basename).at(path).is_ok()
153 }
154}
155
156impl RenderOnce for Route {
157 fn render(mut self, window: &mut Window, cx: &mut App) -> impl IntoElement {
158 if let Some(element_fn) = self.element {
159 return element_fn(window, cx);
160 }
161
162 if let Some(mut layout) = self.layout {
163 let pathname = cx.global::<RouterState>().location.pathname.clone();
164 let basename = self.basename.trim_end_matches('/');
165 let basename = match self.path {
166 Some(ref path) => format!("{}/{}", basename, path),
167 None => basename.to_string(),
168 };
169 let routes = std::mem::take(&mut self.routes);
170 let route = routes.into_iter().find(|route| route.in_pattern(&basename, &pathname));
171 if let Some(route) = route {
172 layout.outlet(route.basename(basename).render(window, cx).into_any_element());
173 }
174 return layout.render_layout(window, cx).into_any_element();
175 }
176 Empty {}.into_any_element()
177 }
178}