From a7baa03c64f6e64282d0a52349e56cc18fd1025f Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Thu, 17 Sep 2026 16:58:35 +0700 Subject: [PATCH] migrate resizable onto gpui-base --- crates/ui/src/dock/dock.rs | 16 +- crates/ui/src/dock/stack_panel.rs | 7 +- crates/ui/src/resizable/mod.rs | 319 ++---------------- crates/ui/src/resizable/panel.rs | 408 ----------------------- crates/ui/src/resizable/resize_handle.rs | 225 ------------- 5 files changed, 49 insertions(+), 926 deletions(-) delete mode 100644 crates/ui/src/resizable/panel.rs delete mode 100644 crates/ui/src/resizable/resize_handle.rs diff --git a/crates/ui/src/dock/dock.rs b/crates/ui/src/dock/dock.rs index 4fb4321c..a3b7b6d5 100644 --- a/crates/ui/src/dock/dock.rs +++ b/crates/ui/src/dock/dock.rs @@ -7,12 +7,13 @@ use gpui::{ MouseUpEvent, ParentElement as _, Pixels, Point, Render, Style, StyleRefinement, Styled as _, WeakEntity, Window, div, px, }; +use gpui_base::Side; use super::{DockArea, DockItem}; use crate::StyledExt; use crate::dock::panel::PanelView; use crate::dock::tab_panel::TabPanel; -use crate::resizable::{PANEL_MIN_SIZE, resize_handle}; +use crate::resizable::{PANEL_MIN_SIZE, resize_handle, resize_handle_appearance}; #[derive(Clone)] struct ResizePanel; @@ -51,6 +52,16 @@ impl DockPlacement { pub fn is_right(&self) -> bool { matches!(self, Self::Right) } + + /// Base positions the handle against the window edge for the left dock only, + /// so every other placement is the same to it as the right one. + fn side(&self) -> Side { + if self.is_left() { + Side::Left + } else { + Side::Right + } + } } /// The Dock is a fixed container that places at left, bottom, right of the Windows. @@ -242,7 +253,8 @@ impl Dock { let view = cx.entity().clone(); resize_handle("resize-handle", axis) - .placement(self.placement) + .placement(self.placement.side()) + .with_appearance(resize_handle_appearance()) .on_drag(ResizePanel {}, move |info, _, _, cx| { cx.stop_propagation(); view.update(cx, |view, _cx| { diff --git a/crates/ui/src/dock/stack_panel.rs b/crates/ui/src/dock/stack_panel.rs index fff92ec8..165f1f94 100644 --- a/crates/ui/src/dock/stack_panel.rs +++ b/crates/ui/src/dock/stack_panel.rs @@ -13,8 +13,8 @@ use crate::dock::panel::{Panel, PanelView}; use crate::dock::tab_panel::TabPanel; use crate::h_flex; use crate::resizable::{ - PANEL_MIN_SIZE, ResizablePanelEvent, ResizablePanelGroup, ResizablePanelState, ResizableState, - resizable_panel, + PANEL_MIN_SIZE, ResizablePanelEvent, ResizablePanelGroup, ResizableState, resizable_panel, + resize_handle_appearance, }; pub struct StackPanel { @@ -267,7 +267,7 @@ impl StackPanel { if let Some(ix) = self.index_of_panel(old_panel.clone()) { self.panels[ix] = Arc::new(new_panel.clone()); self.state.update(cx, |state, cx| { - state.replace_panel(ix, ResizablePanelState::default(), cx); + state.reset_panel(ix, cx); }); cx.emit(PanelEvent::LayoutChanged); } @@ -376,6 +376,7 @@ impl Render for StackPanel { h_flex().size_full().overflow_hidden().child( ResizablePanelGroup::new("stack-panel-group") .with_state(&self.state) + .with_handle_appearance(resize_handle_appearance()) .axis(self.axis) .children(self.panels.clone().into_iter().map(|panel| { resizable_panel() diff --git a/crates/ui/src/resizable/mod.rs b/crates/ui/src/resizable/mod.rs index 44567183..7c0f2f40 100644 --- a/crates/ui/src/resizable/mod.rs +++ b/crates/ui/src/resizable/mod.rs @@ -1,294 +1,37 @@ -use std::ops::Range; +use std::rc::Rc; -use gpui::{ - Along, App, Axis, Bounds, Context, ElementId, EventEmitter, IsZero, Pixels, Window, px, +use gpui::prelude::FluentBuilder as _; +use gpui::{App, InteractiveElement as _, IntoElement, Pixels, Styled as _, Window, div, px}; +pub(crate) use gpui_base::{PANEL_MIN_SIZE, resize_handle}; +pub use gpui_base::{ + ResizablePanel, ResizablePanelEvent, ResizablePanelGroup, ResizableState, h_resizable, + resizable_panel, v_resizable, }; +use gpui_base::{ResizeHandleContext, ResizeHandleRenderer}; +use theme::{ActiveTheme as _, AxisExt as _}; -mod panel; -mod resize_handle; -pub use panel::*; -pub(crate) use resize_handle::*; +const HANDLE_SIZE: Pixels = px(1.); -pub(crate) const PANEL_MIN_SIZE: Pixels = px(100.); +/// The coop divider: a 1px line that appears only while the handle is hovered +/// or dragged, tinted from the coop palette. Base's built-in line is always +/// painted and takes its colours from `ResizableTheme`. +pub(crate) fn resize_handle_appearance() -> ResizeHandleRenderer { + Rc::new( + |context: &ResizeHandleContext, _: &mut Window, cx: &mut App| { + let color = if context.is_active() { + cx.theme().border_selected + } else { + cx.theme().border + }; + let axis = context.axis(); -/// Create a [`ResizablePanelGroup`] with horizontal resizing -pub fn h_resizable(id: impl Into) -> ResizablePanelGroup { - ResizablePanelGroup::new(id).axis(Axis::Horizontal) -} - -/// Create a [`ResizablePanelGroup`] with vertical resizing -pub fn v_resizable(id: impl Into) -> ResizablePanelGroup { - ResizablePanelGroup::new(id).axis(Axis::Vertical) -} - -/// Create a [`ResizablePanel`]. -pub fn resizable_panel() -> ResizablePanel { - ResizablePanel::new() -} - -/// State for a [`ResizablePanel`] -#[derive(Debug, Clone)] -pub struct ResizableState { - /// The `axis` will sync to actual axis of the ResizablePanelGroup in use. - axis: Axis, - panels: Vec, - sizes: Vec, - pub(crate) resizing_panel_ix: Option, - bounds: Bounds, -} - -impl Default for ResizableState { - fn default() -> Self { - Self { - axis: Axis::Horizontal, - panels: vec![], - sizes: vec![], - resizing_panel_ix: None, - bounds: Bounds::default(), - } - } -} - -impl ResizableState { - /// Get the size of the panels. - pub fn sizes(&self) -> &Vec { - &self.sizes - } - - pub(crate) fn insert_panel( - &mut self, - size: Option, - ix: Option, - cx: &mut Context, - ) { - let panel_state = ResizablePanelState { - size, - ..Default::default() - }; - - let size = size.unwrap_or(PANEL_MIN_SIZE); - - // We make sure that the size always sums up to the container size - // by reducing the size of all other panels first. - let container_size = self.container_size().max(px(1.)); - let total_leftover_size = (container_size - size).max(px(1.)); - - for (i, panel) in self.panels.iter_mut().enumerate() { - let ratio = self.sizes[i] / container_size; - self.sizes[i] = total_leftover_size * ratio; - panel.size = Some(self.sizes[i]); - } - - if let Some(ix) = ix { - self.panels.insert(ix, panel_state); - self.sizes.insert(ix, size); - } else { - self.panels.push(panel_state); - self.sizes.push(size); - }; - - cx.notify(); - } - - pub(crate) fn sync_panels_count( - &mut self, - axis: Axis, - panels_count: usize, - cx: &mut Context, - ) { - let mut changed = self.axis != axis; - self.axis = axis; - - if panels_count > self.panels.len() { - let diff = panels_count - self.panels.len(); - self.panels - .extend(vec![ResizablePanelState::default(); diff]); - self.sizes.extend(vec![PANEL_MIN_SIZE; diff]); - changed = true; - } - - if panels_count < self.panels.len() { - self.panels.truncate(panels_count); - self.sizes.truncate(panels_count); - changed = true; - } - - if changed { - // We need to make sure the total size is in line with the container size. - self.adjust_to_container_size(cx); - } - } - - pub(crate) fn update_panel_size( - &mut self, - panel_ix: usize, - bounds: Bounds, - size_range: Range, - cx: &mut Context, - ) { - let size = bounds.size.along(self.axis); - // This check is only necessary to stop the very first panel from resizing on its own - // it needs to be passed when the panel is freshly created so we get the initial size, - // but its also fine when it sometimes passes later. - if self.sizes[panel_ix].to_f64() == PANEL_MIN_SIZE.to_f64() { - self.sizes[panel_ix] = size; - self.panels[panel_ix].size = Some(size); - } - self.panels[panel_ix].bounds = bounds; - self.panels[panel_ix].size_range = size_range; - cx.notify(); - } - - pub(crate) fn remove_panel(&mut self, panel_ix: usize, cx: &mut Context) { - self.panels.remove(panel_ix); - self.sizes.remove(panel_ix); - if let Some(resizing_panel_ix) = self.resizing_panel_ix - && resizing_panel_ix > panel_ix - { - self.resizing_panel_ix = Some(resizing_panel_ix - 1); - } - self.adjust_to_container_size(cx); - } - - pub(crate) fn replace_panel( - &mut self, - panel_ix: usize, - panel: ResizablePanelState, - cx: &mut Context, - ) { - let old_size = self.sizes[panel_ix]; - - self.panels[panel_ix] = panel; - self.sizes[panel_ix] = old_size; - self.adjust_to_container_size(cx); - } - - pub(crate) fn clear(&mut self) { - self.panels.clear(); - self.sizes.clear(); - } - - #[inline] - pub(crate) fn container_size(&self) -> Pixels { - self.bounds.size.along(self.axis) - } - - pub(crate) fn done_resizing(&mut self, cx: &mut Context) { - self.resizing_panel_ix = None; - cx.emit(ResizablePanelEvent::Resized); - } - - fn panel_size_range(&self, ix: usize) -> Range { - let Some(panel) = self.panels.get(ix) else { - return PANEL_MIN_SIZE..Pixels::MAX; - }; - - panel.size_range.clone() - } - - fn sync_real_panel_sizes(&mut self, _: &App) { - for (i, panel) in self.panels.iter().enumerate() { - self.sizes[i] = panel.bounds.size.along(self.axis); - } - } - - /// The `ix`` is the index of the panel to resize, - /// and the `size` is the new size for the panel. - fn resize_panel(&mut self, ix: usize, size: Pixels, _: &mut Window, cx: &mut Context) { - let old_sizes = self.sizes.clone(); - - let mut ix = ix; - // Only resize the left panels. - if ix >= old_sizes.len() - 1 { - return; - } - let container_size = self.container_size(); - self.sync_real_panel_sizes(cx); - - let move_changed = size - old_sizes[ix]; - if move_changed == px(0.) { - return; - } - - let size_range = self.panel_size_range(ix); - let new_size = size.clamp(size_range.start, size_range.end); - let is_expand = move_changed > px(0.); - - let main_ix = ix; - let mut new_sizes = old_sizes.clone(); - - if is_expand { - let mut changed = new_size - old_sizes[ix]; - new_sizes[ix] = new_size; - - while changed > px(0.) && ix < old_sizes.len() - 1 { - ix += 1; - let size_range = self.panel_size_range(ix); - let available_size = (new_sizes[ix] - size_range.start).max(px(0.)); - let to_reduce = changed.min(available_size); - new_sizes[ix] -= to_reduce; - changed -= to_reduce; - } - } else { - let mut changed = new_size - size; - new_sizes[ix] = new_size; - - while changed > px(0.) && ix > 0 { - ix -= 1; - let size_range = self.panel_size_range(ix); - let available_size = (new_sizes[ix] - size_range.start).max(px(0.)); - let to_reduce = changed.min(available_size); - changed -= to_reduce; - new_sizes[ix] -= to_reduce; - } - - new_sizes[main_ix + 1] += old_sizes[main_ix] - size - changed; - } - - let total_size: Pixels = new_sizes.iter().map(|s| s.to_f64()).sum::().into(); - - // If total size exceeds container size, adjust the main panel - if total_size > container_size { - let overflow = total_size - container_size; - new_sizes[main_ix] = (new_sizes[main_ix] - overflow).max(size_range.start); - } - - for (i, _) in old_sizes.iter().enumerate() { - let size = new_sizes[i]; - self.panels[i].size = Some(size); - } - self.sizes = new_sizes; - cx.notify(); - } - - /// Adjust panel sizes according to the container size. - /// - /// When the container size changes, the panels should take up the same percentage as they did before. - fn adjust_to_container_size(&mut self, cx: &mut Context) { - if self.container_size().is_zero() { - return; - } - - let container_size = self.container_size(); - let total_size = px(self.sizes.iter().map(f32::from).sum::()); - - for i in 0..self.panels.len() { - let size = self.sizes[i]; - let ratio = size / total_size; - let new_size = container_size * ratio; - - self.sizes[i] = new_size; - self.panels[i].size = Some(new_size); - } - cx.notify(); - } -} - -impl EventEmitter for ResizableState {} - -#[derive(Debug, Clone, Default)] -pub(crate) struct ResizablePanelState { - pub size: Option, - pub size_range: Range, - bounds: Bounds, + Some( + div() + .group_hover("handle", move |this| this.bg(color)) + .when(axis.is_horizontal(), |this| this.h_full().w(HANDLE_SIZE)) + .when(axis.is_vertical(), |this| this.w_full().h(HANDLE_SIZE)) + .into_any_element(), + ) + }, + ) } diff --git a/crates/ui/src/resizable/panel.rs b/crates/ui/src/resizable/panel.rs deleted file mode 100644 index 2d268d80..00000000 --- a/crates/ui/src/resizable/panel.rs +++ /dev/null @@ -1,408 +0,0 @@ -use std::ops::{Deref, Range}; -use std::rc::Rc; - -use gpui::prelude::FluentBuilder; -use gpui::{ - Along, AnyElement, App, AppContext, Axis, Bounds, Context, Element, ElementId, Empty, Entity, - EventEmitter, InteractiveElement as _, IntoElement, IsZero as _, MouseMoveEvent, MouseUpEvent, - ParentElement, Pixels, Render, RenderOnce, Style, Styled, Window, div, -}; -use theme::AxisExt; - -use super::{ResizableState, resizable_panel, resize_handle}; -use crate::resizable::PANEL_MIN_SIZE; -use crate::{ElementExt, h_flex, v_flex}; - -pub enum ResizablePanelEvent { - Resized, -} - -#[derive(Clone)] -pub(crate) struct DragPanel; -impl Render for DragPanel { - fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement { - Empty - } -} - -/// A group of resizable panels. -#[allow(clippy::type_complexity)] -#[derive(IntoElement)] -pub struct ResizablePanelGroup { - id: ElementId, - state: Option>, - axis: Axis, - size: Option, - children: Vec, - on_resize: Rc, &mut Window, &mut App)>, -} - -impl ResizablePanelGroup { - /// Create a new resizable panel group. - pub fn new(id: impl Into) -> Self { - Self { - id: id.into(), - axis: Axis::Horizontal, - children: vec![], - state: None, - size: None, - on_resize: Rc::new(|_, _, _| {}), - } - } - - /// Bind yourself to a resizable state entity. - /// - /// If not provided, it will handle its own state internally. - pub fn with_state(mut self, state: &Entity) -> Self { - self.state = Some(state.clone()); - self - } - - /// Set the axis of the resizable panel group, default is horizontal. - pub fn axis(mut self, axis: Axis) -> Self { - self.axis = axis; - self - } - - /// Add a panel to the group. - /// - /// - The `axis` will be set to the same axis as the group. - /// - The `initial_size` will be set to the average size of all panels if not provided. - /// - The `group` will be set to the group entity. - pub fn child(mut self, panel: impl Into) -> Self { - self.children.push(panel.into()); - self - } - - /// Add multiple panels to the group. - pub fn children(mut self, panels: impl IntoIterator) -> Self - where - I: Into, - { - self.children = panels.into_iter().map(|panel| panel.into()).collect(); - self - } - - /// Set size of the resizable panel group - /// - /// - When the axis is horizontal, the size is the height of the group. - /// - When the axis is vertical, the size is the width of the group. - pub fn size(mut self, size: Pixels) -> Self { - self.size = Some(size); - self - } - - /// Set the callback to be called when the panels are resized. - /// - /// ## Callback arguments - /// - /// - Entity: The state of the ResizablePanelGroup. - pub fn on_resize( - mut self, - on_resize: impl Fn(&Entity, &mut Window, &mut App) + 'static, - ) -> Self { - self.on_resize = Rc::new(on_resize); - self - } -} - -impl From for ResizablePanel -where - T: Into, -{ - fn from(value: T) -> Self { - resizable_panel().child(value.into()) - } -} - -impl From for ResizablePanel { - fn from(value: ResizablePanelGroup) -> Self { - resizable_panel().child(value) - } -} - -impl EventEmitter for ResizablePanelGroup {} - -impl RenderOnce for ResizablePanelGroup { - fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { - let state = self.state.unwrap_or( - window.use_keyed_state(self.id.clone(), cx, |_, _| ResizableState::default()), - ); - let container = if self.axis.is_horizontal() { - h_flex() - } else { - v_flex() - }; - - // Sync panels to the state - let panels_count = self.children.len(); - state.update(cx, |state, cx| { - state.sync_panels_count(self.axis, panels_count, cx); - }); - - container - .id(self.id) - .size_full() - .children( - self.children - .into_iter() - .enumerate() - .map(|(ix, mut panel)| { - panel.panel_ix = ix; - panel.axis = self.axis; - panel.state = Some(state.clone()); - panel - }), - ) - .on_prepaint({ - let state = state.clone(); - move |bounds, _, cx| { - state.update(cx, |state, cx| { - let size_changed = - state.bounds.size.along(self.axis) != bounds.size.along(self.axis); - - state.bounds = bounds; - - if size_changed { - state.adjust_to_container_size(cx); - } - }) - } - }) - .child(ResizePanelGroupElement { - state: state.clone(), - axis: self.axis, - on_resize: self.on_resize.clone(), - }) - } -} - -/// A resizable panel inside a [`ResizablePanelGroup`]. -#[derive(IntoElement)] -pub struct ResizablePanel { - axis: Axis, - panel_ix: usize, - state: Option>, - /// Initial size is the size that the panel has when it is created. - initial_size: Option, - /// size range limit of this panel. - size_range: Range, - children: Vec, - visible: bool, -} - -impl ResizablePanel { - /// Create a new resizable panel. - pub(super) fn new() -> Self { - Self { - panel_ix: 0, - initial_size: None, - state: None, - size_range: (PANEL_MIN_SIZE..Pixels::MAX), - axis: Axis::Horizontal, - children: vec![], - visible: true, - } - } - - /// Set the visibility of the panel, default is true. - pub fn visible(mut self, visible: bool) -> Self { - self.visible = visible; - self - } - - /// Set the initial size of the panel. - pub fn size(mut self, size: impl Into) -> Self { - self.initial_size = Some(size.into()); - self - } - - /// Set the size range to limit panel resize. - /// - /// Default is [`PANEL_MIN_SIZE`] to [`Pixels::MAX`]. - pub fn size_range(mut self, range: impl Into>) -> Self { - self.size_range = range.into(); - self - } -} - -impl ParentElement for ResizablePanel { - fn extend(&mut self, elements: impl IntoIterator) { - self.children.extend(elements); - } -} - -impl RenderOnce for ResizablePanel { - fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { - if !self.visible { - return div().id(("resizable-panel", self.panel_ix)); - } - - let state = self - .state - .expect("BUG: The `state` in ResizablePanel should be present."); - - let panel_state = state - .read(cx) - .panels - .get(self.panel_ix) - .expect("BUG: The `index` of ResizablePanel should be one of in `state`."); - - let size_range = self.size_range.clone(); - - div() - .id(("resizable-panel", self.panel_ix)) - .flex() - .flex_grow_1() - .size_full() - .relative() - .when(self.axis.is_vertical(), |this| { - this.min_h(size_range.start).max_h(size_range.end) - }) - .when(self.axis.is_horizontal(), |this| { - this.min_w(size_range.start).max_w(size_range.end) - }) - // 1. initial_size is None, to use auto size. - // 2. initial_size is Some and size is none, to use the initial size of the panel for first time render. - // 3. initial_size is Some and size is Some, use `size`. - .when(self.initial_size.is_none(), |this| this.flex_shrink_1()) - .when_some(self.initial_size, |this, initial_size| { - // The `self.size` is None, that mean the initial size for the panel, - // so we need set `flex_shrink_0` To let it keep the initial size. - this.when( - panel_state.size.is_none() && !initial_size.is_zero(), - |this| this.flex_none(), - ) - .flex_basis(initial_size) - }) - .map(|this| match panel_state.size { - Some(size) => this.flex_basis(size.min(size_range.end).max(size_range.start)), - None => this, - }) - .on_prepaint({ - let state = state.clone(); - move |bounds, _, cx| { - state.update(cx, |state, cx| { - state.update_panel_size(self.panel_ix, bounds, self.size_range, cx) - }) - } - }) - .children(self.children) - .when(self.panel_ix > 0, |this| { - let ix = self.panel_ix - 1; - this.child(resize_handle(("resizable-handle", ix), self.axis).on_drag( - DragPanel, - move |drag_panel, _, _, cx| { - cx.stop_propagation(); - // Set current resizing panel ix - state.update(cx, |state, _| { - state.resizing_panel_ix = Some(ix); - }); - cx.new(|_| drag_panel.deref().clone()) - }, - )) - }) - } -} - -#[allow(clippy::type_complexity)] -struct ResizePanelGroupElement { - state: Entity, - on_resize: Rc, &mut Window, &mut App)>, - axis: Axis, -} - -impl IntoElement for ResizePanelGroupElement { - type Element = Self; - - fn into_element(self) -> Self::Element { - self - } -} - -impl Element for ResizePanelGroupElement { - type PrepaintState = (); - type RequestLayoutState = (); - - fn id(&self) -> Option { - None - } - - fn source_location(&self) -> Option<&'static std::panic::Location<'static>> { - None - } - - fn request_layout( - &mut self, - _: Option<&gpui::GlobalElementId>, - _: Option<&gpui::InspectorElementId>, - window: &mut Window, - cx: &mut App, - ) -> (gpui::LayoutId, Self::RequestLayoutState) { - (window.request_layout(Style::default(), None, cx), ()) - } - - fn prepaint( - &mut self, - _: Option<&gpui::GlobalElementId>, - _: Option<&gpui::InspectorElementId>, - _: Bounds, - _: &mut Self::RequestLayoutState, - _window: &mut Window, - _cx: &mut App, - ) -> Self::PrepaintState { - } - - fn paint( - &mut self, - _: Option<&gpui::GlobalElementId>, - _: Option<&gpui::InspectorElementId>, - _: Bounds, - _: &mut Self::RequestLayoutState, - _: &mut Self::PrepaintState, - window: &mut Window, - cx: &mut App, - ) { - window.on_mouse_event({ - let state = self.state.clone(); - let axis = self.axis; - let current_ix = state.read(cx).resizing_panel_ix; - move |e: &MouseMoveEvent, phase, window, cx| { - if !phase.bubble() { - return; - } - let Some(ix) = current_ix else { return }; - - state.update(cx, |state, cx| { - let panel = state.panels.get(ix).expect("BUG: invalid panel index"); - - match axis { - Axis::Horizontal => { - state.resize_panel(ix, e.position.x - panel.bounds.left(), window, cx) - } - Axis::Vertical => { - state.resize_panel(ix, e.position.y - panel.bounds.top(), window, cx); - } - } - cx.notify(); - }) - } - }); - - // When any mouse up, stop dragging - window.on_mouse_event({ - let state = self.state.clone(); - let current_ix = state.read(cx).resizing_panel_ix; - let on_resize = self.on_resize.clone(); - move |_: &MouseUpEvent, phase, window, cx| { - if current_ix.is_none() { - return; - } - if phase.bubble() { - state.update(cx, |state, cx| state.done_resizing(cx)); - on_resize(&state, window, cx); - } - } - }) - } -} diff --git a/crates/ui/src/resizable/resize_handle.rs b/crates/ui/src/resizable/resize_handle.rs deleted file mode 100644 index 2cbd353f..00000000 --- a/crates/ui/src/resizable/resize_handle.rs +++ /dev/null @@ -1,225 +0,0 @@ -use std::cell::Cell; -use std::rc::Rc; - -use gpui::prelude::FluentBuilder as _; -use gpui::{ - AnyElement, App, Axis, Element, ElementId, Entity, GlobalElementId, InteractiveElement, - IntoElement, MouseDownEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render, - StatefulInteractiveElement, Styled as _, Window, div, px, -}; -use theme::{ActiveTheme, AxisExt}; - -use crate::dock::DockPlacement; - -pub(crate) const HANDLE_PADDING: Pixels = px(4.); -pub(crate) const HANDLE_SIZE: Pixels = px(1.); - -/// Create a resize handle for a resizable panel. -pub(crate) fn resize_handle( - id: impl Into, - axis: Axis, -) -> ResizeHandle { - ResizeHandle::new(id, axis) -} - -#[allow(clippy::type_complexity)] -pub(crate) struct ResizeHandle { - id: ElementId, - axis: Axis, - drag_value: Option>, - placement: Option, - on_drag: Option, &mut Window, &mut App) -> Entity>>, -} - -impl ResizeHandle { - fn new(id: impl Into, axis: Axis) -> Self { - let id = id.into(); - Self { - id: id.clone(), - on_drag: None, - drag_value: None, - placement: None, - axis, - } - } - - pub(crate) fn on_drag( - mut self, - value: T, - f: impl Fn(Rc, &Point, &mut Window, &mut App) -> Entity + 'static, - ) -> Self { - let value = Rc::new(value); - self.drag_value = Some(value.clone()); - self.on_drag = Some(Rc::new(move |p, window, cx| { - f(value.clone(), p, window, cx) - })); - self - } - - #[allow(dead_code)] - pub(crate) fn placement(mut self, placement: DockPlacement) -> Self { - self.placement = Some(placement); - self - } -} - -#[derive(Default, Debug, Clone)] -struct ResizeHandleState { - active: Cell, -} - -impl ResizeHandleState { - fn set_active(&self, active: bool) { - self.active.set(active); - } - - fn is_active(&self) -> bool { - self.active.get() - } -} - -impl IntoElement for ResizeHandle { - type Element = ResizeHandle; - - fn into_element(self) -> Self::Element { - self - } -} - -impl Element for ResizeHandle { - type PrepaintState = (); - type RequestLayoutState = AnyElement; - - fn id(&self) -> Option { - Some(self.id.clone()) - } - - fn source_location(&self) -> Option<&'static std::panic::Location<'static>> { - None - } - - fn request_layout( - &mut self, - id: Option<&GlobalElementId>, - _: Option<&gpui::InspectorElementId>, - window: &mut Window, - cx: &mut App, - ) -> (gpui::LayoutId, Self::RequestLayoutState) { - let neg_offset = -HANDLE_PADDING; - let axis = self.axis; - - window.with_element_state(id.unwrap(), |state, window| { - let state = state.unwrap_or(ResizeHandleState::default()); - - let bg_color = if state.is_active() { - cx.theme().border_selected - } else { - cx.theme().border - }; - - let mut ele = div() - .id(self.id.clone()) - .occlude() - .absolute() - .flex_shrink_0() - .group("handle") - .when_some(self.on_drag.clone(), |this, on_drag| { - this.on_drag( - self.drag_value.clone().unwrap(), - move |_, position, window, cx| on_drag(&position, window, cx), - ) - }) - .map(|this| match self.placement { - Some(DockPlacement::Left) => { - // Special for Left Dock - // 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) - .pl(HANDLE_PADDING) - } - _ => this - .when(axis.is_horizontal(), |this| { - this.cursor_col_resize() - .top_0() - .left(neg_offset) - .h_full() - .w(HANDLE_SIZE) - .px(HANDLE_PADDING) - }) - .when(axis.is_vertical(), |this| { - this.cursor_row_resize() - .top(neg_offset) - .left_0() - .w_full() - .h(HANDLE_SIZE) - .py(HANDLE_PADDING) - }), - }) - .child( - div() - .group_hover("handle", |this| this.bg(bg_color)) - .when(axis.is_horizontal(), |this| this.h_full().w(HANDLE_SIZE)) - .when(axis.is_vertical(), |this| this.w_full().h(HANDLE_SIZE)), - ) - .into_any_element(); - - let layout_id = ele.request_layout(window, cx); - - ((layout_id, ele), state) - }) - } - - fn prepaint( - &mut self, - _: Option<&GlobalElementId>, - _: Option<&gpui::InspectorElementId>, - _: gpui::Bounds, - request_layout: &mut Self::RequestLayoutState, - window: &mut Window, - cx: &mut App, - ) -> Self::PrepaintState { - request_layout.prepaint(window, cx); - } - - fn paint( - &mut self, - id: Option<&GlobalElementId>, - _: Option<&gpui::InspectorElementId>, - bounds: gpui::Bounds, - request_layout: &mut Self::RequestLayoutState, - _: &mut Self::PrepaintState, - window: &mut Window, - cx: &mut App, - ) { - request_layout.paint(window, cx); - - window.with_element_state(id.unwrap(), |state: Option, window| { - let state = state.unwrap_or_default(); - - window.on_mouse_event({ - let state = state.clone(); - move |ev: &MouseDownEvent, phase, window, _| { - if bounds.contains(&ev.position) && phase.bubble() { - state.set_active(true); - window.refresh(); - } - } - }); - - window.on_mouse_event({ - let state = state.clone(); - move |_: &MouseUpEvent, _, window, _| { - if state.is_active() { - state.set_active(false); - window.refresh(); - } - } - }); - - ((), state) - }); - } -}