From ecbbba4f2e78ee0e5b0abac3cc978fa4e85a3521 Mon Sep 17 00:00:00 2001 From: vangel76 Date: Fri, 16 Oct 2026 20:31:51 +0200 Subject: [PATCH 1/3] Panels: floating panel groups paint an opaque background (#2421) The shared dock left its pane and tab strip backgrounds transparent because the docked card behind them is filled. Floating groups lie on the canvas, so the canvas showed through. They now paint the panel colour, which is what the card shows when docked. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/panel_docking.rs | 8 ++++--- crates/ui-egui/src/panel_docking_tests.rs | 28 ++++++++++++++++++++++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/crates/ui-egui/src/panel_docking.rs b/crates/ui-egui/src/panel_docking.rs index d01858d3c..9cb1cdbf6 100644 --- a/crates/ui-egui/src/panel_docking.rs +++ b/crates/ui-egui/src/panel_docking.rs @@ -644,9 +644,11 @@ pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) -> bool { } let mut style = craft_ui::docking::DockStyle::from_ui(ui); style.tab_height = 33.0; - // The dock is a card (`theme::card`): its panes and tab strip lie on the card's colour. - style.background = egui::Color32::TRANSPARENT; - style.tab_background = egui::Color32::TRANSPARENT; + // The dock is a card (`theme::card`) filled with the panel colour. Floating groups lie on + // the canvas, not on that card, so panes and tab strip paint the same colour themselves; + // transparent they showed the canvas through (#2421). + style.background = t.panel; + style.tab_background = t.panel; style.active_background = t.button; style.text = t.text_strong; style.inactive_text = t.text_dim; diff --git a/crates/ui-egui/src/panel_docking_tests.rs b/crates/ui-egui/src/panel_docking_tests.rs index c66623e75..9eccbf0e8 100644 --- a/crates/ui-egui/src/panel_docking_tests.rs +++ b/crates/ui-egui/src/panel_docking_tests.rs @@ -1,5 +1,5 @@ use super::*; -use egui::{Event, Modifiers, PointerButton, Pos2, vec2}; +use egui::{Event, Modifiers, PointerButton, Pos2, pos2, vec2}; use egui_kittest::{Harness, kittest::Queryable}; fn app() -> VectorcraftApp { @@ -614,3 +614,29 @@ fn the_window_menu_item_hides_a_shown_panel_and_shows_it_again() { app.run("window.panel", json!({"panel":"properties"})).unwrap(); assert!(shown(&app), "without the menu's toggle the command only shows the panel"); } + +/// A floating group lies on the canvas, not on the dock's card, so its tab strip and body paint +/// their own opaque background (#2421). +#[test] +fn floating_group_paints_an_opaque_background() { + let mut app = app(); + app.run("window.panel.float", json!({"panel":"properties","x":400,"y":200,"width":300,"height":420})).unwrap(); + let h = harness(app, vec2(1280.0, 800.0)); + // Paint order: the last big filled rectangle over a point decides what the user sees there + // (outlines and small widgets don't count). + fn topmost(shape: &egui::Shape, p: Pos2, found: &mut Option) { + match shape { + egui::Shape::Rect(r) if r.rect.contains(p) && r.rect.width() >= 200.0 && r.stroke.is_empty() => *found = Some(r.fill), + egui::Shape::Vec(v) => v.iter().for_each(|s| topmost(s, p, found)), + _ => {} + } + } + let opaque_at = |p: Pos2| { + let mut found = None; + h.output().shapes.iter().for_each(|s| topmost(&s.shape, p, &mut found)); + found.is_some_and(|c| c.a() == 255) + }; + // Tab strip to the right of the tabs, and the body's empty bottom-right corner. + assert!(opaque_at(pos2(690.0, 210.0)), "tab strip is transparent"); + assert!(opaque_at(pos2(690.0, 610.0)), "body is transparent"); +} From 47b6b3285db2252ebe0d697e7876f0046979aabe Mon Sep 17 00:00:00 2001 From: bflatastic Date: Sat, 17 Oct 2026 12:50:24 +0700 Subject: [PATCH 2/3] Panels: floating groups get an opaque fill beneath them; the docked card keeps its rounded corners (#2421) --- crates/ui-egui/src/panel_docking.rs | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/crates/ui-egui/src/panel_docking.rs b/crates/ui-egui/src/panel_docking.rs index 9cb1cdbf6..7e1df99c4 100644 --- a/crates/ui-egui/src/panel_docking.rs +++ b/crates/ui-egui/src/panel_docking.rs @@ -625,6 +625,17 @@ pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) -> bool { display .floating .retain(|group| !app.ui.floating_panels.iter().any(|native| decorated(native) && native.panels.iter().any(|id| group.panels.contains(id)))); + // Floating groups lie on the canvas, not on the dock's card: reserve a fill under each one + // before the dock draws it, sized once it is placed, so the canvas doesn't show through (#2421). + let fills: Vec<_> = display + .floating + .iter() + .filter_map(|group| group.panels.first()) + .map(|first| { + let layer = egui::LayerId::new(egui::Order::Middle, area.with(("floating", first))); + (layer, ui.ctx().layer_painter(layer).add(egui::Shape::Noop)) + }) + .collect(); let mut draw = |ui: &mut egui::Ui| { if layout.root.is_some() && crate::dock::collapse_header(ui, collapsed) { let _ = app.run("window.collapseDock", json!({"collapsed":!collapsed})); @@ -644,11 +655,10 @@ pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) -> bool { } let mut style = craft_ui::docking::DockStyle::from_ui(ui); style.tab_height = 33.0; - // The dock is a card (`theme::card`) filled with the panel colour. Floating groups lie on - // the canvas, not on that card, so panes and tab strip paint the same colour themselves; - // transparent they showed the canvas through (#2421). - style.background = t.panel; - style.tab_background = t.panel; + // The dock is a card (`theme::card`): its panes and tab strip lie on the card's colour + // (square fills would cover its rounded corners). Floating groups get the fills below. + style.background = egui::Color32::TRANSPARENT; + style.tab_background = egui::Color32::TRANSPARENT; style.active_background = t.button; style.text = t.text_strong; style.inactive_text = t.text_dim; @@ -661,7 +671,7 @@ pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) -> bool { style.resize_label = tl!("Resize panels").to_string(); style.resize_window_label = tl!("Resize panel window").to_string(); let mut content = PanelContent { app, commands: Vec::new() }; - let output = craft_ui::docking::DockArea::new(egui::Id::new("vectorcraft-panel-docking")).show_customized( + let output = craft_ui::docking::DockArea::new(area).show_customized( ui, &display, &style, @@ -756,6 +766,11 @@ pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) -> bool { ui.ctx().data_mut(|data| data.insert_temp(crate::floating::group_rect_id(first), rect)); } } + for (layer, fill) in fills { + if let Some(rect) = ui.ctx().memory(|memory| memory.area_rect(layer.id)) { + ui.ctx().layer_painter(layer).set(fill, egui::Shape::rect_filled(rect, 0.0, t.panel)); + } + } icon_rail(app, ui); crate::floating::docked_columns(app, ui); true From 39979d47531d6125b948becead089bba8a59e6e4 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Sat, 17 Oct 2026 13:20:19 +0700 Subject: [PATCH 3/3] Tests: the floating-panel test skips fills that paint nothing (#2421) --- crates/ui-egui/src/panel_docking_tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/panel_docking_tests.rs b/crates/ui-egui/src/panel_docking_tests.rs index 9eccbf0e8..f0a556d51 100644 --- a/crates/ui-egui/src/panel_docking_tests.rs +++ b/crates/ui-egui/src/panel_docking_tests.rs @@ -623,10 +623,10 @@ fn floating_group_paints_an_opaque_background() { app.run("window.panel.float", json!({"panel":"properties","x":400,"y":200,"width":300,"height":420})).unwrap(); let h = harness(app, vec2(1280.0, 800.0)); // Paint order: the last big filled rectangle over a point decides what the user sees there - // (outlines and small widgets don't count). + // (outlines, small widgets and fully transparent fills, which paint nothing, don't count). fn topmost(shape: &egui::Shape, p: Pos2, found: &mut Option) { match shape { - egui::Shape::Rect(r) if r.rect.contains(p) && r.rect.width() >= 200.0 && r.stroke.is_empty() => *found = Some(r.fill), + egui::Shape::Rect(r) if r.rect.contains(p) && r.rect.width() >= 200.0 && r.stroke.is_empty() && r.fill.a() > 0 => *found = Some(r.fill), egui::Shape::Vec(v) => v.iter().for_each(|s| topmost(s, p, found)), _ => {} }