1use crate::title_bar_settings::TitleBarSettings;
2use gpui::{Action, Hsla, MouseButton, prelude::*, svg};
3use settings::{Settings, WindowControlsPosition};
4use ui::prelude::*;
5
6#[derive(IntoElement)]
7pub struct LinuxWindowControls {
8 close_window_action: Box<dyn Action>,
9}
10
11impl LinuxWindowControls {
12 pub fn new(close_window_action: Box<dyn Action>) -> Self {
13 Self {
14 close_window_action,
15 }
16 }
17}
18
19impl LinuxWindowControls {
20 /// Builds the window controls based on the position setting.
21 fn build_controls(
22 position: WindowControlsPosition,
23 window: &Window,
24 close_action: Box<dyn Action>,
25 cx: &mut App,
26 ) -> Vec<WindowControl> {
27 let maximize_type = if window.is_maximized() {
28 WindowControlType::Restore
29 } else {
30 WindowControlType::Maximize
31 };
32
33 match position {
34 WindowControlsPosition::Left => {
35 // Left side: Close, Minimize, Maximize (left to right)
36 vec![
37 WindowControl::new_close("close", WindowControlType::Close, close_action, cx),
38 WindowControl::new("minimize", WindowControlType::Minimize, cx),
39 WindowControl::new("maximize-or-restore", maximize_type, cx),
40 ]
41 }
42 WindowControlsPosition::Right => {
43 // Right side: Minimize, Maximize, Close (left to right)
44 vec![
45 WindowControl::new("minimize", WindowControlType::Minimize, cx),
46 WindowControl::new("maximize-or-restore", maximize_type, cx),
47 WindowControl::new_close("close", WindowControlType::Close, close_action, cx),
48 ]
49 }
50 }
51 }
52}
53
54impl RenderOnce for LinuxWindowControls {
55 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
56 let title_bar_settings = TitleBarSettings::get(None, cx);
57 let controls = Self::build_controls(
58 title_bar_settings.window_controls_position,
59 window,
60 self.close_window_action,
61 cx,
62 );
63
64 let mut element = h_flex()
65 .id("generic-window-controls")
66 .px_3()
67 .gap_3()
68 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation());
69
70 for control in controls {
71 element = element.child(control);
72 }
73
74 element
75 }
76}
77
78#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
79pub enum WindowControlType {
80 Minimize,
81 Restore,
82 Maximize,
83 Close,
84}
85
86impl WindowControlType {
87 /// Returns the icon name for the window control type.
88 ///
89 /// Will take a [PlatformStyle] in the future to return a different
90 /// icon name based on the platform.
91 pub fn icon(&self) -> IconName {
92 match self {
93 WindowControlType::Minimize => IconName::GenericMinimize,
94 WindowControlType::Restore => IconName::GenericRestore,
95 WindowControlType::Maximize => IconName::GenericMaximize,
96 WindowControlType::Close => IconName::GenericClose,
97 }
98 }
99}
100
101#[allow(unused)]
102pub struct WindowControlStyle {
103 background: Hsla,
104 background_hover: Hsla,
105 icon: Hsla,
106 icon_hover: Hsla,
107}
108
109impl WindowControlStyle {
110 pub fn default(cx: &mut App) -> Self {
111 let colors = cx.theme().colors();
112
113 Self {
114 background: colors.title_bar_background,
115 background_hover: colors.ghost_element_hover,
116 icon: colors.icon,
117 icon_hover: colors.icon_muted,
118 }
119 }
120
121 #[allow(unused)]
122 /// Sets the background color of the control.
123 pub fn background(mut self, color: impl Into<Hsla>) -> Self {
124 self.background = color.into();
125 self
126 }
127
128 #[allow(unused)]
129 /// Sets the background color of the control when hovered.
130 pub fn background_hover(mut self, color: impl Into<Hsla>) -> Self {
131 self.background_hover = color.into();
132 self
133 }
134
135 #[allow(unused)]
136 /// Sets the color of the icon.
137 pub fn icon(mut self, color: impl Into<Hsla>) -> Self {
138 self.icon = color.into();
139 self
140 }
141
142 #[allow(unused)]
143 /// Sets the color of the icon when hovered.
144 pub fn icon_hover(mut self, color: impl Into<Hsla>) -> Self {
145 self.icon_hover = color.into();
146 self
147 }
148}
149
150#[derive(IntoElement)]
151pub struct WindowControl {
152 id: ElementId,
153 icon: WindowControlType,
154 style: WindowControlStyle,
155 close_action: Option<Box<dyn Action>>,
156}
157
158impl WindowControl {
159 pub fn new(id: impl Into<ElementId>, icon: WindowControlType, cx: &mut App) -> Self {
160 let style = WindowControlStyle::default(cx);
161
162 Self {
163 id: id.into(),
164 icon,
165 style,
166 close_action: None,
167 }
168 }
169
170 pub fn new_close(
171 id: impl Into<ElementId>,
172 icon: WindowControlType,
173 close_action: Box<dyn Action>,
174 cx: &mut App,
175 ) -> Self {
176 let style = WindowControlStyle::default(cx);
177
178 Self {
179 id: id.into(),
180 icon,
181 style,
182 close_action: Some(close_action.boxed_clone()),
183 }
184 }
185
186 #[allow(unused)]
187 pub fn custom_style(
188 id: impl Into<ElementId>,
189 icon: WindowControlType,
190 style: WindowControlStyle,
191 ) -> Self {
192 Self {
193 id: id.into(),
194 icon,
195 style,
196 close_action: None,
197 }
198 }
199}
200
201impl RenderOnce for WindowControl {
202 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
203 let icon = svg()
204 .size_4()
205 .flex_none()
206 .path(self.icon.icon().path())
207 .text_color(self.style.icon)
208 .group_hover("", |this| this.text_color(self.style.icon_hover));
209
210 h_flex()
211 .id(self.id)
212 .group("")
213 .cursor_pointer()
214 .justify_center()
215 .content_center()
216 .rounded_2xl()
217 .w_5()
218 .h_5()
219 .bg(self.style.background)
220 .hover(|this| this.bg(self.style.background_hover))
221 .active(|this| this.bg(self.style.background_hover))
222 .child(icon)
223 .on_mouse_move(|_, _, cx| cx.stop_propagation())
224 .on_click(move |_, window, cx| {
225 cx.stop_propagation();
226 match self.icon {
227 WindowControlType::Minimize => window.minimize_window(),
228 WindowControlType::Restore => window.zoom_window(),
229 WindowControlType::Maximize => window.zoom_window(),
230 WindowControlType::Close => window.dispatch_action(
231 self.close_action
232 .as_ref()
233 .expect("Use WindowControl::new_close() for close control.")
234 .boxed_clone(),
235 cx,
236 ),
237 }
238 })
239 }
240}