use iced::widget::scrollable::{Properties, Scrollbar, Scroller}; use iced::widget::{ button, column, container, horizontal_space, progress_bar, radio, row, scrollable, slider, text, vertical_space, }; use iced::{executor, theme, Alignment, Color}; use iced::{Application, Command, Element, Length, Settings, Theme}; use once_cell::sync::Lazy; static SCROLLABLE_ID: Lazy = Lazy::new(scrollable::Id::unique); pub fn main() -> iced::Result { ScrollableDemo::run(Settings::default()) } struct ScrollableDemo { scrollable_direction: Direction, scrollbar_width: u16, scrollbar_margin: u16, scroller_width: u16, current_scroll_offset: scrollable::RelativeOffset, alignment: scrollable::Alignment, } #[derive(Debug, Clone, Eq, PartialEq, Copy)] enum Direction { Vertical, Horizontal, Multi, } #[derive(Debug, Clone)] enum Message { SwitchDirection(Direction), AlignmentChanged(scrollable::Alignment), ScrollbarWidthChanged(u16), ScrollbarMarginChanged(u16), ScrollerWidthChanged(u16), ScrollToBeginning, ScrollToEnd, Scrolled(scrollable::Viewport), } impl Application for ScrollableDemo { type Executor = executor::Default; type Message = Message; type Theme = Theme; type Flags = (); fn new(_flags: Self::Flags) -> (Self, Command) { ( ScrollableDemo { scrollable_direction: Direction::Vertical, scrollbar_width: 10, scrollbar_margin: 0, scroller_width: 10, current_scroll_offset: scrollable::RelativeOffset::START, alignment: scrollable::Alignment::Start, }, Command::none(), ) } fn title(&self) -> String { String::from("Scrollable - Iced") } fn update(&mut self, message: Message) -> Command { match message { Message::SwitchDirection(direction) => { self.current_scroll_offset = scrollable::RelativeOffset::START; self.scrollable_direction = direction; scrollable::snap_to( SCROLLABLE_ID.clone(), self.current_scroll_offset, ) } Message::AlignmentChanged(alignment) => { self.current_scroll_offset = scrollable::RelativeOffset::START; self.alignment = alignment; scrollable::snap_to( SCROLLABLE_ID.clone(), self.current_scroll_offset, ) } Message::ScrollbarWidthChanged(width) => { self.scrollbar_width = width; Command::none() } Message::ScrollbarMarginChanged(margin) => { self.scrollbar_margin = margin; Command::none() } Message::ScrollerWidthChanged(width) => { self.scroller_width = width; Command::none() } Message::ScrollToBeginning => { self.current_scroll_offset = scrollable::RelativeOffset::START; scrollable::snap_to( SCROLLABLE_ID.clone(), self.current_scroll_offset, ) } Message::ScrollToEnd => { self.current_scroll_offset = scrollable::RelativeOffset::END; scrollable::snap_to( SCROLLABLE_ID.clone(), self.current_scroll_offset, ) } Message::Scrolled(viewport) => { self.current_scroll_offset = viewport.relative_offset(); Command::none() } } } fn view(&self) -> Element { 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 scroll_slider_controls = column![ text("Scrollbar width:"), scrollbar_width_slider, text("Scrollbar margin:"), scrollbar_margin_slider, text("Scroller width:"), scroller_width_slider, ] .spacing(10) .width(Length::Fill); 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(), ]) .spacing(10) .width(Length::Fill); let scroll_alignment_controls = column(vec![ text("Scrollable alignment:").into(), radio( "Start", scrollable::Alignment::Start, Some(self.alignment), Message::AlignmentChanged, ) .into(), radio( "End", scrollable::Alignment::End, Some(self.alignment), Message::AlignmentChanged, ) .into(), ]) .spacing(10) .width(Length::Fill); let scroll_controls = row![ scroll_slider_controls, scroll_orientation_controls, scroll_alignment_controls ] .spacing(20) .width(Length::Fill); let scroll_to_end_button = || { button("Scroll to end") .padding(10) .on_press(Message::ScrollToEnd) }; let scroll_to_beginning_button = || { button("Scroll to beginning") .padding(10) .on_press(Message::ScrollToBeginning) }; let scrollable_content: Element = Element::from(match self.scrollable_direction { Direction::Vertical => scrollable( column![ scroll_to_end_button(), text("Beginning!"), vertical_space(1200), text("Middle!"), vertical_space(1200), text("End!"), scroll_to_beginning_button(), ] .width(Length::Fill) .align_items(Alignment::Center) .padding([40, 0, 40, 0]) .spacing(40), ) .height(Length::Fill) .direction(scrollable::Direction::Vertical( Properties::new() .width(self.scrollbar_width) .margin(self.scrollbar_margin) .scroller_width(self.scroller_width) .alignment(self.alignment), )) .id(SCROLLABLE_ID.clone()) .on_scroll(Message::Scrolled), Direction::Horizontal => scrollable( row![ scroll_to_end_button(), text("Beginning!"), horizontal_space(1200), text("Middle!"), horizontal_space(1200), text("End!"), scroll_to_beginning_button(), ] .height(450) .align_items(Alignment::Center) .padding([0, 40, 0, 40]) .spacing(40), ) .height(Length::Fill) .direction(scrollable::Direction::Horizontal( Properties::new() .width(self.scrollbar_width) .margin(self.scrollbar_margin) .scroller_width(self.scroller_width) .alignment(self.alignment), )) .style(theme::Scrollable::custom(ScrollbarCustomStyle)) .id(SCROLLABLE_ID.clone()) .on_scroll(Message::Scrolled), Direction::Multi => scrollable( //horizontal content row![ column![ text("Let's do some scrolling!"), vertical_space(2400) ], scroll_to_end_button(), text("Horizontal - Beginning!"), horizontal_space(1200), //vertical content column![ text("Horizontal - Middle!"), scroll_to_end_button(), text("Vertical - Beginning!"), vertical_space(1200), text("Vertical - Middle!"), vertical_space(1200), text("Vertical - End!"), scroll_to_beginning_button(), vertical_space(40), ] .spacing(40), horizontal_space(1200), text("Horizontal - End!"), scroll_to_beginning_button(), ] .align_items(Alignment::Center) .padding([0, 40, 0, 40]) .spacing(40), ) .height(Length::Fill) .direction({ let properties = Properties::new() .width(self.scrollbar_width) .margin(self.scrollbar_margin) .scroller_width(self.scroller_width) .alignment(self.alignment); scrollable::Direction::Both { horizontal: properties, vertical: properties, } }) .style(theme::Scrollable::Custom(Box::new( ScrollbarCustomStyle, ))) .id(SCROLLABLE_ID.clone()) .on_scroll(Message::Scrolled), }); let progress_bars: Element = 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(progress_bar_custom_style) .into() } 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(progress_bar_custom_style) ] .spacing(10) .into(), }; let content: Element = 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) -> Self::Theme { Theme::Dark } } struct ScrollbarCustomStyle; 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, is_mouse_over_scrollbar: bool, ) -> Scrollbar { style.hovered(&theme::Scrollable::Default, is_mouse_over_scrollbar) } fn hovered_horizontal( &self, style: &Self::Style, is_mouse_over_scrollbar: bool, ) -> Scrollbar { if is_mouse_over_scrollbar { Scrollbar { background: style .active(&theme::Scrollable::default()) .background, border_radius: 0.0.into(), border_width: 0.0, border_color: Default::default(), scroller: Scroller { color: Color::from_rgb8(250, 85, 134), border_radius: 0.0.into(), border_width: 0.0, border_color: Default::default(), }, } } else { self.active(style) } } } fn progress_bar_custom_style(theme: &Theme) -> progress_bar::Appearance { progress_bar::Appearance { background: theme.extended_palette().background.strong.color.into(), bar: Color::from_rgb8(250, 85, 134).into(), border_radius: 0.0.into(), } }