From 7612b220efa63b5b5a2ee6c22ad284182581571d Mon Sep 17 00:00:00 2001 From: bflatastic Date: Thu, 8 Oct 2026 11:14:02 +0700 Subject: [PATCH] Project panel: a panel too short for the scroll bar draws (#231) The Project panel's vertical scroll bar clamped its thumb between 16 points and the track's height. When the track was shorter than 16 points (an empty project in a 1280 x 800 browser window at device pixel ratio 2, or any short panel), that clamp panicked on the first frame: the browser canvas went blank and the app stopped responding. The thumb is now at most the track's length, and both scroll bars share the calculation. Test: a_short_project_panel_draws (ui_project_rows). Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/panels/project.rs | 10 ++++++++-- crates/ui-egui/tests/ui_project_rows.rs | 12 ++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/panels/project.rs b/crates/ui-egui/src/panels/project.rs index 8d84e71d0..c14b81312 100644 --- a/crates/ui-egui/src/panels/project.rs +++ b/crates/ui-egui/src/panels/project.rs @@ -174,6 +174,12 @@ fn fit(r: Rect, w: f32, h: f32) -> Rect { Rect::from_center_size(r.center(), vec2(w * s, h * s)) } +/// A scroll bar thumb's length on a `track` long: the share of `content` in view, at least 16 +/// points but never longer than the track (a panel only a few points tall still draws, #231). +fn thumb_len(track: f32, visible: f32, content: f32) -> f32 { + (track * visible / content).max(16.0).min(track).max(0.0) +} + /// An inline text edit in progress: (item, field `name`|`comment`, text). type Editing = (u64, String, String); fn edit_id() -> egui::Id { @@ -340,7 +346,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { } if overflow > 0.0 { let track = Rect::from_min_max(pos2(opt_x0, list.max.y - 5.0), pos2(rect.max.x - 4.0, list.max.y - 1.0)); - let tw = (track.width() * track.width() / (track.width() + overflow)).max(16.0); + let tw = thumb_len(track.width(), track.width(), track.width() + overflow); let thumb = Rect::from_min_size(pos2(track.min.x + (track.width() - tw) * (hscroll / overflow), track.min.y), vec2(tw, track.height())); let sresp = ui.interact(track.expand2(vec2(0.0, 2.0)), egui::Id::new("proj-hscroll"), Sense::drag()); p.rect_filled(track, 2.0, t.field_bg); @@ -396,7 +402,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let scroll = app.ui.project_scroll; if max_scroll > 0.0 { let track = Rect::from_min_max(pos2(rect.max.x - 6.0, list.min.y + 1.0), pos2(rect.max.x - 2.0, list.max.y - 7.0)); - let th = (track.height() * list.height() / content_h).clamp(16.0, track.height()); + let th = thumb_len(track.height(), list.height(), content_h); let thumb = Rect::from_min_size(pos2(track.min.x, track.min.y + (track.height() - th) * (scroll / max_scroll)), vec2(track.width(), th)); let vresp = ui.interact(track.expand2(vec2(2.0, 0.0)), egui::Id::new("proj-vscroll"), Sense::drag()); p.rect_filled(thumb, 2.0, if vresp.hovered() || vresp.dragged() { t.text_dim } else { t.text_faint }); diff --git a/crates/ui-egui/tests/ui_project_rows.rs b/crates/ui-egui/tests/ui_project_rows.rs index 074b41b3e..1fc076686 100644 --- a/crates/ui-egui/tests/ui_project_rows.rs +++ b/crates/ui-egui/tests/ui_project_rows.rs @@ -119,3 +119,15 @@ fn dragging_in_the_empty_area_box_selects_items() { click_at(&mut h, from); assert!(h.state().session.state.project_selection.is_empty()); } + +/// A Project panel shorter than its scroll bar's thumb draws instead of panicking: an empty +/// project in a 1280 × 800 browser window at device pixel ratio 2 (640 × 400 points) left the +/// list too short for the thumb's 16-point minimum (#231). +#[test] +fn a_short_project_panel_draws() { + for height in [400.0, 300.0] { + let mut h = Harness::builder().with_size(egui::vec2(640.0, height)).build_eframe(|_| EffectcraftApp::new(Session::default())); + h.run_steps(3); + assert!(h.state().auto.find("project.empty").is_some(), "the Project panel drew at {height} points"); + } +}