1use gpui::{
19 App, Bounds, CursorStyle, Decorations, Div, HitboxBehavior, IntoElement, MouseButton, Pixels,
20 Point, ResizeEdge, Size, Window, canvas, div, point, prelude::*, px, size,
21};
22
23use theme::Theme;
24
25pub fn frame(child: impl IntoElement, window: &mut Window, cx: &App) -> Div {
39 let Decorations::Client { tiling } = window.window_decorations() else {
40 return div().size_full().child(child);
41 };
42
43 let inset = px(Theme::CLIENT_INSET);
44 let radius = px(Theme::surface_radius());
45 let border = Theme::of(cx).border;
46 window.set_client_inset(inset);
47
48 div()
49 .size_full()
50 .child(
51 canvas(
52 move |_, window, _| {
53 let size = window.window_bounds().get_bounds().size;
54 bands(size, inset).map(|(edge, band)| {
55 (edge, window.insert_hitbox(band, HitboxBehavior::Normal))
56 })
57 },
58 |_, bands, window, _| {
59 for (edge, band) in bands {
60 window.set_cursor_style(cursor(edge), &band);
61 }
62 },
63 )
64 .absolute()
65 .size_full(),
66 )
67 .when(!tiling.top, |frame| frame.pt(inset))
68 .when(!tiling.bottom, |frame| frame.pb(inset))
69 .when(!tiling.left, |frame| frame.pl(inset))
70 .when(!tiling.right, |frame| frame.pr(inset))
71 .on_mouse_down(MouseButton::Left, move |event, window, _| {
72 let size = window.window_bounds().get_bounds().size;
73 if let Some(edge) = resize_edge(event.position, inset, size) {
74 window.start_window_resize(edge);
75 }
76 })
77 .child(
78 div()
79 .size_full()
80 .overflow_hidden()
83 .border_color(border)
84 .when(!tiling.top, |surface| surface.border_t(px(1.0)))
85 .when(!tiling.bottom, |surface| surface.border_b(px(1.0)))
86 .when(!tiling.left, |surface| surface.border_l(px(1.0)))
87 .when(!tiling.right, |surface| surface.border_r(px(1.0)))
88 .when(!(tiling.top || tiling.left), |surface| {
89 surface.rounded_tl(radius)
90 })
91 .when(!(tiling.top || tiling.right), |surface| {
92 surface.rounded_tr(radius)
93 })
94 .when(!(tiling.bottom || tiling.left), |surface| {
95 surface.rounded_bl(radius)
96 })
97 .when(!(tiling.bottom || tiling.right), |surface| {
98 surface.rounded_br(radius)
99 })
100 .when(!tiling.is_tiled(), |surface| {
101 surface.shadow(theme::surface_shadows())
102 })
103 .child(child),
104 )
105}
106
107pub fn resize_edge(pos: Point<Pixels>, inset: Pixels, size: Size<Pixels>) -> Option<ResizeEdge> {
113 let (top, bottom) = (pos.y < inset, pos.y > size.height - inset);
114 let (left, right) = (pos.x < inset, pos.x > size.width - inset);
115
116 Some(match (top, bottom, left, right) {
117 (true, _, true, _) => ResizeEdge::TopLeft,
118 (true, _, _, true) => ResizeEdge::TopRight,
119 (_, true, true, _) => ResizeEdge::BottomLeft,
120 (_, true, _, true) => ResizeEdge::BottomRight,
121 (true, ..) => ResizeEdge::Top,
122 (_, true, ..) => ResizeEdge::Bottom,
123 (_, _, true, _) => ResizeEdge::Left,
124 (_, _, _, true) => ResizeEdge::Right,
125 _ => return None,
126 })
127}
128
129fn bands(window: Size<Pixels>, inset: Pixels) -> [(ResizeEdge, Bounds<Pixels>); 8] {
133 let band = |x: Pixels, y: Pixels, width: Pixels, height: Pixels| Bounds {
134 origin: point(x, y),
135 size: size(width, height),
136 };
137 let (far_x, far_y) = (window.width - inset, window.height - inset);
138 let (span_x, span_y) = (window.width - inset - inset, window.height - inset - inset);
139 let zero = px(0.0);
140
141 [
142 (ResizeEdge::TopLeft, band(zero, zero, inset, inset)),
143 (ResizeEdge::TopRight, band(far_x, zero, inset, inset)),
144 (ResizeEdge::BottomLeft, band(zero, far_y, inset, inset)),
145 (ResizeEdge::BottomRight, band(far_x, far_y, inset, inset)),
146 (ResizeEdge::Top, band(inset, zero, span_x, inset)),
147 (ResizeEdge::Bottom, band(inset, far_y, span_x, inset)),
148 (ResizeEdge::Left, band(zero, inset, inset, span_y)),
149 (ResizeEdge::Right, band(far_x, inset, inset, span_y)),
150 ]
151}
152
153fn cursor(edge: ResizeEdge) -> CursorStyle {
155 match edge {
156 ResizeEdge::Top | ResizeEdge::Bottom => CursorStyle::ResizeUpDown,
157 ResizeEdge::Left | ResizeEdge::Right => CursorStyle::ResizeLeftRight,
158 ResizeEdge::TopLeft | ResizeEdge::BottomRight => CursorStyle::ResizeUpLeftDownRight,
159 ResizeEdge::TopRight | ResizeEdge::BottomLeft => CursorStyle::ResizeUpRightDownLeft,
160 }
161}