platform_title_bar.rs

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