Add multidirectional scrolling capabilities to the existing Scrollable.

This commit is contained in:
Bingus 2022-11-19 10:29:37 -08:00 committed by bungoboingo
parent a6d0d5773f
commit d91f4f6aa7
12 changed files with 1148 additions and 576 deletions

View file

@ -7,3 +7,4 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["debug"] }
lazy_static = "1.4"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 509 KiB

Before After
Before After

View file

@ -1,44 +1,60 @@
use iced::executor;
use iced::widget::scrollable::{Scrollbar, Scroller};
use iced::widget::{
button, column, container, horizontal_rule, progress_bar, radio,
scrollable, text, vertical_space, Row,
button, column, container, horizontal_space, progress_bar, radio, row,
scrollable, slider, text, vertical_space,
};
use iced::{executor, theme, Alignment, Color, Vector};
use iced::{Application, Command, Element, Length, Settings, Theme};
use lazy_static::lazy_static;
lazy_static! {
static ref SCROLLABLE_ID: scrollable::Id = scrollable::Id::unique();
}
pub fn main() -> iced::Result {
ScrollableDemo::run(Settings::default())
}
struct ScrollableDemo {
theme: Theme,
variants: Vec<Variant>,
scrollable_direction: Direction,
scrollbar_width: u16,
scrollbar_margin: u16,
scroller_width: u16,
current_scroll_offset: Vector<f32>,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum ThemeType {
Light,
Dark,
#[derive(Debug, Clone, Eq, PartialEq, Copy)]
enum Direction {
Vertical,
Horizontal,
Multi,
}
#[derive(Debug, Clone)]
enum Message {
ThemeChanged(ThemeType),
ScrollToTop(usize),
ScrollToBottom(usize),
Scrolled(usize, f32),
SwitchDirection(Direction),
ScrollbarWidthChanged(u16),
ScrollbarMarginChanged(u16),
ScrollerWidthChanged(u16),
ScrollToBeginning(scrollable::Direction),
ScrollToEnd(scrollable::Direction),
Scrolled(Vector<f32>),
}
impl Application for ScrollableDemo {
type Executor = executor::Default;
type Message = Message;
type Theme = Theme;
type Executor = executor::Default;
type Flags = ();
fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
(
ScrollableDemo {
theme: Default::default(),
variants: Variant::all(),
scrollable_direction: Direction::Vertical,
scrollbar_width: 10,
scrollbar_margin: 0,
scroller_width: 10,
current_scroll_offset: Vector::new(0.0, 0.0),
},
Command::none(),
)
@ -50,209 +66,333 @@ impl Application for ScrollableDemo {
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::ThemeChanged(theme) => {
self.theme = match theme {
ThemeType::Light => Theme::Light,
ThemeType::Dark => Theme::Dark,
};
Message::SwitchDirection(direction) => {
self.current_scroll_offset = Vector::new(0.0, 0.0);
self.scrollable_direction = direction;
Command::none()
}
Message::ScrollToTop(i) => {
if let Some(variant) = self.variants.get_mut(i) {
variant.latest_offset = 0.0;
Message::ScrollbarWidthChanged(width) => {
self.scrollbar_width = width;
scrollable::snap_to(Variant::id(i), 0.0)
} else {
Command::none()
}
Command::none()
}
Message::ScrollToBottom(i) => {
if let Some(variant) = self.variants.get_mut(i) {
variant.latest_offset = 1.0;
Message::ScrollbarMarginChanged(margin) => {
self.scrollbar_margin = margin;
scrollable::snap_to(Variant::id(i), 1.0)
} else {
Command::none()
}
Command::none()
}
Message::Scrolled(i, offset) => {
if let Some(variant) = self.variants.get_mut(i) {
variant.latest_offset = offset;
Message::ScrollerWidthChanged(width) => {
self.scroller_width = width;
Command::none()
}
Message::ScrollToBeginning(direction) => {
match direction {
scrollable::Direction::Horizontal => {
self.current_scroll_offset.x = 0.0;
}
scrollable::Direction::Vertical => {
self.current_scroll_offset.y = 0.0;
}
}
scrollable::snap_to(
SCROLLABLE_ID.clone(),
Vector::new(
self.current_scroll_offset.x,
self.current_scroll_offset.y,
),
)
}
Message::ScrollToEnd(direction) => {
match direction {
scrollable::Direction::Horizontal => {
self.current_scroll_offset.x = 1.0;
}
scrollable::Direction::Vertical => {
self.current_scroll_offset.y = 1.0;
}
}
scrollable::snap_to(
SCROLLABLE_ID.clone(),
Vector::new(
self.current_scroll_offset.x,
self.current_scroll_offset.y,
),
)
}
Message::Scrolled(offset) => {
self.current_scroll_offset = offset;
Command::none()
}
}
}
fn view(&self) -> Element<Message> {
let ScrollableDemo { variants, .. } = self;
let choose_theme = [ThemeType::Light, ThemeType::Dark].iter().fold(
column!["Choose a theme:"].spacing(10),
|column, option| {
column.push(radio(
format!("{:?}", option),
*option,
Some(*option),
Message::ThemeChanged,
))
},
let scrollbar_width_slider = slider(
0..=15,
self.scrollbar_width,
Message::ScrollbarWidthChanged,
);
let scrollbar_margin_slider = slider(
0..=15,
self.scrollbar_margin,
Message::ScrollbarMarginChanged,
);
let scroller_width_slider =
slider(0..=15, self.scroller_width, Message::ScrollerWidthChanged);
let scrollable_row = Row::with_children(
variants
.iter()
.enumerate()
.map(|(i, variant)| {
let mut contents = column![
variant.title,
button("Scroll to bottom",)
.width(Length::Fill)
.padding(10)
.on_press(Message::ScrollToBottom(i)),
]
.padding(10)
.spacing(10)
.width(Length::Fill);
let scroll_slider_controls = column![
text("Scrollbar width:"),
scrollbar_width_slider,
text("Scrollbar margin:"),
scrollbar_margin_slider,
text("Scroller width:"),
scroller_width_slider,
]
.width(Length::Fill);
if let Some(scrollbar_width) = variant.scrollbar_width {
contents = contents.push(text(format!(
"scrollbar_width: {:?}",
scrollbar_width
)));
}
let scroll_orientation_controls = column(vec![
text("Scrollbar direction:").into(),
radio(
"Vertical",
Direction::Vertical,
Some(self.scrollable_direction),
Message::SwitchDirection,
)
.into(),
radio(
"Horizontal",
Direction::Horizontal,
Some(self.scrollable_direction),
Message::SwitchDirection,
)
.into(),
radio(
"Both!",
Direction::Multi,
Some(self.scrollable_direction),
Message::SwitchDirection,
)
.into(),
])
.width(Length::Fill);
if let Some(scrollbar_margin) = variant.scrollbar_margin {
contents = contents.push(text(format!(
"scrollbar_margin: {:?}",
scrollbar_margin
)));
}
let scroll_controls =
row![scroll_slider_controls, scroll_orientation_controls]
.spacing(20)
.width(Length::Fill);
if let Some(scroller_width) = variant.scroller_width {
contents = contents.push(text(format!(
"scroller_width: {:?}",
scroller_width
)));
}
let scroll_to_end_button = |direction: scrollable::Direction| {
button("Scroll to end")
.padding(10)
.width(Length::Units(120))
.on_press(Message::ScrollToEnd(direction))
};
contents = contents
.push(vertical_space(Length::Units(100)))
.push(
"Some content that should wrap within the \
scrollable. Let's output a lot of short words, so \
that we'll make sure to see how wrapping works \
with these scrollbars.",
)
.push(vertical_space(Length::Units(1200)))
.push("Middle")
.push(vertical_space(Length::Units(1200)))
.push("The End.")
.push(
button("Scroll to top")
.width(Length::Fill)
.padding(10)
.on_press(Message::ScrollToTop(i)),
);
let mut scrollable = scrollable(contents)
.id(Variant::id(i))
.height(Length::Fill)
.on_scroll(move |offset| Message::Scrolled(i, offset));
if let Some(scrollbar_width) = variant.scrollbar_width {
scrollable =
scrollable.scrollbar_width(scrollbar_width);
}
if let Some(scrollbar_margin) = variant.scrollbar_margin {
scrollable =
scrollable.scrollbar_margin(scrollbar_margin);
}
if let Some(scroller_width) = variant.scroller_width {
scrollable = scrollable.scroller_width(scroller_width);
}
let scroll_to_beginning_button = |direction: scrollable::Direction| {
button("Scroll to beginning")
.padding(10)
.width(Length::Units(120))
.on_press(Message::ScrollToBeginning(direction))
};
let scrollable_content: Element<Message> =
Element::from(match self.scrollable_direction {
Direction::Vertical => scrollable(
column![
scrollable,
progress_bar(0.0..=1.0, variant.latest_offset,)
scroll_to_end_button(scrollable::Direction::Vertical),
text("Beginning!"),
vertical_space(Length::Units(1200)),
text("Middle!"),
vertical_space(Length::Units(1200)),
text("End!"),
scroll_to_beginning_button(
scrollable::Direction::Vertical
),
]
.width(Length::Fill)
.height(Length::Fill)
.spacing(10)
.align_items(Alignment::Center)
.padding([40, 0, 40, 0])
.spacing(40),
)
.height(Length::Fill)
.scrollbar_width(self.scrollbar_width)
.scrollbar_margin(self.scrollbar_margin)
.scroller_width(self.scroller_width)
.id(SCROLLABLE_ID.clone())
.on_scroll(Message::Scrolled),
Direction::Horizontal => scrollable(
row![
scroll_to_end_button(scrollable::Direction::Horizontal),
text("Beginning!"),
horizontal_space(Length::Units(1200)),
text("Middle!"),
horizontal_space(Length::Units(1200)),
text("End!"),
scroll_to_beginning_button(
scrollable::Direction::Horizontal
),
]
.height(Length::Units(450))
.align_items(Alignment::Center)
.padding([0, 40, 0, 40])
.spacing(40),
)
.height(Length::Fill)
.horizontal_scroll(
scrollable::Horizontal::new()
.scrollbar_height(self.scrollbar_width)
.scrollbar_margin(self.scrollbar_margin)
.scroller_height(self.scroller_width),
)
.style(theme::Scrollable::Custom(Box::new(
ScrollbarCustomStyle,
)))
.id(SCROLLABLE_ID.clone())
.on_scroll(Message::Scrolled),
Direction::Multi => scrollable(
//horizontal content
row![
column![
text("Let's do some scrolling!"),
vertical_space(Length::Units(2400))
],
scroll_to_end_button(scrollable::Direction::Horizontal),
text("Horizontal - Beginning!"),
horizontal_space(Length::Units(1200)),
//vertical content
column![
text("Horizontal - Middle!"),
scroll_to_end_button(
scrollable::Direction::Vertical
),
text("Vertical - Beginning!"),
vertical_space(Length::Units(1200)),
text("Vertical - Middle!"),
vertical_space(Length::Units(1200)),
text("Vertical - End!"),
scroll_to_beginning_button(
scrollable::Direction::Vertical
)
]
.align_items(Alignment::Fill)
.spacing(40),
horizontal_space(Length::Units(1200)),
text("Horizontal - End!"),
scroll_to_beginning_button(
scrollable::Direction::Horizontal
),
]
.align_items(Alignment::Center)
.padding([0, 40, 0, 40])
.spacing(40),
)
.height(Length::Fill)
.scrollbar_width(self.scrollbar_width)
.scrollbar_margin(self.scrollbar_margin)
.scroller_width(self.scroller_width)
.horizontal_scroll(
scrollable::Horizontal::new()
.scrollbar_height(self.scrollbar_width)
.scrollbar_margin(self.scrollbar_margin)
.scroller_height(self.scroller_width),
)
.style(theme::Scrollable::Custom(Box::new(
ScrollbarCustomStyle,
)))
.id(SCROLLABLE_ID.clone())
.on_scroll(Message::Scrolled),
});
let progress_bars: Element<Message> = match self.scrollable_direction {
Direction::Vertical => {
progress_bar(0.0..=1.0, self.current_scroll_offset.y).into()
}
Direction::Horizontal => {
progress_bar(0.0..=1.0, self.current_scroll_offset.x)
.style(theme::ProgressBar::Custom(Box::new(
ProgressBarCustomStyle,
)))
.into()
})
.collect(),
}
Direction::Multi => column![
progress_bar(0.0..=1.0, self.current_scroll_offset.y),
progress_bar(0.0..=1.0, self.current_scroll_offset.x).style(
theme::ProgressBar::Custom(Box::new(
ProgressBarCustomStyle,
))
)
]
.spacing(10)
.into(),
};
let content: Element<Message> =
column![scroll_controls, scrollable_content, progress_bars]
.width(Length::Fill)
.height(Length::Fill)
.align_items(Alignment::Center)
.spacing(10)
.into();
Element::from(
container(content)
.width(Length::Fill)
.height(Length::Fill)
.padding(40)
.center_x()
.center_y(),
)
.spacing(20)
.width(Length::Fill)
.height(Length::Fill);
let content =
column![choose_theme, horizontal_rule(20), scrollable_row]
.spacing(20)
.padding(20);
container(content)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y()
.into()
}
fn theme(&self) -> Theme {
self.theme.clone()
fn theme(&self) -> Self::Theme {
Theme::Dark
}
}
/// A version of a scrollable
struct Variant {
title: &'static str,
scrollbar_width: Option<u16>,
scrollbar_margin: Option<u16>,
scroller_width: Option<u16>,
latest_offset: f32,
}
struct ScrollbarCustomStyle;
impl Variant {
pub fn all() -> Vec<Self> {
vec![
Self {
title: "Default Scrollbar",
scrollbar_width: None,
scrollbar_margin: None,
scroller_width: None,
latest_offset: 0.0,
},
Self {
title: "Slimmed & Margin",
scrollbar_width: Some(4),
scrollbar_margin: Some(3),
scroller_width: Some(4),
latest_offset: 0.0,
},
Self {
title: "Wide Scroller",
scrollbar_width: Some(4),
scrollbar_margin: None,
scroller_width: Some(10),
latest_offset: 0.0,
},
Self {
title: "Narrow Scroller",
scrollbar_width: Some(10),
scrollbar_margin: None,
scroller_width: Some(4),
latest_offset: 0.0,
},
]
impl scrollable::StyleSheet for ScrollbarCustomStyle {
type Style = Theme;
fn active(&self, style: &Self::Style) -> Scrollbar {
style.active(&theme::Scrollable::Default)
}
pub fn id(i: usize) -> scrollable::Id {
scrollable::Id::new(format!("scrollable-{}", i))
fn hovered(&self, style: &Self::Style) -> Scrollbar {
style.hovered(&theme::Scrollable::Default)
}
fn hovered_horizontal(&self, style: &Self::Style) -> Scrollbar {
Scrollbar {
background: style.active(&theme::Scrollable::default()).background,
border_radius: 0.0,
border_width: 0.0,
border_color: Default::default(),
scroller: Scroller {
color: Color::from_rgb8(250, 85, 134),
border_radius: 0.0,
border_width: 0.0,
border_color: Default::default(),
},
}
}
}
struct ProgressBarCustomStyle;
impl progress_bar::StyleSheet for ProgressBarCustomStyle {
type Style = Theme;
fn appearance(&self, style: &Self::Style) -> progress_bar::Appearance {
progress_bar::Appearance {
background: style.extended_palette().background.strong.color.into(),
bar: Color::from_rgb8(250, 85, 134).into(),
border_radius: 0.0,
}
}
}