Introduce RelativeOffset type in scrollable
This commit is contained in:
parent
9f85e0c721
commit
624a4ada79
6 changed files with 75 additions and 69 deletions
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue