From e4b188b0ee61010b5e6ab09460b7d2ff392e10c0 Mon Sep 17 00:00:00 2001 From: Rod Date: Sun, 12 Apr 2026 13:17:22 +0700 Subject: [PATCH 1/4] feat(dock): add bg_color() trait method for per-panel tile backgrounds Add WorkbenchPanel::bg_color() method allowing each panel to specify its own tile background color. Return None for transparent tiles (e.g., center spacer for 3D scene), or Some(Color32) for opaque panels. Changes: - dock.rs: add bg_color() trait method with default opaque background - dock/ui.rs: draw tile background from panel.bg_color() in pane_ui() - dock/ui.rs: make CentralPanel transparent with Frame::NONE to let 3D scene show through behind the dock layout Co-authored-by: Qwen-Coder --- src/dock.rs | 5 +++++ src/dock/ui.rs | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/dock.rs b/src/dock.rs index 088ae8d..1e3c541 100644 --- a/src/dock.rs +++ b/src/dock.rs @@ -80,6 +80,11 @@ pub trait WorkbenchPanel: Send + Sync + std::any::Any + 'static { false } + /// Background color for this panel's tile. Return `None` for transparent. + fn bg_color(&self) -> Option { + Some(egui::Color32::from_rgb(35, 35, 40)) + } + /// Whether the panel tab can be closed (default: true). fn closable(&self) -> bool { true diff --git a/src/dock/ui.rs b/src/dock/ui.rs index 297a6af..b07c333 100644 --- a/src/dock/ui.rs +++ b/src/dock/ui.rs @@ -66,6 +66,10 @@ impl egui_tiles::Behavior for WorkbenchBehavior<'_> { pane: &mut PaneEntry, ) -> egui_tiles::UiResponse { if let Some(panel) = self.panels.get_mut(&pane.panel_id) { + // Draw tile background from panel's bg_color (None = transparent for 3D scene) + if let Some(color) = panel.bg_color() { + ui.painter().rect_filled(ui.max_rect(), 0.0, color); + } if panel.needs_world() && let Some(world) = self.world.as_deref_mut() { @@ -190,7 +194,10 @@ pub fn tiles_ui_system(world: &mut World) { let mut closed_panel_ids: Vec = Vec::new(); if let Some(ref mut tree) = tree { - egui::CentralPanel::default().show(&ctx, |ui| { + // Transparent CentralPanel — lets 3D scene show through behind tiles + egui::CentralPanel::default() + .frame(egui::Frame::NONE) + .show(&ctx, |ui| { let mut behavior = WorkbenchBehavior { panels: &mut panels, world: Some(world), From fb1b1decf0d36809718520d4dc6f9594485d97ad Mon Sep 17 00:00:00 2001 From: Rod Date: Sun, 12 Apr 2026 14:06:14 +0700 Subject: [PATCH 2/4] feat(dock): add hide_tab() for panels without tab bars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add WorkbenchPanel::hide_tab() method. When true, the panel is inserted as a plain pane tile without a tab header — ideal for viewport-style panels like 3D scene viewports. Changes: - dock.rs: add hide_tab() trait method with default false - dock.rs: add make_tile() to separate hidden-tab from tabbed panels - dock.rs: update build_default_tree to use make_tile() for left/center/right slots, so hidden-tab panels render without tab bars Co-authored-by: Qwen-Coder --- src/dock.rs | 66 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/src/dock.rs b/src/dock.rs index 1e3c541..7dcb4d0 100644 --- a/src/dock.rs +++ b/src/dock.rs @@ -85,6 +85,12 @@ pub trait WorkbenchPanel: Send + Sync + std::any::Any + 'static { Some(egui::Color32::from_rgb(35, 35, 40)) } + /// Whether to hide the tab bar for this panel. + /// Use for panels that act as a viewport (e.g., 3D scene) rather than a tool. + fn hide_tab(&self) -> bool { + false + } + /// Whether the panel tab can be closed (default: true). fn closable(&self) -> bool { true @@ -257,26 +263,26 @@ impl TileLayoutState { } }); - // Build tab containers for each slot (always with tab headers for drag support) - let left_tile = Self::make_tab(&mut tiles, left_panes); - let center_tile = Self::make_tab(&mut tiles, center_panes); - let right_tile = Self::make_tab(&mut tiles, right_panes); + // Build tile containers for each slot + let left_tiles = self.make_tile(&mut tiles, left_panes); + let center_tiles = self.make_tile(&mut tiles, center_panes); + let right_tiles = self.make_tile(&mut tiles, right_panes); let bottom_tile = Self::make_tab(&mut tiles, bottom_panes); // Build main horizontal row: [left? | center | right?] let mut main_children = Vec::new(); let mut main_shares = Vec::new(); - if let Some(left) = left_tile { - main_children.push(left); - main_shares.push((left, 1.0)); + for tile in left_tiles { + main_children.push(tile); + main_shares.push((tile, 1.0)); } - if let Some(center) = center_tile { - main_children.push(center); - main_shares.push((center, 4.0)); // center takes most space + for tile in center_tiles { + main_children.push(tile); + main_shares.push((tile, 4.0)); } - if let Some(right) = right_tile { - main_children.push(right); - main_shares.push((right, 1.5)); + for tile in right_tiles { + main_children.push(tile); + main_shares.push((tile, 1.5)); } let root = if main_children.is_empty() && bottom_tile.is_none() { @@ -316,11 +322,43 @@ impl TileLayoutState { if panes.is_empty() { None } else { - // Always wrap in a tab container so every slot has a draggable tab header. Some(tiles.insert_tab_tile(panes)) } } + /// Build tab containers for panels that show tabs. + /// Panels with `hide_tab()` are inserted as plain panes (no tab bar). + fn make_tile( + &self, + tiles: &mut egui_tiles::Tiles, + panes: Vec, + ) -> Vec { + if panes.is_empty() { + return Vec::new(); + } + + // Separate hidden-tab panels from tabbed panels + let mut hidden: Vec = Vec::new(); + let mut tabbed: Vec = Vec::new(); + + for tile_id in panes { + if let Some(egui_tiles::Tile::Pane(pane)) = tiles.get(tile_id) { + if self.panels.get(&pane.panel_id).map_or(false, |p| p.hide_tab()) { + hidden.push(tile_id); + continue; + } + } + tabbed.push(tile_id); + } + + // Wrap tabbed panes in a tab container + let mut result = hidden; + if !tabbed.is_empty() { + result.push(tiles.insert_tab_tile(tabbed)); + } + result + } + /// Focus an existing panel tab by its string ID, re-inserting if closed. pub fn open_or_focus_panel(&mut self, panel_str_id: &str) { let Some(&panel_id) = self.panel_id_map.get(panel_str_id) else { From 870afbc1e389195046b81ba4d9c7d0ae5fbcc935 Mon Sep 17 00:00:00 2001 From: Rod Date: Sun, 12 Apr 2026 14:18:49 +0700 Subject: [PATCH 3/4] fix(dock): disable all_panes_must_have_tabs to allow transparent viewports Set all_panes_must_have_tabs: false so egui_tiles doesn't wrap every pane in a tab container. This allows hide_tab() panels (like 3D scene viewports) to render without any tab bar. Co-authored-by: Qwen-Coder --- src/dock/ui.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dock/ui.rs b/src/dock/ui.rs index b07c333..2ef224b 100644 --- a/src/dock/ui.rs +++ b/src/dock/ui.rs @@ -115,7 +115,7 @@ impl egui_tiles::Behavior for WorkbenchBehavior<'_> { fn simplification_options(&self) -> egui_tiles::SimplificationOptions { egui_tiles::SimplificationOptions { - all_panes_must_have_tabs: true, + all_panes_must_have_tabs: false, ..Default::default() } } From b7b9e9011ebbc1339545d0b18f0a5ab2c1e46fda Mon Sep 17 00:00:00 2001 From: Rod Date: Sun, 12 Apr 2026 21:06:26 +0700 Subject: [PATCH 4/4] feat: disable egui pointer capture for gizmo picking compatibility Add EguiContextSettings with capture_pointer_input: false to the PrimaryEguiContext so that transform-gizmo-bevy can receive pointer events in areas not covered by interactive egui widgets. Also increase center panel layout share from 4.0 to 10.0 for more 3D viewport space. Co-authored-by: Qwen-Coder --- src/dock.rs | 2 +- src/lib.rs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/dock.rs b/src/dock.rs index 7dcb4d0..a919584 100644 --- a/src/dock.rs +++ b/src/dock.rs @@ -305,7 +305,7 @@ impl TileLayoutState { if let Some(bottom) = bottom_tile { // Vertical split: main row on top, bottom panel below let root_id = tiles.insert_vertical_tile(vec![main_row, bottom]); - ui::set_linear_shares(&mut tiles, root_id, &[(main_row, 4.0), (bottom, 1.0)]); + ui::set_linear_shares(&mut tiles, root_id, &[(main_row, 10.0), (bottom, 1.0)]); root_id } else { main_row diff --git a/src/lib.rs b/src/lib.rs index d9d9693..49ec045 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -247,6 +247,13 @@ fn assign_primary_egui_context_system( commands.entity(entity).insert(( bevy_egui::EguiContext::default(), bevy_egui::PrimaryEguiContext, + // Disable egui pointer capture so that gizmo picking + // (transform-gizmo-bevy) can receive pointer events in + // areas not covered by interactive egui widgets. + bevy_egui::EguiContextSettings { + capture_pointer_input: false, + ..default() + }, Name::new("workbench_ui_camera"), inspector::WorkbenchInternal, ));