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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 J@S+$Y`Y%9~)?EMq 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(), }; }