From e2689114dc569b833581658cd0a5d8450c1e0b2a Mon Sep 17 00:00:00 2001 From: multiplex55 <6619098+multiplex55@users.noreply.github.com> Date: Fri, 9 Jan 2026 17:37:50 -0500 Subject: [PATCH 1/2] Enable two-axis scroll for dashboard widgets --- src/dashboard/dashboard.rs | 81 +++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/src/dashboard/dashboard.rs b/src/dashboard/dashboard.rs index 19673d87..9d345f40 100644 --- a/src/dashboard/dashboard.rs +++ b/src/dashboard/dashboard.rs @@ -325,7 +325,7 @@ impl Dashboard { slot.slot.row, slot.slot.col, )); - let scroll_area = egui::ScrollArea::vertical() + let scroll_area = egui::ScrollArea::both() .id_source(scroll_id) .auto_shrink([false; 2]) .max_height(body_height) @@ -334,7 +334,9 @@ impl Dashboard { SCROLL_VISIBILITY_RECORDS.lock().unwrap().push(visibility); scroll_area - .show_viewport(ui, |ui, _viewport| { + .show_viewport(ui, |ui, viewport| { + #[cfg(test)] + SCROLL_VIEWPORTS.lock().unwrap().push(viewport); ui.set_clip_rect(ui.clip_rect().intersect(slot_clip)); ui.set_min_height(body_height); Self::render_widget_content(slot, ui, ctx, activation) @@ -365,6 +367,8 @@ enum OverflowPolicy { #[cfg(test)] static SCROLL_VISIBILITY_RECORDS: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); +#[cfg(test)] +static SCROLL_VIEWPORTS: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); #[cfg(test)] mod tests { @@ -395,6 +399,9 @@ mod tests { label: String, } + #[derive(Default)] + struct OverflowWidget; + #[derive(Clone, Copy)] struct SlotRecord { clip: egui::Rect, @@ -402,6 +409,7 @@ mod tests { static RECORDS: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); static RENDERS: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); + static OVERFLOW_RECORDS: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); static CREATED: AtomicUsize = AtomicUsize::new(0); static UPDATED: AtomicUsize = AtomicUsize::new(0); @@ -443,6 +451,21 @@ mod tests { } } + impl Widget for OverflowWidget { + fn render( + &mut self, + ui: &mut egui::Ui, + _ctx: &DashboardContext<'_>, + _activation: WidgetActivation, + ) -> Option { + let rect = ui + .allocate_space(egui::vec2(320.0, 180.0)) + .rect; + OVERFLOW_RECORDS.lock().unwrap().push(rect); + None + } + } + fn take_records() -> Vec { std::mem::take(&mut *RECORDS.lock().unwrap()) } @@ -455,6 +478,14 @@ mod tests { std::mem::take(&mut *SCROLL_VISIBILITY_RECORDS.lock().unwrap()) } + fn take_scroll_viewports() -> Vec { + std::mem::take(&mut *SCROLL_VIEWPORTS.lock().unwrap()) + } + + fn take_overflow_records() -> Vec { + std::mem::take(&mut *OVERFLOW_RECORDS.lock().unwrap()) + } + fn recording_registry() -> WidgetRegistry { let mut reg = WidgetRegistry::default(); reg.register( @@ -464,6 +495,12 @@ mod tests { reg } + fn overflow_registry() -> WidgetRegistry { + let mut reg = WidgetRegistry::default(); + reg.register("overflow", WidgetFactory::new(|_: RecordingConfig| OverflowWidget)); + reg + } + fn updating_registry() -> WidgetRegistry { let mut reg = WidgetRegistry::default(); reg.register( @@ -652,6 +689,46 @@ mod tests { assert!(take_scroll_visibilities().is_empty()); } + #[test] + fn scroll_area_allows_horizontal_and_vertical_overflow() { + take_scroll_visibilities(); + take_scroll_viewports(); + take_overflow_records(); + let cfg = DashboardConfig { + version: 1, + grid: GridConfig { rows: 1, cols: 1 }, + slots: vec![SlotConfig { + overflow: OverflowMode::Scroll, + ..SlotConfig::with_widget("overflow", 0, 0) + }], + }; + let tmp = tempfile::NamedTempFile::new().unwrap(); + cfg.save(tmp.path()).unwrap(); + + let registry = overflow_registry(); + let mut dashboard = dashboard_with_config(tmp.path(), registry); + let plugins = PluginManager::new(); + let data_cache = DashboardDataCache::new(); + let ctx = dashboard_context(&plugins, &data_cache); + + egui::__run_test_ui(|ui| { + let rect = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(120.0, 80.0)); + ui.allocate_ui_at_rect(rect, |ui| { + dashboard.ui(ui, &ctx, WidgetActivation::Click); + }); + }); + + let viewports = take_scroll_viewports(); + let overflow_records = take_overflow_records(); + assert_eq!(viewports.len(), 1); + assert_eq!(overflow_records.len(), 1); + let viewport = viewports[0]; + let overflow = overflow_records[0]; + assert!(overflow.width() > viewport.width()); + assert!(overflow.height() > viewport.height()); + assert_eq!(take_scroll_visibilities(), vec![ScrollBarVisibility::AlwaysVisible]); + } + #[test] fn reuses_widget_instances_on_reload() { CREATED.store(0, Ordering::SeqCst); From 84eabd4c3e180980b20e91f8bd0262a783fbbb82 Mon Sep 17 00:00:00 2001 From: multiplex55 <6619098+multiplex55@users.noreply.github.com> Date: Fri, 9 Jan 2026 17:45:28 -0500 Subject: [PATCH 2/2] Fix scroll test allocation for egui response --- src/dashboard/dashboard.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dashboard/dashboard.rs b/src/dashboard/dashboard.rs index 9d345f40..e2b3881b 100644 --- a/src/dashboard/dashboard.rs +++ b/src/dashboard/dashboard.rs @@ -337,6 +337,8 @@ impl Dashboard { .show_viewport(ui, |ui, viewport| { #[cfg(test)] SCROLL_VIEWPORTS.lock().unwrap().push(viewport); + #[cfg(not(test))] + let _ = viewport; ui.set_clip_rect(ui.clip_rect().intersect(slot_clip)); ui.set_min_height(body_height); Self::render_widget_content(slot, ui, ctx, activation) @@ -458,9 +460,7 @@ mod tests { _ctx: &DashboardContext<'_>, _activation: WidgetActivation, ) -> Option { - let rect = ui - .allocate_space(egui::vec2(320.0, 180.0)) - .rect; + let (_, rect) = ui.allocate_space(egui::vec2(320.0, 180.0)); OVERFLOW_RECORDS.lock().unwrap().push(rect); None }