Reworked Scrollable to account for lack of widget order guarantees.
Fixed thumb "snapping" bug on scrollable when cursor is out of bounds.
This commit is contained in:
parent
d91f4f6aa7
commit
9f85e0c721
9 changed files with 624 additions and 697 deletions
|
|
@ -75,3 +75,11 @@ 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,12 @@ impl Rectangle<f32> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::cmp::PartialOrd for Rectangle<f32> {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
(self.width * self.height).partial_cmp(&(other.width * other.height))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::ops::Mul<f32> for Rectangle<f32> {
|
impl std::ops::Mul<f32> for Rectangle<f32> {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,4 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced = { path = "../..", features = ["debug"] }
|
iced = { path = "../..", features = ["debug"] }
|
||||||
lazy_static = "1.4"
|
once_cell = "1.16.0"
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
use iced::widget::scrollable::{Scrollbar, Scroller};
|
use iced::widget::scrollable::{Properties, Scrollbar, Scroller};
|
||||||
use iced::widget::{
|
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, Vector};
|
use iced::{executor, theme, Alignment, Color, Point};
|
||||||
use iced::{Application, Command, Element, Length, Settings, Theme};
|
use iced::{Application, Command, Element, Length, Settings, Theme};
|
||||||
use lazy_static::lazy_static;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
lazy_static! {
|
static SCROLLABLE_ID: Lazy<scrollable::Id> = Lazy::new(scrollable::Id::unique);
|
||||||
static ref SCROLLABLE_ID: scrollable::Id = scrollable::Id::unique();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
ScrollableDemo::run(Settings::default())
|
ScrollableDemo::run(Settings::default())
|
||||||
|
|
@ -20,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: Vector<f32>,
|
current_scroll_offset: Point,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Copy)]
|
#[derive(Debug, Clone, Eq, PartialEq, Copy)]
|
||||||
|
|
@ -36,9 +34,9 @@ enum Message {
|
||||||
ScrollbarWidthChanged(u16),
|
ScrollbarWidthChanged(u16),
|
||||||
ScrollbarMarginChanged(u16),
|
ScrollbarMarginChanged(u16),
|
||||||
ScrollerWidthChanged(u16),
|
ScrollerWidthChanged(u16),
|
||||||
ScrollToBeginning(scrollable::Direction),
|
ScrollToBeginning,
|
||||||
ScrollToEnd(scrollable::Direction),
|
ScrollToEnd,
|
||||||
Scrolled(Vector<f32>),
|
Scrolled(Point),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Application for ScrollableDemo {
|
impl Application for ScrollableDemo {
|
||||||
|
|
@ -54,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: Vector::new(0.0, 0.0),
|
current_scroll_offset: Point::ORIGIN,
|
||||||
},
|
},
|
||||||
Command::none(),
|
Command::none(),
|
||||||
)
|
)
|
||||||
|
|
@ -67,10 +65,13 @@ 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 = Vector::new(0.0, 0.0);
|
self.current_scroll_offset = Point::ORIGIN;
|
||||||
self.scrollable_direction = direction;
|
self.scrollable_direction = direction;
|
||||||
|
|
||||||
Command::none()
|
scrollable::snap_to(
|
||||||
|
SCROLLABLE_ID.clone(),
|
||||||
|
self.current_scroll_offset,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Message::ScrollbarWidthChanged(width) => {
|
Message::ScrollbarWidthChanged(width) => {
|
||||||
self.scrollbar_width = width;
|
self.scrollbar_width = width;
|
||||||
|
|
@ -87,40 +88,20 @@ impl Application for ScrollableDemo {
|
||||||
|
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
Message::ScrollToBeginning(direction) => {
|
Message::ScrollToBeginning => {
|
||||||
match direction {
|
self.current_scroll_offset = Point::ORIGIN;
|
||||||
scrollable::Direction::Horizontal => {
|
|
||||||
self.current_scroll_offset.x = 0.0;
|
|
||||||
}
|
|
||||||
scrollable::Direction::Vertical => {
|
|
||||||
self.current_scroll_offset.y = 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollable::snap_to(
|
scrollable::snap_to(
|
||||||
SCROLLABLE_ID.clone(),
|
SCROLLABLE_ID.clone(),
|
||||||
Vector::new(
|
self.current_scroll_offset,
|
||||||
self.current_scroll_offset.x,
|
|
||||||
self.current_scroll_offset.y,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Message::ScrollToEnd(direction) => {
|
Message::ScrollToEnd => {
|
||||||
match direction {
|
self.current_scroll_offset = Point::new(1.0, 1.0);
|
||||||
scrollable::Direction::Horizontal => {
|
|
||||||
self.current_scroll_offset.x = 1.0;
|
|
||||||
}
|
|
||||||
scrollable::Direction::Vertical => {
|
|
||||||
self.current_scroll_offset.y = 1.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollable::snap_to(
|
scrollable::snap_to(
|
||||||
SCROLLABLE_ID.clone(),
|
SCROLLABLE_ID.clone(),
|
||||||
Vector::new(
|
self.current_scroll_offset,
|
||||||
self.current_scroll_offset.x,
|
|
||||||
self.current_scroll_offset.y,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Message::Scrolled(offset) => {
|
Message::Scrolled(offset) => {
|
||||||
|
|
@ -186,33 +167,29 @@ impl Application for ScrollableDemo {
|
||||||
.spacing(20)
|
.spacing(20)
|
||||||
.width(Length::Fill);
|
.width(Length::Fill);
|
||||||
|
|
||||||
let scroll_to_end_button = |direction: scrollable::Direction| {
|
let scroll_to_end_button = || {
|
||||||
button("Scroll to end")
|
button("Scroll to end")
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.width(Length::Units(120))
|
.on_press(Message::ScrollToEnd)
|
||||||
.on_press(Message::ScrollToEnd(direction))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let scroll_to_beginning_button = |direction: scrollable::Direction| {
|
let scroll_to_beginning_button = || {
|
||||||
button("Scroll to beginning")
|
button("Scroll to beginning")
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.width(Length::Units(120))
|
.on_press(Message::ScrollToBeginning)
|
||||||
.on_press(Message::ScrollToBeginning(direction))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let scrollable_content: Element<Message> =
|
let scrollable_content: Element<Message> =
|
||||||
Element::from(match self.scrollable_direction {
|
Element::from(match self.scrollable_direction {
|
||||||
Direction::Vertical => scrollable(
|
Direction::Vertical => scrollable(
|
||||||
column![
|
column![
|
||||||
scroll_to_end_button(scrollable::Direction::Vertical),
|
scroll_to_end_button(),
|
||||||
text("Beginning!"),
|
text("Beginning!"),
|
||||||
vertical_space(Length::Units(1200)),
|
vertical_space(Length::Units(1200)),
|
||||||
text("Middle!"),
|
text("Middle!"),
|
||||||
vertical_space(Length::Units(1200)),
|
vertical_space(Length::Units(1200)),
|
||||||
text("End!"),
|
text("End!"),
|
||||||
scroll_to_beginning_button(
|
scroll_to_beginning_button(),
|
||||||
scrollable::Direction::Vertical
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
|
|
@ -220,22 +197,23 @@ impl Application for ScrollableDemo {
|
||||||
.spacing(40),
|
.spacing(40),
|
||||||
)
|
)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.scrollbar_width(self.scrollbar_width)
|
.vertical_scroll(
|
||||||
.scrollbar_margin(self.scrollbar_margin)
|
Properties::new()
|
||||||
.scroller_width(self.scroller_width)
|
.width(self.scrollbar_width)
|
||||||
|
.margin(self.scrollbar_margin)
|
||||||
|
.scroller_width(self.scroller_width),
|
||||||
|
)
|
||||||
.id(SCROLLABLE_ID.clone())
|
.id(SCROLLABLE_ID.clone())
|
||||||
.on_scroll(Message::Scrolled),
|
.on_scroll(Message::Scrolled),
|
||||||
Direction::Horizontal => scrollable(
|
Direction::Horizontal => scrollable(
|
||||||
row![
|
row![
|
||||||
scroll_to_end_button(scrollable::Direction::Horizontal),
|
scroll_to_end_button(),
|
||||||
text("Beginning!"),
|
text("Beginning!"),
|
||||||
horizontal_space(Length::Units(1200)),
|
horizontal_space(Length::Units(1200)),
|
||||||
text("Middle!"),
|
text("Middle!"),
|
||||||
horizontal_space(Length::Units(1200)),
|
horizontal_space(Length::Units(1200)),
|
||||||
text("End!"),
|
text("End!"),
|
||||||
scroll_to_beginning_button(
|
scroll_to_beginning_button(),
|
||||||
scrollable::Direction::Horizontal
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
.height(Length::Units(450))
|
.height(Length::Units(450))
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
|
|
@ -244,14 +222,12 @@ impl Application for ScrollableDemo {
|
||||||
)
|
)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.horizontal_scroll(
|
.horizontal_scroll(
|
||||||
scrollable::Horizontal::new()
|
Properties::new()
|
||||||
.scrollbar_height(self.scrollbar_width)
|
.width(self.scrollbar_width)
|
||||||
.scrollbar_margin(self.scrollbar_margin)
|
.margin(self.scrollbar_margin)
|
||||||
.scroller_height(self.scroller_width),
|
.scroller_width(self.scroller_width),
|
||||||
)
|
)
|
||||||
.style(theme::Scrollable::Custom(Box::new(
|
.style(theme::Scrollable::custom(ScrollbarCustomStyle))
|
||||||
ScrollbarCustomStyle,
|
|
||||||
)))
|
|
||||||
.id(SCROLLABLE_ID.clone())
|
.id(SCROLLABLE_ID.clone())
|
||||||
.on_scroll(Message::Scrolled),
|
.on_scroll(Message::Scrolled),
|
||||||
Direction::Multi => scrollable(
|
Direction::Multi => scrollable(
|
||||||
|
|
@ -261,45 +237,43 @@ impl Application for ScrollableDemo {
|
||||||
text("Let's do some scrolling!"),
|
text("Let's do some scrolling!"),
|
||||||
vertical_space(Length::Units(2400))
|
vertical_space(Length::Units(2400))
|
||||||
],
|
],
|
||||||
scroll_to_end_button(scrollable::Direction::Horizontal),
|
scroll_to_end_button(),
|
||||||
text("Horizontal - Beginning!"),
|
text("Horizontal - Beginning!"),
|
||||||
horizontal_space(Length::Units(1200)),
|
horizontal_space(Length::Units(1200)),
|
||||||
//vertical content
|
//vertical content
|
||||||
column![
|
column![
|
||||||
text("Horizontal - Middle!"),
|
text("Horizontal - Middle!"),
|
||||||
scroll_to_end_button(
|
scroll_to_end_button(),
|
||||||
scrollable::Direction::Vertical
|
|
||||||
),
|
|
||||||
text("Vertical - Beginning!"),
|
text("Vertical - Beginning!"),
|
||||||
vertical_space(Length::Units(1200)),
|
vertical_space(Length::Units(1200)),
|
||||||
text("Vertical - Middle!"),
|
text("Vertical - Middle!"),
|
||||||
vertical_space(Length::Units(1200)),
|
vertical_space(Length::Units(1200)),
|
||||||
text("Vertical - End!"),
|
text("Vertical - End!"),
|
||||||
scroll_to_beginning_button(
|
scroll_to_beginning_button(),
|
||||||
scrollable::Direction::Vertical
|
vertical_space(Length::Units(40)),
|
||||||
)
|
|
||||||
]
|
]
|
||||||
.align_items(Alignment::Fill)
|
.align_items(Alignment::Fill)
|
||||||
.spacing(40),
|
.spacing(40),
|
||||||
horizontal_space(Length::Units(1200)),
|
horizontal_space(Length::Units(1200)),
|
||||||
text("Horizontal - End!"),
|
text("Horizontal - End!"),
|
||||||
scroll_to_beginning_button(
|
scroll_to_beginning_button(),
|
||||||
scrollable::Direction::Horizontal
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
.padding([0, 40, 0, 40])
|
.padding([0, 40, 0, 40])
|
||||||
.spacing(40),
|
.spacing(40),
|
||||||
)
|
)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.scrollbar_width(self.scrollbar_width)
|
.vertical_scroll(
|
||||||
.scrollbar_margin(self.scrollbar_margin)
|
Properties::new()
|
||||||
.scroller_width(self.scroller_width)
|
.width(self.scrollbar_width)
|
||||||
|
.margin(self.scrollbar_margin)
|
||||||
|
.scroller_width(self.scroller_width),
|
||||||
|
)
|
||||||
.horizontal_scroll(
|
.horizontal_scroll(
|
||||||
scrollable::Horizontal::new()
|
Properties::new()
|
||||||
.scrollbar_height(self.scrollbar_width)
|
.width(self.scrollbar_width)
|
||||||
.scrollbar_margin(self.scrollbar_margin)
|
.margin(self.scrollbar_margin)
|
||||||
.scroller_height(self.scroller_width),
|
.scroller_width(self.scroller_width),
|
||||||
)
|
)
|
||||||
.style(theme::Scrollable::Custom(Box::new(
|
.style(theme::Scrollable::Custom(Box::new(
|
||||||
ScrollbarCustomStyle,
|
ScrollbarCustomStyle,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use iced::alignment::{self, Alignment};
|
||||||
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, Vector};
|
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(),
|
||||||
Vector::new(0.0, 1.0),
|
Point::new(0.0, 1.0),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
//! 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::Vector;
|
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`.
|
/// Snaps the scroll of the widget to the given `percentage` along the horizontal & vertical axis.
|
||||||
fn snap_to(&mut self, percentage: Vector<f32>);
|
fn snap_to(&mut self, percentage: Point);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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: Vector<f32>) -> impl Operation<T> {
|
pub fn snap_to<T>(target: Id, percentage: Point) -> impl Operation<T> {
|
||||||
struct SnapTo {
|
struct SnapTo {
|
||||||
target: Id,
|
target: Id,
|
||||||
percentage: Vector<f32>,
|
percentage: Point,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Operation<T> for SnapTo {
|
impl<T> Operation<T> for SnapTo {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -99,8 +99,7 @@ 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, Direction, Horizontal, Id,
|
snap_to, style::Scrollbar, style::Scroller, Id, Properties, StyleSheet,
|
||||||
StyleSheet,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A widget that can vertically display an infinite amount of content
|
/// A widget that can vertically display an infinite amount of content
|
||||||
|
|
|
||||||
|
|
@ -872,6 +872,15 @@ pub enum Scrollable {
|
||||||
Custom(Box<dyn scrollable::StyleSheet<Style = Theme>>),
|
Custom(Box<dyn scrollable::StyleSheet<Style = Theme>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Scrollable {
|
||||||
|
/// Creates a custom [`Scrollable`] theme.
|
||||||
|
pub fn custom<T: scrollable::StyleSheet<Style = Theme> + 'static>(
|
||||||
|
style: T,
|
||||||
|
) -> Self {
|
||||||
|
Self::Custom(Box::new(style))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl scrollable::StyleSheet for Theme {
|
impl scrollable::StyleSheet for Theme {
|
||||||
type Style = Scrollable;
|
type Style = Scrollable;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue