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