window_shadow.rs

  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}