Make tab close button square (#4052)

Marshall Bowers created

This PR makes the close button for tabs square.

`IconButton` now accepts a `shape`, and using `IconButtonShape::Square`
will ensure the `IconButton` is square with respect to its contained
icon.

#### Before

<img width="119" alt="Screenshot 2024-01-15 at 10 32 40 AM"
src="https://github.com/zed-industries/zed/assets/1486634/dc806b9b-411f-4cd9-8c10-676d2cbd298b">

#### After

<img width="116" alt="Screenshot 2024-01-15 at 10 32 24 AM"
src="https://github.com/zed-industries/zed/assets/1486634/8b4ef43c-14b6-449f-a235-5d7affd82c4e">

Release Notes:

- Changed the tab close button to be square.

Change summary

crates/ui/src/components/button/button_like.rs |  9 ++++
crates/ui/src/components/button/icon_button.rs | 39 +++++++++++++++----
crates/ui/src/components/stories/tab.rs        |  3 +
crates/workspace/src/pane.rs                   |  5 +-
4 files changed, 43 insertions(+), 13 deletions(-)

Detailed changes

crates/ui/src/components/button/button_like.rs 🔗

@@ -300,6 +300,7 @@ pub struct ButtonLike {
     pub(super) selected: bool,
     pub(super) selected_style: Option<ButtonStyle>,
     pub(super) width: Option<DefiniteLength>,
+    pub(super) height: Option<DefiniteLength>,
     size: ButtonSize,
     rounding: Option<ButtonLikeRounding>,
     tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
@@ -317,6 +318,7 @@ impl ButtonLike {
             selected: false,
             selected_style: None,
             width: None,
+            height: None,
             size: ButtonSize::Default,
             rounding: Some(ButtonLikeRounding::All),
             tooltip: None,
@@ -325,6 +327,11 @@ impl ButtonLike {
         }
     }
 
+    pub(crate) fn height(mut self, height: DefiniteLength) -> Self {
+        self.height = Some(height);
+        self
+    }
+
     pub(crate) fn rounding(mut self, rounding: impl Into<Option<ButtonLikeRounding>>) -> Self {
         self.rounding = rounding.into();
         self
@@ -417,7 +424,7 @@ impl RenderOnce for ButtonLike {
             .id(self.id.clone())
             .group("")
             .flex_none()
-            .h(self.size.height())
+            .h(self.height.unwrap_or(self.size.height().into()))
             .when_some(self.width, |this, width| this.w(width).justify_center())
             .when_some(self.rounding, |this, rounding| match rounding {
                 ButtonLikeRounding::All => this.rounded_md(),

crates/ui/src/components/button/icon_button.rs 🔗

@@ -5,9 +5,17 @@ use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSiz
 
 use super::button_icon::ButtonIcon;
 
+/// The shape of an [`IconButton`].
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
+pub enum IconButtonShape {
+    Square,
+    Wide,
+}
+
 #[derive(IntoElement)]
 pub struct IconButton {
     base: ButtonLike,
+    shape: IconButtonShape,
     icon: IconName,
     icon_size: IconSize,
     icon_color: Color,
@@ -18,6 +26,7 @@ impl IconButton {
     pub fn new(id: impl Into<ElementId>, icon: IconName) -> Self {
         Self {
             base: ButtonLike::new(id),
+            shape: IconButtonShape::Wide,
             icon,
             icon_size: IconSize::default(),
             icon_color: Color::Default,
@@ -25,6 +34,11 @@ impl IconButton {
         }
     }
 
+    pub fn shape(mut self, shape: IconButtonShape) -> Self {
+        self.shape = shape;
+        self
+    }
+
     pub fn icon_size(mut self, icon_size: IconSize) -> Self {
         self.icon_size = icon_size;
         self
@@ -118,14 +132,21 @@ impl RenderOnce for IconButton {
         let is_selected = self.base.selected;
         let selected_style = self.base.selected_style;
 
-        self.base.child(
-            ButtonIcon::new(self.icon)
-                .disabled(is_disabled)
-                .selected(is_selected)
-                .selected_icon(self.selected_icon)
-                .when_some(selected_style, |this, style| this.selected_style(style))
-                .size(self.icon_size)
-                .color(self.icon_color),
-        )
+        self.base
+            .map(|this| match self.shape {
+                IconButtonShape::Square => this
+                    .width(self.icon_size.rems().into())
+                    .height(self.icon_size.rems().into()),
+                IconButtonShape::Wide => this,
+            })
+            .child(
+                ButtonIcon::new(self.icon)
+                    .disabled(is_disabled)
+                    .selected(is_selected)
+                    .selected_icon(self.selected_icon)
+                    .when_some(selected_style, |this, style| this.selected_style(style))
+                    .size(self.icon_size)
+                    .color(self.icon_color),
+            )
     }
 }

crates/ui/src/components/stories/tab.rs 🔗

@@ -3,7 +3,7 @@ use std::cmp::Ordering;
 use gpui::Render;
 use story::Story;
 
-use crate::{prelude::*, TabPosition};
+use crate::{prelude::*, IconButtonShape, TabPosition};
 use crate::{Indicator, Tab};
 
 pub struct TabStory;
@@ -28,6 +28,7 @@ impl Render for TabStory {
                     Tab::new("tab_1")
                         .end_slot(
                             IconButton::new("close_button", IconName::Close)
+                                .shape(IconButtonShape::Square)
                                 .icon_color(Color::Muted)
                                 .size(ButtonSize::None)
                                 .icon_size(IconSize::XSmall),

crates/workspace/src/pane.rs 🔗

@@ -32,8 +32,8 @@ use std::{
 use theme::ThemeSettings;
 
 use ui::{
-    prelude::*, right_click_menu, ButtonSize, Color, IconButton, IconName, IconSize, Indicator,
-    Label, Tab, TabBar, TabPosition, Tooltip,
+    prelude::*, right_click_menu, ButtonSize, Color, IconButton, IconButtonShape, IconName,
+    IconSize, Indicator, Label, Tab, TabBar, TabPosition, Tooltip,
 };
 use ui::{v_stack, ContextMenu};
 use util::{maybe, truncate_and_remove_front, ResultExt};
@@ -1341,6 +1341,7 @@ impl Pane {
             .start_slot::<Indicator>(indicator)
             .end_slot(
                 IconButton::new("close tab", IconName::Close)
+                    .shape(IconButtonShape::Square)
                     .icon_color(Color::Muted)
                     .size(ButtonSize::None)
                     .icon_size(IconSize::XSmall)