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