From f78a87c4091d252ec45907e00164dc2c2c649367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 3 May 2025 04:15:18 +0200 Subject: [PATCH] Show embedded scrollbars only when necessary in `scrollable` --- examples/tour/src/main.rs | 12 ++- examples/websocket/src/main.rs | 1 + widget/src/scrollable.rs | 137 ++++++++++++++++++++++----------- 3 files changed, 100 insertions(+), 50 deletions(-) diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index f34c9da0..5d31cbd7 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -168,19 +168,17 @@ impl Tour { Screen::End => self.end(), }; - let content: Element<_> = column![screen, controls,] - .max_width(540) - .spacing(20) - .padding(20) - .into(); + let content: Element<_> = + column![screen, controls].max_width(540).spacing(20).into(); let scrollable = scrollable(center_x(if self.debug { content.explain(Color::BLACK) } else { content - })); + })) + .spacing(10); - center_y(scrollable).into() + center_y(scrollable).padding(10).into() } fn can_continue(&self) -> bool { diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index 069ca391..c52de37e 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -104,6 +104,7 @@ impl WebSocket { ) .id(MESSAGE_LOG.clone()) .height(Fill) + .spacing(10) .into() }; diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 72019137..51286ae7 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -426,55 +426,104 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let (right_padding, bottom_padding) = match self.direction { + let mut layout = |right_padding, bottom_padding| { + layout::padded( + limits, + self.width, + self.height, + Padding { + right: right_padding, + bottom: bottom_padding, + ..Padding::ZERO + }, + |limits| { + let child_limits = layout::Limits::new( + Size::new(limits.min().width, limits.min().height), + Size::new( + if self.direction.horizontal().is_some() { + f32::INFINITY + } else { + limits.max().width + }, + if self.direction.vertical().is_some() { + f32::INFINITY + } else { + limits.max().height + }, + ), + ); + + self.content.as_widget().layout( + &mut tree.children[0], + renderer, + &child_limits, + ) + }, + ) + }; + + match self.direction { Direction::Vertical(Scrollbar { width, margin, spacing: Some(spacing), .. - }) => (width + margin * 2.0 + spacing, 0.0), - Direction::Horizontal(Scrollbar { + }) + | Direction::Horizontal(Scrollbar { width, margin, spacing: Some(spacing), .. - }) => (0.0, width + margin * 2.0 + spacing), - _ => (0.0, 0.0), - }; + }) => { + let is_vertical = + matches!(self.direction, Direction::Vertical(_)); - layout::padded( - limits, - self.width, - self.height, - Padding { - right: right_padding, - bottom: bottom_padding, - ..Padding::ZERO - }, - |limits| { - let child_limits = layout::Limits::new( - Size::new(limits.min().width, limits.min().height), - Size::new( - if self.direction.horizontal().is_some() { - f32::INFINITY - } else { - limits.max().width - }, - if self.direction.vertical().is_some() { - f32::INFINITY - } else { - limits.max().height - }, - ), + let padding = width + margin * 2.0 + spacing; + let state = tree.state.downcast_mut::(); + + let status_quo = layout( + if is_vertical && state.is_scrollbar_visible { + padding + } else { + 0.0 + }, + if !is_vertical && state.is_scrollbar_visible { + padding + } else { + 0.0 + }, ); - self.content.as_widget().layout( - &mut tree.children[0], - renderer, - &child_limits, - ) - }, - ) + let is_scrollbar_visible = if is_vertical { + status_quo.children()[0].size().height + > status_quo.size().height + } else { + status_quo.children()[0].size().width + > status_quo.size().width + }; + + if state.is_scrollbar_visible == is_scrollbar_visible { + status_quo + } else { + log::trace!("Scrollbar status quo has changed"); + state.is_scrollbar_visible = is_scrollbar_visible; + + layout( + if is_vertical && state.is_scrollbar_visible { + padding + } else { + 0.0 + }, + if !is_vertical && state.is_scrollbar_visible { + padding + } else { + 0.0 + }, + ) + } + } + _ => layout(0.0, 0.0), + } } fn operate( @@ -1354,6 +1403,7 @@ struct State { keyboard_modifiers: keyboard::Modifiers, last_notified: Option, last_scrolled: Option, + is_scrollbar_visible: bool, } impl Default for State { @@ -1367,6 +1417,7 @@ impl Default for State { keyboard_modifiers: keyboard::Modifiers::default(), last_notified: None, last_scrolled: None, + is_scrollbar_visible: false, } } } @@ -1626,13 +1677,13 @@ impl Scrollbars { ) -> Self { let translation = state.translation(direction, bounds, content_bounds); - let show_scrollbar_x = direction.horizontal().filter(|scrollbar| { - scrollbar.spacing.is_some() || content_bounds.width > bounds.width - }); + let show_scrollbar_x = direction + .horizontal() + .filter(|_scrollbar| content_bounds.width > bounds.width); - let show_scrollbar_y = direction.vertical().filter(|scrollbar| { - scrollbar.spacing.is_some() || content_bounds.height > bounds.height - }); + let show_scrollbar_y = direction + .vertical() + .filter(|_scrollbar| content_bounds.height > bounds.height); let y_scrollbar = if let Some(vertical) = show_scrollbar_y { let Scrollbar {