Skip to main content

ui/components/
title_bar.rs

1use component::{Component, ComponentScope, example_group_with_title, single_example};
2use gpui::{AnyElement, ClickEvent};
3
4use crate::prelude::*;
5
6/// Height of the [`TitleBar`] chrome.
7const TITLE_BAR_HEIGHT: Pixels = px(36.);
8/// Diameter of each traffic-light button.
9const TRAFFIC_LIGHT_SIZE: Pixels = px(12.);
10
11/// A single traffic-light button (`close`/`minimize`/`maximize`).
12struct TrafficLight {
13    color: u32,
14    on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
15}
16
17impl TrafficLight {
18    fn render(self, id: &'static str) -> impl IntoElement {
19        div()
20            .id(id)
21            .size(TRAFFIC_LIGHT_SIZE)
22            .rounded_full()
23            .bg(gpui::rgb(self.color))
24            .when(self.on_click.is_some(), |this| this.cursor_pointer())
25            .when_some(self.on_click, |this, handler| this.on_click(handler))
26    }
27}
28
29/// Chrome-only window title bar: a centered title plus three macOS-style
30/// traffic-light buttons (close/minimize/maximize). Renders no real window
31/// controls — callers supply `on_close`/`on_minimize`/`on_maximize`, which
32/// is the caller's (`crates/app`, via the `gpui_platform` facade) job, not
33/// this crate's. Platform-agnostic: this crate never calls Cocoa/Win32 APIs
34/// directly (see `docs/code-standards.md` § Platform-Specific Code).
35#[derive(IntoElement, RegisterComponent)]
36pub struct TitleBar {
37    title: SharedString,
38    on_close: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
39    on_minimize: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
40    on_maximize: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
41}
42
43impl TitleBar {
44    pub fn new(title: impl Into<SharedString>) -> Self {
45        Self {
46            title: title.into(),
47            on_close: None,
48            on_minimize: None,
49            on_maximize: None,
50        }
51    }
52
53    pub fn on_close(
54        mut self,
55        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
56    ) -> Self {
57        self.on_close = Some(Box::new(handler));
58        self
59    }
60
61    pub fn on_minimize(
62        mut self,
63        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
64    ) -> Self {
65        self.on_minimize = Some(Box::new(handler));
66        self
67    }
68
69    pub fn on_maximize(
70        mut self,
71        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
72    ) -> Self {
73        self.on_maximize = Some(Box::new(handler));
74        self
75    }
76}
77
78impl RenderOnce for TitleBar {
79    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
80        h_flex()
81            .id("title-bar")
82            .w_full()
83            .h(TITLE_BAR_HEIGHT)
84            .flex_none()
85            .items_center()
86            .px_3()
87            .gap_2()
88            .bg(semantic::surface(cx))
89            .border_b_1()
90            .border_color(semantic::border(cx))
91            .child(
92                h_flex()
93                    .flex_none()
94                    .gap_2()
95                    .child(
96                        TrafficLight {
97                            color: 0xFF5F57,
98                            on_click: self.on_close,
99                        }
100                        .render("title-bar-close"),
101                    )
102                    .child(
103                        TrafficLight {
104                            color: 0xFEBC2E,
105                            on_click: self.on_minimize,
106                        }
107                        .render("title-bar-minimize"),
108                    )
109                    .child(
110                        TrafficLight {
111                            color: 0x28C840,
112                            on_click: self.on_maximize,
113                        }
114                        .render("title-bar-maximize"),
115                    ),
116            )
117            .child(
118                div()
119                    .flex_1()
120                    .flex()
121                    .justify_center()
122                    .child(Label::new(self.title).size(LabelSize::Small)),
123            )
124            // Balances the traffic-light cluster's width so the title stays
125            // visually centered in the bar, not just in the remaining space.
126            .child(div().flex_none().w(px(3. * 12. + 2. * 8.)))
127    }
128}
129
130impl Component for TitleBar {
131    fn scope() -> ComponentScope {
132        ComponentScope::Layout
133    }
134
135    fn description() -> Option<&'static str> {
136        Some(
137            "Chrome-only window title bar with a centered title and macOS-style \
138             close/minimize/maximize buttons. No real window control calls.",
139        )
140    }
141
142    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
143        Some(
144            v_flex()
145                .gap_6()
146                .child(example_group_with_title(
147                    "Basic Usage",
148                    vec![
149                        single_example(
150                            "With Handlers",
151                            TitleBar::new("Untitled Project")
152                                .on_close(|_, _, _| {})
153                                .on_minimize(|_, _, _| {})
154                                .on_maximize(|_, _, _| {})
155                                .into_any_element(),
156                        ),
157                        single_example(
158                            "Without Handlers",
159                            TitleBar::new("Read-only Preview").into_any_element(),
160                        ),
161                    ],
162                ))
163                .into_any_element(),
164        )
165    }
166}