From 65401d6d7b95638fa8b9ca4702219b5f096003eb Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Fri, 25 Apr 2025 11:56:16 -0400 Subject: [PATCH] debugger: Make debug panes zoomable (#29365) - [x] Buttons - [x] Make it keyboard-driven Co-authored-by: Anthony Release Notes: - N/A --------- Co-authored-by: Anthony Eid --- crates/debugger_ui/src/debugger_panel.rs | 7 +- crates/debugger_ui/src/session/running.rs | 261 ++++++++++++++-------- 2 files changed, 168 insertions(+), 100 deletions(-) diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index b2bc0bc53fe926aec879be4956b31bafae3490b2..b6fd102ad19d736c4293a7215757a473b86b200a 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -39,7 +39,7 @@ use task::{ use terminal_view::TerminalView; use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*}; use workspace::{ - Workspace, + Pane, Workspace, dock::{DockPosition, Panel, PanelEvent}, }; @@ -1097,9 +1097,14 @@ impl Panel for DebugPanel { Box::new(ToggleFocus) } + fn pane(&self) -> Option> { + None + } + fn activation_priority(&self) -> u32 { 9 } + fn set_active(&mut self, _: bool, _: &mut Window, _: &mut Context) {} } diff --git a/crates/debugger_ui/src/session/running.rs b/crates/debugger_ui/src/session/running.rs index 305d5a8e66a01d97082374dea84a0d9f503037f3..f2b35fc4eaccd936602ba1592f01c3ec358b51de 100644 --- a/crates/debugger_ui/src/session/running.rs +++ b/crates/debugger_ui/src/session/running.rs @@ -29,9 +29,10 @@ use settings::Settings; use stack_frame_list::StackFrameList; use terminal_view::TerminalView; use ui::{ - ActiveTheme, AnyElement, App, Context, ContextMenu, DropdownMenu, FluentBuilder, - InteractiveElement, IntoElement, Label, LabelCommon as _, ParentElement, Render, SharedString, - StatefulInteractiveElement, Styled, Tab, Window, div, h_flex, v_flex, + ActiveTheme, AnyElement, App, ButtonCommon as _, Clickable as _, Context, ContextMenu, + DropdownMenu, FluentBuilder, IconButton, IconName, IconSize, InteractiveElement, IntoElement, + Label, LabelCommon as _, ParentElement, Render, SharedString, StatefulInteractiveElement, + Styled, Tab, Tooltip, Window, div, h_flex, v_flex, }; use util::ResultExt; use variable_list::VariableList; @@ -62,8 +63,16 @@ pub struct RunningState { impl Render for RunningState { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + let zoomed_pane = self + .panes + .panes() + .into_iter() + .find(|pane| pane.read(cx).is_zoomed()); + let active = self.panes.panes().into_iter().next(); - let x = if let Some(active) = active { + let x = if let Some(ref zoomed_pane) = zoomed_pane { + zoomed_pane.update(cx, |pane, cx| pane.render(window, cx).into_any_element()) + } else if let Some(active) = active { self.panes .render( None, @@ -257,109 +266,148 @@ pub(crate) fn new_debugger_pane( window, cx, ); - pane.set_can_split(Some(Arc::new(move |pane, dragged_item, _window, cx| { - if let Some(tab) = dragged_item.downcast_ref::() { - let is_current_pane = tab.pane == cx.entity(); - let Some(can_drag_away) = weak_running - .update(cx, |running_state, _| { - let current_panes = running_state.panes.panes(); - !current_panes.contains(&&tab.pane) - || current_panes.len() > 1 - || (!is_current_pane || pane.items_len() > 1) - }) - .ok() - else { - return false; - }; - if can_drag_away { - let item = if is_current_pane { - pane.item_for_index(tab.ix) - } else { - tab.pane.read(cx).item_for_index(tab.ix) + let focus_handle = pane.focus_handle(cx); + pane.set_can_split(Some(Arc::new({ + let weak_running = weak_running.clone(); + move |pane, dragged_item, _window, cx| { + if let Some(tab) = dragged_item.downcast_ref::() { + let is_current_pane = tab.pane == cx.entity(); + let Some(can_drag_away) = weak_running + .update(cx, |running_state, _| { + let current_panes = running_state.panes.panes(); + !current_panes.contains(&&tab.pane) + || current_panes.len() > 1 + || (!is_current_pane || pane.items_len() > 1) + }) + .ok() + else { + return false; }; - if let Some(item) = item { - return item.downcast::().is_some(); + if can_drag_away { + let item = if is_current_pane { + pane.item_for_index(tab.ix) + } else { + tab.pane.read(cx).item_for_index(tab.ix) + }; + if let Some(item) = item { + return item.downcast::().is_some(); + } } } + false } - false }))); pane.display_nav_history_buttons(None); pane.set_custom_drop_handle(cx, custom_drop_handle); pane.set_should_display_tab_bar(|_, _| true); pane.set_render_tab_bar_buttons(cx, |_, _, _| (None, None)); - pane.set_render_tab_bar(cx, |pane, window, cx| { - let active_pane_item = pane.active_item(); - h_flex() - .w_full() - .px_2() - .gap_1() - .h(Tab::container_height(cx)) - .drag_over::(|bar, _, _, cx| { - bar.bg(cx.theme().colors().drop_target_background) - }) - .on_drop( - cx.listener(move |this, dragged_tab: &DraggedTab, window, cx| { - this.drag_split_direction = None; - this.handle_tab_drop(dragged_tab, this.items_len(), window, cx) - }), - ) - .bg(cx.theme().colors().tab_bar_background) - .border_b_1() - .border_color(cx.theme().colors().border) - .children(pane.items().enumerate().map(|(ix, item)| { - let selected = active_pane_item - .as_ref() - .map_or(false, |active| active.item_id() == item.item_id()); - let item_ = item.boxed_clone(); - div() - .id(SharedString::from(format!( - "debugger_tab_{}", - item.item_id().as_u64() - ))) - .p_1() - .rounded_md() - .cursor_pointer() - .map(|this| { - if selected { - this.bg(cx.theme().colors().tab_active_background) + pane.set_render_tab_bar(cx, { + move |pane, window, cx| { + let active_pane_item = pane.active_item(); + h_flex() + .justify_between() + .bg(cx.theme().colors().tab_bar_background) + .border_b_1() + .border_color(cx.theme().colors().border) + .track_focus(&focus_handle) + .child( + h_flex() + .w_full() + .px_2() + .gap_1() + .h(Tab::container_height(cx)) + .drag_over::(|bar, _, _, cx| { + bar.bg(cx.theme().colors().drop_target_background) + }) + .on_drop(cx.listener( + move |this, dragged_tab: &DraggedTab, window, cx| { + this.drag_split_direction = None; + this.handle_tab_drop(dragged_tab, this.items_len(), window, cx) + }, + )) + .children(pane.items().enumerate().map(|(ix, item)| { + let selected = active_pane_item + .as_ref() + .map_or(false, |active| active.item_id() == item.item_id()); + let item_ = item.boxed_clone(); + div() + .id(SharedString::from(format!( + "debugger_tab_{}", + item.item_id().as_u64() + ))) + .p_1() + .rounded_md() + .cursor_pointer() + .map(|this| { + if selected { + this.bg(cx.theme().colors().tab_active_background) + } else { + let hover_color = cx.theme().colors().element_hover; + this.hover(|style| style.bg(hover_color)) + } + }) + .on_click(cx.listener(move |this, _, window, cx| { + let index = this.index_for_item(&*item_); + if let Some(index) = index { + this.activate_item(index, true, true, window, cx); + } + })) + .child(item.tab_content( + TabContentParams { + selected, + ..Default::default() + }, + window, + cx, + )) + .on_drop(cx.listener( + move |this, dragged_tab: &DraggedTab, window, cx| { + this.drag_split_direction = None; + this.handle_tab_drop(dragged_tab, ix, window, cx) + }, + )) + .on_drag( + DraggedTab { + item: item.boxed_clone(), + pane: cx.entity().clone(), + detail: 0, + is_active: selected, + ix, + }, + |tab, _, _, cx| cx.new(|_| tab.clone()), + ) + })), + ) + .child({ + let zoomed = pane.is_zoomed(); + IconButton::new( + "debug-toggle-zoom", + if zoomed { + IconName::Minimize } else { - let hover_color = cx.theme().colors().element_hover; - this.hover(|style| style.bg(hover_color)) - } - }) - .on_click(cx.listener(move |this, _, window, cx| { - let index = this.index_for_item(&*item_); - if let Some(index) = index { - this.activate_item(index, true, true, window, cx); - } - })) - .child(item.tab_content( - TabContentParams { - selected, - ..Default::default() - }, - window, - cx, - )) - .on_drop( - cx.listener(move |this, dragged_tab: &DraggedTab, window, cx| { - this.drag_split_direction = None; - this.handle_tab_drop(dragged_tab, ix, window, cx) - }), - ) - .on_drag( - DraggedTab { - item: item.boxed_clone(), - pane: cx.entity().clone(), - detail: 0, - is_active: selected, - ix, + IconName::Maximize }, - |tab, _, _, cx| cx.new(|_| tab.clone()), ) - })) - .into_any_element() + .icon_size(IconSize::Small) + .on_click(cx.listener(move |pane, _, window, cx| { + pane.toggle_zoom(&workspace::ToggleZoom, window, cx); + })) + .tooltip({ + let focus_handle = focus_handle.clone(); + move |window, cx| { + let zoomed_text = if zoomed { "Zoom Out" } else { "Zoom In" }; + Tooltip::for_action_in( + zoomed_text, + &workspace::ToggleZoom, + &focus_handle, + window, + cx, + ) + } + }) + }) + .into_any_element() + } }); pane }); @@ -730,10 +778,25 @@ impl RunningState { cx: &mut Context, ) { this.serialize_layout(window, cx); - if let Event::Remove { .. } = event { - let _did_find_pane = this.panes.remove(&source_pane).is_ok(); - debug_assert!(_did_find_pane); - cx.notify(); + match event { + Event::Remove { .. } => { + let _did_find_pane = this.panes.remove(&source_pane).is_ok(); + debug_assert!(_did_find_pane); + cx.notify(); + } + Event::ZoomIn => { + source_pane.update(cx, |pane, cx| { + pane.set_zoomed(true, cx); + }); + cx.notify(); + } + Event::ZoomOut => { + source_pane.update(cx, |pane, cx| { + pane.set_zoomed(false, cx); + }); + cx.notify(); + } + _ => {} } }