Add scrollable Viewport

This commit is contained in:
Cory Forsstrom 2023-04-17 13:55:40 -07:00
parent b623f280ed
commit 6ad5e03d71
No known key found for this signature in database
GPG key ID: 1DFE170A4415C9F5
4 changed files with 61 additions and 52 deletions

View file

@ -36,7 +36,7 @@ enum Message {
ScrollerWidthChanged(u16), ScrollerWidthChanged(u16),
ScrollToBeginning, ScrollToBeginning,
ScrollToEnd, ScrollToEnd,
Scrolled(scrollable::CurrentOffset), Scrolled(scrollable::Viewport),
} }
impl Application for ScrollableDemo { impl Application for ScrollableDemo {
@ -104,8 +104,8 @@ impl Application for ScrollableDemo {
self.current_scroll_offset, self.current_scroll_offset,
) )
} }
Message::Scrolled(offset) => { Message::Scrolled(viewport) => {
self.current_scroll_offset = offset.relative; self.current_scroll_offset = viewport.relative_offset();
Command::none() Command::none()
} }

View file

@ -64,15 +64,6 @@ pub fn scroll_to<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> {
ScrollTo { target, offset } ScrollTo { target, offset }
} }
/// The current absolute & relative offset of a [`Scrollable`]
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct CurrentOffset {
/// The [`AbsoluteOffset`] of a [`Scrollable`]
pub absolute: AbsoluteOffset,
/// The [`RelativeOffset`] of a [`Scrollable`]
pub relative: RelativeOffset,
}
/// The amount of absolute offset in each direction of a [`Scrollable`]. /// The amount of absolute offset in each direction of a [`Scrollable`].
#[derive(Debug, Clone, Copy, PartialEq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct AbsoluteOffset { pub struct AbsoluteOffset {

View file

