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
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:
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
})),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user