Prevent panels from getting so small they can't be resized (#3788)

Antonio Scandurra created

This also switches to using `Pixels` instead of `f32` to store the panel
size everywhere.

Release Notes:

- N/A

Change summary

crates/assistant2/src/assistant_panel.rs            |  8 +-
crates/assistant2/src/assistant_settings.rs         |  5 
crates/collab_ui2/src/chat_panel.rs                 |  8 +-
crates/collab_ui2/src/collab_panel.rs               | 12 +--
crates/collab_ui2/src/notification_panel.rs         |  8 +-
crates/collab_ui2/src/panel_settings.rs             |  7 +
crates/project_panel2/src/project_panel.rs          | 20 ++---
crates/project_panel2/src/project_panel_settings.rs |  3 
crates/terminal2/src/terminal_settings.rs           |  4 
crates/terminal_view2/src/terminal_panel.rs         | 14 ++--
crates/workspace2/src/dock.rs                       | 47 +++++++-------
crates/workspace2/src/workspace2.rs                 |  6 
12 files changed, 70 insertions(+), 72 deletions(-)

Detailed changes

crates/assistant2/src/assistant_panel.rs 🔗

@@ -84,8 +84,8 @@ pub fn init(cx: &mut AppContext) {
 
 pub struct AssistantPanel {
     workspace: WeakView<Workspace>,
-    width: Option<f32>,
-    height: Option<f32>,
+    width: Option<Pixels>,
+    height: Option<Pixels>,
     active_editor_index: Option<usize>,
     prev_active_editor_index: Option<usize>,
     editors: Vec<View<ConversationEditor>>,
@@ -1242,7 +1242,7 @@ impl Panel for AssistantPanel {
         });
     }
 
