summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/pure/component/Cargo.toml11
-rw-r--r--examples/pure/component/src/main.rs168
-rw-r--r--examples/pure/counter/Cargo.toml9
-rw-r--r--examples/pure/counter/README.md18
-rw-r--r--examples/pure/counter/index.html12
-rw-r--r--examples/pure/counter/src/main.rs50
-rw-r--r--examples/pure/pick_list/Cargo.toml9
-rw-r--r--examples/pure/pick_list/README.md18
-rw-r--r--examples/pure/pick_list/src/main.rs111
-rw-r--r--examples/pure/todos/Cargo.toml19
-rw-r--r--examples/pure/todos/README.md20
-rw-r--r--examples/pure/todos/index.html12
-rw-r--r--examples/pure/todos/src/main.rs607
-rw-r--r--examples/pure/tour/Cargo.toml10
-rw-r--r--examples/pure/tour/README.md28
-rw-r--r--examples/pure/tour/index.html12
-rw-r--r--examples/pure/tour/src/main.rs702
-rw-r--r--examples/stopwatch/src/main.rs2
-rw-r--r--examples/tour/src/main.rs2
19 files changed, 1818 insertions, 2 deletions
diff --git a/examples/pure/component/Cargo.toml b/examples/pure/component/Cargo.toml
new file mode 100644
index 00000000..a15f134f
--- /dev/null
+++ b/examples/pure/component/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "pure_component"
+version = "0.1.0"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced = { path = "../../..", features = ["debug", "pure"] }
+iced_native = { path = "../../../native" }
+iced_lazy = { path = "../../../lazy", features = ["pure"] }
diff --git a/examples/pure/component/src/main.rs b/examples/pure/component/src/main.rs
new file mode 100644
index 00000000..0de7bdd9
--- /dev/null
+++ b/examples/pure/component/src/main.rs
@@ -0,0 +1,168 @@
+use iced::pure::widget::container;
+use iced::pure::{Element, Sandbox};
+use iced::{Length, Settings};
+
+use numeric_input::numeric_input;
+
+pub fn main() -> iced::Result {
+ Component::run(Settings::default())
+}
+
+#[derive(Default)]
+struct Component {
+ value: Option<u32>,
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Message {
+ NumericInputChanged(Option<u32>),
+}
+
+impl Sandbox for Component {
+ type Message = Message;
+
+ fn new() -> Self {
+ Self::default()
+ }
+
+ fn title(&self) -> String {
+ String::from("Component - Iced")
+ }
+
+ fn update(&mut self, message: Message) {
+ match message {
+ Message::NumericInputChanged(value) => {
+ self.value = value;
+ }
+ }
+ }
+
+ fn view(&self) -> Element<Message> {
+ container(numeric_input(self.value, Message::NumericInputChanged))
+ .padding(20)
+ .height(Length::Fill)
+ .center_y()
+ .into()
+ }
+}
+
+mod numeric_input {
+ use iced::pure::widget::Element;
+ use iced::pure::widget::{row, text, text_input};
+ use iced_lazy::pure::component::{self, Component};
+ use iced_native::alignment::{self, Alignment};
+ use iced_native::text;
+ use iced_native::Length;
+
+ pub struct NumericInput<Message> {
+ value: Option<u32>,
+ on_change: Box<dyn Fn(Option<u32>) -> Message>,
+ }
+
+ pub fn numeric_input<Message>(
+ value: Option<u32>,
+ on_change: impl Fn(Option<u32>) -> Message + 'static,
+ ) -> NumericInput<Message> {
+ NumericInput::new(value, on_change)
+ }
+
+ #[derive(Debug, Clone)]
+ pub enum Event {
+ InputChanged(String),
+ IncrementPressed,
+ DecrementPressed,
+ }
+
+ impl<Message> NumericInput<Message> {
+ pub fn new(
+ value: Option<u32>,
+ on_change: impl Fn(Option<u32>) -> Message + 'static,
+ ) -> Self {
+ Self {
+ value,
+ on_change: Box::new(on_change),
+ }
+ }
+ }
+
+ impl<Message, Renderer> Component<Message, Renderer> for NumericInput<Message>
+ where
+ Renderer: text::Renderer + 'static,
+ {
+ type State = ();
+ type Event = Event;
+
+ fn update(
+ &mut self,
+ _state: &mut Self::State,
+ event: Event,
+ ) -> Option<Message> {
+ match event {
+ Event::IncrementPressed => Some((self.on_change)(Some(
+ self.value.unwrap_or_default().saturating_add(1),
+ ))),
+ Event::DecrementPressed => Some((self.on_change)(Some(
+ self.value.unwrap_or_default().saturating_sub(1),
+ ))),
+ Event::InputChanged(value) => {
+ if value.is_empty() {
+ Some((self.on_change)(None))
+ } else {
+ value
+ .parse()
+ .ok()
+ .map(Some)
+ .map(self.on_change.as_ref())
+ }
+ }
+ }
+ }
+
+ fn view(&self, _state: &Self::State) -> Element<Event, Renderer> {
+ let button = |label, on_press| {
+ use iced::pure::widget::button;
+
+ button(
+ text(label)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .horizontal_alignment(alignment::Horizontal::Center)
+ .vertical_alignment(alignment::Vertical::Center),
+ )
+ .width(Length::Units(50))
+ .on_press(on_press)
+ };
+
+ row()
+ .push(button("-", Event::DecrementPressed))
+ .push(
+ text_input(
+ "Type a number",
+ self.value
+ .as_ref()
+ .map(u32::to_string)
+ .as_ref()
+ .map(String::as_str)
+ .unwrap_or(""),
+ Event::InputChanged,
+ )
+ .padding(10),
+ )
+ .push(button("+", Event::IncrementPressed))
+ .align_items(Alignment::Fill)
+ .spacing(10)
+ .into()
+ }
+ }
+
+ impl<'a, Message, Renderer> From<NumericInput<Message>>
+ for Element<'a, Message, Renderer>
+ where
+ Message: 'a,
+ Renderer: 'static + text::Renderer,
+ {
+ fn from(numeric_input: NumericInput<Message>) -> Self {
+ component::view(numeric_input)
+ }
+ }
+}
diff --git a/examples/pure/counter/Cargo.toml b/examples/pure/counter/Cargo.toml
new file mode 100644
index 00000000..2fcd22d4
--- /dev/null
+++ b/examples/pure/counter/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "pure_counter"
+version = "0.1.0"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced = { path = "../../..", features = ["pure"] }
diff --git a/examples/pure/counter/README.md b/examples/pure/counter/README.md
new file mode 100644
index 00000000..4d9fc5b9
--- /dev/null
+++ b/examples/pure/counter/README.md
@@ -0,0 +1,18 @@
+## Counter
+
+The classic counter example explained in the [`README`](../../README.md).
+
+The __[`main`]__ file contains all the code of the example.
+
+<div align="center">
+ <a href="https://gfycat.com/fairdeadcatbird">
+ <img src="https://thumbs.gfycat.com/FairDeadCatbird-small.gif">
+ </a>
+</div>
+
+You can run it with `cargo run`:
+```
+cargo run --package counter
+```
+
+[`main`]: src/main.rs
diff --git a/examples/pure/counter/index.html b/examples/pure/counter/index.html
new file mode 100644
index 00000000..d2e368e4
--- /dev/null
+++ b/examples/pure/counter/index.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8" content="text/html; charset=utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <title>Counter - Iced</title>
+ <base data-trunk-public-url />
+</head>
+<body>
+<link data-trunk rel="rust" href="Cargo.toml" data-wasm-opt="z" data-bin="counter" />
+</body>
+</html>
diff --git a/examples/pure/counter/src/main.rs b/examples/pure/counter/src/main.rs
new file mode 100644
index 00000000..9b520347
--- /dev/null
+++ b/examples/pure/counter/src/main.rs
@@ -0,0 +1,50 @@
+use iced::pure::widget::{button, column, text};
+use iced::pure::{Element, Sandbox};
+use iced::{Alignment, Settings};
+
+pub fn main() -> iced::Result {
+ Counter::run(Settings::default())
+}
+
+struct Counter {
+ value: i32,
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Message {
+ IncrementPressed,
+ DecrementPressed,
+}
+
+impl Sandbox for Counter {
+ type Message = Message;
+
+ fn new() -> Self {
+ Self { value: 0 }
+ }
+
+ fn title(&self) -> String {
+ String::from("Counter - Iced")
+ }
+
+ fn update(&mut self, message: Message) {
+ match message {
+ Message::IncrementPressed => {
+ self.value += 1;
+ }
+ Message::DecrementPressed => {
+ self.value -= 1;
+ }
+ }
+ }
+
+ fn view(&self) -> Element<Message> {
+ column()
+ .padding(20)
+ .align_items(Alignment::Center)
+ .push(button("Increment").on_press(Message::IncrementPressed))
+ .push(text(self.value.to_string()).size(50))
+ .push(button("Decrement").on_press(Message::DecrementPressed))
+ .into()
+ }
+}
diff --git a/examples/pure/pick_list/Cargo.toml b/examples/pure/pick_list/Cargo.toml
new file mode 100644
index 00000000..c0fcac3c
--- /dev/null
+++ b/examples/pure/pick_list/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "pure_pick_list"
+version = "0.1.0"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced = { path = "../../..", features = ["debug", "pure"] }
diff --git a/examples/pure/pick_list/README.md b/examples/pure/pick_list/README.md
new file mode 100644
index 00000000..6dc80bf4
--- /dev/null
+++ b/examples/pure/pick_list/README.md
@@ -0,0 +1,18 @@
+## Pick-list
+
+A dropdown list of selectable options.
+
+It displays and positions an overlay based on the window position of the widget.
+
+The __[`main`]__ file contains all the code of the example.
+
+<div align="center">
+ <img src="https://user-images.githubusercontent.com/518289/87125075-2c232e80-c28a-11ea-95c2-769c610b8843.gif">
+</div>
+
+You can run it with `cargo run`:
+```
+cargo run --package pick_list
+```
+
+[`main`]: src/main.rs
diff --git a/examples/pure/pick_list/src/main.rs b/examples/pure/pick_list/src/main.rs
new file mode 100644
index 00000000..f9d55dd0
--- /dev/null
+++ b/examples/pure/pick_list/src/main.rs
@@ -0,0 +1,111 @@
+use iced::pure::widget::{
+ column, container, pick_list, scrollable, vertical_space,
+};
+use iced::pure::{Element, Sandbox};
+use iced::{Alignment, Length, Settings};
+
+pub fn main() -> iced::Result {
+ Example::run(Settings::default())
+}
+
+#[derive(Default)]
+struct Example {
+ selected_language: Option<Language>,
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Message {
+ LanguageSelected(Language),
+}
+
+impl Sandbox for Example {
+ type Message = Message;
+
+ fn new() -> Self {
+ Self::default()
+ }
+
+ fn title(&self) -> String {
+ String::from("Pick list - Iced")
+ }
+
+ fn update(&mut self, message: Message) {
+ match message {
+ Message::LanguageSelected(language) => {
+ self.selected_language = Some(language);
+ }
+ }
+ }
+
+ fn view(&self) -> Element<Message> {
+ let pick_list = pick_list(
+ &Language::ALL[..],
+ self.selected_language,
+ Message::LanguageSelected,
+ )
+ .placeholder("Choose a language...");
+
+ let content = column()
+ .width(Length::Fill)
+ .align_items(Alignment::Center)
+ .spacing(10)
+ .push(vertical_space(Length::Units(600)))
+ .push("Which is your favorite language?")
+ .push(pick_list)
+ .push(vertical_space(Length::Units(600)));
+
+ container(scrollable(content))
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .center_x()
+ .center_y()
+ .into()
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Language {
+ Rust,
+ Elm,
+ Ruby,
+ Haskell,
+ C,
+ Javascript,
+ Other,
+}
+
+impl Language {
+ const ALL: [Language; 7] = [
+ Language::C,
+ Language::Elm,
+ Language::Ruby,
+ Language::Haskell,
+ Language::Rust,
+ Language::Javascript,
+ Language::Other,
+ ];
+}
+
+impl Default for Language {
+ fn default() -> Language {
+ Language::Rust
+ }
+}
+
+impl std::fmt::Display for Language {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ Language::Rust => "Rust",
+ Language::Elm => "Elm",
+ Language::Ruby => "Ruby",
+ Language::Haskell => "Haskell",
+ Language::C => "C",
+ Language::Javascript => "Javascript",
+ Language::Other => "Some other language",
+ }
+ )
+ }
+}
diff --git a/examples/pure/todos/Cargo.toml b/examples/pure/todos/Cargo.toml
new file mode 100644
index 00000000..217179e8
--- /dev/null
+++ b/examples/pure/todos/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "pure_todos"
+version = "0.1.0"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced = { path = "../../..", features = ["async-std", "debug", "default_system_font", "pure"] }
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+
+[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
+async-std = "1.0"
+directories-next = "2.0"
+
+[target.'cfg(target_arch = "wasm32")'.dependencies]
+web-sys = { version = "0.3", features = ["Window", "Storage"] }
+wasm-timer = "0.2"
diff --git a/examples/pure/todos/README.md b/examples/pure/todos/README.md
new file mode 100644
index 00000000..9c2598b9
--- /dev/null
+++ b/examples/pure/todos/README.md
@@ -0,0 +1,20 @@
+## Todos
+
+A todos tracker inspired by [TodoMVC]. It showcases dynamic layout, text input, checkboxes, scrollables, icons, and async actions! It automatically saves your tasks in the background, even if you did not finish typing them.
+
+All the example code is located in the __[`main`]__ file.
+
+<div align="center">
+ <a href="https://gfycat.com/littlesanehalicore">
+ <img src="https://thumbs.gfycat.com/LittleSaneHalicore-small.gif" height="400px">
+ </a>
+</div>
+
+You can run the native version with `cargo run`:
+```
+cargo run --package todos
+```
+We have not yet implemented a `LocalStorage` version of the auto-save feature. Therefore, it does not work on web _yet_!
+
+[`main`]: src/main.rs
+[TodoMVC]: http://todomvc.com/
diff --git a/examples/pure/todos/index.html b/examples/pure/todos/index.html
new file mode 100644
index 00000000..ee5570fb
--- /dev/null
+++ b/examples/pure/todos/index.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8" content="text/html; charset=utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <title>Todos - Iced</title>
+ <base data-trunk-public-url />
+</head>
+<body>
+<link data-trunk rel="rust" href="Cargo.toml" data-wasm-opt="z" data-bin="todos" />
+</body>
+</html>
diff --git a/examples/pure/todos/src/main.rs b/examples/pure/todos/src/main.rs
new file mode 100644
index 00000000..e993c598
--- /dev/null
+++ b/examples/pure/todos/src/main.rs
@@ -0,0 +1,607 @@
+use iced::alignment::{self, Alignment};
+use iced::pure::widget::{
+ button, checkbox, column, container, row, scrollable, text, text_input,
+};
+use iced::pure::{Application, Element, Text};
+use iced::window;
+use iced::{Command, Font, Length, Settings};
+use serde::{Deserialize, Serialize};
+
+pub fn main() -> iced::Result {
+ Todos::run(Settings {
+ window: window::Settings {
+ size: (500, 800),
+ ..window::Settings::default()
+ },
+ ..Settings::default()
+ })
+}
+
+#[derive(Debug)]
+enum Todos {
+ Loading,
+ Loaded(State),
+}
+
+#[derive(Debug, Default)]
+struct State {
+ input_value: String,
+ filter: Filter,
+ tasks: Vec<Task>,
+ dirty: bool,
+ saving: bool,
+}
+
+#[derive(Debug, Clone)]
+enum Message {
+ Loaded(Result<SavedState, LoadError>),
+ Saved(Result<(), SaveError>),
+ InputChanged(String),
+ CreateTask,
+ FilterChanged(Filter),
+ TaskMessage(usize, TaskMessage),
+}
+
+impl Application for Todos {
+ type Executor = iced::executor::Default;
+ type Message = Message;
+ type Flags = ();
+
+ fn new(_flags: ()) -> (Todos, Command<Message>) {
+ (
+ Todos::Loading,
+ Command::perform(SavedState::load(), Message::Loaded),
+ )
+ }
+
+ fn title(&self) -> String {
+ let dirty = match self {
+ Todos::Loading => false,
+ Todos::Loaded(state) => state.dirty,
+ };
+
+ format!("Todos{} - Iced", if dirty { "*" } else { "" })
+ }
+
+ fn update(&mut self, message: Message) -> Command<Message> {
+ match self {
+ Todos::Loading => {
+ match message {
+ Message::Loaded(Ok(state)) => {
+ *self = Todos::Loaded(State {
+ input_value: state.input_value,
+ filter: state.filter,
+ tasks: state.tasks,
+ ..State::default()
+ });
+ }
+ Message::Loaded(Err(_)) => {
+ *self = Todos::Loaded(State::default());
+ }
+ _ => {}
+ }
+
+ Command::none()
+ }
+ Todos::Loaded(state) => {
+ let mut saved = false;
+
+ match message {
+ Message::InputChanged(value) => {
+ state.input_value = value;
+ }
+ Message::CreateTask => {
+ if !state.input_value.is_empty() {
+ state
+ .tasks
+ .push(Task::new(state.input_value.clone()));
+ state.input_value.clear();
+ }
+ }
+ Message::FilterChanged(filter) => {
+ state.filter = filter;
+ }
+ Message::TaskMessage(i, TaskMessage::Delete) => {
+ state.tasks.remove(i);
+ }
+ Message::TaskMessage(i, task_message) => {
+ if let Some(task) = state.tasks.get_mut(i) {
+ task.update(task_message);
+ }
+ }
+ Message::Saved(_) => {
+ state.saving = false;
+ saved = true;
+ }
+ _ => {}
+ }
+
+ if !saved {
+ state.dirty = true;
+ }
+
+ if state.dirty && !state.saving {
+ state.dirty = false;
+ state.saving = true;
+
+ Command::perform(
+ SavedState {
+ input_value: state.input_value.clone(),
+ filter: state.filter,
+ tasks: state.tasks.clone(),
+ }
+ .save(),
+ Message::Saved,
+ )
+ } else {
+ Command::none()
+ }
+ }
+ }
+ }
+
+ fn view(&self) -> Element<Message> {
+ match self {
+ Todos::Loading => loading_message(),
+ Todos::Loaded(State {
+ input_value,
+ filter,
+ tasks,
+ ..
+ }) => {
+ let title = text("todos")
+ .width(Length::Fill)
+ .size(100)
+ .color([0.5, 0.5, 0.5])
+ .horizontal_alignment(alignment::Horizontal::Center);
+
+ let input = text_input(
+ "What needs to be done?",
+ input_value,
+ Message::InputChanged,
+ )
+ .padding(15)
+ .size(30)
+ .on_submit(Message::CreateTask);
+
+ let controls = view_controls(&tasks, *filter);
+ let filtered_tasks =
+ tasks.iter().filter(|task| filter.matches(task));
+
+ let tasks: Element<_> = if filtered_tasks.count() > 0 {
+ tasks
+ .iter()
+ .enumerate()
+ .filter(|(_, task)| filter.matches(task))
+ .fold(column().spacing(20), |column, (i, task)| {
+ column.push(task.view().map(move |message| {
+ Message::TaskMessage(i, message)
+ }))
+ })
+ .into()
+ } else {
+ empty_message(match filter {
+ Filter::All => "You have not created a task yet...",
+ Filter::Active => "All your tasks are done! :D",
+ Filter::Completed => {
+ "You have not completed a task yet..."
+ }
+ })
+ };
+
+ let content = column()
+ .spacing(20)
+ .max_width(800)
+ .push(title)
+ .push(input)
+ .push(controls)
+ .push(tasks);
+
+ scrollable(
+ container(content)
+ .width(Length::Fill)
+ .padding(40)
+ .center_x(),
+ )
+ .into()
+ }
+ }
+ }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+struct Task {
+ description: String,
+ completed: bool,
+
+ #[serde(skip)]
+ state: TaskState,
+}
+
+#[derive(Debug, Clone)]
+pub enum TaskState {
+ Idle,
+ Editing,
+}
+
+impl Default for TaskState {
+ fn default() -> Self {
+ Self::Idle
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum TaskMessage {
+ Completed(bool),
+ Edit,
+ DescriptionEdited(String),
+ FinishEdition,
+ Delete,
+}
+
+impl Task {
+ fn new(description: String) -> Self {
+ Task {
+ description,
+ completed: false,
+ state: TaskState::Idle,
+ }
+ }
+
+ fn update(&mut self, message: TaskMessage) {
+ match message {
+ TaskMessage::Completed(completed) => {
+ self.completed = completed;
+ }
+ TaskMessage::Edit => {
+ self.state = TaskState::Editing;
+ }
+ TaskMessage::DescriptionEdited(new_description) => {
+ self.description = new_description;
+ }
+ TaskMessage::FinishEdition => {
+ if !self.description.is_empty() {
+ self.state = TaskState::Idle;
+ }
+ }
+ TaskMessage::Delete => {}
+ }
+ }
+
+ fn view(&self) -> Element<TaskMessage> {
+ match &self.state {
+ TaskState::Idle => {
+ let checkbox = checkbox(
+ &self.description,
+ self.completed,
+ TaskMessage::Completed,
+ )
+ .width(Length::Fill);
+
+ row()
+ .spacing(20)
+ .align_items(Alignment::Center)
+ .push(checkbox)
+ .push(
+ button(edit_icon())
+ .on_press(TaskMessage::Edit)
+ .padding(10)
+ .style(style::Button::Icon),
+ )
+ .into()
+ }
+ TaskState::Editing => {
+ let text_input = text_input(
+ "Describe your task...",
+ &self.description,
+ TaskMessage::DescriptionEdited,
+ )
+ .on_submit(TaskMessage::FinishEdition)
+ .padding(10);
+
+ row()
+ .spacing(20)
+ .align_items(Alignment::Center)
+ .push(text_input)
+ .push(
+ button(
+ row()
+ .spacing(10)
+ .push(delete_icon())
+ .push("Delete"),
+ )
+ .on_press(TaskMessage::Delete)
+ .padding(10)
+ .style(style::Button::Destructive),
+ )
+ .into()
+ }
+ }
+ }
+}
+
+fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> {
+ let tasks_left = tasks.iter().filter(|task| !task.completed).count();
+
+ let filter_button = |label, filter, current_filter| {
+ let label = text(label).size(16);
+
+ let button = button(label).style(if filter == current_filter {
+ style::Button::FilterSelected
+ } else {
+ style::Button::FilterActive
+ });
+
+ button.on_press(Message::FilterChanged(filter)).padding(8)
+ };
+
+ row()
+ .spacing(20)
+ .align_items(Alignment::Center)
+ .push(
+ text(format!(
+ "{} {} left",
+ tasks_left,
+ if tasks_left == 1 { "task" } else { "tasks" }
+ ))
+ .width(Length::Fill)
+ .size(16),
+ )
+ .push(
+ row()
+ .width(Length::Shrink)
+ .spacing(10)
+ .push(filter_button("All", Filter::All, current_filter))
+ .push(filter_button("Active", Filter::Active, current_filter))
+ .push(filter_button(
+ "Completed",
+ Filter::Completed,
+ current_filter,
+ )),
+ )
+ .into()
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
+pub enum Filter {
+ All,
+ Active,
+ Completed,
+}
+
+impl Default for Filter {
+ fn default() -> Self {
+ Filter::All
+ }
+}
+
+impl Filter {
+ fn matches(&self, task: &Task) -> bool {
+ match self {
+ Filter::All => true,
+ Filter::Active => !task.completed,
+ Filter::Completed => task.completed,
+ }
+ }
+}
+
+fn loading_message<'a>() -> Element<'a, Message> {
+ container(
+ text("Loading...")
+ .horizontal_alignment(alignment::Horizontal::Center)
+ .size(50),
+ )
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .center_y()
+ .into()
+}
+
+fn empty_message(message: &str) -> Element<'_, Message> {
+ container(
+ text(message)
+ .width(Length::Fill)
+ .size(25)
+ .horizontal_alignment(alignment::Horizontal::Center)
+ .color([0.7, 0.7, 0.7]),
+ )
+ .width(Length::Fill)
+ .height(Length::Units(200))
+ .center_y()
+ .into()
+}
+
+// Fonts
+const ICONS: Font = Font::External {
+ name: "Icons",
+ bytes: include_bytes!("../../../todos/fonts/icons.ttf"),
+};
+
+fn icon(unicode: char) -> Text {
+ Text::new(unicode.to_string())
+ .font(ICONS)
+ .width(Length::Units(20))
+ .horizontal_alignment(alignment::Horizontal::Center)
+ .size(20)
+}
+
+fn edit_icon() -> Text {
+ icon('\u{F303}')
+}
+
+fn delete_icon() -> Text {
+ icon('\u{F1F8}')
+}
+
+// Persistence
+#[derive(Debug, Clone, Serialize, Deserialize)]
+struct SavedState {
+ input_value: String,
+ filter: Filter,
+ tasks: Vec<Task>,
+}
+
+#[derive(Debug, Clone)]
+enum LoadError {
+ FileError,
+ FormatError,
+}
+
+#[derive(Debug, Clone)]
+enum SaveError {
+ FileError,
+ WriteError,
+ FormatError,
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+impl SavedState {
+ fn path() -> std::path::PathBuf {
+ let mut path = if let Some(project_dirs) =
+ directories_next::ProjectDirs::from("rs", "Iced", "Todos")
+ {
+ project_dirs.data_dir().into()
+ } else {
+ std::env::current_dir().unwrap_or(std::path::PathBuf::new())
+ };
+
+ path.push("todos.json");
+
+ path
+ }
+
+ async fn load() -> Result<SavedState, LoadError> {
+ use async_std::prelude::*;
+
+ let mut contents = String::new();
+
+ let mut file = async_std::fs::File::open(Self::path())
+ .await
+ .map_err(|_| LoadError::FileError)?;
+
+ file.read_to_string(&mut contents)
+ .await
+ .map_err(|_| LoadError::FileError)?;
+
+ serde_json::from_str(&contents).map_err(|_| LoadError::FormatError)
+ }
+
+ async fn save(self) -> Result<(), SaveError> {
+ use async_std::prelude::*;
+
+ let json = serde_json::to_string_pretty(&self)
+ .map_err(|_| SaveError::FormatError)?;
+
+ let path = Self::path();
+
+ if let Some(dir) = path.parent() {
+ async_std::fs::create_dir_all(dir)
+ .await
+ .map_err(|_| SaveError::FileError)?;
+ }
+
+ {
+ let mut file = async_std::fs::File::create(path)
+ .await
+ .map_err(|_| SaveError::FileError)?;
+
+ file.write_all(json.as_bytes())
+ .await
+ .map_err(|_| SaveError::WriteError)?;
+ }
+
+ // This is a simple way to save at most once every couple seconds
+ async_std::task::sleep(std::time::Duration::from_secs(2)).await;
+
+ Ok(())
+ }
+}
+
+#[cfg(target_arch = "wasm32")]
+impl SavedState {
+ fn storage() -> Option<web_sys::Storage> {
+ let window = web_sys::window()?;
+
+ window.local_storage().ok()?
+ }
+
+ async fn load() -> Result<SavedState, LoadError> {
+ let storage = Self::storage().ok_or(LoadError::FileError)?;
+
+ let contents = storage
+ .get_item("state")
+ .map_err(|_| LoadError::FileError)?
+ .ok_or(LoadError::FileError)?;
+
+ serde_json::from_str(&contents).map_err(|_| LoadError::FormatError)
+ }
+
+ async fn save(self) -> Result<(), SaveError> {
+ let storage = Self::storage().ok_or(SaveError::FileError)?;
+
+ let json = serde_json::to_string_pretty(&self)
+ .map_err(|_| SaveError::FormatError)?;
+
+ storage
+ .set_item("state", &json)
+ .map_err(|_| SaveError::WriteError)?;
+
+ let _ = wasm_timer::Delay::new(std::time::Duration::from_secs(2)).await;
+
+ Ok(())
+ }
+}
+
+mod style {
+ use iced::{button, Background, Color, Vector};
+
+ pub enum Button {
+ FilterActive,
+ FilterSelected,
+ Icon,
+ Destructive,
+ }
+
+ impl button::StyleSheet for Button {
+ fn active(&self) -> button::Style {
+ match self {
+ Button::FilterActive => button::Style::default(),
+ Button::FilterSelected => button::Style {
+ background: Some(Background::Color(Color::from_rgb(
+ 0.2, 0.2, 0.7,
+ ))),
+ border_radius: 10.0,
+ text_color: Color::WHITE,
+ ..button::Style::default()
+ },
+ Button::Icon => button::Style {
+ text_color: Color::from_rgb(0.5, 0.5, 0.5),
+ ..button::Style::default()
+ },
+ Button::Destructive => button::Style {
+ background: Some(Background::Color(Color::from_rgb(
+ 0.8, 0.2, 0.2,
+ ))),
+ border_radius: 5.0,
+ text_color: Color::WHITE,
+ shadow_offset: Vector::new(1.0, 1.0),
+ ..button::Style::default()
+ },
+ }
+ }
+
+ fn hovered(&self) -> button::Style {
+ let active = self.active();
+
+ button::Style {
+ text_color: match self {
+ Button::Icon => Color::from_rgb(0.2, 0.2, 0.7),
+ Button::FilterActive => Color::from_rgb(0.2, 0.2, 0.7),
+ _ => active.text_color,
+ },
+ shadow_offset: active.shadow_offset + Vector::new(0.0, 1.0),
+ ..active
+ }
+ }
+ }
+}
diff --git a/examples/pure/tour/Cargo.toml b/examples/pure/tour/Cargo.toml
new file mode 100644
index 00000000..8ce5f198
--- /dev/null
+++ b/examples/pure/tour/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "pure_tour"
+version = "0.1.0"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced = { path = "../../..", features = ["image", "debug", "pure"] }
+env_logger = "0.8"
diff --git a/examples/pure/tour/README.md b/examples/pure/tour/README.md
new file mode 100644
index 00000000..e7cd2d5c
--- /dev/null
+++ b/examples/pure/tour/README.md
@@ -0,0 +1,28 @@
+## Tour
+
+A simple UI tour that can run both on native platforms and the web! It showcases different widgets that can be built using Iced.
+
+The __[`main`]__ file contains all the code of the example! All the cross-platform GUI is defined in terms of __state__, __messages__, __update logic__ and __view logic__.
+
+<div align="center">
+ <a href="https://gfycat.com/politeadorableiberianmole">
+ <img src="https://thumbs.gfycat.com/PoliteAdorableIberianmole-small.gif">
+ </a>
+</div>
+
+[`main`]: src/main.rs
+[`iced_winit`]: ../../winit
+[`iced_native`]: ../../native
+[`iced_wgpu`]: ../../wgpu
+[`iced_web`]: https://github.com/iced-rs/iced_web
+[`winit`]: https://github.com/rust-windowing/winit
+[`wgpu`]: https://github.com/gfx-rs/wgpu-rs
+
+You can run the native version with `cargo run`:
+```
+cargo run --package tour
+```
+
+The web version can be run by following [the usage instructions of `iced_web`] or by accessing [iced.rs](https://iced.rs/)!
+
+[the usage instructions of `iced_web`]: https://github.com/iced-rs/iced_web#usage
diff --git a/examples/pure/tour/index.html b/examples/pure/tour/index.html
new file mode 100644
index 00000000..c64af912
--- /dev/null
+++ b/examples/pure/tour/index.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8" content="text/html; charset=utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <title>Tour - Iced</title>
+ <base data-trunk-public-url />
+</head>
+<body>
+<link data-trunk rel="rust" href="Cargo.toml" data-wasm-opt="z" data-bin="tour" />
+</body>
+</html>
diff --git a/examples/pure/tour/src/main.rs b/examples/pure/tour/src/main.rs
new file mode 100644
index 00000000..7a50bcdc
--- /dev/null
+++ b/examples/pure/tour/src/main.rs
@@ -0,0 +1,702 @@
+use iced::alignment;
+use iced::pure::widget::{
+ checkbox, column, container, horizontal_space, image, radio, row,
+ scrollable, slider, text, text_input, toggler, vertical_space,
+};
+use iced::pure::{Button, Column, Container, Element, Sandbox, Slider};
+use iced::{Color, Length, Settings};
+
+pub fn main() -> iced::Result {
+ env_logger::init();
+
+ Tour::run(Settings::default())
+}
+
+pub struct Tour {
+ steps: Steps,
+ debug: bool,
+}
+
+impl Sandbox for Tour {
+ type Message = Message;
+
+ fn new() -> Tour {
+ Tour {
+ steps: Steps::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(&self) -> Element<Message> {
+ let Tour { steps, .. } = self;
+
+ let mut controls = row();
+
+ if steps.has_previous() {
+ controls = controls.push(
+ button("Back")
+ .on_press(Message::BackPressed)
+ .style(style::Button::Secondary),
+ );
+ }
+
+ controls = controls.push(horizontal_space(Length::Fill));
+
+ if steps.can_continue() {
+ controls = controls.push(
+ button("Next")
+ .on_press(Message::NextPressed)
+ .style(style::Button::Primary),
+ );
+ }
+
+ let content: Element<_> = column()
+ .max_width(540)
+ .spacing(20)
+ .padding(20)
+ .push(steps.view(self.debug).map(Message::StepMessage))
+ .push(controls)
+ .into();
+
+ let content = if self.debug {
+ // TODO
+ //content.explain(Color::BLACK)
+ content
+ } else {
+ content
+ };
+
+ let scrollable =
+ scrollable(container(content).width(Length::Fill).center_x());
+
+ container(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 { 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 },
+ Step::Scrollable,
+ Step::TextInput {
+ value: String::new(),
+ is_secure: 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()
+ }
+}
+
+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 },
+ Scrollable,
+ TextInput { value: String, is_secure: bool },
+ Debugger,
+ End,
+}
+
+#[derive(Debug, Clone)]
+pub enum StepMessage {
+ SliderChanged(u8),
+ LayoutChanged(Layout),
+ SpacingChanged(u16),
+ TextSizeChanged(u16),
+ TextColorChanged(Color),
+ LanguageSelected(Language),
+ ImageWidthChanged(u16),
+ InputChanged(String),
+ ToggleSecureInput(bool),
+ DebugToggled(bool),
+ 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;
+ }
+ }
+ 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;
+ }
+ }
+ StepMessage::TextSizeChanged(new_size) => {
+ if let Step::Text { size, .. } = self {
+ *size = new_size;
+ }
+ }
+ 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;
+ }
+ }
+ StepMessage::ImageWidthChanged(new_width) => {
+ if let Step::Image { width, .. } = self {
+ *width = new_width;
+ }
+ }
+ 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;
+ }
+ }
+ StepMessage::TogglerChanged(value) => {
+ if let Step::Toggler { can_continue, .. } = self {
+ *can_continue = value;
+ }
+ }
+ };
+ }
+
+ 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 can_continue(&self) -> bool {
+ match self {
+ 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::Scrollable => true,
+ Step::TextInput { value, .. } => !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 } => Self::image(*width),
+ Step::RowsAndColumns { layout, spacing } => {
+ Self::rows_and_columns(*layout, *spacing)
+ }
+ Step::Scrollable => Self::scrollable(),
+ Step::TextInput { value, is_secure } => {
+ Self::text_input(value, *is_secure)
+ }
+ Step::Debugger => Self::debugger(debug),
+ Step::End => Self::end(),
+ }
+ .into()
+ }
+
+ fn container(title: &str) -> Column<'a, StepMessage> {
+ column().spacing(20).push(text(title).size(50))
+ }
+
+ fn welcome() -> Column<'a, StepMessage> {
+ Self::container("Welcome!")
+ .push(
+ "This is a simple tour meant to showcase a bunch of widgets \
+ that can be easily implemented on top of Iced.",
+ )
+ .push(
+ "Iced is a cross-platform GUI library for Rust focused on \
+ simplicity and type-safety. It is heavily inspired by Elm.",
+ )
+ .push(
+ "It was originally born as part of Coffee, an opinionated \
+ 2D game engine for Rust.",
+ )
+ .push(
+ "On native platforms, Iced provides by default a renderer \
+ built on top of wgpu, a graphics library supporting Vulkan, \
+ Metal, DX11, and DX12.",
+ )
+ .push(
+ "Additionally, this tour can also run on WebAssembly thanks \
+ to dodrio, an experimental VDOM library for Rust.",
+ )
+ .push(
+ "You will need to interact with the UI in order to reach the \
+ end!",
+ )
+ }
+
+ fn slider(value: u8) -> Column<'a, StepMessage> {
+ Self::container("Slider")
+ .push(
+ "A slider allows you to smoothly select a value from a range \
+ of values.",
+ )
+ .push(
+ "The following slider lets you choose an integer from \
+ 0 to 100:",
+ )
+ .push(slider(0..=100, value, StepMessage::SliderChanged))
+ .push(
+ text(value.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);
+
+ let column_radio = radio(
+ "Column",
+ Layout::Column,
+ Some(layout),
+ StepMessage::LayoutChanged,
+ );
+
+ let layout_section: Element<_> = match layout {
+ Layout::Row => row()
+ .spacing(spacing)
+ .push(row_radio)
+ .push(column_radio)
+ .into(),
+ Layout::Column => column()
+ .spacing(spacing)
+ .push(row_radio)
+ .push(column_radio)
+ .into(),
+ };
+
+ let spacing_section = column()
+ .spacing(10)
+ .push(slider(0..=80, spacing, StepMessage::SpacingChanged))
+ .push(
+ text(format!("{} px", spacing))
+ .width(Length::Fill)
+ .horizontal_alignment(alignment::Horizontal::Center),
+ );
+
+ Self::container("Rows and columns")
+ .spacing(spacing)
+ .push(
+ "Iced uses a layout model based on flexbox to position UI \
+ elements.",
+ )
+ .push(
+ "Rows and columns can be used to distribute content \
+ horizontally or vertically, respectively.",
+ )
+ .push(layout_section)
+ .push("You can also easily change the spacing between elements:")
+ .push(spacing_section)
+ }
+
+ fn text(size: u16, color: Color) -> Column<'a, StepMessage> {
+ let size_section = column()
+ .padding(20)
+ .spacing(20)
+ .push("You can change its size:")
+ .push(text(format!("This text is {} pixels", size)).size(size))
+ .push(Slider::new(10..=70, size, StepMessage::TextSizeChanged));
+
+ let color_sliders = row()
+ .spacing(10)
+ .push(color_slider(color.r, move |r| Color { r, ..color }))
+ .push(color_slider(color.g, move |g| Color { g, ..color }))
+ .push(color_slider(color.b, move |b| Color { b, ..color }));
+
+ let color_section = column()
+ .padding(20)
+ .spacing(20)
+ .push("And its color:")
+ .push(text(format!("{:?}", color)).color(color))
+ .push(color_sliders);
+
+ Self::container("Text")
+ .push(
+ "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()
+ .padding(20)
+ .spacing(10)
+ .push(text("Iced is written in...").size(24))
+ .push(Language::all().iter().cloned().fold(
+ column().padding(10).spacing(20),
+ |choices, language| {
+ choices.push(radio(
+ language,
+ language,
+ selection,
+ StepMessage::LanguageSelected,
+ ))
+ },
+ ));
+
+ Self::container("Radio button")
+ .push(
+ "A radio button is normally used to represent a choice... \
+ Surprise test!",
+ )
+ .push(question)
+ .push(
+ "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 toggler(can_continue: bool) -> Column<'a, StepMessage> {
+ 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,
+ ))
+ .padding([0, 40]),
+ )
+ }
+
+ fn image(width: u16) -> Column<'a, StepMessage> {
+ Self::container("Image")
+ .push("An image that tries to keep its aspect ratio.")
+ .push(ferris(width))
+ .push(slider(100..=500, width, StepMessage::ImageWidthChanged))
+ .push(
+ text(format!("Width: {} px", width.to_string()))
+ .width(Length::Fill)
+ .horizontal_alignment(alignment::Horizontal::Center),
+ )
+ }
+
+ fn scrollable() -> Column<'a, StepMessage> {
+ Self::container("Scrollable")
+ .push(
+ "Iced supports scrollable content. Try it out! Find the \
+ button further below.",
+ )
+ .push(
+ text("Tip: You can use the scrollbar to scroll down faster!")
+ .size(16),
+ )
+ .push(vertical_space(Length::Units(4096)))
+ .push(
+ text("You are halfway there!")
+ .width(Length::Fill)
+ .size(30)
+ .horizontal_alignment(alignment::Horizontal::Center),
+ )
+ .push(vertical_space(Length::Units(4096)))
+ .push(ferris(300))
+ .push(
+ text("You made it!")
+ .width(Length::Fill)
+ .size(50)
+ .horizontal_alignment(alignment::Horizontal::Center),
+ )
+ }
+
+ fn text_input(value: &str, is_secure: bool) -> Column<'a, StepMessage> {
+ let text_input = text_input(
+ "Type something to continue...",
+ value,
+ StepMessage::InputChanged,
+ )
+ .padding(10)
+ .size(30);
+
+ Self::container("Text input")
+ .push("Use a text input to ask for different kinds of information.")
+ .push(if is_secure {
+ text_input.password()
+ } else {
+ text_input
+ })
+ .push(checkbox(
+ "Enable password mode",
+ is_secure,
+ StepMessage::ToggleSecureInput,
+ ))
+ .push(
+ "A text input produces a message every time it changes. It is \
+ very easy to keep track of its contents:",
+ )
+ .push(
+ text(if value.is_empty() {
+ "You have not typed anything yet..."
+ } else {
+ value
+ })
+ .width(Length::Fill)
+ .horizontal_alignment(alignment::Horizontal::Center),
+ )
+ }
+
+ fn debugger(debug: bool) -> Column<'a, StepMessage> {
+ Self::container("Debugger")
+ .push(
+ "You can ask Iced to visually explain the layouting of the \
+ different elements comprising your UI!",
+ )
+ .push(
+ "Give it a shot! Check the following checkbox to be able to \
+ see element boundaries.",
+ )
+ .push(if cfg!(target_arch = "wasm32") {
+ Element::new(
+ text("Not available on web yet!")
+ .color([0.7, 0.7, 0.7])
+ .horizontal_alignment(alignment::Horizontal::Center),
+ )
+ } else {
+ checkbox("Explain layout", debug, StepMessage::DebugToggled)
+ .into()
+ })
+ .push("Feel free to go back and take a look.")
+ }
+
+ fn end() -> Column<'a, StepMessage> {
+ 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 ferris<'a>(width: u16) -> Container<'a, StepMessage> {
+ container(
+ // This should go away once we unify resource loading on native
+ // platforms
+ if cfg!(target_arch = "wasm32") {
+ image("tour/images/ferris.png")
+ } else {
+ image(format!(
+ "{}/../../tour/images/ferris.png",
+ env!("CARGO_MANIFEST_DIR")
+ ))
+ }
+ .width(Length::Units(width)),
+ )
+ .width(Length::Fill)
+ .center_x()
+}
+
+fn button<'a, Message: Clone>(label: &str) -> Button<'a, Message> {
+ iced::pure::button(
+ text(label).horizontal_alignment(alignment::Horizontal::Center),
+ )
+ .padding(12)
+ .width(Length::Units(100))
+}
+
+fn color_slider<'a>(
+ component: f32,
+ update: impl Fn(f32) -> Color + 'a,
+) -> Slider<'a, f64, StepMessage> {
+ slider(0.0..=1.0, f64::from(component), move |c| {
+ StepMessage::TextColorChanged(update(c as f32))
+ })
+ .step(0.01)
+}
+
+#[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 String {
+ fn from(language: Language) -> String {
+ String::from(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.0,
+ 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()
+ }
+ }
+ }
+}
diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs
index dc8a4de7..377d7a2d 100644
--- a/examples/stopwatch/src/main.rs
+++ b/examples/stopwatch/src/main.rs
@@ -105,8 +105,8 @@ impl Application for Stopwatch {
Text::new(label)
.horizontal_alignment(alignment::Horizontal::Center),
)
- .min_width(80)
.padding(10)
+ .width(Length::Units(80))
.style(style)
};
diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs
index e199c88c..2024d25a 100644
--- a/examples/tour/src/main.rs
+++ b/examples/tour/src/main.rs
@@ -763,7 +763,7 @@ fn button<'a, Message: Clone>(
Text::new(label).horizontal_alignment(alignment::Horizontal::Center),
)
.padding(12)
- .min_width(100)
+ .width(Length::Units(100))
}
fn color_slider(