window_shadow.rs

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