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