platform_title_bar.rs

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