window_shadow.rs

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