Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 58 additions & 15 deletions src/dock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,17 @@ 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<egui::Color32> {
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
Expand Down Expand Up @@ -252,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() {
Expand All @@ -294,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
Expand All @@ -311,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<PaneEntry>,
panes: Vec<egui_tiles::TileId>,
) -> Vec<egui_tiles::TileId> {
if panes.is_empty() {
return Vec::new();
}

// Separate hidden-tab panels from tabbed panels
let mut hidden: Vec<egui_tiles::TileId> = Vec::new();
let mut tabbed: Vec<egui_tiles::TileId> = 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 {
Expand Down
11 changes: 9 additions & 2 deletions src/dock/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ impl egui_tiles::Behavior<PaneEntry> 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()
{
Expand Down Expand Up @@ -111,7 +115,7 @@ impl egui_tiles::Behavior<PaneEntry> 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()
}
}
Expand Down Expand Up @@ -190,7 +194,10 @@ pub fn tiles_ui_system(world: &mut World) {
let mut closed_panel_ids: Vec<String> = 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),
Expand Down
7 changes: 7 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
));
Expand Down