use gpui::{ black, canvas, div, green, point, prelude::*, px, rgb, size, transparent_black, white, App, AppContext, Bounds, CursorStyle, Decorations, Hsla, MouseButton, Pixels, Point, ResizeEdge, Size, ViewContext, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowOptions, }; struct WindowShadow {} // Things to do: // 1. We need a way of calculating which edge or corner the mouse is on, // and then dispatch on that // 2. We need to improve the shadow rendering significantly // 3. We need to implement the techniques in here in Zed impl Render for WindowShadow { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let decorations = cx.window_decorations(); let rounding = px(10.0); let shadow_size = px(10.0); let border_size = px(1.0); let grey = rgb(0x808080); cx.set_client_inset(shadow_size); div() .id("window-backdrop") .bg(transparent_black()) .map(|div| match decorations { Decorations::Server => div, Decorations::Client { tiling, .. } => div .bg(gpui::transparent_black()) .child( canvas( |_bounds, cx| { cx.insert_hitbox( Bounds::new( point(px(0.0), px(0.0)), cx.window_bounds().get_bounds().size, ), false, ) }, move |_bounds, hitbox, cx| { let mouse = cx.mouse_position(); let size = cx.window_bounds().get_bounds().size; let Some(edge) = resize_edge(mouse, shadow_size, size) else { return; }; cx.set_cursor_style( match edge { ResizeEdge::Top | ResizeEdge::Bottom => { CursorStyle::ResizeUpDown } ResizeEdge::Left | ResizeEdge::Right => { CursorStyle::ResizeLeftRight } ResizeEdge::TopLeft | ResizeEdge::BottomRight => { CursorStyle::ResizeUpLeftDownRight } ResizeEdge::TopRight | ResizeEdge::BottomLeft => { CursorStyle::ResizeUpRightDownLeft } }, &hitbox, ); }, ) .size_full() .absolute(), ) .when(!(tiling.top || tiling.right), |div| { div.rounded_tr(rounding) }) .when(!(tiling.top || tiling.left), |div| div.rounded_tl(rounding)) .when(!tiling.top, |div| div.pt(shadow_size)) .when(!tiling.bottom, |div| div.pb(shadow_size)) .when(!tiling.left, |div| div.pl(shadow_size)) .when(!tiling.right, |div| div.pr(shadow_size)) .on_mouse_move(|_e, cx| cx.refresh()) .on_mouse_down(MouseButton::Left, move |e, cx| { let size = cx.window_bounds().get_bounds().size; let pos = e.position; match resize_edge(pos, shadow_size, size) { Some(edge) => cx.start_window_resize(edge), None => cx.start_window_move(), }; }), }) .size_full() .child( div() .cursor(CursorStyle::Arrow) .map(|div| match decorations { Decorations::Server => div, Decorations::Client { tiling } => div .border_color(grey) .when(!(tiling.top || tiling.right), |div| { div.rounded_tr(rounding) }) .when(!(tiling.top || tiling.left), |div| div.rounded_tl(rounding)) .when(!tiling.top, |div| div.border_t(border_size)) .when(!tiling.bottom, |div| div.border_b(border_size)) .when(!tiling.left, |div| div.border_l(border_size)) .when(!tiling.right, |div| div.border_r(border_size)) .when(!tiling.is_tiled(), |div| { div.shadow(smallvec::smallvec![gpui::BoxShadow { color: Hsla { h: 0., s: 0., l: 0., a: 0.4, }, blur_radius: shadow_size / 2., spread_radius: px(0.), offset: point(px(0.0), px(0.0)), }]) }), }) .on_mouse_move(|_e, cx| { cx.stop_propagation(); }) .bg(gpui::rgb(0xCCCCFF)) .size_full() .flex() .flex_col() .justify_around() .child( div().w_full().flex().flex_row().justify_around().child( div() .flex() .bg(white()) .size(px(300.0)) .justify_center() .items_center() .shadow_lg() .border_1() .border_color(rgb(0x0000ff)) .text_xl() .text_color(rgb(0xffffff)) .child( div() .id("hello") .w(px(200.0)) .h(px(100.0)) .bg(green()) .shadow(smallvec::smallvec![gpui::BoxShadow { color: Hsla { h: 0., s: 0., l: 0., a: 1.0, }, blur_radius: px(20.0), spread_radius: px(0.0), offset: point(px(0.0), px(0.0)), }]) .map(|div| match decorations { Decorations::Server => div, Decorations::Client { .. } => div .on_mouse_down(MouseButton::Left, |_e, cx| { cx.start_window_move(); }) .on_click(|e, cx| { if e.down.button == MouseButton::Right { cx.show_window_menu(e.up.position); } }) .text_color(black()) .child("this is the custom titlebar"), }), ), ), ), ) } } fn resize_edge(pos: Point, shadow_size: Pixels, size: Size) -> Option { let edge = if pos.y < shadow_size && pos.x < shadow_size { ResizeEdge::TopLeft } else if pos.y < shadow_size && pos.x > size.width - shadow_size { ResizeEdge::TopRight } else if pos.y < shadow_size { ResizeEdge::Top } else if pos.y > size.height - shadow_size && pos.x < shadow_size { ResizeEdge::BottomLeft } else if pos.y > size.height - shadow_size && pos.x > size.width - shadow_size { ResizeEdge::BottomRight } else if pos.y > size.height - shadow_size { ResizeEdge::Bottom } else if pos.x < shadow_size { ResizeEdge::Left } else if pos.x > size.width - shadow_size { ResizeEdge::Right } else { return None; }; Some(edge) } fn main() { App::new().run(|cx: &mut AppContext| { let bounds = Bounds::centered(None, size(px(600.0), px(600.0)), cx); cx.open_window( WindowOptions { window_bounds: Some(WindowBounds::Windowed(bounds)), window_background: WindowBackgroundAppearance::Opaque, window_decorations: Some(WindowDecorations::Client), ..Default::default() }, |cx| { cx.new_view(|cx| { cx.observe_window_appearance(|_, cx| { cx.refresh(); }) .detach(); WindowShadow {} }) }, ) .unwrap(); }); }