1use gpui::{
2 AnyElement, Context, Decorations, Entity, Hsla, InteractiveElement, IntoElement, MouseButton,
3 ParentElement, Pixels, StatefulInteractiveElement, Styled, Window, WindowControlArea, div, px,
4};
5use settings::{Settings, WindowControlsPosition};
6use smallvec::SmallVec;
7use std::mem;
8use ui::prelude::*;
9
10use crate::{
11 platforms::{platform_linux, platform_mac, platform_windows},
12 system_window_tabs::SystemWindowTabs,
13 title_bar_settings::TitleBarSettings,
14};
15
16pub struct PlatformTitleBar {
17 id: ElementId,
18 platform_style: PlatformStyle,
19 children: SmallVec<[AnyElement; 2]>,
20 should_move: bool,
21 system_window_tabs: Entity<SystemWindowTabs>,
22}
23
24impl PlatformTitleBar {
25 pub fn new(id: impl Into<ElementId>, cx: &mut Context<Self>) -> Self {
26 let platform_style = PlatformStyle::platform();
27 let system_window_tabs = cx.new(|_cx| SystemWindowTabs::new());
28
29 Self {
30 id: id.into(),
31 platform_style,
32 children: SmallVec::new(),
33 should_move: false,
34 system_window_tabs,
35 }
36 }
37
38 #[cfg(not(target_os = "windows"))]
39 pub fn height(window: &mut Window) -> Pixels {
40 (1.75 * window.rem_size()).max(px(34.))
41 }
42
43 #[cfg(target_os = "windows")]
44 pub fn height(_window: &mut Window) -> Pixels {
45 // todo(windows) instead of hard coded size report the actual size to the Windows platform API
46 px(32.)
47 }
48
49 pub fn title_bar_color(&self, window: &mut Window, cx: &mut Context<Self>) -> Hsla {
50 if cfg!(any(target_os = "linux", target_os = "freebsd")) {
51 if window.is_window_active() && !self.should_move {
52 cx.theme().colors().title_bar_background
53 } else {
54 cx.theme().colors().title_bar_inactive_background
55 }
56 } else {
57 cx.theme().colors().title_bar_background
58 }
59 }
60
61 pub fn set_children<T>(&mut self, children: T)
62 where
63 T: IntoIterator<Item = AnyElement>,
64 {
65 self.children = children.into_iter().collect();
66 }
67}
68
69impl Render for PlatformTitleBar {
70 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
71 let supported_controls = window.window_controls();
72 let decorations = window.window_decorations();
73 let height = Self::height(window);
74 let titlebar_color = self.title_bar_color(window, cx);
75 let close_action = Box::new(workspace::CloseWindow);
76 let children = mem::take(&mut self.children);
77
78 let title_bar = h_flex()
79 .window_control_area(WindowControlArea::Drag)
80 .w_full()
81 .h(height)
82 .map(|this| {
83 if window.is_fullscreen() {
84 this.pl_2()
85 } else if self.platform_style == PlatformStyle::Mac {
86 this.pl(px(platform_mac::TRAFFIC_LIGHT_PADDING))
87 } else {
88 this.pl_2()
89 }
90 })
91 .map(|el| match decorations {
92 Decorations::Server => el,
93 Decorations::Client { tiling, .. } => el
94 .when(!(tiling.top || tiling.right), |el| {
95 el.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
96 })
97 .when(!(tiling.top || tiling.left), |el| {
98 el.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
99 })
100 // this border is to avoid a transparent gap in the rounded corners
101 .mt(px(-1.))
102 .mb(px(-1.))
103 .border(px(1.))
104 .border_color(titlebar_color),
105 })
106 .bg(titlebar_color)
107 .content_stretch()
108 .child(
109 div()
110 .id(self.id.clone())
111 .flex()
112 .flex_row()
113 .items_center()
114 .justify_between()
115 .overflow_x_hidden()
116 .w_full()
117 // Note: On Windows the title bar behavior is handled by the platform implementation.
118 .when(self.platform_style == PlatformStyle::Mac, |this| {
119 this.on_click(|event, window, _| {
120 if event.click_count() == 2 {
121 window.titlebar_double_click();
122 }
123 })
124 })
125 .when(self.platform_style == PlatformStyle::Linux, |this| {
126 this.on_click(|event, window, _| {
127 if event.click_count() == 2 {
128 window.zoom_window();
129 }
130 })
131 })
132 .children(children),
133 )
134 .when(!window.is_fullscreen(), |title_bar| {
135 match self.platform_style {
136 PlatformStyle::Mac => title_bar,
137 PlatformStyle::Linux => {
138 if matches!(decorations, Decorations::Client { .. }) {
139 let title_bar_settings = TitleBarSettings::get(None, cx);
140 match title_bar_settings.window_controls_position {
141 WindowControlsPosition::Left => h_flex()
142 .w_full()
143 .bg(titlebar_color)
144 .child(platform_linux::LinuxWindowControls::new(close_action))
145 .child(title_bar)
146 .when(supported_controls.window_menu, |titlebar| {
147 titlebar.on_mouse_down(
148 MouseButton::Right,
149 move |ev, window, _| {
150 window.show_window_menu(ev.position)
151 },
152 )
153 })
154 .on_mouse_move(cx.listener(move |this, _ev, window, _| {
155 if this.should_move {
156 this.should_move = false;
157 window.start_window_move();
158 }
159 }))
160 .on_mouse_down_out(cx.listener(
161 move |this, _ev, _window, _cx| {
162 this.should_move = false;
163 },
164 ))
165 .on_mouse_up(
166 MouseButton::Left,
167 cx.listener(move |this, _ev, _window, _cx| {
168 this.should_move = false;
169 }),
170 )
171 .on_mouse_down(
172 MouseButton::Left,
173 cx.listener(move |this, _ev, _window, _cx| {
174 this.should_move = true;
175 }),
176 ),
177 WindowControlsPosition::Right => title_bar
178 .child(platform_linux::LinuxWindowControls::new(close_action))
179 .when(supported_controls.window_menu, |titlebar| {
180 titlebar.on_mouse_down(
181 MouseButton::Right,
182 move |ev, window, _| {
183 window.show_window_menu(ev.position)
184 },
185 )
186 })
187 .on_mouse_move(cx.listener(move |this, _ev, window, _| {
188 if this.should_move {
189 this.should_move = false;
190 window.start_window_move();
191 }
192 }))
193 .on_mouse_down_out(cx.listener(
194 move |this, _ev, _window, _cx| {
195 this.should_move = false;
196 },
197 ))
198 .on_mouse_up(
199 MouseButton::Left,
200 cx.listener(move |this, _ev, _window, _cx| {
201 this.should_move = false;
202 }),
203 )
204 .on_mouse_down(
205 MouseButton::Left,
206 cx.listener(move |this, _ev, _window, _cx| {
207 this.should_move = true;
208 }),
209 ),
210 }
211 } else {
212 title_bar
213 }
214 }
215 PlatformStyle::Windows => {
216 title_bar.child(platform_windows::WindowsWindowControls::new(height))
217 }
218 }
219 });
220
221 v_flex()
222 .w_full()
223 .child(title_bar)
224 .child(self.system_window_tabs.clone().into_any_element())
225 }
226}
227
228impl ParentElement for PlatformTitleBar {
229 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
230 self.children.extend(elements)
231 }
232}