window_shadow.rs

  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}