1use gpui::{
19 App, Bounds, CursorStyle, Decorations, Div, HitboxBehavior, IntoElement, MouseButton, Pixels,
20 Point, ResizeEdge, Size, Tiling, 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 {
41 let Decorations::Client { tiling } = window.window_decorations() else {
42 return div().size_full().child(child);
43 };
44 let tiling = match window.is_maximized() {
45 true => Tiling::tiled(),
46 false => tiling,
47 };
48
49 let inset = px(Theme::CLIENT_INSET);
50 let radius = px(Theme::surface_radius());
51 let border = Theme::of(cx).border;
52 window.set_client_inset(inset);
53
54 div()
55 .size_full()
56 .child(
57 canvas(
58 move |_, window, _| {
59 let size = window.window_bounds().get_bounds().size;
60 bands(size, inset)
61 .into_iter()
62 .filter(|(edge, _)| live(*edge, tiling))
63 .map(|(edge, band)| {
64 (edge, window.insert_hitbox(band, HitboxBehavior::Normal))
65 })
66 .collect::<Vec<_>>()
67 },
68 |_, bands, window, _| {
69 for (edge, band) in bands {
70 window.set_cursor_style(cursor(edge), &band);
71 }
72 },
73 )
74 .absolute()
75 .size_full(),
76 )
77 .when(!tiling.top, |frame| frame.pt(inset))
78 .when(!tiling.bottom, |frame| frame.pb(inset))
79 .when(!tiling.left, |frame| frame.pl(inset))
80 .when(!tiling.right, |frame| frame.pr(inset))
81 .on_mouse_down(MouseButton::Left, move |event, window, _| {
82 let size = window.window_bounds().get_bounds().size;
83 if let Some(edge) =
84 resize_edge(event.position, inset, size).filter(|edge| live(*edge, tiling))
85 {
86 window.start_window_resize(edge);
87 }
88 })
89 .child(
90 div()
91 .size_full()
92 .overflow_hidden()
95 .border_color(border)
96 .when(!tiling.top, |surface| surface.border_t(px(1.0)))
97 .when(!tiling.bottom, |surface| surface.border_b(px(1.0)))
98 .when(!tiling.left, |surface| surface.border_l(px(1.0)))
99 .when(!tiling.right, |surface| surface.border_r(px(1.0)))
100 .when(!(tiling.top || tiling.left), |surface| {
101 surface.rounded_tl(radius)
102 })
103 .when(!(tiling.top || tiling.right), |surface| {
104 surface.rounded_tr(radius)
105 })
106 .when(!(tiling.bottom || tiling.left), |surface| {
107 surface.rounded_bl(radius)
108 })
109 .when(!(tiling.bottom || tiling.right), |surface| {
110 surface.rounded_br(radius)
111 })
112 .when(!tiling.is_tiled(), |surface| {
113 surface.shadow(theme::frame_shadows())
114 })
115 .child(child),
116 )
117}
118
119pub fn resize_edge(pos: Point<Pixels>, inset: Pixels, size: Size<Pixels>) -> Option<ResizeEdge> {
125 let (top, bottom) = (pos.y < inset, pos.y > size.height - inset);
126 let (left, right) = (pos.x < inset, pos.x > size.width - inset);
127
128 Some(match (top, bottom, left, right) {
129 (true, _, true, _) => ResizeEdge::TopLeft,
130 (true, _, _, true) => ResizeEdge::TopRight,
131 (_, true, true, _) => ResizeEdge::BottomLeft,
132 (_, true, _, true) => ResizeEdge::BottomRight,
133 (true, ..) => ResizeEdge::Top,
134 (_, true, ..) => ResizeEdge::Bottom,
135 (_, _, true, _) => ResizeEdge::Left,
136 (_, _, _, true) => ResizeEdge::Right,
137 _ => return None,
138 })
139}
140
141fn bands(window: Size<Pixels>, inset: Pixels) -> [(ResizeEdge, Bounds<Pixels>); 8] {
145 let band = |x: Pixels, y: Pixels, width: Pixels, height: Pixels| Bounds {
146 origin: point(x, y),
147 size: size(width, height),
148 };
149 let (far_x, far_y) = (window.width - inset, window.height - inset);
150 let (span_x, span_y) = (window.width - inset - inset, window.height - inset - inset);
151 let zero = px(0.0);
152
153 [
154 (ResizeEdge::TopLeft, band(zero, zero, inset, inset)),
155 (ResizeEdge::TopRight, band(far_x, zero, inset, inset)),
156 (ResizeEdge::BottomLeft, band(zero, far_y, inset, inset)),
157 (ResizeEdge::BottomRight, band(far_x, far_y, inset, inset)),
158 (ResizeEdge::Top, band(inset, zero, span_x, inset)),
159 (ResizeEdge::Bottom, band(inset, far_y, span_x, inset)),
160 (ResizeEdge::Left, band(zero, inset, inset, span_y)),
161 (ResizeEdge::Right, band(far_x, inset, inset, span_y)),
162 ]
163}
164
165pub fn live(edge: ResizeEdge, tiling: Tiling) -> bool {
167 let (top, bottom, left, right) = match edge {
168 ResizeEdge::Top => (true, false, false, false),
169 ResizeEdge::Bottom => (false, true, false, false),
170 ResizeEdge::Left => (false, false, true, false),
171 ResizeEdge::Right => (false, false, false, true),
172 ResizeEdge::TopLeft => (true, false, true, false),
173 ResizeEdge::TopRight => (true, false, false, true),
174 ResizeEdge::BottomLeft => (false, true, true, false),
175 ResizeEdge::BottomRight => (false, true, false, true),
176 };
177 !(top && tiling.top || bottom && tiling.bottom || left && tiling.left || right && tiling.right)
178}
179
180fn cursor(edge: ResizeEdge) -> CursorStyle {
182 match edge {
183 ResizeEdge::Top | ResizeEdge::Bottom => CursorStyle::ResizeUpDown,
184 ResizeEdge::Left | ResizeEdge::Right => CursorStyle::ResizeLeftRight,
185 ResizeEdge::TopLeft | ResizeEdge::BottomRight => CursorStyle::ResizeUpLeftDownRight,
186 ResizeEdge::TopRight | ResizeEdge::BottomLeft => CursorStyle::ResizeUpRightDownLeft,
187 }
188}