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