#2796: ScriptUI panel bars hide while the window fits its dock (#2880)

A docked panel's solid bars, once shown, took their width from the viewport, so a window that exactly filled its dock stayed too large and kept its bars. The bars are now hidden while the window fits its dock (to the whole point, #2585) and stay solid when it overflows (#269).
This commit is contained in:
ash authored and GitHub committed 2026-11-12 11:37:26 +05:30
1 parent 79c42749a8
commit 8a2b4cc633
1 file changed
+96 -3
+96 -3
View File
@@ -608,9 +608,8 @@ pub fn panel(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect, id: u32) {
ui.data_mut(|d| d.insert_temp(told, dock));
acts.push(Act::Resize(id, dock));
}
let content = rect;
egui::ScrollArea::both().id_salt(("scriptui-panel", id)).max_width(content.width()).max_height(content.height()).show(ui, |ui| {
let (r, _) = ui.allocate_exact_size(vec2(w.root.bounds[2] as f32, w.root.bounds[3] as f32), Sense::hover());
let window = vec2(w.root.bounds[2] as f32, w.root.bounds[3] as f32);
panel_scroll(ui, ("scriptui-panel", id), rect.size(), window, |ui, r| {
draw_children(app, ui, r.min, id, &w.root, &mut acts);
mouse_events(ui, r.min, r.intersect(ui.clip_rect()), id, &w.root, !modal, &mut acts);
});
@@ -619,6 +618,36 @@ pub fn panel(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect, id: u32) {
perform(app, &ctx, acts);
}
/// The scroll area a docked ScriptUI panel shows its `window`-sized window in, at most `dock`
/// big, with the app's scroll style (solid bars, #269). `body` draws the window in the rect it is
/// given.
fn panel_scroll<R>(
ui: &mut egui::Ui,
salt: impl std::hash::Hash + std::fmt::Debug,
dock: egui::Vec2,
window: egui::Vec2,
body: impl FnOnce(&mut egui::Ui, Rect) -> R,
) -> egui::scroll_area::ScrollAreaOutput<R> {
egui::ScrollArea::both().id_salt(salt).max_width(dock.x).max_height(dock.y).scroll_bar_visibility(panel_bars(dock, window)).show(ui, |ui| {
let (r, _) = ui.allocate_exact_size(window, Sense::hover());
body(ui, r)
})
}
/// Whether a docked panel's scroll bars may show: never while its window fits the dock.
///
/// The fit is decided from the window and the dock, not from what the scroll area has left. A
/// solid bar, once shown, takes its width from the viewport, so a window that exactly fills its
/// dock was "too large" for the narrower viewport and its bars never went away (#2796). The fit
/// rounds the dock up to the whole point, as egui's scroll area does, because the script is told
/// its dock's size rounded (#2585) and its window can be a fraction of a point bigger. A window
/// that overflows only one way still gets both bars: the first one's width hides the edge of a
/// window laid out at the dock's full size, and the second one scrolls to it.
fn panel_bars(dock: egui::Vec2, window: egui::Vec2) -> egui::scroll_area::ScrollBarVisibility {
use egui::scroll_area::ScrollBarVisibility;
if window.x <= dock.x.ceil() && window.y <= dock.y.ceil() { ScrollBarVisibility::AlwaysHidden } else { ScrollBarVisibility::VisibleWhenNeeded }
}
/// Tab title of a script panel.
pub fn panel_title(app: &EffectcraftApp, id: u32) -> Option<String> {
app.session.script_ui.window(id).map(|w| if w.title.is_empty() { w.script.clone() } else { w.title.clone() })
@@ -640,6 +669,70 @@ pub fn sync_panels(app: &mut EffectcraftApp) {
mod tests {
use super::*;
const DOCK: egui::Vec2 = egui::vec2(300.0, 200.0);
/// Twelve frames of a ScriptUI panel docked in `dock`, with the app's solid scroll bars
/// (#269): its window is `first` big on the first frame and `then` big after that. The last
/// frame's scroll output (viewport and content size).
fn panel_frames(dock: egui::Vec2, first: egui::Vec2, then: egui::Vec2) -> (Rect, egui::Vec2) {
let ctx = egui::Context::default();
ctx.all_styles_mut(|s| s.spacing.scroll = egui::style::ScrollStyle::solid());
let screen = Rect::from_min_size(egui::Pos2::ZERO, vec2(800.0, 600.0));
let mut last = (Rect::NOTHING, egui::Vec2::ZERO);
for frame in 0..12 {
let window = if frame == 0 { first } else { then };
let input = egui::RawInput { screen_rect: Some(screen), time: Some(f64::from(frame) * 0.1), predicted_dt: 0.1, ..Default::default() };
let mut out = ctx.run_ui(input, |ui| {
let mut panel = ui.new_child(UiBuilder::new().max_rect(Rect::from_min_size(egui::Pos2::ZERO, dock)));
let o = panel_scroll(&mut panel, "panel-test", dock, window, |_, _| ());
last = (o.inner_rect, o.content_size);
});
// Nothing paints these frames, and egui expects their texture uploads to be consumed.
out.textures_delta.clear();
}
last
}
/// #2796: a solid bar, once shown, took its width from the viewport, so a window that exactly
/// fills its dock stayed "too large" for what was left and kept its bars for good. A window
/// that overflowed for one frame (a dock dragged smaller before the script learns its size)
/// and then fits gets the whole dock back.
#[test]
fn a_panel_whose_window_fits_hides_its_bars() {
let (viewport, _) = panel_frames(DOCK, DOCK + vec2(40.0, 40.0), DOCK);
assert_eq!(viewport.size(), DOCK, "the bars go away once the window fits");
}
/// The script is told its dock's size rounded (#2585), so in a dock a fraction of a pixel
/// smaller its window is that fraction bigger. That still fits.
#[test]
fn a_panel_in_a_fractional_dock_hides_its_bars() {
let dock = vec2(299.6, 199.6);
let (viewport, _) = panel_frames(dock, dock + vec2(40.0, 40.0), DOCK);
// egui snaps rects to 1/32 of a point; a bar would take 10 points.
assert!((viewport.size() - dock).length() < 0.05, "a window within the whole pixel fits ({viewport:?})");
}
/// #269 holds: a window bigger than its dock keeps solid bars that take their room from the
/// panel, and scrolls.
#[test]
fn a_panel_whose_window_is_too_big_keeps_its_solid_bars() {
let big = DOCK + vec2(120.0, 80.0);
let (viewport, content) = panel_frames(DOCK, big, big);
let bar = egui::style::ScrollStyle::solid().allocated_width();
assert_eq!(viewport.size(), DOCK - vec2(bar, bar), "solid bars on both axes");
assert_eq!(content, big, "the whole window is scrollable");
}
#[test]
fn panel_bars_only_show_for_a_window_bigger_than_its_dock() {
use egui::scroll_area::ScrollBarVisibility::{AlwaysHidden, VisibleWhenNeeded};
assert_eq!(panel_bars(DOCK, DOCK), AlwaysHidden);
assert_eq!(panel_bars(vec2(299.4, 199.4), DOCK), AlwaysHidden);
assert_eq!(panel_bars(DOCK, DOCK + vec2(1.0, 0.0)), VisibleWhenNeeded);
assert_eq!(panel_bars(DOCK, DOCK + vec2(0.0, 1.0)), VisibleWhenNeeded);
}
/// Script colours are sRGB, not linear: `#2B2B2B` drew as RGB 114 when read as linear light.
#[test]
fn draw_colours_are_srgb() {