use fluent_core::{Theme, ThemeProvider as _};
use gpui::{
div, prelude::*, px, svg, ClickEvent, Context, IntoElement, MouseButton, MouseDownEvent,
Render, SharedString, Window,
};
const DOUBLE_CLICK_WINDOW_MS: u64 = 350;
pub struct TitleBar {
pub title: SharedString,
pub icon: Option<SharedString>,
pub show_controls: bool,
last_press_ms: u64,
}
impl TitleBar {
pub fn new(cx: &mut Context<Self>, title: impl Into<SharedString>) -> Self {
cx.observe_global::<Theme>(|_, cx| cx.notify()).detach();
Self {
title: title.into(),
icon: None,
show_controls: true,
last_press_ms: 0,
}
}
pub fn show_controls(mut self, show: bool) -> Self {
self.show_controls = show;
self
}
pub fn icon(mut self, path: impl Into<SharedString>) -> Self {
self.icon = Some(path.into());
self
}
}
impl Render for TitleBar {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let theme = cx.theme();
let colors = theme.colors.clone();
let spacing = theme.spacing;
let typography = theme.typography;
let title = self.title.clone();
let show_controls = self.show_controls;
let bar_bg = colors.surface_dim;
let fg = colors.on_neutral;
let ctrl_hover = colors.subtle_hover;
let close_hover: gpui::Hsla = gpui::rgb(0xC42B1C).into();
let drag_handler = cx.listener(
|this: &mut TitleBar, _: &MouseDownEvent, window: &mut Window, _| {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
if now.saturating_sub(this.last_press_ms) < DOUBLE_CLICK_WINDOW_MS {
this.last_press_ms = 0;
window.zoom_window();
return;
}
this.last_press_ms = now;
window.start_window_move();
},
);
let min_handler =
cx.listener(|_: &mut TitleBar, _: &ClickEvent, window: &mut Window, _| {
window.minimize_window();
});
let max_handler =
cx.listener(|_: &mut TitleBar, _: &ClickEvent, window: &mut Window, _| {
window.zoom_window();
});
let close_handler =
cx.listener(|_: &mut TitleBar, _: &ClickEvent, window: &mut Window, _| {
window.remove_window();
});
let icon = self.icon.clone();
let accent_fg = colors.on_neutral_accent;
let mut title_area = div()
.flex_1()
.h_full()
.flex()
.items_center()
.gap(px(spacing.sm))
.pl(px(spacing.md))
.text_size(px(typography.caption.size))
.text_color(fg)
.on_mouse_down(MouseButton::Left, drag_handler);
if let Some(path) = icon {
title_area = title_area.child(svg().path(path).size(px(16.0)).text_color(accent_fg));
}
let title_area = title_area.child(title);
let bar = div()
.flex()
.flex_row()
.h(px(36.0))
.bg(bar_bg)
.child(title_area);
if !show_controls {
return bar;
}
bar.child(
div()
.flex()
.flex_row()
.h_full()
.child(
div()
.id("titlebar-min")
.w(px(46.0))
.h_full()
.flex()
.items_center()
.justify_center()
.cursor_pointer()
.hover(move |s| s.bg(ctrl_hover))
.on_click(min_handler)
.child(
svg()
.path("icons/minimize.svg")
.size(px(10.0))
.text_color(fg),
),
)
.child(
div()
.id("titlebar-max")
.w(px(46.0))
.h_full()
.flex()
.items_center()
.justify_center()
.cursor_pointer()
.hover(move |s| s.bg(ctrl_hover))
.on_click(max_handler)
.child(
svg()
.path("icons/maximize.svg")
.size(px(10.0))
.text_color(fg),
),
)
.child(
div()
.id("titlebar-close")
.w(px(46.0))
.h_full()
.flex()
.items_center()
.justify_center()
.cursor_pointer()
.hover(move |s| s.bg(close_hover))
.on_click(close_handler)
.child(
svg()
.path("icons/dismiss.svg")
.size(px(10.0))
.text_color(fg),
),
),
)
}
}