Merge pull request #1796 from tarkah/feat/scrollable-scroll-to
Add `scroll_to` operation
This commit is contained in:
commit
4052ccf2b5
4 changed files with 116 additions and 33 deletions
|
|
@ -36,7 +36,7 @@ enum Message {
|
||||||
ScrollerWidthChanged(u16),
|
ScrollerWidthChanged(u16),
|
||||||
ScrollToBeginning,
|
ScrollToBeginning,
|
||||||
ScrollToEnd,
|
ScrollToEnd,
|
||||||
Scrolled(scrollable::RelativeOffset),
|
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;
|
self.current_scroll_offset = viewport.relative_offset();
|
||||||
|
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ use crate::widget::{Id, Operation};
|
||||||
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, offset: RelativeOffset);
|
fn snap_to(&mut self, offset: RelativeOffset);
|
||||||
|
|
||||||
|
/// Scroll the widget to the given [`AbsoluteOffset`] along the horizontal & vertical axis.
|
||||||
|
fn scroll_to(&mut self, offset: AbsoluteOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produces an [`Operation`] that snaps the widget with the given [`Id`] to
|
/// Produces an [`Operation`] that snaps the widget with the given [`Id`] to
|
||||||
|
|
@ -34,7 +37,43 @@ pub fn snap_to<T>(target: Id, offset: RelativeOffset) -> impl Operation<T> {
|
||||||
SnapTo { target, offset }
|
SnapTo { target, offset }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The amount of offset in each direction of a [`Scrollable`].
|
/// Produces an [`Operation`] that scrolls the widget with the given [`Id`] to
|
||||||
|
/// the provided [`AbsoluteOffset`].
|
||||||
|
pub fn scroll_to<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> {
|
||||||
|
struct ScrollTo {
|
||||||
|
target: Id,
|
||||||
|
offset: AbsoluteOffset,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Operation<T> for ScrollTo {
|
||||||
|
fn container(
|
||||||
|
&mut self,
|
||||||
|
_id: Option<&Id>,
|
||||||
|
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||||
|
) {
|
||||||
|
operate_on_children(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) {
|
||||||
|
if Some(&self.target) == id {
|
||||||
|
state.scroll_to(self.offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollTo { target, offset }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The amount of absolute offset in each direction of a [`Scrollable`].
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||||
|
pub struct AbsoluteOffset {
|
||||||
|
/// The amount of horizontal offset
|
||||||
|
pub x: f32,
|
||||||
|
/// The amount of vertical offset
|
||||||
|
pub y: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The amount of relative offset in each direction of a [`Scrollable`].
|
||||||
///
|
///
|
||||||
/// A value of `0.0` means start, while `1.0` means end.
|
/// A value of `0.0` means start, while `1.0` means end.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use iced_style::scrollable::StyleSheet;
|
pub use iced_style::scrollable::StyleSheet;
|
||||||
pub use operation::scrollable::RelativeOffset;
|
pub use operation::scrollable::{AbsoluteOffset, RelativeOffset};
|
||||||
|
|
||||||
pub mod style {
|
pub mod style {
|
||||||
//! The styles of a [`Scrollable`].
|
//! The styles of a [`Scrollable`].
|
||||||
|
|
@ -38,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(RelativeOffset) -> Message + 'a>>,
|
on_scroll: Option<Box<dyn Fn(Viewport) -> Message + 'a>>,
|
||||||
style: <Renderer::Theme as StyleSheet>::Style,
|
style: <Renderer::Theme as StyleSheet>::Style,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,12 +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 new relative x & y offset of the [`Scrollable`]
|
/// The function takes the [`Viewport`] of the [`Scrollable`]
|
||||||
/// (e.g. `0` means beginning, while `1` means end).
|
pub fn on_scroll(mut self, f: impl Fn(Viewport) -> 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
|
||||||
}
|
}
|
||||||
|
|
@ -436,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(RelativeOffset) -> Message + '_>>,
|
on_scroll: &Option<Box<dyn Fn(Viewport) -> Message + '_>>,
|
||||||
update_content: impl FnOnce(
|
update_content: impl FnOnce(
|
||||||
Event,
|
Event,
|
||||||
Layout<'_>,
|
Layout<'_>,
|
||||||
|
|
@ -896,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(RelativeOffset) -> 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>,
|
||||||
|
|
@ -908,31 +904,36 @@ fn notify_on_scroll<Message>(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let x = state.offset_x.absolute(bounds.width, content_bounds.width)
|
let viewport = Viewport {
|
||||||
/ (content_bounds.width - bounds.width);
|
offset_x: state.offset_x,
|
||||||
|
offset_y: state.offset_y,
|
||||||
|
bounds,
|
||||||
|
content_bounds,
|
||||||
|
};
|
||||||
|
|
||||||
let y = state
|
// Don't publish redundant viewports to shell
|
||||||
.offset_y
|
if let Some(last_notified) = state.last_notified {
|
||||||
.absolute(bounds.height, content_bounds.height)
|
let last_relative_offset = last_notified.relative_offset();
|
||||||
/ (content_bounds.height - bounds.height);
|
let current_relative_offset = viewport.relative_offset();
|
||||||
|
|
||||||
let new_offset = RelativeOffset { x, y };
|
let last_absolute_offset = last_notified.absolute_offset();
|
||||||
|
let current_absolute_offset = viewport.absolute_offset();
|
||||||
|
|
||||||
// Don't publish redundant offsets to shell
|
|
||||||
if let Some(prev_offset) = state.last_notified {
|
|
||||||
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_offset.x, new_offset.x)
|
if unchanged(last_relative_offset.x, current_relative_offset.x)
|
||||||
&& unchanged(prev_offset.y, new_offset.y)
|
&& unchanged(last_relative_offset.y, current_relative_offset.y)
|
||||||
|
&& unchanged(last_absolute_offset.x, current_absolute_offset.x)
|
||||||
|
&& unchanged(last_absolute_offset.y, current_absolute_offset.y)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shell.publish(on_scroll(new_offset));
|
shell.publish(on_scroll(viewport));
|
||||||
state.last_notified = Some(new_offset);
|
state.last_notified = Some(viewport);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -945,7 +946,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 {
|
||||||
|
|
@ -966,6 +967,10 @@ impl operation::Scrollable for State {
|
||||||
fn snap_to(&mut self, offset: RelativeOffset) {
|
fn snap_to(&mut self, offset: RelativeOffset) {
|
||||||
State::snap_to(self, offset);
|
State::snap_to(self, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn scroll_to(&mut self, offset: AbsoluteOffset) {
|
||||||
|
State::scroll_to(self, offset)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
@ -975,18 +980,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 {
|
||||||
|
|
@ -1052,6 +1090,12 @@ impl State {
|
||||||
self.offset_y = Offset::Relative(offset.y.clamp(0.0, 1.0));
|
self.offset_y = Offset::Relative(offset.y.clamp(0.0, 1.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Scroll to the provided [`AbsoluteOffset`].
|
||||||
|
pub fn scroll_to(&mut self, offset: AbsoluteOffset) {
|
||||||
|
self.offset_x = Offset::Absolute(offset.x.max(0.0));
|
||||||
|
self.offset_y = Offset::Absolute(offset.y.max(0.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) {
|
||||||
|
|
|
||||||
|
|
@ -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, Id, Properties,
|
snap_to, style::Scrollbar, style::Scroller, AbsoluteOffset, Id,
|
||||||
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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue