use std::{cell::Cell, rc::Rc};
use gpui::{
App, Div, ElementId, MAX_BUTTONS_PER_SIDE, MouseButton, Stateful, Window, WindowButton,
WindowButtonLayout, WindowControlArea, div, prelude::*, px,
};
use theme::Theme;
#[derive(Clone, Default)]
pub struct DragState(Rc<Cell<bool>>);
pub fn titlebar(id: impl Into<ElementId>, traffic_lights: bool, window: &Window) -> Stateful<Div> {
div()
.id(id)
.w_full()
.h(px(Theme::TITLEBAR_HEIGHT))
.flex()
.flex_row()
.items_center()
.when(traffic_lights && !window.is_fullscreen(), |bar| {
bar.pl(px(Theme::TRAFFIC_LIGHT_INSET))
})
}
pub fn grip(id: impl Into<ElementId>, drag: &DragState, window: &Window) -> Stateful<Div> {
let (armed, disarm, release) = (drag.0.clone(), drag.0.clone(), drag.0.clone());
let moving = drag.0.clone();
let zoomable = window.window_controls().maximize && window.is_resizable();
div()
.id(id)
.flex_1()
.self_stretch()
.on_mouse_down(MouseButton::Left, move |_, _, _| armed.set(true))
.on_mouse_up(MouseButton::Left, move |_, _, _| release.set(false))
.on_mouse_down_out(move |_, _, _| disarm.set(false))
.on_mouse_move(move |_, window, _| {
if moving.replace(false) {
window.start_window_move();
}
})
.window_control_area(WindowControlArea::Drag)
.when(window.window_controls().window_menu, |grip| {
grip.on_mouse_down(MouseButton::Right, |event, window, _| {
window.show_window_menu(event.position);
})
})
.on_click(move |click, window, _| {
if click.click_count() == 2 {
match cfg!(any(target_os = "linux", target_os = "freebsd")) {
true if zoomable => window.zoom_window(),
true => {}
false => window.titlebar_double_click(),
}
}
})
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CaptionSide {
Left,
Right,
}
pub fn controls(side: CaptionSide, window: &Window, cx: &App) -> Div {
let row = div().flex().flex_row().items_center().h_full();
if window.is_fullscreen() {
return row;
}
let allowed = window.window_controls();
let layout = cx.button_layout().unwrap_or(TRAILING);
let buttons = match side {
CaptionSide::Left => layout.left,
CaptionSide::Right => layout.right,
};
buttons
.into_iter()
.flatten()
.filter(|button| match button {
WindowButton::Close => true,
WindowButton::Maximize => allowed.maximize,
WindowButton::Minimize => allowed.minimize,
})
.fold(row, |row, button| {
row.child(caption_button(button, window, cx))
})
}
const TRAILING: WindowButtonLayout = WindowButtonLayout {
left: [None; MAX_BUTTONS_PER_SIDE],
right: [
Some(WindowButton::Minimize),
Some(WindowButton::Maximize),
Some(WindowButton::Close),
],
};
const CAPTION_GLYPH: f32 = 10.0;
fn caption_button(button: WindowButton, window: &Window, cx: &App) -> Stateful<Div> {
let theme = Theme::of(cx);
let (area, glyph, hover) = match button {
WindowButton::Close => (WindowControlArea::Close, icons::glyph::X, theme.danger),
WindowButton::Minimize => (
WindowControlArea::Min,
icons::glyph::Minus,
theme.element_hover,
),
WindowButton::Maximize => (
WindowControlArea::Max,
match window.is_maximized() {
true => icons::glyph::Copy,
false => icons::glyph::Square,
},
theme.element_hover,
),
};
div()
.id(button.id())
.flex()
.items_center()
.justify_center()
.w(px(Theme::CAPTION_BUTTON_WIDTH))
.h_full()
.hover(|button| button.bg(hover))
.child(
icons::icon(glyph)
.size(px(CAPTION_GLYPH))
.text_color(theme.text),
)
.window_control_area(area)
.when(!cfg!(target_os = "windows"), |control| {
control.on_click(move |_, window, _| match button {
WindowButton::Close => window.remove_window(),
WindowButton::Minimize => window.minimize_window(),
WindowButton::Maximize => window.zoom_window(),
})
})
}