summaryrefslogtreecommitdiffstats
path: root/examples/pure/todos/src/main.rs
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2022-07-27 06:49:20 +0200
committerLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2022-07-27 06:49:20 +0200
commitff2519b1d43d481987351a83b6dd7237524c21f0 (patch)
tree5731eeb7eb1247d4a8951de0d5bc5d8102640559 /examples/pure/todos/src/main.rs
parentc44267b85f7aaa2997e3caf1323b837d95818c22 (diff)
downloadiced-ff2519b1d43d481987351a83b6dd7237524c21f0.tar.gz
iced-ff2519b1d43d481987351a83b6dd7237524c21f0.tar.bz2
iced-ff2519b1d43d481987351a83b6dd7237524c21f0.zip
Replace stateful widgets with new `iced_pure` API
Diffstat (limited to 'examples/pure/todos/src/main.rs')
-rw-r--r--examples/pure/todos/src/main.rs556
1 files changed, 0 insertions, 556 deletions
diff --git a/examples/pure/todos/src/main.rs b/examples/pure/todos/src/main.rs
deleted file mode 100644
index 9a74ea71..00000000
--- a/examples/pure/todos/src/main.rs
+++ /dev/null
@@ -1,556 +0,0 @@
-use iced::alignment::{self, Alignment};
-use iced::pure::widget::Text;
-use iced::pure::{
- button, checkbox, column, container, row, scrollable, text, text_input,
- Application, Element,
-};
-use iced::theme::{self, Theme};
-use iced::window;
-use iced::{Color, 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 Message = Message;
- type Theme = Theme;
- type Executor = iced::executor::Default;
- 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)
- .style(Color::from([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(theme::Button::Text),
- )
- .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(theme::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 {
- theme::Button::Primary
- } else {
- theme::Button::Text
- });
-
- 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)
- .style(Color::from([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 {
- File,
- Format,
-}
-
-#[derive(Debug, Clone)]
-enum SaveError {
- File,
- Write,
- Format,
-}
-
-#[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_default()
- };
-
- 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::File)?;
-
- file.read_to_string(&mut contents)
- .await
- .map_err(|_| LoadError::File)?;
-
- serde_json::from_str(&contents).map_err(|_| LoadError::Format)
- }
-
- async fn save(self) -> Result<(), SaveError> {
- use async_std::prelude::*;
-
- let json = serde_json::to_string_pretty(&self)
- .map_err(|_| SaveError::Format)?;
-
- let path = Self::path();
-
- if let Some(dir) = path.parent() {
- async_std::fs::create_dir_all(dir)
- .await
- .map_err(|_| SaveError::File)?;
- }
-
- {
- let mut file = async_std::fs::File::create(path)
- .await
- .map_err(|_| SaveError::File)?;
-
- file.write_all(json.as_bytes())
- .await
- .map_err(|_| SaveError::Write)?;
- }
-
- // 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::File)?;
-
- let contents = storage
- .get_item("state")
- .map_err(|_| LoadError::File)?
- .ok_or(LoadError::File)?;
-
- serde_json::from_str(&contents).map_err(|_| LoadError::Format)
- }
-
- async fn save(self) -> Result<(), SaveError> {
- let storage = Self::storage().ok_or(SaveError::File)?;
-
- let json = serde_json::to_string_pretty(&self)
- .map_err(|_| SaveError::Format)?;
-
- storage
- .set_item("state", &json)
- .map_err(|_| SaveError::Write)?;
-
- let _ = wasm_timer::Delay::new(std::time::Duration::from_secs(2)).await;
-
- Ok(())
- }
-}