diff options
author | 2022-11-19 10:29:37 -0800 | |
---|---|---|
committer | 2022-12-29 10:21:23 -0800 | |
commit | d91f4f6aa74d0693179a02167d626efa3ac4c20b (patch) | |
tree | 293852763793fbf6cb142fffac222ee7a87a1edf /examples | |
parent | a6d0d5773f0561a841a84b538523cbd97e91eccd (diff) | |
download | iced-d91f4f6aa74d0693179a02167d626efa3ac4c20b.tar.gz iced-d91f4f6aa74d0693179a02167d626efa3ac4c20b.tar.bz2 iced-d91f4f6aa74d0693179a02167d626efa3ac4c20b.zip |
Add multidirectional scrolling capabilities to the existing Scrollable.
Diffstat (limited to 'examples')
-rw-r--r-- | examples/README.md | 2 | ||||
-rw-r--r-- | examples/scrollable/Cargo.toml | 1 | ||||
-rw-r--r-- | examples/scrollable/screenshot.png | bin | 104995 -> 521151 bytes | |||
-rw-r--r-- | examples/scrollable/src/main.rs | 506 | ||||
-rw-r--r-- | examples/websocket/src/main.rs | 7 |
5 files changed, 330 insertions, 186 deletions
diff --git a/examples/README.md b/examples/README.md index bb15dc2e..74cf145b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -99,7 +99,7 @@ A bunch of simpler examples exist: - [`pick_list`](pick_list), a dropdown list of selectable options. - [`pokedex`](pokedex), an application that displays a random Pokédex entry (sprite included!) by using the [PokéAPI]. - [`progress_bar`](progress_bar), a simple progress bar that can be filled by using a slider. -- [`scrollable`](scrollable), a showcase of the various scrollbar width options. +- [`scrollable`](scrollable), a showcase of various scrollable content configurations. - [`sierpinski_triangle`](sierpinski_triangle), a [sierpiński triangle](https://en.wikipedia.org/wiki/Sierpi%C5%84ski_triangle) Emulator, use `Canvas` and `Slider`. - [`solar_system`](solar_system), an animated solar system drawn using the `Canvas` widget and showcasing how to compose different transforms. - [`stopwatch`](stopwatch), a watch with start/stop and reset buttons showcasing how to listen to time. diff --git a/examples/scrollable/Cargo.toml b/examples/scrollable/Cargo.toml index 610c13b4..4bef4281 100644 --- a/examples/scrollable/Cargo.toml +++ b/examples/scrollable/Cargo.toml @@ -7,3 +7,4 @@ publish = false [dependencies] iced = { path = "../..", features = ["debug"] } +lazy_static = "1.4" diff --git a/examples/scrollable/screenshot.png b/examples/scrollable/screenshot.png Binary files differindex e91fd565..ee044447 100644 --- a/examples/scrollable/screenshot.png +++ b/examples/scrollable/screenshot.png diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index 6eba34e2..1481afcc 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -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::ScrollbarMarginChanged(margin) => { + self.scrollbar_margin = margin; + + Command::none() } - Message::ScrollToBottom(i) => { - if let Some(variant) = self.variants.get_mut(i) { - variant.latest_offset = 1.0; + Message::ScrollerWidthChanged(width) => { + self.scroller_width = width; - scrollable::snap_to(Variant::id(i), 1.0) - } else { - Command::none() + 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::Scrolled(i, offset) => { - if let Some(variant) = self.variants.get_mut(i) { - variant.latest_offset = offset; + 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); - - if let Some(scrollbar_width) = variant.scrollbar_width { - contents = contents.push(text(format!( - "scrollbar_width: {:?}", - scrollbar_width - ))); - } - - if let Some(scrollbar_margin) = variant.scrollbar_margin { - contents = contents.push(text(format!( - "scrollbar_margin: {:?}", - scrollbar_margin - ))); - } + 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(scroller_width) = variant.scroller_width { - contents = contents.push(text(format!( - "scroller_width: {:?}", - scroller_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); - 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); - } + let scroll_controls = + row![scroll_slider_controls, scroll_orientation_controls] + .spacing(20) + .width(Length::Fill); - if let Some(scrollbar_margin) = variant.scrollbar_margin { - scrollable = - scrollable.scrollbar_margin(scrollbar_margin); - } + let scroll_to_end_button = |direction: scrollable::Direction| { + button("Scroll to end") + .padding(10) + .width(Length::Units(120)) + .on_press(Message::ScrollToEnd(direction)) + }; - 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(), - ) - .spacing(20) - .width(Length::Fill) - .height(Length::Fill); + } + 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 = - 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() + 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(), + ) } - 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) + } + + 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(), }, - ] + } } +} - pub fn id(i: usize) -> scrollable::Id { - scrollable::Id::new(format!("scrollable-{}", i)) +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, + } } } diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index ff2929da..b10ef17e 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -1,10 +1,10 @@ mod echo; use iced::alignment::{self, Alignment}; -use iced::executor; use iced::widget::{ button, column, container, row, scrollable, text, text_input, Column, }; +use iced::{executor, Vector}; use iced::{ Application, Color, Command, Element, Length, Settings, Subscription, Theme, }; @@ -81,7 +81,10 @@ impl Application for WebSocket { echo::Event::MessageReceived(message) => { self.messages.push(message); - scrollable::snap_to(MESSAGE_LOG.clone(), 1.0) + scrollable::snap_to( + MESSAGE_LOG.clone(), + Vector::new(0.0, 1.0), + ) } }, Message::Server => Command::none(), |