Add UI: icon button and right-click tab menu item

Danilo Leal created

Change summary

crates/workspace/src/pane.rs | 72 ++++++++++++++++++++++++++++++-------
1 file changed, 58 insertions(+), 14 deletions(-)

Detailed changes

crates/workspace/src/pane.rs 🔗

@@ -2647,6 +2647,24 @@ impl Pane {
         let is_pinned = self.is_tab_pinned(ix);
         let position_relative_to_active_item = ix.cmp(&self.active_item_index);
 
+        let read_only_toggle = || {
+            IconButton::new("toggle_read_only", IconName::FileLock)
+                .size(ButtonSize::None)
+                .shape(IconButtonShape::Square)
+                .icon_color(Color::Muted)
+                .icon_size(IconSize::Small)
+                .tooltip(move |_, cx| {
+                    Tooltip::with_meta("Unlock File", None, "This will make this file editable", cx)
+                })
+                .on_click(cx.listener(move |pane, _, window, cx| {
+                    if let Some(item) = pane.item_for_index(ix) {
+                        item.toggle_read_only(window, cx);
+                    }
+                }))
+        };
+
+        let has_file_icon = icon.is_some() | decorated_icon.is_some();
+
         let tab = Tab::new(ix)
             .position(if is_first_item {
                 TabPosition::First
@@ -2781,30 +2799,36 @@ impl Pane {
             })
             .child(
                 h_flex()
+                    .id(("pane-tab-content", ix))
                     .gap_1()
-                    .items_center()
                     .children(if let Some(decorated_icon) = decorated_icon {
-                        Some(div().child(decorated_icon.into_any_element()))
+                        Some(decorated_icon.into_any_element())
+                    } else if let Some(icon) = icon {
+                        Some(icon.into_any_element())
+                    } else if item.is_read_only(cx) {
+                        Some(read_only_toggle().into_any_element())
                     } else {
-                        icon.map(|icon| div().child(icon.into_any_element()))
+                        None
                     })
                     .child(label)
-                    .id(("pane-tab-content", ix))
                     .map(|this| match tab_tooltip_content {
-                        Some(TabTooltipContent::Text(text)) => this.tooltip(Tooltip::text(text)),
+                        Some(TabTooltipContent::Text(text)) => {
+                            if item.is_read_only(cx) {
+                                this.tooltip(move |_, cx| {
+                                    let text = text.clone();
+                                    Tooltip::with_meta(text, None, "Read-Only File", cx)
+                                })
+                            } else {
+                                this.tooltip(Tooltip::text(text))
+                            }
+                        }
                         Some(TabTooltipContent::Custom(element_fn)) => {
                             this.tooltip(move |window, cx| element_fn(window, cx))
                         }
                         None => this,
                     })
-                    .children(if item.is_read_only(cx) {
-                        Some(
-                            Icon::new(IconName::FileLock)
-                                .color(Color::Muted)
-                                .size(IconSize::Small),
-                        )
-                    } else {
-                        None
+                    .when(item.is_read_only(cx) && has_file_icon, |this| {
+                        this.child(read_only_toggle())
                     }),
             );
 
@@ -2821,8 +2845,11 @@ impl Pane {
         let has_items_to_right = ix < total_items - 1;
         let has_clean_items = self.items.iter().any(|item| !item.is_dirty(cx));
         let is_pinned = self.is_tab_pinned(ix);
+        let is_read_only = item.is_read_only(cx);
+
         let pane = cx.entity().downgrade();
         let menu_context = item.item_focus_handle(cx);
+
         right_click_menu(ix)
             .trigger(|_, _, _| tab)
             .menu(move |window, cx| {
@@ -2969,6 +2996,22 @@ impl Pane {
                                 }
                             })
                         };
+
+                        let read_only_label = if is_read_only {
+                            "Make File Editable"
+                        } else {
+                            "Make File Read-Only"
+                        };
+                        menu = menu.separator().entry(
+                            read_only_label,
+                            None,
+                            window.handler_for(&pane, move |pane, window, cx| {
+                                if let Some(item) = pane.item_for_index(ix) {
+                                    item.toggle_read_only(window, cx);
+                                }
+                            }),
+                        );
+
                         if let Some(entry) = single_entry_to_resolve {
                             let project_path = pane
                                 .read(cx)
@@ -3000,6 +3043,7 @@ impl Pane {
                                 && worktree.is_some_and(|worktree| worktree.read(cx).is_visible());
 
                             let entry_id = entry.to_proto();
+
                             menu = menu
                                 .separator()
                                 .when_some(entry_abs_path, |menu, abs_path| {
@@ -3064,7 +3108,7 @@ impl Pane {
                         } else {
                             menu = menu.map(pin_tab_entries);
                         }
-                    }
+                    };
 
                     menu.context(menu_context)
                 })