summaryrefslogtreecommitdiffstats
path: root/examples/tour
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2020-01-20 06:27:01 +0100
committerLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2020-01-20 06:27:01 +0100
commit7cea7371150e6de28032827519936008592f112d (patch)
tree3bfd82272094ef69493de622af6c9b06389a7c27 /examples/tour
parent04086a90c9e933ebfb42de378054e1115b33529d (diff)
downloadiced-7cea7371150e6de28032827519936008592f112d.tar.gz
iced-7cea7371150e6de28032827519936008592f112d.tar.bz2
iced-7cea7371150e6de28032827519936008592f112d.zip
Package examples and remove `dev-dependencies`
Diffstat (limited to 'examples/tour')
-rw-r--r--examples/tour/Cargo.toml13
-rw-r--r--examples/tour/images/ferris.pngbin0 -> 33061 bytes
-rw-r--r--examples/tour/src/main.rs794
3 files changed, 807 insertions, 0 deletions
diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml
new file mode 100644
index 00000000..10c3f1da
--- /dev/null
+++ b/examples/tour/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "tour"
+version = "0.1.0"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../.." }
+env_logger = "0.7"
+
+[target.'cfg(target_arch = "wasm32")'.dependencies]
+wasm-bindgen = "0.2.51"
diff --git a/examples/tour/images/ferris.png b/examples/tour/images/ferris.png
new file mode 100644
index 00000000..ebce1a14
--- /dev/null
+++ b/examples/tour/images/ferris.png
Binary files differ
diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs
new file mode 100644
index 00000000..43c7e50f
--- /dev/null
+++ b/examples/tour/src/main.rs
@@ -0,0 +1,794 @@
+use iced::{
+ button, scrollable, slider, text_input, Button, Checkbox, Color, Column,
+ Container, Element, HorizontalAlignment, Image, Length, Radio, Row,
+ Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput,
+};
+
+pub fn main() {
+ env_logger::init();
+
+ Tour::run(Settings::default())
+}
+
+pub struct Tour {
+ steps: Steps,
+ scroll: scrollable::State,
+ back_button: button::State,
+ next_button: button::State,
+ debug: bool,
+}
+
+impl Sandbox for Tour {
+ type Message = Message;
+
+ fn new() -> Tour {
+ Tour {
+ steps: Steps::new(),
+ scroll: scrollable::State::new(),
+ back_button: button::State::new(),
+ next_button: button::State::new(),
+ debug: false,
+ }
+ }
+
+ 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(&mut self) -> Element<Message> {
+ let Tour {
+ steps,
+ scroll,
+ back_button,
+ next_button,
+ ..
+ } = self;
+
+ let mut controls = Row::new();
+
+ if steps.has_previous() {
+ controls = controls.push(
+ button(back_button, "Back")
+ .on_press(Message::BackPressed)
+ .style(style::Button::Secondary),
+ );
+ }
+
+ controls = controls.push(Space::with_width(Length::Fill));
+
+ if steps.can_continue() {
+ controls = controls.push(
+ button(next_button, "Next")
+ .on_press(Message::NextPressed)
+ .style(style::Button::Primary),
+ );
+ }
+
+ let content: Element<_> = Column::new()
+ .max_width(540)
+ .spacing(20)
+ .padding(20)
+ .push(steps.view(self.debug).map(Message::StepMessage))
+ .push(controls)
+ .into();
+
+ let content = if self.debug {
+ content.explain(Color::BLACK)
+ } else {
+ content
+ };
+
+ let scrollable = Scrollable::new(scroll)
+ .push(Container::new(content).width(Length::Fill).center_x());
+
+ Container::new(scrollable)
+ .height(Length::Fill)
+ .center_y()
+ .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 {
+ state: slider::State::new(),
+ value: 50,
+ },
+ Step::RowsAndColumns {
+ layout: Layout::Row,
+ spacing_slider: slider::State::new(),
+ spacing: 20,
+ },
+ Step::Text {
+ size_slider: slider::State::new(),
+ size: 30,
+ color_sliders: [slider::State::new(); 3],
+ color: Color::BLACK,
+ },
+ Step::Radio { selection: None },
+ Step::Image {
+ width: 300,
+ slider: slider::State::new(),
+ },
+ Step::Scrollable,
+ Step::TextInput {
+ value: String::new(),
+ is_secure: false,
+ state: text_input::State::new(),
+ },
+ Step::Debugger,
+ Step::End,
+ ],
+ current: 0,
+ }
+ }
+
+ fn update(&mut self, msg: StepMessage, debug: &mut bool) {
+ self.steps[self.current].update(msg, debug);
+ }
+
+ fn view(&mut 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()
+ }
+}
+
+enum Step {
+ Welcome,
+ Slider {
+ state: slider::State,
+ value: u16,
+ },
+ RowsAndColumns {
+ layout: Layout,
+ spacing_slider: slider::State,
+ spacing: u16,
+ },
+ Text {
+ size_slider: slider::State,
+ size: u16,
+ color_sliders: [slider::State; 3],
+ color: Color,
+ },
+ Radio {
+ selection: Option<Language>,
+ },
+ Image {
+ width: u16,
+ slider: slider::State,
+ },
+ Scrollable,
+ TextInput {
+ value: String,
+ is_secure: bool,
+ state: text_input::State,
+ },
+ Debugger,
+ End,
+}
+
+#[derive(Debug, Clone)]
+pub enum StepMessage {
+ SliderChanged(f32),
+ LayoutChanged(Layout),
+ SpacingChanged(f32),
+ TextSizeChanged(f32),
+ TextColorChanged(Color),
+ LanguageSelected(Language),
+ ImageWidthChanged(f32),
+ InputChanged(String),
+ ToggleSecureInput(bool),
+ DebugToggled(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;
+ }
+ }
+ StepMessage::LanguageSelected(language) => {
+ if let Step::Radio { selection } = self {
+ *selection = Some(language);
+ }
+ }
+ StepMessage::SliderChanged(new_value) => {
+ if let Step::Slider { value, .. } = self {
+ *value = new_value.round() as u16;
+ }
+ }
+ StepMessage::TextSizeChanged(new_size) => {
+ if let Step::Text { size, .. } = self {
+ *size = new_size.round() as u16;
+ }
+ }
+ StepMessage::TextColorChanged(new_color) => {
+ if let Step::Text { color, .. } = self {
+ *color = new_color;
+ }
+ }
+ StepMessage::LayoutChanged(new_layout) => {
+ if let Step::RowsAndColumns { layout, .. } = self {
+ *layout = new_layout;
+ }
+ }
+ StepMessage::SpacingChanged(new_spacing) => {
+ if let Step::RowsAndColumns { spacing, .. } = self {
+ *spacing = new_spacing.round() as u16;
+ }
+ }
+ StepMessage::ImageWidthChanged(new_width) => {
+ if let Step::Image { width, .. } = self {
+ *width = new_width.round() as u16;
+ }
+ }
+ StepMessage::InputChanged(new_value) => {
+ if let Step::TextInput { value, .. } = self {
+ *value = new_value;
+ }
+ }
+ StepMessage::ToggleSecureInput(toggle) => {
+ if let Step::TextInput { is_secure, .. } = self {
+ *is_secure = toggle;
+ }
+ }
+ };
+ }
+
+ fn title(&self) -> &str {
+ match self {
+ Step::Welcome => "Welcome",
+ Step::Radio { .. } => "Radio button",
+ 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 can_continue(&self) -> bool {
+ match self {
+ Step::Welcome => true,
+ Step::Radio { selection } => *selection == Some(Language::Rust),
+ Step::Slider { .. } => true,
+ Step::Text { .. } => true,
+ Step::Image { .. } => true,
+ Step::RowsAndColumns { .. } => true,
+ Step::Scrollable => true,
+ Step::TextInput { value, .. } => !value.is_empty(),
+ Step::Debugger => true,
+ Step::End => false,
+ }
+ }
+
+ fn view(&mut self, debug: bool) -> Element<StepMessage> {
+ match self {
+ Step::Welcome => Self::welcome(),
+ Step::Radio { selection } => Self::radio(*selection),
+ Step::Slider { state, value } => Self::slider(state, *value),
+ Step::Text {
+ size_slider,
+ size,
+ color_sliders,
+ color,
+ } => Self::text(size_slider, *size, color_sliders, *color),
+ Step::Image { width, slider } => Self::image(*width, slider),
+ Step::RowsAndColumns {
+ layout,
+ spacing_slider,
+ spacing,
+ } => Self::rows_and_columns(*layout, spacing_slider, *spacing),
+ Step::Scrollable => Self::scrollable(),
+ Step::TextInput {
+ value,
+ is_secure,
+ state,
+ } => Self::text_input(value, *is_secure, state),
+ Step::Debugger => Self::debugger(debug),
+ Step::End => Self::end(),
+ }
+ .into()
+ }
+
+ fn container(title: &str) -> Column<'a, StepMessage> {
+ Column::new().spacing(20).push(Text::new(title).size(50))
+ }
+
+ fn welcome() -> Column<'a, StepMessage> {
+ Self::container("Welcome!")
+ .push(Text::new(
+ "This is a simple tour meant to showcase a bunch of widgets \
+ that can be easily implemented on top of Iced.",
+ ))
+ .push(Text::new(
+ "Iced is a cross-platform GUI library for Rust focused on \
+ simplicity and type-safety. It is heavily inspired by Elm.",
+ ))
+ .push(Text::new(
+ "It was originally born as part of Coffee, an opinionated \
+ 2D game engine for Rust.",
+ ))
+ .push(Text::new(
+ "On native platforms, Iced provides by default a renderer \
+ built on top of wgpu, a graphics library supporting Vulkan, \
+ Metal, DX11, and DX12.",
+ ))
+ .push(Text::new(
+ "Additionally, this tour can also run on WebAssembly thanks \
+ to dodrio, an experimental VDOM library for Rust.",
+ ))
+ .push(Text::new(
+ "You will need to interact with the UI in order to reach the \
+ end!",
+ ))
+ }
+
+ fn slider(
+ state: &'a mut slider::State,
+ value: u16,
+ ) -> Column<'a, StepMessage> {
+ Self::container("Slider")
+ .push(Text::new(
+ "A slider allows you to smoothly select a value from a range \
+ of values.",
+ ))
+ .push(Text::new(
+ "The following slider lets you choose an integer from \
+ 0 to 100:",
+ ))
+ .push(Slider::new(
+ state,
+ 0.0..=100.0,
+ value as f32,
+ StepMessage::SliderChanged,
+ ))
+ .push(
+ Text::new(&value.to_string())
+ .width(Length::Fill)
+ .horizontal_alignment(HorizontalAlignment::Center),
+ )
+ }
+
+ fn rows_and_columns(
+ layout: Layout,
+ spacing_slider: &'a mut slider::State,
+ spacing: u16,
+ ) -> Column<'a, StepMessage> {
+ let row_radio = Radio::new(
+ Layout::Row,
+ "Row",
+ Some(layout),
+ StepMessage::LayoutChanged,
+ );
+
+ let column_radio = Radio::new(
+ Layout::Column,
+ "Column",
+ Some(layout),
+ StepMessage::LayoutChanged,
+ );
+
+ let layout_section: Element<_> = match layout {
+ Layout::Row => Row::new()
+ .spacing(spacing)
+ .push(row_radio)
+ .push(column_radio)
+ .into(),
+ Layout::Column => Column::new()
+ .spacing(spacing)
+ .push(row_radio)
+ .push(column_radio)
+ .into(),
+ };
+
+ let spacing_section = Column::new()
+ .spacing(10)
+ .push(Slider::new(
+ spacing_slider,
+ 0.0..=80.0,
+ spacing as f32,
+ StepMessage::SpacingChanged,
+ ))
+ .push(
+ Text::new(&format!("{} px", spacing))
+ .width(Length::Fill)
+ .horizontal_alignment(HorizontalAlignment::Center),
+ );
+
+ Self::container("Rows and columns")
+ .spacing(spacing)
+ .push(Text::new(
+ "Iced uses a layout model based on flexbox to position UI \
+ elements.",
+ ))
+ .push(Text::new(
+ "Rows and columns can be used to distribute content \
+ horizontally or vertically, respectively.",
+ ))
+ .push(layout_section)
+ .push(Text::new(
+ "You can also easily change the spacing between elements:",
+ ))
+ .push(spacing_section)
+ }
+
+ fn text(
+ size_slider: &'a mut slider::State,
+ size: u16,
+ color_sliders: &'a mut [slider::State; 3],
+ color: Color,
+ ) -> Column<'a, StepMessage> {
+ let size_section = Column::new()
+ .padding(20)
+ .spacing(20)
+ .push(Text::new("You can change its size:"))
+ .push(
+ Text::new(&format!("This text is {} pixels", size)).size(size),
+ )
+ .push(Slider::new(
+ size_slider,
+ 10.0..=70.0,
+ size as f32,
+ StepMessage::TextSizeChanged,
+ ));
+
+ let [red, green, blue] = color_sliders;
+ let color_section = Column::new()
+ .padding(20)
+ .spacing(20)
+ .push(Text::new("And its color:"))
+ .push(Text::new(&format!("{:?}", color)).color(color))
+ .push(
+ Row::new()
+ .spacing(10)
+ .push(Slider::new(red, 0.0..=1.0, color.r, move |r| {
+ StepMessage::TextColorChanged(Color { r, ..color })
+ }))
+ .push(Slider::new(green, 0.0..=1.0, color.g, move |g| {
+ StepMessage::TextColorChanged(Color { g, ..color })
+ }))
+ .push(Slider::new(blue, 0.0..=1.0, color.b, move |b| {
+ StepMessage::TextColorChanged(Color { b, ..color })
+ })),
+ );
+
+ Self::container("Text")
+ .push(Text::new(
+ "Text is probably the most essential widget for your UI. \
+ It will try to adapt to the dimensions of its container.",
+ ))
+ .push(size_section)
+ .push(color_section)
+ }
+
+ fn radio(selection: Option<Language>) -> Column<'a, StepMessage> {
+ let question = Column::new()
+ .padding(20)
+ .spacing(10)
+ .push(Text::new("Iced is written in...").size(24))
+ .push(Language::all().iter().cloned().fold(
+ Column::new().padding(10).spacing(20),
+ |choices, language| {
+ choices.push(Radio::new(
+ language,
+ language.into(),
+ selection,
+ StepMessage::LanguageSelected,
+ ))
+ },
+ ));
+
+ Self::container("Radio button")
+ .push(Text::new(
+ "A radio button is normally used to represent a choice... \
+ Surprise test!",
+ ))
+ .push(question)
+ .push(Text::new(
+ "Iced works very well with iterators! The list above is \
+ basically created by folding a column over the different \
+ choices, creating a radio button for each one of them!",
+ ))
+ }
+
+ fn image(
+ width: u16,
+ slider: &'a mut slider::State,
+ ) -> Column<'a, StepMessage> {
+ Self::container("Image")
+ .push(Text::new("An image that tries to keep its aspect ratio."))
+ .push(ferris(width))
+ .push(Slider::new(
+ slider,
+ 100.0..=500.0,
+ width as f32,
+ StepMessage::ImageWidthChanged,
+ ))
+ .push(
+ Text::new(&format!("Width: {} px", width.to_string()))
+ .width(Length::Fill)
+ .horizontal_alignment(HorizontalAlignment::Center),
+ )
+ }
+
+ fn scrollable() -> Column<'a, StepMessage> {
+ Self::container("Scrollable")
+ .push(Text::new(
+ "Iced supports scrollable content. Try it out! Find the \
+ button further below.",
+ ))
+ .push(
+ Text::new(
+ "Tip: You can use the scrollbar to scroll down faster!",
+ )
+ .size(16),
+ )
+ .push(Column::new().height(Length::Units(4096)))
+ .push(
+ Text::new("You are halfway there!")
+ .width(Length::Fill)
+ .size(30)
+ .horizontal_alignment(HorizontalAlignment::Center),
+ )
+ .push(Column::new().height(Length::Units(4096)))
+ .push(ferris(300))
+ .push(
+ Text::new("You made it!")
+ .width(Length::Fill)
+ .size(50)
+ .horizontal_alignment(HorizontalAlignment::Center),
+ )
+ }
+
+ fn text_input(
+ value: &str,
+ is_secure: bool,
+ state: &'a mut text_input::State,
+ ) -> Column<'a, StepMessage> {
+ let text_input = TextInput::new(
+ state,
+ "Type something to continue...",
+ value,
+ StepMessage::InputChanged,
+ )
+ .padding(10)
+ .size(30);
+ Self::container("Text input")
+ .push(Text::new(
+ "Use a text input to ask for different kinds of information.",
+ ))
+ .push(if is_secure {
+ text_input.password()
+ } else {
+ text_input
+ })
+ .push(Checkbox::new(
+ is_secure,
+ "Enable password mode",
+ StepMessage::ToggleSecureInput,
+ ))
+ .push(Text::new(
+ "A text input produces a message every time it changes. It is \
+ very easy to keep track of its contents:",
+ ))
+ .push(
+ Text::new(if value.is_empty() {
+ "You have not typed anything yet..."
+ } else {
+ value
+ })
+ .width(Length::Fill)
+ .horizontal_alignment(HorizontalAlignment::Center),
+ )
+ }
+
+ fn debugger(debug: bool) -> Column<'a, StepMessage> {
+ Self::container("Debugger")
+ .push(Text::new(
+ "You can ask Iced to visually explain the layouting of the \
+ different elements comprising your UI!",
+ ))
+ .push(Text::new(
+ "Give it a shot! Check the following checkbox to be able to \
+ see element boundaries.",
+ ))
+ .push(if cfg!(target_arch = "wasm32") {
+ Element::new(
+ Text::new("Not available on web yet!")
+ .color([0.7, 0.7, 0.7])
+ .horizontal_alignment(HorizontalAlignment::Center),
+ )
+ } else {
+ Element::new(Checkbox::new(
+ debug,
+ "Explain layout",
+ StepMessage::DebugToggled,
+ ))
+ })
+ .push(Text::new("Feel free to go back and take a look."))
+ }
+
+ fn end() -> Column<'a, StepMessage> {
+ Self::container("You reached the end!")
+ .push(Text::new(
+ "This tour will be updated as more features are added.",
+ ))
+ .push(Text::new("Make sure to keep an eye on it!"))
+ }
+}
+
+fn ferris<'a>(width: u16) -> Container<'a, StepMessage> {
+ Container::new(
+ // This should go away once we unify resource loading on native
+ // platforms
+ if cfg!(target_arch = "wasm32") {
+ Image::new("images/ferris.png")
+ } else {
+ Image::new(format!(
+ "{}/images/ferris.png",
+ env!("CARGO_MANIFEST_DIR")
+ ))
+ }
+ .width(Length::Units(width)),
+ )
+ .width(Length::Fill)
+ .center_x()
+}
+
+fn button<'a, Message>(
+ state: &'a mut button::State,
+ label: &str,
+) -> Button<'a, Message> {
+ Button::new(
+ state,
+ Text::new(label).horizontal_alignment(HorizontalAlignment::Center),
+ )
+ .padding(12)
+ .min_width(100)
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Language {
+ Rust,
+ Elm,
+ Ruby,
+ Haskell,
+ C,
+ Other,
+}
+
+impl Language {
+ fn all() -> [Language; 6] {
+ [
+ Language::C,
+ Language::Elm,
+ Language::Ruby,
+ Language::Haskell,
+ Language::Rust,
+ Language::Other,
+ ]
+ }
+}
+
+impl From<Language> for &str {
+ fn from(language: Language) -> &'static str {
+ match language {
+ Language::Rust => "Rust",
+ Language::Elm => "Elm",
+ Language::Ruby => "Ruby",
+ Language::Haskell => "Haskell",
+ Language::C => "C",
+ Language::Other => "Other",
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Layout {
+ Row,
+ Column,
+}
+
+mod style {
+ use iced::{button, Background, Color, Vector};
+
+ pub enum Button {
+ Primary,
+ Secondary,
+ }
+
+ impl button::StyleSheet for Button {
+ fn active(&self) -> button::Style {
+ button::Style {
+ background: Some(Background::Color(match self {
+ Button::Primary => Color::from_rgb(0.11, 0.42, 0.87),
+ Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5),
+ })),
+ border_radius: 12,
+ shadow_offset: Vector::new(1.0, 1.0),
+ text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE),
+ ..button::Style::default()
+ }
+ }
+
+ fn hovered(&self) -> button::Style {
+ button::Style {
+ text_color: Color::WHITE,
+ shadow_offset: Vector::new(1.0, 2.0),
+ ..self.active()
+ }
+ }
+ }
+}
+
+// This should be gracefully handled by Iced in the future. Probably using our
+// own proc macro, or maybe the whole process is streamlined by `wasm-pack` at
+// some point.
+#[cfg(target_arch = "wasm32")]
+mod wasm {
+ use wasm_bindgen::prelude::*;
+
+ #[wasm_bindgen(start)]
+ pub fn run() {
+ super::main()
+ }
+}