-    fn size(&self, cx: &WindowContext) -> f32 {
+    fn size(&self, cx: &WindowContext) -> Pixels {
         let settings = AssistantSettings::get_global(cx);
         match self.position(cx) {
             DockPosition::Left | DockPosition::Right => {
@@ -1252,7 +1252,7 @@ impl Panel for AssistantPanel {
         }
     }
 
-    fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
+    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
         match self.position(cx) {
             DockPosition::Left | DockPosition::Right => self.width = size,
             DockPosition::Bottom => self.height = size,

crates/assistant2/src/assistant_settings.rs 🔗

@@ -1,4 +1,5 @@
 use anyhow;
+use gpui::Pixels;
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 use settings::Settings;
@@ -51,8 +52,8 @@ pub enum AssistantDockPosition {
 pub struct AssistantSettings {
     pub button: bool,
     pub dock: AssistantDockPosition,
-    pub default_width: f32,
-    pub default_height: f32,
+    pub default_width: Pixels,
+    pub default_height: Pixels,
     pub default_open_ai_model: OpenAIModel,
 }
 

crates/collab_ui2/src/chat_panel.rs 🔗

@@ -51,7 +51,7 @@ pub struct ChatPanel {
     input_editor: View<MessageEditor>,
     local_timezone: UtcOffset,
     fs: Arc<dyn Fs>,
-    width: Option<f32>,
+    width: Option<Pixels>,
     active: bool,
     pending_serialization: Task<Option<()>>,
     subscriptions: Vec<gpui::Subscription>,
@@ -62,7 +62,7 @@ pub struct ChatPanel {
 
 #[derive(Serialize, Deserialize)]
 struct SerializedChatPanel {
-    width: Option<f32>,
+    width: Option<Pixels>,
 }
 
 #[derive(Debug)]
@@ -584,12 +584,12 @@ impl Panel for ChatPanel {
         });
     }
 
-    fn size(&self, cx: &gpui::WindowContext) -> f32 {
+    fn size(&self, cx: &gpui::WindowContext) -> Pixels {
         self.width
             .unwrap_or_else(|| ChatPanelSettings::get_global(cx).default_width)
     }
 
-    fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
+    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
         self.width = size;
         self.serialize(cx);
         cx.notify();

crates/collab_ui2/src/collab_panel.rs 🔗

@@ -2314,15 +2314,13 @@ impl Panel for CollabPanel {
         );
     }
 
-    fn size(&self, cx: &gpui::WindowContext) -> f32 {
-        self.width.map_or_else(
-            || CollaborationPanelSettings::get_global(cx).default_width,
-            |width| width.0,
-        )
+    fn size(&self, cx: &gpui::WindowContext) -> Pixels {
+        self.width
+            .unwrap_or_else(|| CollaborationPanelSettings::get_global(cx).default_width)
     }
 
-    fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
-        self.width = size.map(|s| px(s));
+    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
+        self.width = size;
         self.serialize(cx);
         cx.notify();
     }

crates/collab_ui2/src/notification_panel.rs 🔗

@@ -37,7 +37,7 @@ pub struct NotificationPanel {
     channel_store: Model<ChannelStore>,
     notification_store: Model<NotificationStore>,
     fs: Arc<dyn Fs>,
-    width: Option<f32>,
+    width: Option<Pixels>,
     active: bool,
     notification_list: ListState,
     pending_serialization: Task<Option<()>>,
@@ -51,7 +51,7 @@ pub struct NotificationPanel {
 
 #[derive(Serialize, Deserialize)]
 struct SerializedNotificationPanel {
-    width: Option<f32>,
+    width: Option<Pixels>,
 }
 
 #[derive(Debug)]
@@ -639,12 +639,12 @@ impl Panel for NotificationPanel {
         );
     }
 
-    fn size(&self, cx: &gpui::WindowContext) -> f32 {
+    fn size(&self, cx: &gpui::WindowContext) -> Pixels {
         self.width
             .unwrap_or_else(|| NotificationPanelSettings::get_global(cx).default_width)
     }
 
-    fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
+    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
         self.width = size;
         self.serialize(cx);
         cx.notify();

crates/collab_ui2/src/panel_settings.rs 🔗

@@ -1,4 +1,5 @@
 use anyhow;
+use gpui::Pixels;
 use schemars::JsonSchema;
 use serde_derive::{Deserialize, Serialize};
 use settings::Settings;
@@ -8,21 +9,21 @@ use workspace::dock::DockPosition;
 pub struct CollaborationPanelSettings {
     pub button: bool,
     pub dock: DockPosition,
-    pub default_width: f32,
+    pub default_width: Pixels,
 }
 
 #[derive(Deserialize, Debug)]
 pub struct ChatPanelSettings {
     pub button: bool,
     pub dock: DockPosition,
-    pub default_width: f32,
+    pub default_width: Pixels,
 }
 
 #[derive(Deserialize, Debug)]
 pub struct NotificationPanelSettings {
     pub button: bool,
     pub dock: DockPosition,
-    pub default_width: f32,
+    pub default_width: Pixels,
 }
 
 #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]

crates/project_panel2/src/project_panel.rs 🔗

@@ -156,7 +156,7 @@ pub enum Event {
 
 #[derive(Serialize, Deserialize)]
 struct SerializedProjectPanel {
-    width: Option<f32>,
+    width: Option<Pixels>,
 }
 
 struct DraggedProjectEntryView {
@@ -333,7 +333,7 @@ impl ProjectPanel {
             let panel = ProjectPanel::new(workspace, cx);
             if let Some(serialized_panel) = serialized_panel {
                 panel.update(cx, |panel, cx| {
-                    panel.width = serialized_panel.width.map(px);
+                    panel.width = serialized_panel.width;
                     cx.notify();
                 });
             }
@@ -348,9 +348,7 @@ impl ProjectPanel {
                 KEY_VALUE_STORE
                     .write_kvp(
                         PROJECT_PANEL_KEY.into(),
-                        serde_json::to_string(&SerializedProjectPanel {
-                            width: width.map(|p| p.0),
-                        })?,
+                        serde_json::to_string(&SerializedProjectPanel { width })?,
                     )
                     .await?;
                 anyhow::Ok(())
@@ -1602,15 +1600,13 @@ impl Panel for ProjectPanel {
         );
     }
 
-    fn size(&self, cx: &WindowContext) -> f32 {
-        self.width.map_or_else(
-            || ProjectPanelSettings::get_global(cx).default_width,
-            |width| width.0,
-        )
+    fn size(&self, cx: &WindowContext) -> Pixels {
+        self.width
+            .unwrap_or_else(|| ProjectPanelSettings::get_global(cx).default_width)
     }
 
-    fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
-        self.width = size.map(px);
+    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
+        self.width = size;
         self.serialize(cx);
         cx.notify();
     }

crates/project_panel2/src/project_panel_settings.rs 🔗

@@ -1,4 +1,5 @@
 use anyhow;
+use gpui::Pixels;
 use schemars::JsonSchema;
 use serde_derive::{Deserialize, Serialize};
 use settings::Settings;
@@ -12,7 +13,7 @@ pub enum ProjectPanelDockPosition {
 
 #[derive(Deserialize, Debug)]
 pub struct ProjectPanelSettings {
-    pub default_width: f32,
+    pub default_width: Pixels,
     pub dock: ProjectPanelDockPosition,
     pub file_icons: bool,
     pub folder_icons: bool,

crates/terminal2/src/terminal_settings.rs 🔗

@@ -25,8 +25,8 @@ pub struct TerminalSettings {
     pub option_as_meta: bool,
     pub copy_on_select: bool,
     pub dock: TerminalDockPosition,
-    pub default_width: f32,
-    pub default_height: f32,
+    pub default_width: Pixels,
+    pub default_height: Pixels,
     pub detect_venv: VenvSettings,
 }
 

crates/terminal_view2/src/terminal_panel.rs 🔗

@@ -4,7 +4,7 @@ use crate::TerminalView;
 use db::kvp::KEY_VALUE_STORE;
 use gpui::{
     actions, div, serde_json, AppContext, AsyncWindowContext, Div, Entity, EventEmitter,
-    ExternalPaths, FocusHandle, FocusableView, IntoElement, ParentElement, Render, Styled,
+    ExternalPaths, FocusHandle, FocusableView, IntoElement, ParentElement, Pixels, Render, Styled,
     Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
 };
 use project::Fs;
@@ -44,8 +44,8 @@ pub struct TerminalPanel {
     pane: View<Pane>,
     fs: Arc<dyn Fs>,
     workspace: WeakView<Workspace>,
-    width: Option<f32>,
-    height: Option<f32>,
+    width: Option<Pixels>,
+    height: Option<Pixels>,
     pending_serialization: Task<Option<()>>,
     _subscriptions: Vec<Subscription>,
 }
@@ -364,7 +364,7 @@ impl Panel for TerminalPanel {
         });
     }
 
-    fn size(&self, cx: &WindowContext) -> f32 {
+    fn size(&self, cx: &WindowContext) -> Pixels {
         let settings = TerminalSettings::get_global(cx);
         match self.position(cx) {
             DockPosition::Left | DockPosition::Right => {
@@ -374,7 +374,7 @@ impl Panel for TerminalPanel {
         }
     }
 
-    fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
+    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
         match self.position(cx) {
             DockPosition::Left | DockPosition::Right => self.width = size,
             DockPosition::Bottom => self.height = size,
@@ -428,6 +428,6 @@ impl Panel for TerminalPanel {
 struct SerializedTerminalPanel {
     items: Vec<u64>,
     active_item_id: Option<u64>,
-    width: Option<f32>,
-    height: Option<f32>,
+    width: Option<Pixels>,
+    height: Option<Pixels>,
 }

crates/workspace2/src/dock.rs 🔗

@@ -11,6 +11,8 @@ use std::sync::Arc;
 use ui::{h_stack, ContextMenu, IconButton, Tooltip};
 use ui::{prelude::*, right_click_menu};
 
+const RESIZE_HANDLE_SIZE: Pixels = Pixels(6.);
+
 pub enum PanelEvent {
     ChangePosition,
     ZoomIn,
@@ -25,8 +27,8 @@ pub trait Panel: FocusableView + EventEmitter<PanelEvent> {
     fn position(&self, cx: &WindowContext) -> DockPosition;
     fn position_is_valid(&self, position: DockPosition) -> bool;
     fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>);
-    fn size(&self, cx: &WindowContext) -> f32;
-    fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>);
+    fn size(&self, cx: &WindowContext) -> Pixels;
+    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>);
     // todo!("We should have a icon tooltip method, rather than using persistant_name")
     fn icon(&self, cx: &WindowContext) -> Option<ui::Icon>;
     fn toggle_action(&self) -> Box<dyn Action>;
@@ -49,8 +51,8 @@ pub trait PanelHandle: Send + Sync {
     fn is_zoomed(&self, cx: &WindowContext) -> bool;
     fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext);
     fn set_active(&self, active: bool, cx: &mut WindowContext);
-    fn size(&self, cx: &WindowContext) -> f32;
-    fn set_size(&self, size: Option<f32>, cx: &mut WindowContext);
+    fn size(&self, cx: &WindowContext) -> Pixels;
+    fn set_size(&self, size: Option<Pixels>, cx: &mut WindowContext);
     fn icon(&self, cx: &WindowContext) -> Option<ui::Icon>;
     fn toggle_action(&self, cx: &WindowContext) -> Box<dyn Action>;
     fn icon_label(&self, cx: &WindowContext) -> Option<String>;
@@ -94,11 +96,11 @@ where
         self.update(cx, |this, cx| this.set_active(active, cx))
     }
 
-    fn size(&self, cx: &WindowContext) -> f32 {
+    fn size(&self, cx: &WindowContext) -> Pixels {
         self.read(cx).size(cx)
     }
 
-    fn set_size(&self, size: Option<f32>, cx: &mut WindowContext) {
+    fn set_size(&self, size: Option<Pixels>, cx: &mut WindowContext) {
         self.update(cx, |this, cx| this.set_size(size, cx))
     }
 
@@ -446,14 +448,14 @@ impl Dock {
         }
     }
 
-    pub fn panel_size(&self, panel: &dyn PanelHandle, cx: &WindowContext) -> Option<f32> {
+    pub fn panel_size(&self, panel: &dyn PanelHandle, cx: &WindowContext) -> Option<Pixels> {
         self.panel_entries
             .iter()
             .find(|entry| entry.panel.panel_id() == panel.panel_id())
             .map(|entry| entry.panel.size(cx))
     }
 
-    pub fn active_panel_size(&self, cx: &WindowContext) -> Option<f32> {
+    pub fn active_panel_size(&self, cx: &WindowContext) -> Option<Pixels> {
         if self.is_open {
             self.panel_entries
                 .get(self.active_panel_index)
@@ -463,8 +465,9 @@ impl Dock {
         }
     }
 
-    pub fn resize_active_panel(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
+    pub fn resize_active_panel(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
         if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) {
+            let size = size.map(|size| size.max(RESIZE_HANDLE_SIZE));
             entry.panel.set_size(size, cx);
             cx.notify();
         }
@@ -502,8 +505,6 @@ impl Render for Dock {
                 .z_index(1)
                 .block_mouse();
 
-            const HANDLE_SIZE: Pixels = Pixels(6.);
-
             match self.position() {
                 DockPosition::Left => {
                     handle = handle
@@ -511,7 +512,7 @@ impl Render for Dock {
                         .right(px(0.))
                         .top(px(0.))
                         .h_full()
-                        .w(HANDLE_SIZE)
+                        .w(RESIZE_HANDLE_SIZE)
                         .cursor_col_resize();
                 }
                 DockPosition::Bottom => {
@@ -520,7 +521,7 @@ impl Render for Dock {
                         .top(px(0.))
                         .left(px(0.))
                         .w_full()
-                        .h(HANDLE_SIZE)
+                        .h(RESIZE_HANDLE_SIZE)
                         .cursor_row_resize();
                 }
                 DockPosition::Right => {
@@ -529,7 +530,7 @@ impl Render for Dock {
                         .top(px(0.))
                         .left(px(0.))
                         .h_full()
-                        .w(HANDLE_SIZE)
+                        .w(RESIZE_HANDLE_SIZE)
                         .cursor_col_resize();
                 }
             }
@@ -539,8 +540,8 @@ impl Render for Dock {
                 .border_color(cx.theme().colors().border)
                 .overflow_hidden()
                 .map(|this| match self.position().axis() {
-                    Axis::Horizontal => this.w(px(size)).h_full().flex_row(),
-                    Axis::Vertical => this.h(px(size)).w_full().flex_col(),
+                    Axis::Horizontal => this.w(size).h_full().flex_row(),
+                    Axis::Vertical => this.h(size).w_full().flex_col(),
                 })
                 .map(|this| match self.position() {
                     DockPosition::Left => this.border_r(),
@@ -550,8 +551,8 @@ impl Render for Dock {
                 .child(
                     div()
                         .map(|this| match self.position().axis() {
-                            Axis::Horizontal => this.min_w(px(size)).h_full(),
-                            Axis::Vertical => this.min_h(px(size)).w_full(),
+                            Axis::Horizontal => this.min_w(size).h_full(),
+                            Axis::Vertical => this.min_h(size).w_full(),
                         })
                         .child(entry.panel.to_any()),
                 )
@@ -674,7 +675,7 @@ pub mod test {
         pub zoomed: bool,
         pub active: bool,
         pub focus_handle: FocusHandle,
-        pub size: f32,
+        pub size: Pixels,
     }
     actions!(test, [ToggleTestPanel]);
 
@@ -687,7 +688,7 @@ pub mod test {
                 zoomed: false,
                 active: false,
                 focus_handle: cx.focus_handle(),
-                size: 300.,
+                size: px(300.),
             }
         }
     }
@@ -718,12 +719,12 @@ pub mod test {
             cx.emit(PanelEvent::ChangePosition);
         }
 
-        fn size(&self, _: &WindowContext) -> f32 {
+        fn size(&self, _: &WindowContext) -> Pixels {
             self.size
         }
 
-        fn set_size(&mut self, size: Option<f32>, _: &mut ViewContext<Self>) {
-            self.size = size.unwrap_or(300.);
+        fn set_size(&mut self, size: Option<Pixels>, _: &mut ViewContext<Self>) {
+            self.size = size.unwrap_or(px(300.));
         }
 
         fn icon(&self, _: &WindowContext) -> Option<ui::Icon> {

crates/workspace2/src/workspace2.rs 🔗

@@ -3549,19 +3549,19 @@ impl Render for Workspace {
                                 DockPosition::Left => {
                                     let size = workspace.bounds.left() + e.event.position.x;
                                     workspace.left_dock.update(cx, |left_dock, cx| {
-                                        left_dock.resize_active_panel(Some(size.0), cx);
+                                        left_dock.resize_active_panel(Some(size), cx);
                                     });
                                 }
                                 DockPosition::Right => {
                                     let size = workspace.bounds.right() - e.event.position.x;
                                     workspace.right_dock.update(cx, |right_dock, cx| {
-                                        right_dock.resize_active_panel(Some(size.0), cx);
+                                        right_dock.resize_active_panel(Some(size), cx);
                                     });
                                 }
                                 DockPosition::Bottom => {
                                     let size = workspace.bounds.bottom() - e.event.position.y;
                                     workspace.bottom_dock.update(cx, |bottom_dock, cx| {
-                                        bottom_dock.resize_active_panel(Some(size.0), cx);
+                                        bottom_dock.resize_active_panel(Some(size), cx);
                                     });
                                 }
                             }