// From: // https://github.com/zed-industries/zed/blob/a8afc63a91f6b75528540dcffe73dc8ce0c92ad8/crates/gpui/examples/window_shadow.rs use gpui::{ canvas, div, point, prelude::FluentBuilder as _, px, AnyElement, Bounds, CursorStyle, Decorations, Edges, Hsla, InteractiveElement as _, IntoElement, MouseButton, ParentElement, Pixels, Point, RenderOnce, ResizeEdge, Size, Styled as _, WindowContext, }; use crate::theme::ActiveTheme; #[cfg(not(target_os = "linux"))] pub(crate) const SHADOW_SIZE: Pixels = Pixels(0.0); #[cfg(target_os = "linux")] pub(crate) const SHADOW_SIZE: Pixels = Pixels(12.0); pub(crate) const BORDER_SIZE: Pixels = Pixels(1.0); pub(crate) const BORDER_RADIUS: Pixels = Pixels(0.0); /// Create a new window border. pub fn window_border() -> WindowBorder { WindowBorder::new() } /// Window border use to render a custom window border and shadow for Linux. #[derive(IntoElement, Default)] pub struct WindowBorder { children: Vec, } /// Get the window paddings. pub fn window_paddings(cx: &WindowContext) -> Edges { match cx.window_decorations() { Decorations::Server => Edges::all(px(0.0)), Decorations::Client { tiling } => { let mut paddings = Edges::all(SHADOW_SIZE); if tiling.top { paddings.top = px(0.0); } if tiling.bottom { paddings.bottom = px(0.0); } if tiling.left { paddings.left = px(0.0); } if tiling.right { paddings.right = px(0.0); } paddings } } } impl WindowBorder { pub fn new() -> Self { Self { ..Default::default() } } } impl ParentElement for WindowBorder { fn extend(&mut self, elements: impl IntoIterator) { self.children.extend(elements); } } impl RenderOnce for WindowBorder { fn render(self, cx: &mut WindowContext) -> impl IntoElement { let decorations = cx.window_decorations(); cx.set_client_inset(SHADOW_SIZE); div() .id("window-backdrop") .bg(gpui::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(BORDER_RADIUS) }) .when(!(tiling.top || tiling.left), |div| { div.rounded_tl(BORDER_RADIUS) }) .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 |_, cx| { let size = cx.window_bounds().get_bounds().size; let pos = cx.mouse_position(); if let Some(edge) = resize_edge(pos, SHADOW_SIZE, size) { cx.start_window_resize(edge) }; }), }) .size_full() .child( div() .map(|div| match decorations { Decorations::Server => div, Decorations::Client { tiling } => div .border_color(cx.theme().window_border) .when(!(tiling.top || tiling.right), |div| { div.rounded_tr(BORDER_RADIUS) }) .when(!(tiling.top || tiling.left), |div| { div.rounded_tl(BORDER_RADIUS) }) .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.3, }, 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::transparent_black()) .size_full() .children(self.children), ) } } 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) }