Merge pull request #3554 from Taha-Mahmoodi/fix/3443-layers-scrollbar

Fix #3443: the Layers list's scroll bar no longer covers the rows' selection marks
This commit is contained in:
BFlat
2026-10-22 16:50:18 +07:00
committed by GitHub
2 changed files with 64 additions and 9 deletions
+43
View File
@@ -723,6 +723,49 @@ mod tests {
assert!(covered.is_empty(), "under the scroll bar at {bar:?}: {covered:#?}");
}
/// #3443: the Layers list's scroll bar sits beside the rows, not over the target and selection
/// marks at their right edge.
#[test]
fn the_layers_scroll_bar_covers_no_row() {
use egui::accesskit::Role;
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 300, "height": 300})).unwrap();
for k in 0..40 {
app.run("shape.rectangle", json!({"x": k, "y": k, "width": 20, "height": 20})).unwrap();
}
app.run("window.panel", json!({"panel": "layers"})).unwrap();
let ctx = egui::Context::default();
ctx.enable_accesskit();
theme::install_fonts(&ctx);
let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, vec2(1440.0, 600.0));
let mut nodes = vec![];
for k in 0..4 {
let input = egui::RawInput { time: Some(f64::from(k) * 0.1), screen_rect: Some(screen), ..Default::default() };
let mut out = ctx.run_ui(input, |ui| show(&mut app, ui));
out.textures_delta.clear();
nodes = out.platform_output.accesskit_update.unwrap().nodes;
}
let rect = |n: &egui::accesskit::Node| {
n.bounds().map(|b| egui::Rect::from_min_max(egui::pos2(b.x0 as f32, b.y0 as f32), egui::pos2(b.x1 as f32, b.y1 as f32)))
};
let bar = nodes
.iter()
.filter(|(_, n)| n.role() == Role::ScrollBar)
.filter_map(|(_, n)| rect(n))
.max_by(|a, b| a.left().total_cmp(&b.left()))
.filter(|bar| bar.left() > screen.right() - DOCK_WIDTH)
.expect("40 layers scroll in a 600 pt window");
let rows: Vec<egui::Rect> = nodes
.iter()
.filter(|(_, n)| n.role() != Role::ScrollBar)
.filter_map(|(_, n)| rect(n))
.filter(|r| r.top() >= bar.top() && r.bottom() <= bar.bottom() && r.right() > bar.left() - 40.0)
.collect();
assert!(!rows.is_empty(), "the rows beside the bar are in the tree");
let covered: Vec<_> = rows.iter().filter(|r| r.intersect(bar).is_positive()).collect();
assert!(covered.is_empty(), "under the scroll bar at {bar:?}: {covered:#?}");
}
const SCREEN: egui::Vec2 = vec2(1600.0, 1000.0);
/// A frame of the dock and its flyout on a 1600 × 1000 window.
+21 -9
View File
@@ -613,15 +613,27 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
let revealed = reveal.and_then(|id| list.iter().position(|r| r.node.id == id));
let scrolled = ui.scope(|ui| {
ui.spacing_mut().item_spacing.y = 0.0;
egui::ScrollArea::vertical().max_height(list_h).auto_shrink([false, false]).show_rows(ui, view.h, list.len(), |ui, range| {
if let Some(i) = revealed.filter(|i| !range.contains(i)) {
let top = ui.max_rect().top() + (i as f32 - range.start as f32) * view.h;
ui.scroll_to_rect(egui::Rect::from_min_size(egui::pos2(ui.max_rect().left(), top), vec2(ui.max_rect().width(), view.h)), None);
}
for r in list.get(range).unwrap_or_default() {
row(ui, &view, r, &mut out);
}
})
// The bar takes a column of its own, so it never covers the target and selection marks at
// the rows' right edge (#3443). Whether the rows overflow is known before they are laid
// out, so the bar is decided up front and the rows never shift when it appears.
ui.spacing_mut().scroll = crate::widgets::list_scroll_style();
let overflows = view.h * list.len() as f32 > list_h;
let bar =
if overflows { egui::scroll_area::ScrollBarVisibility::AlwaysVisible } else { egui::scroll_area::ScrollBarVisibility::AlwaysHidden };
egui::ScrollArea::vertical().max_height(list_h).auto_shrink([false, false]).scroll_bar_visibility(bar).show_rows(
ui,
view.h,
list.len(),
|ui, range| {
if let Some(i) = revealed.filter(|i| !range.contains(i)) {
let top = ui.max_rect().top() + (i as f32 - range.start as f32) * view.h;
ui.scroll_to_rect(egui::Rect::from_min_size(egui::pos2(ui.max_rect().left(), top), vec2(ui.max_rect().width(), view.h)), None);
}
for r in list.get(range).unwrap_or_default() {
row(ui, &view, r, &mut out);
}
},
)
});
let list_rect = scrolled.inner.inner_rect;
resolve_click(ui, &view, &list, &mut out);