update theme
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 9m53s
Rust / build (macos-latest, stable) (push) Has been cancelled
Rust / build (windows-latest, stable) (push) Has been cancelled

This commit is contained in:
2026-02-21 10:43:36 +07:00
parent bc588114c4
commit e3141aba19
16 changed files with 1229 additions and 1588 deletions

View File

@@ -1,32 +1,27 @@
use std::ops::Deref;
use std::sync::Arc;
use gpui::prelude::FluentBuilder as _;
use gpui::{
div, px, App, AppContext, Axis, Context, Element, Entity, InteractiveElement as _, IntoElement,
MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render,
StatefulInteractiveElement, Style, Styled as _, WeakEntity, Window,
div, px, App, AppContext, Axis, Context, Element, Entity, IntoElement, MouseMoveEvent,
MouseUpEvent, ParentElement as _, Pixels, Point, Render, Style, Styled as _, WeakEntity,
Window,
};
use serde::{Deserialize, Serialize};
use theme::ActiveTheme;
use super::{DockArea, DockItem};
use crate::dock_area::panel::PanelView;
use crate::dock_area::tab_panel::TabPanel;
use crate::resizable::{HANDLE_PADDING, HANDLE_SIZE, PANEL_MIN_SIZE};
use crate::{AxisExt as _, StyledExt};
use crate::resizable::{resize_handle, PANEL_MIN_SIZE};
use crate::StyledExt;
#[derive(Clone, Render)]
struct ResizePanel;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DockPlacement {
#[serde(rename = "center")]
Center,
#[serde(rename = "left")]
Left,
#[serde(rename = "bottom")]
Bottom,
#[serde(rename = "right")]
Right,
}
@@ -58,16 +53,21 @@ impl DockPlacement {
pub struct Dock {
pub(super) placement: DockPlacement,
dock_area: WeakEntity<DockArea>,
/// Dock layout
pub(crate) panel: DockItem,
/// The size is means the width or height of the Dock, if the placement is left or right, the size is width, otherwise the size is height.
pub(super) size: Pixels,
/// Whether the Dock is open
pub(super) open: bool,
/// Whether the Dock is collapsible, default: true
pub(super) collapsible: bool,
// Runtime state
/// Whether the Dock is resizing
is_resizing: bool,
resizing: bool,
}
impl Dock {
@@ -98,7 +98,7 @@ impl Dock {
open: true,
collapsible: true,
size: px(200.0),
is_resizing: false,
resizing: false,
}
}
@@ -231,54 +231,16 @@ impl Dock {
cx: &mut Context<Self>,
) -> impl IntoElement {
let axis = self.placement.axis();
let neg_offset = -HANDLE_PADDING;
let view = cx.entity().clone();
div()
.id("resize-handle")
.occlude()
.absolute()
.flex_shrink_0()
.when(self.placement.is_left(), |this| {
// FIXME: Improve this to let the scroll bar have px(HANDLE_PADDING)
this.cursor_col_resize()
.top_0()
.right(px(1.))
.h_full()
.w(HANDLE_SIZE)
.pt_12()
.pb_4()
})
.when(self.placement.is_right(), |this| {
this.cursor_col_resize()
.top_0()
.left(px(-0.5))
.h_full()
.w(HANDLE_SIZE)
.pt_12()
.pb_4()
})
.when(self.placement.is_bottom(), |this| {
this.cursor_row_resize()
.top(neg_offset)
.left_0()
.w_full()
.h(HANDLE_SIZE)
.py(HANDLE_PADDING)
})
.child(
div()
.rounded_full()
.hover(|this| this.bg(cx.theme().border_variant))
.when(axis.is_horizontal(), |this| this.h_full().w(HANDLE_SIZE))
.when(axis.is_vertical(), |this| this.w_full().h(HANDLE_SIZE)),
)
resize_handle("resize-handle", axis)
.placement(self.placement)
.on_drag(ResizePanel {}, move |info, _, _, cx| {
cx.stop_propagation();
view.update(cx, |view, _| {
view.is_resizing = true;
view.update(cx, |view, _cx| {
view.resizing = true;
});
cx.new(|_| info.clone())
cx.new(|_| info.deref().clone())
})
}
@@ -288,7 +250,7 @@ impl Dock {
_window: &mut Window,
cx: &mut Context<Self>,
) {
if !self.is_resizing {
if !self.resizing {
return;
}
@@ -349,7 +311,7 @@ impl Dock {
}
fn done_resizing(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
self.is_resizing = false;
self.resizing = false;
}
}
@@ -440,7 +402,7 @@ impl Element for DockElement {
) {
window.on_mouse_event({
let view = self.view.clone();
let is_resizing = view.read(cx).is_resizing;
let is_resizing = view.read(cx).resizing;
move |e: &MouseMoveEvent, phase, window, cx| {
if !is_resizing {
return;

View File

@@ -2,30 +2,23 @@ use std::sync::Arc;
use gpui::prelude::FluentBuilder;
use gpui::{
actions, canvas, div, px, AnyElement, AnyView, App, AppContext, Axis, Bounds, Context, Edges,
Entity, EntityId, EventEmitter, Focusable, InteractiveElement as _, IntoElement,
ParentElement as _, Pixels, Render, SharedString, Styled, Subscription, WeakEntity, Window,
actions, div, px, AnyElement, AnyView, App, AppContext, Axis, Bounds, Context, Edges, Entity,
EntityId, EventEmitter, Focusable, InteractiveElement as _, IntoElement, ParentElement as _,
Pixels, Render, SharedString, Styled, Subscription, WeakEntity, Window,
};
use crate::dock_area::dock::{Dock, DockPlacement};
use crate::dock_area::panel::{Panel, PanelEvent, PanelStyle, PanelView};
use crate::dock_area::stack_panel::StackPanel;
use crate::dock_area::tab_panel::TabPanel;
use crate::ElementExt;
pub mod dock;
pub mod panel;
pub mod stack_panel;
pub mod tab_panel;
actions!(
dock,
[
/// Zoom the current panel
ToggleZoom,
/// Close the current panel
ClosePanel
]
);
actions!(dock, [ToggleZoom, ClosePanel]);
pub enum DockEvent {
/// The layout of the dock has changed, subscribers this to save the layout.
@@ -38,20 +31,31 @@ pub enum DockEvent {
/// The main area of the dock.
pub struct DockArea {
pub(crate) bounds: Bounds<Pixels>,
/// The center view of the dockarea.
pub items: DockItem,
/// The entity_id of the [`TabPanel`](TabPanel) where each toggle button should be displayed,
toggle_button_panels: Edges<Option<EntityId>>,
/// The left dock of the dock_area.
left_dock: Option<Entity<Dock>>,
/// The bottom dock of the dock_area.
bottom_dock: Option<Entity<Dock>>,
/// The right dock of the dock_area.
right_dock: Option<Entity<Dock>>,
/// The entity_id of the [`TabPanel`](TabPanel) where each toggle button should be displayed,
toggle_button_panels: Edges<Option<EntityId>>,
/// Whether to show the toggle button.
toggle_button_visible: bool,
/// The top zoom view of the dock_area, if any.
zoom_view: Option<AnyView>,
/// Lock panels layout, but allow to resize.
is_locked: bool,
/// The panel style, default is [`PanelStyle::Default`](PanelStyle::Default).
pub(crate) panel_style: PanelStyle,
subscriptions: Vec<Subscription>,
@@ -330,6 +334,7 @@ impl DockArea {
items: dock_item,
zoom_view: None,
toggle_button_panels: Edges::default(),
toggle_button_visible: true,
left_dock: None,
right_dock: None,
bottom_dock: None,
@@ -344,7 +349,7 @@ impl DockArea {
}
/// Set the panel style of the dock area.
pub fn panel_style(mut self, style: PanelStyle) -> Self {
pub fn style(mut self, style: PanelStyle) -> Self {
self.panel_style = style;
self
}
@@ -649,31 +654,35 @@ impl DockArea {
cx.subscribe_in(
view,
window,
move |_, panel, event, window, cx| match event {
move |_this, panel, event, window, cx| match event {
PanelEvent::ZoomIn => {
let panel = panel.clone();
cx.spawn_in(window, async move |view, window| {
_ = view.update_in(window, |view, window, cx| {
view.update_in(window, |view, window, cx| {
view.set_zoomed_in(panel, window, cx);
cx.notify();
});
})
.ok();
})
.detach();
}
PanelEvent::ZoomOut => cx
.spawn_in(window, async move |view, window| {
PanelEvent::ZoomOut => {
cx.spawn_in(window, async move |view, window| {
_ = view.update_in(window, |view, window, cx| {
view.set_zoomed_out(window, cx);
});
})
.detach(),
.detach();
}
PanelEvent::LayoutChanged => {
cx.spawn_in(window, async move |view, window| {
_ = view.update_in(window, |view, window, cx| {
view.update_in(window, |view, window, cx| {
view.update_toggle_button_tab_panels(window, cx)
});
})
.ok();
})
.detach();
// Emit layout changed event for dock
cx.emit(DockEvent::LayoutChanged);
}
},
@@ -746,14 +755,7 @@ impl Render for DockArea {
.relative()
.size_full()
.overflow_hidden()
.child(
canvas(
move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds),
|_, _, _, _| {},
)
.absolute()
.size_full(),
)
.on_prepaint(move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds))
.map(|this| {
if let Some(zoom_view) = self.zoom_view.clone() {
this.child(zoom_view)

View File

@@ -6,6 +6,7 @@ use gpui::{
use crate::button::Button;
use crate::menu::PopupMenu;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PanelEvent {
ZoomIn,
ZoomOut,

View File

@@ -7,13 +7,14 @@ use gpui::{
Window,
};
use smallvec::SmallVec;
use theme::{ActiveTheme, CLIENT_SIDE_DECORATION_ROUNDING};
use super::{DockArea, PanelEvent};
use crate::dock_area::panel::{Panel, PanelView};
use crate::dock_area::tab_panel::TabPanel;
use crate::resizable::{
h_resizable, resizable_panel, v_resizable, ResizablePanel, ResizablePanelEvent,
ResizablePanelGroup,
resizable_panel, ResizablePanelEvent, ResizablePanelGroup, ResizablePanelState, ResizableState,
PANEL_MIN_SIZE,
};
use crate::{h_flex, AxisExt as _, Placement};
@@ -22,9 +23,8 @@ pub struct StackPanel {
pub(super) axis: Axis,
focus_handle: FocusHandle,
pub(crate) panels: SmallVec<[Arc<dyn PanelView>; 2]>,
panel_group: Entity<ResizablePanelGroup>,
#[allow(dead_code)]
subscriptions: Vec<Subscription>,
state: Entity<ResizableState>,
_subscriptions: Vec<Subscription>,
}
impl Panel for StackPanel {
@@ -39,28 +39,23 @@ impl Panel for StackPanel {
impl StackPanel {
pub fn new(axis: Axis, window: &mut Window, cx: &mut Context<Self>) -> Self {
let panel_group = cx.new(|cx| {
if axis == Axis::Horizontal {
h_resizable(window, cx)
} else {
v_resizable(window, cx)
}
});
let state = cx.new(|_| ResizableState::default());
// Bubble up the resize event.
let subscriptions = vec![cx.subscribe_in(
&panel_group,
window,
|_, _, _: &ResizablePanelEvent, _, cx| cx.emit(PanelEvent::LayoutChanged),
)];
let subscriptions =
vec![
cx.subscribe_in(&state, window, |_, _, _: &ResizablePanelEvent, _, cx| {
cx.emit(PanelEvent::LayoutChanged)
}),
];
Self {
axis,
parent: None,
focus_handle: cx.focus_handle(),
panels: SmallVec::new(),
panel_group,
subscriptions,
state,
_subscriptions: subscriptions,
}
}
@@ -70,7 +65,7 @@ impl StackPanel {
}
/// Return true if self or parent only have last panel.
pub(super) fn is_last_panel(&self, cx: &App) -> bool {
pub fn is_last_panel(&self, cx: &App) -> bool {
if self.panels.len() > 1 {
return false;
}
@@ -84,12 +79,12 @@ impl StackPanel {
true
}
pub(super) fn panels_len(&self) -> usize {
pub fn panels_len(&self) -> usize {
self.panels.len()
}
/// Return the index of the panel.
pub(crate) fn index_of_panel(&self, panel: Arc<dyn PanelView>) -> Option<usize> {
pub fn index_of_panel(&self, panel: Arc<dyn PanelView>) -> Option<usize> {
self.panels.iter().position(|p| p == &panel)
}
@@ -172,13 +167,6 @@ impl StackPanel {
self.insert_panel(panel, ix + 1, size, dock_area, window, cx);
}
fn new_resizable_panel(panel: Arc<dyn PanelView>, size: Option<Pixels>) -> ResizablePanel {
resizable_panel()
.content_view(panel.view())
.content_visible(move |cx| panel.visible(cx))
.when_some(size, |this, size| this.size(size))
}
fn insert_panel(
&mut self,
panel: Arc<dyn PanelView>,
@@ -225,14 +213,21 @@ impl StackPanel {
ix
};
// Get avg size of all panels to insert new panel, if size is None.
let size = match size {
Some(size) => size,
None => {
let state = self.state.read(cx);
(state.container_size() / (state.sizes().len() + 1) as f32).max(PANEL_MIN_SIZE)
}
};
// Insert panel
self.panels.insert(ix, panel.clone());
self.panel_group.update(cx, |view, cx| {
view.insert_child(
Self::new_resizable_panel(panel.clone(), size),
ix,
window,
cx,
)
// Update resizable state
self.state.update(cx, |state, cx| {
state.insert_panel(Some(size), Some(ix), cx);
});
cx.emit(PanelEvent::LayoutChanged);
@@ -240,47 +235,47 @@ impl StackPanel {
}
/// Remove panel from the stack.
///
/// If `ix` is not found, do nothing.
pub fn remove_panel(
&mut self,
panel: Arc<dyn PanelView>,
window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(ix) = self.index_of_panel(panel.clone()) {
self.panels.remove(ix);
self.panel_group.update(cx, |view, cx| {
view.remove_child(ix, window, cx);
});
let Some(ix) = self.index_of_panel(panel.clone()) else {
return;
};
cx.emit(PanelEvent::LayoutChanged);
self.remove_self_if_empty(window, cx);
}
self.panels.remove(ix);
self.state.update(cx, |state, cx| {
state.remove_panel(ix, cx);
});
cx.emit(PanelEvent::LayoutChanged);
self.remove_self_if_empty(window, cx);
}
/// Replace the old panel with the new panel at same index.
pub(super) fn replace_panel(
pub fn replace_panel(
&mut self,
old_panel: Arc<dyn PanelView>,
new_panel: Entity<StackPanel>,
window: &mut Window,
_window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(ix) = self.index_of_panel(old_panel.clone()) {
self.panels[ix] = Arc::new(new_panel.clone());
self.panel_group.update(cx, |view, cx| {
view.replace_child(
Self::new_resizable_panel(Arc::new(new_panel.clone()), None),
ix,
window,
cx,
);
self.state.update(cx, |state, cx| {
state.replace_panel(ix, ResizablePanelState::default(), cx);
});
cx.emit(PanelEvent::LayoutChanged);
}
}
/// If children is empty, remove self from parent view.
pub(crate) fn remove_self_if_empty(&mut self, window: &mut Window, cx: &mut Context<Self>) {
pub fn remove_self_if_empty(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if self.is_root() {
return;
}
@@ -301,11 +296,7 @@ impl StackPanel {
}
/// Find the first top left in the stack.
pub(super) fn left_top_tab_panel(
&self,
check_parent: bool,
cx: &App,
) -> Option<Entity<TabPanel>> {
pub fn left_top_tab_panel(&self, check_parent: bool, cx: &App) -> Option<Entity<TabPanel>> {
if check_parent {
if let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade()) {
if let Some(panel) = parent.read(cx).left_top_tab_panel(true, cx) {
@@ -329,11 +320,7 @@ impl StackPanel {
}
/// Find the first top right in the stack.
pub(super) fn right_top_tab_panel(
&self,
check_parent: bool,
cx: &App,
) -> Option<Entity<TabPanel>> {
pub fn right_top_tab_panel(&self, check_parent: bool, cx: &App) -> Option<Entity<TabPanel>> {
if check_parent {
if let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade()) {
if let Some(panel) = parent.read(cx).right_top_tab_panel(true, cx) {
@@ -362,17 +349,17 @@ impl StackPanel {
}
/// Remove all panels from the stack.
pub(super) fn remove_all_panels(&mut self, window: &mut Window, cx: &mut Context<Self>) {
pub fn remove_all_panels(&mut self, _: &mut Window, cx: &mut Context<Self>) {
self.panels.clear();
self.panel_group
.update(cx, |view, cx| view.remove_all_children(window, cx));
self.state.update(cx, |state, cx| {
state.clear();
cx.notify();
});
}
/// Change the axis of the stack panel.
pub(super) fn set_axis(&mut self, axis: Axis, window: &mut Window, cx: &mut Context<Self>) {
pub fn set_axis(&mut self, axis: Axis, _: &mut Window, cx: &mut Context<Self>) {
self.axis = axis;
self.panel_group
.update(cx, |view, cx| view.set_axis(axis, window, cx));
cx.notify();
}
}
@@ -388,10 +375,23 @@ impl EventEmitter<PanelEvent> for StackPanel {}
impl EventEmitter<DismissEvent> for StackPanel {}
impl Render for StackPanel {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
h_flex()
.size_full()
.overflow_hidden()
.child(self.panel_group.clone())
.bg(cx.theme().panel_background)
.when(cx.theme().platform.is_linux(), |this| {
this.rounded_br(CLIENT_SIDE_DECORATION_ROUNDING)
})
.child(
ResizablePanelGroup::new("stack-panel-group")
.with_state(&self.state)
.axis(self.axis)
.children(self.panels.clone().into_iter().map(|panel| {
resizable_panel()
.child(panel.view())
.visible(panel.visible(cx))
})),
)
}
}

View File

@@ -7,13 +7,13 @@ use gpui::{
MouseButton, ParentElement, Pixels, Render, ScrollHandle, SharedString,
StatefulInteractiveElement, Styled, WeakEntity, Window,
};
use theme::ActiveTheme;
use theme::{ActiveTheme, CLIENT_SIDE_DECORATION_ROUNDING, TITLEBAR_HEIGHT};
use super::panel::PanelView;
use super::stack_panel::StackPanel;
use super::{ClosePanel, DockArea, PanelEvent, PanelStyle, ToggleZoom};
use crate::button::{Button, ButtonVariants as _};
use crate::dock_area::panel::Panel;
use crate::dock_area::dock::DockPlacement;
use crate::dock_area::panel::{Panel, PanelView};
use crate::dock_area::stack_panel::StackPanel;
use crate::dock_area::{ClosePanel, DockArea, PanelEvent, PanelStyle, ToggleZoom};
use crate::menu::{DropdownMenu, PopupMenu};
use crate::tab::tab_bar::TabBar;
use crate::tab::Tab;
@@ -65,16 +65,29 @@ impl Render for DragPanel {
pub struct TabPanel {
focus_handle: FocusHandle,
dock_area: WeakEntity<DockArea>,
/// The stock_panel can be None, if is None, that means the panels can't be split or move
stack_panel: Option<WeakEntity<StackPanel>>,
/// List of panels in the tab panel
pub(crate) panels: Vec<Arc<dyn PanelView>>,
/// Current active panel index
pub(crate) active_ix: usize,
/// If this is true, the Panel closeable will follow the active panel's closeable,
/// otherwise this TabPanel will not able to close
pub(crate) closable: bool,
/// The stock_panel can be None, if is None, that means the panels can't be split or move
stack_panel: Option<WeakEntity<StackPanel>>,
/// Scroll handle for the tab bar
tab_bar_scroll_handle: ScrollHandle,
is_zoomed: bool,
is_collapsed: bool,
/// Whether the tab panel is zoomeds
zoomed: bool,
/// Whether the tab panel is collapsed
collapsed: bool,
/// When drag move, will get the placement of the panel to be split
will_split_placement: Option<Placement>,
}
@@ -142,8 +155,8 @@ impl TabPanel {
active_ix: 0,
tab_bar_scroll_handle: ScrollHandle::new(),
will_split_placement: None,
is_zoomed: false,
is_collapsed: false,
zoomed: false,
collapsed: false,
closable: true,
}
}
@@ -339,7 +352,7 @@ impl TabPanel {
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.is_collapsed = collapsed;
self.collapsed = collapsed;
cx.notify();
}
@@ -352,7 +365,7 @@ impl TabPanel {
return true;
}
if self.is_zoomed {
if self.zoomed {
return true;
}
@@ -408,7 +421,7 @@ impl TabPanel {
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let is_zoomed = self.is_zoomed && state.zoomable;
let is_zoomed = self.zoomed && state.zoomable;
let view = cx.entity().clone();
let build_popup_menu = move |this, cx: &App| view.read(cx).popup_menu(this, cx);
let toolbar = self.toolbar_buttons(window, cx);
@@ -420,7 +433,7 @@ impl TabPanel {
.occlude()
.rounded_full()
.children(toolbar.into_iter().map(|btn| btn.small().ghost().rounded()))
.when(self.is_zoomed, |this| {
.when(self.zoomed, |this| {
this.child(
Button::new("zoom")
.icon(IconName::Zoom)
@@ -433,8 +446,7 @@ impl TabPanel {
)
})
.when(has_toolbar, |this| {
this.bg(cx.theme().surface_background)
.child(div().flex_shrink_0().h_4().w_px().bg(cx.theme().border))
this.child(div().flex_shrink_0().h_4().w_px().bg(cx.theme().border))
})
.child(
Button::new("menu")
@@ -461,21 +473,113 @@ impl TabPanel {
)
}
fn render_dock_toggle_button(
&self,
placement: DockPlacement,
_window: &mut Window,
cx: &mut Context<Self>,
) -> Option<Button> {
if self.zoomed {
return None;
}
let dock_area = self.dock_area.upgrade()?.read(cx);
if !dock_area.toggle_button_visible {
return None;
}
if !dock_area.is_dock_collapsible(placement, cx) {
return None;
}
let view_entity_id = cx.entity().entity_id();
let toggle_button_panels = dock_area.toggle_button_panels;
// Check if current TabPanel's entity_id matches the one stored in DockArea for this placement
if !match placement {
DockPlacement::Left => {
dock_area.left_dock.is_some() && toggle_button_panels.left == Some(view_entity_id)
}
DockPlacement::Right => {
dock_area.right_dock.is_some() && toggle_button_panels.right == Some(view_entity_id)
}
DockPlacement::Bottom => {
dock_area.bottom_dock.is_some()
&& toggle_button_panels.bottom == Some(view_entity_id)
}
DockPlacement::Center => unreachable!(),
} {
return None;
}
let is_open = dock_area.is_dock_open(placement, cx);
let icon = match placement {
DockPlacement::Left => {
if is_open {
IconName::PanelLeft
} else {
IconName::PanelLeftOpen
}
}
DockPlacement::Right => {
if is_open {
IconName::PanelRight
} else {
IconName::PanelRightOpen
}
}
DockPlacement::Bottom => {
if is_open {
IconName::PanelBottom
} else {
IconName::PanelBottomOpen
}
}
DockPlacement::Center => unreachable!(),
};
Some(
Button::new(SharedString::from(format!("toggle-dock:{:?}", placement)))
.icon(icon)
.small()
.ghost()
.tab_stop(false)
.tooltip(match is_open {
true => "Collapse",
false => "Expand",
})
.on_click(cx.listener({
let dock_area = self.dock_area.clone();
move |_this, _ev, window, cx| {
_ = dock_area.update(cx, |dock_area, cx| {
dock_area.toggle_dock(placement, window, cx);
});
}
})),
)
}
fn render_title_bar(
&self,
state: &TabState,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let view = cx.entity().clone();
// Get the dock area entity
let Some(dock_area) = self.dock_area.upgrade() else {
// Return a default element if the dock area is not available
return div().into_any_element();
};
let panel_style = dock_area.read(cx).panel_style;
let left_dock_button = self.render_dock_toggle_button(DockPlacement::Left, window, cx);
let bottom_dock_button = self.render_dock_toggle_button(DockPlacement::Bottom, window, cx);
let right_dock_button = self.render_dock_toggle_button(DockPlacement::Right, window, cx);
let has_extend_dock_button = left_dock_button.is_some() || bottom_dock_button.is_some();
let tabs_count = self.panels.len();
if self.panels.len() == 1 && panel_style == PanelStyle::Default {
if tabs_count == 1 && dock_area.read(cx).panel_style == PanelStyle::Default {
let panel = self.panels.first().unwrap();
if !panel.visible(cx) {
@@ -488,7 +592,20 @@ impl TabPanel {
.line_height(rems(1.0))
.h(px(30.))
.py_2()
.px_3()
.pl_3()
.pr_2()
.when(left_dock_button.is_some(), |this| this.pl_2())
.when(right_dock_button.is_some(), |this| this.pr_2())
.when(has_extend_dock_button, |this| {
this.child(
h_flex()
.flex_shrink_0()
.mr_1()
.gap_1()
.children(left_dock_button)
.children(bottom_dock_button),
)
})
.child(
div()
.id("tab")
@@ -507,7 +624,7 @@ impl TabPanel {
this.on_drag(
DragPanel {
panel: panel.clone(),
tab_panel: view,
tab_panel: cx.entity(),
},
|drag, _, _, cx| {
cx.stop_propagation();
@@ -526,25 +643,43 @@ impl TabPanel {
.into_any_element();
}
let tabs_count = self.panels.len();
TabBar::new("tab-bar")
.track_scroll(self.tab_bar_scroll_handle.clone())
TabBar::new()
.track_scroll(&self.tab_bar_scroll_handle)
.h(TITLEBAR_HEIGHT)
.when(has_extend_dock_button, |this| {
this.prefix(
h_flex()
.items_center()
.top_0()
.right(-px(1.))
.border_r_1()
.border_b_1()
.h_full()
.border_color(cx.theme().border)
.bg(cx.theme().surface_background)
.px_2()
.children(left_dock_button)
.children(bottom_dock_button),
)
})
.children(self.panels.iter().enumerate().filter_map(|(ix, panel)| {
let disabled = self.collapsed;
let mut active = state.active_panel.as_ref() == Some(panel);
let disabled = self.is_collapsed;
// If the panel is not visible, hide the tabbar
if !panel.visible(cx) {
return None;
}
// Always not show active tab style, if the panel is collapsed
if self.is_collapsed {
if self.collapsed {
active = false;
}
Some(
Tab::new(("tab", ix), panel.title(cx))
Tab::new()
.ix(ix)
.label(panel.title(cx))
.py_2()
.selected(active)
.disabled(disabled)
@@ -563,7 +698,7 @@ impl TabPanel {
}))
.when(state.draggable, |this| {
this.on_drag(
DragPanel::new(panel.clone(), view.clone()),
DragPanel::new(panel.clone(), cx.entity().clone()),
|drag, _, _, cx| {
cx.stop_propagation();
cx.new(|_| drag.clone())
@@ -587,16 +722,17 @@ impl TabPanel {
}),
)
}))
.child(
.last_empty_space(
// empty space to allow move to last tab right
div()
.id("tab-bar-empty-space")
.h_full()
.flex_grow()
.min_w_16()
.rounded(cx.theme().radius)
.when(state.droppable, |this| {
this.drag_over::<DragPanel>(|this, _, _, cx| {
let view = cx.entity();
this.drag_over::<DragPanel>(|this, _d, _window, cx| {
this.bg(cx.theme().surface_background)
})
.on_drop(cx.listener(
@@ -614,16 +750,22 @@ impl TabPanel {
))
}),
)
.suffix(
h_flex()
.items_center()
.top_0()
.right_0()
.h_full()
.px_2()
.gap_1()
.child(self.render_toolbar(state, window, cx)),
)
.when(!self.collapsed, |this| {
this.suffix(
h_flex()
.items_center()
.px_2()
.gap_1()
.top_0()
.right_0()
.h_full()
.border_color(cx.theme().border)
.border_l_1()
.border_b_1()
.child(self.render_toolbar(state, window, cx))
.when_some(right_dock_button, |this, btn| this.child(btn)),
)
})
.into_any_element()
}
@@ -633,7 +775,7 @@ impl TabPanel {
_window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
if self.is_collapsed {
if self.collapsed {
return Empty {}.into_any_element();
}
@@ -646,14 +788,13 @@ impl TabPanel {
.group("")
.overflow_hidden()
.flex_1()
.p_1()
.child(
div()
.size_full()
.rounded(cx.theme().radius_lg)
.when(cx.theme().shadow, |this| this.shadow_sm())
.when(cx.theme().mode.is_dark(), |this| this.shadow_lg())
.bg(cx.theme().panel_background)
.when(cx.theme().platform.is_linux(), |this| {
this.rounded_b(CLIENT_SIDE_DECORATION_ROUNDING)
})
.overflow_hidden()
.child(
active_panel
@@ -667,7 +808,6 @@ impl TabPanel {
div()
.invisible()
.absolute()
.p_1()
.child(
div()
.rounded(cx.theme().radius_lg)
@@ -911,16 +1051,16 @@ impl TabPanel {
return;
}
if !self.is_zoomed {
if !self.zoomed {
cx.emit(PanelEvent::ZoomIn)
} else {
cx.emit(PanelEvent::ZoomOut)
}
self.is_zoomed = !self.is_zoomed;
self.zoomed = !self.zoomed;
cx.spawn({
let is_zoomed = self.is_zoomed;
let is_zoomed = self.zoomed;
async move |view, cx| {
view.update(cx, |view, cx| {
view.set_zoomed(is_zoomed, cx);
@@ -933,7 +1073,7 @@ impl TabPanel {
fn on_action_close_panel(
&mut self,
_: &ClosePanel,
_ev: &ClosePanel,
window: &mut Window,
cx: &mut Context<Self>,
) {
@@ -954,7 +1094,6 @@ impl Focusable for TabPanel {
}
impl EventEmitter<DismissEvent> for TabPanel {}
impl EventEmitter<PanelEvent> for TabPanel {}
impl Render for TabPanel {
@@ -975,11 +1114,12 @@ impl Render for TabPanel {
}
v_flex()
.when(!self.is_collapsed, |this| {
.when(!self.collapsed, |this| {
this.on_action(cx.listener(Self::on_action_toggle_zoom))
.on_action(cx.listener(Self::on_action_close_panel))
})
.id("tab-panel")
.tab_group()
.track_focus(&focus_handle)
.size_full()
.overflow_hidden()