From 3c866c15aa1db944a2056f01449a2fbdda2f5abb Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Tue, 17 Jan 2023 10:01:17 -0800 Subject: Add group overlay element --- native/src/overlay.rs | 2 + native/src/overlay/group.rs | 165 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 native/src/overlay/group.rs diff --git a/native/src/overlay.rs b/native/src/overlay.rs index 22f8b6ec..0b1e8daf 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -1,9 +1,11 @@ //! Display interactive elements on top of other widgets. mod element; +mod group; pub mod menu; pub use element::Element; +pub use group::Group; pub use menu::Menu; use crate::event::{self, Event}; diff --git a/native/src/overlay/group.rs b/native/src/overlay/group.rs new file mode 100644 index 00000000..f894f911 --- /dev/null +++ b/native/src/overlay/group.rs @@ -0,0 +1,165 @@ +use iced_core::{Point, Rectangle, Size}; + +use crate::event; +use crate::layout; +use crate::mouse; +use crate::overlay; +use crate::renderer; +use crate::widget; +use crate::{Clipboard, Event, Layout, Overlay, Shell}; + +/// An [`Overlay`] container that displays multiple overlay +/// [`overlay::Element`] children +#[allow(missing_debug_implementations)] +pub struct Group<'a, Message, Renderer> { + children: Vec>, +} + +impl<'a, Message, Renderer> Group<'a, Message, Renderer> +where + Renderer: 'a + crate::Renderer, + Message: 'a, +{ + /// Creates an empty [`Group`]. + pub fn new() -> Self { + Self::default() + } + + /// Creates a [`Group`] with the given elements. + pub fn with_children( + children: Vec>, + ) -> Self { + Group { children } + } + + /// Adds an [`overlay::Element`] to the [`Group`]. + pub fn push( + mut self, + child: impl Into>, + ) -> Self { + self.children.push(child.into()); + self + } + + /// Turns the [`Group`] into an overlay [`overlay::Element`] + pub fn overlay(self) -> overlay::Element<'a, Message, Renderer> { + overlay::Element::new(Point::ORIGIN, Box::new(self)) + } +} + +impl<'a, Message, Renderer> Default for Group<'a, Message, Renderer> +where + Renderer: 'a + crate::Renderer, + Message: 'a, +{ + fn default() -> Self { + Self::with_children(Vec::new()) + } +} + +impl<'a, Message, Renderer> Overlay + for Group<'a, Message, Renderer> +where + Renderer: crate::Renderer, +{ + fn layout( + &self, + renderer: &Renderer, + bounds: Size, + _position: Point, + ) -> layout::Node { + layout::Node::with_children( + bounds, + self.children + .iter() + .map(|child| child.layout(renderer, bounds)) + .collect(), + ) + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.children + .iter_mut() + .zip(layout.children()) + .map(|(child, layout)| { + child.on_event( + event.clone(), + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + }) + .fold(event::Status::Ignored, event::Status::merge) + } + + fn draw( + &self, + renderer: &mut Renderer, + theme: &::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + ) { + for (child, layout) in self.children.iter().zip(layout.children()) { + child.draw(renderer, theme, style, layout, cursor_position); + } + } + + fn mouse_interaction( + &self, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.children + .iter() + .zip(layout.children()) + .map(|(child, layout)| { + child.mouse_interaction( + layout, + cursor_position, + viewport, + renderer, + ) + }) + .max() + .unwrap_or_default() + } + + fn operate( + &mut self, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn widget::Operation, + ) { + operation.container(None, &mut |operation| { + self.children.iter_mut().zip(layout.children()).for_each( + |(child, layout)| { + child.operate(layout, renderer, operation); + }, + ) + }); + } +} + +impl<'a, Message, Renderer> From> + for overlay::Element<'a, Message, Renderer> +where + Renderer: 'a + crate::Renderer, + Message: 'a, +{ + fn from(group: Group<'a, Message, Renderer>) -> Self { + group.overlay() + } +} -- cgit From b2a3a85acb2a0722e90c46b70d574f1d676da9d1 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Tue, 17 Jan 2023 10:12:51 -0800 Subject: Use group overlay for containers w/ children --- native/src/overlay.rs | 8 +++++--- native/src/widget/pane_grid.rs | 13 ++++++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/native/src/overlay.rs b/native/src/overlay.rs index 0b1e8daf..e7394494 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -91,7 +91,7 @@ where } } -/// Obtains the first overlay [`Element`] found in the given children. +/// Returns a [`Group`] of overlay [`Element`] children. /// /// This method will generally only be used by advanced users that are /// implementing the [`Widget`](crate::Widget) trait. @@ -104,12 +104,14 @@ pub fn from_children<'a, Message, Renderer>( where Renderer: crate::Renderer, { - children + let children = children .iter_mut() .zip(&mut tree.children) .zip(layout.children()) .filter_map(|((child, state), layout)| { child.as_widget_mut().overlay(state, layout, renderer) }) - .next() + .collect::>(); + + (!children.is_empty()).then(|| Group::with_children(children).overlay()) } diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 8dbd1825..eb04c0ba 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -35,7 +35,7 @@ pub use iced_style::pane_grid::{Line, StyleSheet}; use crate::event::{self, Event}; use crate::layout; use crate::mouse; -use crate::overlay; +use crate::overlay::{self, Group}; use crate::renderer; use crate::touch; use crate::widget; @@ -450,14 +450,17 @@ where layout: Layout<'_>, renderer: &Renderer, ) -> Option> { - self.contents + let children = self + .contents .iter_mut() .zip(&mut tree.children) .zip(layout.children()) - .filter_map(|(((_, pane), tree), layout)| { - pane.overlay(tree, layout, renderer) + .filter_map(|(((_, content), state), layout)| { + content.overlay(state, layout, renderer) }) - .next() + .collect::>(); + + (!children.is_empty()).then(|| Group::with_children(children).overlay()) } } -- cgit From 3ab679725526bd095cc1a160705312b16c408b92 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Tue, 17 Jan 2023 11:12:10 -0800 Subject: New method to determine if overlay contains cursor This is needed for "container" overlay's such as `Group` which should only consider it's childrens layouts and not it's own when determining if the cursor is captured by the overlay. --- native/src/overlay.rs | 9 +++++++++ native/src/overlay/element.rs | 17 +++++++++++++++++ native/src/overlay/group.rs | 13 +++++++++++++ native/src/user_interface.rs | 11 +++++++++-- 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/native/src/overlay.rs b/native/src/overlay.rs index e7394494..16d8bb31 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -89,6 +89,15 @@ where ) -> mouse::Interaction { mouse::Interaction::Idle } + + /// Whether the [`Overlay`] contains the cursor + fn contains_cursor( + &self, + layout: Layout<'_>, + cursor_position: Point, + ) -> bool { + layout.bounds().contains(cursor_position) + } } /// Returns a [`Group`] of overlay [`Element`] children. diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index 41a8a597..125258c5 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -115,6 +115,15 @@ where ) { self.overlay.operate(layout, renderer, operation); } + + /// Whether the [`Overlay`] contains the cursor + pub fn contains_cursor( + &self, + layout: Layout<'_>, + cursor_position: Point, + ) -> bool { + self.overlay.contains_cursor(layout, cursor_position) + } } struct Map<'a, A, B, Renderer> { @@ -252,4 +261,12 @@ where self.content .draw(renderer, theme, style, layout, cursor_position) } + + fn contains_cursor( + &self, + layout: Layout<'_>, + cursor_position: Point, + ) -> bool { + self.content.contains_cursor(layout, cursor_position) + } } diff --git a/native/src/overlay/group.rs b/native/src/overlay/group.rs index f894f911..96d10c19 100644 --- a/native/src/overlay/group.rs +++ b/native/src/overlay/group.rs @@ -151,6 +151,19 @@ where ) }); } + + fn contains_cursor( + &self, + layout: Layout<'_>, + cursor_position: Point, + ) -> bool { + self.children + .iter() + .zip(layout.children()) + .any(|(child, layout)| { + child.contains_cursor(layout, cursor_position) + }) + } } impl<'a, Message, Renderer> From> diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 29cc3472..8659caa3 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -261,7 +261,11 @@ where } } - let base_cursor = if layout.bounds().contains(cursor_position) { + let base_cursor = if manual_overlay + .as_ref() + .unwrap() + .contains_cursor(Layout::new(&layout), cursor_position) + { // TODO: Type-safe cursor availability Point::new(-1.0, -1.0) } else { @@ -504,7 +508,10 @@ where ); }); - if overlay_bounds.contains(cursor_position) { + if overlay.contains_cursor( + Layout::new(layout), + cursor_position, + ) { overlay_interaction } else { base_interaction -- cgit From d470467718ecad0f37599a811bef846846dbb2b9 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Tue, 17 Jan 2023 17:10:58 -0800 Subject: Add toast example --- examples/toast/Cargo.toml | 10 + examples/toast/src/main.rs | 674 +++++++++++++++++++++++++++++++++++++++++++++ native/src/shell.rs | 5 + 3 files changed, 689 insertions(+) create mode 100644 examples/toast/Cargo.toml create mode 100644 examples/toast/src/main.rs diff --git a/examples/toast/Cargo.toml b/examples/toast/Cargo.toml new file mode 100644 index 00000000..f1f986aa --- /dev/null +++ b/examples/toast/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "toast" +version = "0.1.0" +authors = ["tarkah "] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", features = [] } +iced_native = { path = "../../native" } diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs new file mode 100644 index 00000000..11b55895 --- /dev/null +++ b/examples/toast/src/main.rs @@ -0,0 +1,674 @@ +use iced::widget::{ + self, button, column, container, pick_list, row, slider, text, text_input, +}; +use iced::{ + executor, keyboard, subscription, Alignment, Application, Command, Element, + Event, Length, Settings, Subscription, +}; + +use toast::{Status, Toast}; + +pub fn main() -> iced::Result { + App::run(Settings::default()) +} + +#[derive(Default)] +struct App { + toasts: Vec, + editing: Toast, + timeout_secs: u64, +} + +#[derive(Debug, Clone)] +#[allow(clippy::enum_variant_names)] +enum Message { + Add, + Close(usize), + Title(String), + Body(String), + Status(Status), + Timeout(f64), + Event(Event), +} + +impl Application for App { + type Executor = executor::Default; + type Message = Message; + type Theme = iced::Theme; + type Flags = (); + + fn new(_flags: ()) -> (Self, Command) { + ( + App { + toasts: vec![Toast { + title: "Example Toast".into(), + body: "Add more toasts in the form below!".into(), + status: Status::Primary, + }], + timeout_secs: toast::DEFAULT_TIMEOUT, + ..Default::default() + }, + Command::none(), + ) + } + + fn title(&self) -> String { + String::from("Toast - Iced") + } + + fn subscription(&self) -> Subscription { + subscription::events().map(Message::Event) + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Add => { + if !self.editing.title.is_empty() + && !self.editing.body.is_empty() + { + self.toasts.push(std::mem::take(&mut self.editing)); + } + Command::none() + } + Message::Close(index) => { + self.toasts.remove(index); + Command::none() + } + Message::Title(title) => { + self.editing.title = title; + Command::none() + } + Message::Body(body) => { + self.editing.body = body; + Command::none() + } + Message::Status(status) => { + self.editing.status = status; + Command::none() + } + Message::Timeout(timeout) => { + self.timeout_secs = timeout as u64; + Command::none() + } + Message::Event(Event::Keyboard(keyboard::Event::KeyPressed { + key_code: keyboard::KeyCode::Tab, + modifiers, + })) if modifiers.shift() => widget::focus_previous(), + Message::Event(Event::Keyboard(keyboard::Event::KeyPressed { + key_code: keyboard::KeyCode::Tab, + .. + })) => widget::focus_next(), + Message::Event(_) => Command::none(), + } + } + + fn view<'a>(&'a self) -> Element<'a, Message> { + let subtitle = |title, content: Element<'a, Message>| { + column![text(title).size(14), content] + .width(Length::Fill) + .spacing(5) + }; + + let mut add_toast = button("Add Toast"); + + if !self.editing.body.is_empty() && !self.editing.title.is_empty() { + add_toast = add_toast.on_press(Message::Add); + } + + let content = container( + column![ + subtitle( + "Title", + text_input("", &self.editing.title, Message::Title) + .on_submit(Message::Add) + .into() + ), + subtitle( + "Message", + text_input("", &self.editing.body, Message::Body) + .on_submit(Message::Add) + .into() + ), + subtitle( + "Status", + pick_list( + toast::Status::ALL, + Some(self.editing.status), + Message::Status + ) + .width(Length::Fill) + .into() + ), + subtitle( + "Timeout", + row![ + text(format!("{:0>2} sec", self.timeout_secs)), + slider( + 1.0..=30.0, + self.timeout_secs as f64, + Message::Timeout + ) + .step(1.0) + .width(Length::Fill) + ] + .spacing(5) + .into() + ), + column![add_toast] + .width(Length::Fill) + .align_items(Alignment::End) + ] + .spacing(10) + .max_width(200), + ) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y(); + + toast::Manager::new(content, &self.toasts, Message::Close) + .timeout(self.timeout_secs) + .into() + } +} + +mod toast { + use std::fmt; + use std::time::{Duration, Instant}; + + use iced::theme; + use iced::widget::{ + button, column, container, horizontal_rule, horizontal_space, row, text, + }; + use iced::{ + Alignment, Element, Length, Point, Rectangle, Renderer, Size, Theme, + Vector, + }; + use iced_native::widget::{tree, Operation, Tree}; + use iced_native::{event, layout, mouse, overlay, renderer, window}; + use iced_native::{Clipboard, Event, Layout, Shell, Widget}; + + pub const DEFAULT_TIMEOUT: u64 = 5; + + #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] + pub enum Status { + #[default] + Primary, + Secondary, + Success, + Danger, + } + + impl Status { + pub const ALL: &[Self] = + &[Self::Primary, Self::Secondary, Self::Success, Self::Danger]; + } + + impl container::StyleSheet for Status { + type Style = Theme; + + fn appearance(&self, theme: &Theme) -> container::Appearance { + let palette = theme.extended_palette(); + + let pair = match self { + Status::Primary => palette.primary.weak, + Status::Secondary => palette.secondary.weak, + Status::Success => palette.success.weak, + Status::Danger => palette.danger.weak, + }; + + container::Appearance { + background: pair.color.into(), + text_color: pair.text.into(), + ..Default::default() + } + } + } + + impl fmt::Display for Status { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Status::Primary => "Primary", + Status::Secondary => "Secondary", + Status::Success => "Success", + Status::Danger => "Danger", + } + .fmt(f) + } + } + + #[derive(Debug, Clone, Default)] + pub struct Toast { + pub title: String, + pub body: String, + pub status: Status, + } + + pub struct Manager<'a, Message> { + content: Element<'a, Message>, + toasts: Vec>, + timeout_secs: u64, + on_close: Box Message + 'a>, + } + + impl<'a, Message> Manager<'a, Message> + where + Message: 'a + Clone, + { + pub fn new( + content: impl Into>, + toasts: &'a [Toast], + on_close: impl Fn(usize) -> Message + 'a, + ) -> Self { + let toasts = toasts + .iter() + .enumerate() + .map(|(index, toast)| { + container(column![ + container( + row![ + text(toast.title.as_str()), + horizontal_space(Length::Fill), + button("X") + .on_press((on_close)(index)) + .padding(3), + ] + .align_items(Alignment::Center) + ) + .width(Length::Fill) + .padding(5) + .style( + theme::Container::Custom(Box::new(toast.status)) + ), + horizontal_rule(1), + container(text(toast.body.as_str())) + .width(Length::Fill) + .padding(5) + .style(theme::Container::Box), + ]) + .max_width(200) + .into() + }) + .collect(); + + Self { + content: content.into(), + toasts, + timeout_secs: DEFAULT_TIMEOUT, + on_close: Box::new(on_close), + } + } + + pub fn timeout(self, seconds: u64) -> Self { + Self { + timeout_secs: seconds, + ..self + } + } + } + + impl<'a, Message> Widget for Manager<'a, Message> { + fn width(&self) -> Length { + self.content.as_widget().width() + } + + fn height(&self) -> Length { + self.content.as_widget().height() + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + self.content.as_widget().layout(renderer, limits) + } + + fn tag(&self) -> tree::Tag { + struct Marker(Vec); + iced_native::widget::tree::Tag::of::() + } + + fn state(&self) -> tree::State { + iced_native::widget::tree::State::new(Vec::>::new()) + } + + fn children(&self) -> Vec { + std::iter::once(Tree::new(&self.content)) + .chain(self.toasts.iter().map(Tree::new)) + .collect() + } + + fn diff(&self, tree: &mut Tree) { + let instants = tree.state.downcast_mut::>>(); + + // Invalidating removed instants to None allows us to remove + // them here so that diffing for removed / new toast instants + // is accurate + instants.retain(Option::is_some); + + match (instants.len(), self.toasts.len()) { + (old, new) if old > new => { + instants.truncate(new); + } + (old, new) if old < new => { + instants.extend( + std::iter::repeat(Some(Instant::now())).take(new - old), + ); + } + _ => {} + } + + tree.diff_children( + &std::iter::once(&self.content) + .chain(self.toasts.iter()) + .collect::>(), + ); + } + + fn operate( + &self, + state: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn Operation, + ) { + operation.container(None, &mut |operation| { + self.content.as_widget().operate( + &mut state.children[0], + layout, + renderer, + operation, + ); + }); + } + + fn on_event( + &mut self, + state: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.content.as_widget_mut().on_event( + &mut state.children[0], + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + } + + fn draw( + &self, + state: &Tree, + renderer: &mut Renderer, + theme: &Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + self.content.as_widget().draw( + &state.children[0], + renderer, + theme, + style, + layout, + cursor_position, + viewport, + ); + } + + fn mouse_interaction( + &self, + state: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.content.as_widget().mouse_interaction( + &state.children[0], + layout, + cursor_position, + viewport, + renderer, + ) + } + + fn overlay<'b>( + &'b mut self, + state: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + let instants = state.state.downcast_mut::>>(); + + let (content_state, toasts_state) = state.children.split_at_mut(1); + + let content = self.content.as_widget_mut().overlay( + &mut content_state[0], + layout, + renderer, + ); + + let toasts = (!self.toasts.is_empty()).then(|| { + overlay::Element::new( + layout.bounds().position(), + Box::new(Overlay { + toasts: &mut self.toasts, + state: toasts_state, + instants, + on_close: &self.on_close, + timeout_secs: self.timeout_secs, + }), + ) + }); + let overlays = + content.into_iter().chain(toasts).collect::>(); + + (!overlays.is_empty()) + .then(|| overlay::Group::with_children(overlays).overlay()) + } + } + + struct Overlay<'a, 'b, Message> { + toasts: &'b mut [Element<'a, Message>], + state: &'b mut [Tree], + instants: &'b mut [Option], + on_close: &'b dyn Fn(usize) -> Message, + timeout_secs: u64, + } + + impl<'a, 'b, Message> overlay::Overlay + for Overlay<'a, 'b, Message> + { + fn layout( + &self, + renderer: &Renderer, + bounds: Size, + position: Point, + ) -> layout::Node { + let limits = layout::Limits::new(Size::ZERO, bounds) + .width(Length::Fill) + .height(Length::Fill); + + layout::flex::resolve( + layout::flex::Axis::Vertical, + renderer, + &limits, + 10.into(), + 10.0, + Alignment::End, + self.toasts, + ) + .translate(Vector::new(position.x, position.y)) + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + if let Event::Window(window::Event::RedrawRequested(now)) = &event { + let mut next_redraw: Option = None; + + self.instants.iter_mut().enumerate().for_each( + |(index, maybe_instant)| { + if let Some(instant) = maybe_instant.as_mut() { + let remaining = + Duration::from_secs(self.timeout_secs) + .saturating_sub(instant.elapsed()); + + if remaining == Duration::ZERO { + maybe_instant.take(); + shell.publish((self.on_close)(index)); + next_redraw = + Some(window::RedrawRequest::NextFrame); + } else { + let redraw_at = + window::RedrawRequest::At(*now + remaining); + next_redraw = next_redraw + .map(|redraw| redraw.min(redraw_at)) + .or(Some(redraw_at)); + } + } + }, + ); + + if let Some(redraw) = next_redraw { + shell.request_redraw(redraw); + } + } + + self.toasts + .iter_mut() + .zip(self.state.iter_mut()) + .zip(layout.children()) + .zip(self.instants.iter_mut()) + .map(|(((child, state), layout), instant)| { + let mut local_messages = vec![]; + let mut local_shell = Shell::new(&mut local_messages); + + let status = child.as_widget_mut().on_event( + state, + event.clone(), + layout, + cursor_position, + renderer, + clipboard, + &mut local_shell, + ); + + if !local_shell.is_empty() { + instant.take(); + } + + shell.merge(local_shell, std::convert::identity); + + status + }) + .fold(event::Status::Ignored, event::Status::merge) + } + + fn draw( + &self, + renderer: &mut Renderer, + theme: &::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + ) { + let viewport = layout.bounds(); + + for ((child, state), layout) in self + .toasts + .iter() + .zip(self.state.iter()) + .zip(layout.children()) + { + child.as_widget().draw( + state, + renderer, + theme, + style, + layout, + cursor_position, + &viewport, + ); + } + } + + fn operate( + &mut self, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn iced_native::widget::Operation, + ) { + operation.container(None, &mut |operation| { + self.toasts + .iter() + .zip(self.state.iter_mut()) + .zip(layout.children()) + .for_each(|((child, state), layout)| { + child + .as_widget() + .operate(state, layout, renderer, operation); + }) + }); + } + + fn mouse_interaction( + &self, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.toasts + .iter() + .zip(self.state.iter()) + .zip(layout.children()) + .map(|((child, state), layout)| { + child.as_widget().mouse_interaction( + state, + layout, + cursor_position, + viewport, + renderer, + ) + }) + .max() + .unwrap_or_default() + } + + fn contains_cursor( + &self, + layout: Layout<'_>, + cursor_position: Point, + ) -> bool { + layout + .children() + .any(|layout| layout.bounds().contains(cursor_position)) + } + } + + impl<'a, Message> From> for Element<'a, Message> + where + Message: 'a, + { + fn from(manager: Manager<'a, Message>) -> Self { + Element::new(manager) + } + } +} diff --git a/native/src/shell.rs b/native/src/shell.rs index f1ddb48e..74a5c616 100644 --- a/native/src/shell.rs +++ b/native/src/shell.rs @@ -25,6 +25,11 @@ impl<'a, Message> Shell<'a, Message> { } } + /// Returns true if the [`Shell`] contains no published messages + pub fn is_empty(&self) -> bool { + self.messages.is_empty() + } + /// Publish the given `Message` for an application to process it. pub fn publish(&mut self, message: Message) { self.messages.push(message); -- cgit From be860508a9deed1f4583e045790eb9ddd74d07d5 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Tue, 17 Jan 2023 17:20:53 -0800 Subject: Rename method to is_over --- examples/toast/src/main.rs | 6 +----- native/src/overlay.rs | 8 ++------ native/src/overlay/element.rs | 18 +++++------------- native/src/overlay/group.rs | 10 ++-------- native/src/user_interface.rs | 8 +++----- 5 files changed, 13 insertions(+), 37 deletions(-) diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 11b55895..e74b3ee6 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -652,11 +652,7 @@ mod toast { .unwrap_or_default() } - fn contains_cursor( - &self, - layout: Layout<'_>, - cursor_position: Point, - ) -> bool { + fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { layout .children() .any(|layout| layout.bounds().contains(cursor_position)) diff --git a/native/src/overlay.rs b/native/src/overlay.rs index 16d8bb31..1c3d0fb9 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -90,12 +90,8 @@ where mouse::Interaction::Idle } - /// Whether the [`Overlay`] contains the cursor - fn contains_cursor( - &self, - layout: Layout<'_>, - cursor_position: Point, - ) -> bool { + /// Returns true if the cursor is over the [`Overlay`] + fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { layout.bounds().contains(cursor_position) } } diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index 125258c5..edeb7dbf 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -116,13 +116,9 @@ where self.overlay.operate(layout, renderer, operation); } - /// Whether the [`Overlay`] contains the cursor - pub fn contains_cursor( - &self, - layout: Layout<'_>, - cursor_position: Point, - ) -> bool { - self.overlay.contains_cursor(layout, cursor_position) + /// Returns true if the cursor is over the [`Element`] + pub fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { + self.overlay.is_over(layout, cursor_position) } } @@ -262,11 +258,7 @@ where .draw(renderer, theme, style, layout, cursor_position) } - fn contains_cursor( - &self, - layout: Layout<'_>, - cursor_position: Point, - ) -> bool { - self.content.contains_cursor(layout, cursor_position) + fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { + self.content.is_over(layout, cursor_position) } } diff --git a/native/src/overlay/group.rs b/native/src/overlay/group.rs index 96d10c19..fa3c7396 100644 --- a/native/src/overlay/group.rs +++ b/native/src/overlay/group.rs @@ -152,17 +152,11 @@ where }); } - fn contains_cursor( - &self, - layout: Layout<'_>, - cursor_position: Point, - ) -> bool { + fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { self.children .iter() .zip(layout.children()) - .any(|(child, layout)| { - child.contains_cursor(layout, cursor_position) - }) + .any(|(child, layout)| child.is_over(layout, cursor_position)) } } diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 8659caa3..0def730c 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -264,7 +264,7 @@ where let base_cursor = if manual_overlay .as_ref() .unwrap() - .contains_cursor(Layout::new(&layout), cursor_position) + .is_over(Layout::new(&layout), cursor_position) { // TODO: Type-safe cursor availability Point::new(-1.0, -1.0) @@ -508,10 +508,8 @@ where ); }); - if overlay.contains_cursor( - Layout::new(layout), - cursor_position, - ) { + if overlay.is_over(Layout::new(layout), cursor_position) + { overlay_interaction } else { base_interaction -- cgit From e80c7dff8d1c6ac40084bbaaece57844443c807c Mon Sep 17 00:00:00 2001 From: Jedsek Date: Thu, 19 Jan 2023 19:09:45 +0800 Subject: Update preset.rs --- examples/game_of_life/src/preset.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/examples/game_of_life/src/preset.rs b/examples/game_of_life/src/preset.rs index 964b9120..fef10a97 100644 --- a/examples/game_of_life/src/preset.rs +++ b/examples/game_of_life/src/preset.rs @@ -1,6 +1,7 @@ -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub enum Preset { Custom, + [default] Xkcd, Glider, SmallExploder, @@ -114,12 +115,6 @@ impl Preset { } } -impl Default for Preset { - fn default() -> Preset { - Preset::Xkcd - } -} - impl std::fmt::Display for Preset { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( -- cgit From 8acf7c41fc11d95056e6a4823b616afbcb2b99d0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Thu, 19 Jan 2023 20:20:13 +0100 Subject: Fix `#[default]` in `preset` for `game_of_life` example --- examples/game_of_life/src/preset.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/game_of_life/src/preset.rs b/examples/game_of_life/src/preset.rs index fef10a97..552527b1 100644 --- a/examples/game_of_life/src/preset.rs +++ b/examples/game_of_life/src/preset.rs @@ -1,7 +1,7 @@ #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub enum Preset { Custom, - [default] + #[default] Xkcd, Glider, SmallExploder, -- cgit From 01c484245be54c1aeb6605659fb0f222856c28da Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 24 Jan 2023 01:59:34 +0100 Subject: Fix some minor documentation inconsistencies --- native/src/overlay.rs | 5 ++++- native/src/overlay/element.rs | 2 +- native/src/overlay/group.rs | 6 +++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/native/src/overlay.rs b/native/src/overlay.rs index 1c3d0fb9..6cada416 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -90,7 +90,10 @@ where mouse::Interaction::Idle } - /// Returns true if the cursor is over the [`Overlay`] + /// Returns true if the cursor is over the [`Overlay`]. + /// + /// By default, it returns true if the bounds of the `layout` contain + /// the `cursor_position`. fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { layout.bounds().contains(cursor_position) } diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index edeb7dbf..bdf7766e 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -116,7 +116,7 @@ where self.overlay.operate(layout, renderer, operation); } - /// Returns true if the cursor is over the [`Element`] + /// Returns true if the cursor is over the [`Element`]. pub fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { self.overlay.is_over(layout, cursor_position) } diff --git a/native/src/overlay/group.rs b/native/src/overlay/group.rs index fa3c7396..5c9cf809 100644 --- a/native/src/overlay/group.rs +++ b/native/src/overlay/group.rs @@ -8,8 +8,8 @@ use crate::renderer; use crate::widget; use crate::{Clipboard, Event, Layout, Overlay, Shell}; -/// An [`Overlay`] container that displays multiple overlay -/// [`overlay::Element`] children +/// An [`Overlay`] container that displays multiple overlay [`overlay::Element`] +/// children. #[allow(missing_debug_implementations)] pub struct Group<'a, Message, Renderer> { children: Vec>, @@ -41,7 +41,7 @@ where self } - /// Turns the [`Group`] into an overlay [`overlay::Element`] + /// Turns the [`Group`] into an overlay [`overlay::Element`]. pub fn overlay(self) -> overlay::Element<'a, Message, Renderer> { overlay::Element::new(Point::ORIGIN, Box::new(self)) } -- cgit From 2e4aefa7fc19e4d66152332d28baafe1349c1636 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 26 Jan 2023 16:10:45 -0800 Subject: Annotate `Command` and `Subscription` with `#[must_use]` Calling a function returning one of these types without using it is almost certainly a mistake. Luckily Rust's `#[must_use]` can help warn about this. --- futures/src/command.rs | 1 + futures/src/subscription.rs | 1 + native/src/command.rs | 1 + 3 files changed, 3 insertions(+) diff --git a/futures/src/command.rs b/futures/src/command.rs index 05c3a1d0..3d1ec3f9 100644 --- a/futures/src/command.rs +++ b/futures/src/command.rs @@ -1,4 +1,5 @@ /// A set of asynchronous actions to be performed by some runtime. +#[must_use = "`Command` must be returned to runtime to take effect"] #[derive(Debug)] pub struct Command(Internal); diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs index e96fa704..d18ec4f7 100644 --- a/futures/src/subscription.rs +++ b/futures/src/subscription.rs @@ -20,6 +20,7 @@ use crate::BoxStream; /// `Hasher`. /// /// [`Command`]: crate::Command +#[must_use = "`Subscription` must be returned to runtime to take effect"] pub struct Subscription { recipes: Vec>>, } diff --git a/native/src/command.rs b/native/src/command.rs index 89ee7375..ca9d0b64 100644 --- a/native/src/command.rs +++ b/native/src/command.rs @@ -11,6 +11,7 @@ use std::fmt; use std::future::Future; /// A set of asynchronous actions to be performed by some runtime. +#[must_use = "`Command` must be returned to runtime to take effect"] pub struct Command(iced_futures::Command>); impl Command { -- cgit From d2008eed47ced61937fafbec19978291397389e0 Mon Sep 17 00:00:00 2001 From: frey Date: Thu, 26 Jan 2023 20:39:47 -0600 Subject: Mapped operations is missing text_input()... This fixes a bug where some operations could be dropped. --- native/src/widget/action.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/native/src/widget/action.rs b/native/src/widget/action.rs index 1e21ff38..3f1b6b6c 100644 --- a/native/src/widget/action.rs +++ b/native/src/widget/action.rs @@ -1,4 +1,6 @@ -use crate::widget::operation::{self, Focusable, Operation, Scrollable}; +use crate::widget::operation::{ + self, Focusable, Operation, Scrollable, TextInput, +}; use crate::widget::Id; use iced_futures::MaybeSend; @@ -86,6 +88,14 @@ where self.operation.focusable(state, id); } + fn text_input( + &mut self, + state: &mut dyn TextInput, + id: Option<&Id>, + ) { + self.operation.text_input(state, id); + } + fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) { self.operation.custom(state, id); } -- cgit From 5bc37f10e2a325c543860d21b658734a590dd725 Mon Sep 17 00:00:00 2001 From: Casper Storm Date: Fri, 27 Jan 2023 10:21:57 +0100 Subject: Fixed a small pixel width issue on `pick_list` --- native/src/overlay/menu.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 099b1a97..9e37380f 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -281,10 +281,7 @@ where renderer.fill_quad( renderer::Quad { - bounds: Rectangle { - width: bounds.width - 1.0, - ..bounds - }, + bounds, border_color: appearance.border_color, border_width: appearance.border_width, border_radius: appearance.border_radius.into(), -- cgit From 0ff1061d52d4ecb35018c8aab173225fe14d1e25 Mon Sep 17 00:00:00 2001 From: 13r0ck Date: Fri, 27 Jan 2023 13:16:26 -0700 Subject: Fix: Clippy lint 'let_underscore_future' --- futures/src/backend/native/async_std.rs | 1 + futures/src/backend/native/tokio.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/futures/src/backend/native/async_std.rs b/futures/src/backend/native/async_std.rs index e8641626..b324dbf1 100644 --- a/futures/src/backend/native/async_std.rs +++ b/futures/src/backend/native/async_std.rs @@ -10,6 +10,7 @@ impl crate::Executor for Executor { Ok(Self) } + #[allow(clippy::let_underscore_future)] fn spawn(&self, future: impl Future + Send + 'static) { let _ = async_std::task::spawn(future); } diff --git a/futures/src/backend/native/tokio.rs b/futures/src/backend/native/tokio.rs index f86b0ea3..dd818bd1 100644 --- a/futures/src/backend/native/tokio.rs +++ b/futures/src/backend/native/tokio.rs @@ -9,6 +9,7 @@ impl crate::Executor for Executor { tokio::runtime::Runtime::new() } + #[allow(clippy::let_underscore_future)] fn spawn(&self, future: impl Future + Send + 'static) { let _ = tokio::runtime::Runtime::spawn(self, future); } -- cgit From e6092e81a42efe0bbaaad2e7ce475c25a379e672 Mon Sep 17 00:00:00 2001 From: 13r0ck Date: Fri, 27 Jan 2023 13:37:32 -0700 Subject: Fix: Clippy lint 'needless_lifetimes' --- graphics/src/renderer.rs | 4 ++-- native/src/renderer.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index aabdf7fc..298cf4a1 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -53,9 +53,9 @@ where { type Theme = T; - fn layout<'a, Message>( + fn layout( &mut self, - element: &Element<'a, Message, Self>, + element: &Element<'_, Message, Self>, limits: &layout::Limits, ) -> layout::Node { let layout = element.as_widget().layout(self, limits); diff --git a/native/src/renderer.rs b/native/src/renderer.rs index d5329acd..2ac78982 100644 --- a/native/src/renderer.rs +++ b/native/src/renderer.rs @@ -16,9 +16,9 @@ pub trait Renderer: Sized { /// /// You should override this if you need to perform any operations before or /// after layouting. For instance, trimming the measurements cache. - fn layout<'a, Message>( + fn layout( &mut self, - element: &Element<'a, Message, Self>, + element: &Element<'_, Message, Self>, limits: &layout::Limits, ) -> layout::Node { element.as_widget().layout(self, limits) -- cgit From 42b1bfe66d2407901c1ae640f80ce9e0d3c64b60 Mon Sep 17 00:00:00 2001 From: 13r0ck Date: Fri, 27 Jan 2023 13:25:04 -0700 Subject: Fix: Clippy lint 'uninlined_format_args' --- examples/download_progress/src/main.rs | 2 +- examples/events/src/main.rs | 2 +- examples/game_of_life/src/main.rs | 2 +- examples/integration_opengl/src/controls.rs | 2 +- examples/integration_opengl/src/scene.rs | 2 +- examples/integration_wgpu/src/controls.rs | 2 +- examples/integration_wgpu/src/main.rs | 2 +- examples/pokedex/src/main.rs | 8 +++----- examples/styling/src/main.rs | 2 +- examples/system_information/src/main.rs | 5 ++--- examples/todos/src/main.rs | 2 +- examples/tour/src/main.rs | 8 ++++---- examples/websocket/src/echo.rs | 2 +- examples/websocket/src/echo/server.rs | 2 +- glow/src/program.rs | 2 +- native/src/command/action.rs | 6 +++--- native/src/debug/basic.rs | 8 ++++---- native/src/image.rs | 4 ++-- native/src/svg.rs | 2 +- native/src/widget/operation.rs | 2 +- native/src/window/action.rs | 11 +++++------ src/window/icon.rs | 15 ++++++--------- wgpu/src/settings.rs | 2 +- winit/src/application.rs | 2 +- winit/src/application/profiler.rs | 4 ++-- 25 files changed, 47 insertions(+), 54 deletions(-) diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index 3ef9ef7a..001a1f8f 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -177,7 +177,7 @@ impl Download { .into() } State::Downloading { .. } => { - text(format!("Downloading... {:.2}%", current_progress)).into() + text(format!("Downloading... {current_progress:.2}%")).into() } State::Errored => column![ "Something went wrong :(", diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index 4ae8d6fb..0e583479 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -77,7 +77,7 @@ impl Application for Events { let events = Column::with_children( self.last .iter() - .map(|event| text(format!("{:?}", event)).size(40)) + .map(|event| text(format!("{event:?}")).size(40)) .map(Element::from) .collect(), ); diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index b0f1c96d..ed911160 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -176,7 +176,7 @@ fn view_controls<'a>( let speed_controls = row![ slider(1.0..=1000.0, speed as f32, Message::SpeedChanged), - text(format!("x{}", speed)).size(16), + text(format!("x{speed}")).size(16), ] .width(Length::Fill) .align_items(Alignment::Center) diff --git a/examples/integration_opengl/src/controls.rs b/examples/integration_opengl/src/controls.rs index 076d37d3..22c41066 100644 --- a/examples/integration_opengl/src/controls.rs +++ b/examples/integration_opengl/src/controls.rs @@ -90,7 +90,7 @@ impl Program for Controls { ) .push(sliders) .push( - Text::new(format!("{:?}", background_color)) + Text::new(format!("{background_color:?}")) .size(14) .style(Color::WHITE), ), diff --git a/examples/integration_opengl/src/scene.rs b/examples/integration_opengl/src/scene.rs index fc74b78a..c1d05b65 100644 --- a/examples/integration_opengl/src/scene.rs +++ b/examples/integration_opengl/src/scene.rs @@ -49,7 +49,7 @@ impl Scene { .expect("Cannot create shader"); gl.shader_source( shader, - &format!("{}\n{}", shader_version, shader_source), + &format!("{shader_version}\n{shader_source}"), ); gl.compile_shader(shader); if !gl.get_shader_compile_status(shader) { diff --git a/examples/integration_wgpu/src/controls.rs b/examples/integration_wgpu/src/controls.rs index 6c41738c..92300a45 100644 --- a/examples/integration_wgpu/src/controls.rs +++ b/examples/integration_wgpu/src/controls.rs @@ -96,7 +96,7 @@ impl Program for Controls { ) .push(sliders) .push( - Text::new(format!("{:?}", background_color)) + Text::new(format!("{background_color:?}")) .size(14) .style(Color::WHITE), ) diff --git a/examples/integration_wgpu/src/main.rs b/examples/integration_wgpu/src/main.rs index 70f9a48b..2a56b6fa 100644 --- a/examples/integration_wgpu/src/main.rs +++ b/examples/integration_wgpu/src/main.rs @@ -274,7 +274,7 @@ pub fn main() { } Err(error) => match error { wgpu::SurfaceError::OutOfMemory => { - panic!("Swapchain error: {}. Rendering cannot continue.", error) + panic!("Swapchain error: {error}. Rendering cannot continue.") } _ => { // Try rendering again next frame. diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs index 4fe2d07c..748acae0 100644 --- a/examples/pokedex/src/main.rs +++ b/examples/pokedex/src/main.rs @@ -41,7 +41,7 @@ impl Application for Pokedex { Pokedex::Errored { .. } => "Whoops!", }; - format!("{} - Pokédex", subtitle) + format!("{subtitle} - Pokédex") } fn update(&mut self, message: Message) -> Command { @@ -157,8 +157,7 @@ impl Pokemon { }; let fetch_entry = async { - let url = - format!("https://pokeapi.co/api/v2/pokemon-species/{}", id); + let url = format!("https://pokeapi.co/api/v2/pokemon-species/{id}"); reqwest::get(&url).await?.json().await }; @@ -187,8 +186,7 @@ impl Pokemon { async fn fetch_image(id: u16) -> Result { let url = format!( - "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{}.png", - id + "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{id}.png" ); #[cfg(not(target_arch = "wasm32"))] diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs index e16860ad..49bedce7 100644 --- a/examples/styling/src/main.rs +++ b/examples/styling/src/main.rs @@ -78,7 +78,7 @@ impl Sandbox for Styling { column![text("Choose a theme:")].spacing(10), |column, theme| { column.push(radio( - format!("{:?}", theme), + format!("{theme:?}"), *theme, Some(match self.theme { Theme::Light => ThemeType::Light, diff --git a/examples/system_information/src/main.rs b/examples/system_information/src/main.rs index 175b4387..633b6e2b 100644 --- a/examples/system_information/src/main.rs +++ b/examples/system_information/src/main.rs @@ -114,13 +114,12 @@ impl Application for Example { { let memory_readable = ByteSize::kb(memory_used).to_string(); - format!("{} kb ({})", memory_used, memory_readable) + format!("{memory_used} kb ({memory_readable})") } else { String::from("None") }; - let memory_used = - text(format!("Memory (used): {}", memory_text)); + let memory_used = text(format!("Memory (used): {memory_text}")); let graphics_adapter = text(format!( "Graphics adapter: {}", diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 690d9c09..04411ed7 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -303,7 +303,7 @@ pub enum TaskMessage { impl Task { fn text_input_id(i: usize) -> text_input::Id { - text_input::Id::new(format!("task-{}", i)) + text_input::Id::new(format!("task-{i}")) } fn new(description: String) -> Self { diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 378508e1..5ee65562 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -388,7 +388,7 @@ impl<'a> Step { let spacing_section = column![ slider(0..=80, spacing, StepMessage::SpacingChanged), - text(format!("{} px", spacing)) + text(format!("{spacing} px")) .width(Length::Fill) .horizontal_alignment(alignment::Horizontal::Center), ] @@ -412,7 +412,7 @@ impl<'a> Step { fn text(size: u16, color: Color) -> Column<'a, StepMessage> { let size_section = column![ "You can change its size:", - text(format!("This text is {} pixels", size)).size(size), + text(format!("This text is {size} pixels")).size(size), slider(10..=70, size, StepMessage::TextSizeChanged), ] .padding(20) @@ -427,7 +427,7 @@ impl<'a> Step { let color_section = column![ "And its color:", - text(format!("{:?}", color)).style(color), + text(format!("{color:?}")).style(color), color_sliders, ] .padding(20) @@ -497,7 +497,7 @@ impl<'a> Step { .push(ferris(width)) .push(slider(100..=500, width, StepMessage::ImageWidthChanged)) .push( - text(format!("Width: {} px", width)) + text(format!("Width: {width} px")) .width(Length::Fill) .horizontal_alignment(alignment::Horizontal::Center), ) diff --git a/examples/websocket/src/echo.rs b/examples/websocket/src/echo.rs index ae65e064..e74768a6 100644 --- a/examples/websocket/src/echo.rs +++ b/examples/websocket/src/echo.rs @@ -141,7 +141,7 @@ impl fmt::Display for Message { Message::Disconnected => { write!(f, "Connection lost... Retrying...") } - Message::User(message) => write!(f, "{}", message), + Message::User(message) => write!(f, "{message}"), } } } diff --git a/examples/websocket/src/echo/server.rs b/examples/websocket/src/echo/server.rs index fef89a12..dd234984 100644 --- a/examples/websocket/src/echo/server.rs +++ b/examples/websocket/src/echo/server.rs @@ -41,7 +41,7 @@ async fn user_connected(ws: WebSocket) { tokio::task::spawn(async move { while let Some(message) = rx.next().await { user_ws_tx.send(message).await.unwrap_or_else(|e| { - eprintln!("websocket send error: {}", e); + eprintln!("websocket send error: {e}"); }); } }); diff --git a/glow/src/program.rs b/glow/src/program.rs index 1eb9c535..e2155222 100644 --- a/glow/src/program.rs +++ b/glow/src/program.rs @@ -54,7 +54,7 @@ impl Version { String::from("#version 120\n#define in varying"), ), // OpenGL 1.1+ - _ => panic!("Incompatible context version: {:?}", version), + _ => panic!("Incompatible context version: {version:?}"), }; log::info!("Shader directive: {}", vertex.lines().next().unwrap()); diff --git a/native/src/command/action.rs b/native/src/command/action.rs index a6954f8f..a51b8c21 100644 --- a/native/src/command/action.rs +++ b/native/src/command/action.rs @@ -58,10 +58,10 @@ impl fmt::Debug for Action { match self { Self::Future(_) => write!(f, "Action::Future"), Self::Clipboard(action) => { - write!(f, "Action::Clipboard({:?})", action) + write!(f, "Action::Clipboard({action:?})") } - Self::Window(action) => write!(f, "Action::Window({:?})", action), - Self::System(action) => write!(f, "Action::System({:?})", action), + Self::Window(action) => write!(f, "Action::Window({action:?})"), + Self::System(action) => write!(f, "Action::System({action:?})"), Self::Widget(_action) => write!(f, "Action::Widget"), } } diff --git a/native/src/debug/basic.rs b/native/src/debug/basic.rs index 603f2fd5..92f614da 100644 --- a/native/src/debug/basic.rs +++ b/native/src/debug/basic.rs @@ -133,7 +133,7 @@ impl Debug { } pub fn log_message(&mut self, message: &Message) { - self.last_messages.push_back(format!("{:?}", message)); + self.last_messages.push_back(format!("{message:?}")); if self.last_messages.len() > 10 { let _ = self.last_messages.pop_front(); @@ -150,7 +150,7 @@ impl Debug { let mut lines = Vec::new(); fn key_value(key: &str, value: T) -> String { - format!("{} {:?}", key, value) + format!("{key} {value:?}") } lines.push(format!( @@ -176,9 +176,9 @@ impl Debug { lines.push(String::from("Last messages:")); lines.extend(self.last_messages.iter().map(|msg| { if msg.len() <= 100 { - format!(" {}", msg) + format!(" {msg}") } else { - format!(" {:.100}...", msg) + format!(" {msg:.100}...") } })); diff --git a/native/src/image.rs b/native/src/image.rs index 06fd7ae6..5d2843c9 100644 --- a/native/src/image.rs +++ b/native/src/image.rs @@ -107,10 +107,10 @@ pub enum Data { impl std::fmt::Debug for Data { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Data::Path(path) => write!(f, "Path({:?})", path), + Data::Path(path) => write!(f, "Path({path:?})"), Data::Bytes(_) => write!(f, "Bytes(...)"), Data::Rgba { width, height, .. } => { - write!(f, "Pixels({} * {})", width, height) + write!(f, "Pixels({width} * {height})") } } } diff --git a/native/src/svg.rs b/native/src/svg.rs index 2168e409..9b98877a 100644 --- a/native/src/svg.rs +++ b/native/src/svg.rs @@ -71,7 +71,7 @@ pub enum Data { impl std::fmt::Debug for Data { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Data::Path(path) => write!(f, "Path({:?})", path), + Data::Path(path) => write!(f, "Path({path:?})"), Data::Bytes(_) => write!(f, "Bytes(...)"), } } diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs index 73e6a6b0..53688a21 100644 --- a/native/src/widget/operation.rs +++ b/native/src/widget/operation.rs @@ -62,7 +62,7 @@ where fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::None => write!(f, "Outcome::None"), - Self::Some(output) => write!(f, "Outcome::Some({:?})", output), + Self::Some(output) => write!(f, "Outcome::Some({output:?})"), Self::Chain(_) => write!(f, "Outcome::Chain(...)"), } } diff --git a/native/src/window/action.rs b/native/src/window/action.rs index 37fcc273..525434e4 100644 --- a/native/src/window/action.rs +++ b/native/src/window/action.rs @@ -106,15 +106,14 @@ impl fmt::Debug for Action { Self::Drag => write!(f, "Action::Drag"), Self::Resize { width, height } => write!( f, - "Action::Resize {{ widget: {}, height: {} }}", - width, height + "Action::Resize {{ widget: {width}, height: {height} }}" ), - Self::Maximize(value) => write!(f, "Action::Maximize({})", value), - Self::Minimize(value) => write!(f, "Action::Minimize({}", value), + Self::Maximize(value) => write!(f, "Action::Maximize({value})"), + Self::Minimize(value) => write!(f, "Action::Minimize({value}"), Self::Move { x, y } => { - write!(f, "Action::Move {{ x: {}, y: {} }}", x, y) + write!(f, "Action::Move {{ x: {x}, y: {y} }}") } - Self::SetMode(mode) => write!(f, "Action::SetMode({:?})", mode), + Self::SetMode(mode) => write!(f, "Action::SetMode({mode:?})"), Self::FetchMode(_) => write!(f, "Action::FetchMode"), Self::ToggleMaximize => write!(f, "Action::ToggleMaximize"), Self::ToggleDecorations => write!(f, "Action::ToggleDecorations"), diff --git a/src/window/icon.rs b/src/window/icon.rs index bacad41a..d57eb79c 100644 --- a/src/window/icon.rs +++ b/src/window/icon.rs @@ -133,10 +133,9 @@ impl fmt::Display for Error { Error::InvalidData { byte_count } => { write!( f, - "The provided RGBA data (with length {:?}) isn't divisble by \ + "The provided RGBA data (with length {byte_count:?}) isn't divisble by \ 4. Therefore, it cannot be safely interpreted as 32bpp RGBA \ - pixels.", - byte_count, + pixels." ) } Error::DimensionsMismatch { @@ -146,20 +145,18 @@ impl fmt::Display for Error { } => { write!( f, - "The number of RGBA pixels ({:?}) does not match the provided \ - dimensions ({:?}x{:?}).", - pixel_count, width, height, + "The number of RGBA pixels ({pixel_count:?}) does not match the provided \ + dimensions ({width:?}x{height:?})." ) } Error::OsError(e) => write!( f, "The underlying OS failed to create the window \ - icon: {:?}", - e + icon: {e:?}" ), #[cfg(feature = "image_rs")] Error::ImageError(e) => { - write!(f, "Unable to create icon from a file: {:?}", e) + write!(f, "Unable to create icon from a file: {e:?}") } } } diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index 7bc752ff..21c2427d 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -83,7 +83,7 @@ fn backend_from_env() -> Option { "gl" => wgpu::Backends::GL, "webgpu" => wgpu::Backends::BROWSER_WEBGPU, "primary" => wgpu::Backends::PRIMARY, - other => panic!("Unknown backend: {}", other), + other => panic!("Unknown backend: {other}"), } }) } diff --git a/winit/src/application.rs b/winit/src/application.rs index 77ca4b31..8c841533 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -535,7 +535,7 @@ async fn run_instance( Err(error) => match error { // This is an unrecoverable error. compositor::SurfaceError::OutOfMemory => { - panic!("{:?}", error); + panic!("{error:?}"); } _ => { debug.render_finished(); diff --git a/winit/src/application/profiler.rs b/winit/src/application/profiler.rs index 23eaa390..7031507a 100644 --- a/winit/src/application/profiler.rs +++ b/winit/src/application/profiler.rs @@ -49,8 +49,8 @@ impl Profiler { .to_str() .unwrap_or("trace"); - let path = out_dir - .join(format!("{}_trace_{}.json", curr_exe_name, time)); + let path = + out_dir.join(format!("{curr_exe_name}_trace_{time}.json")); layer = layer.file(path); } else { -- cgit From 7356f2b68d6716699990fb9c6e053debcbf781ae Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sat, 28 Jan 2023 18:41:33 -0800 Subject: Refactor image draw to standalone function --- native/src/widget/image.rs | 68 +++++++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 8bd8ca1e..3ff06a76 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -111,6 +111,45 @@ where layout::Node::new(final_size) } +/// Draws an [`Image`] +pub fn draw( + renderer: &mut Renderer, + layout: Layout<'_>, + handle: &Handle, + content_fit: ContentFit, +) where + Renderer: image::Renderer, + Handle: Clone + Hash, +{ + let Size { width, height } = renderer.dimensions(handle); + let image_size = Size::new(width as f32, height as f32); + + let bounds = layout.bounds(); + let adjusted_fit = content_fit.fit(image_size, bounds.size()); + + let render = |renderer: &mut Renderer| { + let offset = Vector::new( + (bounds.width - adjusted_fit.width).max(0.0) / 2.0, + (bounds.height - adjusted_fit.height).max(0.0) / 2.0, + ); + + let drawing_bounds = Rectangle { + width: adjusted_fit.width, + height: adjusted_fit.height, + ..bounds + }; + + renderer.draw(handle.clone(), drawing_bounds + offset) + }; + + if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height + { + renderer.with_layer(bounds, render); + } else { + render(renderer) + } +} + impl Widget for Image where Renderer: image::Renderer, @@ -149,34 +188,7 @@ where _cursor_position: Point, _viewport: &Rectangle, ) { - let Size { width, height } = renderer.dimensions(&self.handle); - let image_size = Size::new(width as f32, height as f32); - - let bounds = layout.bounds(); - let adjusted_fit = self.content_fit.fit(image_size, bounds.size()); - - let render = |renderer: &mut Renderer| { - let offset = Vector::new( - (bounds.width - adjusted_fit.width).max(0.0) / 2.0, - (bounds.height - adjusted_fit.height).max(0.0) / 2.0, - ); - - let drawing_bounds = Rectangle { - width: adjusted_fit.width, - height: adjusted_fit.height, - ..bounds - }; - - renderer.draw(self.handle.clone(), drawing_bounds + offset) - }; - - if adjusted_fit.width > bounds.width - || adjusted_fit.height > bounds.height - { - renderer.with_layer(bounds, render); - } else { - render(renderer) - } + draw(renderer, layout, &self.handle, self.content_fit) } } -- cgit From a3bfa0724c5e7fca72231f751619901c71007b13 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 30 Jan 2023 03:41:03 +0100 Subject: Fix widget-driven animations for `Component` --- lazy/src/component.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lazy/src/component.rs b/lazy/src/component.rs index d8f21f8a..d3c52df5 100644 --- a/lazy/src/component.rs +++ b/lazy/src/component.rs @@ -227,6 +227,10 @@ where local_shell.revalidate_layout(|| shell.invalidate_layout()); + if let Some(redraw_request) = local_shell.redraw_request() { + shell.request_redraw(redraw_request); + } + if !local_messages.is_empty() { let mut heads = self.state.take().unwrap().into_heads(); -- cgit From a50cc32d09ddff1d061701074908c28d5c5509ba Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 30 Jan 2023 05:01:28 +0100 Subject: Fix layout translation in `overlay::Group` This bug produced improper positioning of overlays of elements inside a `Scrollable`. --- core/src/vector.rs | 5 +++++ lazy/src/component.rs | 4 ++-- lazy/src/lazy.rs | 4 ++-- lazy/src/responsive.rs | 4 ++-- native/src/overlay/element.rs | 10 ++++++++-- native/src/overlay/group.rs | 6 ++++-- native/src/user_interface.rs | 18 ++++++++++-------- 7 files changed, 33 insertions(+), 18 deletions(-) diff --git a/core/src/vector.rs b/core/src/vector.rs index b550869c..1380c3b3 100644 --- a/core/src/vector.rs +++ b/core/src/vector.rs @@ -15,6 +15,11 @@ impl Vector { } } +impl Vector { + /// The zero [`Vector`]. + pub const ZERO: Self = Self::new(0.0, 0.0); +} + impl std::ops::Add for Vector where T: std::ops::Add, diff --git a/lazy/src/component.rs b/lazy/src/component.rs index d3c52df5..94263274 100644 --- a/lazy/src/component.rs +++ b/lazy/src/component.rs @@ -455,9 +455,9 @@ where position: Point, ) -> layout::Node { self.with_overlay_maybe(|overlay| { - let vector = position - overlay.position(); + let translation = position - overlay.position(); - overlay.layout(renderer, bounds).translate(vector) + overlay.layout(renderer, bounds, translation) }) .unwrap_or_default() } diff --git a/lazy/src/lazy.rs b/lazy/src/lazy.rs index 933def96..9795afa4 100644 --- a/lazy/src/lazy.rs +++ b/lazy/src/lazy.rs @@ -313,9 +313,9 @@ where position: Point, ) -> layout::Node { self.with_overlay_maybe(|overlay| { - let vector = position - overlay.position(); + let translation = position - overlay.position(); - overlay.layout(renderer, bounds).translate(vector) + overlay.layout(renderer, bounds, translation) }) .unwrap_or_default() } diff --git a/lazy/src/responsive.rs b/lazy/src/responsive.rs index 52badda2..e399e7b0 100644 --- a/lazy/src/responsive.rs +++ b/lazy/src/responsive.rs @@ -356,9 +356,9 @@ where position: Point, ) -> layout::Node { self.with_overlay_maybe(|overlay| { - let vector = position - overlay.position(); + let translation = position - overlay.position(); - overlay.layout(renderer, bounds).translate(vector) + overlay.layout(renderer, bounds, translation) }) .unwrap_or_default() } diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index bdf7766e..237d25d1 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -53,8 +53,14 @@ where } /// Computes the layout of the [`Element`] in the given bounds. - pub fn layout(&self, renderer: &Renderer, bounds: Size) -> layout::Node { - self.overlay.layout(renderer, bounds, self.position) + pub fn layout( + &self, + renderer: &Renderer, + bounds: Size, + translation: Vector, + ) -> layout::Node { + self.overlay + .layout(renderer, bounds, self.position + translation) } /// Processes a runtime [`Event`]. diff --git a/native/src/overlay/group.rs b/native/src/overlay/group.rs index 5c9cf809..1126f0cf 100644 --- a/native/src/overlay/group.rs +++ b/native/src/overlay/group.rs @@ -66,13 +66,15 @@ where &self, renderer: &Renderer, bounds: Size, - _position: Point, + position: Point, ) -> layout::Node { + let translation = position - Point::ORIGIN; + layout::Node::with_children( bounds, self.children .iter() - .map(|child| child.layout(renderer, bounds)) + .map(|child| child.layout(renderer, bounds, translation)) .collect(), ) } diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 0def730c..80dece21 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -6,7 +6,9 @@ use crate::mouse; use crate::renderer; use crate::widget; use crate::window; -use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; +use crate::{ + Clipboard, Element, Layout, Point, Rectangle, Shell, Size, Vector, +}; /// A set of interactive graphical elements with a specific [`Layout`]. /// @@ -203,7 +205,7 @@ where let bounds = self.bounds; let mut overlay = manual_overlay.as_mut().unwrap(); - let mut layout = overlay.layout(renderer, bounds); + let mut layout = overlay.layout(renderer, bounds, Vector::ZERO); let mut event_statuses = Vec::new(); for event in events.iter().cloned() { @@ -252,7 +254,7 @@ where overlay = manual_overlay.as_mut().unwrap(); shell.revalidate_layout(|| { - layout = overlay.layout(renderer, bounds); + layout = overlay.layout(renderer, bounds, Vector::ZERO); }); } @@ -434,10 +436,9 @@ where .as_widget_mut() .overlay(&mut self.state, Layout::new(&self.base), renderer) { - let overlay_layout = self - .overlay - .take() - .unwrap_or_else(|| overlay.layout(renderer, self.bounds)); + let overlay_layout = self.overlay.take().unwrap_or_else(|| { + overlay.layout(renderer, self.bounds, Vector::ZERO) + }); let new_cursor_position = if overlay_layout.bounds().contains(cursor_position) { @@ -538,7 +539,8 @@ where renderer, ) { if self.overlay.is_none() { - self.overlay = Some(overlay.layout(renderer, self.bounds)); + self.overlay = + Some(overlay.layout(renderer, self.bounds, Vector::ZERO)); } overlay.operate( -- cgit From ecc5bfaeff6503e9e0752035c5e0c94c5a5263a2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 31 Jan 2023 04:04:29 +0100 Subject: Improve consistency of `window::Action` --- native/src/window/action.rs | 17 +++++++++-------- winit/src/application.rs | 2 +- winit/src/window.rs | 10 +++++----- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/native/src/window/action.rs b/native/src/window/action.rs index 525434e4..d277649b 100644 --- a/native/src/window/action.rs +++ b/native/src/window/action.rs @@ -33,18 +33,19 @@ pub enum Action { /// The new logical y location of the window y: i32, }, - /// Set the [`Mode`] of the window. - SetMode(Mode), + /// Change the [`Mode`] of the window. + ChangeMode(Mode), /// Fetch the current [`Mode`] of the window. FetchMode(Box T + 'static>), - /// Sets the window to maximized or back + /// Toggle the window to maximized or back ToggleMaximize, - /// Toggles whether window has decorations + /// Toggle whether window has decorations. + /// /// ## Platform-specific /// - **X11:** Not implemented. /// - **Web:** Unsupported. ToggleDecorations, - /// Requests user attention to the window, this has no effect if the application + /// Request user attention to the window, this has no effect if the application /// is already focused. How requesting for user attention manifests is platform dependent, /// see [`UserAttentionType`] for details. /// @@ -58,7 +59,7 @@ pub enum Action { /// - **X11:** Requests for user attention must be manually cleared. /// - **Wayland:** Requires `xdg_activation_v1` protocol, `None` has no effect. RequestUserAttention(Option), - /// Brings the window to the front and sets input focus. Has no effect if the window is + /// Bring the window to the front and sets input focus. Has no effect if the window is /// already in focus, minimized, or not visible. /// /// This method steals input focus from other applications. Do not use this method unless @@ -87,7 +88,7 @@ impl Action { Self::Maximize(bool) => Action::Maximize(bool), Self::Minimize(bool) => Action::Minimize(bool), Self::Move { x, y } => Action::Move { x, y }, - Self::SetMode(mode) => Action::SetMode(mode), + Self::ChangeMode(mode) => Action::ChangeMode(mode), Self::FetchMode(o) => Action::FetchMode(Box::new(move |s| f(o(s)))), Self::ToggleMaximize => Action::ToggleMaximize, Self::ToggleDecorations => Action::ToggleDecorations, @@ -113,7 +114,7 @@ impl fmt::Debug for Action { Self::Move { x, y } => { write!(f, "Action::Move {{ x: {x}, y: {y} }}") } - Self::SetMode(mode) => write!(f, "Action::SetMode({mode:?})"), + Self::ChangeMode(mode) => write!(f, "Action::SetMode({mode:?})"), Self::FetchMode(_) => write!(f, "Action::FetchMode"), Self::ToggleMaximize => write!(f, "Action::ToggleMaximize"), Self::ToggleDecorations => write!(f, "Action::ToggleDecorations"), diff --git a/winit/src/application.rs b/winit/src/application.rs index 8c841533..c1836ed9 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -749,7 +749,7 @@ pub fn run_command( y, }); } - window::Action::SetMode(mode) => { + window::Action::ChangeMode(mode) => { window.set_visible(conversion::visible(mode)); window.set_fullscreen(conversion::fullscreen( window.primary_monitor(), diff --git a/winit/src/window.rs b/winit/src/window.rs index 2306bdf1..0685c87c 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -22,12 +22,12 @@ pub fn resize(width: u32, height: u32) -> Command { })) } -/// Sets the window to maximized or back. +/// Maximizes the window. pub fn maximize(value: bool) -> Command { Command::single(command::Action::Window(window::Action::Maximize(value))) } -/// Set the window to minimized or back. +/// Minimes the window. pub fn minimize(value: bool) -> Command { Command::single(command::Action::Window(window::Action::Minimize(value))) } @@ -38,11 +38,11 @@ pub fn move_to(x: i32, y: i32) -> Command { } /// Sets the [`Mode`] of the window. -pub fn set_mode(mode: Mode) -> Command { - Command::single(command::Action::Window(window::Action::SetMode(mode))) +pub fn change_mode(mode: Mode) -> Command { + Command::single(command::Action::Window(window::Action::ChangeMode(mode))) } -/// Sets the window to maximized or back. +/// Toggles the window to maximized or back. pub fn toggle_maximize() -> Command { Command::single(command::Action::Window(window::Action::ToggleMaximize)) } -- cgit From 98a717383acf71d7939d7cc90d350743487f0380 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 31 Jan 2023 04:08:19 +0100 Subject: Write missing `window::Action` helpers in `window` --- native/src/window/action.rs | 2 +- winit/src/window.rs | 39 ++++++++++++++++++++++++++++++++++----- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/native/src/window/action.rs b/native/src/window/action.rs index d277649b..168974bc 100644 --- a/native/src/window/action.rs +++ b/native/src/window/action.rs @@ -47,7 +47,7 @@ pub enum Action { ToggleDecorations, /// Request user attention to the window, this has no effect if the application /// is already focused. How requesting for user attention manifests is platform dependent, - /// see [`UserAttentionType`] for details. + /// see [`UserAttention`] for details. /// /// Providing `None` will unset the request for user attention. Unsetting the request for /// user attention might not be done automatically by the WM when the window receives input. diff --git a/winit/src/window.rs b/winit/src/window.rs index 0685c87c..6e3a383a 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -42,11 +42,6 @@ pub fn change_mode(mode: Mode) -> Command { Command::single(command::Action::Window(window::Action::ChangeMode(mode))) } -/// Toggles the window to maximized or back. -pub fn toggle_maximize() -> Command { - Command::single(command::Action::Window(window::Action::ToggleMaximize)) -} - /// Fetches the current [`Mode`] of the window. pub fn fetch_mode( f: impl FnOnce(Mode) -> Message + 'static, @@ -55,3 +50,37 @@ pub fn fetch_mode( Box::new(f), ))) } + +/// Toggles the window to maximized or back. +pub fn toggle_maximize() -> Command { + Command::single(command::Action::Window(window::Action::ToggleMaximize)) +} + +/// Toggles the window decorations. +pub fn toggle_decorations() -> Command { + Command::single(command::Action::Window(window::Action::ToggleDecorations)) +} + +/// Request user attention to the window, this has no effect if the application +/// is already focused. How requesting for user attention manifests is platform dependent, +/// see [`UserAttention`] for details. +/// +/// Providing `None` will unset the request for user attention. Unsetting the request for +/// user attention might not be done automatically by the WM when the window receives input. +pub fn request_user_attention( + user_attention: Option, +) -> Command { + Command::single(command::Action::Window( + window::Action::RequestUserAttention(user_attention), + )) +} + +/// Brings the window to the front and sets input focus. Has no effect if the window is +/// already in focus, minimized, or not visible. +/// +/// This [`Command`] steals input focus from other applications. Do not use this method unless +/// you are certain that's what the user wants. Focus stealing can cause an extremely disruptive +/// user experience. +pub fn gain_focus() -> Command { + Command::single(command::Action::Window(window::Action::GainFocus)) +} -- cgit From c7d8467c46e519b28f8f630061e7d55fb3cd3f8b Mon Sep 17 00:00:00 2001 From: sushigiri <117967760+sushigiri@users.noreply.github.com> Date: Mon, 6 Feb 2023 09:05:07 -0700 Subject: Accept FnOnce instead of Fn in canvas cache draw Use FnOnce in `draw` function signature instead of `Fn`, permitting the use of iterators and other one-time functions. --- graphics/src/widget/canvas/cache.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/graphics/src/widget/canvas/cache.rs b/graphics/src/widget/canvas/cache.rs index 49873ac9..52217bbb 100644 --- a/graphics/src/widget/canvas/cache.rs +++ b/graphics/src/widget/canvas/cache.rs @@ -49,7 +49,11 @@ impl Cache { /// Otherwise, the previously stored [`Geometry`] will be returned. The /// [`Cache`] is not cleared in this case. In other words, it will keep /// returning the stored [`Geometry`] if needed. - pub fn draw(&self, bounds: Size, draw_fn: impl Fn(&mut Frame)) -> Geometry { + pub fn draw( + &self, + bounds: Size, + draw_fn: impl FnOnce(&mut Frame), + ) -> Geometry { use std::ops::Deref; if let State::Filled { -- cgit From 92ba26b8a168b1d58b8330d84753c5ab7e116f17 Mon Sep 17 00:00:00 2001 From: Yoo Dongryul Date: Wed, 8 Feb 2023 05:20:46 +0900 Subject: Resize images on README.md (#1659) * Resize images on README.md * hotfix: for mobile layout * fix: set height of both images equally --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b3790478..8ebd0e9c 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,10 @@ A cross-platform GUI library for Rust focused on simplicity and type-safety. Inspired by [Elm]. - + - + -- cgit From 9506fb1181e60e78d92b6f0712d385371966e07f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 11 Feb 2023 03:06:42 +0100 Subject: Hide window until `Renderer` has been initialized --- winit/src/application.rs | 20 +++++++++++++++----- winit/src/settings.rs | 3 +-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/winit/src/application.rs b/winit/src/application.rs index c1836ed9..769fe9dd 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -147,11 +147,15 @@ where #[cfg(target_arch = "wasm32")] let target = settings.window.platform_specific.target.clone(); - let builder = settings.window.into_builder( - &application.title(), - event_loop.primary_monitor(), - settings.id, - ); + let should_be_visible = settings.window.visible; + let builder = settings + .window + .into_builder( + &application.title(), + event_loop.primary_monitor(), + settings.id, + ) + .with_visible(false); log::info!("Window builder: {:#?}", builder); @@ -202,6 +206,7 @@ where control_sender, init_command, window, + should_be_visible, settings.exit_on_close_request, ); @@ -268,6 +273,7 @@ async fn run_instance( mut control_sender: mpsc::UnboundedSender, init_command: Command, window: winit::window::Window, + should_be_visible: bool, exit_on_close_request: bool, ) where A: Application + 'static, @@ -295,6 +301,10 @@ async fn run_instance( physical_size.height, ); + if should_be_visible { + window.set_visible(true); + } + run_command( &application, &mut cache, diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 9bbdef5c..45f38833 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -114,8 +114,7 @@ impl Window { .with_decorations(self.decorations) .with_transparent(self.transparent) .with_window_icon(self.icon) - .with_always_on_top(self.always_on_top) - .with_visible(self.visible); + .with_always_on_top(self.always_on_top); if let Some(position) = conversion::position( primary_monitor.as_ref(), -- cgit From 8fe851057df0df113aaac6b8dc067b3a75d18d65 Mon Sep 17 00:00:00 2001 From: Nick Senger Date: Fri, 10 Feb 2023 12:08:22 -0800 Subject: fix: lazy widgets overlay is_over --- lazy/src/component.rs | 7 +++++++ lazy/src/lazy.rs | 7 +++++++ lazy/src/responsive.rs | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/lazy/src/component.rs b/lazy/src/component.rs index 94263274..4c03e2a3 100644 --- a/lazy/src/component.rs +++ b/lazy/src/component.rs @@ -563,4 +563,11 @@ where event_status } + + fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { + self.with_overlay_maybe(|overlay| { + overlay.is_over(layout, cursor_position) + }) + .unwrap_or_default() + } } diff --git a/lazy/src/lazy.rs b/lazy/src/lazy.rs index 9795afa4..5e909a49 100644 --- a/lazy/src/lazy.rs +++ b/lazy/src/lazy.rs @@ -372,6 +372,13 @@ where }) .unwrap_or(iced_native::event::Status::Ignored) } + + fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { + self.with_overlay_maybe(|overlay| { + overlay.is_over(layout, cursor_position) + }) + .unwrap_or_default() + } } impl<'a, Message, Renderer, Dependency, View> diff --git a/lazy/src/responsive.rs b/lazy/src/responsive.rs index e399e7b0..93069493 100644 --- a/lazy/src/responsive.rs +++ b/lazy/src/responsive.rs @@ -415,4 +415,11 @@ where }) .unwrap_or(iced_native::event::Status::Ignored) } + + fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { + self.with_overlay_maybe(|overlay| { + overlay.is_over(layout, cursor_position) + }) + .unwrap_or_default() + } } -- cgit From 41822d0dc55a68d3b515e76a6bc6d2accdc611f4 Mon Sep 17 00:00:00 2001 From: Nick Senger Date: Sat, 11 Feb 2023 09:11:00 -0800 Subject: fix: panic when overlay event processing removes overlay --- native/src/user_interface.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 80dece21..2358bff1 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -263,16 +263,16 @@ where } } - let base_cursor = if manual_overlay + let base_cursor = manual_overlay .as_ref() - .unwrap() - .is_over(Layout::new(&layout), cursor_position) - { - // TODO: Type-safe cursor availability - Point::new(-1.0, -1.0) - } else { - cursor_position - }; + .filter(|overlay| { + overlay.is_over(Layout::new(&layout), cursor_position) + }) + .map(|_| { + // TODO: Type-safe cursor availability + Point::new(-1.0, -1.0) + }) + .unwrap_or(cursor_position); self.overlay = Some(layout); -- cgit From 2201f33c65e8bf85265ee1e996b36cbc5b43257e Mon Sep 17 00:00:00 2001 From: Nick Senger Date: Sun, 12 Feb 2023 11:07:08 -0800 Subject: fix: diff widget sub-tree after rebuilding component with operation --- lazy/src/component.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lazy/src/component.rs b/lazy/src/component.rs index 94263274..e29ccca8 100644 --- a/lazy/src/component.rs +++ b/lazy/src/component.rs @@ -311,6 +311,8 @@ where } self.with_element(|element| { + tree.diff_children(std::slice::from_ref(&element)); + element.as_widget().operate( &mut tree.children[0], layout, -- cgit From efaa80fb4429e7f9bd2f8a1161be3fa66a3e9e32 Mon Sep 17 00:00:00 2001 From: Casper Storm Date: Thu, 26 Jan 2023 12:20:43 +0100 Subject: Extend pick_list::Handle --- native/src/widget/pick_list.rs | 82 ++++++++++++++++++++++++++++++++++-------- src/widget.rs | 4 ++- 2 files changed, 71 insertions(+), 15 deletions(-) diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index c2853314..862c4157 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -20,6 +20,43 @@ use std::borrow::Cow; pub use iced_style::pick_list::{Appearance, StyleSheet}; +/// The content of the [`Handle`]. +#[derive(Clone)] +pub struct HandleContent +where + Renderer: text::Renderer, +{ + /// Font that will be used to display the `text`, + pub font: Renderer::Font, + /// Text that will be shown. + pub text: String, + /// Font size of the content. + pub size: Option, +} + +impl std::fmt::Debug for HandleContent +where + Renderer: text::Renderer, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("HandleIcon") + .field("text", &self.text) + .field("size", &self.size) + .finish() + } +} + +impl PartialEq for HandleContent +where + Renderer: text::Renderer, +{ + fn eq(&self, other: &Self) -> bool { + self.text.eq(&other.text) && self.size.eq(&other.size) + } +} + +impl Eq for HandleContent where Renderer: text::Renderer {} + /// The handle to the right side of the [`PickList`]. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Handle @@ -33,14 +70,14 @@ where /// Font size of the content. size: Option, }, - /// A custom handle. - Custom { - /// Font that will be used to display the `text`, - font: Renderer::Font, - /// Text that will be shown. - text: String, - /// Font size of the content. - size: Option, + /// A custom static handle. + Static(HandleContent), + /// A custom dynamic handle. + Dynamic { + /// The [`HandleContent`] used when [`PickList`] is closed. + closed: HandleContent, + /// The [`HandleContent`] used when [`PickList`] is open. + open: HandleContent, }, /// No handle will be shown. None, @@ -59,16 +96,30 @@ impl Handle where Renderer: text::Renderer, { - fn content(&self) -> Option<(Renderer::Font, String, Option)> { + fn content( + &self, + is_open: bool, + ) -> Option<(Renderer::Font, String, Option)> { match self { Self::Arrow { size } => Some(( Renderer::ICON_FONT, Renderer::ARROW_DOWN_ICON.to_string(), *size, )), - Self::Custom { font, text, size } => { + Self::Static(HandleContent { font, text, size }) => { Some((font.clone(), text.clone(), *size)) } + Self::Dynamic { open, closed } => { + if is_open { + Some((open.font.clone(), open.text.clone(), open.size)) + } else { + Some(( + closed.font.clone(), + closed.text.clone(), + closed.size, + )) + } + } Self::None => None, } } @@ -258,7 +309,7 @@ where fn draw( &self, - _tree: &Tree, + tree: &Tree, renderer: &mut Renderer, theme: &Renderer::Theme, _style: &renderer::Style, @@ -278,6 +329,7 @@ where self.selected.as_ref(), &self.handle, &self.style, + || tree.state.downcast_ref::>(), ) } @@ -568,7 +620,7 @@ where } /// Draws a [`PickList`]. -pub fn draw( +pub fn draw<'a, T, Renderer>( renderer: &mut Renderer, theme: &Renderer::Theme, layout: Layout<'_>, @@ -580,11 +632,13 @@ pub fn draw( selected: Option<&T>, handle: &Handle, style: &::Style, + state: impl FnOnce() -> &'a State, ) where Renderer: text::Renderer, Renderer::Theme: StyleSheet, - T: ToString, + T: ToString + 'a, { + let state = state(); let bounds = layout.bounds(); let is_mouse_over = bounds.contains(cursor_position); let is_selected = selected.is_some(); @@ -605,7 +659,7 @@ pub fn draw( style.background, ); - if let Some((font, text, size)) = handle.content() { + if let Some((font, text, size)) = handle.content(state.is_open) { let size = f32::from(size.unwrap_or_else(|| renderer.default_size())); renderer.fill_text(Text { diff --git a/src/widget.rs b/src/widget.rs index f0058f57..00edd763 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -80,7 +80,9 @@ pub mod pane_grid { pub mod pick_list { //! Display a dropdown list of selectable values. - pub use iced_native::widget::pick_list::{Appearance, Handle, StyleSheet}; + pub use iced_native::widget::pick_list::{ + Appearance, Handle, HandleContent, StyleSheet, + }; /// A widget allowing the selection of a single value from a list of options. pub type PickList<'a, T, Message, Renderer = crate::Renderer> = -- cgit From 5569e12149f9c29345fe404552ce156ef0bebf0e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Feb 2023 06:56:59 +0100 Subject: Rename `HandleContent` to `Icon` and simplify generics --- native/src/widget/pick_list.rs | 74 ++++++++++++------------------------------ src/widget.rs | 2 +- 2 files changed, 21 insertions(+), 55 deletions(-) diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 862c4157..17fb00d5 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -20,49 +20,20 @@ use std::borrow::Cow; pub use iced_style::pick_list::{Appearance, StyleSheet}; -/// The content of the [`Handle`]. -#[derive(Clone)] -pub struct HandleContent -where - Renderer: text::Renderer, -{ +/// The icon of a [`Handle`]. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Icon { /// Font that will be used to display the `text`, - pub font: Renderer::Font, + pub font: Font, /// Text that will be shown. pub text: String, /// Font size of the content. pub size: Option, } -impl std::fmt::Debug for HandleContent -where - Renderer: text::Renderer, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("HandleIcon") - .field("text", &self.text) - .field("size", &self.size) - .finish() - } -} - -impl PartialEq for HandleContent -where - Renderer: text::Renderer, -{ - fn eq(&self, other: &Self) -> bool { - self.text.eq(&other.text) && self.size.eq(&other.size) - } -} - -impl Eq for HandleContent where Renderer: text::Renderer {} - /// The handle to the right side of the [`PickList`]. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum Handle -where - Renderer: text::Renderer, -{ +pub enum Handle { /// Displays an arrow icon (▼). /// /// This is the default. @@ -71,42 +42,36 @@ where size: Option, }, /// A custom static handle. - Static(HandleContent), + Static(Icon), /// A custom dynamic handle. Dynamic { - /// The [`HandleContent`] used when [`PickList`] is closed. - closed: HandleContent, - /// The [`HandleContent`] used when [`PickList`] is open. - open: HandleContent, + /// The [`Icon`] used when [`PickList`] is closed. + closed: Icon, + /// The [`Icon`] used when [`PickList`] is open. + open: Icon, }, /// No handle will be shown. None, } -impl Default for Handle -where - Renderer: text::Renderer, -{ +impl Default for Handle { fn default() -> Self { Self::Arrow { size: None } } } -impl Handle -where - Renderer: text::Renderer, -{ - fn content( +impl Handle { + fn content>( &self, is_open: bool, - ) -> Option<(Renderer::Font, String, Option)> { + ) -> Option<(Font, String, Option)> { match self { Self::Arrow { size } => Some(( Renderer::ICON_FONT, Renderer::ARROW_DOWN_ICON.to_string(), *size, )), - Self::Static(HandleContent { font, text, size }) => { + Self::Static(Icon { font, text, size }) => { Some((font.clone(), text.clone(), *size)) } Self::Dynamic { open, closed } => { @@ -141,7 +106,7 @@ where padding: Padding, text_size: Option, font: Renderer::Font, - handle: Handle, + handle: Handle, style: ::Style, } @@ -212,7 +177,7 @@ where } /// Sets the [`Handle`] of the [`PickList`]. - pub fn handle(mut self, handle: Handle) -> Self { + pub fn handle(mut self, handle: Handle) -> Self { self.handle = handle; self } @@ -630,7 +595,7 @@ pub fn draw<'a, T, Renderer>( font: &Renderer::Font, placeholder: Option<&str>, selected: Option<&T>, - handle: &Handle, + handle: &Handle, style: &::Style, state: impl FnOnce() -> &'a State, ) where @@ -659,7 +624,8 @@ pub fn draw<'a, T, Renderer>( style.background, ); - if let Some((font, text, size)) = handle.content(state.is_open) { + if let Some((font, text, size)) = handle.content::(state.is_open) + { let size = f32::from(size.unwrap_or_else(|| renderer.default_size())); renderer.fill_text(Text { diff --git a/src/widget.rs b/src/widget.rs index 00edd763..5bf7b6b4 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -81,7 +81,7 @@ pub mod pane_grid { pub mod pick_list { //! Display a dropdown list of selectable values. pub use iced_native::widget::pick_list::{ - Appearance, Handle, HandleContent, StyleSheet, + Appearance, Handle, Icon, StyleSheet, }; /// A widget allowing the selection of a single value from a list of options. -- cgit From 0272cac89e2bb3e037d0d9d5caa8b7c584386417 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Feb 2023 06:59:37 +0100 Subject: Move `Handle` and `Icon` definitions in `pick_list` --- native/src/widget/pick_list.rs | 140 ++++++++++++++++++++--------------------- 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 17fb00d5..b9f6f088 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -20,76 +20,6 @@ use std::borrow::Cow; pub use iced_style::pick_list::{Appearance, StyleSheet}; -/// The icon of a [`Handle`]. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Icon { - /// Font that will be used to display the `text`, - pub font: Font, - /// Text that will be shown. - pub text: String, - /// Font size of the content. - pub size: Option, -} - -/// The handle to the right side of the [`PickList`]. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Handle { - /// Displays an arrow icon (▼). - /// - /// This is the default. - Arrow { - /// Font size of the content. - size: Option, - }, - /// A custom static handle. - Static(Icon), - /// A custom dynamic handle. - Dynamic { - /// The [`Icon`] used when [`PickList`] is closed. - closed: Icon, - /// The [`Icon`] used when [`PickList`] is open. - open: Icon, - }, - /// No handle will be shown. - None, -} - -impl Default for Handle { - fn default() -> Self { - Self::Arrow { size: None } - } -} - -impl Handle { - fn content>( - &self, - is_open: bool, - ) -> Option<(Font, String, Option)> { - match self { - Self::Arrow { size } => Some(( - Renderer::ICON_FONT, - Renderer::ARROW_DOWN_ICON.to_string(), - *size, - )), - Self::Static(Icon { font, text, size }) => { - Some((font.clone(), text.clone(), *size)) - } - Self::Dynamic { open, closed } => { - if is_open { - Some((open.font.clone(), open.text.clone(), open.size)) - } else { - Some(( - closed.font.clone(), - closed.text.clone(), - closed.size, - )) - } - } - Self::None => None, - } - } -} - /// A widget for selecting a single value from a list of options. #[allow(missing_debug_implementations)] pub struct PickList<'a, T, Message, Renderer> @@ -366,6 +296,76 @@ impl Default for State { } } +/// The handle to the right side of the [`PickList`]. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Handle { + /// Displays an arrow icon (▼). + /// + /// This is the default. + Arrow { + /// Font size of the content. + size: Option, + }, + /// A custom static handle. + Static(Icon), + /// A custom dynamic handle. + Dynamic { + /// The [`Icon`] used when [`PickList`] is closed. + closed: Icon, + /// The [`Icon`] used when [`PickList`] is open. + open: Icon, + }, + /// No handle will be shown. + None, +} + +impl Default for Handle { + fn default() -> Self { + Self::Arrow { size: None } + } +} + +impl Handle { + fn content>( + &self, + is_open: bool, + ) -> Option<(Font, String, Option)> { + match self { + Self::Arrow { size } => Some(( + Renderer::ICON_FONT, + Renderer::ARROW_DOWN_ICON.to_string(), + *size, + )), + Self::Static(Icon { font, text, size }) => { + Some((font.clone(), text.clone(), *size)) + } + Self::Dynamic { open, closed } => { + if is_open { + Some((open.font.clone(), open.text.clone(), open.size)) + } else { + Some(( + closed.font.clone(), + closed.text.clone(), + closed.size, + )) + } + } + Self::None => None, + } + } +} + +/// The icon of a [`Handle`]. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Icon { + /// Font that will be used to display the `text`, + pub font: Font, + /// Text that will be shown. + pub text: String, + /// Font size of the content. + pub size: Option, +} + /// Computes the layout of a [`PickList`]. pub fn layout( renderer: &Renderer, -- cgit From bbff06b4621ae586b951564c8cc4bf95608bbb81 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Feb 2023 07:02:33 +0100 Subject: Use `char` instead of `String` for `pick_list::Icon` --- native/src/widget/pick_list.rs | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index b9f6f088..b96ffac2 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -329,25 +329,21 @@ impl Handle { fn content>( &self, is_open: bool, - ) -> Option<(Font, String, Option)> { + ) -> Option<(Font, char, Option)> { match self { - Self::Arrow { size } => Some(( - Renderer::ICON_FONT, - Renderer::ARROW_DOWN_ICON.to_string(), - *size, - )), - Self::Static(Icon { font, text, size }) => { - Some((font.clone(), text.clone(), *size)) + Self::Arrow { size } => { + Some((Renderer::ICON_FONT, Renderer::ARROW_DOWN_ICON, *size)) } + Self::Static(Icon { + font, + code_point, + size, + }) => Some((font.clone(), *code_point, *size)), Self::Dynamic { open, closed } => { if is_open { - Some((open.font.clone(), open.text.clone(), open.size)) + Some((open.font.clone(), open.code_point, open.size)) } else { - Some(( - closed.font.clone(), - closed.text.clone(), - closed.size, - )) + Some((closed.font.clone(), closed.code_point, closed.size)) } } Self::None => None, @@ -358,10 +354,10 @@ impl Handle { /// The icon of a [`Handle`]. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Icon { - /// Font that will be used to display the `text`, + /// Font that will be used to display the `code_point`, pub font: Font, - /// Text that will be shown. - pub text: String, + /// The unicode code point that will be used as the icon. + pub code_point: char, /// Font size of the content. pub size: Option, } @@ -624,12 +620,13 @@ pub fn draw<'a, T, Renderer>( style.background, ); - if let Some((font, text, size)) = handle.content::(state.is_open) + if let Some((font, code_point, size)) = + handle.content::(state.is_open) { let size = f32::from(size.unwrap_or_else(|| renderer.default_size())); renderer.fill_text(Text { - content: &text, + content: &code_point.to_string(), size, font, color: style.handle_color, -- cgit From fee1ab69e2bab01e5d736540be8b253ff62c3e5e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Feb 2023 07:05:18 +0100 Subject: Provide `State` reference instead of closure to `pick_list::draw` --- native/src/widget/pick_list.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index b96ffac2..8189dd61 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -224,7 +224,7 @@ where self.selected.as_ref(), &self.handle, &self.style, - || tree.state.downcast_ref::>(), + tree.state.downcast_ref::>(), ) } @@ -593,13 +593,12 @@ pub fn draw<'a, T, Renderer>( selected: Option<&T>, handle: &Handle, style: &::Style, - state: impl FnOnce() -> &'a State, + state: &State, ) where Renderer: text::Renderer, Renderer::Theme: StyleSheet, T: ToString + 'a, { - let state = state(); let bounds = layout.bounds(); let is_mouse_over = bounds.contains(cursor_position); let is_selected = selected.is_some(); -- cgit From 7f1d58aa4591eba40dd6f3e6bc2869dcdc3e9adb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Feb 2023 07:09:24 +0100 Subject: Inline `Handle::content` for simplicity and efficiency We can avoid downcasting `state` :^) --- native/src/widget/pick_list.rs | 53 ++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 8189dd61..b1cdfad4 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -224,7 +224,7 @@ where self.selected.as_ref(), &self.handle, &self.style, - tree.state.downcast_ref::>(), + || tree.state.downcast_ref::>(), ) } @@ -325,32 +325,6 @@ impl Default for Handle { } } -impl Handle { - fn content>( - &self, - is_open: bool, - ) -> Option<(Font, char, Option)> { - match self { - Self::Arrow { size } => { - Some((Renderer::ICON_FONT, Renderer::ARROW_DOWN_ICON, *size)) - } - Self::Static(Icon { - font, - code_point, - size, - }) => Some((font.clone(), *code_point, *size)), - Self::Dynamic { open, closed } => { - if is_open { - Some((open.font.clone(), open.code_point, open.size)) - } else { - Some((closed.font.clone(), closed.code_point, closed.size)) - } - } - Self::None => None, - } - } -} - /// The icon of a [`Handle`]. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Icon { @@ -593,7 +567,7 @@ pub fn draw<'a, T, Renderer>( selected: Option<&T>, handle: &Handle, style: &::Style, - state: &State, + state: impl FnOnce() -> &'a State, ) where Renderer: text::Renderer, Renderer::Theme: StyleSheet, @@ -619,9 +593,26 @@ pub fn draw<'a, T, Renderer>( style.background, ); - if let Some((font, code_point, size)) = - handle.content::(state.is_open) - { + let handle = match handle { + Handle::Arrow { size } => { + Some((Renderer::ICON_FONT, Renderer::ARROW_DOWN_ICON, *size)) + } + Handle::Static(Icon { + font, + code_point, + size, + }) => Some((font.clone(), *code_point, *size)), + Handle::Dynamic { open, closed } => { + if state().is_open { + Some((open.font.clone(), open.code_point, open.size)) + } else { + Some((closed.font.clone(), closed.code_point, closed.size)) + } + } + Handle::None => None, + }; + + if let Some((font, code_point, size)) = handle { let size = f32::from(size.unwrap_or_else(|| renderer.default_size())); renderer.fill_text(Text { -- cgit From a9992d131b8cbbc2c73854ecf073ca28c35397e6 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Tue, 14 Feb 2023 11:40:29 -0800 Subject: Pad after setting width Otherwise `width` will set limits back to a fixed width if `Length::Units` is used, overwriting padding. --- native/src/widget/text_input.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 8755b85d..5bfc918c 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -389,8 +389,8 @@ where let padding = padding.fit(Size::ZERO, limits.max()); let limits = limits - .pad(padding) .width(width) + .pad(padding) .height(Length::Units(text_size)); let mut text = layout::Node::new(limits.resolve(Size::ZERO)); -- cgit