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