1use gpui::{
20 App, Bounds, CursorStyle, Decorations, Div, HitboxBehavior, Hsla, IntoElement, MouseButton,
21 Pixels, Point, ResizeEdge, Size, Tiling, Window, canvas, div, point, prelude::*, px, size,
22};
23
24use theme::Theme;
25
26pub fn frame(child: impl IntoElement, window: &mut Window, cx: &App) -> Div {
42 let Decorations::Client { tiling } = window.window_decorations() else {
43 return div().size_full().child(child);
44 };
45 let tiling = match window.is_maximized() {
46 true => Tiling::tiled(),
47 false => tiling,
48 };
49
50 let inset = px(Theme::CLIENT_INSET);
51 let radius = px(Theme::surface_radius());
52 let border = Theme::of(cx).border;
53 window.set_client_inset(inset);
54
55 div()
56 .size_full()
57 .child(
58 canvas(
59 move |_, window, _| {
60 let size = window.window_bounds().get_bounds().size;
61 bands(size, inset)
62 .into_iter()
63 .filter(|(edge, _)| live(*edge, tiling))
64 .map(|(edge, band)| {
65 (edge, window.insert_hitbox(band, HitboxBehavior::Normal))
66 })
67 .collect::<Vec<_>>()
68 },
69 |_, bands, window, _| {
70 for (edge, band) in bands {
71 window.set_cursor_style(cursor(edge), &band);
72 }
73 },
74 )
75 .absolute()
76 .size_full(),
77 )
78 .when(!tiling.top, |frame| frame.pt(inset))
79 .when(!tiling.bottom, |frame| frame.pb(inset))
80 .when(!tiling.left, |frame| frame.pl(inset))
81 .when(!tiling.right, |frame| frame.pr(inset))
82 .on_mouse_down(MouseButton::Left, move |event, window, _| {
83 let size = window.window_bounds().get_bounds().size;
84 if let Some(edge) =
85 resize_edge(event.position, inset, size).filter(|edge| live(*edge, tiling))
86 {
87 window.start_window_resize(edge);
88 }
89 })
90 .child(
91 div()
92 .size_full()
93 .overflow_hidden()
96 .border_color(border)
97 .when(!tiling.top, |surface| surface.border_t(px(1.0)))
98 .when(!tiling.bottom, |surface| surface.border_b(px(1.0)))
99 .when(!tiling.left, |surface| surface.border_l(px(1.0)))
100 .when(!tiling.right, |surface| surface.border_r(px(1.0)))
101 .when(!(tiling.top || tiling.left), |surface| {
102 surface.rounded_tl(radius)
103 })
104 .when(!(tiling.top || tiling.right), |surface| {
105 surface.rounded_tr(radius)
106 })
107 .when(!(tiling.bottom || tiling.left), |surface| {
108 surface.rounded_bl(radius)
109 })
110 .when(!(tiling.bottom || tiling.right), |surface| {
111 surface.rounded_br(radius)
112 })
113 .when(!tiling.is_tiled(), |surface| {
114 surface.shadow(theme::frame_shadows())
115 })
116 .child(child),
117 )
118}
119
120pub fn content(floating: bool, cx: &App) -> Div {
134 let theme = Theme::of(cx);
135 match floating {
136 true => card(theme.content_bg(), theme).when(!theme.vibrancy, |card| {
137 card.shadow(theme::content_shadows())
138 }),
139 false => div().bg(theme.window_bg()),
140 }
141}
142
143pub fn resting(cx: &App) -> Div {
147 let theme = Theme::of(cx);
148 card(theme.content_resting_bg(), theme)
149}
150
151pub fn content_fill(floating: bool, theme: &Theme) -> Hsla {
154 match floating {
155 true => theme::over(theme.content_bg(), theme.sidebar_bg()),
156 false => theme.window_bg(),
157 }
158}
159
160pub fn resting_fill(theme: &Theme) -> Hsla {
162 theme::over(theme.content_resting_bg(), theme.sidebar_bg())
163}
164
165fn card(fill: Hsla, theme: &Theme) -> Div {
166 div()
167 .bg(fill)
168 .rounded(px(Theme::panel_radius()))
169 .border_1()
170 .border_color(theme.border)
171 .overflow_hidden()
174}
175
176pub fn resize_edge(pos: Point<Pixels>, inset: Pixels, size: Size<Pixels>) -> Option<ResizeEdge> {
182 let (top, bottom) = (pos.y < inset, pos.y > size.height - inset);
183 let (left, right) = (pos.x < inset, pos.x > size.width - inset);
184
185 Some(match (top, bottom, left, right) {
186 (true, _, true, _) => ResizeEdge::TopLeft,
187 (true, _, _, true) => ResizeEdge::TopRight,
188 (_, true, true, _) => ResizeEdge::BottomLeft,
189 (_, true, _, true) => ResizeEdge::BottomRight,
190 (true, ..) => ResizeEdge::Top,
191 (_, true, ..) => ResizeEdge::Bottom,
192 (_, _, true, _) => ResizeEdge::Left,
193 (_, _, _, true) => ResizeEdge::Right,
194 _ => return None,
195 })
196}
197
198fn bands(window: Size<Pixels>, inset: Pixels) -> [(ResizeEdge, Bounds<Pixels>); 8] {
202 let band = |x: Pixels, y: Pixels, width: Pixels, height: Pixels| Bounds {
203 origin: point(x, y),
204 size: size(width, height),
205 };
206 let (far_x, far_y) = (window.width - inset, window.height - inset);
207 let (span_x, span_y) = (window.width - inset - inset, window.height - inset - inset);
208 let zero = px(0.0);
209
210 [
211 (ResizeEdge::TopLeft, band(zero, zero, inset, inset)),
212 (ResizeEdge::TopRight, band(far_x, zero, inset, inset)),
213 (ResizeEdge::BottomLeft, band(zero, far_y, inset, inset)),
214 (ResizeEdge::BottomRight, band(far_x, far_y, inset, inset)),
215 (ResizeEdge::Top, band(inset, zero, span_x, inset)),
216 (ResizeEdge::Bottom, band(inset, far_y, span_x, inset)),
217 (ResizeEdge::Left, band(zero, inset, inset, span_y)),
218 (ResizeEdge::Right, band(far_x, inset, inset, span_y)),
219 ]
220}
221
222pub fn live(edge: ResizeEdge, tiling: Tiling) -> bool {
224 let (top, bottom, left, right) = match edge {
225 ResizeEdge::Top => (true, false, false, false),
226 ResizeEdge::Bottom => (false, true, false, false),
227 ResizeEdge::Left => (false, false, true, false),
228 ResizeEdge::Right => (false, false, false, true),
229 ResizeEdge::TopLeft => (true, false, true, false),
230 ResizeEdge::TopRight => (true, false, false, true),
231 ResizeEdge::BottomLeft => (false, true, true, false),
232 ResizeEdge::BottomRight => (false, true, false, true),
233 };
234 !(top && tiling.top || bottom && tiling.bottom || left && tiling.left || right && tiling.right)
235}
236
237fn cursor(edge: ResizeEdge) -> CursorStyle {
239 match edge {
240 ResizeEdge::Top | ResizeEdge::Bottom => CursorStyle::ResizeUpDown,
241 ResizeEdge::Left | ResizeEdge::Right => CursorStyle::ResizeLeftRight,
242 ResizeEdge::TopLeft | ResizeEdge::BottomRight => CursorStyle::ResizeUpLeftDownRight,
243 ResizeEdge::TopRight | ResizeEdge::BottomLeft => CursorStyle::ResizeUpRightDownLeft,
244 }
245}