Introduce RelativeOffset type in scrollable

This commit is contained in:
Héctor Ramón Jiménez 2023-01-08 20:07:11 +01:00
parent 9f85e0c721
commit 624a4ada79
No known key found for this signature in database
GPG key ID: 140CC052C94F138E
6 changed files with 75 additions and 69 deletions

View file

@ -75,11 +75,3 @@ impl std::ops::Sub<Point> for Point {
Vector::new(self.x - point.x, self.y - point.y) Vector::new(self.x - point.x, self.y - point.y)
} }
} }
impl std::ops::Add<Point> for Point {
type Output = Point;
fn add(self, point: Point) -> Point {
Point::new(self.x + point.x, self.y + point.y)
}
}

View file

@ -3,7 +3,7 @@ use iced::widget::{
button, column, container, horizontal_space, progress_bar, radio, row, button, column, container, horizontal_space, progress_bar, radio, row,
scrollable, slider, text, vertical_space, scrollable, slider, text, vertical_space,
}; };
use iced::{executor, theme, Alignment, Color, Point}; use iced::{executor, theme, Alignment, Color};
use iced::{Application, Command, Element, Length, Settings, Theme}; use iced::{Application, Command, Element, Length, Settings, Theme};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
@ -18,7 +18,7 @@ struct ScrollableDemo {
scrollbar_width: u16, scrollbar_width: u16,
scrollbar_margin: u16, scrollbar_margin: u16,
scroller_width: u16, scroller_width: u16,
current_scroll_offset: Point, current_scroll_offset: scrollable::RelativeOffset,
} }
#[derive(Debug, Clone, Eq, PartialEq, Copy)] #[derive(Debug, Clone, Eq, PartialEq, Copy)]
@ -36,7 +36,7 @@ enum Message {
ScrollerWidthChanged(u16), ScrollerWidthChanged(u16),
ScrollToBeginning, ScrollToBeginning,
ScrollToEnd, ScrollToEnd,
Scrolled(Point), Scrolled(scrollable::RelativeOffset),
} }
impl Application for ScrollableDemo { impl Application for ScrollableDemo {
@ -52,7 +52,7 @@ impl Application for ScrollableDemo {
scrollbar_width: 10, scrollbar_width: 10,
scrollbar_margin: 0, scrollbar_margin: 0,
scroller_width: 10, scroller_width: 10,
current_scroll_offset: Point::ORIGIN, current_scroll_offset: scrollable::RelativeOffset::START,
}, },
Command::none(), Command::none(),
) )
@ -65,7 +65,7 @@ impl Application for ScrollableDemo {
fn update(&mut self, message: Message) -> Command<Message> { fn update(&mut self, message: Message) -> Command<Message> {
match message { match message {
Message::SwitchDirection(direction) => { Message::SwitchDirection(direction) => {
self.current_scroll_offset = Point::ORIGIN; self.current_scroll_offset = scrollable::RelativeOffset::START;
self.scrollable_direction = direction; self.scrollable_direction = direction;
scrollable::snap_to( scrollable::snap_to(
@ -89,7 +89,7 @@ impl Application for ScrollableDemo {
Command::none() Command::none()
} }
Message::ScrollToBeginning => { Message::ScrollToBeginning => {
self.current_scroll_offset = Point::ORIGIN; self.current_scroll_offset = scrollable::RelativeOffset::START;
scrollable::snap_to( scrollable::snap_to(
SCROLLABLE_ID.clone(), SCROLLABLE_ID.clone(),
@ -97,7 +97,7 @@ impl Application for ScrollableDemo {
) )
} }
Message::ScrollToEnd => { Message::ScrollToEnd => {
self.current_scroll_offset = Point::new(1.0, 1.0); self.current_scroll_offset = scrollable::RelativeOffset::END;
scrollable::snap_to( scrollable::snap_to(
SCROLLABLE_ID.clone(), SCROLLABLE_ID.clone(),

View file

@ -1,10 +1,10 @@
mod echo; mod echo;
use iced::alignment::{self, Alignment}; use iced::alignment::{self, Alignment};
use iced::executor;
use iced::widget::{ use iced::widget::{
button, column, container, row, scrollable, text, text_input, Column, button, column, container, row, scrollable, text, text_input, Column,
}; };
use iced::{executor, Point};
use iced::{ use iced::{
Application, Color, Command, Element, Length, Settings, Subscription, Theme, Application, Color, Command, Element, Length, Settings, Subscription, Theme,
}; };
@ -83,7 +83,7 @@ impl Application for WebSocket {
scrollable::snap_to( scrollable::snap_to(
MESSAGE_LOG.clone(), MESSAGE_LOG.clone(),
Point::new(0.0, 1.0), scrollable::RelativeOffset::END,
) )
} }
}, },

View file

@ -1,19 +1,18 @@
//! Operate on widgets that can be scrolled. //! Operate on widgets that can be scrolled.
use crate::widget::{Id, Operation}; use crate::widget::{Id, Operation};
use iced_core::Point;
/// The internal state of a widget that can be scrolled. /// The internal state of a widget that can be scrolled.
pub trait Scrollable { pub trait Scrollable {
/// Snaps the scroll of the widget to the given `percentage` along the horizontal & vertical axis. /// Snaps the scroll of the widget to the given `percentage` along the horizontal & vertical axis.
fn snap_to(&mut self, percentage: Point); fn snap_to(&mut self, offset: RelativeOffset);
} }
/// Produces an [`Operation`] that snaps the widget with the given [`Id`] to /// Produces an [`Operation`] that snaps the widget with the given [`Id`] to
/// the provided `percentage`. /// the provided `percentage`.
pub fn snap_to<T>(target: Id, percentage: Point) -> impl Operation<T> { pub fn snap_to<T>(target: Id, offset: RelativeOffset) -> impl Operation<T> {
struct SnapTo { struct SnapTo {
target: Id, target: Id,
percentage: Point, offset: RelativeOffset,
} }
impl<T> Operation<T> for SnapTo { impl<T> Operation<T> for SnapTo {
@ -27,10 +26,29 @@ pub fn snap_to<T>(target: Id, percentage: Point) -> impl Operation<T> {
fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) { fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) {
if Some(&self.target) == id { if Some(&self.target) == id {
state.snap_to(self.percentage); state.snap_to(self.offset);
} }
} }
} }
SnapTo { target, percentage } SnapTo { target, offset }
}
/// The amount of offset in each direction of a [`Scrollable`].
///
/// A value of `0.0` means start, while `1.0` means end.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct RelativeOffset {
/// The amount of horizontal offset
pub x: f32,
/// The amount of vertical offset
pub y: f32,
}
impl RelativeOffset {
/// A relative offset that points to the top-left of a [`Scrollable`].
pub const START: Self = Self { x: 0.0, y: 0.0 };
/// A relative offset that points to the bottom-right of a [`Scrollable`].
pub const END: Self = Self { x: 1.0, y: 1.0 };
} }

View file

@ -14,6 +14,7 @@ use crate::{
}; };
pub use iced_style::scrollable::StyleSheet; pub use iced_style::scrollable::StyleSheet;
pub use operation::scrollable::RelativeOffset;
pub mod style { pub mod style {
//! The styles of a [`Scrollable`]. //! The styles of a [`Scrollable`].
@ -35,7 +36,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(Point) -> Message + 'a>>, on_scroll: Option<Box<dyn Fn(RelativeOffset) -> Message + 'a>>,
style: <Renderer::Theme as StyleSheet>::Style, style: <Renderer::Theme as StyleSheet>::Style,
} }
@ -85,7 +86,10 @@ where
/// ///
/// The function takes the new relative x & y offset of the [`Scrollable`] /// The function takes the new relative x & y offset of the [`Scrollable`]
/// (e.g. `0` means beginning, while `1` means end). /// (e.g. `0` means beginning, while `1` means end).
pub fn on_scroll(mut self, f: impl Fn(Point) -> Message + 'a) -> Self { pub fn on_scroll(
mut self,
f: impl Fn(RelativeOffset) -> Message + 'a,
) -> Self {
self.on_scroll = Some(Box::new(f)); self.on_scroll = Some(Box::new(f));
self self
} }
@ -375,9 +379,9 @@ impl From<Id> for widget::Id {
/// to the provided `percentage` along the x & y axis. /// to the provided `percentage` along the x & y axis.
pub fn snap_to<Message: 'static>( pub fn snap_to<Message: 'static>(
id: Id, id: Id,
percentage: Point, offset: RelativeOffset,
) -> Command<Message> { ) -> Command<Message> {
Command::widget(operation::scrollable::snap_to(id.0, percentage)) Command::widget(operation::scrollable::snap_to(id.0, offset))
} }
/// Computes the layout of a [`Scrollable`]. /// Computes the layout of a [`Scrollable`].
@ -428,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(Point) -> Message + '_>>, on_scroll: &Option<Box<dyn Fn(RelativeOffset) -> Message + '_>>,
update_content: impl FnOnce( update_content: impl FnOnce(
Event, Event,
Layout<'_>, Layout<'_>,
@ -872,7 +876,7 @@ pub fn draw<Renderer>(
fn notify_on_scroll<Message>( fn notify_on_scroll<Message>(
state: &State, state: &State,
on_scroll: &Option<Box<dyn Fn(Point) -> Message + '_>>, on_scroll: &Option<Box<dyn Fn(RelativeOffset) -> Message + '_>>,
bounds: Rectangle, bounds: Rectangle,
content_bounds: Rectangle, content_bounds: Rectangle,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
@ -882,13 +886,15 @@ fn notify_on_scroll<Message>(
return; return;
} }
let x_offset = state.offset_x.absolute_x(bounds, content_bounds) let x = state.offset_x.absolute(bounds.width, content_bounds.width)
/ (content_bounds.width - bounds.width); / (content_bounds.width - bounds.width);
let y_offset = state.offset_y.absolute_y(bounds, content_bounds) let y = state
.offset_y
.absolute(bounds.height, content_bounds.height)
/ (content_bounds.height - bounds.height); / (content_bounds.height - bounds.height);
shell.publish(on_scroll(Point::new(x_offset, y_offset))) shell.publish(on_scroll(RelativeOffset { x, y }))
} }
} }
@ -915,12 +921,11 @@ impl Default for State {
} }
impl operation::Scrollable for State { impl operation::Scrollable for State {
fn snap_to(&mut self, percentage: Point) { fn snap_to(&mut self, offset: RelativeOffset) {
State::snap_to(self, percentage); State::snap_to(self, offset);
} }
} }
/// The offset of a [`Scrollable`].
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
enum Offset { enum Offset {
Absolute(f32), Absolute(f32),
@ -928,24 +933,13 @@ enum Offset {
} }
impl Offset { impl Offset {
fn absolute_x(self, bounds: Rectangle, content_bounds: Rectangle) -> f32 { fn absolute(self, window: f32, content: f32) -> f32 {
match self { match self {
Offset::Absolute(absolute) => { Offset::Absolute(absolute) => {
absolute.min((content_bounds.width - bounds.width).max(0.0)) absolute.min((content - window).max(0.0))
} }
Offset::Relative(percentage) => { Offset::Relative(percentage) => {
((content_bounds.width - bounds.width) * percentage).max(0.0) ((content - window) * percentage).max(0.0)
}
}
}
fn absolute_y(self, bounds: Rectangle, content_bounds: Rectangle) -> f32 {
match self {
Offset::Absolute(absolute) => {
absolute.min((content_bounds.height - bounds.height).max(0.0))
}
Offset::Relative(percentage) => {
((content_bounds.height - bounds.height) * percentage).max(0.0)
} }
} }
} }
@ -967,14 +961,16 @@ impl State {
) { ) {
if bounds.height < content_bounds.height { if bounds.height < content_bounds.height {
self.offset_y = Offset::Absolute( self.offset_y = Offset::Absolute(
(self.offset_y.absolute_y(bounds, content_bounds) - delta.y) (self.offset_y.absolute(bounds.height, content_bounds.height)
- delta.y)
.clamp(0.0, content_bounds.height - bounds.height), .clamp(0.0, content_bounds.height - bounds.height),
) )
} }
if bounds.width < content_bounds.width { if bounds.width < content_bounds.width {
self.offset_x = Offset::Absolute( self.offset_x = Offset::Absolute(
(self.offset_x.absolute_x(bounds, content_bounds) - delta.x) (self.offset_x.absolute(bounds.width, content_bounds.width)
- delta.x)
.clamp(0.0, content_bounds.width - bounds.width), .clamp(0.0, content_bounds.width - bounds.width),
); );
} }
@ -1008,34 +1004,33 @@ impl State {
self.unsnap(bounds, content_bounds); self.unsnap(bounds, content_bounds);
} }
/// Snaps the scroll position to a relative amount. /// Snaps the scroll position to a [`RelativeOffset`].
/// pub fn snap_to(&mut self, offset: RelativeOffset) {
/// `0` represents scrollbar at the beginning, while `1` represents scrollbar at self.offset_x = Offset::Relative(offset.x.clamp(0.0, 1.0));
/// the end. self.offset_y = Offset::Relative(offset.y.clamp(0.0, 1.0));
pub fn snap_to(&mut self, percentage: Point) {
self.offset_x = Offset::Relative(percentage.x.clamp(0.0, 1.0));
self.offset_y = Offset::Relative(percentage.y.clamp(0.0, 1.0));
} }
/// Unsnaps the current scroll position, if snapped, given the bounds of the /// Unsnaps the current scroll position, if snapped, given the bounds of the
/// [`Scrollable`] and its contents. /// [`Scrollable`] and its contents.
pub fn unsnap(&mut self, bounds: Rectangle, content_bounds: Rectangle) { pub fn unsnap(&mut self, bounds: Rectangle, content_bounds: Rectangle) {
self.offset_x = self.offset_x = Offset::Absolute(
Offset::Absolute(self.offset_x.absolute_x(bounds, content_bounds)); self.offset_x.absolute(bounds.width, content_bounds.width),
self.offset_y = );
Offset::Absolute(self.offset_y.absolute_y(bounds, content_bounds)); self.offset_y = Offset::Absolute(
self.offset_y.absolute(bounds.height, content_bounds.height),
);
} }
/// Returns the current x & y scrolling offset of the [`State`], given the bounds /// Returns the scrolling offset of the [`State`], given the bounds of the
/// of the [`Scrollable`] and its contents. /// [`Scrollable`] and its contents.
pub fn offset( pub fn offset(
&self, &self,
bounds: Rectangle, bounds: Rectangle,
content_bounds: Rectangle, content_bounds: Rectangle,
) -> Point { ) -> Vector {
Point::new( Vector::new(
self.offset_x.absolute_x(bounds, content_bounds), self.offset_x.absolute(bounds.width, content_bounds.width),
self.offset_y.absolute_y(bounds, content_bounds), self.offset_y.absolute(bounds.height, content_bounds.height),
) )
} }

View file

@ -99,7 +99,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, Id, Properties, StyleSheet, snap_to, style::Scrollbar, style::Scroller, Id, Properties,
RelativeOffset, StyleSheet,
}; };
/// A widget that can vertically display an infinite amount of content /// A widget that can vertically display an infinite amount of content