From 23370296024fc30b8721902955867c2fdab88832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 13 Nov 2019 07:34:07 +0100 Subject: [PATCH 1/6] Remove default styling of `Button` - A background will only show if explicitly set. - `iced_wgpu` won't apply a `min_width` of 100 units anymore. --- core/src/widget/button.rs | 8 ++++++ examples/tour.rs | 1 + wgpu/src/renderer/widget/button.rs | 45 +++++++++++++++--------------- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/core/src/widget/button.rs b/core/src/widget/button.rs index 9cf20071..e7961284 100644 --- a/core/src/widget/button.rs +++ b/core/src/widget/button.rs @@ -19,6 +19,8 @@ pub struct Button<'a, Message, Element> { pub width: Length, + pub min_width: u32, + pub padding: u16, pub background: Option, @@ -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 diff --git a/examples/tour.rs b/examples/tour.rs index 3fd031b8..34ad0a34 100644 --- a/examples/tour.rs +++ b/examples/tour.rs @@ -671,6 +671,7 @@ fn button<'a, Message>( ) .padding(12) .border_radius(12) + .min_width(100) } fn primary_button<'a, Message>( diff --git a/wgpu/src/renderer/widget/button.rs b/wgpu/src/renderer/widget/button.rs index 3d5e42ba..a19c7d86 100644 --- a/wgpu/src/renderer/widget/button.rs +++ b/wgpu/src/renderer/widget/button.rs @@ -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 From ef15a6b027232e51198ef95698d978e25dc52ce7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 13 Nov 2019 07:37:13 +0100 Subject: [PATCH 2/6] Fix `Widget::width` implementation of `Checkbox` --- native/src/widget/checkbox.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index a7040e02..3f0f8dda 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -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 Widget for Checkbox where Renderer: self::Renderer, { + fn width(&self) -> Length { + Length::Fill + } + fn layout( &self, renderer: &Renderer, From 621e5a55b78e29a0a40387d74ce8f355a38538b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 13 Nov 2019 07:37:42 +0100 Subject: [PATCH 3/6] Loosen `layout::Limits` for `Container` children --- native/src/widget/container.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index c616db2a..fd7a5ca5 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -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); From 657e513651b4153268e06074ccc8ac91078ffe69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 13 Nov 2019 07:38:06 +0100 Subject: [PATCH 4/6] Implement `text_input::State::focused` --- core/src/widget/text_input.rs | 11 +++++++++++ native/src/widget/text_input.rs | 8 ++++++++ 2 files changed, 19 insertions(+) diff --git a/core/src/widget/text_input.rs b/core/src/widget/text_input.rs index 9c7f4bc8..c4ca0abc 100644 --- a/core/src/widget/text_input.rs +++ b/core/src/widget/text_input.rs @@ -91,6 +91,13 @@ impl State { Self::default() } + pub fn focused(value: &str) -> Self { + Self { + is_focused: true, + cursor_position: Value::new(value).len(), + } + } + pub fn move_cursor_right(&mut self, value: &Value) { let current = self.cursor_position(value); @@ -107,6 +114,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()) } diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 7e81e257..35e10000 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -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() => From cf3c53a063b2bb8dcb612e259796fed3fd9b7147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 13 Nov 2019 07:39:29 +0100 Subject: [PATCH 5/6] Implement task edition/deletion in `todos` example --- examples/resources/icons.ttf | Bin 0 -> 5596 bytes examples/todos.rs | 178 +++++++++++++++++++++++++++++++---- 2 files changed, 162 insertions(+), 16 deletions(-) create mode 100644 examples/resources/icons.ttf diff --git a/examples/resources/icons.ttf b/examples/resources/icons.ttf new file mode 100644 index 0000000000000000000000000000000000000000..4498299db26811ff8490001ea6f681529a4118f4 GIT binary patch literal 5596 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 literal 0 HcmV?d00001 diff --git a/examples/todos.rs b/examples/todos.rs index 7f37300b..e6c932e9 100644 --- a/examples/todos.rs +++ b/examples/todos.rs @@ -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 { - 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(&self.description), + 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 { + 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}') +} From be5466a0a7ee6a16b5c07e61c9a22068ad8e127f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 13 Nov 2019 07:57:22 +0100 Subject: [PATCH 6/6] Remove argument from `text_input::State::focused` --- core/src/widget/text_input.rs | 6 ++++-- examples/todos.rs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/src/widget/text_input.rs b/core/src/widget/text_input.rs index c4ca0abc..450a7cae 100644 --- a/core/src/widget/text_input.rs +++ b/core/src/widget/text_input.rs @@ -91,10 +91,12 @@ impl State { Self::default() } - pub fn focused(value: &str) -> Self { + pub fn focused() -> Self { + use std::usize; + Self { is_focused: true, - cursor_position: Value::new(value).len(), + cursor_position: usize::MAX, } } diff --git a/examples/todos.rs b/examples/todos.rs index e6c932e9..028b2d65 100644 --- a/examples/todos.rs +++ b/examples/todos.rs @@ -153,7 +153,7 @@ impl Task { } TaskMessage::Edit => { self.state = TaskState::Editing { - text_input: text_input::State::focused(&self.description), + text_input: text_input::State::focused(), delete_button: button::State::new(), }; }