summaryrefslogtreecommitdiffstats
path: root/examples/todos.rs
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/todos.rs
parent04086a90c9e933ebfb42de378054e1115b33529d (diff)
downloadiced-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.rs615
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
- }
- }
- }
-}