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