diff options
-rw-r--r-- | core/src/widget/button.rs | 8 | ||||
-rw-r--r-- | core/src/widget/text_input.rs | 13 | ||||
-rw-r--r-- | examples/resources/icons.ttf | bin | 0 -> 5596 bytes | |||
-rw-r--r-- | examples/todos.rs | 178 | ||||
-rw-r--r-- | examples/tour.rs | 1 | ||||
-rw-r--r-- | native/src/widget/checkbox.rs | 6 | ||||
-rw-r--r-- | native/src/widget/container.rs | 2 | ||||
-rw-r--r-- | native/src/widget/text_input.rs | 8 | ||||
-rw-r--r-- | wgpu/src/renderer/widget/button.rs | 45 |
9 files changed, 221 insertions, 40 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<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 diff --git a/core/src/widget/text_input.rs b/core/src/widget/text_input.rs index 9c7f4bc8..450a7cae 100644 --- a/core/src/widget/text_input.rs +++ b/core/src/widget/text_input.rs @@ -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()) } diff --git a/examples/resources/icons.ttf b/examples/resources/icons.ttf Binary files differnew file mode 100644 index 00000000..4498299d --- /dev/null +++ b/examples/resources/icons.ttf diff --git a/examples/todos.rs b/examples/todos.rs index 7f37300b..028b2d65 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<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}') +} 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/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<Message, Renderer> Widget<Message, Renderer> for Checkbox<Message> where Renderer: self::Renderer, { + fn width(&self) -> Length { + Length::Fill + } + fn layout( &self, renderer: &Renderer, 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); 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() => 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 |