Merge pull request #55 from hecrj/example/edit-todos
Edit and delete todos
This commit is contained in:
commit
af5ec49412
9 changed files with 221 additions and 40 deletions
|
|
@ -19,6 +19,8 @@ pub struct Button<'a, Message, Element> {
|
||||||
|
|
||||||
pub width: Length,
|
pub width: Length,
|
||||||
|
|
||||||
|
pub min_width: u32,
|
||||||
|
|
||||||
pub padding: u16,
|
pub padding: u16,
|
||||||
|
|
||||||
pub background: Option<Background>,
|
pub background: Option<Background>,
|
||||||
|
|
@ -52,6 +54,7 @@ impl<'a, Message, Element> Button<'a, Message, Element> {
|
||||||
content: content.into(),
|
content: content.into(),
|
||||||
on_press: None,
|
on_press: None,
|
||||||
width: Length::Shrink,
|
width: Length::Shrink,
|
||||||
|
min_width: 0,
|
||||||
padding: 0,
|
padding: 0,
|
||||||
background: None,
|
background: None,
|
||||||
border_radius: 0,
|
border_radius: 0,
|
||||||
|
|
@ -66,6 +69,11 @@ impl<'a, Message, Element> Button<'a, Message, Element> {
|
||||||
self
|
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 {
|
pub fn padding(mut self, padding: u16) -> Self {
|
||||||
self.padding = padding;
|
self.padding = padding;
|
||||||
self
|
self
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,15 @@ impl State {
|
||||||
Self::default()
|
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) {
|
pub fn move_cursor_right(&mut self, value: &Value) {
|
||||||
let current = self.cursor_position(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 {
|
pub fn cursor_position(&self, value: &Value) -> usize {
|
||||||
self.cursor_position.min(value.len())
|
self.cursor_position.min(value.len())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
examples/resources/icons.ttf
Normal file
BIN
examples/resources/icons.ttf
Normal file
Binary file not shown.
|
|
@ -1,6 +1,7 @@
|
||||||
use iced::{
|
use iced::{
|
||||||
scrollable, text::HorizontalAlignment, text_input, Application, Checkbox,
|
button, scrollable, text::HorizontalAlignment, text_input, Align,
|
||||||
Color, Column, Container, Element, Length, Scrollable, Text, TextInput,
|
Application, Background, Button, Checkbox, Color, Column, Container,
|
||||||
|
Element, Font, Length, Row, Scrollable, Text, TextInput,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
|
|
@ -19,7 +20,7 @@ struct Todos {
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
InputChanged(String),
|
InputChanged(String),
|
||||||
CreateTask,
|
CreateTask,
|
||||||
TaskChanged(usize, bool),
|
TaskMessage(usize, TaskMessage),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Application for Todos {
|
impl Application for Todos {
|
||||||
|
|
@ -40,9 +41,12 @@ impl Application for Todos {
|
||||||
self.input_value.clear();
|
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) {
|
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)
|
.size(30)
|
||||||
.on_submit(Message::CreateTask);
|
.on_submit(Message::CreateTask);
|
||||||
|
|
||||||
let tasks = self.tasks.iter_mut().enumerate().fold(
|
let tasks: Element<_> =
|
||||||
Column::new().spacing(20),
|
if self.tasks.len() > 0 {
|
||||||
|column, (i, task)| {
|
self.tasks
|
||||||
column.push(
|
.iter_mut()
|
||||||
task.view()
|
.enumerate()
|
||||||
.map(move |state| Message::TaskChanged(i, state)),
|
.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()
|
let content = Column::new()
|
||||||
.max_width(800)
|
.max_width(800)
|
||||||
|
|
@ -94,6 +112,27 @@ impl Application for Todos {
|
||||||
struct Task {
|
struct Task {
|
||||||
description: String,
|
description: String,
|
||||||
completed: bool,
|
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 {
|
impl Task {
|
||||||
|
|
@ -101,12 +140,97 @@ impl Task {
|
||||||
Task {
|
Task {
|
||||||
description,
|
description,
|
||||||
completed: false,
|
completed: false,
|
||||||
|
state: TaskState::Idle {
|
||||||
|
edit_button: button::State::new(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&mut self) -> Element<bool> {
|
fn update(&mut self, message: TaskMessage) {
|
||||||
Checkbox::new(self.completed, &self.description, |checked| checked)
|
match message {
|
||||||
.into()
|
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,
|
b: 0.5,
|
||||||
a: 1.0,
|
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}')
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -671,6 +671,7 @@ fn button<'a, Message>(
|
||||||
)
|
)
|
||||||
.padding(12)
|
.padding(12)
|
||||||
.border_radius(12)
|
.border_radius(12)
|
||||||
|
.min_width(100)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn primary_button<'a, Message>(
|
fn primary_button<'a, Message>(
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
use crate::input::{mouse, ButtonState};
|
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;
|
pub use iced_core::Checkbox;
|
||||||
|
|
||||||
|
|
@ -10,6 +10,10 @@ impl<Message, Renderer> Widget<Message, Renderer> for Checkbox<Message>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: self::Renderer,
|
||||||
{
|
{
|
||||||
|
fn width(&self) -> Length {
|
||||||
|
Length::Fill
|
||||||
|
}
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ where
|
||||||
.width(self.width)
|
.width(self.width)
|
||||||
.height(self.height);
|
.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());
|
let size = limits.resolve(content.size());
|
||||||
|
|
||||||
content.align(self.horizontal_alignment, self.vertical_alignment, size);
|
content.align(self.horizontal_alignment, self.vertical_alignment, size);
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,10 @@ where
|
||||||
Renderer: self::Renderer,
|
Renderer: self::Renderer,
|
||||||
Message: Clone + std::fmt::Debug,
|
Message: Clone + std::fmt::Debug,
|
||||||
{
|
{
|
||||||
|
fn width(&self) -> Length {
|
||||||
|
self.width
|
||||||
|
}
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
|
|
@ -46,6 +50,10 @@ where
|
||||||
}) => {
|
}) => {
|
||||||
self.state.is_focused =
|
self.state.is_focused =
|
||||||
layout.bounds().contains(cursor_position);
|
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))
|
Event::Keyboard(keyboard::Event::CharacterReceived(c))
|
||||||
if self.state.is_focused && !c.is_control() =>
|
if self.state.is_focused && !c.is_control() =>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ impl button::Renderer for Renderer {
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
let padding = f32::from(button.padding);
|
let padding = f32::from(button.padding);
|
||||||
let limits = limits
|
let limits = limits
|
||||||
.min_width(100)
|
.min_width(button.min_width)
|
||||||
.width(button.width)
|
.width(button.width)
|
||||||
.height(Length::Shrink)
|
.height(Length::Shrink)
|
||||||
.pad(padding);
|
.pad(padding);
|
||||||
|
|
@ -56,28 +56,29 @@ impl button::Renderer for Renderer {
|
||||||
};
|
};
|
||||||
|
|
||||||
(
|
(
|
||||||
Primitive::Group {
|
match button.background {
|
||||||
primitives: vec![
|
None => content,
|
||||||
Primitive::Quad {
|
Some(background) => Primitive::Group {
|
||||||
bounds: Rectangle {
|
primitives: vec![
|
||||||
x: bounds.x + 1.0,
|
Primitive::Quad {
|
||||||
y: bounds.y + shadow_offset,
|
bounds: Rectangle {
|
||||||
..bounds
|
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(
|
Primitive::Quad {
|
||||||
[0.0, 0.0, 0.0, 0.5].into(),
|
bounds,
|
||||||
),
|
background,
|
||||||
border_radius: button.border_radius,
|
border_radius: button.border_radius,
|
||||||
},
|
},
|
||||||
Primitive::Quad {
|
content,
|
||||||
bounds,
|
],
|
||||||
background: button.background.unwrap_or(
|
},
|
||||||
Background::Color([0.8, 0.8, 0.8].into()),
|
|
||||||
),
|
|
||||||
border_radius: button.border_radius,
|
|
||||||
},
|
|
||||||
content,
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
if is_mouse_over {
|
if is_mouse_over {
|
||||||
MouseCursor::Pointer
|
MouseCursor::Pointer
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue