logiguard fork: GPUI xdg-activation keyboard-focus serial fix
Some checks failed
Update All Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Triage Project Sync (#84) / Sync triage project (push) Has been cancelled
release_nightly / notify_on_failure (push) Has been cancelled
release_nightly / check_style (push) Has been cancelled
release_nightly / run_tests_windows (push) Has been cancelled
release_nightly / clippy_windows (push) Has been cancelled
release_nightly / bundle_linux_aarch64 (push) Has been cancelled
release_nightly / bundle_linux_x86_64 (push) Has been cancelled
release_nightly / bundle_mac_aarch64 (push) Has been cancelled
release_nightly / bundle_mac_x86_64 (push) Has been cancelled
release_nightly / bundle_windows_aarch64 (push) Has been cancelled
release_nightly / bundle_windows_x86_64 (push) Has been cancelled
release_nightly / build_nix_linux_x86_64 (push) Has been cancelled
release_nightly / build_nix_mac_aarch64 (push) Has been cancelled
release_nightly / update_nightly_tag (push) Has been cancelled
Hotfix Review Monitor / check-hotfix-reviews (push) Has been cancelled
Stale PR Review Reminder / check-stale-prs (push) Has been cancelled
Update Weekly Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Bump collab-staging Tag / update-collab-staging-tag (push) Has been cancelled
compliance_check / scheduled_compliance_check (push) Has been cancelled
Some checks failed
Update All Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Triage Project Sync (#84) / Sync triage project (push) Has been cancelled
release_nightly / notify_on_failure (push) Has been cancelled
release_nightly / check_style (push) Has been cancelled
release_nightly / run_tests_windows (push) Has been cancelled
release_nightly / clippy_windows (push) Has been cancelled
release_nightly / bundle_linux_aarch64 (push) Has been cancelled
release_nightly / bundle_linux_x86_64 (push) Has been cancelled
release_nightly / bundle_mac_aarch64 (push) Has been cancelled
release_nightly / bundle_mac_x86_64 (push) Has been cancelled
release_nightly / bundle_windows_aarch64 (push) Has been cancelled
release_nightly / bundle_windows_x86_64 (push) Has been cancelled
release_nightly / build_nix_linux_x86_64 (push) Has been cancelled
release_nightly / build_nix_mac_aarch64 (push) Has been cancelled
release_nightly / update_nightly_tag (push) Has been cancelled
Hotfix Review Monitor / check-hotfix-reviews (push) Has been cancelled
Stale PR Review Reminder / check-stale-prs (push) Has been cancelled
Update Weekly Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Bump collab-staging Tag / update-collab-staging-tag (push) Has been cancelled
compliance_check / scheduled_compliance_check (push) Has been cancelled
Single-commit orphan branch: full zed-industries/zed @ 8c74db0 source tree with a 3-file patch applied (no upstream history). Patch (crates/gpui_linux/src/linux/wayland/): - serial.rs: add SerialKind::KeyboardEnter - client.rs: store wl_keyboard.enter serial; add latest_serial_of() - window.rs: activate() uses keyboard-enter serial (Mutter focus gate) Mutter honors window activation only when the token carries the keyboard- focus serial from wl_keyboard.enter; GPUI used a stale mouse-press serial. See docs/tray-window-focus-wayland.md in logiguard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
333
crates/platform_title_bar/src/platform_title_bar.rs
Normal file
333
crates/platform_title_bar/src/platform_title_bar.rs
Normal file
@@ -0,0 +1,333 @@
|
||||
pub mod platforms;
|
||||
mod system_window_tabs;
|
||||
|
||||
use gpui::{
|
||||
Action, AnyElement, App, Context, Decorations, Entity, Hsla, InteractiveElement, IntoElement,
|
||||
MouseButton, ParentElement, StatefulInteractiveElement, Styled, WeakEntity, Window,
|
||||
WindowButtonLayout, WindowControlArea, div, px,
|
||||
};
|
||||
use project::DisableAiSettings;
|
||||
use settings::Settings;
|
||||
use smallvec::SmallVec;
|
||||
use std::mem;
|
||||
use ui::{
|
||||
prelude::*,
|
||||
utils::{TRAFFIC_LIGHT_PADDING, platform_title_bar_height},
|
||||
};
|
||||
use workspace::{MultiWorkspace, SidebarRenderState, SidebarSide};
|
||||
|
||||
use crate::{
|
||||
platforms::{platform_linux, platform_windows},
|
||||
system_window_tabs::SystemWindowTabs,
|
||||
};
|
||||
|
||||
pub use system_window_tabs::{
|
||||
DraggedWindowTab, MergeAllWindows, MoveTabToNewWindow, ShowNextWindowTab, ShowPreviousWindowTab,
|
||||
};
|
||||
|
||||
pub struct PlatformTitleBar {
|
||||
id: ElementId,
|
||||
platform_style: PlatformStyle,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
should_move: bool,
|
||||
system_window_tabs: Entity<SystemWindowTabs>,
|
||||
button_layout: Option<WindowButtonLayout>,
|
||||
multi_workspace: Option<WeakEntity<MultiWorkspace>>,
|
||||
}
|
||||
|
||||
impl PlatformTitleBar {
|
||||
pub fn new(id: impl Into<ElementId>, cx: &mut Context<Self>) -> Self {
|
||||
let platform_style = PlatformStyle::platform();
|
||||
let system_window_tabs = cx.new(|_cx| SystemWindowTabs::new());
|
||||
|
||||
Self {
|
||||
id: id.into(),
|
||||
platform_style,
|
||||
children: SmallVec::new(),
|
||||
should_move: false,
|
||||
system_window_tabs,
|
||||
button_layout: None,
|
||||
multi_workspace: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_multi_workspace(mut self, multi_workspace: WeakEntity<MultiWorkspace>) -> Self {
|
||||
self.multi_workspace = Some(multi_workspace);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_multi_workspace(&mut self, multi_workspace: WeakEntity<MultiWorkspace>) {
|
||||
self.multi_workspace = Some(multi_workspace);
|
||||
}
|
||||
|
||||
pub fn title_bar_color(&self, window: &mut Window, cx: &mut Context<Self>) -> Hsla {
|
||||
if cfg!(any(target_os = "linux", target_os = "freebsd")) {
|
||||
if window.is_window_active() && !self.should_move {
|
||||
cx.theme().colors().title_bar_background
|
||||
} else {
|
||||
cx.theme().colors().title_bar_inactive_background
|
||||
}
|
||||
} else {
|
||||
cx.theme().colors().title_bar_background
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_children<T>(&mut self, children: T)
|
||||
where
|
||||
T: IntoIterator<Item = AnyElement>,
|
||||
{
|
||||
self.children = children.into_iter().collect();
|
||||
}
|
||||
|
||||
pub fn set_button_layout(&mut self, button_layout: Option<WindowButtonLayout>) {
|
||||
self.button_layout = button_layout;
|
||||
}
|
||||
|
||||
fn effective_button_layout(
|
||||
&self,
|
||||
decorations: &Decorations,
|
||||
cx: &App,
|
||||
) -> Option<WindowButtonLayout> {
|
||||
if self.platform_style == PlatformStyle::Linux
|
||||
&& matches!(decorations, Decorations::Client { .. })
|
||||
{
|
||||
self.button_layout.or_else(|| cx.button_layout())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
SystemWindowTabs::init(cx);
|
||||
}
|
||||
|
||||
fn sidebar_render_state(&self, cx: &App) -> SidebarRenderState {
|
||||
self.multi_workspace
|
||||
.as_ref()
|
||||
.and_then(|mw| mw.upgrade())
|
||||
.map(|mw| mw.read(cx).sidebar_render_state(cx))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn is_multi_workspace_enabled(cx: &App) -> bool {
|
||||
!DisableAiSettings::get_global(cx).disable_ai
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders the platform-appropriate left-side window controls (e.g. Ubuntu/GNOME close button).
|
||||
///
|
||||
/// Only relevant on Linux with client-side decorations when the window manager
|
||||
/// places controls on the left.
|
||||
pub fn render_left_window_controls(
|
||||
button_layout: Option<WindowButtonLayout>,
|
||||
close_action: Box<dyn Action>,
|
||||
window: &Window,
|
||||
) -> Option<AnyElement> {
|
||||
if PlatformStyle::platform() != PlatformStyle::Linux {
|
||||
return None;
|
||||
}
|
||||
if !matches!(window.window_decorations(), Decorations::Client { .. }) {
|
||||
return None;
|
||||
}
|
||||
let button_layout = button_layout?;
|
||||
if button_layout.left[0].is_none() {
|
||||
return None;
|
||||
}
|
||||
Some(
|
||||
platform_linux::LinuxWindowControls::new(
|
||||
"left-window-controls",
|
||||
button_layout.left,
|
||||
close_action,
|
||||
)
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Renders the platform-appropriate right-side window controls (close, minimize, maximize).
|
||||
///
|
||||
/// Returns `None` on Mac or when the platform doesn't need custom controls
|
||||
/// (e.g. Linux with server-side decorations).
|
||||
pub fn render_right_window_controls(
|
||||
button_layout: Option<WindowButtonLayout>,
|
||||
close_action: Box<dyn Action>,
|
||||
window: &Window,
|
||||
) -> Option<AnyElement> {
|
||||
let decorations = window.window_decorations();
|
||||
let height = platform_title_bar_height(window);
|
||||
|
||||
match PlatformStyle::platform() {
|
||||
PlatformStyle::Linux => {
|
||||
if !matches!(decorations, Decorations::Client { .. }) {
|
||||
return None;
|
||||
}
|
||||
let button_layout = button_layout?;
|
||||
if button_layout.right[0].is_none() {
|
||||
return None;
|
||||
}
|
||||
Some(
|
||||
platform_linux::LinuxWindowControls::new(
|
||||
"right-window-controls",
|
||||
button_layout.right,
|
||||
close_action,
|
||||
)
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
PlatformStyle::Windows => {
|
||||
Some(platform_windows::WindowsWindowControls::new(height).into_any_element())
|
||||
}
|
||||
PlatformStyle::Mac => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for PlatformTitleBar {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let supported_controls = window.window_controls();
|
||||
let decorations = window.window_decorations();
|
||||
let height = platform_title_bar_height(window);
|
||||
let titlebar_color = self.title_bar_color(window, cx);
|
||||
let close_action = Box::new(workspace::CloseWindow);
|
||||
let children = mem::take(&mut self.children);
|
||||
|
||||
let button_layout = self.effective_button_layout(&decorations, cx);
|
||||
let sidebar = self.sidebar_render_state(cx);
|
||||
|
||||
let title_bar = h_flex()
|
||||
.window_control_area(WindowControlArea::Drag)
|
||||
.w_full()
|
||||
.h(height)
|
||||
.map(|this| {
|
||||
this.on_mouse_down_out(cx.listener(move |this, _ev, _window, _cx| {
|
||||
this.should_move = false;
|
||||
}))
|
||||
.on_mouse_up(
|
||||
gpui::MouseButton::Left,
|
||||
cx.listener(move |this, _ev, _window, _cx| {
|
||||
this.should_move = false;
|
||||
}),
|
||||
)
|
||||
.on_mouse_down(
|
||||
gpui::MouseButton::Left,
|
||||
cx.listener(move |this, _ev, _window, _cx| {
|
||||
this.should_move = true;
|
||||
}),
|
||||
)
|
||||
.on_mouse_move(cx.listener(move |this, _ev, window, _| {
|
||||
if this.should_move {
|
||||
this.should_move = false;
|
||||
window.start_window_move();
|
||||
}
|
||||
}))
|
||||
})
|
||||
.map(|this| {
|
||||
// Note: On Windows the title bar behavior is handled by the platform implementation.
|
||||
this.id(self.id.clone())
|
||||
.when(self.platform_style == PlatformStyle::Mac, |this| {
|
||||
this.on_click(|event, window, _| {
|
||||
if event.click_count() == 2 {
|
||||
window.titlebar_double_click();
|
||||
}
|
||||
})
|
||||
})
|
||||
.when(self.platform_style == PlatformStyle::Linux, |this| {
|
||||
this.on_click(|event, window, _| {
|
||||
if event.click_count() == 2 {
|
||||
window.zoom_window();
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
.map(|this| {
|
||||
let show_left_controls = !(sidebar.open && sidebar.side == SidebarSide::Left);
|
||||
|
||||
if window.is_fullscreen() {
|
||||
this.pl_2()
|
||||
} else if self.platform_style == PlatformStyle::Mac && show_left_controls {
|
||||
this.pl(px(TRAFFIC_LIGHT_PADDING))
|
||||
} else if let Some(controls) = show_left_controls
|
||||
.then(|| {
|
||||
render_left_window_controls(
|
||||
button_layout,
|
||||
close_action.as_ref().boxed_clone(),
|
||||
window,
|
||||
)
|
||||
})
|
||||
.flatten()
|
||||
{
|
||||
this.child(controls)
|
||||
} else {
|
||||
this.pl_2()
|
||||
}
|
||||
})
|
||||
.map(|el| match decorations {
|
||||
Decorations::Server => el,
|
||||
Decorations::Client { tiling, .. } => el
|
||||
.when(
|
||||
!(tiling.top || tiling.right)
|
||||
&& !(sidebar.open && sidebar.side == SidebarSide::Right),
|
||||
|el| el.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING),
|
||||
)
|
||||
.when(
|
||||
!(tiling.top || tiling.left)
|
||||
&& !(sidebar.open && sidebar.side == SidebarSide::Left),
|
||||
|el| el.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING),
|
||||
)
|
||||
// this border is to avoid a transparent gap in the rounded corners
|
||||
.mt(px(-1.))
|
||||
.mb(px(-1.))
|
||||
.border(px(1.))
|
||||
.border_color(titlebar_color),
|
||||
})
|
||||
.bg(titlebar_color)
|
||||
.content_stretch()
|
||||
.child(
|
||||
div()
|
||||
.id(self.id.clone())
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.overflow_x_hidden()
|
||||
.w_full()
|
||||
.children(children),
|
||||
)
|
||||
.when(!window.is_fullscreen(), |title_bar| {
|
||||
let show_right_controls = !(sidebar.open && sidebar.side == SidebarSide::Right);
|
||||
|
||||
let title_bar = title_bar.children(
|
||||
show_right_controls
|
||||
.then(|| {
|
||||
render_right_window_controls(
|
||||
button_layout,
|
||||
close_action.as_ref().boxed_clone(),
|
||||
window,
|
||||
)
|
||||
})
|
||||
.flatten(),
|
||||
);
|
||||
|
||||
if self.platform_style == PlatformStyle::Linux
|
||||
&& matches!(decorations, Decorations::Client { .. })
|
||||
{
|
||||
title_bar.when(supported_controls.window_menu, |titlebar| {
|
||||
titlebar.on_mouse_down(MouseButton::Right, move |ev, window, _| {
|
||||
window.show_window_menu(ev.position)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
title_bar
|
||||
}
|
||||
});
|
||||
|
||||
v_flex()
|
||||
.w_full()
|
||||
.child(title_bar)
|
||||
.child(self.system_window_tabs.clone().into_any_element())
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for PlatformTitleBar {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.children.extend(elements)
|
||||
}
|
||||
}
|
||||
2
crates/platform_title_bar/src/platforms.rs
Normal file
2
crates/platform_title_bar/src/platforms.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod platform_linux;
|
||||
pub mod platform_windows;
|
||||
245
crates/platform_title_bar/src/platforms/platform_linux.rs
Normal file
245
crates/platform_title_bar/src/platforms/platform_linux.rs
Normal file
@@ -0,0 +1,245 @@
|
||||
use gpui::{
|
||||
Action, AnyElement, Hsla, MAX_BUTTONS_PER_SIDE, MouseButton, WindowButton, prelude::*, svg,
|
||||
};
|
||||
use ui::prelude::*;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct LinuxWindowControls {
|
||||
id: &'static str,
|
||||
buttons: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
|
||||
close_action: Box<dyn Action>,
|
||||
}
|
||||
|
||||
impl LinuxWindowControls {
|
||||
pub fn new(
|
||||
id: &'static str,
|
||||
buttons: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
|
||||
close_action: Box<dyn Action>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
buttons,
|
||||
close_action,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for LinuxWindowControls {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let is_maximized = window.is_maximized();
|
||||
let supported_controls = window.window_controls();
|
||||
let button_elements: Vec<AnyElement> = self
|
||||
.buttons
|
||||
.iter()
|
||||
.filter_map(|b| *b)
|
||||
.filter(|button| match button {
|
||||
WindowButton::Minimize => supported_controls.minimize,
|
||||
WindowButton::Maximize => supported_controls.maximize,
|
||||
WindowButton::Close => true,
|
||||
})
|
||||
.map(|button| {
|
||||
create_window_button(button, button.id(), is_maximized, &*self.close_action, cx)
|
||||
})
|
||||
.collect();
|
||||
|
||||
h_flex()
|
||||
.id(self.id)
|
||||
.when(!button_elements.is_empty(), |el| {
|
||||
el.gap_3()
|
||||
.px_3()
|
||||
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
||||
.children(button_elements)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn create_window_button(
|
||||
button: WindowButton,
|
||||
id: &'static str,
|
||||
is_maximized: bool,
|
||||
close_action: &dyn Action,
|
||||
cx: &mut App,
|
||||
) -> AnyElement {
|
||||
match button {
|
||||
WindowButton::Minimize => {
|
||||
WindowControl::new(id, WindowControlType::Minimize, cx).into_any_element()
|
||||
}
|
||||
WindowButton::Maximize => WindowControl::new(
|
||||
id,
|
||||
if is_maximized {
|
||||
WindowControlType::Restore
|
||||
} else {
|
||||
WindowControlType::Maximize
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.into_any_element(),
|
||||
WindowButton::Close => {
|
||||
WindowControl::new_close(id, WindowControlType::Close, close_action.boxed_clone(), cx)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
pub enum WindowControlType {
|
||||
Minimize,
|
||||
Restore,
|
||||
Maximize,
|
||||
Close,
|
||||
}
|
||||
|
||||
impl WindowControlType {
|
||||
/// Returns the icon name for the window control type.
|
||||
///
|
||||
/// Will take a [PlatformStyle] in the future to return a different
|
||||
/// icon name based on the platform.
|
||||
pub fn icon(&self) -> IconName {
|
||||
match self {
|
||||
WindowControlType::Minimize => IconName::GenericMinimize,
|
||||
WindowControlType::Restore => IconName::GenericRestore,
|
||||
WindowControlType::Maximize => IconName::GenericMaximize,
|
||||
WindowControlType::Close => IconName::GenericClose,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub struct WindowControlStyle {
|
||||
background: Hsla,
|
||||
background_hover: Hsla,
|
||||
icon: Hsla,
|
||||
icon_hover: Hsla,
|
||||
}
|
||||
|
||||
impl WindowControlStyle {
|
||||
pub fn default(cx: &mut App) -> Self {
|
||||
let colors = cx.theme().colors();
|
||||
|
||||
Self {
|
||||
background: colors.ghost_element_background,
|
||||
background_hover: colors.ghost_element_hover,
|
||||
icon: colors.icon,
|
||||
icon_hover: colors.icon_muted,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
/// Sets the background color of the control.
|
||||
pub fn background(mut self, color: impl Into<Hsla>) -> Self {
|
||||
self.background = color.into();
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
/// Sets the background color of the control when hovered.
|
||||
pub fn background_hover(mut self, color: impl Into<Hsla>) -> Self {
|
||||
self.background_hover = color.into();
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
/// Sets the color of the icon.
|
||||
pub fn icon(mut self, color: impl Into<Hsla>) -> Self {
|
||||
self.icon = color.into();
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
/// Sets the color of the icon when hovered.
|
||||
pub fn icon_hover(mut self, color: impl Into<Hsla>) -> Self {
|
||||
self.icon_hover = color.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct WindowControl {
|
||||
id: ElementId,
|
||||
icon: WindowControlType,
|
||||
style: WindowControlStyle,
|
||||
close_action: Option<Box<dyn Action>>,
|
||||
}
|
||||
|
||||
impl WindowControl {
|
||||
pub fn new(id: impl Into<ElementId>, icon: WindowControlType, cx: &mut App) -> Self {
|
||||
let style = WindowControlStyle::default(cx);
|
||||
|
||||
Self {
|
||||
id: id.into(),
|
||||
icon,
|
||||
style,
|
||||
close_action: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_close(
|
||||
id: impl Into<ElementId>,
|
||||
icon: WindowControlType,
|
||||
close_action: Box<dyn Action>,
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
let style = WindowControlStyle::default(cx);
|
||||
|
||||
Self {
|
||||
id: id.into(),
|
||||
icon,
|
||||
style,
|
||||
close_action: Some(close_action.boxed_clone()),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn custom_style(
|
||||
id: impl Into<ElementId>,
|
||||
icon: WindowControlType,
|
||||
style: WindowControlStyle,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
icon,
|
||||
style,
|
||||
close_action: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for WindowControl {
|
||||
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||
let icon = svg()
|
||||
.size_4()
|
||||
.flex_none()
|
||||
.path(self.icon.icon().path())
|
||||
.text_color(self.style.icon)
|
||||
.group_hover("", |this| this.text_color(self.style.icon_hover));
|
||||
|
||||
h_flex()
|
||||
.id(self.id)
|
||||
.group("")
|
||||
.cursor_pointer()
|
||||
.justify_center()
|
||||
.content_center()
|
||||
.rounded_2xl()
|
||||
.w_5()
|
||||
.h_5()
|
||||
.hover(|this| this.bg(self.style.background_hover))
|
||||
.active(|this| this.bg(self.style.background_hover))
|
||||
.child(icon)
|
||||
.on_mouse_move(|_, _, cx| cx.stop_propagation())
|
||||
.on_click(move |_, window, cx| {
|
||||
cx.stop_propagation();
|
||||
match self.icon {
|
||||
WindowControlType::Minimize => window.minimize_window(),
|
||||
WindowControlType::Restore => window.zoom_window(),
|
||||
WindowControlType::Maximize => window.zoom_window(),
|
||||
WindowControlType::Close => window.dispatch_action(
|
||||
self.close_action
|
||||
.as_ref()
|
||||
.expect("Use WindowControl::new_close() for close control.")
|
||||
.boxed_clone(),
|
||||
cx,
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
137
crates/platform_title_bar/src/platforms/platform_windows.rs
Normal file
137
crates/platform_title_bar/src/platforms/platform_windows.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
use gpui::{Hsla, Rgba, WindowControlArea, prelude::*};
|
||||
|
||||
use ui::prelude::*;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct WindowsWindowControls {
|
||||
button_height: Pixels,
|
||||
}
|
||||
|
||||
impl WindowsWindowControls {
|
||||
pub fn new(button_height: Pixels) -> Self {
|
||||
Self { button_height }
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn get_font() -> &'static str {
|
||||
"Segoe Fluent Icons"
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn get_font() -> &'static str {
|
||||
use windows::Wdk::System::SystemServices::RtlGetVersion;
|
||||
|
||||
let mut version = unsafe { std::mem::zeroed() };
|
||||
let status = unsafe { RtlGetVersion(&mut version) };
|
||||
|
||||
if status.is_ok() && version.dwBuildNumber >= 22000 {
|
||||
"Segoe Fluent Icons"
|
||||
} else {
|
||||
"Segoe MDL2 Assets"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for WindowsWindowControls {
|
||||
fn render(self, window: &mut Window, _: &mut App) -> impl IntoElement {
|
||||
div()
|
||||
.id("windows-window-controls")
|
||||
.font_family(Self::get_font())
|
||||
.flex()
|
||||
.flex_row()
|
||||
.justify_center()
|
||||
.content_stretch()
|
||||
.max_h(self.button_height)
|
||||
.min_h(self.button_height)
|
||||
.child(WindowsCaptionButton::Minimize)
|
||||
.map(|this| {
|
||||
this.child(if window.is_maximized() {
|
||||
WindowsCaptionButton::Restore
|
||||
} else {
|
||||
WindowsCaptionButton::Maximize
|
||||
})
|
||||
})
|
||||
.child(WindowsCaptionButton::Close)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
enum WindowsCaptionButton {
|
||||
Minimize,
|
||||
Restore,
|
||||
Maximize,
|
||||
Close,
|
||||
}
|
||||
|
||||
impl WindowsCaptionButton {
|
||||
#[inline]
|
||||
fn id(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Minimize => "minimize",
|
||||
Self::Restore => "restore",
|
||||
Self::Maximize => "maximize",
|
||||
Self::Close => "close",
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn icon(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Minimize => "\u{e921}",
|
||||
Self::Restore => "\u{e923}",
|
||||
Self::Maximize => "\u{e922}",
|
||||
Self::Close => "\u{e8bb}",
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn control_area(&self) -> WindowControlArea {
|
||||
match self {
|
||||
Self::Close => WindowControlArea::Close,
|
||||
Self::Maximize | Self::Restore => WindowControlArea::Max,
|
||||
Self::Minimize => WindowControlArea::Min,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for WindowsCaptionButton {
|
||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let (hover_bg, hover_fg, active_bg, active_fg) = match self {
|
||||
Self::Close => {
|
||||
let color: Hsla = Rgba {
|
||||
r: 232.0 / 255.0,
|
||||
g: 17.0 / 255.0,
|
||||
b: 32.0 / 255.0,
|
||||
a: 1.0,
|
||||
}
|
||||
.into();
|
||||
|
||||
(
|
||||
color,
|
||||
gpui::white(),
|
||||
color.opacity(0.8),
|
||||
gpui::white().opacity(0.8),
|
||||
)
|
||||
}
|
||||
_ => (
|
||||
cx.theme().colors().ghost_element_hover,
|
||||
cx.theme().colors().text,
|
||||
cx.theme().colors().ghost_element_active,
|
||||
cx.theme().colors().text,
|
||||
),
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.id(self.id())
|
||||
.justify_center()
|
||||
.content_center()
|
||||
.occlude()
|
||||
.w(px(36.))
|
||||
.h_full()
|
||||
.text_size(px(10.0))
|
||||
.hover(|style| style.bg(hover_bg).text_color(hover_fg))
|
||||
.active(|style| style.bg(active_bg).text_color(active_fg))
|
||||
.window_control_area(self.control_area())
|
||||
.child(self.icon())
|
||||
}
|
||||
}
|
||||
529
crates/platform_title_bar/src/system_window_tabs.rs
Normal file
529
crates/platform_title_bar/src/system_window_tabs.rs
Normal file
@@ -0,0 +1,529 @@
|
||||
use settings::{Settings, SettingsStore};
|
||||
|
||||
use gpui::{
|
||||
AnyWindowHandle, Context, Hsla, InteractiveElement, MouseButton, ParentElement, ScrollHandle,
|
||||
Styled, SystemWindowTab, SystemWindowTabController, Window, WindowId, actions, canvas, div,
|
||||
};
|
||||
|
||||
use theme_settings::ThemeSettings;
|
||||
use ui::{
|
||||
Color, ContextMenu, DynamicSpacing, IconButton, IconButtonShape, IconName, IconSize, Label,
|
||||
LabelSize, Tab, h_flex, prelude::*, right_click_menu,
|
||||
};
|
||||
use workspace::{
|
||||
CloseWindow, ItemSettings, Workspace, WorkspaceSettings,
|
||||
item::{ClosePosition, ShowCloseButton},
|
||||
};
|
||||
|
||||
actions!(
|
||||
window,
|
||||
[
|
||||
ShowNextWindowTab,
|
||||
ShowPreviousWindowTab,
|
||||
MergeAllWindows,
|
||||
MoveTabToNewWindow
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DraggedWindowTab {
|
||||
pub id: WindowId,
|
||||
pub ix: usize,
|
||||
pub handle: AnyWindowHandle,
|
||||
pub title: String,
|
||||
pub width: Pixels,
|
||||
pub is_active: bool,
|
||||
pub active_background_color: Hsla,
|
||||
pub inactive_background_color: Hsla,
|
||||
}
|
||||
|
||||
pub struct SystemWindowTabs {
|
||||
tab_bar_scroll_handle: ScrollHandle,
|
||||
measured_tab_width: Pixels,
|
||||
last_dragged_tab: Option<DraggedWindowTab>,
|
||||
}
|
||||
|
||||
impl SystemWindowTabs {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
tab_bar_scroll_handle: ScrollHandle::new(),
|
||||
measured_tab_width: px(0.),
|
||||
last_dragged_tab: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
let mut was_use_system_window_tabs =
|
||||
WorkspaceSettings::get_global(cx).use_system_window_tabs;
|
||||
|
||||
cx.observe_global::<SettingsStore>(move |cx| {
|
||||
let use_system_window_tabs = WorkspaceSettings::get_global(cx).use_system_window_tabs;
|
||||
if use_system_window_tabs == was_use_system_window_tabs {
|
||||
return;
|
||||
}
|
||||
was_use_system_window_tabs = use_system_window_tabs;
|
||||
|
||||
let tabbing_identifier = if use_system_window_tabs {
|
||||
Some(String::from("zed"))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if use_system_window_tabs {
|
||||
SystemWindowTabController::init(cx);
|
||||
}
|
||||
|
||||
cx.windows().iter().for_each(|handle| {
|
||||
let _ = handle.update(cx, |_, window, cx| {
|
||||
window.set_tabbing_identifier(tabbing_identifier.clone());
|
||||
if use_system_window_tabs {
|
||||
let tabs = if let Some(tabs) = window.tabbed_windows() {
|
||||
tabs
|
||||
} else {
|
||||
vec![SystemWindowTab::new(
|
||||
SharedString::from(window.window_title()),
|
||||
window.window_handle(),
|
||||
)]
|
||||
};
|
||||
|
||||
SystemWindowTabController::add_tab(cx, handle.window_id(), tabs);
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.observe_new(|workspace: &mut Workspace, _, _| {
|
||||
workspace.register_action_renderer(|div, _, window, cx| {
|
||||
let window_id = window.window_handle().window_id();
|
||||
let controller = cx.global::<SystemWindowTabController>();
|
||||
|
||||
let tab_groups = controller.tab_groups();
|
||||
let tabs = controller.tabs(window_id);
|
||||
let Some(tabs) = tabs else {
|
||||
return div;
|
||||
};
|
||||
|
||||
div.when(tabs.len() > 1, |div| {
|
||||
div.on_action(move |_: &ShowNextWindowTab, window, cx| {
|
||||
SystemWindowTabController::select_next_tab(
|
||||
cx,
|
||||
window.window_handle().window_id(),
|
||||
);
|
||||
})
|
||||
.on_action(move |_: &ShowPreviousWindowTab, window, cx| {
|
||||
SystemWindowTabController::select_previous_tab(
|
||||
cx,
|
||||
window.window_handle().window_id(),
|
||||
);
|
||||
})
|
||||
.on_action(move |_: &MoveTabToNewWindow, window, cx| {
|
||||
SystemWindowTabController::move_tab_to_new_window(
|
||||
cx,
|
||||
window.window_handle().window_id(),
|
||||
);
|
||||
window.move_tab_to_new_window();
|
||||
})
|
||||
})
|
||||
.when(tab_groups.len() > 1, |div| {
|
||||
div.on_action(move |_: &MergeAllWindows, window, cx| {
|
||||
SystemWindowTabController::merge_all_windows(
|
||||
cx,
|
||||
window.window_handle().window_id(),
|
||||
);
|
||||
window.merge_all_windows();
|
||||
})
|
||||
})
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn render_tab(
|
||||
&self,
|
||||
ix: usize,
|
||||
item: SystemWindowTab,
|
||||
tabs: Vec<SystemWindowTab>,
|
||||
active_background_color: Hsla,
|
||||
inactive_background_color: Hsla,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement + use<> {
|
||||
let entity = cx.entity();
|
||||
let settings = ItemSettings::get_global(cx);
|
||||
let close_side = &settings.close_position;
|
||||
let show_close_button = &settings.show_close_button;
|
||||
|
||||
let rem_size = window.rem_size();
|
||||
let width = self.measured_tab_width.max(rem_size * 10);
|
||||
let is_active = window.window_handle().window_id() == item.id;
|
||||
let title = item.title.to_string();
|
||||
|
||||
let label = Label::new(&title)
|
||||
.size(LabelSize::Small)
|
||||
.truncate()
|
||||
.color(if is_active {
|
||||
Color::Default
|
||||
} else {
|
||||
Color::Muted
|
||||
});
|
||||
|
||||
let tab = h_flex()
|
||||
.id(ix)
|
||||
.group("tab")
|
||||
.w_full()
|
||||
.overflow_hidden()
|
||||
.h(Tab::content_height(cx))
|
||||
.relative()
|
||||
.px(DynamicSpacing::Base16.px(cx))
|
||||
.justify_center()
|
||||
.border_l_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.cursor_pointer()
|
||||
.on_drag(
|
||||
DraggedWindowTab {
|
||||
id: item.id,
|
||||
ix,
|
||||
handle: item.handle,
|
||||
title: item.title.to_string(),
|
||||
width,
|
||||
is_active,
|
||||
active_background_color,
|
||||
inactive_background_color,
|
||||
},
|
||||
move |tab, _, _, cx| {
|
||||
entity.update(cx, |this, _cx| {
|
||||
this.last_dragged_tab = Some(tab.clone());
|
||||
});
|
||||
cx.new(|_| tab.clone())
|
||||
},
|
||||
)
|
||||
.drag_over::<DraggedWindowTab>({
|
||||
let tab_ix = ix;
|
||||
move |element, dragged_tab: &DraggedWindowTab, _, cx| {
|
||||
let mut styled_tab = element
|
||||
.bg(cx.theme().colors().drop_target_background)
|
||||
.border_color(cx.theme().colors().drop_target_border)
|
||||
.border_0();
|
||||
|
||||
if tab_ix < dragged_tab.ix {
|
||||
styled_tab = styled_tab.border_l_2();
|
||||
} else if tab_ix > dragged_tab.ix {
|
||||
styled_tab = styled_tab.border_r_2();
|
||||
}
|
||||
|
||||
styled_tab
|
||||
}
|
||||
})
|
||||
.on_drop({
|
||||
let tab_ix = ix;
|
||||
cx.listener(move |this, dragged_tab: &DraggedWindowTab, _window, cx| {
|
||||
this.last_dragged_tab = None;
|
||||
Self::handle_tab_drop(dragged_tab, tab_ix, cx);
|
||||
})
|
||||
})
|
||||
.on_click(move |_, _, cx| {
|
||||
let _ = item.handle.update(cx, |_, window, _| {
|
||||
window.activate_window();
|
||||
});
|
||||
})
|
||||
.on_mouse_up(MouseButton::Middle, move |_, window, cx| {
|
||||
if item.handle.window_id() == window.window_handle().window_id() {
|
||||
window.dispatch_action(Box::new(CloseWindow), cx);
|
||||
} else {
|
||||
let _ = item.handle.update(cx, |_, window, cx| {
|
||||
window.dispatch_action(Box::new(CloseWindow), cx);
|
||||
});
|
||||
}
|
||||
})
|
||||
.child(label)
|
||||
.map(|this| match show_close_button {
|
||||
ShowCloseButton::Hidden => this,
|
||||
_ => this.child(
|
||||
div()
|
||||
.absolute()
|
||||
.top_2()
|
||||
.w_4()
|
||||
.h_4()
|
||||
.map(|this| match close_side {
|
||||
ClosePosition::Left => this.left_1(),
|
||||
ClosePosition::Right => this.right_1(),
|
||||
})
|
||||
.child(
|
||||
IconButton::new("close", IconName::Close)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click({
|
||||
move |_, window, cx| {
|
||||
if item.handle.window_id()
|
||||
== window.window_handle().window_id()
|
||||
{
|
||||
window.dispatch_action(Box::new(CloseWindow), cx);
|
||||
} else {
|
||||
let _ = item.handle.update(cx, |_, window, cx| {
|
||||
window.dispatch_action(Box::new(CloseWindow), cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.map(|this| match show_close_button {
|
||||
ShowCloseButton::Hover => this.visible_on_hover("tab"),
|
||||
_ => this,
|
||||
}),
|
||||
),
|
||||
),
|
||||
})
|
||||
.into_any();
|
||||
|
||||
let menu = right_click_menu(ix)
|
||||
.trigger(|_, _, _| tab)
|
||||
.menu(move |window, cx| {
|
||||
let focus_handle = cx.focus_handle();
|
||||
let tabs = tabs.clone();
|
||||
let other_tabs = tabs.clone();
|
||||
let move_tabs = tabs.clone();
|
||||
let merge_tabs = tabs.clone();
|
||||
|
||||
ContextMenu::build(window, cx, move |mut menu, _window_, _cx| {
|
||||
menu = menu.entry("Close Tab", None, move |window, cx| {
|
||||
Self::handle_right_click_action(
|
||||
cx,
|
||||
window,
|
||||
&tabs,
|
||||
|tab| tab.id == item.id,
|
||||
|window, cx| {
|
||||
window.dispatch_action(Box::new(CloseWindow), cx);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
menu = menu.entry("Close Other Tabs", None, move |window, cx| {
|
||||
Self::handle_right_click_action(
|
||||
cx,
|
||||
window,
|
||||
&other_tabs,
|
||||
|tab| tab.id != item.id,
|
||||
|window, cx| {
|
||||
window.dispatch_action(Box::new(CloseWindow), cx);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
menu = menu.entry("Move Tab to New Window", None, move |window, cx| {
|
||||
Self::handle_right_click_action(
|
||||
cx,
|
||||
window,
|
||||
&move_tabs,
|
||||
|tab| tab.id == item.id,
|
||||
|window, cx| {
|
||||
SystemWindowTabController::move_tab_to_new_window(
|
||||
cx,
|
||||
window.window_handle().window_id(),
|
||||
);
|
||||
window.move_tab_to_new_window();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
menu = menu.entry("Show All Tabs", None, move |window, cx| {
|
||||
Self::handle_right_click_action(
|
||||
cx,
|
||||
window,
|
||||
&merge_tabs,
|
||||
|tab| tab.id == item.id,
|
||||
|window, _cx| {
|
||||
window.toggle_window_tab_overview();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
menu.context(focus_handle)
|
||||
})
|
||||
});
|
||||
|
||||
div()
|
||||
.flex_1()
|
||||
.min_w(rem_size * 10)
|
||||
.when(is_active, |this| this.bg(active_background_color))
|
||||
.border_t_1()
|
||||
.border_color(if is_active {
|
||||
active_background_color
|
||||
} else {
|
||||
cx.theme().colors().border
|
||||
})
|
||||
.child(menu)
|
||||
}
|
||||
|
||||
fn handle_tab_drop(dragged_tab: &DraggedWindowTab, ix: usize, cx: &mut Context<Self>) {
|
||||
SystemWindowTabController::update_tab_position(cx, dragged_tab.id, ix);
|
||||
}
|
||||
|
||||
fn handle_right_click_action<F, P>(
|
||||
cx: &mut App,
|
||||
window: &mut Window,
|
||||
tabs: &Vec<SystemWindowTab>,
|
||||
predicate: P,
|
||||
mut action: F,
|
||||
) where
|
||||
P: Fn(&SystemWindowTab) -> bool,
|
||||
F: FnMut(&mut Window, &mut App),
|
||||
{
|
||||
for tab in tabs {
|
||||
if predicate(tab) {
|
||||
if tab.id == window.window_handle().window_id() {
|
||||
action(window, cx);
|
||||
} else {
|
||||
let _ = tab.handle.update(cx, |_view, window, cx| {
|
||||
action(window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for SystemWindowTabs {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let use_system_window_tabs = WorkspaceSettings::get_global(cx).use_system_window_tabs;
|
||||
let active_background_color = cx.theme().colors().title_bar_background;
|
||||
let inactive_background_color = cx.theme().colors().tab_bar_background;
|
||||
let entity = cx.entity();
|
||||
|
||||
let controller = cx.global::<SystemWindowTabController>();
|
||||
let visible = controller.is_visible();
|
||||
let current_window_tab = vec![SystemWindowTab::new(
|
||||
SharedString::from(window.window_title()),
|
||||
window.window_handle(),
|
||||
)];
|
||||
let tabs = controller
|
||||
.tabs(window.window_handle().window_id())
|
||||
.unwrap_or(¤t_window_tab)
|
||||
.clone();
|
||||
|
||||
let tab_items = tabs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(ix, item)| {
|
||||
self.render_tab(
|
||||
ix,
|
||||
item.clone(),
|
||||
tabs.clone(),
|
||||
active_background_color,
|
||||
inactive_background_color,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let number_of_tabs = tab_items.len().max(1);
|
||||
if (!window.tab_bar_visible() && !visible)
|
||||
|| (!use_system_window_tabs && number_of_tabs == 1)
|
||||
{
|
||||
return h_flex().into_any_element();
|
||||
}
|
||||
|
||||
h_flex()
|
||||
.w_full()
|
||||
.h(Tab::container_height(cx))
|
||||
.bg(inactive_background_color)
|
||||
.on_mouse_up_out(
|
||||
MouseButton::Left,
|
||||
cx.listener(|this, _event, window, cx| {
|
||||
if let Some(tab) = this.last_dragged_tab.take() {
|
||||
SystemWindowTabController::move_tab_to_new_window(cx, tab.id);
|
||||
if tab.id == window.window_handle().window_id() {
|
||||
window.move_tab_to_new_window();
|
||||
} else {
|
||||
let _ = tab.handle.update(cx, |_, window, _cx| {
|
||||
window.move_tab_to_new_window();
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.id("window tabs")
|
||||
.w_full()
|
||||
.h(Tab::container_height(cx))
|
||||
.bg(inactive_background_color)
|
||||
.overflow_x_scroll()
|
||||
.track_scroll(&self.tab_bar_scroll_handle)
|
||||
.children(tab_items)
|
||||
.child(
|
||||
canvas(
|
||||
|_, _, _| (),
|
||||
move |bounds, _, _, cx| {
|
||||
let entity = entity.clone();
|
||||
entity.update(cx, |this, cx| {
|
||||
let width = bounds.size.width / number_of_tabs as f32;
|
||||
if width != this.measured_tab_width {
|
||||
this.measured_tab_width = width;
|
||||
cx.notify();
|
||||
}
|
||||
});
|
||||
},
|
||||
)
|
||||
.absolute()
|
||||
.size_full(),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.h_full()
|
||||
.px(DynamicSpacing::Base06.rems(cx))
|
||||
.border_t_1()
|
||||
.border_l_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
IconButton::new("plus", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.on_click(|_event, window, cx| {
|
||||
window.dispatch_action(
|
||||
Box::new(zed_actions::OpenRecent {
|
||||
create_new_window: true,
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
}),
|
||||
),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for DraggedWindowTab {
|
||||
fn render(
|
||||
&mut self,
|
||||
_window: &mut gpui::Window,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) -> impl gpui::IntoElement {
|
||||
let ui_font = ThemeSettings::get_global(cx).ui_font.clone();
|
||||
let label = Label::new(self.title.clone())
|
||||
.size(LabelSize::Small)
|
||||
.truncate()
|
||||
.color(if self.is_active {
|
||||
Color::Default
|
||||
} else {
|
||||
Color::Muted
|
||||
});
|
||||
|
||||
h_flex()
|
||||
.h(Tab::container_height(cx))
|
||||
.w(self.width)
|
||||
.px(DynamicSpacing::Base16.px(cx))
|
||||
.justify_center()
|
||||
.bg(if self.is_active {
|
||||
self.active_background_color
|
||||
} else {
|
||||
self.inactive_background_color
|
||||
})
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.font(ui_font)
|
||||
.child(label)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user