1#![cfg_attr(target_family = "wasm", no_main)]
2
3use gpui::{
4 App, Bounds, Context, CursorStyle, Decorations, HitboxBehavior, Hsla, MouseButton, Pixels,
5 Point, ResizeEdge, Size, Window, WindowBackgroundAppearance, WindowBounds, WindowDecorations,
6 WindowOptions, black, canvas, div, green, point, prelude::*, px, rgb, size, transparent_black,
7 white,
8};
9use gpui_platform::application;
10
11struct WindowShadow {}
12
13// Things to do:
14// 1. We need a way of calculating which edge or corner the mouse is on,
15// and then dispatch on that
16// 2. We need to improve the shadow rendering significantly
17// 3. We need to implement the techniques in here in Zed
18
19impl Render for WindowShadow {
20 fn render(&mut self, window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
21 let decorations = window.window_decorations();
22 let rounding = px(10.0);
23 let shadow_size = px(10.0);
24 let border_size = px(1.0);
25 let grey = rgb(0x808080);
26 window.set_client_inset(shadow_size);
27
28 div()
29 .id("window-backdrop")
30 .bg(transparent_black())
31 .map(|div| match decorations {
32 Decorations::Server => div,
33 Decorations::Client { tiling, .. } => div
34 .bg(gpui::transparent_black())
35 .child(
36 canvas(
37 |_bounds, window, _cx| {
38 window.insert_hitbox(
39 Bounds::new(
40 point(px(0.0), px(0.0)),
41 window.window_bounds().get_bounds().size,
42 ),
43 HitboxBehavior::Normal,
44 )
45 },
46 move |_bounds, hitbox, window, _cx| {
47 let mouse = window.mouse_position();
48 let size = window.window_bounds().get_bounds().size;
49 let Some(edge) = resize_edge(mouse, shadow_size, size) else {
50 return;
51 };
52 window.set_cursor_style(
53 match edge {
54 ResizeEdge::Top | ResizeEdge::Bottom => {
55 CursorStyle::ResizeUpDown
56 }
57 ResizeEdge::Left | ResizeEdge::Right => {
58 CursorStyle::ResizeLeftRight
59 }
60 ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
61 CursorStyle::ResizeUpLeftDownRight
62 }
63 ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
64 CursorStyle::ResizeUpRightDownLeft
65 }
66 },
67 &hitbox,
68 );
69 },
70 )
71 .size_full()
72 .absolute(),
73 )
74 .when(!(tiling.top || tiling.right), |div| {
75 div.rounded_tr(rounding)
76 })
77 .when(!(tiling.top || tiling.left), |div| div.rounded_tl(rounding))
78 .when(!tiling.top, |div| div.pt(shadow_size))
79 .when(!tiling.bottom, |div| div.pb(shadow_size))
80 .when(!tiling.left, |div| div.pl(shadow_size))
81 .when(!tiling.right, |div| div.pr(shadow_size))
82 .on_mouse_move(|_e, window, _cx| window.refresh())
83 .on_mouse_down(MouseButton::Left, move |e, window, _cx| {
84 let size = window.window_bounds().get_bounds().size;
85 let pos = e.position;
86
87 match resize_edge(pos, shadow_size, size) {
88 Some(edge) => window.start_window_resize(edge),
89 None => window.start_window_move(),
90 };
91 }),
92 })
93 .size_full()
94 .child(
95 div()
96 .cursor(CursorStyle::Arrow)
97 .map(|div| match decorations {
98 Decorations::Server => div,
99 Decorations::Client { tiling } => div
100 .border_color(grey)
101 .when(!(tiling.top || tiling.right), |div| {
102 div.rounded_tr(rounding)
103 })
104 .when(!(tiling.top || tiling.left), |div| div.rounded_tl(rounding))
105 .when(!tiling.top, |div| div.border_t(border_size))
106 .when(!tiling.bottom, |div| div.border_b(border_size))
107 .when(!tiling.left, |div| div.border_l(border_size))
108 .when(!tiling.right, |div| div.border_r(border_size))
109 .when(!tiling.is_tiled(), |div| {
110 div.shadow(vec![gpui::BoxShadow {
111 color: Hsla {
112 h: 0.,
113 s: 0.,
114 l: 0.,
115 a: 0.4,
116 },
117 blur_radius: shadow_size / 2.,
118 spread_radius: px(0.),
119 offset: point(px(0.0), px(0.0)),
120 }])
121 }),
122 })
123 .on_mouse_move(|_e, _, cx| {
124 cx.stop_propagation();
125 })
126 .bg(gpui::rgb(0xCCCCFF))
127 .size_full()
128 .flex()
129 .flex_col()
130 .justify_around()
131 .child(
132 div().w_full().flex().flex_row().justify_around().child(
133 div()
134 .flex()
135 .bg(white())
136 .size(px(300.0))
137 .justify_center()
138 .items_center()
139 .shadow_lg()
140 .border_1()
141 .border_color(rgb(0x0000ff))
142 .text_xl()
143 .text_color(rgb(0xffffff))
144 .child(
145 div()
146 .id("hello")
147 .w(px(200.0))
148 .h(px(100.0))
149 .bg(green())
150 .shadow(vec![gpui::BoxShadow {
151 color: Hsla {
152 h: 0.,
153 s: 0.,
154 l: 0.,
155 a: 1.0,
156 },
157 blur_radius: px(20.0),
158 spread_radius: px(0.0),
159 offset: point(px(0.0), px(0.0)),
160 }])
161 .map(|div| match decorations {
162 Decorations::Server => div,
163 Decorations::Client { .. } => div
164 .on_mouse_down(
165 MouseButton::Left,
166 |_e, window, _| {
167 window.start_window_move();
168 },
169 )
170 .on_click(|e, window, _| {
171 if e.is_right_click() {
172 window.show_window_menu(e.position());
173 }
174 })
175 .text_color(black())
176 .child("this is the custom titlebar"),
177 }),
178 ),
179 ),
180 ),
181 )
182 }
183}
184
185fn resize_edge(pos: Point<Pixels>, shadow_size: Pixels, size: Size<Pixels>) -> Option<ResizeEdge> {
186 let edge = if pos.y < shadow_size && pos.x < shadow_size {
187 ResizeEdge::TopLeft
188 } else if pos.y < shadow_size && pos.x > size.width - shadow_size {
189 ResizeEdge::TopRight
190 } else if pos.y < shadow_size {
191 ResizeEdge::Top
192 } else if pos.y > size.height - shadow_size && pos.x < shadow_size {
193 ResizeEdge::BottomLeft
194 } else if pos.y > size.height - shadow_size && pos.x > size.width - shadow_size {
195 ResizeEdge::BottomRight
196 } else if pos.y > size.height - shadow_size {
197 ResizeEdge::Bottom
198 } else if pos.x < shadow_size {
199 ResizeEdge::Left
200 } else if pos.x > size.width - shadow_size {
201 ResizeEdge::Right
202 } else {
203 return None;
204 };
205 Some(edge)
206}
207
208fn run_example() {
209 application().run(|cx: &mut App| {
210 let bounds = Bounds::centered(None, size(px(600.0), px(600.0)), cx);
211 cx.open_window(
212 WindowOptions {
213 window_bounds: Some(WindowBounds::Windowed(bounds)),
214 window_background: WindowBackgroundAppearance::Opaque,
215 window_decorations: Some(WindowDecorations::Client),
216 ..Default::default()
217 },
218 |window, cx| {
219 cx.new(|cx| {
220 cx.observe_window_appearance(window, |_, window, _| {
221 window.refresh();
222 })
223 .detach();
224 WindowShadow {}
225 })
226 },
227 )
228 .unwrap();
229 });
230}
231
232#[cfg(not(target_family = "wasm"))]
233fn main() {
234 run_example();
235}
236
237#[cfg(target_family = "wasm")]
238#[wasm_bindgen::prelude::wasm_bindgen(start)]
239pub fn start() {
240 gpui_platform::web_init();
241 run_example();
242}