@ -15,9 +15,7 @@ use crate::{
}; };
pub use iced_style::scrollable::StyleSheet; pub use iced_style::scrollable::StyleSheet;
pub use operation::scrollable::{ pub use operation::scrollable::{AbsoluteOffset, RelativeOffset};
AbsoluteOffset, CurrentOffset, RelativeOffset,
};
pub mod style { pub mod style {
//! The styles of a [`Scrollable`]. //! The styles of a [`Scrollable`].
@ -40,7 +38,7 @@ where
vertical: Properties, vertical: Properties,
horizontal: Option<Properties>, horizontal: Option<Properties>,
content: Element<'a, Message, Renderer>, content: Element<'a, Message, Renderer>,
on_scroll: Option<Box<dyn Fn(CurrentOffset) -> Message + 'a>>, on_scroll: Option<Box<dyn Fn(Viewport) -> Message + 'a>>,
style: <Renderer::Theme as StyleSheet>::Style, style: <Renderer::Theme as StyleSheet>::Style,
} }
@ -95,11 +93,8 @@ where
/// Sets a function to call when the [`Scrollable`] is scrolled. /// Sets a function to call when the [`Scrollable`] is scrolled.
/// ///
/// The function takes the [`CurrentOffset`] of the [`Scrollable`] /// The function takes the [`Viewport`] of the [`Scrollable`]
pub fn on_scroll( pub fn on_scroll(mut self, f: impl Fn(Viewport) -> Message + 'a) -> Self {
mut self,
f: impl Fn(CurrentOffset) -> Message + 'a,
) -> Self {
self.on_scroll = Some(Box::new(f)); self.on_scroll = Some(Box::new(f));
self self
} }
@ -437,7 +432,7 @@ pub fn update<Message>(
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
vertical: &Properties, vertical: &Properties,
horizontal: Option<&Properties>, horizontal: Option<&Properties>,
on_scroll: &Option<Box<dyn Fn(CurrentOffset) -> Message + '_>>, on_scroll: &Option<Box<dyn Fn(Viewport) -> Message + '_>>,
update_content: impl FnOnce( update_content: impl FnOnce(
Event, Event,
Layout<'_>, Layout<'_>,
@ -897,7 +892,7 @@ pub fn draw<Renderer>(
fn notify_on_scroll<Message>( fn notify_on_scroll<Message>(
state: &mut State, state: &mut State,
on_scroll: &Option<Box<dyn Fn(CurrentOffset) -> Message + '_>>, on_scroll: &Option<Box<dyn Fn(Viewport) -> Message + '_>>,
bounds: Rectangle, bounds: Rectangle,
content_bounds: Rectangle, content_bounds: Rectangle,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
@ -909,39 +904,29 @@ fn notify_on_scroll<Message>(
return; return;
} }
let absolute_x = let viewport = Viewport {
state.offset_x.absolute(bounds.width, content_bounds.width); offset_x: state.offset_x,
let relative_x = absolute_x / (content_bounds.width - bounds.width); offset_y: state.offset_y,
bounds,
let absolute_y = state content_bounds,
.offset_y
.absolute(bounds.height, content_bounds.height);
let relative_y = absolute_y / (content_bounds.height - bounds.height);
let absolute = AbsoluteOffset {
x: absolute_x,
y: absolute_y,
};
let relative = RelativeOffset {
x: relative_x,
y: relative_y,
}; };
// Don't publish redundant offsets to shell // Don't publish redundant viewports to shell
if let Some(prev_relative) = state.last_notified { if let Some(last_notified) = state.last_notified {
let prev = last_notified.relative_offset();
let curr = viewport.relative_offset();
let unchanged = |a: f32, b: f32| { let unchanged = |a: f32, b: f32| {
(a - b).abs() <= f32::EPSILON || (a.is_nan() && b.is_nan()) (a - b).abs() <= f32::EPSILON || (a.is_nan() && b.is_nan())
}; };
if unchanged(prev_relative.x, relative.x) if unchanged(prev.x, curr.x) && unchanged(prev.y, curr.y) {
&& unchanged(prev_relative.y, relative.y)
{
return; return;
} }
} }
shell.publish(on_scroll(CurrentOffset { absolute, relative })); shell.publish(on_scroll(viewport));
state.last_notified = Some(relative); state.last_notified = Some(viewport);
} }
} }
@ -954,7 +939,7 @@ pub struct State {
offset_x: Offset, offset_x: Offset,
x_scroller_grabbed_at: Option<f32>, x_scroller_grabbed_at: Option<f32>,
keyboard_modifiers: keyboard::Modifiers, keyboard_modifiers: keyboard::Modifiers,
last_notified: Option<RelativeOffset>, last_notified: Option<Viewport>,
} }
impl Default for State { impl Default for State {
@ -988,18 +973,51 @@ enum Offset {
} }
impl Offset { impl Offset {
fn absolute(self, window: f32, content: f32) -> f32 { fn absolute(self, viewport: f32, content: f32) -> f32 {
match self { match self {
Offset::Absolute(absolute) => { Offset::Absolute(absolute) => {
absolute.min((content - window).max(0.0)) absolute.min((content - viewport).max(0.0))
} }
Offset::Relative(percentage) => { Offset::Relative(percentage) => {
((content - window) * percentage).max(0.0) ((content - viewport) * percentage).max(0.0)
} }
} }
} }
} }
/// The current [`Viewport`] of the [`Scrollable`].
#[derive(Debug, Clone, Copy)]
pub struct Viewport {
offset_x: Offset,
offset_y: Offset,
bounds: Rectangle,
content_bounds: Rectangle,
}
impl Viewport {
/// Returns the [`AbsoluteOffset`] of the current [`Viewport`].
pub fn absolute_offset(&self) -> AbsoluteOffset {
let x = self
.offset_x
.absolute(self.bounds.width, self.content_bounds.width);
let y = self
.offset_y
.absolute(self.bounds.height, self.content_bounds.height);
AbsoluteOffset { x, y }
}
/// Returns the [`RelativeOffset`] of the current [`Viewport`].
pub fn relative_offset(&self) -> RelativeOffset {
let AbsoluteOffset { x, y } = self.absolute_offset();
let x = x / (self.content_bounds.width - self.bounds.width);
let y = y / (self.content_bounds.height - self.bounds.height);
RelativeOffset { x, y }
}
}
impl State { impl State {
/// Creates a new [`State`] with the scrollbar(s) at the beginning. /// Creates a new [`State`] with the scrollbar(s) at the beginning.
pub fn new() -> Self { pub fn new() -> Self {

View file

@ -109,8 +109,8 @@ pub mod radio {
pub mod scrollable { pub mod scrollable {
//! Navigate an endless amount of content with a scrollbar. //! Navigate an endless amount of content with a scrollbar.
pub use iced_native::widget::scrollable::{ pub use iced_native::widget::scrollable::{
snap_to, style::Scrollbar, style::Scroller, AbsoluteOffset, snap_to, style::Scrollbar, style::Scroller, AbsoluteOffset, Id,
CurrentOffset, Id, Properties, RelativeOffset, StyleSheet, Properties, RelativeOffset, StyleSheet, Viewport,
}; };
/// A widget that can vertically display an infinite amount of content /// A widget that can vertically display an infinite amount of content