Skip to content

Widget changed layer_id during the frame panic when dragging tab into floating window (only debug builds) #317

@mcthesw

Description

@mcthesw

Describe the bug
In debug builds, dragging a tab out of the main dock to create a floating window panics with:

thread 'main' panicked at egui-0.34.x/src/widget_rect.rs:NNN:
DEBUG ASSERT: Widget XXXX changed layer_id during the frame from LayerId { Background ... } to LayerId { Middle ... }

To Reproduce
requires egui_dock with debug assertions enabled

// [dependencies]
// egui = "0.34"
// egui_dock = "0.19"

use egui::{Id, Pos2, RawInput, Rect, Vec2};
use egui_dock::{DockArea, DockState, NodeIndex, SurfaceIndex, TabIndex, TabPath, TabViewer};

struct Tab(u8);
struct Viewer;

impl TabViewer for Viewer {
    type Tab = Tab;

    fn title(&mut self, tab: &mut Self::Tab) -> egui::WidgetText {
        format!("Tab {}", tab.0).into()
    }

    // Both tabs return the same id — the default impl derives from title(),
    // so any two tabs with the same title will also reproduce this.
    fn id(&mut self, _tab: &mut Self::Tab) -> Id {
        Id::new("same-tab-id")
    }

    fn ui(&mut self, ui: &mut egui::Ui, tab: &mut Self::Tab) {
        ui.label(format!("content {}", tab.0));
    }
}

fn main() {
    let ctx = egui::Context::default();
    let mut dock_state = DockState::new(vec![Tab(0), Tab(1)]);

    dock_state.detach_tab(
        TabPath {
            surface: SurfaceIndex::main(),
            node: NodeIndex::root(),
            tab: TabIndex(0),
        },
        Rect::from_min_size(Pos2::new(50.0, 50.0), Vec2::new(200.0, 150.0)),
    );

    let mut input = RawInput::default();
    input.screen_rect = Some(Rect::from_min_size(Pos2::ZERO, Vec2::new(800.0, 600.0)));

    // Panics in debug builds:
    //   Widget XXXX changed layer_id from LayerId { Background ... } to LayerId { Middle ... }
    let _ = ctx.run_ui(input, |ui| {
        DockArea::new(&mut dock_state).show_inside(ui, &mut Viewer);
    });
}
Image

Expected behavior
No panic.

Additional context
The panic originates in tab_body in leaf.rs. The tab body Ui is constructed with:

let id = self.id.with(tab_viewer.id(tab));

This ID doesn't include the surface index, so if the main dock surface and a detached floating window surface both render a tab with the same TabViewer::id in the same frame (which happens during/after an undock), egui sees the same widget ID registered in two different layers (Background for the dock, Middle for the window) and fires the assertion.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions