ui/components/
title_bar.rs1use component::{Component, ComponentScope, example_group_with_title, single_example};
2use gpui::{AnyElement, ClickEvent};
3
4use crate::prelude::*;
5
6const TITLE_BAR_HEIGHT: Pixels = px(36.);
8const TRAFFIC_LIGHT_SIZE: Pixels = px(12.);
10
11struct 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#[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 .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}