gpui_component/
window_border.rs1use gpui::{
4 canvas, div, point, prelude::FluentBuilder as _, px, AnyElement, App, Bounds, CursorStyle,
5 Decorations, Edges, HitboxBehavior, Hsla, InteractiveElement as _, IntoElement, MouseButton,
6 ParentElement, Pixels, Point, RenderOnce, ResizeEdge, Size, Styled as _, Window,
7};
8
9use crate::ActiveTheme;
10
11#[cfg(not(target_os = "linux"))]
12const SHADOW_SIZE: Pixels = px(0.0);
13#[cfg(target_os = "linux")]
14const SHADOW_SIZE: Pixels = px(12.0);
15const BORDER_SIZE: Pixels = px(1.0);
16pub(crate) const BORDER_RADIUS: Pixels = px(0.0);
17
18pub fn window_border() -> WindowBorder {
20 WindowBorder::new()
21}
22
23#[derive(IntoElement, Default)]
25pub struct WindowBorder {
26 children: Vec<AnyElement>,
27}
28
29impl WindowBorder {
30 pub fn new() -> Self {
31 Self {
32 ..Default::default()
33 }
34 }
35}
36
37pub fn window_paddings(window: &Window) -> Edges<Pixels> {
39 match window.window_decorations() {
40 Decorations::Server => Edges::all(px(0.0)),
41 Decorations::Client { tiling } => {
42 let mut paddings = Edges::all(SHADOW_SIZE);
43 if tiling.top {
44 paddings.top = px(0.0);
45 }
46 if tiling.bottom {
47 paddings.bottom = px(0.0);
48 }
49 if tiling.left {
50 paddings.left = px(0.0);
51 }
52 if tiling.right {
53 paddings.right = px(0.0);
54 }
55 paddings
56 }
57 }
58}
59
60impl ParentElement for WindowBorder {
61 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
62 self.children.extend(elements);
63 }
64}
65
66impl RenderOnce for WindowBorder {
67 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
68 let decorations = window.window_decorations();
69 window.set_client_inset(SHADOW_SIZE);
70
71 div()
72 .id("window-backdrop")
73 .bg(gpui::transparent_black())
74 .map(|div| match decorations {
75 Decorations::Server => div,
76 Decorations::Client { tiling, .. } => div
77 .bg(gpui::transparent_black())
78 .child(
79 canvas(
80 |_bounds, window, _| {
81 window.insert_hitbox(
82 Bounds::new(
83 point(px(0.0), px(0.0)),
84 window.window_bounds().get_bounds().size,
85 ),
86 HitboxBehavior::Normal,
87 )
88 },
89 move |_bounds, hitbox, window, _| {
90 let mouse = window.mouse_position();
91 let size = window.window_bounds().get_bounds().size;
92 let Some(edge) = resize_edge(mouse, SHADOW_SIZE, size) else {
93 return;
94 };
95 window.set_cursor_style(
96 match edge {
97 ResizeEdge::Top | ResizeEdge::Bottom => {
98 CursorStyle::ResizeUpDown
99 }
100 ResizeEdge::Left | ResizeEdge::Right => {
101 CursorStyle::ResizeLeftRight
102 }
103 ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
104 CursorStyle::ResizeUpLeftDownRight
105 }
106 ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
107 CursorStyle::ResizeUpRightDownLeft
108 }
109 },
110 &hitbox,
111 );
112 },
113 )
114 .size_full()
115 .absolute(),
116 )
117 .when(!(tiling.top || tiling.right), |div| {
118 div.rounded_tr(BORDER_RADIUS)
119 })
120 .when(!(tiling.top || tiling.left), |div| {
121 div.rounded_tl(BORDER_RADIUS)
122 })
123 .when(!tiling.top, |div| div.pt(SHADOW_SIZE))
124 .when(!tiling.bottom, |div| div.pb(SHADOW_SIZE))
125 .when(!tiling.left, |div| div.pl(SHADOW_SIZE))
126 .when(!tiling.right, |div| div.pr(SHADOW_SIZE))
127 .on_mouse_down(MouseButton::Left, move |_, window, _| {
128 let size = window.window_bounds().get_bounds().size;
129 let pos = window.mouse_position();
130
131 match resize_edge(pos, SHADOW_SIZE, size) {
132 Some(edge) => window.start_window_resize(edge),
133 None => {}
134 };
135 }),
136 })
137 .size_full()
138 .child(
139 div()
140 .map(|div| match decorations {
141 Decorations::Server => div,
142 Decorations::Client { tiling } => div
143 .when(!(tiling.top || tiling.right), |div| {
144 div.rounded_tr(BORDER_RADIUS)
145 })
146 .when(!(tiling.top || tiling.left), |div| {
147 div.rounded_tl(BORDER_RADIUS)
148 })
149 .border_color(cx.theme().window_border)
150 .when(!tiling.top, |div| div.border_t(BORDER_SIZE))
151 .when(!tiling.bottom, |div| div.border_b(BORDER_SIZE))
152 .when(!tiling.left, |div| div.border_l(BORDER_SIZE))
153 .when(!tiling.right, |div| div.border_r(BORDER_SIZE))
154 .when(!tiling.is_tiled(), |div| {
155 div.shadow(vec![gpui::BoxShadow {
156 color: Hsla {
157 h: 0.,
158 s: 0.,
159 l: 0.,
160 a: 0.3,
161 },
162 blur_radius: SHADOW_SIZE / 2.,
163 spread_radius: px(0.),
164 offset: point(px(0.0), px(0.0)),
165 }])
166 }),
167 })
168 .on_mouse_move(|_e, _, cx| {
169 cx.stop_propagation();
170 })
171 .bg(gpui::transparent_black())
172 .size_full()
173 .children(self.children),
174 )
175 }
176}
177
178fn resize_edge(pos: Point<Pixels>, shadow_size: Pixels, size: Size<Pixels>) -> Option<ResizeEdge> {
179 let edge = if pos.y < shadow_size && pos.x < shadow_size {
180 ResizeEdge::TopLeft
181 } else if pos.y < shadow_size && pos.x > size.width - shadow_size {
182 ResizeEdge::TopRight
183 } else if pos.y < shadow_size {
184 ResizeEdge::Top
185 } else if pos.y > size.height - shadow_size && pos.x < shadow_size {
186 ResizeEdge::BottomLeft
187 } else if pos.y > size.height - shadow_size && pos.x > size.width - shadow_size {
188 ResizeEdge::BottomRight
189 } else if pos.y > size.height - shadow_size {
190 ResizeEdge::Bottom
191 } else if pos.x < shadow_size {
192 ResizeEdge::Left
193 } else if pos.x > size.width - shadow_size {
194 ResizeEdge::Right
195 } else {
196 return None;
197 };
198 Some(edge)
199}