Merge pull request #55 from hecrj/example/edit-todos

Edit and delete todos
This commit is contained in:
Héctor Ramón 2019-11-14 06:48:12 +01:00 committed by GitHub
commit af5ec49412
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 221 additions and 40 deletions

View file

@ -19,6 +19,8 @@ pub struct Button<'a, Message, Element> {
pub width: Length,
pub min_width: u32,
pub padding: u16,
pub background: Option<Background>,
@ -52,6 +54,7 @@ impl<'a, Message, Element> Button<'a, Message, Element> {
content: content.into(),
on_press: None,
width: Length::Shrink,
min_width: 0,
padding: 0,
background: None,
border_radius: 0,
@ -66,6 +69,11 @@ impl<'a, Message, Element> Button<'a, Message, Element> {
self
}
pub fn min_width(mut self, min_width: u32) -> Self {
self.min_width = min_width;
self
}
pub fn padding(mut self, padding: u16) -> Self {
self.padding = padding;
self

View file

@ -91,6 +91,15 @@ impl State {
Self::default()
}
pub fn focused() -> Self {
use std::usize;
Self {
is_focused: true,
cursor_position: usize::MAX,
}
}
pub fn move_cursor_right(&mut self, value: &Value) {
let current = self.cursor_position(value);
@ -107,6 +116,10 @@ impl State {
}
}
pub fn move_cursor_to_end(&mut self, value: &Value) {
self.cursor_position = value.len();
}
pub fn cursor_position(&self, value: &Value) -> usize {
self.cursor_position.min(value.len())
}

Binary file not shown.

View file

@ -1,6 +1,7 @@
use iced::{
scrollable, text::HorizontalAlignment, text_input, Application, Checkbox,
Color, Column, Container, Element, Length, Scrollable, Text, TextInput,
button, scrollable, text::HorizontalAlignment, text_input, Align,
Application, Background, Button, Checkbox, Color, Column, Container,
Element, Font, Length, Row, Scrollable, Text, TextInput,
};
pub fn main() {
@ -19,7 +20,7 @@ struct Todos {
pub enum Message {
InputChanged(String),
CreateTask,
TaskChanged(usize, bool),
TaskMessage(usize, TaskMessage),
}
impl Application for Todos {
@ -40,9 +41,12 @@ impl Application for Todos {
self.input_value.clear();
}
}
Message::TaskChanged(i, completed) => {
Message::TaskMessage(i, TaskMessage::Delete) => {
self.tasks.remove(i);
}
Message::TaskMessage(i, task_message) => {
if let Some(task) = self.tasks.get_mut(i) {
task.completed = completed;
task.update(task_message);
}
}
}
@ -66,15 +70,29 @@ impl Application for Todos {
.size(30)
.on_submit(Message::CreateTask);
let tasks = self.tasks.iter_mut().enumerate().fold(
Column::new().spacing(20),
|column, (i, task)| {
column.push(
task.view()
.map(move |state| Message::TaskChanged(i, state)),
let tasks: Element<_> =
if self.tasks.len() > 0 {
self.tasks
.iter_mut()
.enumerate()
.fold(Column::new().spacing(20), |column, (i, task)| {
column.push(task.view().map(move |message| {
Message::TaskMessage(i, message)
}))
})
.into()
} else {
Container::new(
Text::new("You do not have any tasks! :D")
.size(25)
.horizontal_alignment(HorizontalAlignment::Center)
.color([0.7, 0.7, 0.7]),
)
},
);
.width(Length::Fill)
.height(Length::Units(200))
.center_y()
.into()
};
let content = Column::new()
.max_width(800)
@ -94,6 +112,27 @@ impl Application for Todos {
struct Task {
description: String,
completed: bool,
state: TaskState,
}
#[derive(Debug)]
pub enum TaskState {
Idle {
edit_button: button::State,
},
Editing {
text_input: text_input::State,
delete_button: button::State,
},
}
#[derive(Debug, Clone)]
pub enum TaskMessage {
Completed(bool),
Edit,
DescriptionEdited(String),
FinishEdition,
Delete,
}
impl Task {
@ -101,12 +140,97 @@ impl Task {
Task {
description,
completed: false,
state: TaskState::Idle {
edit_button: button::State::new(),
},
}
}
fn view(&mut self) -> Element<bool> {
Checkbox::new(self.completed, &self.description, |checked| checked)
.into()
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,
);
Row::new()
.spacing(20)
.align_items(Align::Center)
.push(checkbox)
.push(
Button::new(
edit_button,
edit_icon().color([0.5, 0.5, 0.5]),
)
.on_press(TaskMessage::Edit)
.padding(10),
)
.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().color(Color::WHITE))
.push(
Text::new("Delete")
.width(Length::Shrink)
.color(Color::WHITE),
),
)
.on_press(TaskMessage::Delete)
.padding(10)
.border_radius(5)
.background(Background::Color([0.8, 0.2, 0.2].into())),
)
.into()
}
}
}
}
@ -117,3 +241,25 @@ const GRAY: Color = Color {
b: 0.5,
a: 1.0,
};
// 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}')
}

