From 922e7e621ca2d510fdd30db55917ed81f07a40bf Mon Sep 17 00:00:00 2001 From: Yannick de Jong Date: Wed, 14 Oct 2026 10:32:08 +0200 Subject: [PATCH] Scroll the call window's tiles when more people are in than fit All of them at the size the room's grid gives, down the grid, or sideways in the row under a share. --- src/ui/call_window.rs | 97 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 3 deletions(-) diff --git a/src/ui/call_window.rs b/src/ui/call_window.rs index 00fd530..ef8ad72 100644 --- a/src/ui/call_window.rs +++ b/src/ui/call_window.rs @@ -270,10 +270,15 @@ pub fn capacity(area: Vec2) -> usize { pub struct Layout { /// The share's area, if one is shown. pub share: Option, - /// Each tile. + /// Each tile, as many as there is room for. pub tiles: Vec, /// How many tiles there is room for. pub room: usize, + /// The tiles' part of the stage. + pub area: Rect, + /// The tiles are in a row under the share (a tall stage): more of + /// them than fit scroll sideways. + pub sideways: bool, } /// Lays out the stage for a share (or none) and `tiles` tiles: without a @@ -328,9 +333,35 @@ pub fn layout(stage: Rect, share: bool, tiles: usize) -> Layout { share, tiles: rects, room, + area, + sideways: area.top() > stage.top(), } } +/// Where every one of `count` tiles goes when only `room` of them fit +/// `area`: all at the size the room's grid gives them, in that grid's +/// columns (or one row, `sideways`), from the top left; and the size of +/// the whole, for scrolling to the rest. +pub fn overflow(count: usize, room: usize, area: Vec2, sideways: bool) -> (Vec, Vec2) { + let (columns, _, size) = grid(room.max(1), area); + let columns = if sideways { count.max(1) } else { columns }; + let rows = count.max(1).div_ceil(columns); + let rects = (0..count) + .map(|i| { + let (column, row) = (i % columns, i / columns); + Rect::from_min_size( + egui::pos2(column as f32 * (size.x + GAP), row as f32 * (size.y + GAP)), + size, + ) + }) + .collect(); + let whole = Vec2::new( + columns as f32 * size.x + (columns - 1) as f32 * GAP, + rows as f32 * size.y + (rows - 1) as f32 * GAP, + ); + (rects, whole) +} + /// A tile's size in pixels as the session hears it: rounded up to /// [`TILE_STEP`]. pub fn tile_pixels(size: Vec2, pixels_per_point: f32) -> [u32; 2] { @@ -403,8 +434,13 @@ pub fn show( if let Some(share) = layout.share { shown.share = share_stage(ui, view, share, pixels); } - for (rect, tile) in layout.tiles.iter().zip(&view.tiles) { - camera_tile(ui, palette, *rect, tile, view.no_video); + if view.tiles.len() > layout.room { + // More than fit: all of them, scrolled to. + scrolled_tiles(ui, palette, view, &layout); + } else { + for (rect, tile) in layout.tiles.iter().zip(&view.tiles) { + camera_tile(ui, palette, *rect, tile, view.no_video); + } } if layout.share.is_none() && view.tiles.is_empty() { note(ui, area, &t("No one has their camera on")); @@ -421,6 +457,40 @@ pub fn show( shown } +/// Every tile when more than fit the tiles' part of the stage: at the +/// size the room's grid gives them, the rest scrolled to (down, or +/// sideways under a share). +fn scrolled_tiles(ui: &mut egui::Ui, palette: &Palette, view: &CallView, layout: &Layout) { + let (rects, whole) = overflow( + view.tiles.len(), + layout.room, + layout.area.size(), + layout.sideways, + ); + ui.scope_builder(egui::UiBuilder::new().max_rect(layout.area), |ui| { + let scroll = if layout.sideways { + egui::ScrollArea::horizontal() + } else { + egui::ScrollArea::vertical() + }; + scroll + .id_salt("call-window-tiles") + .auto_shrink(false) + .show(ui, |ui| { + let (origin, _) = ui.allocate_exact_size(whole, Sense::hover()); + for (rect, tile) in rects.iter().zip(&view.tiles) { + camera_tile( + ui, + palette, + rect.translate(origin.min.to_vec2()), + tile, + view.no_video, + ); + } + }); + }); +} + /// The share in its part of the stage; gives the size it is shown at. fn share_stage(ui: &mut egui::Ui, view: &CallView, area: Rect, pixels: f32) -> [usize; 2] { match view.picture { @@ -880,6 +950,27 @@ mod tests { assert_eq!(capacity(Vec2::new(5000.0, 3000.0)), huddles::MAX_TILES); } + #[test] + fn more_tiles_than_fit_keep_the_grids_columns_and_scroll_down() { + let area = Vec2::new(640.0, 360.0); + let room = capacity(area); + let (columns, _, size) = grid(room, area); + let (rects, whole) = overflow(14, room, area, false); + assert_eq!(rects.len(), 14); + assert!(rects.iter().all(|r| r.size() == size)); + assert_eq!( + rects[columns].min.x, 0.0, + "the second row starts at the left" + ); + assert!(rects[columns].min.y > rects[0].max.y); + assert!(whole.y > area.y, "there is something to scroll to"); + assert!(whole.x <= area.x); + // Under a share: one row, scrolled sideways. + let (row, whole) = overflow(5, 2, Vec2::new(700.0, 200.0), true); + assert!(row.iter().all(|r| r.min.y == 0.0)); + assert!(whole.x > 700.0); + } + #[test] fn tiles_go_beside_a_share_in_a_wide_window_and_below_in_a_tall_one() { let wide = layout(rect(1264.0, 732.0), true, 3);