summaryrefslogtreecommitdiffstats
path: root/examples/tour/src/main.rs
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-06-16 23:13:20 +0200
committerLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-06-16 23:15:12 +0200
commite0b4ddf7b725ad584399ebf19a6c624a3bfcfe10 (patch)
tree50c6d3882e0edc8bef090296003a53645d98f850 /examples/tour/src/main.rs
parent964ae95827f53f49bfc090e86c50378937074e5e (diff)
downloadiced-e0b4ddf7b725ad584399ebf19a6c624a3bfcfe10.tar.gz
iced-e0b4ddf7b725ad584399ebf19a6c624a3bfcfe10.tar.bz2
iced-e0b4ddf7b725ad584399ebf19a6c624a3bfcfe10.zip
Flatten state in `tour` example
Diffstat (limited to '')
-rw-r--r--examples/tour/src/main.rs600
1 files changed, 261 insertions, 339 deletions
diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs
index f624053c..6b10ec75 100644
--- a/examples/tour/src/main.rs
+++ b/examples/tour/src/main.rs
@@ -21,190 +21,27 @@ pub fn main() -> iced::Result {
.run()
}
-#[derive(Default)]
pub struct Tour {
- steps: Steps,
+ step: Step,
+ slider: u8,
+ layout: Layout,
+ spacing: u16,
+ text_size: u16,
+ text_color: Color,
+ language: Option<Language>,
+ toggler: bool,
+ image_width: u16,
+ image_filter_method: image::FilterMethod,
+ input_value: String,
+ input_is_secure: bool,
+ input_is_showing_icon: bool,
debug: bool,
}
-impl Tour {
- fn title(&self) -> String {
- format!("{} - Iced", self.steps.title())
- }
-
- fn update(&mut self, event: Message) {
- match event {
- Message::BackPressed => {
- self.steps.go_back();
- }
- Message::NextPressed => {
- self.steps.advance();
- }
- Message::StepMessage(step_msg) => {
- self.steps.update(step_msg, &mut self.debug);
- }
- }
- }
-
- fn view(&self) -> Element<Message> {
- let Tour { steps, .. } = self;
-
- let controls =
- row![]
- .push_maybe(steps.has_previous().then(|| {
- padded_button("Back")
- .on_press(Message::BackPressed)
- .style(button::secondary)
- }))
- .push(horizontal_space())
- .push_maybe(steps.can_continue().then(|| {
- padded_button("Next").on_press(Message::NextPressed)
- }));
-
- let content: Element<_> = column![
- steps.view(self.debug).map(Message::StepMessage),
- controls,
- ]
- .max_width(540)
- .spacing(20)
- .padding(20)
- .into();
-
- let scrollable = scrollable(
- container(if self.debug {
- content.explain(Color::BLACK)
- } else {
- content
- })
- .center_x(Length::Fill),
- );
-
- container(scrollable).center_y(Length::Fill).into()
- }
-}
-
#[derive(Debug, Clone)]
pub enum Message {
BackPressed,
NextPressed,
- StepMessage(StepMessage),
-}
-
-struct Steps {
- steps: Vec<Step>,
- current: usize,
-}
-
-impl Steps {
- fn new() -> Steps {
- Steps {
- steps: vec![
- Step::Welcome,
- Step::Slider { value: 50 },
- Step::RowsAndColumns {
- layout: Layout::Row,
- spacing: 20,
- },
- Step::Text {
- size: 30,
- color: Color::BLACK,
- },
- Step::Radio { selection: None },
- Step::Toggler {
- can_continue: false,
- },
- Step::Image {
- width: 300,
- filter_method: image::FilterMethod::Linear,
- },
- Step::Scrollable,
- Step::TextInput {
- value: String::new(),
- is_secure: false,
- is_showing_icon: false,
- },
- Step::Debugger,
- Step::End,
- ],
- current: 0,
- }
- }
-
- fn update(&mut self, msg: StepMessage, debug: &mut bool) {
- self.steps[self.current].update(msg, debug);
- }
-
- fn view(&self, debug: bool) -> Element<StepMessage> {
- self.steps[self.current].view(debug)
- }
-
- fn advance(&mut self) {
- if self.can_continue() {
- self.current += 1;
- }
- }
-
- fn go_back(&mut self) {
- if self.has_previous() {
- self.current -= 1;
- }
- }
-
- fn has_previous(&self) -> bool {
- self.current > 0
- }
-
- fn can_continue(&self) -> bool {
- self.current + 1 < self.steps.len()
- && self.steps[self.current].can_continue()
- }
-
- fn title(&self) -> &str {
- self.steps[self.current].title()
- }
-}
-
-impl Default for Steps {
- fn default() -> Self {
- Steps::new()
- }
-}
-
-enum Step {
- Welcome,
- Slider {
- value: u8,
- },
- RowsAndColumns {
- layout: Layout,
- spacing: u16,
- },
- Text {
- size: u16,
- color: Color,
- },
- Radio {
- selection: Option<Language>,
- },
- Toggler {
- can_continue: bool,
- },
- Image {
- width: u16,
- filter_method: image::FilterMethod,
- },
- Scrollable,
- TextInput {
- value: String,
- is_secure: bool,
- is_showing_icon: bool,
- },
- Debugger,
- End,
-}
-
-#[derive(Debug, Clone)]
-pub enum StepMessage {
SliderChanged(u8),
LayoutChanged(Layout),
SpacingChanged(u16),
@@ -220,147 +57,145 @@ pub enum StepMessage {
TogglerChanged(bool),
}
-impl<'a> Step {
- fn update(&mut self, msg: StepMessage, debug: &mut bool) {
- match msg {
- StepMessage::DebugToggled(value) => {
- if let Step::Debugger = self {
- *debug = value;
+impl Tour {
+ fn title(&self) -> String {
+ let screen = match self.step {
+ Step::Welcome => "Welcome",
+ Step::Radio => "Radio button",
+ Step::Toggler => "Toggler",
+ Step::Slider => "Slider",
+ Step::Text => "Text",
+ Step::Image => "Image",
+ Step::RowsAndColumns => "Rows and columns",
+ Step::Scrollable => "Scrollable",
+ Step::TextInput => "Text input",
+ Step::Debugger => "Debugger",
+ Step::End => "End",
+ };
+
+ format!("{} - Iced", screen)
+ }
+
+ fn update(&mut self, event: Message) {
+ match event {
+ Message::BackPressed => {
+ if let Some(step) = self.step.previous() {
+ self.step = step;
}
}
- StepMessage::LanguageSelected(language) => {
- if let Step::Radio { selection } = self {
- *selection = Some(language);
+ Message::NextPressed => {
+ if let Some(step) = self.step.next() {
+ self.step = step;
}
}
- StepMessage::SliderChanged(new_value) => {
- if let Step::Slider { value, .. } = self {
- *value = new_value;
- }
+ Message::SliderChanged(value) => {
+ self.slider = value;
}
- StepMessage::TextSizeChanged(new_size) => {
- if let Step::Text { size, .. } = self {
- *size = new_size;
- }
+ Message::LayoutChanged(layout) => {
+ self.layout = layout;
}
- StepMessage::TextColorChanged(new_color) => {
- if let Step::Text { color, .. } = self {
- *color = new_color;
- }
+ Message::SpacingChanged(spacing) => {
+ self.spacing = spacing;
}
- StepMessage::LayoutChanged(new_layout) => {
- if let Step::RowsAndColumns { layout, .. } = self {
- *layout = new_layout;
- }
+ Message::TextSizeChanged(text_size) => {
+ self.text_size = text_size;
}
- StepMessage::SpacingChanged(new_spacing) => {
- if let Step::RowsAndColumns { spacing, .. } = self {
- *spacing = new_spacing;
- }
+ Message::TextColorChanged(text_color) => {
+ self.text_color = text_color;
}
- StepMessage::ImageWidthChanged(new_width) => {
- if let Step::Image { width, .. } = self {
- *width = new_width;
- }
+ Message::LanguageSelected(language) => {
+ self.language = Some(language);
}
- StepMessage::ImageUseNearestToggled(use_nearest) => {
- if let Step::Image { filter_method, .. } = self {
- *filter_method = if use_nearest {
- image::FilterMethod::Nearest
- } else {
- image::FilterMethod::Linear
- };
- }
+ Message::ImageWidthChanged(image_width) => {
+ self.image_width = image_width;
}
- StepMessage::InputChanged(new_value) => {
- if let Step::TextInput { value, .. } = self {
- *value = new_value;
- }
+ Message::ImageUseNearestToggled(use_nearest) => {
+ self.image_filter_method = if use_nearest {
+ image::FilterMethod::Nearest
+ } else {
+ image::FilterMethod::Linear
+ };
}
- StepMessage::ToggleSecureInput(toggle) => {
- if let Step::TextInput { is_secure, .. } = self {
- *is_secure = toggle;
- }
+ Message::InputChanged(input_value) => {
+ self.input_value = input_value;
}
- StepMessage::TogglerChanged(value) => {
- if let Step::Toggler { can_continue, .. } = self {
- *can_continue = value;
- }
+ Message::ToggleSecureInput(is_secure) => {
+ self.input_is_secure = is_secure;
}
- StepMessage::ToggleTextInputIcon(toggle) => {
- if let Step::TextInput {
- is_showing_icon, ..
- } = self
- {
- *is_showing_icon = toggle;
- }
+ Message::ToggleTextInputIcon(show_icon) => {
+ self.input_is_showing_icon = show_icon;
}
- };
+ Message::DebugToggled(debug) => {
+ self.debug = debug;
+ }
+ Message::TogglerChanged(toggler) => {
+ self.toggler = toggler;
+ }
+ }
}
- fn title(&self) -> &str {
- match self {
- Step::Welcome => "Welcome",
- Step::Radio { .. } => "Radio button",
- Step::Toggler { .. } => "Toggler",
- Step::Slider { .. } => "Slider",
- Step::Text { .. } => "Text",
- Step::Image { .. } => "Image",
- Step::RowsAndColumns { .. } => "Rows and columns",
- Step::Scrollable => "Scrollable",
- Step::TextInput { .. } => "Text input",
- Step::Debugger => "Debugger",
- Step::End => "End",
- }
+ fn view(&self) -> Element<Message> {
+ let controls =
+ row![]
+ .push_maybe(self.step.previous().is_some().then(|| {
+ padded_button("Back")
+ .on_press(Message::BackPressed)
+ .style(button::secondary)
+ }))
+ .push(horizontal_space())
+ .push_maybe(self.can_continue().then(|| {
+ padded_button("Next").on_press(Message::NextPressed)
+ }));
+
+ let screen = match self.step {
+ Step::Welcome => self.welcome(),
+ Step::Radio => self.radio(),
+ Step::Toggler => self.toggler(),
+ Step::Slider => self.slider(),
+ Step::Text => self.text(),
+ Step::Image => self.image(),
+ Step::RowsAndColumns => self.rows_and_columns(),
+ Step::Scrollable => self.scrollable(),
+ Step::TextInput => self.text_input(),
+ Step::Debugger => self.debugger(),
+ Step::End => self.end(),
+ };
+
+ let content: Element<_> = column![screen, controls,]
+ .max_width(540)
+ .spacing(20)
+ .padding(20)
+ .into();
+
+ let scrollable = scrollable(
+ container(if self.debug {
+ content.explain(Color::BLACK)
+ } else {
+ content
+ })
+ .center_x(Length::Fill),
+ );
+
+ container(scrollable).center_y(Length::Fill).into()
}
fn can_continue(&self) -> bool {
- match self {
+ match self.step {
Step::Welcome => true,
- Step::Radio { selection } => *selection == Some(Language::Rust),
- Step::Toggler { can_continue } => *can_continue,
- Step::Slider { .. } => true,
- Step::Text { .. } => true,
- Step::Image { .. } => true,
- Step::RowsAndColumns { .. } => true,
+ Step::Radio => self.language == Some(Language::Rust),
+ Step::Toggler => self.toggler,
+ Step::Slider => true,
+ Step::Text => true,
+ Step::Image => true,
+ Step::RowsAndColumns => true,
Step::Scrollable => true,
- Step::TextInput { value, .. } => !value.is_empty(),
+ Step::TextInput => !self.input_value.is_empty(),
Step::Debugger => true,
Step::End => false,
}
}
- fn view(&self, debug: bool) -> Element<StepMessage> {
- match self {
- Step::Welcome => Self::welcome(),
- Step::Radio { selection } => Self::radio(*selection),
- Step::Toggler { can_continue } => Self::toggler(*can_continue),
- Step::Slider { value } => Self::slider(*value),
- Step::Text { size, color } => Self::text(*size, *color),
- Step::Image {
- width,
- filter_method,
- } => Self::image(*width, *filter_method),
- Step::RowsAndColumns { layout, spacing } => {
- Self::rows_and_columns(*layout, *spacing)
- }
- Step::Scrollable => Self::scrollable(),
- Step::TextInput {
- value,
- is_secure,
- is_showing_icon,
- } => Self::text_input(value, *is_secure, *is_showing_icon),
- Step::Debugger => Self::debugger(debug),
- Step::End => Self::end(),
- }
- .into()
- }
-
- fn container(title: &str) -> Column<'_, StepMessage> {
- column![text(title).size(50)].spacing(20)
- }
-
- fn welcome() -> Column<'a, StepMessage> {
+ fn welcome(&self) -> Column<Message> {
Self::container("Welcome!")
.push(
"This is a simple tour meant to showcase a bunch of widgets \
@@ -389,7 +224,7 @@ impl<'a> Step {
)
}
- fn slider(value: u8) -> Column<'a, StepMessage> {
+ fn slider(&self) -> Column<Message> {
Self::container("Slider")
.push(
"A slider allows you to smoothly select a value from a range \
@@ -399,47 +234,48 @@ impl<'a> Step {
"The following slider lets you choose an integer from \
0 to 100:",
)
- .push(slider(0..=100, value, StepMessage::SliderChanged))
+ .push(slider(0..=100, self.slider, Message::SliderChanged))
.push(
- text(value.to_string())
+ text(self.slider.to_string())
.width(Length::Fill)
.horizontal_alignment(alignment::Horizontal::Center),
)
}
- fn rows_and_columns(
- layout: Layout,
- spacing: u16,
- ) -> Column<'a, StepMessage> {
- let row_radio =
- radio("Row", Layout::Row, Some(layout), StepMessage::LayoutChanged);
+ fn rows_and_columns(&self) -> Column<Message> {
+ let row_radio = radio(
+ "Row",
+ Layout::Row,
+ Some(self.layout),
+ Message::LayoutChanged,
+ );
let column_radio = radio(
"Column",
Layout::Column,
- Some(layout),
- StepMessage::LayoutChanged,
+ Some(self.layout),
+ Message::LayoutChanged,
);
- let layout_section: Element<_> = match layout {
+ let layout_section: Element<_> = match self.layout {
Layout::Row => {
- row![row_radio, column_radio].spacing(spacing).into()
- }
- Layout::Column => {
- column![row_radio, column_radio].spacing(spacing).into()
+ row![row_radio, column_radio].spacing(self.spacing).into()
}
+ Layout::Column => column![row_radio, column_radio]
+ .spacing(self.spacing)
+ .into(),
};
let spacing_section = column![
- slider(0..=80, spacing, StepMessage::SpacingChanged),
- text!("{spacing} px")
+ slider(0..=80, self.spacing, Message::SpacingChanged),
+ text!("{} px", self.spacing)
.width(Length::Fill)
.horizontal_alignment(alignment::Horizontal::Center),
]
.spacing(10);
Self::container("Rows and columns")
- .spacing(spacing)
+ .spacing(self.spacing)
.push(
"Iced uses a layout model based on flexbox to position UI \
elements.",
@@ -453,11 +289,14 @@ impl<'a> Step {
.push(spacing_section)
}
- fn text(size: u16, color: Color) -> Column<'a, StepMessage> {
+ fn text(&self) -> Column<Message> {
+ let size = self.text_size;
+ let color = self.text_color;
+
let size_section = column![
"You can change its size:",
text!("This text is {size} pixels").size(size),
- slider(10..=70, size, StepMessage::TextSizeChanged),
+ slider(10..=70, size, Message::TextSizeChanged),
]
.padding(20)
.spacing(20);
@@ -486,7 +325,7 @@ impl<'a> Step {
.push(color_section)
}
- fn radio(selection: Option<Language>) -> Column<'a, StepMessage> {
+ fn radio(&self) -> Column<Message> {
let question = column![
text("Iced is written in...").size(24),
column(
@@ -497,8 +336,8 @@ impl<'a> Step {
radio(
language,
language,
- selection,
- StepMessage::LanguageSelected,
+ self.language,
+ Message::LanguageSelected,
)
})
.map(Element::from)
@@ -521,27 +360,27 @@ impl<'a> Step {
)
}
- fn toggler(can_continue: bool) -> Column<'a, StepMessage> {
+ fn toggler(&self) -> Column<Message> {
Self::container("Toggler")
.push("A toggler is mostly used to enable or disable something.")
.push(
Container::new(toggler(
"Toggle me to continue...".to_owned(),
- can_continue,
- StepMessage::TogglerChanged,
+ self.toggler,
+ Message::TogglerChanged,
))
.padding([0, 40]),
)
}
- fn image(
- width: u16,
- filter_method: image::FilterMethod,
- ) -> Column<'a, StepMessage> {
+ fn image(&self) -> Column<Message> {
+ let width = self.image_width;
+ let filter_method = self.image_filter_method;
+
Self::container("Image")
.push("An image that tries to keep its aspect ratio.")
.push(ferris(width, filter_method))
- .push(slider(100..=500, width, StepMessage::ImageWidthChanged))
+ .push(slider(100..=500, width, Message::ImageWidthChanged))
.push(
text!("Width: {width} px")
.width(Length::Fill)
@@ -552,12 +391,12 @@ impl<'a> Step {
"Use nearest interpolation",
filter_method == image::FilterMethod::Nearest,
)
- .on_toggle(StepMessage::ImageUseNearestToggled),
+ .on_toggle(Message::ImageUseNearestToggled),
)
.align_items(Alignment::Center)
}
- fn scrollable() -> Column<'a, StepMessage> {
+ fn scrollable(&self) -> Column<Message> {
Self::container("Scrollable")
.push(
"Iced supports scrollable content. Try it out! Find the \
@@ -584,13 +423,13 @@ impl<'a> Step {
)
}
- fn text_input(
- value: &str,
- is_secure: bool,
- is_showing_icon: bool,
- ) -> Column<'_, StepMessage> {
+ fn text_input(&self) -> Column<Message> {
+ let value = &self.input_value;
+ let is_secure = self.input_is_secure;
+ let is_showing_icon = self.input_is_showing_icon;
+
let mut text_input = text_input("Type something to continue...", value)
- .on_input(StepMessage::InputChanged)
+ .on_input(Message::InputChanged)
.padding(10)
.size(30);
@@ -609,11 +448,11 @@ impl<'a> Step {
.push(text_input.secure(is_secure))
.push(
checkbox("Enable password mode", is_secure)
- .on_toggle(StepMessage::ToggleSecureInput),
+ .on_toggle(Message::ToggleSecureInput),
)
.push(
checkbox("Show icon", is_showing_icon)
- .on_toggle(StepMessage::ToggleTextInputIcon),
+ .on_toggle(Message::ToggleTextInputIcon),
)
.push(
"A text input produces a message every time it changes. It is \
@@ -630,7 +469,7 @@ impl<'a> Step {
)
}
- fn debugger(debug: bool) -> Column<'a, StepMessage> {
+ fn debugger(&self) -> Column<Message> {
Self::container("Debugger")
.push(
"You can ask Iced to visually explain the layouting of the \
@@ -641,23 +480,85 @@ impl<'a> Step {
see element boundaries.",
)
.push(
- checkbox("Explain layout", debug)
- .on_toggle(StepMessage::DebugToggled),
+ checkbox("Explain layout", self.debug)
+ .on_toggle(Message::DebugToggled),
)
.push("Feel free to go back and take a look.")
}
- fn end() -> Column<'a, StepMessage> {
+ fn end(&self) -> Column<Message> {
Self::container("You reached the end!")
.push("This tour will be updated as more features are added.")
.push("Make sure to keep an eye on it!")
}
+
+ fn container(title: &str) -> Column<'_, Message> {
+ column![text(title).size(50)].spacing(20)
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum Step {
+ Welcome,
+ Slider,
+ RowsAndColumns,
+ Text,
+ Radio,
+ Toggler,
+ Image,
+ Scrollable,
+ TextInput,
+ Debugger,
+ End,
+}
+
+impl Step {
+ const ALL: &'static [Self] = &[
+ Self::Welcome,
+ Self::Slider,
+ Self::RowsAndColumns,
+ Self::Text,
+ Self::Radio,
+ Self::Toggler,
+ Self::Image,
+ Self::Scrollable,
+ Self::TextInput,
+ Self::Debugger,
+ Self::End,
+ ];
+
+ pub fn next(self) -> Option<Step> {
+ Self::ALL
+ .get(
+ Self::ALL
+ .iter()
+ .copied()
+ .position(|step| step == self)
+ .expect("Step must exist")
+ + 1,
+ )
+ .copied()
+ }
+
+ pub fn previous(self) -> Option<Step> {
+ let position = Self::ALL
+ .iter()
+ .copied()
+ .position(|step| step == self)
+ .expect("Step must exist");
+
+ if position > 0 {
+ Some(Self::ALL[position - 1])
+ } else {
+ None
+ }
+ }
}
fn ferris<'a>(
width: u16,
filter_method: image::FilterMethod,
-) -> Container<'a, StepMessage> {
+) -> Container<'a, Message> {
container(
// This should go away once we unify resource loading on native
// platforms
@@ -679,9 +580,9 @@ fn padded_button<Message: Clone>(label: &str) -> Button<'_, Message> {
fn color_slider<'a>(
component: f32,
update: impl Fn(f32) -> Color + 'a,
-) -> Slider<'a, f64, StepMessage> {
+) -> Slider<'a, f64, Message> {
slider(0.0..=1.0, f64::from(component), move |c| {
- StepMessage::TextColorChanged(update(c as f32))
+ Message::TextColorChanged(update(c as f32))
})
.step(0.01)
}
@@ -727,3 +628,24 @@ pub enum Layout {
Row,
Column,
}
+
+impl Default for Tour {
+ fn default() -> Self {
+ Self {
+ step: Step::Welcome,
+ slider: 50,
+ layout: Layout::Row,
+ spacing: 20,
+ text_size: 30,
+ text_color: Color::BLACK,
+ language: None,
+ toggler: false,
+ image_width: 300,
+ image_filter_method: image::FilterMethod::Linear,
+ input_value: String::new(),
+ input_is_secure: false,
+ input_is_showing_icon: false,
+ debug: false,
+ }
+ }
+}