View file

@ -671,6 +671,7 @@ fn button<'a, Message>(
)
.padding(12)
.border_radius(12)
.min_width(100)
}
fn primary_button<'a, Message>(

View file

@ -2,7 +2,7 @@
use std::hash::Hash;
use crate::input::{mouse, ButtonState};
use crate::{layout, Element, Event, Hasher, Layout, Point, Widget};
use crate::{layout, Element, Event, Hasher, Layout, Length, Point, Widget};
pub use iced_core::Checkbox;
@ -10,6 +10,10 @@ impl<Message, Renderer> Widget<Message, Renderer> for Checkbox<Message>
where
Renderer: self::Renderer,
{
fn width(&self) -> Length {
Length::Fill
}
fn layout(
&self,
renderer: &Renderer,

View file

@ -27,7 +27,7 @@ where
.width(self.width)
.height(self.height);
let mut content = self.content.layout(renderer, &limits);
let mut content = self.content.layout(renderer, &limits.loose());
let size = limits.resolve(content.size());
content.align(self.horizontal_alignment, self.vertical_alignment, size);

View file

@ -11,6 +11,10 @@ where
Renderer: self::Renderer,
Message: Clone + std::fmt::Debug,
{
fn width(&self) -> Length {
self.width
}
fn layout(
&self,
renderer: &Renderer,
@ -46,6 +50,10 @@ where
}) => {
self.state.is_focused =
layout.bounds().contains(cursor_position);
if self.state.cursor_position(&self.value) == 0 {
self.state.move_cursor_to_end(&self.value);
}
}
Event::Keyboard(keyboard::Event::CharacterReceived(c))
if self.state.is_focused && !c.is_control() =>

View file

@ -12,7 +12,7 @@ impl button::Renderer for Renderer {
) -> layout::Node {
let padding = f32::from(button.padding);
let limits = limits
.min_width(100)
.min_width(button.min_width)
.width(button.width)
.height(Length::Shrink)
.pad(padding);
@ -56,28 +56,29 @@ impl button::Renderer for Renderer {
};
(
Primitive::Group {
primitives: vec![
Primitive::Quad {
bounds: Rectangle {
x: bounds.x + 1.0,
y: bounds.y + shadow_offset,
..bounds
match button.background {
None => content,
Some(background) => Primitive::Group {
primitives: vec![
Primitive::Quad {
bounds: Rectangle {
x: bounds.x + 1.0,
y: bounds.y + shadow_offset,
..bounds
},
background: Background::Color(
[0.0, 0.0, 0.0, 0.5].into(),
),
border_radius: button.border_radius,
},
background: Background::Color(
[0.0, 0.0, 0.0, 0.5].into(),
),
border_radius: button.border_radius,
},
Primitive::Quad {
bounds,
background: button.background.unwrap_or(
Background::Color([0.8, 0.8, 0.8].into()),
),
border_radius: button.border_radius,
},
content,
],
Primitive::Quad {
bounds,
background,
border_radius: button.border_radius,
},
content,
],
},
},
if is_mouse_over {
MouseCursor::Pointer