## Before <img width="1712" alt="image" src="https://github.com/user-attachments/assets/f4e74cf0-e386-4e91-86d1-05ee75b26471"> ## After <img width="1712" alt="image" src="https://github.com/user-attachments/assets/da393ea9-79ee-4535-a46d-68684d7ad738">
207 lines
5.6 KiB
Rust
207 lines
5.6 KiB
Rust
use crate::{h_flex, theme::ActiveTheme, Icon, IconName, InteractiveElementExt as _, Sizable as _};
|
|
use gpui::{
|
|
div, prelude::FluentBuilder as _, px, AnyElement, Hsla, InteractiveElement as _, IntoElement,
|
|
ParentElement, Pixels, RenderOnce, StatefulInteractiveElement as _, Styled, WindowContext,
|
|
};
|
|
|
|
/// TitleBar used to customize the appearance of the title bar.
|
|
///
|
|
/// We can put some elements inside the title bar.
|
|
#[derive(IntoElement)]
|
|
pub struct TitleBar {
|
|
children: Vec<AnyElement>,
|
|
}
|
|
|
|
pub const TITLE_BAR_HEIGHT: Pixels = px(35.);
|
|
|
|
impl TitleBar {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
children: Vec::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ParentElement for TitleBar {
|
|
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
|
self.children.extend(elements);
|
|
}
|
|
}
|
|
|
|
// The Windows control buttons have a fixed width of 35px.
|
|
//
|
|
// We don't need implementation the click event for the control buttons.
|
|
// If user clicked in the bounds, the window event will be triggered.
|
|
#[derive(IntoElement, Clone, Copy)]
|
|
enum ControlIcon {
|
|
Minimize,
|
|
Restore,
|
|
Maximize,
|
|
Close,
|
|
}
|
|
|
|
impl ControlIcon {
|
|
fn minimize() -> Self {
|
|
Self::Minimize
|
|
}
|
|
|
|
fn restore() -> Self {
|
|
Self::Restore
|
|
}
|
|
|
|
fn maximize() -> Self {
|
|
Self::Maximize
|
|
}
|
|
|
|
fn close() -> Self {
|
|
Self::Close
|
|
}
|
|
|
|
fn id(&self) -> &'static str {
|
|
match self {
|
|
Self::Minimize => "minimize",
|
|
Self::Restore => "restore",
|
|
Self::Maximize => "maximize",
|
|
Self::Close => "close",
|
|
}
|
|
}
|
|
|
|
fn icon(&self) -> IconName {
|
|
match self {
|
|
Self::Minimize => IconName::WindowMinimize,
|
|
Self::Restore => IconName::WindowRestore,
|
|
Self::Maximize => IconName::WindowMaximize,
|
|
Self::Close => IconName::WindowClose,
|
|
}
|
|
}
|
|
|
|
fn is_close(&self) -> bool {
|
|
matches!(self, Self::Close)
|
|
}
|
|
|
|
fn fg(&self, cx: &WindowContext) -> Hsla {
|
|
if cx.theme().mode.is_dark() {
|
|
crate::white()
|
|
} else {
|
|
crate::black()
|
|
}
|
|
}
|
|
|
|
fn hover_fg(&self, cx: &WindowContext) -> Hsla {
|
|
if self.is_close() || cx.theme().mode.is_dark() {
|
|
crate::white()
|
|
} else {
|
|
crate::black()
|
|
}
|
|
}
|
|
|
|
fn hover_bg(&self, cx: &WindowContext) -> Hsla {
|
|
if self.is_close() {
|
|
if cx.theme().mode.is_dark() {
|
|
crate::red_800()
|
|
} else {
|
|
crate::red_600()
|
|
}
|
|
} else if cx.theme().mode.is_dark() {
|
|
crate::stone_700()
|
|
} else {
|
|
crate::stone_200()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl RenderOnce for ControlIcon {
|
|
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
|
let fg = self.fg(cx);
|
|
let hover_fg = self.hover_fg(cx);
|
|
let hover_bg = self.hover_bg(cx);
|
|
let icon = self;
|
|
let is_linux = cfg!(target_os = "linux");
|
|
|
|
div()
|
|
.id(self.id())
|
|
.flex()
|
|
.cursor_pointer()
|
|
.w(TITLE_BAR_HEIGHT)
|
|
.h_full()
|
|
.justify_center()
|
|
.content_center()
|
|
.items_center()
|
|
.text_color(fg)
|
|
.when(is_linux, |this| {
|
|
this.on_click(move |_, cx| match icon {
|
|
Self::Minimize => cx.minimize_window(),
|
|
Self::Restore => cx.zoom_window(),
|
|
Self::Maximize => cx.zoom_window(),
|
|
Self::Close => cx.remove_window(),
|
|
})
|
|
})
|
|
.hover(|style| style.bg(hover_bg).text_color(hover_fg))
|
|
.active(|style| style.bg(hover_bg.opacity(0.7)))
|
|
.child(Icon::new(self.icon()).small())
|
|
}
|
|
}
|
|
|
|
#[derive(IntoElement)]
|
|
struct WindowControls {}
|
|
|
|
impl RenderOnce for WindowControls {
|
|
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
|
if cfg!(target_os = "macos") {
|
|
return div().id("window-controls");
|
|
}
|
|
|
|
h_flex()
|
|
.id("window-controls")
|
|
.items_center()
|
|
.flex_shrink_0()
|
|
.h_full()
|
|
.child(
|
|
h_flex()
|
|
.justify_center()
|
|
.content_stretch()
|
|
.h_full()
|
|
.child(ControlIcon::minimize())
|
|
.child(if cx.is_maximized() {
|
|
ControlIcon::restore()
|
|
} else {
|
|
ControlIcon::maximize()
|
|
}),
|
|
)
|
|
.child(ControlIcon::close())
|
|
}
|
|
}
|
|
|
|
impl RenderOnce for TitleBar {
|
|
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
|
let macos_pl = if cfg!(target_os = "macos") {
|
|
Some(px(80.))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
h_flex()
|
|
.id("title-bar")
|
|
.flex_shrink_0()
|
|
.items_center()
|
|
.justify_between()
|
|
.pl(px(12.))
|
|
.when(!cx.is_fullscreen(), |this| {
|
|
// Leave space for the macOS window controls.
|
|
this.when_some(macos_pl, |this, pl| this.pl(pl))
|
|
})
|
|
.border_b_1()
|
|
.border_color(cx.theme().title_bar_border)
|
|
.bg(cx.theme().title_bar_background)
|
|
.on_double_click(|_, cx| cx.zoom_window())
|
|
.child(
|
|
h_flex()
|
|
.h(px(34.))
|
|
.justify_between()
|
|
.flex_shrink_0()
|
|
.flex_1()
|
|
.children(self.children),
|
|
)
|
|
.child(WindowControls {})
|
|
}
|
|
}
|