update dock

This commit is contained in:
2026-08-19 11:00:21 +07:00
parent c8bf7eb5d3
commit cc4fcd315c
6 changed files with 25 additions and 126 deletions
-2
View File
@@ -1,5 +1,3 @@
//! Dock is a fixed container that places at left, bottom, right of the Windows.
use std::ops::Deref; use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
+4 -11
View File
@@ -15,7 +15,7 @@ use gpui::prelude::FluentBuilder;
use gpui::{ use gpui::{
AnyElement, AnyView, App, AppContext, Axis, Bounds, Context, Edges, Entity, EntityId, AnyElement, AnyView, App, AppContext, Axis, Bounds, Context, Edges, Entity, EntityId,
EventEmitter, InteractiveElement as _, IntoElement, ParentElement as _, Pixels, Render, EventEmitter, InteractiveElement as _, IntoElement, ParentElement as _, Pixels, Render,
SharedString, Styled, Subscription, WeakEntity, Window, actions, div, SharedString, Styled, Subscription, WeakEntity, Window, actions, div, px,
}; };
use gpui_component::{ElementExt, Placement}; use gpui_component::{ElementExt, Placement};
pub use panel::*; pub use panel::*;
@@ -52,6 +52,9 @@ pub(crate) fn t(key: &'static str) -> &'static str {
} }
} }
/// The fixed height of the tab bar, which doubles as the window title bar.
pub(crate) const TAB_BAR_HEIGHT: Pixels = px(44.);
/// A host-owned drag item, dragged into the dock by the application. /// A host-owned drag item, dragged into the dock by the application.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct AnyDrag { pub struct AnyDrag {
@@ -115,9 +118,6 @@ pub struct DockArea {
/// Lock panels layout, but allow to resize. /// Lock panels layout, but allow to resize.
locked: bool, locked: bool,
/// The panel style, default is [`PanelStyle::Default`](PanelStyle::Default).
pub(crate) panel_style: PanelStyle,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
@@ -509,7 +509,6 @@ impl DockArea {
toggle_button_panels: Edges::default(), toggle_button_panels: Edges::default(),
toggle_button_visible: true, toggle_button_visible: true,
locked: false, locked: false,
panel_style: PanelStyle::default(),
_subscriptions: vec![], _subscriptions: vec![],
}; };
@@ -523,12 +522,6 @@ impl DockArea {
self.bounds self.bounds
} }
/// Set the panel style of the dock area.
pub fn panel_style(mut self, style: PanelStyle) -> Self {
self.panel_style = style;
self
}
/// Set version of the dock area. /// Set version of the dock area.
pub fn set_version(&mut self, version: usize, _: &mut Window, cx: &mut Context<Self>) { pub fn set_version(&mut self, version: usize, _: &mut Window, cx: &mut Context<Self>) {
self.version = Some(version); self.version = Some(version);
+1 -26
View File
@@ -3,7 +3,7 @@ use std::sync::Arc;
use gpui::{ use gpui::{
AnyElement, AnyView, App, AppContext as _, Context, Entity, EntityId, EventEmitter, AnyElement, AnyView, App, AppContext as _, Context, Entity, EntityId, EventEmitter,
FocusHandle, Focusable, Global, Hsla, IntoElement, Render, SharedString, WeakEntity, Window, FocusHandle, Focusable, Global, IntoElement, Render, SharedString, WeakEntity, Window,
}; };
use gpui_component::button::Button; use gpui_component::button::Button;
use gpui_component::menu::PopupMenu; use gpui_component::menu::PopupMenu;
@@ -18,21 +18,6 @@ pub enum PanelEvent {
LayoutChanged, LayoutChanged,
} }
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum PanelStyle {
/// Display the TabBar when there are multiple tabs, otherwise display the simple title.
#[default]
Auto,
/// Always display the tab bar.
TabBar,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TitleStyle {
pub background: Hsla,
pub foreground: Hsla,
}
#[derive(Clone, Copy, Default)] #[derive(Clone, Copy, Default)]
pub enum PanelControl { pub enum PanelControl {
Both, Both,
@@ -74,11 +59,6 @@ pub trait Panel: EventEmitter<PanelEvent> + Render + Focusable {
t("Dock.Unnamed") t("Dock.Unnamed")
} }
/// The theme of the panel title, default is `None`.
fn title_style(&self, cx: &App) -> Option<TitleStyle> {
None
}
/// The suffix of the panel title, default is `None`. /// The suffix of the panel title, default is `None`.
/// ///
/// This is used to add a suffix element to the panel title. /// This is used to add a suffix element to the panel title.
@@ -182,7 +162,6 @@ pub trait PanelView: 'static + Send + Sync {
fn tab_name(&self, cx: &App) -> Option<SharedString>; fn tab_name(&self, cx: &App) -> Option<SharedString>;
fn title(&self, window: &mut Window, cx: &mut App) -> AnyElement; fn title(&self, window: &mut Window, cx: &mut App) -> AnyElement;
fn title_suffix(&self, window: &mut Window, cx: &mut App) -> Option<AnyElement>; fn title_suffix(&self, window: &mut Window, cx: &mut App) -> Option<AnyElement>;
fn title_style(&self, cx: &App) -> Option<TitleStyle>;
fn closable(&self, cx: &App) -> bool; fn closable(&self, cx: &App) -> bool;
fn zoomable(&self, cx: &App) -> Option<PanelControl>; fn zoomable(&self, cx: &App) -> Option<PanelControl>;
fn visible(&self, cx: &App) -> bool; fn visible(&self, cx: &App) -> bool;
@@ -222,10 +201,6 @@ impl<T: Panel> PanelView for Entity<T> {
}) })
} }
fn title_style(&self, cx: &App) -> Option<TitleStyle> {
self.read(cx).title_style(cx)
}
fn closable(&self, cx: &App) -> bool { fn closable(&self, cx: &App) -> bool {
self.read(cx).closable(cx) self.read(cx).closable(cx)
} }
+12 -84
View File
@@ -11,7 +11,7 @@ use gpui::{
DragMoveEvent, Empty, Entity, EntityId, EventEmitter, FocusHandle, Focusable, DragMoveEvent, Empty, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
InteractiveElement as _, IntoElement, MouseButton, ParentElement, Pixels, Point, Render, InteractiveElement as _, IntoElement, MouseButton, ParentElement, Pixels, Point, Render,
ScrollHandle, SharedString, Stateful, StatefulInteractiveElement, StyleRefinement, Styled, ScrollHandle, SharedString, Stateful, StatefulInteractiveElement, StyleRefinement, Styled,
WeakEntity, Window, WindowControlArea, div, point, px, rems, WeakEntity, Window, WindowControlArea, div, point, px,
}; };
use gpui_component::animation::{Lerp, ease_out_cubic}; use gpui_component::animation::{Lerp, ease_out_cubic};
use gpui_component::button::{Button, ButtonVariants as _}; use gpui_component::button::{Button, ButtonVariants as _};
@@ -23,9 +23,9 @@ use gpui_component::{
use super::{ use super::{
AnyDrag, ClosePanel, DockArea, DockEvent, DockPlacement, DropTarget, Panel, PanelControl, AnyDrag, ClosePanel, DockArea, DockEvent, DockPlacement, DropTarget, Panel, PanelControl,
PanelEvent, PanelState, PanelStyle, PanelView, StackPanel, ToggleZoom, PanelEvent, PanelState, PanelView, StackPanel, ToggleZoom,
}; };
use crate::{PanelInfo, t, window_controls}; use crate::{PanelInfo, TAB_BAR_HEIGHT, t, window_controls};
#[derive(Clone)] #[derive(Clone)]
struct TabState { struct TabState {
@@ -640,7 +640,7 @@ impl TabPanel {
this.children( this.children(
buttons buttons
.into_iter() .into_iter()
.map(|btn| btn.xsmall().ghost().tab_stop(false)), .map(|btn| btn.small().ghost().tab_stop(false)),
) )
}) })
.map(|this| { .map(|this| {
@@ -656,7 +656,7 @@ impl TabPanel {
this.child( this.child(
Button::new(id) Button::new(id)
.icon(icon) .icon(icon)
.xsmall() .small()
.ghost() .ghost()
.tab_stop(false) .tab_stop(false)
.tooltip_with_action(tooltip, &ToggleZoom, None) .tooltip_with_action(tooltip, &ToggleZoom, None)
@@ -672,7 +672,7 @@ impl TabPanel {
.child( .child(
Button::new("menu") Button::new("menu")
.icon(IconName::Ellipsis) .icon(IconName::Ellipsis)
.xsmall() .small()
.ghost() .ghost()
.tab_stop(false) .tab_stop(false)
.dropdown_menu({ .dropdown_menu({
@@ -743,7 +743,7 @@ impl TabPanel {
Some( Some(
Button::new(SharedString::from(format!("toggle-dock:{:?}", placement))) Button::new(SharedString::from(format!("toggle-dock:{:?}", placement)))
.icon(icon) .icon(icon)
.xsmall() .small()
.ghost() .ghost()
.tab_stop(false) .tab_stop(false)
.tooltip(match is_open { .tooltip(match is_open {
@@ -769,10 +769,6 @@ impl TabPanel {
) -> impl IntoElement { ) -> impl IntoElement {
let view = cx.entity().clone(); let view = cx.entity().clone();
let Some(dock_area) = self.dock_area.upgrade() else {
return div().into_any_element();
};
let left_dock_button = self.render_dock_toggle_button(DockPlacement::Left, window, cx); let left_dock_button = self.render_dock_toggle_button(DockPlacement::Left, window, cx);
let has_extend_dock_button = left_dock_button.is_some(); let has_extend_dock_button = left_dock_button.is_some();
@@ -786,75 +782,6 @@ impl TabPanel {
.and_then(|area| area.read(cx).left_dock.clone()) .and_then(|area| area.read(cx).left_dock.clone())
.is_none_or(|dock| !dock.read(cx).is_open()); .is_none_or(|dock| !dock.read(cx).is_open());
let panel_style = dock_area.read(cx).panel_style;
let visible_panels = self.visible_panels(cx).collect::<Vec<_>>();
if visible_panels.len() == 1 && panel_style == PanelStyle::default() {
let panel = visible_panels.first().unwrap();
if !panel.visible(cx) {
return div().into_any_element();
}
let title_style = panel.title_style(cx);
return title_bar_drag_handlers(
h_flex()
.id(("tab-panel-simple-title", cx.entity().entity_id()))
.justify_between()
.line_height(rems(1.0))
.h(px(30.))
.py_2()
.pl_3()
.pr_2()
.when(left_dock_button.is_some(), |this| this.pl_2())
.when(needs_traffic_light_padding, |this| this.pl(px(80.)))
.when_some(title_style, |this, theme| {
this.bg(theme.background).text_color(theme.foreground)
})
.when(has_extend_dock_button, |this| {
this.child(
h_flex()
.flex_shrink_0()
.mr_1()
.gap_1()
.children(left_dock_button),
)
})
.child(
div()
.id("tab")
.flex_1()
.min_w_16()
.overflow_hidden()
.text_ellipsis()
.whitespace_nowrap()
.child(panel.title(window, cx))
.when(state.draggable, |this| {
this.on_drag(
DragPanel::new(panel.clone(), view),
|drag, offset, _, cx| {
cx.stop_propagation();
drag.drag_offset.set(offset);
cx.new(|_| drag.clone())
},
)
}),
)
.children(panel.title_suffix(window, cx))
.child(
h_flex()
.flex_shrink_0()
.ml_1()
.gap_1()
.child(self.render_toolbar(state, window, cx)),
),
window,
cx,
)
.into_any_element();
}
if let Some(panel_ix) = self.pending_scroll_to_ix.take() if let Some(panel_ix) = self.pending_scroll_to_ix.take()
&& let Some(visible_ix) = self && let Some(visible_ix) = self
.panels .panels
@@ -920,6 +847,9 @@ impl TabPanel {
}) })
.child( .child(
TabBar::new("tab-bar") TabBar::new("tab-bar")
.h(TAB_BAR_HEIGHT)
.pill()
.small()
.track_scroll(&self.tab_bar_scroll_handle) .track_scroll(&self.tab_bar_scroll_handle)
.when( .when(
has_extend_dock_button || needs_traffic_light_padding, has_extend_dock_button || needs_traffic_light_padding,
@@ -931,7 +861,6 @@ impl TabPanel {
// Right -1 for avoid border overlap with the first tab // Right -1 for avoid border overlap with the first tab
.right(-px(1.)) .right(-px(1.))
.h_full() .h_full()
.bg(cx.theme().tokens.tab_bar)
.px_2() .px_2()
.when(needs_traffic_light_padding, |this| { .when(needs_traffic_light_padding, |this| {
this.pl(px(80.)) this.pl(px(80.))
@@ -1073,7 +1002,6 @@ impl TabPanel {
.top_0() .top_0()
.right_0() .right_0()
.h_full() .h_full()
.bg(cx.theme().tokens.tab_bar)
.px_2() .px_2()
.gap_1() .gap_1()
.on_prepaint({ .on_prepaint({
@@ -1116,7 +1044,7 @@ impl TabPanel {
return Empty {}.into_any_element(); return Empty {}.into_any_element();
}; };
let is_render_in_tabs = self.panels.len() > 1 && self.inner_padding(cx); let has_inner_padding = self.inner_padding(cx);
let placeholder = self.drop_placeholder_animation; let placeholder = self.drop_placeholder_animation;
let placeholder_animation_name = self.drop_placeholder_animation_name.clone(); let placeholder_animation_name = self.drop_placeholder_animation_name.clone();
@@ -1125,7 +1053,7 @@ impl TabPanel {
.id("active-panel") .id("active-panel")
.group("") .group("")
.flex_1() .flex_1()
.when(is_render_in_tabs, |this| this.pt_2()) .when(has_inner_padding, |this| this.pt_2())
.child( .child(
div() div()
.id("tab-content") .id("tab-content")
+7
View File
@@ -5,6 +5,8 @@ use gpui::{
}; };
use gpui_component::{ActiveTheme, Icon, IconName, Sizable as _, h_flex}; use gpui_component::{ActiveTheme, Icon, IconName, Sizable as _, h_flex};
use crate::TAB_BAR_HEIGHT;
/// The standard width of a window control button. /// The standard width of a window control button.
const CONTROL_WIDTH: f32 = 34.; const CONTROL_WIDTH: f32 = 34.;
@@ -145,6 +147,11 @@ pub(crate) fn window_controls(window: &mut Window, cx: &mut App) -> impl IntoEle
.items_center() .items_center()
.flex_shrink_0() .flex_shrink_0()
.h_full() .h_full()
// Like native windows apps, the controls span the title bar but never
// grow past the tab bar height.
.when(cfg!(target_os = "windows"), |this| {
this.max_h(TAB_BAR_HEIGHT)
})
.border_l_1() .border_l_1()
.border_b_1() .border_b_1()
.border_color(cx.theme().border) .border_color(cx.theme().border)
+1 -3
View File
@@ -19,9 +19,7 @@ pub struct Workspace {
impl Workspace { impl Workspace {
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self { pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let dock = cx.new(|cx| { let dock = cx.new(|cx| DockArea::new("dock", Some(1), window, cx));
DockArea::new("dock", Some(1), window, cx).panel_style(dock::PanelStyle::TabBar)
});
let weak_dock = dock.downgrade(); let weak_dock = dock.downgrade();
let sidebar = cx.new(|cx| SidebarPanel::new(weak_dock.clone(), cx)); let sidebar = cx.new(|cx| SidebarPanel::new(weak_dock.clone(), cx));