root: Add window shadow to Root for support window border style on Linux. (#514)
And fix the title bar buttons click handle on Linux.
This commit is contained in:
parent
3601cb399b
commit
466c5e2005
6 changed files with 226 additions and 46 deletions
|
|
@ -1,5 +1,6 @@
|
|||
use anyhow::{Context, Result};
|
||||
use gpui::*;
|
||||
use prelude::FluentBuilder as _;
|
||||
use std::time::Duration;
|
||||
use story::{Assets, ButtonStory, IconStory, StoryContainer};
|
||||
use ui::{
|
||||
|
|
|
|||
|
|
@ -343,6 +343,7 @@ impl StoryWorkspace {
|
|||
cx.spawn(|mut cx| async move {
|
||||
let options = WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(window_bounds)),
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
titlebar: Some(TitlebarOptions {
|
||||
title: None,
|
||||
appears_transparent: true,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ mod styled;
|
|||
mod svg_img;
|
||||
mod time;
|
||||
mod title_bar;
|
||||
mod window_border;
|
||||
|
||||
pub mod accordion;
|
||||
pub mod animation;
|
||||
|
|
@ -60,6 +61,7 @@ pub use styled::*;
|
|||
pub use time::*;
|
||||
pub use title_bar::*;
|
||||
pub use virtual_list::{h_virtual_list, v_virtual_list, VirtualList};
|
||||
pub use window_border::{window_border, WindowBorder};
|
||||
|
||||
pub use colors::*;
|
||||
pub use icon::*;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use crate::{
|
|||
modal::Modal,
|
||||
notification::{Notification, NotificationList},
|
||||
theme::ActiveTheme,
|
||||
Placement,
|
||||
window_border, Placement,
|
||||
};
|
||||
use gpui::{
|
||||
canvas, div, prelude::FluentBuilder as _, AnyView, DefiniteLength, FocusHandle,
|
||||
|
|
@ -402,12 +402,14 @@ impl Render for Root {
|
|||
let base_font_size = cx.theme().font_size;
|
||||
cx.set_rem_size(base_font_size);
|
||||
|
||||
div()
|
||||
.id("root")
|
||||
.size_full()
|
||||
.font_family(".SystemUIFont")
|
||||
.bg(cx.theme().background)
|
||||
.text_color(cx.theme().foreground)
|
||||
.child(self.view.clone())
|
||||
window_border().child(
|
||||
div()
|
||||
.id("root")
|
||||
.size_full()
|
||||
.font_family(".SystemUIFont")
|
||||
.bg(cx.theme().background)
|
||||
.text_color(cx.theme().foreground)
|
||||
.child(self.view.clone()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::rc::Rc;
|
|||
use crate::{h_flex, theme::ActiveTheme, Icon, IconName, InteractiveElementExt as _, Sizable as _};
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, px, relative, AnyElement, ClickEvent, Div, Element, Hsla,
|
||||
InteractiveElement as _, IntoElement, ParentElement, Pixels, RenderOnce, Stateful,
|
||||
InteractiveElement as _, IntoElement, MouseButton, ParentElement, Pixels, RenderOnce, Stateful,
|
||||
StatefulInteractiveElement as _, Style, Styled, WindowContext,
|
||||
};
|
||||
|
||||
|
|
@ -152,7 +152,11 @@ impl RenderOnce for ControlIcon {
|
|||
.items_center()
|
||||
.text_color(fg)
|
||||
.when(is_linux, |this| {
|
||||
this.on_click(move |_, cx| match icon {
|
||||
this.on_mouse_down(MouseButton::Left, move |_, cx| {
|
||||
cx.prevent_default();
|
||||
cx.stop_propagation();
|
||||
})
|
||||
.on_click(move |_, cx| match icon {
|
||||
Self::Minimize => cx.minimize_window(),
|
||||
Self::Restore => cx.zoom_window(),
|
||||
Self::Maximize => cx.zoom_window(),
|
||||
|
|
@ -221,43 +225,41 @@ impl RenderOnce for TitleBar {
|
|||
|
||||
const HEIGHT: Pixels = px(34.);
|
||||
|
||||
div()
|
||||
.flex_shrink_0()
|
||||
.child(
|
||||
self.base
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.h(HEIGHT)
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().title_bar_border)
|
||||
.bg(cx.theme().title_bar)
|
||||
.when(cx.is_fullscreen(), |this| this.pl(px(12.)))
|
||||
.on_double_click(|_, cx| cx.zoom_window())
|
||||
.child(
|
||||
h_flex()
|
||||
.h_full()
|
||||
.justify_between()
|
||||
.flex_shrink_0()
|
||||
.flex_1()
|
||||
.children(self.children),
|
||||
)
|
||||
.child(WindowControls {
|
||||
on_close_window: self.on_close_window,
|
||||
}),
|
||||
)
|
||||
.when(is_linux, |this| {
|
||||
this.child(
|
||||
div()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.absolute()
|
||||
.size_full()
|
||||
div().flex_shrink_0().child(
|
||||
self.base
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.h(HEIGHT)
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().title_bar_border)
|
||||
.bg(cx.theme().title_bar)
|
||||
.when(cx.is_fullscreen(), |this| this.pl(px(12.)))
|
||||
.on_double_click(|_, cx| cx.zoom_window())
|
||||
.child(
|
||||
h_flex()
|
||||
.h_full()
|
||||
.child(TitleBarElement {}),
|
||||
.justify_between()
|
||||
.flex_shrink_0()
|
||||
.flex_1()
|
||||
.when(is_linux, |this| {
|
||||
this.child(
|
||||
div()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.absolute()
|
||||
.size_full()
|
||||
.h_full()
|
||||
.child(TitleBarElement {}),
|
||||
)
|
||||
})
|
||||
.children(self.children),
|
||||
)
|
||||
})
|
||||
.child(WindowControls {
|
||||
on_close_window: self.on_close_window,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -322,7 +324,7 @@ impl Element for TitleBarElement {
|
|||
});
|
||||
|
||||
cx.on_mouse_event(move |ev: &MouseUpEvent, _, cx: &mut WindowContext| {
|
||||
if ev.button == MouseButton::Left {
|
||||
if bounds.contains(&ev.position) && ev.button == MouseButton::Right {
|
||||
cx.show_window_menu(ev.position);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
172
crates/ui/src/window_border.rs
Normal file
172
crates/ui/src/window_border.rs
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
// 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, Hsla, InteractiveElement as _, IntoElement, MouseButton, ParentElement, Pixels,
|
||||
Point, RenderOnce, ResizeEdge, Size, Styled as _, WindowContext,
|
||||
};
|
||||
|
||||
const SHADOW_SIZE: Pixels = Pixels(12.0);
|
||||
const BORDER_SIZE: Pixels = Pixels(0.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<AnyElement>,
|
||||
}
|
||||
|
||||
impl WindowBorder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for WindowBorder {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
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();
|
||||
|
||||
match resize_edge(pos, SHADOW_SIZE, size) {
|
||||
Some(edge) => cx.start_window_resize(edge),
|
||||
None => {}
|
||||
};
|
||||
}),
|
||||
})
|
||||
.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<Pixels>, shadow_size: Pixels, size: Size<Pixels>) -> Option<ResizeEdge> {
|
||||
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)
|
||||
}
|
||||
Loading…
Reference in a new issue