From df754566ddd4afc53479b610c65db8fd8e583942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristoffer=20S=C3=B8holm?= Date: Fri, 22 May 2026 14:21:15 +0200 Subject: [PATCH 1/3] Close tabs on middle click --- crates/jackdaw_panels/src/tabs.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/jackdaw_panels/src/tabs.rs b/crates/jackdaw_panels/src/tabs.rs index 9d9929a0..04ce61d6 100644 --- a/crates/jackdaw_panels/src/tabs.rs +++ b/crates/jackdaw_panels/src/tabs.rs @@ -1,3 +1,4 @@ +use bevy::picking::pointer::PointerButton; use bevy::prelude::*; use jackdaw_feathers::{icons::IconFont, tokens}; use lucide_icons::Icon; @@ -22,7 +23,8 @@ pub struct DockTabPlugin; impl Plugin for DockTabPlugin { fn build(&self, app: &mut App) { app.add_systems(Update, (handle_dock_tab_clicks, show_close_on_hover)) - .add_observer(on_close_button_click); + .add_observer(on_close_button_click) + .add_observer(on_tab_middle_click); } } @@ -330,3 +332,17 @@ fn on_close_button_click( }; tree.remove_tab(close_btn.tab_id); } + +fn on_tab_middle_click( + trigger: On>, + tabs: Query<&DockTab>, + mut tree: ResMut, +) { + if trigger.event().button != PointerButton::Middle { + return; + } + let Ok(tab) = tabs.get(trigger.event_target()) else { + return; + }; + tree.remove_tab(tab.tab_id); +} From 131dca024722776d75a6a5985cbf24dd7acd36b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristoffer=20S=C3=B8holm?= Date: Fri, 22 May 2026 14:47:04 +0200 Subject: [PATCH 2/3] Convert to operator --- crates/jackdaw_panels/src/tabs.rs | 30 +------------ src/core_extension.rs | 1 + src/dock_ops.rs | 70 +++++++++++++++++++++++++++++++ src/lib.rs | 2 + 4 files changed, 74 insertions(+), 29 deletions(-) create mode 100644 src/dock_ops.rs diff --git a/crates/jackdaw_panels/src/tabs.rs b/crates/jackdaw_panels/src/tabs.rs index 04ce61d6..53644610 100644 --- a/crates/jackdaw_panels/src/tabs.rs +++ b/crates/jackdaw_panels/src/tabs.rs @@ -1,4 +1,3 @@ -use bevy::picking::pointer::PointerButton; use bevy::prelude::*; use jackdaw_feathers::{icons::IconFont, tokens}; use lucide_icons::Icon; @@ -22,9 +21,7 @@ pub struct DockTabPlugin; impl Plugin for DockTabPlugin { fn build(&self, app: &mut App) { - app.add_systems(Update, (handle_dock_tab_clicks, show_close_on_hover)) - .add_observer(on_close_button_click) - .add_observer(on_tab_middle_click); + app.add_systems(Update, (handle_dock_tab_clicks, show_close_on_hover)); } } @@ -321,28 +318,3 @@ fn show_close_on_hover( } } -fn on_close_button_click( - trigger: On>, - close_buttons: Query<&crate::area::DockTabCloseButton>, - mut tree: ResMut, -) { - let entity = trigger.event_target(); - let Ok(close_btn) = close_buttons.get(entity) else { - return; - }; - tree.remove_tab(close_btn.tab_id); -} - -fn on_tab_middle_click( - trigger: On>, - tabs: Query<&DockTab>, - mut tree: ResMut, -) { - if trigger.event().button != PointerButton::Middle { - return; - } - let Ok(tab) = tabs.get(trigger.event_target()) else { - return; - }; - tree.remove_tab(tab.tab_id); -} diff --git a/src/core_extension.rs b/src/core_extension.rs index 0cfad9cf..16a71a9e 100644 --- a/src/core_extension.rs +++ b/src/core_extension.rs @@ -243,6 +243,7 @@ impl JackdawExtension for JackdawCoreExtension { crate::viewport::add_to_extension(ctx); crate::command_palette::add_to_extension(ctx); crate::document_ops::add_to_extension(ctx); + crate::dock_ops::add_to_extension(ctx); } fn register_input_context(&self, app: &mut App) { diff --git a/src/dock_ops.rs b/src/dock_ops.rs new file mode 100644 index 00000000..0dfccc50 --- /dev/null +++ b/src/dock_ops.rs @@ -0,0 +1,70 @@ +//! Operators for the panel docking system. + +use bevy::picking::pointer::PointerButton; +use bevy::prelude::*; +use jackdaw_api::prelude::*; +use jackdaw_panels::area::{DockTab, DockTabCloseButton}; +use jackdaw_panels::tree::{DockTree, TabId}; + +pub struct DockOpsPlugin; + +impl Plugin for DockOpsPlugin { + fn build(&self, app: &mut App) { + app.add_observer(on_close_button_click) + .add_observer(on_tab_middle_click); + } +} + +pub(crate) fn add_to_extension(ctx: &mut ExtensionContext) { + ctx.register_operator::(); +} + +#[operator( + id = "dock.close_tab", + label = "Close Tab", + description = "Close the specified docked tab.", + allows_undo = false, + params(tab_id(i64, doc = "TabId of the tab to close.")), +)] +pub(crate) fn dock_close_tab( + In(params): In, + mut tree: ResMut, +) -> OperatorResult { + let Some(tab_id) = params.as_int("tab_id") else { + warn!("dock.close_tab: missing 'tab_id' parameter"); + return OperatorResult::Cancelled; + }; + tree.remove_tab(TabId(tab_id as u64)); + OperatorResult::Finished +} + +fn on_close_button_click( + trigger: On>, + close_buttons: Query<&DockTabCloseButton>, + mut commands: Commands, +) { + let Ok(close_btn) = close_buttons.get(trigger.event_target()) else { + return; + }; + commands + .operator(DockCloseTabOp::ID) + .param("tab_id", close_btn.tab_id.0 as i64) + .call(); +} + +fn on_tab_middle_click( + trigger: On>, + tabs: Query<&DockTab>, + mut commands: Commands, +) { + if trigger.event().button != PointerButton::Middle { + return; + } + let Ok(tab) = tabs.get(trigger.event_target()) else { + return; + }; + commands + .operator(DockCloseTabOp::ID) + .param("tab_id", tab.tab_id.0 as i64) + .call(); +} diff --git a/src/lib.rs b/src/lib.rs index cee2e214..4551a275 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,7 @@ use std::{collections::BTreeMap, marker::PhantomData}; pub use inspector::{EditorCategory, EditorDescription, EditorHidden, SkipSerialization}; pub mod core_extension; +pub mod dock_ops; pub mod document_ops; pub mod ext_build; mod extension_lifecycle; @@ -313,6 +314,7 @@ impl Plugin for EditorCorePlugin { .add_plugins(extensions_dialog::ExtensionsDialogPlugin) .add_plugins(hot_reload::HotReloadPlugin) .add_plugins(pie::PiePlugin) + .add_plugins(dock_ops::DockOpsPlugin) // Force-exit on `AppExit`: bypass wgpu device cleanup // and AsyncComputeTaskPool shutdown that otherwise hang // the process after window close. Hosted here so every From 76f81d65c9a17e129d020f3ad27b1fdb693688a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristoffer=20S=C3=B8holm?= Date: Fri, 22 May 2026 14:49:52 +0200 Subject: [PATCH 3/3] format --- crates/jackdaw_panels/src/tabs.rs | 1 - src/dock_ops.rs | 8 ++------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/jackdaw_panels/src/tabs.rs b/crates/jackdaw_panels/src/tabs.rs index 53644610..36eedf1b 100644 --- a/crates/jackdaw_panels/src/tabs.rs +++ b/crates/jackdaw_panels/src/tabs.rs @@ -317,4 +317,3 @@ fn show_close_on_hover( } } } - diff --git a/src/dock_ops.rs b/src/dock_ops.rs index 0dfccc50..bf2e1c6c 100644 --- a/src/dock_ops.rs +++ b/src/dock_ops.rs @@ -24,7 +24,7 @@ pub(crate) fn add_to_extension(ctx: &mut ExtensionContext) { label = "Close Tab", description = "Close the specified docked tab.", allows_undo = false, - params(tab_id(i64, doc = "TabId of the tab to close.")), + params(tab_id(i64, doc = "TabId of the tab to close.")) )] pub(crate) fn dock_close_tab( In(params): In, @@ -52,11 +52,7 @@ fn on_close_button_click( .call(); } -fn on_tab_middle_click( - trigger: On>, - tabs: Query<&DockTab>, - mut commands: Commands, -) { +fn on_tab_middle_click(trigger: On>, tabs: Query<&DockTab>, mut commands: Commands) { if trigger.event().button != PointerButton::Middle { return; }