Make panels independently resizable
This commit is contained in:
parent
5549669316
commit
214354b4da
8 changed files with 135 additions and 154 deletions
|
@ -108,7 +108,8 @@
|
||||||
"auto_update": true,
|
"auto_update": true,
|
||||||
// Git gutter behavior configuration.
|
// Git gutter behavior configuration.
|
||||||
"project_panel": {
|
"project_panel": {
|
||||||
"dock": "left"
|
"dock": "left",
|
||||||
|
"default_width": 240
|
||||||
},
|
},
|
||||||
"git": {
|
"git": {
|
||||||
// Control whether the git gutter is shown. May take 2 values:
|
// Control whether the git gutter is shown. May take 2 values:
|
||||||
|
|
|
@ -187,25 +187,21 @@ pub trait Element<V: View>: 'static {
|
||||||
Tooltip::new::<Tag, V>(id, text, action, style, self.into_any(), cx)
|
Tooltip::new::<Tag, V>(id, text, action, style, self.into_any(), cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_resize_handle<Tag: 'static>(
|
fn resizable(
|
||||||
self,
|
self,
|
||||||
element_id: usize,
|
side: HandleSide,
|
||||||
side: Side,
|
size: f32,
|
||||||
handle_size: f32,
|
on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext<V>),
|
||||||
initial_size: f32,
|
|
||||||
cx: &mut ViewContext<V>,
|
|
||||||
) -> Resizable<V>
|
) -> Resizable<V>
|
||||||
where
|
where
|
||||||
Self: 'static + Sized,
|
Self: 'static + Sized,
|
||||||
{
|
{
|
||||||
Resizable::new::<Tag, V>(
|
Resizable::new(
|
||||||
self.into_any(),
|
self.into_any(),
|
||||||
element_id,
|
|
||||||
side,
|
side,
|
||||||
handle_size,
|
size,
|
||||||
initial_size,
|
on_resize
|
||||||
cx,
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{cell::Cell, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
use pathfinder_geometry::vector::{vec2f, Vector2F};
|
use pathfinder_geometry::vector::{vec2f, Vector2F};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
@ -7,25 +7,23 @@ use crate::{
|
||||||
geometry::rect::RectF,
|
geometry::rect::RectF,
|
||||||
platform::{CursorStyle, MouseButton},
|
platform::{CursorStyle, MouseButton},
|
||||||
scene::MouseDrag,
|
scene::MouseDrag,
|
||||||
AnyElement, Axis, Element, ElementStateHandle, LayoutContext, MouseRegion, SceneBuilder, View,
|
AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, View,
|
||||||
ViewContext,
|
ViewContext, SizeConstraint,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{ConstrainedBox, Hook};
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum Side {
|
pub enum HandleSide {
|
||||||
Top,
|
Top,
|
||||||
Bottom,
|
Bottom,
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Side {
|
impl HandleSide {
|
||||||
fn axis(&self) -> Axis {
|
fn axis(&self) -> Axis {
|
||||||
match self {
|
match self {
|
||||||
Side::Left | Side::Right => Axis::Horizontal,
|
HandleSide::Left | HandleSide::Right => Axis::Horizontal,
|
||||||
Side::Top | Side::Bottom => Axis::Vertical,
|
HandleSide::Top | HandleSide::Bottom => Axis::Vertical,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,8 +31,8 @@ impl Side {
|
||||||
/// then top-to-bottom
|
/// then top-to-bottom
|
||||||
fn before_content(self) -> bool {
|
fn before_content(self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Side::Left | Side::Top => true,
|
HandleSide::Left | HandleSide::Top => true,
|
||||||
Side::Right | Side::Bottom => false,
|
HandleSide::Right | HandleSide::Bottom => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,14 +53,14 @@ impl Side {
|
||||||
|
|
||||||
fn of_rect(&self, bounds: RectF, handle_size: f32) -> RectF {
|
fn of_rect(&self, bounds: RectF, handle_size: f32) -> RectF {
|
||||||
match self {
|
match self {
|
||||||
Side::Top => RectF::new(bounds.origin(), vec2f(bounds.width(), handle_size)),
|
HandleSide::Top => RectF::new(bounds.origin(), vec2f(bounds.width(), handle_size)),
|
||||||
Side::Left => RectF::new(bounds.origin(), vec2f(handle_size, bounds.height())),
|
HandleSide::Left => RectF::new(bounds.origin(), vec2f(handle_size, bounds.height())),
|
||||||
Side::Bottom => {
|
HandleSide::Bottom => {
|
||||||
let mut origin = bounds.lower_left();
|
let mut origin = bounds.lower_left();
|
||||||
origin.set_y(origin.y() - handle_size);
|
origin.set_y(origin.y() - handle_size);
|
||||||
RectF::new(origin, vec2f(bounds.width(), handle_size))
|
RectF::new(origin, vec2f(bounds.width(), handle_size))
|
||||||
}
|
}
|
||||||
Side::Right => {
|
HandleSide::Right => {
|
||||||
let mut origin = bounds.upper_right();
|
let mut origin = bounds.upper_right();
|
||||||
origin.set_x(origin.x() - handle_size);
|
origin.set_x(origin.x() - handle_size);
|
||||||
RectF::new(origin, vec2f(handle_size, bounds.height()))
|
RectF::new(origin, vec2f(handle_size, bounds.height()))
|
||||||
|
@ -71,69 +69,44 @@ impl Side {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ResizeHandleState {
|
pub struct Resizable<V: View> {
|
||||||
actual_dimension: Cell<f32>,
|
child: AnyElement<V>,
|
||||||
custom_dimension: Cell<f32>,
|
handle_side: HandleSide,
|
||||||
|
handle_size: f32,
|
||||||
|
on_resize: Rc<RefCell<dyn FnMut(&mut V, f32, &mut ViewContext<V>)>>
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Resizable<V: View> {
|
const DEFAULT_HANDLE_SIZE: f32 = 4.0;
|
||||||
side: Side,
|
|
||||||
handle_size: f32,
|
|
||||||
child: AnyElement<V>,
|
|
||||||
state: Rc<ResizeHandleState>,
|
|
||||||
_state_handle: ElementStateHandle<Rc<ResizeHandleState>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: View> Resizable<V> {
|
impl<V: View> Resizable<V> {
|
||||||
pub fn new<Tag: 'static, T: View>(
|
pub fn new(
|
||||||
child: AnyElement<V>,
|
child: AnyElement<V>,
|
||||||
element_id: usize,
|
handle_side: HandleSide,
|
||||||
side: Side,
|
size: f32,
|
||||||
handle_size: f32,
|
on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext<V>)
|
||||||
initial_size: f32,
|
|
||||||
cx: &mut ViewContext<V>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let state_handle = cx.element_state::<Tag, Rc<ResizeHandleState>>(
|
let child = match handle_side.axis() {
|
||||||
element_id,
|
Axis::Horizontal => child.constrained().with_max_width(size),
|
||||||
Rc::new(ResizeHandleState {
|
Axis::Vertical => child.constrained().with_max_height(size),
|
||||||
actual_dimension: Cell::new(initial_size),
|
}
|
||||||
custom_dimension: Cell::new(initial_size),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
let state = state_handle.read(cx).clone();
|
|
||||||
|
|
||||||
let child = Hook::new({
|
|
||||||
let constrained = ConstrainedBox::new(child);
|
|
||||||
match side.axis() {
|
|
||||||
Axis::Horizontal => constrained.with_max_width(state.custom_dimension.get()),
|
|
||||||
Axis::Vertical => constrained.with_max_height(state.custom_dimension.get()),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on_after_layout({
|
|
||||||
let state = state.clone();
|
|
||||||
move |size, _| {
|
|
||||||
state.actual_dimension.set(side.relevant_component(size));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.into_any();
|
.into_any();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
side,
|
|
||||||
child,
|
child,
|
||||||
handle_size,
|
handle_side,
|
||||||
state,
|
handle_size: DEFAULT_HANDLE_SIZE,
|
||||||
_state_handle: state_handle,
|
on_resize: Rc::new(RefCell::new(on_resize)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn current_size(&self) -> f32 {
|
pub fn with_handle_size(mut self, handle_size: f32) -> Self {
|
||||||
self.state.actual_dimension.get()
|
self.handle_size = handle_size;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: View> Element<V> for Resizable<V> {
|
impl<V: View> Element<V> for Resizable<V> {
|
||||||
type LayoutState = ();
|
type LayoutState = SizeConstraint;
|
||||||
type PaintState = ();
|
type PaintState = ();
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
|
@ -142,7 +115,7 @@ impl<V: View> Element<V> for Resizable<V> {
|
||||||
view: &mut V,
|
view: &mut V,
|
||||||
cx: &mut LayoutContext<V>,
|
cx: &mut LayoutContext<V>,
|
||||||
) -> (Vector2F, Self::LayoutState) {
|
) -> (Vector2F, Self::LayoutState) {
|
||||||
(self.child.layout(constraint, view, cx), ())
|
(self.child.layout(constraint, view, cx), constraint)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
|
@ -150,34 +123,37 @@ impl<V: View> Element<V> for Resizable<V> {
|
||||||
scene: &mut SceneBuilder,
|
scene: &mut SceneBuilder,
|
||||||
bounds: pathfinder_geometry::rect::RectF,
|
bounds: pathfinder_geometry::rect::RectF,
|
||||||
visible_bounds: pathfinder_geometry::rect::RectF,
|
visible_bounds: pathfinder_geometry::rect::RectF,
|
||||||
_child_size: &mut Self::LayoutState,
|
constraint: &mut SizeConstraint,
|
||||||
view: &mut V,
|
view: &mut V,
|
||||||
cx: &mut ViewContext<V>,
|
cx: &mut ViewContext<V>,
|
||||||
) -> Self::PaintState {
|
) -> Self::PaintState {
|
||||||
scene.push_stacking_context(None, None);
|
scene.push_stacking_context(None, None);
|
||||||
|
|
||||||
let handle_region = self.side.of_rect(bounds, self.handle_size);
|
let handle_region = self.handle_side.of_rect(bounds, self.handle_size);
|
||||||
|
|
||||||
enum ResizeHandle {}
|
enum ResizeHandle {}
|
||||||
scene.push_mouse_region(
|
scene.push_mouse_region(
|
||||||
MouseRegion::new::<ResizeHandle>(cx.view_id(), self.side as usize, handle_region)
|
MouseRegion::new::<ResizeHandle>(cx.view_id(), self.handle_side as usize, handle_region)
|
||||||
.on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
|
.on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
|
||||||
.on_drag(MouseButton::Left, {
|
.on_drag(MouseButton::Left, {
|
||||||
let state = self.state.clone();
|
let bounds = bounds.clone();
|
||||||
let side = self.side;
|
let side = self.handle_side;
|
||||||
move |e, _: &mut V, cx| {
|
let prev_size = side.relevant_component(bounds.size());
|
||||||
let prev_width = state.actual_dimension.get();
|
let min_size = side.relevant_component(constraint.min);
|
||||||
state
|
let max_size = side.relevant_component(constraint.max);
|
||||||
.custom_dimension
|
let on_resize = self.on_resize.clone();
|
||||||
.set(0f32.max(prev_width + side.compute_delta(e)).round());
|
move |event, view: &mut V, cx| {
|
||||||
cx.notify();
|
let new_size = min_size.max(prev_size + side.compute_delta(event)).min(max_size).round();
|
||||||
|
if new_size != prev_size {
|
||||||
|
on_resize.borrow_mut()(view, new_size, cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
scene.push_cursor_region(crate::CursorRegion {
|
scene.push_cursor_region(crate::CursorRegion {
|
||||||
bounds: handle_region,
|
bounds: handle_region,
|
||||||
style: match self.side.axis() {
|
style: match self.handle_side.axis() {
|
||||||
Axis::Horizontal => CursorStyle::ResizeLeftRight,
|
Axis::Horizontal => CursorStyle::ResizeLeftRight,
|
||||||
Axis::Vertical => CursorStyle::ResizeUpDown,
|
Axis::Vertical => CursorStyle::ResizeUpDown,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1347,12 +1347,9 @@ impl Entity for ProjectPanel {
|
||||||
impl workspace::dock::Panel for ProjectPanel {
|
impl workspace::dock::Panel for ProjectPanel {
|
||||||
fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
|
fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
|
||||||
let settings = cx.global::<Settings>();
|
let settings = cx.global::<Settings>();
|
||||||
let dock = settings
|
match settings
|
||||||
.project_panel_overrides
|
.project_panel
|
||||||
.dock
|
.dock {
|
||||||
.or(settings.project_panel_defaults.dock)
|
|
||||||
.unwrap();
|
|
||||||
match dock {
|
|
||||||
settings::ProjectPanelDockPosition::Left => DockPosition::Left,
|
settings::ProjectPanelDockPosition::Left => DockPosition::Left,
|
||||||
settings::ProjectPanelDockPosition::Right => DockPosition::Right,
|
settings::ProjectPanelDockPosition::Right => DockPosition::Right,
|
||||||
}
|
}
|
||||||
|
@ -1374,6 +1371,10 @@ impl workspace::dock::Panel for ProjectPanel {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_size(&self, cx: &gpui::WindowContext) -> f32 {
|
||||||
|
cx.global::<Settings>().project_panel.default_width
|
||||||
|
}
|
||||||
|
|
||||||
fn icon_path(&self) -> &'static str {
|
fn icon_path(&self) -> &'static str {
|
||||||
"icons/folder_tree_16.svg"
|
"icons/folder_tree_16.svg"
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,8 +44,7 @@ pub struct Settings {
|
||||||
pub show_call_status_icon: bool,
|
pub show_call_status_icon: bool,
|
||||||
pub vim_mode: bool,
|
pub vim_mode: bool,
|
||||||
pub autosave: Autosave,
|
pub autosave: Autosave,
|
||||||
pub project_panel_defaults: ProjectPanelSettings,
|
pub project_panel: ProjectPanelSettings,
|
||||||
pub project_panel_overrides: ProjectPanelSettings,
|
|
||||||
pub editor_defaults: EditorSettings,
|
pub editor_defaults: EditorSettings,
|
||||||
pub editor_overrides: EditorSettings,
|
pub editor_overrides: EditorSettings,
|
||||||
pub git: GitSettings,
|
pub git: GitSettings,
|
||||||
|
@ -158,9 +157,15 @@ pub enum GitGutter {
|
||||||
|
|
||||||
pub struct GitGutterConfig {}
|
pub struct GitGutterConfig {}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct ProjectPanelSettings {
|
pub struct ProjectPanelSettings {
|
||||||
|
pub dock: ProjectPanelDockPosition,
|
||||||
|
pub default_width: f32,
|
||||||
|
}
|
||||||
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||||
|
pub struct ProjectPanelSettingsContent {
|
||||||
pub dock: Option<ProjectPanelDockPosition>,
|
pub dock: Option<ProjectPanelDockPosition>,
|
||||||
|
pub default_width: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
|
@ -253,6 +258,8 @@ impl Default for HourFormat {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct TerminalSettings {
|
pub struct TerminalSettings {
|
||||||
|
pub default_width: Option<f32>,
|
||||||
|
pub default_height: Option<f32>,
|
||||||
pub shell: Option<Shell>,
|
pub shell: Option<Shell>,
|
||||||
pub working_directory: Option<WorkingDirectory>,
|
pub working_directory: Option<WorkingDirectory>,
|
||||||
pub font_size: Option<f32>,
|
pub font_size: Option<f32>,
|
||||||
|
@ -387,7 +394,7 @@ pub struct SettingsFileContent {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub editor: EditorSettings,
|
pub editor: EditorSettings,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub project_panel: ProjectPanelSettings,
|
pub project_panel: ProjectPanelSettingsContent,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub journal: JournalSettings,
|
pub journal: JournalSettings,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -423,7 +430,6 @@ pub struct Features {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub struct FeaturesContent {
|
pub struct FeaturesContent {
|
||||||
pub copilot: Option<bool>,
|
pub copilot: Option<bool>,
|
||||||
}
|
}
|
||||||
|
@ -482,8 +488,10 @@ impl Settings {
|
||||||
show_call_status_icon: defaults.show_call_status_icon.unwrap(),
|
show_call_status_icon: defaults.show_call_status_icon.unwrap(),
|
||||||
vim_mode: defaults.vim_mode.unwrap(),
|
vim_mode: defaults.vim_mode.unwrap(),
|
||||||
autosave: defaults.autosave.unwrap(),
|
autosave: defaults.autosave.unwrap(),
|
||||||
project_panel_defaults: defaults.project_panel,
|
project_panel: ProjectPanelSettings {
|
||||||
project_panel_overrides: Default::default(),
|
dock: defaults.project_panel.dock.unwrap(),
|
||||||
|
default_width: defaults.project_panel.default_width.unwrap(),
|
||||||
|
},
|
||||||
editor_defaults: EditorSettings {
|
editor_defaults: EditorSettings {
|
||||||
tab_size: required(defaults.editor.tab_size),
|
tab_size: required(defaults.editor.tab_size),
|
||||||
hard_tabs: required(defaults.editor.hard_tabs),
|
hard_tabs: required(defaults.editor.hard_tabs),
|
||||||
|
@ -590,7 +598,8 @@ impl Settings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.editor_overrides = data.editor;
|
self.editor_overrides = data.editor;
|
||||||
self.project_panel_overrides = data.project_panel;
|
merge(&mut self.project_panel.dock, data.project_panel.dock);
|
||||||
|
merge(&mut self.project_panel.default_width, data.project_panel.default_width);
|
||||||
self.git_overrides = data.git.unwrap_or_default();
|
self.git_overrides = data.git.unwrap_or_default();
|
||||||
self.journal_overrides = data.journal;
|
self.journal_overrides = data.journal;
|
||||||
self.terminal_defaults.font_size = data.terminal.font_size;
|
self.terminal_defaults.font_size = data.terminal.font_size;
|
||||||
|
@ -778,8 +787,10 @@ impl Settings {
|
||||||
show_call_status_icon: true,
|
show_call_status_icon: true,
|
||||||
vim_mode: false,
|
vim_mode: false,
|
||||||
autosave: Autosave::Off,
|
autosave: Autosave::Off,
|
||||||
project_panel_defaults: Default::default(),
|
project_panel: ProjectPanelSettings {
|
||||||
project_panel_overrides: Default::default(),
|
dock: ProjectPanelDockPosition::Left,
|
||||||
|
default_width: 240.,
|
||||||
|
},
|
||||||
editor_defaults: EditorSettings {
|
editor_defaults: EditorSettings {
|
||||||
tab_size: Some(4.try_into().unwrap()),
|
tab_size: Some(4.try_into().unwrap()),
|
||||||
hard_tabs: Some(false),
|
hard_tabs: Some(false),
|
||||||
|
|
|
@ -179,6 +179,14 @@ impl Panel for TerminalPanel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_size(&self, cx: &gpui::WindowContext) -> f32 {
|
||||||
|
let settings = &cx.global::<Settings>().terminal_overrides;
|
||||||
|
match self.position(cx) {
|
||||||
|
DockPosition::Left | DockPosition::Right => settings.default_width.unwrap_or(640.),
|
||||||
|
DockPosition::Bottom => settings.default_height.unwrap_or(320.),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn icon_path(&self) -> &'static str {
|
fn icon_path(&self) -> &'static str {
|
||||||
"icons/terminal_12.svg"
|
"icons/terminal_12.svg"
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ pub trait Panel: View {
|
||||||
fn position(&self, cx: &WindowContext) -> DockPosition;
|
fn position(&self, cx: &WindowContext) -> DockPosition;
|
||||||
fn position_is_valid(&self, position: DockPosition) -> bool;
|
fn position_is_valid(&self, position: DockPosition) -> bool;
|
||||||
fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>);
|
fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>);
|
||||||
|
fn default_size(&self, cx: &WindowContext) -> f32;
|
||||||
fn icon_path(&self) -> &'static str;
|
fn icon_path(&self) -> &'static str;
|
||||||
fn icon_tooltip(&self) -> String;
|
fn icon_tooltip(&self) -> String;
|
||||||
fn icon_label(&self, _: &AppContext) -> Option<String> {
|
fn icon_label(&self, _: &AppContext) -> Option<String> {
|
||||||
|
@ -27,6 +28,7 @@ pub trait PanelHandle {
|
||||||
fn position(&self, cx: &WindowContext) -> DockPosition;
|
fn position(&self, cx: &WindowContext) -> DockPosition;
|
||||||
fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool;
|
fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool;
|
||||||
fn set_position(&self, position: DockPosition, cx: &mut WindowContext);
|
fn set_position(&self, position: DockPosition, cx: &mut WindowContext);
|
||||||
|
fn default_size(&self, cx: &WindowContext) -> f32;
|
||||||
fn icon_path(&self, cx: &WindowContext) -> &'static str;
|
fn icon_path(&self, cx: &WindowContext) -> &'static str;
|
||||||
fn icon_tooltip(&self, cx: &WindowContext) -> String;
|
fn icon_tooltip(&self, cx: &WindowContext) -> String;
|
||||||
fn icon_label(&self, cx: &WindowContext) -> Option<String>;
|
fn icon_label(&self, cx: &WindowContext) -> Option<String>;
|
||||||
|
@ -54,6 +56,10 @@ where
|
||||||
self.update(cx, |this, cx| this.set_position(position, cx))
|
self.update(cx, |this, cx| this.set_position(position, cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_size(&self, cx: &WindowContext) -> f32 {
|
||||||
|
self.read(cx).default_size(cx)
|
||||||
|
}
|
||||||
|
|
||||||
fn icon_path(&self, cx: &WindowContext) -> &'static str {
|
fn icon_path(&self, cx: &WindowContext) -> &'static str {
|
||||||
self.read(cx).icon_path()
|
self.read(cx).icon_path()
|
||||||
}
|
}
|
||||||
|
@ -104,17 +110,18 @@ impl DockPosition {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_resizable_side(self) -> Side {
|
fn to_resize_handle_side(self) -> HandleSide {
|
||||||
match self {
|
match self {
|
||||||
Self::Left => Side::Right,
|
Self::Left => HandleSide::Right,
|
||||||
Self::Bottom => Side::Top,
|
Self::Bottom => HandleSide::Top,
|
||||||
Self::Right => Side::Left,
|
Self::Right => HandleSide::Left,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PanelEntry {
|
struct PanelEntry {
|
||||||
panel: Rc<dyn PanelHandle>,
|
panel: Rc<dyn PanelHandle>,
|
||||||
|
size: f32,
|
||||||
context_menu: ViewHandle<ContextMenu>,
|
context_menu: ViewHandle<ContextMenu>,
|
||||||
_subscriptions: [Subscription; 2],
|
_subscriptions: [Subscription; 2],
|
||||||
}
|
}
|
||||||
|
@ -181,8 +188,10 @@ impl Dock {
|
||||||
];
|
];
|
||||||
|
|
||||||
let dock_view_id = cx.view_id();
|
let dock_view_id = cx.view_id();
|
||||||
|
let size = panel.default_size(cx);
|
||||||
self.panel_entries.push(PanelEntry {
|
self.panel_entries.push(PanelEntry {
|
||||||
panel: Rc::new(panel),
|
panel: Rc::new(panel),
|
||||||
|
size,
|
||||||
context_menu: cx.add_view(|cx| {
|
context_menu: cx.add_view(|cx| {
|
||||||
let mut menu = ContextMenu::new(dock_view_id, cx);
|
let mut menu = ContextMenu::new(dock_view_id, cx);
|
||||||
menu.set_position_mode(OverlayPositionMode::Local);
|
menu.set_position_mode(OverlayPositionMode::Local);
|
||||||
|
@ -237,6 +246,23 @@ impl Dock {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn active_panel_size(&self) -> Option<f32> {
|
||||||
|
if self.is_open {
|
||||||
|
self.panel_entries
|
||||||
|
.get(self.active_panel_index)
|
||||||
|
.map(|entry| entry.size)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resize_active_panel(&mut self, size: f32, cx: &mut ViewContext<Self>) {
|
||||||
|
if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) {
|
||||||
|
entry.size = size;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for Dock {
|
impl Entity for Dock {
|
||||||
|
@ -250,18 +276,14 @@ impl View for Dock {
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||||
if let Some(active_panel) = self.active_panel() {
|
if let Some(active_panel) = self.active_panel() {
|
||||||
enum ResizeHandleTag {}
|
let size = self.active_panel_size().unwrap();
|
||||||
let style = &cx.global::<Settings>().theme.workspace.dock;
|
let style = &cx.global::<Settings>().theme.workspace.dock;
|
||||||
ChildView::new(active_panel.as_any(), cx)
|
ChildView::new(active_panel.as_any(), cx)
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(style.container)
|
.with_style(style.container)
|
||||||
.with_resize_handle::<ResizeHandleTag>(
|
.resizable(self.position.to_resize_handle_side(), size, |dock: &mut Self, size, cx| {
|
||||||
self.position as usize,
|
dock.resize_active_panel(size, cx);
|
||||||
self.position.to_resizable_side(),
|
})
|
||||||
4.,
|
|
||||||
style.initial_size,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.into_any()
|
.into_any()
|
||||||
} else {
|
} else {
|
||||||
Empty::new().into_any()
|
Empty::new().into_any()
|
||||||
|
@ -464,6 +486,10 @@ pub(crate) mod test {
|
||||||
cx.emit(TestPanelEvent::PositionChanged);
|
cx.emit(TestPanelEvent::PositionChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_size(&self, _: &WindowContext) -> f32 {
|
||||||
|
300.
|
||||||
|
}
|
||||||
|
|
||||||
fn icon_path(&self) -> &'static str {
|
fn icon_path(&self) -> &'static str {
|
||||||
"icons/test_panel.svg"
|
"icons/test_panel.svg"
|
||||||
}
|
}
|
||||||
|
|
|
@ -766,7 +766,7 @@ impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod test {
|
pub(crate) mod test {
|
||||||
use super::{Item, ItemEvent};
|
use super::{Item, ItemEvent};
|
||||||
use crate::{dock::Panel, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
|
use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
elements::Empty, AnyElement, AppContext, Element, Entity, ModelHandle, Task, View,
|
elements::Empty, AnyElement, AppContext, Element, Entity, ModelHandle, Task, View,
|
||||||
ViewContext, ViewHandle, WeakViewHandle,
|
ViewContext, ViewHandle, WeakViewHandle,
|
||||||
|
@ -1059,42 +1059,4 @@ pub(crate) mod test {
|
||||||
Task::Ready(Some(anyhow::Ok(view)))
|
Task::Ready(Some(anyhow::Ok(view)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Panel for TestItem {
|
|
||||||
fn position(&self, _cx: &gpui::WindowContext) -> crate::dock::DockPosition {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn position_is_valid(&self, _position: crate::dock::DockPosition) -> bool {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_position(
|
|
||||||
&mut self,
|
|
||||||
_position: crate::dock::DockPosition,
|
|
||||||
_cx: &mut ViewContext<Self>,
|
|
||||||
) {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn icon_path(&self) -> &'static str {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn icon_tooltip(&self) -> String {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn should_change_position_on_event(_: &Self::Event) -> bool {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue