diff --git a/examples/split_scroller/Cargo.toml b/examples/split_scroller/Cargo.toml new file mode 100644 index 00000000..9ad10b3b --- /dev/null +++ b/examples/split_scroller/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "split_scroller" +version = "0.1.0" +authors = ["Kaiden42 "] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +iced_aw = { workspace = true, features = ["split"] } +iced.workspace = true +once_cell = "1.17.1" \ No newline at end of file diff --git a/examples/split_scroller/src/main.rs b/examples/split_scroller/src/main.rs new file mode 100644 index 00000000..5235ff22 --- /dev/null +++ b/examples/split_scroller/src/main.rs @@ -0,0 +1,537 @@ +use iced::executor; +use iced::{Application, Command, Element, Settings, Theme}; +use router::Router; + +#[derive(Debug, Clone)] +pub enum Message { + RouterMessage(router::Message), +} + +struct MyApp { + router: Router, +} + +pub fn main() -> iced::Result { + MyApp::run(Settings::default()) +} + +impl Application for MyApp { + type Executor = executor::Default; + type Message = Message; + type Theme = Theme; + type Flags = (); + + fn new(_flags: Self::Flags) -> (Self, Command) { + ( + MyApp { + router: Router::new(), + }, + Command::none(), + ) + } + + fn title(&self) -> String { + String::from("Scrollable - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::RouterMessage(router_msg) => { + return self.router.update(router_msg).map(Message::RouterMessage); + } + } + + // Command::none() + } + + fn view(&self) -> Element { + self.router.view().map(Message::RouterMessage) + } + + fn theme(&self) -> Self::Theme { + Theme::Dark + } +} + +mod router { + use iced::{ + widget::{button, column, row}, + Command, Element, + }; + + use crate::demo::{self, ScrollableDemo}; + + #[derive(Debug, Clone)] + pub enum Message { + SplittedMessage(splitted::Message), + DemoMessage(demo::Message), + GoToDemo, + GoToSplitted, + } + pub struct Router { + previous_state: Option, + state: ViewState, + } + impl Router { + pub fn new() -> Self { + Self { + previous_state: None, + state: ViewState::splitted(), + } + } + fn next_state(&mut self, next: ViewState) { + let old_state = std::mem::replace(&mut self.state, next); + self.previous_state = Some(old_state); + } + fn _next_state_skip(&mut self, next: ViewState) { + self.state = next; + } + fn _back(&mut self) { + if let Some(s) = self.previous_state.take() { + self.state = s; + } + } + + pub fn view(&self) -> Element { + let nav_bar = row![ + button("Go to Demo").on_press(Message::GoToDemo), + button("Go to Splitted").on_press(Message::GoToSplitted), + ] + .padding(20) + .spacing(10); + let view_cp = self.state.view(); + column![nav_bar, view_cp].into() + } + + pub fn update(&mut self, message: Message) -> Command { + match message { + Message::GoToDemo => { + self.next_state(ViewState::demo()); + } + Message::GoToSplitted => { + self.next_state(ViewState::splitted()); + } + Message::SplittedMessage(msg) => { + if let ViewState::Splitted { state } = &mut self.state { + return state.update(msg).map(Message::SplittedMessage); + } + } + Message::DemoMessage(demo_msg) => { + if let ViewState::Demo { state } = &mut self.state { + return state.update(demo_msg).map(Message::DemoMessage); + } + } + } + + Command::none() + } + } + + pub enum ViewState { + Splitted { state: splitted::State }, + Demo { state: demo::ScrollableDemo }, + } + + impl ViewState { + pub fn splitted() -> Self { + Self::Splitted { + state: splitted::State::new(), + } + } + pub fn demo() -> Self { + Self::Demo { + state: ScrollableDemo::new(), + } + } + pub fn view(&self) -> Element { + match self { + Self::Splitted { state } => state.view().map(Message::SplittedMessage), + Self::Demo { state } => state.view().map(Message::DemoMessage), + } + } + } + + mod splitted { + use iced::{ + widget::{container, text}, + Command, Element, + }; + + use crate::demo::{self, ScrollableDemo}; + + #[derive(Debug, Clone)] + pub enum Message { + DemoMessage(demo::Message), + OnVerResize(u16), + } + + pub struct State { + demo_state: ScrollableDemo, + divider_position: u16, + } + impl State { + pub fn new() -> Self { + Self { + demo_state: ScrollableDemo::new(), + divider_position: 200, + } + } + pub fn update(&mut self, message: Message) -> Command { + match message { + Message::OnVerResize(new_size) => { + self.divider_position = new_size; + Command::none() + } + Message::DemoMessage(msg) => { + self.demo_state.update(msg).map(Message::DemoMessage) + } + } + } + pub fn view(&self) -> Element { + let demo = self.demo_state.view().map(Message::DemoMessage); + + let first: Element<_> = container(text("First Container")).into(); + let splitted = iced_aw::split::Split::new( + first, + demo, + Some(self.divider_position), + iced_aw::split::Axis::Vertical, + Message::OnVerResize, + ) + .spacing(1.0) + .min_size_second(300); + + splitted.into() + } + } + } +} + +mod demo { + 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::{theme, Alignment, Color}; + use iced::{Command, Element, Length, Theme}; + use once_cell::sync::Lazy; + + static SCROLLABLE_ID: Lazy = Lazy::new(scrollable::Id::unique); + + pub struct ScrollableDemo { + scrollable_direction: Direction, + scrollbar_width: u16, + scrollbar_margin: u16, + scroller_width: u16, + current_scroll_offset: scrollable::RelativeOffset, + } + + #[derive(Debug, Clone, Eq, PartialEq, Copy)] + pub enum Direction { + Vertical, + Horizontal, + Multi, + } + + #[derive(Debug, Clone)] + pub enum Message { + SwitchDirection(Direction), + ScrollbarWidthChanged(u16), + ScrollbarMarginChanged(u16), + ScrollerWidthChanged(u16), + ScrollToBeginning, + ScrollToEnd, + Scrolled(scrollable::RelativeOffset), + } + impl ScrollableDemo { + pub fn new() -> Self { + ScrollableDemo { + scrollable_direction: Direction::Vertical, + scrollbar_width: 10, + scrollbar_margin: 0, + scroller_width: 10, + current_scroll_offset: scrollable::RelativeOffset::START, + } + } + + pub 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::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(offset) => { + self.current_scroll_offset = offset; + + Command::none() + } + } + } + + pub 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_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 = + 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 = 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(), + 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 = + 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(), + ) + } + } + + 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: bool) -> Scrollbar { + style.hovered(&theme::Scrollable::Default, is_mouse_over) + } + + fn hovered_horizontal(&self, style: &Self::Style, _is_mouse_over: bool) -> 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, + } + } + } +}