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    background_color: Option<Hsla>,
 34    system_window_tabs: Entity<SystemWindowTabs>,
 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            background_color: None,
 48            system_window_tabs,
 49        }
 50    }
 51
 52    pub fn title_bar_color(&self, window: &mut Window, cx: &mut Context<Self>) -> Hsla {
 53        if let Some(background_color) = self.background_color {
 54            return background_color;
 55        }
 56
 57        if cfg!(any(target_os = "linux", target_os = "freebsd")) {
 58            if window.is_window_active() && !self.should_move {
 59                cx.theme().colors().title_bar_background
 60            } else {
 61                cx.theme().colors().title_bar_inactive_background
 62            }
 63        } else {
 64            cx.theme().colors().title_bar_background
 65        }
 66    }
 67
 68    pub fn set_children<T>(&mut self, children: T)
 69    where
 70        T: IntoIterator<Item = AnyElement>,
 71    {
 72        self.children = children.into_iter().collect();
 73    }
 74
 75    pub fn set_background_color(&mut self, background_color: Option<Hsla>) {
 76        self.background_color = background_color;
 77    }
 78
 79    pub fn init(cx: &mut App) {
 80        SystemWindowTabs::init(cx);
 81    }
 82
 83    pub fn is_multi_workspace_enabled(cx: &App) -> bool {
 84        cx.has_flag::<AgentV2FeatureFlag>() && !DisableAiSettings::get_global(cx).disable_ai
 85    }
 86}
 87
 88impl Render for PlatformTitleBar {
 89    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 90        let supported_controls = window.window_controls();
 91        let decorations = window.window_decorations();
 92        let height = platform_title_bar_height(window);
 93        let titlebar_color = self.title_bar_color(window, cx);
 94        let close_action = Box::new(workspace::CloseWindow);
 95        let children = mem::take(&mut self.children);
 96
 97        let title_bar = h_flex()
 98            .window_control_area(WindowControlArea::Drag)
 99            .w_full()
100            .h(height)
101            .map(|this| {
102                this.on_mouse_down_out(cx.listener(move |this, _ev, _window, _cx| {
103                    this.should_move = false;
104                }))
105                .on_mouse_up(
106                    gpui::MouseButton::Left,
107                    cx.listener(move |this, _ev, _window, _cx| {
108                        this.should_move = false;
109                    }),
110                )
111                .on_mouse_down(
112                    gpui::MouseButton::Left,
113                    cx.listener(move |this, _ev, _window, _cx| {
114                        this.should_move = true;
115                    }),
116                )
117                .on_mouse_move(cx.listener(move |this, _ev, window, _| {
118                    if this.should_move {
119                        this.should_move = false;
120                        window.start_window_move();
121                    }
122                }))
123            })
124            .map(|this| {
125                // Note: On Windows the title bar behavior is handled by the platform implementation.
126                this.id(self.id.clone())
127                    .when(self.platform_style == PlatformStyle::Mac, |this| {
128                        this.on_click(|event, window, _| {
129                            if event.click_count() == 2 {
130                                window.titlebar_double_click();
131                            }
132                        })
133                    })
134                    .when(self.platform_style == PlatformStyle::Linux, |this| {
135                        this.on_click(|event, window, _| {
136                            if event.click_count() == 2 {
137                                window.zoom_window();
138                            }
139                        })
140                    })
141            })
142            .map(|this| {
143                if window.is_fullscreen() {
144                    this.pl_2()
145                } else if self.platform_style == PlatformStyle::Mac {
146                    this.pl(px(TRAFFIC_LIGHT_PADDING))
147                } else {
148                    this.pl_2()
149                }
150            })
151            .map(|el| match decorations {
152                Decorations::Server => el,
153                Decorations::Client { tiling, .. } => el
154                    .when(!(tiling.top || tiling.right), |el| {
155                        el.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
156                    })
157                    .when(!(tiling.top || tiling.left), |el| {
158                        el.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
159                    })
160                    // this border is to avoid a transparent gap in the rounded corners
161                    .mt(px(-1.))
162                    .mb(px(-1.))
163                    .border(px(1.))
164                    .border_color(titlebar_color),
165            })
166            .bg(titlebar_color)
167            .content_stretch()
168            .child(
169                div()
170                    .id(self.id.clone())
171                    .flex()
172                    .flex_row()
173                    .items_center()
174                    .justify_between()
175                    .overflow_x_hidden()
176                    .w_full()
177                    .children(children),
178            )
179            .when(!window.is_fullscreen(), |title_bar| {
180                match self.platform_style {
181                    PlatformStyle::Mac => title_bar,
182                    PlatformStyle::Linux => {
183                        if matches!(decorations, Decorations::Client { .. }) {
184                            title_bar
185                                .child(platform_linux::LinuxWindowControls::new(close_action))
186                                .when(supported_controls.window_menu, |titlebar| {
187                                    titlebar
188                                        .on_mouse_down(MouseButton::Right, move |ev, window, _| {
189                                            window.show_window_menu(ev.position)
190                                        })
191                                })
192                        } else {
193                            title_bar
194                        }
195                    }
196                    PlatformStyle::Windows => {
197                        title_bar.child(platform_windows::WindowsWindowControls::new(height))
198                    }
199                }
200            });
201
202        v_flex()
203            .w_full()
204            .child(title_bar)
205            .child(self.system_window_tabs.clone().into_any_element())
206    }
207}
208
209impl ParentElement for PlatformTitleBar {
210    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
211        self.children.extend(elements)
212    }
213}