iced-yoda/examples/scrollable/src/main.rs

377 lines
12 KiB
Rust
Raw Normal View History

use iced::widget::scrollable::{Properties, Scrollbar, Scroller};
use iced::widget::{
button, column, container, horizontal_space, progress_bar, radio, row,
scrollable, slider, text, vertical_space,
2020-10-22 16:10:10 -05:00
};
use iced::{executor, theme, Alignment, Color};
use iced::{Application, Command, Element, Length, Settings, Theme};
use once_cell::sync::Lazy;
static SCROLLABLE_ID: Lazy<scrollable::Id> = Lazy::new(scrollable::Id::unique);
2020-10-22 16:10:10 -05:00
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,
2020-10-22 16:10:10 -05:00
}
#[derive(Debug, Clone, Eq, PartialEq, Copy)]
enum Direction {
Vertical,
Horizontal,
Multi,
2022-09-08 17:36:59 -04:00
}
2020-10-22 16:10:10 -05:00
#[derive(Debug, Clone)]
enum Message {
SwitchDirection(Direction),
ScrollbarWidthChanged(u16),
ScrollbarMarginChanged(u16),
ScrollerWidthChanged(u16),
ScrollToBeginning,
ScrollToEnd,
2023-04-17 13:55:40 -07:00
Scrolled(scrollable::Viewport),
2020-10-22 16:10:10 -05:00
}
impl Application for ScrollableDemo {
type Executor = executor::Default;
2020-10-22 16:10:10 -05:00
type Message = Message;
type Theme = Theme;
type Flags = ();
fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
(
ScrollableDemo {
scrollable_direction: Direction::Vertical,
scrollbar_width: 10,
scrollbar_margin: 0,
scroller_width: 10,
current_scroll_offset: scrollable::RelativeOffset::START,
},
Command::none(),
)
2020-10-22 16:10:10 -05:00
}
fn title(&self) -> String {
String::from("Scrollable - Iced")
}
fn update(&mut self, message: Message) -> Command<Message> {
2020-10-22 16:10:10 -05:00
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::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,
)
}
2023-04-17 13:55:40 -07:00
Message::Scrolled(viewport) => {
self.current_scroll_offset = viewport.relative_offset();
Command::none()
}
2020-10-22 16:10:10 -05:00
}
}
fn view(&self) -> Element<Message> {
let scrollbar_width_slider = slider(
0..=15,
self.scrollbar_width,
Message::ScrollbarWidthChanged,
2020-10-22 16:10:10 -05:00
);
let scrollbar_margin_slider = slider(
0..=15,
self.scrollbar_margin,
Message::ScrollbarMarginChanged,
);
let scroller_width_slider =
slider(0..=15, self.scroller_width, Message::ScrollerWidthChanged);
2020-10-22 16:10:10 -05:00
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);
2020-10-22 16:10:10 -05:00
let scroll_controls =
row![scroll_slider_controls, scroll_orientation_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<Message> =
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)
.vertical_scroll(
Properties::new()
.width(self.scrollbar_width)
.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(),
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)
.horizontal_scroll(
Properties::new()
.width(self.scrollbar_width)
.margin(self.scrollbar_margin)
.scroller_width(self.scroller_width),
)
.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)
.vertical_scroll(
Properties::new()
.width(self.scrollbar_width)
.margin(self.scrollbar_margin)
.scroller_width(self.scroller_width),
)
.horizontal_scroll(
Properties::new()
.width(self.scrollbar_width)
.margin(self.scrollbar_margin)
.scroller_width(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)
Support conversion from Fn trait to custom theme ...instead of just from function pointers. I'm making this change not because I actually want to pass a closure, but to make passing a single fixed function work. This commit also simplifies the scrollable example slightly, and without the other half of this change that simplified example fails to compile with: ``` error[E0277]: the trait bound `iced::theme::ProgressBar: From<for<'a> fn(&'a Theme) -> iced::widget::progress_bar::Appearance {progress_bar_custom_style}>` is not satisfied --> examples/scrollable/src/main.rs:292:28 | 292 | .style(progress_bar_custom_style) | ----- ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<for<'a> fn(&'a Theme) -> iced::widget::progress_bar::Appearance {progress_bar_custom_style}>` is not implemented for `iced::theme::ProgressBar` | | | required by a bound introduced by this call | = help: the trait `From<for<'a> fn(&'a Theme) -> iced::widget::progress_bar::Appearance>` is implemented for `iced::theme::ProgressBar` = note: required for `for<'a> fn(&'a Theme) -> iced::widget::progress_bar::Appearance {progress_bar_custom_style}` to implement `Into<iced::theme::ProgressBar>` note: required by a bound in `iced::widget::ProgressBar::<Renderer>::style` --> /home/marienz/src/iced/widget/src/progress_bar.rs:77:21 | 77 | style: impl Into<<Renderer::Theme as StyleSheet>::Style>, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `ProgressBar::<Renderer>::style` ``` This happens because `progress_bar_custom_style` by itself is a function item, which is typically coerced to a function pointer when one is needed, but not in this case. It is possible to work around this on the caller's side, but especially since the compiler diagnostic for this is a bit rough (see https://github.com/rust-lang/rust/issues/100116) let's try to make it work out of the box.
2023-05-21 23:41:26 +10:00
.style(progress_bar_custom_style)
.into()
}
Direction::Multi => column![
progress_bar(0.0..=1.0, self.current_scroll_offset.y),
Support conversion from Fn trait to custom theme ...instead of just from function pointers. I'm making this change not because I actually want to pass a closure, but to make passing a single fixed function work. This commit also simplifies the scrollable example slightly, and without the other half of this change that simplified example fails to compile with: ``` error[E0277]: the trait bound `iced::theme::ProgressBar: From<for<'a> fn(&'a Theme) -> iced::widget::progress_bar::Appearance {progress_bar_custom_style}>` is not satisfied --> examples/scrollable/src/main.rs:292:28 | 292 | .style(progress_bar_custom_style) | ----- ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<for<'a> fn(&'a Theme) -> iced::widget::progress_bar::Appearance {progress_bar_custom_style}>` is not implemented for `iced::theme::ProgressBar` | | | required by a bound introduced by this call | = help: the trait `From<for<'a> fn(&'a Theme) -> iced::widget::progress_bar::Appearance>` is implemented for `iced::theme::ProgressBar` = note: required for `for<'a> fn(&'a Theme) -> iced::widget::progress_bar::Appearance {progress_bar_custom_style}` to implement `Into<iced::theme::ProgressBar>` note: required by a bound in `iced::widget::ProgressBar::<Renderer>::style` --> /home/marienz/src/iced/widget/src/progress_bar.rs:77:21 | 77 | style: impl Into<<Renderer::Theme as StyleSheet>::Style>, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `ProgressBar::<Renderer>::style` ``` This happens because `progress_bar_custom_style` by itself is a function item, which is typically coerced to a function pointer when one is needed, but not in this case. It is possible to work around this on the caller's side, but especially since the compiler diagnostic for this is a bit rough (see https://github.com/rust-lang/rust/issues/100116) let's try to make it work out of the box.
2023-05-21 23:41:26 +10:00
progress_bar(0.0..=1.0, self.current_scroll_offset.x)
.style(progress_bar_custom_style)
]
.spacing(10)
.into(),
};
2020-10-22 16:10:10 -05:00
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(),
)
2020-10-22 16:10:10 -05:00
}
fn theme(&self) -> Self::Theme {
Theme::Dark
}
2020-10-22 16:10:10 -05:00
}
struct ScrollbarCustomStyle;
2020-10-22 16:10:10 -05:00
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,
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(),
},
}
} else {
self.active(style)
}
2020-10-22 16:10:10 -05:00
}
}
Support conversion from Fn trait to custom theme ...instead of just from function pointers. I'm making this change not because I actually want to pass a closure, but to make passing a single fixed function work. This commit also simplifies the scrollable example slightly, and without the other half of this change that simplified example fails to compile with: ``` error[E0277]: the trait bound `iced::theme::ProgressBar: From<for<'a> fn(&'a Theme) -> iced::widget::progress_bar::Appearance {progress_bar_custom_style}>` is not satisfied --> examples/scrollable/src/main.rs:292:28 | 292 | .style(progress_bar_custom_style) | ----- ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<for<'a> fn(&'a Theme) -> iced::widget::progress_bar::Appearance {progress_bar_custom_style}>` is not implemented for `iced::theme::ProgressBar` | | | required by a bound introduced by this call | = help: the trait `From<for<'a> fn(&'a Theme) -> iced::widget::progress_bar::Appearance>` is implemented for `iced::theme::ProgressBar` = note: required for `for<'a> fn(&'a Theme) -> iced::widget::progress_bar::Appearance {progress_bar_custom_style}` to implement `Into<iced::theme::ProgressBar>` note: required by a bound in `iced::widget::ProgressBar::<Renderer>::style` --> /home/marienz/src/iced/widget/src/progress_bar.rs:77:21 | 77 | style: impl Into<<Renderer::Theme as StyleSheet>::Style>, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `ProgressBar::<Renderer>::style` ``` This happens because `progress_bar_custom_style` by itself is a function item, which is typically coerced to a function pointer when one is needed, but not in this case. It is possible to work around this on the caller's side, but especially since the compiler diagnostic for this is a bit rough (see https://github.com/rust-lang/rust/issues/100116) let's try to make it work out of the box.
2023-05-21 23:41:26 +10:00
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,
}
2020-10-22 16:10:10 -05:00
}