From 3126f859ef36f3763d315b524391f7d30061dd0a Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 30 Nov 2023 14:01:42 -0500 Subject: [PATCH 01/10] chore: update to 0.12 --- Cargo.toml | 2 - cosmic-config/src/settings_daemon.rs | 0 examples/cosmic/Cargo.toml | 1 + examples/cosmic/src/window.rs | 5 +- examples/cosmic/src/window/demo.rs | 4 +- iced | 2 +- src/app/cosmic.rs | 3 +- src/app/mod.rs | 2 +- src/app/settings.rs | 2 +- src/font.rs | 10 +- src/keyboard_nav.rs | 3 +- src/lib.rs | 1 + src/theme/style/iced.rs | 17 - src/widget/aspect_ratio.rs | 10 +- src/widget/button/widget.rs | 13 +- src/widget/color_picker/mod.rs | 11 +- src/widget/context_drawer/overlay.rs | 12 +- src/widget/context_drawer/widget.rs | 11 +- src/widget/cosmic_container.rs | 13 +- src/widget/dropdown/menu/mod.rs | 42 +- src/widget/dropdown/widget.rs | 132 +++-- src/widget/flex_row/layout.rs | 6 +- src/widget/flex_row/widget.rs | 8 +- src/widget/grid/layout.rs | 16 +- src/widget/grid/widget.rs | 8 +- src/widget/menu/flex.rs | 14 +- src/widget/menu/menu_bar.rs | 4 +- src/widget/menu/menu_inner.rs | 74 ++- src/widget/popover.rs | 18 +- src/widget/rectangle_tracker/mod.rs | 8 +- src/widget/segmented_button/horizontal.rs | 11 +- src/widget/segmented_button/vertical.rs | 11 +- src/widget/segmented_button/widget.rs | 115 +++-- src/widget/text_input/input.rs | 577 +++++++++++----------- 34 files changed, 712 insertions(+), 454 deletions(-) create mode 100644 cosmic-config/src/settings_daemon.rs diff --git a/Cargo.toml b/Cargo.toml index 962c14f1cac..aeb86e1a3fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -143,5 +143,3 @@ exclude = ["examples/design-demo", "iced"] [patch."https://github.com/pop-os/libcosmic"] libcosmic = { path = "./" } -# [patch."https://github.com/pop-os/cosmic-time"] -# cosmic-time = { path = "../cosmic-time" } diff --git a/cosmic-config/src/settings_daemon.rs b/cosmic-config/src/settings_daemon.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/cosmic/Cargo.toml b/examples/cosmic/Cargo.toml index a4c3bf3e16d..fdc939b9e5a 100644 --- a/examples/cosmic/Cargo.toml +++ b/examples/cosmic/Cargo.toml @@ -17,4 +17,5 @@ log = "0.4.17" [dependencies.cosmic-time] git = "https://github.com/pop-os/cosmic-time" default-features = false +branch = "update-0.12" features = ["libcosmic", "once_cell"] \ No newline at end of file diff --git a/examples/cosmic/src/window.rs b/examples/cosmic/src/window.rs index 023dadf75ba..c8e6748448a 100644 --- a/examples/cosmic/src/window.rs +++ b/examples/cosmic/src/window.rs @@ -12,6 +12,7 @@ use cosmic::{ widget::{self, column, container, horizontal_space, row, text}, window::{self, close, drag, minimize, toggle_maximize}, }, + iced_futures::event::listen_raw, keyboard_nav, prelude::*, theme::{self, Theme}, @@ -358,7 +359,7 @@ impl Application for Window { } fn subscription(&self) -> Subscription { - let window_break = subscription::events_with(|event, _| match event { + let window_break = listen_raw(|event, _| match event { cosmic::iced::Event::Window( _window_id, window::Event::Resized { width, height: _ }, @@ -450,7 +451,7 @@ impl Application for Window { _ => (), }, Message::ToggleWarning => self.toggle_warning(), - Message::FontsLoaded => {} + Message::FontsLoaded => {} // Message::Tick(instant) => self.timeline.borrow_mut().now(instant), Message::Tick(instant) => self.timeline.borrow_mut().now(instant), Message::Tick(instant) => self.timeline.borrow_mut().now(instant), } ret diff --git a/examples/cosmic/src/window/demo.rs b/examples/cosmic/src/window/demo.rs index 5a484f09a71..824b243d445 100644 --- a/examples/cosmic/src/window/demo.rs +++ b/examples/cosmic/src/window/demo.rs @@ -4,7 +4,8 @@ use apply::Apply; use cosmic::{ cosmic_theme, iced::widget::{checkbox, column, progress_bar, radio, slider, text, text_input}, - iced::{id, Alignment, Length}, + iced::{Alignment, Length}, + iced_core::id, theme::ThemeType, widget::{ button, color_picker::ColorPickerUpdate, cosmic_container::container, dropdown, icon, @@ -498,7 +499,6 @@ impl State { .on_input(Message::InputChanged) .into(), cosmic::widget::search_input("test", &self.entry_value) - .on_clear(Message::InputChanged("".to_string())) .width(Length::Fixed(100.0)) .on_input(Message::InputChanged) .into(), diff --git a/iced b/iced index b3ede4f9a72..9af6bbb55c3 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit b3ede4f9a72275cfeb29fac80a31546f728783fd +Subproject commit 9af6bbb55c3443a21b835b01f20b2c0032cb50bc diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index f621c79212a..33042725a81 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -13,6 +13,7 @@ use iced::event::wayland::{self, WindowEvent}; #[cfg(feature = "wayland")] use iced::event::PlatformSpecific; use iced::window; +use iced_futures::event::listen_raw; #[cfg(not(feature = "wayland"))] use iced_runtime::command::Action; #[cfg(not(feature = "wayland"))] @@ -126,7 +127,7 @@ where } fn subscription(&self) -> Subscription { - let window_events = iced::subscription::events_with(|event, _| { + let window_events = listen_raw(|event, _| { match event { iced::Event::Window(id, window::Event::Resized { width, height }) => { return Some(Message::WindowResize(id, width, height)); diff --git a/src/app/mod.rs b/src/app/mod.rs index 328fa9c6a6f..f775aba153d 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -82,7 +82,7 @@ pub(crate) fn iced_settings( iced.antialiasing = settings.antialiasing; iced.default_font = settings.default_font; - iced.default_text_size = settings.default_text_size; + iced.default_text_size = iced::Pixels(settings.default_text_size); iced.exit_on_close_request = settings.exit_on_close; iced.id = Some(App::APP_ID.to_owned()); diff --git a/src/app/settings.rs b/src/app/settings.rs index 10ed4dd3bcb..21d649f7624 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -5,7 +5,7 @@ use crate::{font, Theme}; #[cfg(feature = "wayland")] -use iced::Limits; +use iced_core::layout::Limits; use iced_core::Font; /// Configure a new COSMIC application. diff --git a/src/font.rs b/src/font.rs index 8f1be2666cb..926ef14d556 100644 --- a/src/font.rs +++ b/src/font.rs @@ -16,7 +16,7 @@ pub const FONT: Font = Font { family: Family::Name("Fira Sans"), weight: iced_core::font::Weight::Normal, stretch: iced_core::font::Stretch::Normal, - monospaced: false, + style: iced_core::font::Style::Normal, }; pub const FONT_DATA: &[u8] = include_bytes!("../res/Fira/FiraSans-Regular.otf"); @@ -25,7 +25,7 @@ pub const FONT_LIGHT: Font = Font { family: Family::Name("Fira Sans"), weight: iced_core::font::Weight::Light, stretch: iced_core::font::Stretch::Normal, - monospaced: false, + style: iced_core::font::Style::Normal, }; pub const FONT_LIGHT_DATA: &[u8] = include_bytes!("../res/Fira/FiraSans-Light.otf"); @@ -34,7 +34,7 @@ pub const FONT_SEMIBOLD: Font = Font { family: Family::Name("Fira Sans"), weight: iced_core::font::Weight::Semibold, stretch: iced_core::font::Stretch::Normal, - monospaced: false, + style: iced_core::font::Style::Normal, }; pub const FONT_SEMIBOLD_DATA: &[u8] = include_bytes!("../res/Fira/FiraSans-SemiBold.otf"); @@ -43,7 +43,7 @@ pub const FONT_BOLD: Font = Font { family: Family::Name("Fira Sans"), weight: iced_core::font::Weight::Bold, stretch: iced_core::font::Stretch::Normal, - monospaced: false, + style: iced_core::font::Style::Normal, }; pub const FONT_BOLD_DATA: &[u8] = include_bytes!("../res/Fira/FiraSans-Bold.otf"); @@ -52,7 +52,7 @@ pub const FONT_MONO_REGULAR: Font = Font { family: Family::Name("Fira Mono"), weight: iced_core::font::Weight::Normal, stretch: iced_core::font::Stretch::Normal, - monospaced: true, + style: iced_core::font::Style::Normal, }; pub const FONT_MONO_REGULAR_DATA: &[u8] = include_bytes!("../res/Fira/FiraMono-Regular.otf"); diff --git a/src/keyboard_nav.rs b/src/keyboard_nav.rs index 40294a07beb..1f3e7ffdcf2 100644 --- a/src/keyboard_nav.rs +++ b/src/keyboard_nav.rs @@ -12,6 +12,7 @@ use iced_core::{ widget::{operation, Id, Operation}, Rectangle, }; +use iced_futures::event::listen_raw; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Message { @@ -24,7 +25,7 @@ pub enum Message { } pub fn subscription() -> Subscription { - subscription::events_with(|event, status| { + listen_raw(|event, status| { if event::Status::Ignored != status { return None; } diff --git a/src/lib.rs b/src/lib.rs index 6a65e18d50e..b00ad829884 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,5 +66,6 @@ pub use theme::{style, Theme}; pub mod widget; +type Paragraph = ::Paragraph; pub type Renderer = iced::Renderer; pub type Element<'a, Message> = iced::Element<'a, Message, Renderer>; diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index d4868618f06..27635a5acd6 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -146,23 +146,6 @@ impl iced_button::StyleSheet for Theme { } } - fn focused(&self, style: &Self::Style) -> iced_button::Appearance { - if let Button::Custom { hover, .. } = style { - return hover(self); - } - - let active = self.active(style); - let component = style.cosmic(self); - iced_button::Appearance { - background: match style { - Button::Link => None, - Button::LinkActive => Some(Background::Color(component.divider.into())), - _ => Some(Background::Color(component.hover.into())), - }, - ..active - } - } - fn disabled(&self, style: &Self::Style) -> iced_button::Appearance { let active = self.active(style); diff --git a/src/widget/aspect_ratio.rs b/src/widget/aspect_ratio.rs index 076abdb6d8b..471cfaaf377 100644 --- a/src/widget/aspect_ratio.rs +++ b/src/widget/aspect_ratio.rs @@ -161,12 +161,18 @@ where Widget::height(&self.container) } - fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node { + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { let custom_limits = layout::Limits::new( self.constrain_limits(limits.min()), self.constrain_limits(limits.max()), ); - self.container.layout(renderer, &custom_limits) + self.container + .layout(&mut tree.children[0], renderer, &custom_limits) } fn operate( diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index 597993999d7..37cb58f6a61 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -244,14 +244,23 @@ where self.height } - fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node { + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { layout( renderer, limits, self.width, self.height, self.padding, - |renderer, limits| self.content.as_widget().layout(renderer, limits), + |renderer, limits| { + self.content + .as_widget() + .layout(&mut tree.children[0], renderer, limits) + }, ) } diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 327b0b7f412..f9d116b9f4f 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -515,8 +515,15 @@ where Length::Shrink } - fn layout(&self, renderer: &crate::Renderer, limits: &layout::Limits) -> layout::Node { - self.inner.as_widget().layout(renderer, limits) + fn layout( + &self, + tree: &mut Tree, + renderer: &crate::Renderer, + limits: &layout::Limits, + ) -> layout::Node { + self.inner + .as_widget() + .layout(&mut tree.children[0], renderer, limits) } #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] diff --git a/src/widget/context_drawer/overlay.rs b/src/widget/context_drawer/overlay.rs index 3c84cde9958..f2a2bc79f6e 100644 --- a/src/widget/context_drawer/overlay.rs +++ b/src/widget/context_drawer/overlay.rs @@ -20,12 +20,20 @@ impl<'a, 'b, Message> overlay::Overlay for Overlay<'a, where Message: Clone, { - fn layout(&self, renderer: &crate::Renderer, bounds: Size, position: Point) -> layout::Node { + fn layout( + &mut self, + renderer: &crate::Renderer, + bounds: Size, + position: Point, + ) -> layout::Node { let limits = layout::Limits::new(Size::ZERO, bounds) .width(self.width) .height(bounds.height - 8.0 - position.y); - let mut node = self.content.as_widget().layout(renderer, &limits); + let mut node = + self.content + .as_widget() + .layout(&mut self.tree.children[0], renderer, &limits); let node_size = node.size(); node.move_to(Point { diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index c0cd941b15a..dee04a86a4c 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -122,8 +122,15 @@ impl<'a, Message: Clone> Widget for ContextDrawer<'a, Message self.content.as_widget().height() } - fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node { - self.content.as_widget().layout(renderer, limits) + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + self.content + .as_widget() + .layout(&mut tree.children[0], renderer, limits) } fn operate( diff --git a/src/widget/cosmic_container.rs b/src/widget/cosmic_container.rs index 7d170dfd2f2..4f9c7b78d2e 100644 --- a/src/widget/cosmic_container.rs +++ b/src/widget/cosmic_container.rs @@ -144,6 +144,10 @@ where self.container.diff(tree); } + fn state(&self) -> iced_core::widget::tree::State { + self.container.state() + } + fn width(&self) -> Length { Widget::width(&self.container) } @@ -152,8 +156,13 @@ where Widget::height(&self.container) } - fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node { - self.container.layout(renderer, limits) + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + self.container.layout(tree, renderer, limits) } fn operate( diff --git a/src/widget/dropdown/menu/mod.rs b/src/widget/dropdown/menu/mod.rs index 4797adb1f6d..589ff295617 100644 --- a/src/widget/dropdown/menu/mod.rs +++ b/src/widget/dropdown/menu/mod.rs @@ -187,7 +187,12 @@ impl<'a, Message: 'a> Overlay<'a, Message> { } impl<'a, Message> iced_core::Overlay for Overlay<'a, Message> { - fn layout(&self, renderer: &crate::Renderer, bounds: Size, position: Point) -> layout::Node { + fn layout( + &mut self, + renderer: &crate::Renderer, + bounds: Size, + position: Point, + ) -> layout::Node { let space_below = bounds.height - (position.y + self.target_height); let space_above = position.y; @@ -204,7 +209,7 @@ impl<'a, Message> iced_core::Overlay for Overlay<'a, M ) .width(self.width); - let mut node = self.container.layout(renderer, &limits); + let mut node = self.container.layout(&mut self.state, renderer, &limits); node.move_to(if space_below > space_above { position + Vector::new(0.0, self.target_height) @@ -289,13 +294,18 @@ impl<'a, S: AsRef, Message> Widget for List<'a, S Length::Shrink } - fn layout(&self, renderer: &crate::Renderer, limits: &layout::Limits) -> layout::Node { + fn layout( + &self, + _tree: &mut Tree, + renderer: &crate::Renderer, + limits: &layout::Limits, + ) -> layout::Node { use std::f32; let limits = limits.width(Length::Fill).height(Length::Shrink); let text_size = self .text_size - .unwrap_or_else(|| text::Renderer::default_size(renderer)); + .unwrap_or_else(|| text::Renderer::default_size(renderer).0); let text_line_height = self.text_line_height.to_absolute(Pixels(text_size)); @@ -335,7 +345,7 @@ impl<'a, S: AsRef, Message> Widget for List<'a, S if let Some(cursor_position) = cursor.position_in(layout.bounds()) { let text_size = self .text_size - .unwrap_or_else(|| text::Renderer::default_size(renderer)); + .unwrap_or_else(|| text::Renderer::default_size(renderer).0); let option_height = f32::from(self.text_line_height.to_absolute(Pixels(text_size))) @@ -356,7 +366,7 @@ impl<'a, S: AsRef, Message> Widget for List<'a, S if let Some(cursor_position) = cursor.position_in(layout.bounds()) { let text_size = self .text_size - .unwrap_or_else(|| text::Renderer::default_size(renderer)); + .unwrap_or_else(|| text::Renderer::default_size(renderer).0); let option_height = f32::from(self.text_line_height.to_absolute(Pixels(text_size))) @@ -408,7 +418,7 @@ impl<'a, S: AsRef, Message> Widget for List<'a, S let text_size = self .text_size - .unwrap_or_else(|| text::Renderer::default_size(renderer)); + .unwrap_or_else(|| text::Renderer::default_size(renderer).0); let option_height = f32::from(self.text_line_height.to_absolute(Pixels(text_size))) + self.padding.vertical(); @@ -484,24 +494,26 @@ impl<'a, S: AsRef, Message> Widget for List<'a, S (appearance.text_color, crate::font::FONT) }; + let bounds = Rectangle { + x: bounds.x + self.padding.left, + y: bounds.center_y(), + width: f32::INFINITY, + ..bounds + }; text::Renderer::fill_text( renderer, Text { content: option.as_ref(), - bounds: Rectangle { - x: bounds.x + self.padding.left, - y: bounds.center_y(), - width: f32::INFINITY, - ..bounds - }, - size: text_size, + bounds: bounds.size(), + size: Pixels(text_size), line_height: self.text_line_height, font, - color, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, shaping: text::Shaping::Advanced, }, + bounds.position(), + color, ); } } diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index fbcf7dcbffe..632eaf56ed8 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -6,7 +6,7 @@ use super::menu::{self, Menu}; use crate::widget::icon; use derive_setters::Setters; use iced_core::event::{self, Event}; -use iced_core::text::{self, Text}; +use iced_core::text::{self, Paragraph, Text}; use iced_core::widget::tree::{self, Tree}; use iced_core::{alignment, keyboard, layout, mouse, overlay, renderer, svg, touch}; use iced_core::{Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Widget}; @@ -61,6 +61,28 @@ impl<'a, S: AsRef, Message> Dropdown<'a, S, Message> { font: None, } } + + fn update_paragraphs(&self, state: &mut tree::State) { + let state = state.downcast_mut::(); + + state + .selections + .resize_with(self.selections.len(), crate::Paragraph::new); + for (i, selection) in self.selections.iter().enumerate() { + state.selections[i].update(Text { + content: selection.as_ref(), + bounds: Size::INFINITY, + // TODO use the renderer default size + size: iced::Pixels(self.text_size.unwrap_or(14.0)), + + line_height: self.text_line_height, + font: self.font.unwrap_or(crate::font::FONT), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + shaping: text::Shaping::Advanced, + }); + } + } } impl<'a, S: AsRef, Message: 'a> Widget for Dropdown<'a, S, Message> { @@ -69,7 +91,31 @@ impl<'a, S: AsRef, Message: 'a> Widget for Dropdo } fn state(&self) -> tree::State { - tree::State::new(State::new()) + let mut tree = tree::State::new(State::new()); + self.update_paragraphs(&mut tree); + tree + } + + fn diff(&mut self, tree: &mut Tree) { + let state = tree.state.downcast_mut::(); + + state + .selections + .resize_with(self.selections.len(), crate::Paragraph::new); + for (i, selection) in self.selections.iter().enumerate() { + state.selections[i].update(Text { + content: selection.as_ref(), + bounds: Size::INFINITY, + // TODO use the renderer default size + size: iced::Pixels(self.text_size.unwrap_or(14.0)), + + line_height: self.text_line_height, + font: self.font.unwrap_or(crate::font::FONT), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + shaping: text::Shaping::Advanced, + }); + } } fn width(&self) -> Length { @@ -80,7 +126,12 @@ impl<'a, S: AsRef, Message: 'a> Widget for Dropdo Length::Shrink } - fn layout(&self, renderer: &crate::Renderer, limits: &layout::Limits) -> layout::Node { + fn layout( + &self, + tree: &mut Tree, + renderer: &crate::Renderer, + limits: &layout::Limits, + ) -> layout::Node { layout( renderer, limits, @@ -90,9 +141,12 @@ impl<'a, S: AsRef, Message: 'a> Widget for Dropdo self.text_size.unwrap_or(14.0), self.text_line_height, self.font, - self.selected - .and_then(|id| self.selections.get(id)) - .map(AsRef::as_ref), + self.selected.and_then(|id| { + self.selections + .get(id) + .map(AsRef::as_ref) + .zip(tree.state.downcast_mut::().selections.get_mut(id)) + }), ) } @@ -173,6 +227,8 @@ impl<'a, S: AsRef, Message: 'a> Widget for Dropdo self.gap, self.padding, self.text_size.unwrap_or(14.0), + self.text_line_height, + self.font, self.selections, self.selected, &self.on_selected, @@ -196,6 +252,7 @@ pub struct State { keyboard_modifiers: keyboard::Modifiers, is_open: bool, hovered_option: Option, + selections: Vec, } impl State { @@ -214,6 +271,7 @@ impl State { keyboard_modifiers: keyboard::Modifiers::default(), is_open: false, hovered_option: None, + selections: Vec::new(), } } } @@ -235,7 +293,7 @@ pub fn layout( text_size: f32, text_line_height: text::LineHeight, font: Option, - selection: Option<&str>, + selection: Option<(&str, &mut crate::Paragraph)>, ) -> layout::Node { use std::f32; @@ -243,16 +301,18 @@ pub fn layout( let max_width = match width { Length::Shrink => { - let measure = |label: &str| -> f32 { - let width = text::Renderer::measure_width( - renderer, - label, - text_size, - font.unwrap_or_else(|| text::Renderer::default_font(renderer)), - text::Shaping::Advanced, - ); - - width.round() + let measure = move |(label, paragraph): (_, &mut crate::Paragraph)| -> f32 { + paragraph.update(Text { + content: label, + bounds: Size::new(f32::MAX, f32::MAX), + size: iced::Pixels(text_size), + line_height: text_line_height, + font: font.unwrap_or_else(|| text::Renderer::default_font(renderer)), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + shaping: text::Shaping::Advanced, + }); + paragraph.min_width().round() }; selection.map(measure).unwrap_or_default() @@ -358,6 +418,8 @@ pub fn overlay<'a, S: AsRef, Message: 'a>( gap: f32, padding: Padding, text_size: f32, + text_line_height: text::LineHeight, + font: Option, selections: &'a [S], selected_option: Option, on_selected: &'a dyn Fn(usize) -> Message, @@ -378,21 +440,14 @@ pub fn overlay<'a, S: AsRef, Message: 'a>( None, ) .width({ - let measure = |label: &str| -> f32 { - let width = text::Renderer::measure_width( - renderer, - label, - text_size, - crate::font::FONT, - text::Shaping::Advanced, - ); - - width.round() + let measure = |label: &str, selection_paragraph: &mut crate::Paragraph| -> f32 { + selection_paragraph.min_width().round() }; selections .iter() - .map(|label| measure(label.as_ref())) + .zip(state.selections.iter_mut()) + .map(|(label, selection)| measure(label.as_ref(), selection)) .fold(0.0, |next, current| current.max(next)) + gap + 16.0 @@ -462,26 +517,27 @@ pub fn draw<'a, S>( } if let Some(content) = selected.map(AsRef::as_ref) { - let text_size = text_size.unwrap_or_else(|| text::Renderer::default_size(renderer)); - + let text_size = text_size.unwrap_or_else(|| text::Renderer::default_size(renderer).0); + let bounds = Rectangle { + x: bounds.x + padding.left, + y: bounds.center_y(), + width: bounds.width - padding.horizontal(), + height: f32::from(text_line_height.to_absolute(Pixels(text_size))), + }; text::Renderer::fill_text( renderer, Text { content, - size: text_size, + size: iced::Pixels(text_size), line_height: text_line_height, font, - color: style.text_color, - bounds: Rectangle { - x: bounds.x + padding.left, - y: bounds.center_y(), - width: bounds.width - padding.horizontal(), - height: f32::from(text_line_height.to_absolute(Pixels(text_size))), - }, + bounds: bounds.size(), horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, shaping: text::Shaping::Advanced, }, + bounds.position(), + style.text_color, ); } } diff --git a/src/widget/flex_row/layout.rs b/src/widget/flex_row/layout.rs index b1b75b28f8a..ba4de64ec88 100644 --- a/src/widget/flex_row/layout.rs +++ b/src/widget/flex_row/layout.rs @@ -3,6 +3,7 @@ use crate::{Element, Renderer}; use iced_core::layout::{Limits, Node}; +use iced_core::widget::Tree; use iced_core::{Padding, Point, Size}; pub fn resolve( @@ -12,6 +13,7 @@ pub fn resolve( padding: Padding, column_spacing: f32, row_spacing: f32, + tree: &mut [Tree], ) -> Node { let limits = limits.pad(padding); @@ -26,9 +28,9 @@ pub fn resolve( let mut row_buffer = Vec::::with_capacity(8); - for child in items { + for (child, tree) in items.iter().zip(tree.into_iter()) { // Calculate the dimensions of the item. - let child_node = child.as_widget().layout(renderer, &limits); + let child_node = child.as_widget().layout(tree, renderer, &limits); let size = child_node.size(); // Calculate the required additional width to fit the item into the current row. diff --git a/src/widget/flex_row/widget.rs b/src/widget/flex_row/widget.rs index 667b44b0f0f..e969bfb85eb 100644 --- a/src/widget/flex_row/widget.rs +++ b/src/widget/flex_row/widget.rs @@ -58,7 +58,12 @@ impl<'a, Message: 'static + Clone> Widget for FlexRow<'a, Mes Length::Shrink } - fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node { + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { let limits = limits .max_width(self.max_width) .width(self.width()) @@ -71,6 +76,7 @@ impl<'a, Message: 'static + Clone> Widget for FlexRow<'a, Mes self.padding, f32::from(self.column_spacing), f32::from(self.row_spacing), + &mut tree.children, ) } diff --git a/src/widget/grid/layout.rs b/src/widget/grid/layout.rs index 79f6efcdeca..f746e88b0cc 100644 --- a/src/widget/grid/layout.rs +++ b/src/widget/grid/layout.rs @@ -4,6 +4,7 @@ use super::widget::Assignment; use crate::{Element, Renderer}; use iced_core::layout::{Limits, Node}; +use iced_core::widget::Tree; use iced_core::{Alignment, Length, Padding, Point, Size}; use taffy::geometry::{Line, Rect}; @@ -24,6 +25,7 @@ pub fn resolve( row_alignment: Alignment, column_spacing: f32, row_spacing: f32, + tree: &mut [Tree], ) -> Node { let max_size = limits.max(); @@ -33,10 +35,10 @@ pub fn resolve( let mut taffy = TaffyTree::<()>::with_capacity(items.len() + 1); // Attach widgets as child nodes. - for (child, assignment) in items.iter().zip(assignments.iter()) { + for ((child, assignment), tree) in items.iter().zip(assignments.iter()).zip(tree.iter_mut()) { // Calculate the dimensions of the item. let child_widget = child.as_widget(); - let child_node = child_widget.layout(renderer, limits); + let child_node = child_widget.layout(tree, renderer, limits); let size = child_node.size(); nodes.push(child_node); @@ -155,12 +157,18 @@ pub fn resolve( } }; - for (leaf, (child, node)) in leafs.into_iter().zip(items.iter().zip(nodes.iter_mut())) { + for (((leaf, child), node), tree) in leafs + .into_iter() + .zip(items.iter()) + .zip(nodes.iter_mut()) + .zip(tree) + { if let Ok(leaf_layout) = taffy.layout(leaf) { let child_widget = child.as_widget(); match child_widget.width() { Length::Fill | Length::FillPortion(_) => { - *node = child_widget.layout(renderer, &limits.width(leaf_layout.size.width)); + *node = + child_widget.layout(tree, renderer, &limits.width(leaf_layout.size.width)); } _ => (), } diff --git a/src/widget/grid/widget.rs b/src/widget/grid/widget.rs index 9a8a70c64c6..782738fbb12 100644 --- a/src/widget/grid/widget.rs +++ b/src/widget/grid/widget.rs @@ -120,7 +120,12 @@ impl<'a, Message: 'static + Clone> Widget for Grid<'a, Messag self.height } - fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node { + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { let limits = limits .max_width(self.max_width) .width(self.width()) @@ -138,6 +143,7 @@ impl<'a, Message: 'static + Clone> Widget for Grid<'a, Messag self.row_alignment, f32::from(self.column_spacing), f32::from(self.row_spacing), + &mut tree.children, ) } diff --git a/src/widget/menu/flex.rs b/src/widget/menu/flex.rs index ecff8dab26f..17c7e1048c7 100644 --- a/src/widget/menu/flex.rs +++ b/src/widget/menu/flex.rs @@ -1,5 +1,6 @@ // From iced_aw, license MIT +use iced_core::widget::Tree; use iced_widget::core::{ layout::{Limits, Node}, renderer, Alignment, Element, Padding, Point, Size, @@ -54,6 +55,7 @@ pub fn resolve<'a, E, Message, Renderer>( spacing: f32, align_items: Alignment, items: &[E], + tree: &mut [Tree], ) -> Node where E: std::borrow::Borrow>, @@ -73,7 +75,7 @@ where if align_items == Alignment::Center { let mut fill_cross = axis.cross(limits.min()); - for child in items { + for (child, tree) in items.iter().zip(tree.iter_mut()) { let child = child.borrow(); let cross_fill_factor = match axis { Axis::Horizontal => child.as_widget().height(), @@ -86,7 +88,7 @@ where let child_limits = Limits::new(Size::ZERO, Size::new(max_width, max_height)); - let layout = child.as_widget().layout(renderer, &child_limits); + let layout = child.as_widget().layout(tree, renderer, &child_limits); let size = layout.size(); fill_cross = fill_cross.max(axis.cross(size)); @@ -96,7 +98,7 @@ where cross = fill_cross; } - for (i, child) in items.iter().enumerate() { + for (i, (child, tree)) in items.iter().zip(tree.iter_mut()).enumerate() { let child = child.borrow(); let fill_factor = match axis { Axis::Horizontal => child.as_widget().width(), @@ -122,7 +124,7 @@ where Size::new(max_width, max_height), ); - let layout = child.as_widget().layout(renderer, &child_limits); + let layout = child.as_widget().layout(tree, renderer, &child_limits); let size = layout.size(); available -= axis.main(size); @@ -139,7 +141,7 @@ where let remaining = available.max(0.0); - for (i, child) in items.iter().enumerate() { + for (i, (child, tree)) in items.iter().zip(tree.iter_mut()).enumerate() { let child = child.borrow(); let fill_factor = match axis { Axis::Horizontal => child.as_widget().width(), @@ -172,7 +174,7 @@ where Size::new(max_width, max_height), ); - let layout = child.as_widget().layout(renderer, &child_limits); + let layout = child.as_widget().layout(tree, renderer, &child_limits); if align_items != Alignment::Center { cross = cross.max(axis.cross(layout.size())); diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 308b142edb5..a2bcdd3d29d 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -279,7 +279,7 @@ where .collect() } - fn layout(&self, renderer: &Renderer, limits: &Limits) -> Node { + fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { use super::flex; let limits = limits.width(self.width).height(self.height); @@ -296,6 +296,8 @@ where self.spacing, Alignment::Center, &children, + // the children of the tree are the menu roots + &mut tree.children, ) } diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index 63629ae4a0d..3608cf33bb6 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -252,8 +252,13 @@ impl MenuBounds { where Renderer: renderer::Renderer, { - let (children_size, child_positions, child_sizes) = - get_children_layout(menu_tree, renderer, item_width, item_height); + let (children_size, child_positions, child_sizes) = get_children_layout( + menu_tree, + renderer, + item_width, + item_height, + &mut Tree::new(&menu_tree.item), + ); // viewport space parent bounds let view_parent_bounds = parent_bounds + overlay_offset; @@ -299,6 +304,7 @@ impl MenuState { slice: MenuSlice, renderer: &Renderer, menu_tree: &MenuTree<'_, Message, Renderer>, + tree: &mut [Tree], ) -> Node where Renderer: renderer::Renderer, @@ -322,7 +328,8 @@ impl MenuState { .iter() .zip(self.menu_bounds.child_sizes[start_index..=end_index].iter()) .zip(menu_tree.children[start_index..=end_index].iter()) - .map(|((cp, size), mt)| { + .zip(tree[start_index..=end_index].iter_mut()) + .map(|(((cp, size), mt), tree)| { let mut position = *cp; let mut size = *size; @@ -336,7 +343,7 @@ impl MenuState { let limits = Limits::new(Size::ZERO, size); - let mut node = mt.item.as_widget().layout(renderer, &limits); + let mut node = mt.item.as_widget().layout(tree, renderer, &limits); node.move_to(Point::new(0.0, position + self.scroll_offset)); node }) @@ -353,6 +360,7 @@ impl MenuState { index: usize, renderer: &Renderer, menu_tree: &MenuTree<'_, Message, Renderer>, + tree: &mut Tree, ) -> Node where Renderer: renderer::Renderer, @@ -363,7 +371,7 @@ impl MenuState { let position = self.menu_bounds.child_positions[index]; let limits = Limits::new(Size::ZERO, self.menu_bounds.child_sizes[index]); let parent_offset = children_bounds.position() - Point::ORIGIN; - let mut node = menu_tree.item.as_widget().layout(renderer, &limits); + let mut node = menu_tree.item.as_widget().layout(tree, renderer, &limits); node.move_to(Point::new( parent_offset.x, parent_offset.y + position + self.scroll_offset, @@ -458,9 +466,44 @@ where Renderer: renderer::Renderer, Renderer::Theme: StyleSheet, { - fn layout(&self, _renderer: &Renderer, bounds: Size, position: Point) -> Node { + fn layout(&mut self, renderer: &Renderer, bounds: Size, position: Point) -> Node { + // layout children + let state = self.tree.state.downcast_mut::(); + let overlay_offset = Point::ORIGIN - position; + let tree_children = &mut self.tree.children; + let children = state + .active_root + .map(|active_root| { + let root = &self.menu_roots[active_root]; + let active_tree = &mut tree_children[active_root]; + state.menu_states.iter().enumerate().fold( + (root, Vec::new()), + |(menu_root, mut nodes), (i, ms)| { + let slice = ms.slice(bounds, overlay_offset, self.item_height); + let start_index = slice.start_index; + let end_index = slice.end_index; + let children_node = ms.layout( + overlay_offset, + slice, + renderer, + menu_root, + &mut active_tree.children[start_index..=end_index], + ); + nodes.push(children_node); + // only the last menu can have a None active index + ( + ms.index + .map_or(menu_root, |active| &menu_root.children[active]), + nodes, + ) + }, + ) + }) + .map(|(_, l)| l) + .unwrap_or_default(); + // overlay space viewport rectangle - Node::new(bounds).translate(Point::ORIGIN - position) + Node::with_children(bounds, children).translate(Point::ORIGIN - position) } fn on_event( @@ -610,8 +653,9 @@ where state .menu_states .iter() + .zip(layout.children()) .enumerate() - .fold(root, |menu_root, (i, ms)| { + .fold(root, |menu_root, (i, (ms, children_layout))| { let draw_path = self.path_highlight.as_ref().map_or(false, |ph| match ph { PathHighlight::Full => true, PathHighlight::OmitActive => !indices.is_empty() && i < indices.len() - 1, @@ -631,9 +675,6 @@ where let start_index = slice.start_index; let end_index = slice.end_index; - // calc layout - let children_node = ms.layout(overlay_offset, slice, r, menu_root); - let children_layout = Layout::new(&children_node); let children_bounds = children_layout.bounds(); // draw menu background @@ -808,11 +849,17 @@ where // get layout let last_ms = &state.menu_states[indices.len() - 1]; + let last_tree = tree.children[active_root] + .children + .iter_mut() + .last() + .unwrap(); let child_node = last_ms.layout_single( overlay_offset, last_ms.index.expect("missing index within menu state."), renderer, mt, + last_tree, ); let child_layout = Layout::new(&child_node); @@ -1108,6 +1155,7 @@ fn get_children_layout( renderer: &Renderer, item_width: ItemWidth, item_height: ItemHeight, + tree: &mut Tree, ) -> (Size, Vec, Vec) where Renderer: renderer::Renderer, @@ -1130,13 +1178,15 @@ where ItemHeight::Dynamic(d) => menu_tree .children .iter() - .map(|mt| { + .zip(tree.children.iter_mut()) + .map(|(mt, tree)| { let w = mt.item.as_widget(); match w.height() { Length::Fixed(f) => Size::new(width, f), Length::Shrink => { let l_height = w .layout( + tree, renderer, &Limits::new(Size::ZERO, Size::new(width, f32::MAX)), ) diff --git a/src/widget/popover.rs b/src/widget/popover.rs index b0871917425..41a6e5c9305 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -71,8 +71,14 @@ where self.content.as_widget().height() } - fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node { - self.content.as_widget().layout(renderer, limits) + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let tree = &mut tree.children[0]; + self.content.as_widget().layout(tree, renderer, limits) } fn operate( @@ -203,9 +209,13 @@ impl<'a, 'b, Message, Renderer> overlay::Overlay where Renderer: iced_core::Renderer, { - fn layout(&self, renderer: &Renderer, bounds: Size, mut position: Point) -> layout::Node { + fn layout(&mut self, renderer: &Renderer, bounds: Size, mut position: Point) -> layout::Node { let limits = layout::Limits::new(Size::UNIT, bounds); - let mut node = self.content.borrow().as_widget().layout(renderer, &limits); + let mut node = self + .content + .borrow() + .as_widget() + .layout(self.tree, renderer, &limits); if self.centered { // Position is set to the center bottom of the lower widget let width = node.size().width; diff --git a/src/widget/rectangle_tracker/mod.rs b/src/widget/rectangle_tracker/mod.rs index 507e1d817d1..41563a95e08 100644 --- a/src/widget/rectangle_tracker/mod.rs +++ b/src/widget/rectangle_tracker/mod.rs @@ -195,8 +195,14 @@ where Widget::height(&self.container) } - fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node { + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { self.container.layout( + tree, renderer, if self.ignore_bounds { &layout::Limits::NONE diff --git a/src/widget/segmented_button/horizontal.rs b/src/widget/segmented_button/horizontal.rs index 03d696b2a28..8fa28c7c14c 100644 --- a/src/widget/segmented_button/horizontal.rs +++ b/src/widget/segmented_button/horizontal.rs @@ -5,7 +5,7 @@ use super::model::{Model, Selectable}; use super::style::StyleSheet; -use super::widget::{SegmentedButton, SegmentedVariant}; +use super::widget::{LocalState, SegmentedButton, SegmentedVariant}; use iced::{Length, Rectangle, Size}; use iced_core::layout; @@ -61,9 +61,14 @@ where #[allow(clippy::cast_precision_loss)] #[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_sign_loss)] - fn variant_layout(&self, renderer: &crate::Renderer, limits: &layout::Limits) -> layout::Node { + fn variant_layout( + &self, + state: &mut LocalState, + renderer: &crate::Renderer, + limits: &layout::Limits, + ) -> layout::Node { let limits = limits.width(self.width); - let (mut width, height) = self.max_button_dimensions(renderer, limits.max()); + let (mut width, height) = self.max_button_dimensions(state, renderer, limits.max()); let num = self.model.items.len(); let spacing = f32::from(self.spacing); diff --git a/src/widget/segmented_button/vertical.rs b/src/widget/segmented_button/vertical.rs index 7a5f8cf34ac..827f6cf426a 100644 --- a/src/widget/segmented_button/vertical.rs +++ b/src/widget/segmented_button/vertical.rs @@ -5,7 +5,7 @@ use super::model::{Model, Selectable}; use super::style::StyleSheet; -use super::widget::{SegmentedButton, SegmentedVariant}; +use super::widget::{LocalState, SegmentedButton, SegmentedVariant}; use iced::{Length, Rectangle, Size}; use iced_core::layout; @@ -62,9 +62,14 @@ where #[allow(clippy::cast_precision_loss)] #[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_sign_loss)] - fn variant_layout(&self, renderer: &crate::Renderer, limits: &layout::Limits) -> layout::Node { + fn variant_layout( + &self, + state: &mut LocalState, + renderer: &crate::Renderer, + limits: &layout::Limits, + ) -> layout::Node { let limits = limits.width(self.width); - let (width, mut height) = self.max_button_dimensions(renderer, limits.max()); + let (width, mut height) = self.max_button_dimensions(state, renderer, limits.max()); let num = self.model.items.len(); let spacing = f32::from(self.spacing); diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index bb22a516d23..ae4fd78dc64 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -10,15 +10,16 @@ use iced::{ alignment, event, keyboard, mouse, touch, Background, Color, Command, Event, Length, Rectangle, Size, }; -use iced_core::text::{LineHeight, Renderer as TextRenderer, Shaping}; +use iced_core::text::{LineHeight, Paragraph, Renderer as TextRenderer, Shaping}; use iced_core::widget::{self, operation, tree}; use iced_core::{layout, renderer, widget::Tree, Clipboard, Layout, Shell, Widget}; -use iced_core::{BorderRadius, Point, Renderer as IcedRenderer}; +use iced_core::{BorderRadius, Point, Renderer as IcedRenderer, Text}; +use slotmap::SecondaryMap; use std::marker::PhantomData; /// State that is maintained by each individual widget. #[derive(Default)] -struct LocalState { +pub struct LocalState { /// The first focusable key. first: Entity, /// If the widget is focused or not. @@ -27,6 +28,8 @@ struct LocalState { focused_key: Entity, /// The ID of the button that is being hovered. Defaults to null. hovered: Entity, + /// The paragraphs for each text. + paragraphs: SecondaryMap, } impl operation::Focusable for LocalState { @@ -57,7 +60,12 @@ pub trait SegmentedVariant { fn variant_button_bounds(&self, bounds: Rectangle, position: usize) -> Rectangle; /// Calculates the layout of this variant. - fn variant_layout(&self, renderer: &crate::Renderer, limits: &layout::Limits) -> layout::Node; + fn variant_layout( + &self, + state: &mut LocalState, + renderer: &crate::Renderer, + limits: &layout::Limits, + ) -> layout::Node; } /// A conjoined group of items that function together as a button. @@ -214,7 +222,12 @@ where event::Status::Ignored } - pub(super) fn max_button_dimensions(&self, renderer: &Renderer, bounds: Size) -> (f32, f32) { + pub(super) fn max_button_dimensions( + &self, + state: &mut LocalState, + renderer: &Renderer, + bounds: Size, + ) -> (f32, f32) { let mut width = 0.0f32; let mut height = 0.0f32; let font = renderer.default_font(); @@ -224,15 +237,21 @@ where let mut button_height = 0.0f32; // Add text to measurement if text was given. - if let Some(text) = self.model.text(key) { - let Size { width, height } = renderer.measure( - text, - self.font_size, - self.line_height, - font, - bounds, - Shaping::Advanced, - ); + if let Some((text, entry)) = self.model.text.get(key).zip(state.paragraphs.entry(key)) { + let paragraph = entry.or_insert_with(|| { + crate::Paragraph::with_text(Text { + content: text, + size: iced::Pixels(self.font_size), + bounds: Size::INFINITY, + font, + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, + shaping: Shaping::Advanced, + line_height: self.line_height, + }) + }); + + let Size { width, height } = paragraph.min_bounds(); button_width = width; button_height = height; @@ -282,12 +301,44 @@ where } fn state(&self) -> tree::State { + // update the paragraphs for the model tree::State::new(LocalState { first: self.model.order.iter().copied().next().unwrap_or_default(), + paragraphs: SecondaryMap::new(), ..LocalState::default() }) } + fn diff(&mut self, tree: &mut Tree) { + for e in self.model.order.iter().copied() { + if let Some(text) = self.model.text.get(e) { + let text = Text { + content: text, + size: iced::Pixels(self.font_size), + bounds: Size::INFINITY, + font: self.font_active.unwrap_or(crate::font::FONT), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, + shaping: Shaping::Advanced, + line_height: self.line_height, + }; + if let Some(paragraph) = tree + .state + .downcast_mut::() + .paragraphs + .get_mut(e) + { + paragraph.update(text); + } else { + tree.state + .downcast_mut::() + .paragraphs + .insert(e, crate::Paragraph::with_text(text)); + } + } + } + } + fn width(&self) -> Length { self.width } @@ -296,8 +347,13 @@ where self.height } - fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node { - self.variant_layout(renderer, limits) + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + self.variant_layout(tree.state.downcast_mut::(), renderer, limits) } fn on_event( @@ -548,7 +604,7 @@ where }); Widget::::draw( - &Element::::from(icon.clone()), + Element::::from(icon.clone()).as_widget(), &Tree::empty(), renderer, theme, @@ -575,17 +631,20 @@ where bounds.y = y; // Draw the text in this button. - renderer.fill_text(iced_core::text::Text { - content: text, - size: self.font_size, - bounds, - color: status_appearance.text_color, - font, - horizontal_alignment, - vertical_alignment: alignment::Vertical::Center, - shaping: Shaping::Advanced, - line_height: self.line_height, - }); + renderer.fill_text( + iced_core::text::Text { + content: text, + size: iced::Pixels(self.font_size), + bounds: bounds.size(), + font, + horizontal_alignment, + vertical_alignment: alignment::Vertical::Center, + shaping: Shaping::Advanced, + line_height: self.line_height, + }, + bounds.position(), + status_appearance.text_color, + ); } let show_close_button = diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 8930a55dde4..03286cba47f 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -5,6 +5,8 @@ //! Display fields that can be filled with text. //! //! A [`TextInput`] has some local [`State`]. +use std::borrow::Cow; + use crate::theme::THEME; use super::cursor; @@ -20,7 +22,7 @@ use iced_core::keyboard; use iced_core::mouse::{self, click}; use iced_core::overlay::Group; use iced_core::renderer::{self, Renderer as CoreRenderer}; -use iced_core::text::{self, Renderer, Text}; +use iced_core::text::{self, Paragraph, Renderer, Text}; use iced_core::time::{Duration, Instant}; use iced_core::touch; use iced_core::widget::operation::{self, Operation}; @@ -48,7 +50,10 @@ use iced_runtime::command::platform_specific::wayland::data_device::{DataFromMim /// Creates a new [`TextInput`]. /// /// [`TextInput`]: widget::TextInput -pub fn text_input<'a, Message>(placeholder: &str, value: &str) -> TextInput<'a, Message> +pub fn text_input<'a, Message>( + placeholder: impl Into>, + value: impl Into>, +) -> TextInput<'a, Message> where Message: Clone + 'static, { @@ -58,7 +63,10 @@ where /// Creates a new search [`TextInput`]. /// /// [`TextInput`]: widget::TextInput -pub fn search_input<'a, Message>(placeholder: &str, value: &str) -> TextInput<'a, Message> +pub fn search_input<'a, Message>( + placeholder: impl Into>, + value: impl Into>, +) -> TextInput<'a, Message> where Message: Clone + 'static, { @@ -79,8 +87,8 @@ where /// /// [`TextInput`]: widget::TextInput pub fn secure_input<'a, Message>( - placeholder: &str, - value: &str, + placeholder: impl Into>, + value: impl Into>, on_visible_toggle: Option, hidden: bool, ) -> TextInput<'a, Message> @@ -119,7 +127,7 @@ where /// Creates a new inline [`TextInput`]. /// /// [`TextInput`]: widget::TextInput -pub fn inline_input<'a, Message>(value: &str) -> TextInput<'a, Message> +pub fn inline_input<'a, Message>(value: impl Into>) -> TextInput<'a, Message> where Message: Clone + 'static, { @@ -171,7 +179,7 @@ pub type DnDCommand = (); #[must_use] pub struct TextInput<'a, Message> { id: Option, - placeholder: String, + placeholder: Cow<'a, str>, value: Value, is_secure: bool, font: Option<::Font>, @@ -206,13 +214,14 @@ where /// It expects: /// - a placeholder, /// - the current value - pub fn new(placeholder: &str, value: &str) -> Self { + pub fn new(placeholder: impl Into>, value: impl Into>) -> Self { let spacing = THEME.with(|t| t.borrow().cosmic().space_xxs()); + let v: Cow<'a, str> = value.into(); TextInput { id: None, - placeholder: String::from(placeholder), - value: Value::new(value), + placeholder: placeholder.into(), + value: Value::new(v.as_ref()), is_secure: false, font: None, width: Length::Fill, @@ -474,6 +483,22 @@ where fn diff(&mut self, tree: &mut Tree) { let state = tree.state.downcast_mut::(); + // TODO get values from renderer somehow. + replace_paragraph( + state, + Layout::new(&layout::Node::with_children( + Size::INFINITY, + vec![layout::Node::with_children( + Size::INFINITY, + vec![layout::Node::default()], + )], + )), + &self.value, + self.font.unwrap_or(crate::font::FONT), + Pixels(self.size.unwrap_or(14.0)), + self.line_height, + ); + // Unfocus text input if it becomes disabled if self.on_input.is_none() { state.last_click = None; @@ -506,23 +531,39 @@ where Length::Shrink } - fn layout(&self, renderer: &crate::Renderer, limits: &layout::Limits) -> layout::Node { + fn layout( + &self, + tree: &mut Tree, + renderer: &crate::Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let font = self.font.unwrap_or_else(|| renderer.default_font()); if self.dnd_icon { let limits = limits.width(Length::Shrink).height(Length::Shrink); - let size = self.size.unwrap_or_else(|| renderer.default_size()); + let size = self.size.unwrap_or_else(|| renderer.default_size().0); let bounds = limits.max(); - let font = self.font.unwrap_or_else(|| renderer.default_font()); - let Size { width, height } = renderer.measure( - &self.value.to_string(), - size, - self.line_height, + let state = tree.state.downcast_mut::(); + let value_paragraph = &mut state.value; + let v = self.value.to_string(); + value_paragraph.update(Text { + content: if self.value.is_empty() { + &self.placeholder + } else { + &v + }, font, bounds, - text::Shaping::Advanced, - ); + size: iced::Pixels(size), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, + line_height: text::LineHeight::default(), + shaping: text::Shaping::Advanced, + }); + + let Size { width, height } = limits.resolve(value_paragraph.min_bounds()); let size = limits.resolve(Size::new(width, height)); layout::Node::with_children(size, vec![layout::Node::new(size)]) @@ -540,6 +581,8 @@ where self.helper_text, self.helper_size, self.helper_line_height, + font, + tree, ) } } @@ -697,6 +740,7 @@ where self.on_dnd_command_produced.as_deref(), self.surface_ids, self.line_height, + layout, ) } @@ -847,6 +891,8 @@ pub fn layout( helper_text: Option<&str>, helper_text_size: f32, helper_text_line_height: text::LineHeight, + font: iced_core::Font, + tree: &mut Tree, ) -> layout::Node { let limits = limits.width(width); let spacing = THEME.with(|t| t.borrow().cosmic().space_xxs()); @@ -854,15 +900,19 @@ pub fn layout( let text_pos = if let Some(label) = label { let text_bounds = limits.resolve(Size::ZERO); - - let label_size = renderer.measure( - label, - size.unwrap_or_else(|| renderer.default_size()), + let state = tree.state.downcast_mut::(); + let label_paragraph = &mut state.label; + label_paragraph.update(Text { + content: label, + font, + bounds: text_bounds, + size: iced::Pixels(size.unwrap_or_else(|| renderer.default_size().0)), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, line_height, - renderer.default_font(), - text_bounds, - text::Shaping::Advanced, - ); + shaping: text::Shaping::Advanced, + }); + let label_size = label_paragraph.min_bounds(); nodes.push(layout::Node::new(label_size)); Vector::new(0.0, label_size.height + f32::from(spacing)) @@ -870,41 +920,48 @@ pub fn layout( Vector::ZERO }; - let text_size = size.unwrap_or_else(|| renderer.default_size()); + let text_size = size.unwrap_or_else(|| renderer.default_size().0); let mut text_input_height = text_size * 1.2; let padding = padding.fit(Size::ZERO, limits.max()); let helper_pos = if leading_icon.is_some() || trailing_icon.is_some() { + let children = &mut tree.children; // TODO configurable icon spacing, maybe via appearance let limits_copy = limits; let limits = limits.pad(padding); let icon_spacing = 8.0; - let (leading_icon_width, mut leading_icon) = if let Some(icon) = leading_icon.as_ref() { - let icon_node = icon.layout( - renderer, - &Limits::NONE - .width(icon.as_widget().width()) - .height(icon.as_widget().height()), - ); - text_input_height = text_input_height.max(icon_node.bounds().height); - (icon_node.bounds().width + icon_spacing, Some(icon_node)) - } else { - (0.0, None) - }; + let mut c_i = 0; + let (leading_icon_width, mut leading_icon) = + if let Some((icon, tree)) = leading_icon.zip(children.get_mut(c_i)) { + let icon_node = icon.as_widget().layout( + tree, + renderer, + &Limits::NONE + .width(icon.as_widget().width()) + .height(icon.as_widget().height()), + ); + text_input_height = text_input_height.max(icon_node.bounds().height); + c_i += 1; + (icon_node.bounds().width + icon_spacing, Some(icon_node)) + } else { + (0.0, None) + }; - let (trailing_icon_width, mut trailing_icon) = if let Some(icon) = trailing_icon.as_ref() { - let icon_node = icon.layout( - renderer, - &Limits::NONE - .width(icon.as_widget().width()) - .height(icon.as_widget().height()), - ); - text_input_height = text_input_height.max(icon_node.bounds().height); - (icon_node.bounds().width + icon_spacing, Some(icon_node)) - } else { - (0.0, None) - }; + let (trailing_icon_width, mut trailing_icon) = + if let Some((icon, tree)) = trailing_icon.zip(children.get_mut(c_i)) { + let icon_node = icon.layout( + tree, + renderer, + &Limits::NONE + .width(icon.as_widget().width()) + .height(icon.as_widget().height()), + ); + text_input_height = text_input_height.max(icon_node.bounds().height); + (icon_node.bounds().width + icon_spacing, Some(icon_node)) + } else { + (0.0, None) + }; let text_limits = limits.width(width).height(text_size * 1.2); let text_bounds = text_limits.resolve(Size::ZERO); @@ -978,14 +1035,19 @@ pub fn layout( .height(helper_text_size * 1.2); let text_bounds = limits.resolve(Size::ZERO); - let helper_text_size = renderer.measure( - helper_text, - helper_text_size, - helper_text_line_height, - renderer.default_font(), - text_bounds, - text::Shaping::Advanced, - ); + let state = tree.state.downcast_mut::(); + let helper_text_paragraph = &mut state.label; + helper_text_paragraph.update(Text { + content: helper_text, + font, + bounds: text_bounds, + size: iced::Pixels(helper_text_size), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, + line_height: helper_text_line_height, + shaping: text::Shaping::Advanced, + }); + let helper_text_size = helper_text_paragraph.min_bounds(); nodes.push(layout::Node::new(helper_text_size).translate(helper_pos)); }; @@ -1013,16 +1075,16 @@ pub fn layout( #[allow(clippy::missing_panics_doc)] #[allow(clippy::cast_lossless)] #[allow(clippy::cast_possible_truncation)] -pub fn update<'a, Message, Renderer>( +pub fn update<'a, Message>( event: Event, text_layout: Layout<'_>, cursor_position: mouse::Cursor, - renderer: &Renderer, + renderer: &crate::Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, value: &mut Value, size: Option, - font: Option, + font: Option<::Font>, is_secure: bool, on_input: Option<&dyn Fn(String) -> Message>, on_paste: Option<&dyn Fn(String) -> Message>, @@ -1033,11 +1095,17 @@ pub fn update<'a, Message, Renderer>( on_dnd_command_produced: Option<&dyn Fn(DnDCommand) -> Message>, surface_ids: Option<(window::Id, window::Id)>, line_height: text::LineHeight, + layout: Layout<'_>, ) -> event::Status where Message: Clone, - Renderer: text::Renderer, { + let font = font.unwrap_or_else(|| renderer.default_font()); + let size = size.unwrap_or_else(|| renderer.default_size().0); + let update_cache = |state, value| { + replace_paragraph(state, layout, value, font, iced::Pixels(size), line_height); + }; + match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { @@ -1056,9 +1124,6 @@ where None }; - let font: ::Font = - font.unwrap_or_else(|| renderer.default_font()); - if is_clicked { let Some(pos) = cursor_position.position() else { return event::Status::Ignored; @@ -1092,27 +1157,19 @@ where surface_ids, on_input, ) { - let actual_size = size.unwrap_or_else(|| renderer.default_size()); - let left = start.min(end); let right = end.max(start); let (left_position, _left_offset) = measure_cursor_and_scroll_offset( - renderer, + &state.value, text_layout.bounds(), - value, - actual_size, left, - font, ); let (right_position, _right_offset) = measure_cursor_and_scroll_offset( - renderer, + &state.value, text_layout.bounds(), - value, - actual_size, right, - font, ); let width = right_position - left_position; @@ -1160,14 +1217,10 @@ where }; find_cursor_position( - renderer, text_layout.bounds(), - font, - size, &value, - state, + &state, target, - line_height, ) } else { None @@ -1189,16 +1242,7 @@ where value.clone() }; - find_cursor_position( - renderer, - text_layout.bounds(), - font, - size, - &value, - state, - target, - line_height, - ) + find_cursor_position(text_layout.bounds(), &value, state, target) } else { None }; @@ -1210,17 +1254,9 @@ where if is_secure { state.cursor.select_all(value); } else { - let position = find_cursor_position( - renderer, - text_layout.bounds(), - font, - size, - value, - state, - target, - line_height, - ) - .unwrap_or(0); + let position = + find_cursor_position(text_layout.bounds(), value, state, target) + .unwrap_or(0); state.cursor.select_range( value.previous_start_of_word(position), @@ -1260,20 +1296,8 @@ where } else { value.clone() }; - let font: ::Font = - font.unwrap_or_else(|| renderer.default_font()); - - let position = find_cursor_position( - renderer, - text_layout.bounds(), - font, - size, - &value, - state, - target, - line_height, - ) - .unwrap_or(0); + let position = + find_cursor_position(text_layout.bounds(), &value, state, target).unwrap_or(0); state .cursor @@ -1303,6 +1327,8 @@ where focus.updated_at = Instant::now(); + update_cache(state, value); + return event::Status::Captured; } } @@ -1341,6 +1367,8 @@ where let message = (on_input)(editor.contents()); shell.publish(message); + + update_cache(state, value); } keyboard::KeyCode::Delete => { if platform::is_jump_modifier_pressed(modifiers) @@ -1359,6 +1387,8 @@ where let message = (on_input)(editor.contents()); shell.publish(message); + + update_cache(state, value); } keyboard::KeyCode::Left => { if platform::is_jump_modifier_pressed(modifiers) && !is_secure { @@ -1417,6 +1447,8 @@ where let message = (on_input)(editor.contents()); shell.publish(message); + + update_cache(state, value); } keyboard::KeyCode::V => { if state.keyboard_modifiers.command() { @@ -1445,6 +1477,8 @@ where shell.publish(message); state.is_pasting = Some(content); + + update_cache(state, value); } else { state.is_pasting = None; } @@ -1571,18 +1605,7 @@ where value.clone() }; - let font = font.unwrap_or_else(|| renderer.default_font()); - - find_cursor_position( - renderer, - text_layout.bounds(), - font, - size, - &value, - state, - target, - line_height, - ) + find_cursor_position(text_layout.bounds(), &value, state, target) } else { None }; @@ -1651,18 +1674,8 @@ where } else { value.clone() }; - let font = font.unwrap_or_else(|| renderer.default_font()); - find_cursor_position( - renderer, - text_layout.bounds(), - font, - size, - &value, - state, - target, - line_height, - ) + find_cursor_position(text_layout.bounds(), &value, state, target) } else { None }; @@ -1895,17 +1908,20 @@ pub fn draw<'a, Message>( // draw the label if it exists if let (Some(label_layout), Some(label)) = (label_layout, label) { - renderer.fill_text(Text { - content: label, - size: size.unwrap_or_else(|| renderer.default_size()), - font: font.unwrap_or_else(|| renderer.default_font()), - color: appearance.label_color, - bounds: label_layout.bounds(), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, - line_height, - shaping: text::Shaping::Advanced, - }); + renderer.fill_text( + Text { + content: label, + size: iced::Pixels(size.unwrap_or_else(|| renderer.default_size().0)), + font: font.unwrap_or_else(|| renderer.default_font()), + bounds: label_layout.bounds().size(), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + line_height, + shaping: text::Shaping::Advanced, + }, + bounds.position(), + appearance.label_color, + ); } let mut child_index = 0; let leading_icon_tree = children.get(child_index); @@ -1931,19 +1947,13 @@ pub fn draw<'a, Message>( let text = value.to_string(); let font = font.unwrap_or_else(|| renderer.default_font()); - let size = size.unwrap_or_else(|| renderer.default_size()); + let size = size.unwrap_or_else(|| renderer.default_size().0); let (cursor, offset) = if let Some(focus) = &state.is_focused { match state.cursor.state(value) { cursor::State::Index(position) => { - let (text_value_width, offset) = measure_cursor_and_scroll_offset( - renderer, - text_bounds, - value, - size, - position, - font, - ); + let (text_value_width, offset) = + measure_cursor_and_scroll_offset(&state.value, text_bounds, position); let is_cursor_visible = ((focus.now - focus.updated_at).as_millis() / CURSOR_BLINK_INTERVAL_MILLIS) % 2 @@ -1979,23 +1989,12 @@ pub fn draw<'a, Message>( let left = start.min(end); let right = end.max(start); - let (left_position, left_offset) = measure_cursor_and_scroll_offset( - renderer, - text_bounds, - value, - size, - left, - font, - ); + let value_paragraph = &state.value; + let (left_position, left_offset) = + measure_cursor_and_scroll_offset(value_paragraph, text_bounds, left); - let (right_position, right_offset) = measure_cursor_and_scroll_offset( - renderer, - text_bounds, - value, - size, - right, - font, - ); + let (right_position, right_offset) = + measure_cursor_and_scroll_offset(value_paragraph, text_bounds, right); let width = right_position - left_position; @@ -2030,12 +2029,7 @@ pub fn draw<'a, Message>( (None, 0.0) }; - let text_width = renderer.measure_width( - if text.is_empty() { placeholder } else { &text }, - size, - font, - text::Shaping::Advanced, - ); + let text_width = state.value.min_width(); let render = |renderer: &mut crate::Renderer| { if let Some((cursor, color)) = cursor { @@ -2044,25 +2038,30 @@ pub fn draw<'a, Message>( renderer.with_translation(Vector::ZERO, |_| {}); } - renderer.fill_text(Text { - content: if text.is_empty() { placeholder } else { &text }, - color: if text.is_empty() { - appearance.placeholder_color - } else { - appearance.text_color - }, - font, - bounds: Rectangle { - y: text_bounds.center_y(), - width: f32::INFINITY, - ..text_bounds + let bounds = Rectangle { + y: text_bounds.center_y(), + width: f32::INFINITY, + ..text_bounds + }; + let color = if text.is_empty() { + appearance.placeholder_color + } else { + appearance.text_color + }; + renderer.fill_text( + Text { + content: if text.is_empty() { placeholder } else { &text }, + font, + bounds: bounds.size(), + size: iced::Pixels(size), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, + line_height: text::LineHeight::default(), + shaping: text::Shaping::Advanced, }, - size, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, - line_height: text::LineHeight::default(), - shaping: text::Shaping::Advanced, - }); + bounds.position(), + color, + ); }; if text_width > text_bounds.width { @@ -2096,17 +2095,20 @@ pub fn draw<'a, Message>( // draw the helper text if it exists if let (Some(helper_text_layout), Some(helper_text)) = (helper_text_layout, helper_text) { - renderer.fill_text(Text { - content: helper_text, - size: helper_text_size, - font, - color: appearance.text_color, - bounds: helper_text_layout.bounds(), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, - line_height: helper_line_height, - shaping: text::Shaping::Advanced, - }); + renderer.fill_text( + Text { + content: helper_text, + size: iced::Pixels(helper_text_size), + font, + bounds: helper_text_layout.bounds().size(), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + line_height: helper_line_height, + shaping: text::Shaping::Advanced, + }, + helper_text_layout.bounds().position(), + appearance.text_color, + ); } } @@ -2165,6 +2167,9 @@ pub(crate) struct DndOfferState; #[derive(Debug, Default, Clone)] #[must_use] pub struct State { + pub value: crate::Paragraph, + pub placeholder: crate::Paragraph, + pub label: crate::Paragraph, is_focused: Option, dragging_state: Option, dnd_offer: DndOfferState, @@ -2214,6 +2219,10 @@ impl State { /// Creates a new [`State`], representing a focused [`TextInput`]. pub fn focused() -> Self { Self { + value: crate::Paragraph::new(), + placeholder: crate::Paragraph::new(), + label: crate::Paragraph::new(), + is_focused: None, dragging_state: None, #[allow(clippy::default_constructed_unit_structs)] @@ -2307,6 +2316,70 @@ impl operation::TextInput for State { } } +fn measure_cursor_and_scroll_offset( + paragraph: &impl text::Paragraph, + text_bounds: Rectangle, + cursor_index: usize, +) -> (f32, f32) { + let grapheme_position = paragraph + .grapheme_position(0, cursor_index) + .unwrap_or(Point::ORIGIN); + + let offset = ((grapheme_position.x + 5.0) - text_bounds.width).max(0.0); + + (grapheme_position.x, offset) +} + +/// Computes the position of the text cursor at the given X coordinate of +/// a [`TextInput`]. +fn find_cursor_position( + text_bounds: Rectangle, + value: &Value, + state: &State, + x: f32, +) -> Option { + let offset = offset(text_bounds, value, state); + let value = value.to_string(); + + let char_offset = state + .value + .hit_test(Point::new(x + offset, text_bounds.height / 2.0)) + .map(text::Hit::cursor)?; + + Some( + unicode_segmentation::UnicodeSegmentation::graphemes( + &value[..char_offset.min(value.len())], + true, + ) + .count(), + ) +} + +fn replace_paragraph( + state: &mut State, + layout: Layout<'_>, + value: &Value, + font: ::Font, + text_size: Pixels, + line_height: text::LineHeight, +) { + let mut children_layout = layout.children(); + let text_bounds = children_layout.next().unwrap().bounds(); + + state.value = crate::Paragraph::with_text(Text { + font, + line_height, + content: &value.to_string(), + bounds: Size::new(f32::INFINITY, text_bounds.height), + size: text_size, + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + shaping: text::Shaping::Advanced, + }); +} + +const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500; + mod platform { use iced_core::keyboard; @@ -2319,17 +2392,7 @@ mod platform { } } -fn offset( - renderer: &Renderer, - text_bounds: Rectangle, - font: Renderer::Font, - size: f32, - value: &Value, - state: &State, -) -> f32 -where - Renderer: text::Renderer, -{ +fn offset(text_bounds: Rectangle, value: &Value, state: &State) -> f32 { if state.is_focused() { let cursor = state.cursor(); @@ -2338,77 +2401,11 @@ where cursor::State::Selection { end, .. } => end, }; - let (_, offset) = measure_cursor_and_scroll_offset( - renderer, - text_bounds, - value, - size, - focus_position, - font, - ); + let (_, offset) = + measure_cursor_and_scroll_offset(&state.value, text_bounds, focus_position); offset } else { 0.0 } } - -fn measure_cursor_and_scroll_offset( - renderer: &Renderer, - text_bounds: Rectangle, - value: &Value, - size: f32, - cursor_index: usize, - font: Renderer::Font, -) -> (f32, f32) -where - Renderer: text::Renderer, -{ - let text_before_cursor = value.until(cursor_index).to_string(); - - let text_value_width = - renderer.measure_width(&text_before_cursor, size, font, text::Shaping::Advanced); - - let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0); - - (text_value_width, offset) -} - -/// Computes the position of the text cursor at the given X coordinate of -/// a [`TextInput`]. -#[allow(clippy::too_many_arguments)] -fn find_cursor_position( - renderer: &Renderer, - text_bounds: Rectangle, - font: Renderer::Font, - size: Option, - value: &Value, - state: &State, - x: f32, - line_height: text::LineHeight, -) -> Option -where - Renderer: text::Renderer, -{ - let size = size.unwrap_or_else(|| renderer.default_size()); - - let offset = offset(renderer, text_bounds, font, size, value, state); - let value = value.to_string(); - - let char_offset = renderer - .hit_test( - &value, - size, - line_height, - font, - Size::INFINITY, - text::Shaping::Advanced, - Point::new(x + offset, text_bounds.height / 2.0), - true, - ) - .map(text::Hit::cursor)?; - - Some(unicode_segmentation::UnicodeSegmentation::graphemes(&value[..char_offset], true).count()) -} - -const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500; From c4c0c218a4a9485ea6785d78ce35303cba1b7d6b Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 30 Nov 2023 17:21:56 -0500 Subject: [PATCH 02/10] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 9af6bbb55c3..14ef12f4290 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 9af6bbb55c3443a21b835b01f20b2c0032cb50bc +Subproject commit 14ef12f429078be78c45cd3348162b5f8459e993 From 27bb2007f55e0913627c46318e171172ed450931 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 30 Nov 2023 17:47:39 -0500 Subject: [PATCH 03/10] fix: avoid text_input paragraph updates in diff --- src/widget/text_input/input.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 03286cba47f..993dd98746d 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -483,22 +483,6 @@ where fn diff(&mut self, tree: &mut Tree) { let state = tree.state.downcast_mut::(); - // TODO get values from renderer somehow. - replace_paragraph( - state, - Layout::new(&layout::Node::with_children( - Size::INFINITY, - vec![layout::Node::with_children( - Size::INFINITY, - vec![layout::Node::default()], - )], - )), - &self.value, - self.font.unwrap_or(crate::font::FONT), - Pixels(self.size.unwrap_or(14.0)), - self.line_height, - ); - // Unfocus text input if it becomes disabled if self.on_input.is_none() { state.last_click = None; From 934f26578e929e41242b7e3e2134838dc3796fb4 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 1 Dec 2023 14:18:53 -0500 Subject: [PATCH 04/10] update for multi-menu --- iced | 2 +- src/widget/dropdown/multi/menu.rs | 60 +++++++----- src/widget/dropdown/multi/widget.rs | 143 ++++++++++++++++++---------- 3 files changed, 133 insertions(+), 72 deletions(-) diff --git a/iced b/iced index 14ef12f4290..d67f1a1c79c 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 14ef12f429078be78c45cd3348162b5f8459e993 +Subproject commit d67f1a1c79cf3dcb378520ef7e4e9ab653f75bea diff --git a/src/widget/dropdown/multi/menu.rs b/src/widget/dropdown/multi/menu.rs index cd510ddc889..aac00107f84 100644 --- a/src/widget/dropdown/multi/menu.rs +++ b/src/widget/dropdown/multi/menu.rs @@ -190,7 +190,12 @@ impl<'a, Message: 'a> Overlay<'a, Message> { } impl<'a, Message> iced_core::Overlay for Overlay<'a, Message> { - fn layout(&self, renderer: &crate::Renderer, bounds: Size, position: Point) -> layout::Node { + fn layout( + &mut self, + renderer: &crate::Renderer, + bounds: Size, + position: Point, + ) -> layout::Node { let space_below = bounds.height - (position.y + self.target_height); let space_above = position.y; @@ -207,7 +212,9 @@ impl<'a, Message> iced_core::Overlay for Overlay<'a, M ) .width(self.width); - let mut node = self.container.layout(renderer, &limits); + let mut node = self + .container + .layout(&mut self.state.children[0], renderer, &limits); node.move_to(if space_below > space_above { position + Vector::new(0.0, self.target_height) @@ -296,13 +303,18 @@ where Length::Shrink } - fn layout(&self, renderer: &crate::Renderer, limits: &layout::Limits) -> layout::Node { + fn layout( + &self, + _tree: &mut Tree, + renderer: &crate::Renderer, + limits: &layout::Limits, + ) -> layout::Node { use std::f32; let limits = limits.width(Length::Fill).height(Length::Shrink); let text_size = self .text_size - .unwrap_or_else(|| text::Renderer::default_size(renderer)); + .unwrap_or_else(|| text::Renderer::default_size(renderer).0); let text_line_height = self.text_line_height.to_absolute(Pixels(text_size)); @@ -359,7 +371,7 @@ where if let Some(cursor_position) = cursor.position_in(bounds) { let text_size = self .text_size - .unwrap_or_else(|| text::Renderer::default_size(renderer)); + .unwrap_or_else(|| text::Renderer::default_size(renderer).0); let text_line_height = f32::from(self.text_line_height.to_absolute(Pixels(text_size))); @@ -406,7 +418,7 @@ where if let Some(cursor_position) = cursor.position_in(bounds) { let text_size = self .text_size - .unwrap_or_else(|| text::Renderer::default_size(renderer)); + .unwrap_or_else(|| text::Renderer::default_size(renderer).0); let text_line_height = f32::from(self.text_line_height.to_absolute(Pixels(text_size))); @@ -488,7 +500,7 @@ where let text_size = self .text_size - .unwrap_or_else(|| text::Renderer::default_size(renderer)); + .unwrap_or_else(|| text::Renderer::default_size(renderer).0); let offset = viewport.y - bounds.y; @@ -571,24 +583,26 @@ where (appearance.text_color, crate::font::FONT) }; + let bounds = Rectangle { + x: bounds.x + self.padding.left, + y: bounds.y + self.padding.top, + width: bounds.width, + height: bounds.height, + }; text::Renderer::fill_text( renderer, Text { content: option.as_ref(), - bounds: Rectangle { - x: bounds.x + self.padding.left, - y: bounds.center_y(), - width: bounds.width, - ..bounds - }, - size: text_size, + bounds: bounds.size(), + size: iced::Pixels(text_size), line_height: self.text_line_height, font, - color, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, shaping: text::Shaping::Advanced, }, + bounds.position(), + color, ); } @@ -618,23 +632,25 @@ where } OptionElement::Description(description) => { + let bounds = Rectangle { + x: bounds.center_x(), + y: bounds.center_y(), + ..bounds + }; text::Renderer::fill_text( renderer, Text { content: description.as_ref(), - bounds: Rectangle { - x: bounds.center_x(), - y: bounds.center_y(), - ..bounds - }, - size: text_size, + bounds: bounds.size(), + size: iced::Pixels(text_size), line_height: text::LineHeight::Absolute(Pixels(text_line_height + 4.0)), font: crate::font::FONT, - color: appearance.description_color, horizontal_alignment: alignment::Horizontal::Center, vertical_alignment: alignment::Vertical::Center, shaping: text::Shaping::Advanced, }, + bounds.position(), + appearance.description_color, ); } } diff --git a/src/widget/dropdown/multi/widget.rs b/src/widget/dropdown/multi/widget.rs index df5d618bd93..3f59fcc24b2 100644 --- a/src/widget/dropdown/multi/widget.rs +++ b/src/widget/dropdown/multi/widget.rs @@ -6,7 +6,7 @@ use super::menu::{self, Menu}; use crate::widget::icon; use derive_setters::Setters; use iced_core::event::{self, Event}; -use iced_core::text::{self, Text}; +use iced_core::text::{self, Paragraph, Text}; use iced_core::widget::tree::{self, Tree}; use iced_core::{alignment, keyboard, layout, mouse, overlay, renderer, svg, touch}; use iced_core::{Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Widget}; @@ -78,7 +78,12 @@ impl<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static> Length::Shrink } - fn layout(&self, renderer: &crate::Renderer, limits: &layout::Limits) -> layout::Node { + fn layout( + &self, + tree: &mut Tree, + renderer: &crate::Renderer, + limits: &layout::Limits, + ) -> layout::Node { layout( renderer, limits, @@ -88,11 +93,16 @@ impl<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static> self.text_size.unwrap_or(14.0), self.text_line_height, self.font, - self.selections - .selected - .as_ref() - .and_then(|id| self.selections.get(id)) - .map(AsRef::as_ref), + self.selections.selected.as_ref().and_then(|id| { + self.selections.get(id).map(AsRef::as_ref).zip( + tree.state + .downcast_mut::>() + .selections + .iter_mut() + .find(|(i, _)| i == id) + .map(|(_, p)| p), + ) + }), ) } @@ -177,6 +187,7 @@ impl<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static> self.padding, self.text_size.unwrap_or(14.0), self.font, + self.text_line_height, self.selections, &self.on_selected, ) @@ -199,6 +210,8 @@ pub struct State { keyboard_modifiers: keyboard::Modifiers, is_open: bool, hovered_option: Option, + selections: Vec<(Item, crate::Paragraph)>, + descriptions: Vec, } impl State { @@ -217,6 +230,8 @@ impl State { keyboard_modifiers: keyboard::Modifiers::default(), is_open: false, hovered_option: None, + selections: Vec::new(), + descriptions: Vec::new(), } } } @@ -238,7 +253,7 @@ pub fn layout( text_size: f32, text_line_height: text::LineHeight, font: Option, - selection: Option<&str>, + selection: Option<(&str, &mut crate::Paragraph)>, ) -> layout::Node { use std::f32; @@ -246,16 +261,18 @@ pub fn layout( let max_width = match width { Length::Shrink => { - let measure = |label: &str| -> f32 { - let width = text::Renderer::measure_width( - renderer, - label, - text_size, - font.unwrap_or_else(|| text::Renderer::default_font(renderer)), - text::Shaping::Advanced, - ); - - width.round() + let measure = move |(label, paragraph): (_, &mut crate::Paragraph)| -> f32 { + paragraph.update(Text { + content: label, + bounds: Size::new(f32::MAX, f32::MAX), + size: iced::Pixels(text_size), + line_height: text_line_height, + font: font.unwrap_or_else(|| text::Renderer::default_font(renderer)), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + shaping: text::Shaping::Advanced, + }); + paragraph.min_width().round() }; selection.map(measure).unwrap_or_default() @@ -359,6 +376,7 @@ pub fn overlay<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static padding: Padding, text_size: f32, font: Option, + text_line_height: text::LineHeight, selections: &'a super::Model, on_selected: &'a dyn Fn(Item) -> Message, ) -> Option> { @@ -378,38 +396,62 @@ pub fn overlay<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static None, ) .width({ - let measure = |label: &str| -> f32 { - let width = text::Renderer::measure_width( - renderer, - label, - text_size, - crate::font::FONT, - text::Shaping::Advanced, - ); - - width.round() + let measure = |label: &str, paragraph: &mut crate::Paragraph| { + paragraph.update(Text { + content: label, + bounds: Size::new(f32::MAX, f32::MAX), + size: iced::Pixels(text_size), + line_height: text_line_height, + font: font.unwrap_or_else(|| text::Renderer::default_font(renderer)), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + shaping: text::Shaping::Advanced, + }); + paragraph.min_width().round() }; - let measure_description = |label: &str| -> f32 { - let width = text::Renderer::measure_width( - renderer, - label, - text_size + 4.0, - crate::font::FONT, - text::Shaping::Advanced, - ); - - width.round() + let measure_description = |label: &str, paragraph: &mut crate::Paragraph| { + paragraph.update(Text { + content: label, + bounds: Size::new(f32::MAX, f32::MAX), + size: iced::Pixels(text_size + 4.0), + line_height: text_line_height, + font: font.unwrap_or_else(|| text::Renderer::default_font(renderer)), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + shaping: text::Shaping::Advanced, + }); + paragraph.min_width().round() }; + let mut desc_count = 0; selections .elements() .map(|element| match element { super::menu::OptionElement::Description(desc) => { - measure_description(desc.as_ref()) + let paragraph = if state.descriptions.len() > desc_count { + &mut state.descriptions[desc_count] + } else { + state.descriptions.push(crate::Paragraph::new()); + state.descriptions.last_mut().unwrap() + }; + desc_count += 1; + measure_description(desc.as_ref(), paragraph) } - super::menu::OptionElement::Option((option, _item)) => measure(option.as_ref()), + super::menu::OptionElement::Option((option, item)) => { + let paragraph = if let Some(index) = + state.selections.iter().position(|(i, _)| i == item) + { + &mut state.selections[index].1 + } else { + state + .selections + .push((item.clone(), crate::Paragraph::new())); + &mut state.selections.last_mut().unwrap().1 + }; + measure(option.as_ref(), paragraph) + } super::menu::OptionElement::Separator => 1.0, }) @@ -482,26 +524,29 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>( } if let Some(content) = selected.map(AsRef::as_ref) { - let text_size = text_size.unwrap_or_else(|| text::Renderer::default_size(renderer)); + let text_size = text_size.unwrap_or_else(|| text::Renderer::default_size(renderer).0); + + let bounds = Rectangle { + x: bounds.x + padding.left, + y: bounds.center_y(), + width: bounds.width - padding.horizontal(), + height: f32::from(text_line_height.to_absolute(Pixels(text_size))), + }; text::Renderer::fill_text( renderer, Text { content, - size: text_size, + size: iced::Pixels(text_size), line_height: text_line_height, font, - color: style.text_color, - bounds: Rectangle { - x: bounds.x + padding.left, - y: bounds.center_y(), - width: bounds.width - padding.horizontal(), - height: f32::from(text_line_height.to_absolute(Pixels(text_size))), - }, + bounds: bounds.size(), horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, shaping: text::Shaping::Advanced, }, + bounds.position(), + style.text_color, ); } } From 309deb6296364022a37d69171050e174ae704eeb Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 1 Dec 2023 15:52:33 -0500 Subject: [PATCH 05/10] fix: specify that lineheight is absolute for heading --- src/widget/text.rs | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/widget/text.rs b/src/widget/text.rs index 6712d84cdc2..60a6cbd8944 100644 --- a/src/widget/text.rs +++ b/src/widget/text.rs @@ -1,5 +1,6 @@ use crate::Renderer; pub use iced::widget::Text; +use iced_core::text::LineHeight; use std::borrow::Cow; /// Creates a new [`Text`] widget with the provided content. @@ -27,30 +28,36 @@ pub enum Typography { pub fn title1<'a>(text: impl Into> + 'a) -> Text<'a, Renderer> { Text::new(text) .size(32.0) - .line_height(44.0) + .line_height(LineHeight::Absolute(44.0.into())) .font(crate::font::FONT_LIGHT) } /// [`Text`] widget with the Title 2 typography preset. pub fn title2<'a>(text: impl Into> + 'a) -> Text<'a, Renderer> { - Text::new(text).size(28.0).line_height(36.0) + Text::new(text) + .size(28.0) + .line_height(LineHeight::Absolute(36.0.into())) } /// [`Text`] widget with the Title 3 typography preset. pub fn title3<'a>(text: impl Into> + 'a) -> Text<'a, Renderer> { - Text::new(text).size(24.0).line_height(32.0) + Text::new(text) + .size(24.0) + .line_height(LineHeight::Absolute(32.0.into())) } /// [`Text`] widget with the Title 4 typography preset. pub fn title4<'a>(text: impl Into> + 'a) -> Text<'a, Renderer> { - Text::new(text).size(20.0).line_height(28.0) + Text::new(text) + .size(20.0) + .line_height(LineHeight::Absolute(28.0.into())) } /// [`Text`] widget with the Heading typography preset. pub fn heading<'a>(text: impl Into> + 'a) -> Text<'a, Renderer> { Text::new(text) .size(14.0) - .line_height(20.0) + .line_height(LineHeight::Absolute(iced::Pixels(20.0))) .font(crate::font::FONT_SEMIBOLD) } @@ -58,24 +65,28 @@ pub fn heading<'a>(text: impl Into> + 'a) -> Text<'a, Renderer> { pub fn caption_heading<'a>(text: impl Into> + 'a) -> Text<'a, Renderer> { Text::new(text) .size(10.0) - .line_height(14.0) + .line_height(LineHeight::Absolute(iced::Pixels(14.0))) .font(crate::font::FONT_SEMIBOLD) } /// [`Text`] widget with the Body typography preset. pub fn body<'a>(text: impl Into> + 'a) -> Text<'a, Renderer> { - Text::new(text).size(14.0).line_height(20.0) + Text::new(text) + .size(14.0) + .line_height(LineHeight::Absolute(20.0.into())) } /// [`Text`] widget with the Caption typography preset. pub fn caption<'a>(text: impl Into> + 'a) -> Text<'a, Renderer> { - Text::new(text).size(10.0).line_height(14.0) + Text::new(text) + .size(10.0) + .line_height(LineHeight::Absolute(14.0.into())) } /// [`Text`] widget with the Monotext typography preset. pub fn monotext<'a>(text: impl Into> + 'a) -> Text<'a, Renderer> { Text::new(text) .size(14.0) - .line_height(20.0) + .line_height(LineHeight::Absolute(20.0.into())) .font(crate::font::FONT_MONO_REGULAR) } From 0a82a26a861bac0fde708f053905252eacfc161c Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 1 Dec 2023 16:24:37 -0500 Subject: [PATCH 06/10] fix: applets feature --- src/applet/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 04c87b2a464..e6913bb3fab 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -257,7 +257,7 @@ pub fn run(autosize: bool, flags: App::Flags) -> iced::Result iced.antialiasing = settings.antialiasing; iced.default_font = settings.default_font; - iced.default_text_size = settings.default_text_size; + iced.default_text_size = settings.default_text_size.into(); iced.id = Some(App::APP_ID.to_owned()); { From f1b76946990a7412ae13de654d6e577f176a58c1 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Sun, 3 Dec 2023 01:27:06 -0500 Subject: [PATCH 07/10] chore: implement text editor StyleSheet --- src/theme/style/iced.rs | 83 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 27635a5acd6..ba58c1368cb 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -1084,3 +1084,86 @@ impl crate::widget::card::style::StyleSheet for Theme { } } } + +#[derive(Default)] +pub enum TextEditor { + #[default] + Default, + Custom(Box>), +} + +impl iced_style::text_editor::StyleSheet for Theme { + type Style = TextEditor; + + fn active(&self, style: &Self::Style) -> iced_style::text_editor::Appearance { + if let TextEditor::Custom(style) = style { + return style.active(self); + } + + let cosmic = self.cosmic(); + iced_style::text_editor::Appearance { + background: iced::Color::from(cosmic.bg_color()).into(), + border_radius: cosmic.corner_radii.radius_0.into(), + border_width: f32::from(cosmic.space_xxxs()), + border_color: iced::Color::from(cosmic.bg_divider()), + } + } + + fn focused(&self, style: &Self::Style) -> iced_style::text_editor::Appearance { + if let TextEditor::Custom(style) = style { + return style.focused(self); + } + + let cosmic = self.cosmic(); + iced_style::text_editor::Appearance { + background: iced::Color::from(cosmic.bg_color()).into(), + border_radius: cosmic.corner_radii.radius_0.into(), + border_width: f32::from(cosmic.space_xxxs()), + border_color: iced::Color::from(cosmic.accent.base), + } + } + + fn placeholder_color(&self, style: &Self::Style) -> Color { + if let TextEditor::Custom(style) = style { + return style.placeholder_color(self); + } + let palette = self.cosmic(); + let mut neutral_9 = palette.palette.neutral_9; + neutral_9.alpha = 0.7; + neutral_9.into() + } + + fn value_color(&self, style: &Self::Style) -> Color { + if let TextEditor::Custom(style) = style { + return style.value_color(self); + } + let palette = self.cosmic(); + + palette.palette.neutral_9.into() + } + + fn disabled_color(&self, style: &Self::Style) -> Color { + if let TextEditor::Custom(style) = style { + return style.disabled_color(self); + } + let palette = self.cosmic(); + let mut neutral_9 = palette.palette.neutral_9; + neutral_9.alpha = 0.5; + neutral_9.into() + } + + fn selection_color(&self, style: &Self::Style) -> Color { + if let TextEditor::Custom(style) = style { + return style.selection_color(self); + } + let cosmic = self.cosmic(); + cosmic.accent.base.into() + } + + fn disabled(&self, style: &Self::Style) -> iced_style::text_editor::Appearance { + if let TextEditor::Custom(style) = style { + return style.disabled(self); + } + self.active(style) + } +} From acb5aa682b64b70e6768f9c1be8249560b66b028 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 4 Dec 2023 10:14:50 -0500 Subject: [PATCH 08/10] clippy fixes --- src/keyboard_nav.rs | 2 +- src/widget/dropdown/menu/mod.rs | 2 +- src/widget/dropdown/widget.rs | 8 ++++---- src/widget/flex_row/layout.rs | 2 +- src/widget/menu/menu_inner.rs | 2 +- src/widget/segmented_button/widget.rs | 2 +- src/widget/text_input/input.rs | 14 +++++++------- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/keyboard_nav.rs b/src/keyboard_nav.rs index 1f3e7ffdcf2..32bea5b2ba0 100644 --- a/src/keyboard_nav.rs +++ b/src/keyboard_nav.rs @@ -6,7 +6,7 @@ use iced::{ event, keyboard::{self, KeyCode}, - mouse, subscription, Command, Event, Subscription, + mouse, Command, Event, Subscription, }; use iced_core::{ widget::{operation, Id, Operation}, diff --git a/src/widget/dropdown/menu/mod.rs b/src/widget/dropdown/menu/mod.rs index 589ff295617..5c5f490b983 100644 --- a/src/widget/dropdown/menu/mod.rs +++ b/src/widget/dropdown/menu/mod.rs @@ -209,7 +209,7 @@ impl<'a, Message> iced_core::Overlay for Overlay<'a, M ) .width(self.width); - let mut node = self.container.layout(&mut self.state, renderer, &limits); + let mut node = self.container.layout(self.state, renderer, &limits); node.move_to(if space_below > space_above { position + Vector::new(0.0, self.target_height) diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index 632eaf56ed8..37306098e5f 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -413,13 +413,13 @@ pub fn mouse_interaction(layout: Layout<'_>, cursor: mouse::Cursor) -> mouse::In #[allow(clippy::too_many_arguments)] pub fn overlay<'a, S: AsRef, Message: 'a>( layout: Layout<'_>, - renderer: &crate::Renderer, + _renderer: &crate::Renderer, state: &'a mut State, gap: f32, padding: Padding, text_size: f32, - text_line_height: text::LineHeight, - font: Option, + _text_line_height: text::LineHeight, + _font: Option, selections: &'a [S], selected_option: Option, on_selected: &'a dyn Fn(usize) -> Message, @@ -440,7 +440,7 @@ pub fn overlay<'a, S: AsRef, Message: 'a>( None, ) .width({ - let measure = |label: &str, selection_paragraph: &mut crate::Paragraph| -> f32 { + let measure = |_label: &str, selection_paragraph: &mut crate::Paragraph| -> f32 { selection_paragraph.min_width().round() }; diff --git a/src/widget/flex_row/layout.rs b/src/widget/flex_row/layout.rs index ba4de64ec88..bcd9e4a93eb 100644 --- a/src/widget/flex_row/layout.rs +++ b/src/widget/flex_row/layout.rs @@ -28,7 +28,7 @@ pub fn resolve( let mut row_buffer = Vec::::with_capacity(8); - for (child, tree) in items.iter().zip(tree.into_iter()) { + for (child, tree) in items.iter().zip(tree.iter_mut()) { // Calculate the dimensions of the item. let child_node = child.as_widget().layout(tree, renderer, &limits); let size = child_node.size(); diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index 3608cf33bb6..4c748f30b8c 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -478,7 +478,7 @@ where let active_tree = &mut tree_children[active_root]; state.menu_states.iter().enumerate().fold( (root, Vec::new()), - |(menu_root, mut nodes), (i, ms)| { + |(menu_root, mut nodes), (_i, ms)| { let slice = ms.slice(bounds, overlay_offset, self.item_height); let start_index = slice.start_index; let end_index = slice.end_index; diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index ae4fd78dc64..d2847c43931 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -226,7 +226,7 @@ where &self, state: &mut LocalState, renderer: &Renderer, - bounds: Size, + _bounds: Size, ) -> (f32, f32) { let mut width = 0.0f32; let mut height = 0.0f32; diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 993dd98746d..31468188f42 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -196,7 +196,6 @@ pub struct TextInput<'a, Message> { leading_icon: Option>, trailing_icon: Option>, style: <::Theme as StyleSheet>::Style, - // (text_input::State, mime_type, dnd_action) -> Message on_create_dnd_source: Option Message + 'a>>, on_dnd_command_produced: Option Message + 'a>>, surface_ids: Option<(window::Id, window::Id)>, @@ -1074,10 +1073,10 @@ pub fn update<'a, Message>( on_paste: Option<&dyn Fn(String) -> Message>, on_submit: &Option, state: impl FnOnce() -> &'a mut State, - on_start_dnd_source: Option<&dyn Fn(State) -> Message>, - _dnd_icon: bool, - on_dnd_command_produced: Option<&dyn Fn(DnDCommand) -> Message>, - surface_ids: Option<(window::Id, window::Id)>, + #[allow(unused_variables)] on_start_dnd_source: Option<&dyn Fn(State) -> Message>, + #[allow(unused_variables)] dnd_icon: bool, + #[allow(unused_variables)] on_dnd_command_produced: Option<&dyn Fn(DnDCommand) -> Message>, + #[allow(unused_variables)] surface_ids: Option<(window::Id, window::Id)>, line_height: text::LineHeight, layout: Layout<'_>, ) -> event::Status @@ -1203,7 +1202,7 @@ where find_cursor_position( text_layout.bounds(), &value, - &state, + state, target, ) } else { @@ -2156,6 +2155,7 @@ pub struct State { pub label: crate::Paragraph, is_focused: Option, dragging_state: Option, + #[cfg(feature = "wayland")] dnd_offer: DndOfferState, is_pasting: Option, last_click: Option, @@ -2209,7 +2209,7 @@ impl State { is_focused: None, dragging_state: None, - #[allow(clippy::default_constructed_unit_structs)] + #[cfg(feature = "wayland")] dnd_offer: DndOfferState::default(), is_pasting: None, last_click: None, From fce440b72237fb34b94c283483fc4e92d2410e21 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 4 Dec 2023 16:49:14 -0500 Subject: [PATCH 09/10] chore: update iced --- examples/cosmic/src/window/demo.rs | 40 --------------------------- iced | 2 +- src/widget/context_drawer/overlay.rs | 1 + src/widget/dropdown/menu/mod.rs | 2 ++ src/widget/dropdown/multi/menu.rs | 3 ++ src/widget/dropdown/multi/widget.rs | 5 +++- src/widget/dropdown/widget.rs | 5 +++- src/widget/menu/menu_inner.rs | 8 +++++- src/widget/popover.rs | 8 +++++- src/widget/segmented_button/widget.rs | 1 + src/widget/text_input/input.rs | 3 ++ 11 files changed, 33 insertions(+), 45 deletions(-) diff --git a/examples/cosmic/src/window/demo.rs b/examples/cosmic/src/window/demo.rs index 824b243d445..6329dd3140c 100644 --- a/examples/cosmic/src/window/demo.rs +++ b/examples/cosmic/src/window/demo.rs @@ -475,49 +475,9 @@ impl State { &self.entry_value, ) .on_input(Message::InputChanged) - // .on_submit(Message::Activate(None)) .size(20) .id(INPUT_ID.clone()) .into(), - cosmic::widget::text_input("test", &self.entry_value) - .on_clear(Message::InputChanged("".to_string())) - .width(Length::Fill) - .on_input(Message::InputChanged) - .into(), - cosmic::widget::text_input("test", &self.entry_value) - .width(Length::Fixed(600.0)) - .padding(32) - .on_input(Message::InputChanged) - .into(), - cosmic::widget::search_input("test", &self.entry_value) - .on_clear(Message::InputChanged("".to_string())) - .width(Length::Fill) - .on_input(Message::InputChanged) - .into(), - cosmic::widget::text_input("test", &self.entry_value) - .width(Length::Fixed(600.0)) - .on_input(Message::InputChanged) - .into(), - cosmic::widget::search_input("test", &self.entry_value) - .width(Length::Fixed(100.0)) - .on_input(Message::InputChanged) - .into(), - cosmic::widget::search_input("test", &self.entry_value) - .on_clear(Message::InputChanged("".to_string())) - .padding([24, 48]) - .width(Length::Fixed(400.0)) - .on_input(Message::InputChanged) - .into(), - cosmic::widget::search_input("test", &self.entry_value) - .on_clear(Message::InputChanged("".to_string())) - .width(Length::Fixed(400.0)) - .on_input(Message::InputChanged) - .into(), - cosmic::widget::search_input("test", &self.entry_value) - .on_clear(Message::InputChanged("".to_string())) - .width(Length::Fixed(800.0)) - .on_input(Message::InputChanged) - .into(), self.color_picker_model .picker_button(Message::ColorPickerUpdate, None) .width(Length::Fixed(128.0)) diff --git a/iced b/iced index d67f1a1c79c..33b2fd967ad 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit d67f1a1c79cf3dcb378520ef7e4e9ab653f75bea +Subproject commit 33b2fd967ada2d2c86eb1b57eb4997719774499e diff --git a/src/widget/context_drawer/overlay.rs b/src/widget/context_drawer/overlay.rs index f2a2bc79f6e..412da4836e7 100644 --- a/src/widget/context_drawer/overlay.rs +++ b/src/widget/context_drawer/overlay.rs @@ -25,6 +25,7 @@ where renderer: &crate::Renderer, bounds: Size, position: Point, + _translation: iced::Vector, ) -> layout::Node { let limits = layout::Limits::new(Size::ZERO, bounds) .width(self.width) diff --git a/src/widget/dropdown/menu/mod.rs b/src/widget/dropdown/menu/mod.rs index 5c5f490b983..192a76e7040 100644 --- a/src/widget/dropdown/menu/mod.rs +++ b/src/widget/dropdown/menu/mod.rs @@ -192,6 +192,7 @@ impl<'a, Message> iced_core::Overlay for Overlay<'a, M renderer: &crate::Renderer, bounds: Size, position: Point, + _translation: iced::Vector, ) -> layout::Node { let space_below = bounds.height - (position.y + self.target_height); let space_above = position.y; @@ -514,6 +515,7 @@ impl<'a, S: AsRef, Message> Widget for List<'a, S }, bounds.position(), color, + *viewport, ); } } diff --git a/src/widget/dropdown/multi/menu.rs b/src/widget/dropdown/multi/menu.rs index aac00107f84..cf5f5280820 100644 --- a/src/widget/dropdown/multi/menu.rs +++ b/src/widget/dropdown/multi/menu.rs @@ -195,6 +195,7 @@ impl<'a, Message> iced_core::Overlay for Overlay<'a, M renderer: &crate::Renderer, bounds: Size, position: Point, + _translation: iced::Vector, ) -> layout::Node { let space_below = bounds.height - (position.y + self.target_height); let space_above = position.y; @@ -603,6 +604,7 @@ where }, bounds.position(), color, + *viewport, ); } @@ -651,6 +653,7 @@ where }, bounds.position(), appearance.description_color, + *viewport, ); } } diff --git a/src/widget/dropdown/multi/widget.rs b/src/widget/dropdown/multi/widget.rs index 3f59fcc24b2..8b00f4dd7e1 100644 --- a/src/widget/dropdown/multi/widget.rs +++ b/src/widget/dropdown/multi/widget.rs @@ -147,7 +147,7 @@ impl<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static> _style: &iced_core::renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, - _viewport: &Rectangle, + viewport: &Rectangle, ) { let font = self .font @@ -168,6 +168,7 @@ impl<'a, S: AsRef, Message: 'a, Item: Clone + PartialEq + 'static> .as_ref() .and_then(|id| self.selections.get(id)), tree.state.downcast_ref::>(), + viewport, ); } @@ -486,6 +487,7 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>( font: crate::font::Font, selected: Option<&'a S>, state: &'a State, + viewport: &Rectangle, ) where S: AsRef + 'a, { @@ -547,6 +549,7 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>( }, bounds.position(), style.text_color, + *viewport, ); } } diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index 37306098e5f..11fbbeb48b1 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -192,7 +192,7 @@ impl<'a, S: AsRef, Message: 'a> Widget for Dropdo _style: &iced_core::renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, - _viewport: &Rectangle, + viewport: &Rectangle, ) { let font = self .font @@ -209,6 +209,7 @@ impl<'a, S: AsRef, Message: 'a> Widget for Dropdo font, self.selected.and_then(|id| self.selections.get(id)), tree.state.downcast_ref::(), + viewport, ); } @@ -479,6 +480,7 @@ pub fn draw<'a, S>( font: crate::font::Font, selected: Option<&'a S>, state: &'a State, + viewport: &Rectangle, ) where S: AsRef + 'a, { @@ -538,6 +540,7 @@ pub fn draw<'a, S>( }, bounds.position(), style.text_color, + *viewport, ); } } diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index 4c748f30b8c..68dfd35b42a 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -466,7 +466,13 @@ where Renderer: renderer::Renderer, Renderer::Theme: StyleSheet, { - fn layout(&mut self, renderer: &Renderer, bounds: Size, position: Point) -> Node { + fn layout( + &mut self, + renderer: &Renderer, + bounds: Size, + position: Point, + _translation: iced::Vector, + ) -> Node { // layout children let state = self.tree.state.downcast_mut::(); let overlay_offset = Point::ORIGIN - position; diff --git a/src/widget/popover.rs b/src/widget/popover.rs index 41a6e5c9305..9617395defb 100644 --- a/src/widget/popover.rs +++ b/src/widget/popover.rs @@ -209,7 +209,13 @@ impl<'a, 'b, Message, Renderer> overlay::Overlay where Renderer: iced_core::Renderer, { - fn layout(&mut self, renderer: &Renderer, bounds: Size, mut position: Point) -> layout::Node { + fn layout( + &mut self, + renderer: &Renderer, + bounds: Size, + mut position: Point, + _translation: iced::Vector, + ) -> layout::Node { let limits = layout::Limits::new(Size::UNIT, bounds); let mut node = self .content diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index d2847c43931..e6197505aa4 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -644,6 +644,7 @@ where }, bounds.position(), status_appearance.text_color, + *viewport, ); } diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 31468188f42..02386a03636 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -1904,6 +1904,7 @@ pub fn draw<'a, Message>( }, bounds.position(), appearance.label_color, + *viewport, ); } let mut child_index = 0; @@ -2044,6 +2045,7 @@ pub fn draw<'a, Message>( }, bounds.position(), color, + *viewport, ); }; @@ -2091,6 +2093,7 @@ pub fn draw<'a, Message>( }, helper_text_layout.bounds().position(), appearance.text_color, + *viewport, ); } } From dfb04d4ea5c61e255c2c607d36bd7e6e419eae9d Mon Sep 17 00:00:00 2001 From: wiiznokes <78230769+wiiznokes@users.noreply.github.com> Date: Thu, 16 Nov 2023 22:01:50 +0100 Subject: [PATCH 10/10] Update mod.rs --- src/app/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index f775aba153d..5a487fe804e 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -362,7 +362,7 @@ where type Executor: iced_futures::Executor; /// Argument received [`Application::new`]. - type Flags: Clone; + type Flags; /// Message type specific to our app. type Message: Clone + std::fmt::Debug + Send + 'static;