mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Fix #3443: the Layers list's scroll bar no longer covers the rows' selection marks
The Layers list scrolls in its own scroll area, which kept egui's floating scroll bar when #3093 gave the panel bodies a bar in a column of their own. Hovering the list showed the bar over the right edge of the rows, over the target circles and the selection squares the user clicks to select a layer. The list now uses the menus' slim solid bar (list_scroll_style) in its own column, and the rows end beside it. The list knows its total height before it lays the rows out, so the bar is shown exactly when the rows overflow (decided up front): the rows never shift sideways a frame after the bar appears. Test: dock::tests::the_layers_scroll_bar_covers_no_row (40 layers in a 1440x600 window: no row's accessibility rect intersects the bar). It fails without the change and passes with it. The Layers tests that pin row positions across frames still pass. Checked live: the bar sits beside the rows.
This commit is contained in:
committed by
bflatastic
parent
bbc9bd3de2
commit
7141932dfb
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user