diff options
author | 2020-01-20 06:27:01 +0100 | |
---|---|---|
committer | 2020-01-20 06:27:01 +0100 | |
commit | 7cea7371150e6de28032827519936008592f112d (patch) | |
tree | 3bfd82272094ef69493de622af6c9b06389a7c27 /examples/todos.rs | |
parent | 04086a90c9e933ebfb42de378054e1115b33529d (diff) | |
download | iced-7cea7371150e6de28032827519936008592f112d.tar.gz iced-7cea7371150e6de28032827519936008592f112d.tar.bz2 iced-7cea7371150e6de28032827519936008592f112d.zip |
Package examples and remove `dev-dependencies`
Diffstat (limited to 'examples/todos.rs')
-rw-r--r-- | examples/todos.rs | 615 |
1 files changed, 0 insertions, 615 deletions
diff --git a/examples/todos.rs b/examples/todos.rs deleted file mode 100644 index 06595a1e..00000000 --- a/examples/todos.rs +++ /dev/null @@ -1,615 +0,0 @@ -use iced::{ - button, scrollable, text_input, Align, Application, Button, Checkbox, - Column, Command, Container, Element, Font, HorizontalAlignment, Length, - Row, Scrollable, Settings, Text, TextInput, -}; -use serde::{Deserialize, Serialize}; - -pub fn main() { - Todos::run(Settings::default()) -} - -#[derive(Debug)] -enum Todos { - Loading, - Loaded(State), -} - -#[derive(Debug, Default)] -struct State { - scroll: scrollable::State, - input: text_input::State, - input_value: String, - filter: Filter, - tasks: Vec<Task>, - controls: Controls, - 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_futures::executor::AsyncStd; - type Message = Message; - - fn new() -> (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(&mut self) -> Element<Message> { - match self { - Todos::Loading => loading_message(), - Todos::Loaded(State { - scroll, - input, - input_value, - filter, - tasks, - controls, - .. - }) => { - let title = Text::new("todos") - .width(Length::Fill) - .size(100) - .color([0.5, 0.5, 0.5]) - .horizontal_alignment(HorizontalAlignment::Center); - - let input = TextInput::new( - input, - "What needs to be done?", - input_value, - Message::InputChanged, - ) - .padding(15) - .size(30) - .on_submit(Message::CreateTask); - - let controls = controls.view(&tasks, *filter); - let filtered_tasks = - tasks.iter().filter(|task| filter.matches(task)); - - let tasks: Element<_> = if filtered_tasks.count() > 0 { - tasks - .iter_mut() - .enumerate() - .filter(|(_, task)| filter.matches(task)) - .fold(Column::new().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::new() - .max_width(800) - .spacing(20) - .push(title) - .push(input) - .push(controls) - .push(tasks); - - Scrollable::new(scroll) - .padding(40) - .push( - Container::new(content).width(Length::Fill).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 { - edit_button: button::State, - }, - Editing { - text_input: text_input::State, - delete_button: button::State, - }, -} - -impl Default for TaskState { - fn default() -> Self { - TaskState::Idle { - edit_button: button::State::new(), - } - } -} - -#[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 { - edit_button: button::State::new(), - }, - } - } - - fn update(&mut self, message: TaskMessage) { - match message { - TaskMessage::Completed(completed) => { - self.completed = completed; - } - TaskMessage::Edit => { - self.state = TaskState::Editing { - text_input: text_input::State::focused(), - delete_button: button::State::new(), - }; - } - TaskMessage::DescriptionEdited(new_description) => { - self.description = new_description; - } - TaskMessage::FinishEdition => { - if !self.description.is_empty() { - self.state = TaskState::Idle { - edit_button: button::State::new(), - } - } - } - TaskMessage::Delete => {} - } - } - - fn view(&mut self) -> Element<TaskMessage> { - match &mut self.state { - TaskState::Idle { edit_button } => { - let checkbox = Checkbox::new( - self.completed, - &self.description, - TaskMessage::Completed, - ) - .width(Length::Fill); - - Row::new() - .spacing(20) - .align_items(Align::Center) - .push(checkbox) - .push( - Button::new(edit_button, edit_icon()) - .on_press(TaskMessage::Edit) - .padding(10) - .style(style::Button::Icon), - ) - .into() - } - TaskState::Editing { - text_input, - delete_button, - } => { - let text_input = TextInput::new( - text_input, - "Describe your task...", - &self.description, - TaskMessage::DescriptionEdited, - ) - .on_submit(TaskMessage::FinishEdition) - .padding(10); - - Row::new() - .spacing(20) - .align_items(Align::Center) - .push(text_input) - .push( - Button::new( - delete_button, - Row::new() - .spacing(10) - .push(delete_icon()) - .push(Text::new("Delete")), - ) - .on_press(TaskMessage::Delete) - .padding(10) - .style(style::Button::Destructive), - ) - .into() - } - } - } -} - -#[derive(Debug, Default, Clone)] -pub struct Controls { - all_button: button::State, - active_button: button::State, - completed_button: button::State, -} - -impl Controls { - fn view(&mut self, tasks: &[Task], current_filter: Filter) -> Row<Message> { - let Controls { - all_button, - active_button, - completed_button, - } = self; - - let tasks_left = tasks.iter().filter(|task| !task.completed).count(); - - let filter_button = |state, label, filter, current_filter| { - let label = Text::new(label).size(16); - let button = - Button::new(state, label).style(style::Button::Filter { - selected: filter == current_filter, - }); - - button.on_press(Message::FilterChanged(filter)).padding(8) - }; - - Row::new() - .spacing(20) - .align_items(Align::Center) - .push( - Text::new(&format!( - "{} {} left", - tasks_left, - if tasks_left == 1 { "task" } else { "tasks" } - )) - .width(Length::Fill) - .size(16), - ) - .push( - Row::new() - .spacing(10) - .push(filter_button( - all_button, - "All", - Filter::All, - current_filter, - )) - .push(filter_button( - active_button, - "Active", - Filter::Active, - current_filter, - )) - .push(filter_button( - completed_button, - "Completed", - Filter::Completed, - current_filter, - )), - ) - } -} - -#[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() -> Element<'static, Message> { - Container::new( - Text::new("Loading...") - .horizontal_alignment(HorizontalAlignment::Center) - .size(50), - ) - .width(Length::Fill) - .height(Length::Fill) - .center_y() - .into() -} - -fn empty_message(message: &str) -> Element<'static, Message> { - Container::new( - Text::new(message) - .size(25) - .horizontal_alignment(HorizontalAlignment::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!("resources/icons.ttf"), -}; - -fn icon(unicode: char) -> Text { - Text::new(&unicode.to_string()) - .font(ICONS) - .width(Length::Units(20)) - .horizontal_alignment(HorizontalAlignment::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 { - DirectoryError, - FileError, - WriteError, - FormatError, -} - -impl SavedState { - fn path() -> std::path::PathBuf { - let mut path = if let Some(project_dirs) = - directories::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::DirectoryError)?; - } - - { - 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(()) - } -} - -mod style { - use iced::{button, Background, Color, Vector}; - - pub enum Button { - Filter { selected: bool }, - Icon, - Destructive, - } - - impl button::StyleSheet for Button { - fn active(&self) -> button::Style { - match self { - Button::Filter { selected } => { - if *selected { - button::Style { - background: Some(Background::Color( - Color::from_rgb(0.2, 0.2, 0.7), - )), - border_radius: 10, - text_color: Color::WHITE, - ..button::Style::default() - } - } else { - 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, - 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::Filter { selected } if !selected => { - Color::from_rgb(0.2, 0.2, 0.7) - } - _ => active.text_color, - }, - shadow_offset: active.shadow_offset + Vector::new(0.0, 1.0), - ..active - } - } - } -} |