diff options
55 files changed, 451 insertions, 181 deletions
@@ -129,7 +129,7 @@ cosmic-text = "0.10" dark-light = "1.0" futures = "0.3" glam = "0.25" -glyphon = "0.5" +glyphon = { git = "https://github.com/hecrj/glyphon.git", rev = "ceed55403ce53e120ce9d1fae17dcfe388726118" } guillotiere = "0.6" half = "2.2" image = "0.24" @@ -155,7 +155,6 @@ thiserror = "1.0" tiny-skia = "0.11" tokio = "1.0" tracing = "0.1" -xxhash-rust = { version = "0.8", features = ["xxh3"] } unicode-segmentation = "1.0" wasm-bindgen-futures = "0.4" wasm-timer = "0.2" diff --git a/core/Cargo.toml b/core/Cargo.toml index 32d233ee..d3529d98 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -21,10 +21,10 @@ log.workspace = true num-traits.workspace = true once_cell.workspace = true palette.workspace = true +rustc-hash.workspace = true smol_str.workspace = true thiserror.workspace = true web-time.workspace = true -xxhash-rust.workspace = true dark-light.workspace = true dark-light.optional = true diff --git a/core/src/hasher.rs b/core/src/hasher.rs index a13d78af..13180e41 100644 --- a/core/src/hasher.rs +++ b/core/src/hasher.rs @@ -1,7 +1,7 @@ /// The hasher used to compare layouts. #[allow(missing_debug_implementations)] // Doesn't really make sense to have debug on the hasher state anyways. #[derive(Default)] -pub struct Hasher(xxhash_rust::xxh3::Xxh3); +pub struct Hasher(rustc_hash::FxHasher); impl core::hash::Hasher for Hasher { fn write(&mut self, bytes: &[u8]) { diff --git a/core/src/image.rs b/core/src/image.rs index 32b95f03..dc74e5c1 100644 --- a/core/src/image.rs +++ b/core/src/image.rs @@ -1,6 +1,7 @@ //! Load and draw raster graphics. -use crate::{Hasher, Rectangle, Size}; +use crate::{Rectangle, Size}; +use rustc_hash::FxHasher; use std::hash::{Hash, Hasher as _}; use std::path::PathBuf; use std::sync::Arc; @@ -50,7 +51,7 @@ impl Handle { } fn from_data(data: Data) -> Handle { - let mut hasher = Hasher::default(); + let mut hasher = FxHasher::default(); data.hash(&mut hasher); Handle { diff --git a/core/src/lib.rs b/core/src/lib.rs index d076413e..832b2d2d 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -41,7 +41,6 @@ mod background; mod color; mod content_fit; mod element; -mod hasher; mod length; mod padding; mod pixels; @@ -64,7 +63,6 @@ pub use element::Element; pub use event::Event; pub use font::Font; pub use gradient::Gradient; -pub use hasher::Hasher; pub use layout::Layout; pub use length::Length; pub use overlay::Overlay; diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs index c26ce1a5..1caf71b3 100644 --- a/core/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -67,7 +67,7 @@ impl text::Renderer for () { fn fill_text( &mut self, - _paragraph: Text<'_, Self::Font>, + _paragraph: Text, _position: Point, _color: Color, _clip_bounds: Rectangle, @@ -78,11 +78,11 @@ impl text::Renderer for () { impl text::Paragraph for () { type Font = Font; - fn with_text(_text: Text<'_, Self::Font>) -> Self {} + fn with_text(_text: Text<&str>) -> Self {} fn resize(&mut self, _new_bounds: Size) {} - fn compare(&self, _text: Text<'_, Self::Font>) -> text::Difference { + fn compare(&self, _text: Text<&str>) -> text::Difference { text::Difference::None } diff --git a/core/src/svg.rs b/core/src/svg.rs index ab207cca..0106e0c2 100644 --- a/core/src/svg.rs +++ b/core/src/svg.rs @@ -1,6 +1,7 @@ //! Load and draw vector graphics. -use crate::{Color, Hasher, Rectangle, Size}; +use crate::{Color, Rectangle, Size}; +use rustc_hash::FxHasher; use std::borrow::Cow; use std::hash::{Hash, Hasher as _}; use std::path::PathBuf; @@ -30,7 +31,7 @@ impl Handle { } fn from_data(data: Data) -> Handle { - let mut hasher = Hasher::default(); + let mut hasher = FxHasher::default(); data.hash(&mut hasher); Handle { diff --git a/core/src/text.rs b/core/src/text.rs index edef79c2..3f1d2c77 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -16,9 +16,9 @@ use std::hash::{Hash, Hasher}; /// A paragraph. #[derive(Debug, Clone, Copy)] -pub struct Text<'a, Font> { +pub struct Text<Content = String, Font = crate::Font> { /// The content of the paragraph. - pub content: &'a str, + pub content: Content, /// The bounds of the paragraph. pub bounds: Size, @@ -219,7 +219,7 @@ pub trait Renderer: crate::Renderer { /// [`Color`]. fn fill_text( &mut self, - text: Text<'_, Self::Font>, + text: Text<String, Self::Font>, position: Point, color: Color, clip_bounds: Rectangle, diff --git a/core/src/text/paragraph.rs b/core/src/text/paragraph.rs index de1fb74d..8ff04015 100644 --- a/core/src/text/paragraph.rs +++ b/core/src/text/paragraph.rs @@ -8,14 +8,14 @@ pub trait Paragraph: Sized + Default { type Font: Copy + PartialEq; /// Creates a new [`Paragraph`] laid out with the given [`Text`]. - fn with_text(text: Text<'_, Self::Font>) -> Self; + fn with_text(text: Text<&str, Self::Font>) -> Self; /// Lays out the [`Paragraph`] with some new boundaries. fn resize(&mut self, new_bounds: Size); /// Compares the [`Paragraph`] with some desired [`Text`] and returns the /// [`Difference`]. - fn compare(&self, text: Text<'_, Self::Font>) -> Difference; + fn compare(&self, text: Text<&str, Self::Font>) -> Difference; /// Returns the horizontal alignment of the [`Paragraph`]. fn horizontal_alignment(&self) -> alignment::Horizontal; @@ -35,7 +35,7 @@ pub trait Paragraph: Sized + Default { fn grapheme_position(&self, line: usize, index: usize) -> Option<Point>; /// Updates the [`Paragraph`] to match the given [`Text`], if needed. - fn update(&mut self, text: Text<'_, Self::Font>) { + fn update(&mut self, text: Text<&str, Self::Font>) { match self.compare(text) { Difference::None => {} Difference::Bounds => { diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index 12f6956a..f1f0b345 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -21,7 +21,7 @@ where Theme: Catalog, Renderer: text::Renderer, { - content: Cow<'a, str>, + fragment: Fragment<'a>, size: Option<Pixels>, line_height: LineHeight, width: Length, @@ -39,9 +39,9 @@ where Renderer: text::Renderer, { /// Create a new fragment of [`Text`] with the given contents. - pub fn new(content: impl Into<Cow<'a, str>>) -> Self { + pub fn new(fragment: impl IntoFragment<'a>) -> Self { Text { - content: content.into(), + fragment: fragment.into_fragment(), size: None, line_height: LineHeight::default(), font: None, @@ -184,7 +184,7 @@ where limits, self.width, self.height, - &self.content, + &self.fragment, self.line_height, self.size, self.font, @@ -366,3 +366,81 @@ impl Catalog for Theme { class(self) } } + +/// A fragment of [`Text`]. +/// +/// This is just an alias to a string that may be either +/// borrowed or owned. +pub type Fragment<'a> = Cow<'a, str>; + +/// A trait for converting a value to some text [`Fragment`]. +pub trait IntoFragment<'a> { + /// Converts the value to some text [`Fragment`]. + fn into_fragment(self) -> Fragment<'a>; +} + +impl<'a> IntoFragment<'a> for Fragment<'a> { + fn into_fragment(self) -> Fragment<'a> { + self + } +} + +impl<'a, 'b> IntoFragment<'a> for &'a Fragment<'b> { + fn into_fragment(self) -> Fragment<'a> { + Fragment::Borrowed(self) + } +} + +impl<'a> IntoFragment<'a> for &'a str { + fn into_fragment(self) -> Fragment<'a> { + Fragment::Borrowed(self) + } +} + +impl<'a> IntoFragment<'a> for &'a String { + fn into_fragment(self) -> Fragment<'a> { + Fragment::Borrowed(self.as_str()) + } +} + +impl<'a> IntoFragment<'a> for String { + fn into_fragment(self) -> Fragment<'a> { + Fragment::Owned(self) + } +} + +macro_rules! into_fragment { + ($type:ty) => { + impl<'a> IntoFragment<'a> for $type { + fn into_fragment(self) -> Fragment<'a> { + Fragment::Owned(self.to_string()) + } + } + + impl<'a> IntoFragment<'a> for &$type { + fn into_fragment(self) -> Fragment<'a> { + Fragment::Owned(self.to_string()) + } + } + }; +} + +into_fragment!(char); +into_fragment!(bool); + +into_fragment!(u8); +into_fragment!(u16); +into_fragment!(u32); +into_fragment!(u64); +into_fragment!(u128); +into_fragment!(usize); + +into_fragment!(i8); +into_fragment!(i16); +into_fragment!(i32); +into_fragment!(i64); +into_fragment!(i128); +into_fragment!(isize); + +into_fragment!(f32); +into_fragment!(f64); diff --git a/examples/lazy/src/main.rs b/examples/lazy/src/main.rs index 2d53df93..c3f6b8de 100644 --- a/examples/lazy/src/main.rs +++ b/examples/lazy/src/main.rs @@ -173,7 +173,7 @@ impl App { .style(button::danger); row![ - text(&item.name).color(item.color), + text(item.name.clone()).color(item.color), horizontal_space(), pick_list(Color::ALL, Some(item.color), move |color| { Message::ItemColorChanged(item.clone(), color) diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index a88c0dba..e3a2ca87 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -357,7 +357,7 @@ impl<'a> Step { .into() } - fn container(title: &str) -> Column<'a, StepMessage> { + fn container(title: &str) -> Column<'_, StepMessage> { column![text(title).size(50)].spacing(20) } @@ -589,7 +589,7 @@ impl<'a> Step { value: &str, is_secure: bool, is_showing_icon: bool, - ) -> Column<'a, StepMessage> { + ) -> Column<'_, StepMessage> { let mut text_input = text_input("Type something to continue...", value) .on_input(StepMessage::InputChanged) .padding(10) @@ -674,7 +674,7 @@ fn ferris<'a>( .center_x() } -fn padded_button<'a, Message: Clone>(label: &str) -> Button<'a, Message> { +fn padded_button<Message: Clone>(label: &str) -> Button<'_, Message> { button(text(label)).padding([12, 24]) } diff --git a/examples/websocket/src/echo.rs b/examples/websocket/src/echo.rs index 281ed4bd..cd32cb66 100644 --- a/examples/websocket/src/echo.rs +++ b/examples/websocket/src/echo.rs @@ -2,6 +2,7 @@ pub mod server; use iced::futures; use iced::subscription::{self, Subscription}; +use iced::widget::text; use futures::channel::mpsc; use futures::sink::SinkExt; @@ -136,16 +137,24 @@ impl Message { pub fn disconnected() -> Self { Message::Disconnected } + + pub fn as_str(&self) -> &str { + match self { + Message::Connected => "Connected successfully!", + Message::Disconnected => "Connection lost... Retrying...", + Message::User(message) => message.as_str(), + } + } } impl fmt::Display for Message { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Message::Connected => write!(f, "Connected successfully!"), - Message::Disconnected => { - write!(f, "Connection lost... Retrying...") - } - Message::User(message) => write!(f, "{message}"), - } + f.write_str(self.as_str()) + } +} + +impl<'a> text::IntoFragment<'a> for &'a Message { + fn into_fragment(self) -> text::Fragment<'a> { + text::Fragment::Borrowed(self.as_str()) } } diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index 460d9a08..b479fe89 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -96,10 +96,8 @@ impl WebSocket { .into() } else { scrollable( - column( - self.messages.iter().cloned().map(text).map(Element::from), - ) - .spacing(10), + column(self.messages.iter().map(text).map(Element::from)) + .spacing(10), ) .id(MESSAGE_LOG.clone()) .height(Length::Fill) diff --git a/futures/Cargo.toml b/futures/Cargo.toml index 69a915e4..bbcfe01c 100644 --- a/futures/Cargo.toml +++ b/futures/Cargo.toml @@ -22,6 +22,7 @@ iced_core.workspace = true futures.workspace = true log.workspace = true +rustc-hash.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] async-std.workspace = true diff --git a/futures/src/backend/native/async_std.rs b/futures/src/backend/native/async_std.rs index 52b0e914..b7da5e90 100644 --- a/futures/src/backend/native/async_std.rs +++ b/futures/src/backend/native/async_std.rs @@ -18,8 +18,7 @@ impl crate::Executor for Executor { pub mod time { //! Listen and react to time. - use crate::core::Hasher; - use crate::subscription::{self, Subscription}; + use crate::subscription::{self, Hasher, Subscription}; /// Returns a [`Subscription`] that produces messages at a set interval. /// diff --git a/futures/src/backend/native/smol.rs b/futures/src/backend/native/smol.rs index 00d13d35..aaf1518c 100644 --- a/futures/src/backend/native/smol.rs +++ b/futures/src/backend/native/smol.rs @@ -17,8 +17,7 @@ impl crate::Executor for Executor { pub mod time { //! Listen and react to time. - use crate::core::Hasher; - use crate::subscription::{self, Subscription}; + use crate::subscription::{self, Hasher, Subscription}; /// Returns a [`Subscription`] that produces messages at a set interval. /// diff --git a/futures/src/backend/native/tokio.rs b/futures/src/backend/native/tokio.rs index 4698a105..3ab7f675 100644 --- a/futures/src/backend/native/tokio.rs +++ b/futures/src/backend/native/tokio.rs @@ -22,8 +22,7 @@ impl crate::Executor for Executor { pub mod time { //! Listen and react to time. - use crate::core::Hasher; - use crate::subscription::{self, Subscription}; + use crate::subscription::{self, Hasher, Subscription}; /// Returns a [`Subscription`] that produces messages at a set interval. /// diff --git a/futures/src/backend/wasm/wasm_bindgen.rs b/futures/src/backend/wasm/wasm_bindgen.rs index ff7ea0f6..3228dd18 100644 --- a/futures/src/backend/wasm/wasm_bindgen.rs +++ b/futures/src/backend/wasm/wasm_bindgen.rs @@ -16,8 +16,7 @@ impl crate::Executor for Executor { pub mod time { //! Listen and react to time. - use crate::core::Hasher; - use crate::subscription::{self, Subscription}; + use crate::subscription::{self, Hasher, Subscription}; use crate::BoxStream; /// Returns a [`Subscription`] that produces messages at a set interval. diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs index 7537c022..93e35608 100644 --- a/futures/src/subscription.rs +++ b/futures/src/subscription.rs @@ -4,7 +4,6 @@ mod tracker; pub use tracker::Tracker; use crate::core::event::{self, Event}; -use crate::core::Hasher; use crate::futures::{Future, Stream}; use crate::{BoxStream, MaybeSend}; @@ -18,6 +17,9 @@ use std::hash::Hash; /// It is the input of a [`Subscription`]. pub type EventStream = BoxStream<(Event, event::Status)>; +/// The hasher used for identifying subscriptions. +pub type Hasher = rustc_hash::FxHasher; + /// A request to listen to external events. /// /// Besides performing async actions on demand with `Command`, most diff --git a/futures/src/subscription/tracker.rs b/futures/src/subscription/tracker.rs index 15ed5b87..277a446b 100644 --- a/futures/src/subscription/tracker.rs +++ b/futures/src/subscription/tracker.rs @@ -1,12 +1,11 @@ use crate::core::event::{self, Event}; -use crate::core::Hasher; -use crate::subscription::Recipe; +use crate::subscription::{Hasher, Recipe}; use crate::{BoxFuture, MaybeSend}; use futures::channel::mpsc; use futures::sink::{Sink, SinkExt}; +use rustc_hash::FxHashMap; -use std::collections::HashMap; use std::hash::Hasher as _; /// A registry of subscription streams. @@ -18,7 +17,7 @@ use std::hash::Hasher as _; /// [`Subscription`]: crate::Subscription #[derive(Debug, Default)] pub struct Tracker { - subscriptions: HashMap<u64, Execution>, + subscriptions: FxHashMap<u64, Execution>, } #[derive(Debug)] @@ -31,7 +30,7 @@ impl Tracker { /// Creates a new empty [`Tracker`]. pub fn new() -> Self { Self { - subscriptions: HashMap::new(), + subscriptions: FxHashMap::default(), } } diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 0ee6ff47..a42ed477 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -34,7 +34,6 @@ raw-window-handle.workspace = true rustc-hash.workspace = true thiserror.workspace = true unicode-segmentation.workspace = true -xxhash-rust.workspace = true image.workspace = true image.optional = true diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index f517ff3e..fb1a0d73 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -163,13 +163,13 @@ where fn fill_text( &mut self, - text: Text<'_, Self::Font>, + text: Text, position: Point, color: Color, clip_bounds: Rectangle, ) { self.primitives.push(Primitive::Text { - content: text.content.to_string(), + content: text.content, bounds: Rectangle::new(position, text.bounds), size: text.size, line_height: text.line_height, diff --git a/graphics/src/text/cache.rs b/graphics/src/text/cache.rs index 7fb33567..b6473f85 100644 --- a/graphics/src/text/cache.rs +++ b/graphics/src/text/cache.rs @@ -2,9 +2,9 @@ use crate::core::{Font, Size}; use crate::text; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; use std::collections::hash_map; -use std::hash::{BuildHasher, Hash, Hasher}; +use std::hash::{Hash, Hasher}; /// A store of recently used sections of text. #[allow(missing_debug_implementations)] @@ -13,11 +13,8 @@ pub struct Cache { entries: FxHashMap<KeyHash, Entry>, aliases: FxHashMap<KeyHash, KeyHash>, recently_used: FxHashSet<KeyHash>, - hasher: HashBuilder, } -type HashBuilder = xxhash_rust::xxh3::Xxh3Builder; - impl Cache { /// Creates a new empty [`Cache`]. pub fn new() -> Self { @@ -35,7 +32,7 @@ impl Cache { font_system: &mut cosmic_text::FontSystem, key: Key<'_>, ) -> (KeyHash, &mut Entry) { - let hash = key.hash(self.hasher.build_hasher()); + let hash = key.hash(FxHasher::default()); if let Some(hash) = self.aliases.get(&hash) { let _ = self.recently_used.insert(*hash); @@ -77,7 +74,7 @@ impl Cache { ] { if key.bounds != bounds { let _ = self.aliases.insert( - Key { bounds, ..key }.hash(self.hasher.build_hasher()), + Key { bounds, ..key }.hash(FxHasher::default()), hash, ); } diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index 5d027542..31a323ac 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -61,7 +61,7 @@ impl Paragraph { impl core::text::Paragraph for Paragraph { type Font = Font; - fn with_text(text: Text<'_, Font>) -> Self { + fn with_text(text: Text<&str>) -> Self { log::trace!("Allocating paragraph: {}", text.content); let mut font_system = @@ -146,7 +146,7 @@ impl core::text::Paragraph for Paragraph { } } - fn compare(&self, text: Text<'_, Font>) -> core::text::Difference { + fn compare(&self, text: Text<&str>) -> core::text::Difference { let font_system = text::font_system().read().expect("Read font system"); let paragraph = self.internal(); let metrics = paragraph.buffer.metrics(); diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index ef9cc9a9..b9ceb4b2 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -115,7 +115,7 @@ where fn fill_text( &mut self, - text: core::Text<'_, Self::Font>, + text: core::Text<String, Self::Font>, position: Point, color: Color, clip_bounds: Rectangle, diff --git a/src/advanced.rs b/src/advanced.rs index 306c3559..5826ba0f 100644 --- a/src/advanced.rs +++ b/src/advanced.rs @@ -9,10 +9,12 @@ pub use crate::core::renderer::{self, Renderer}; pub use crate::core::svg; pub use crate::core::text::{self, Text}; pub use crate::core::widget::{self, Widget}; -pub use crate::core::{Hasher, Shell}; +pub use crate::core::Shell; pub use crate::renderer::graphics; pub mod subscription { //! Write your own subscriptions. - pub use crate::runtime::futures::subscription::{EventStream, Recipe}; + pub use crate::runtime::futures::subscription::{ + EventStream, Hasher, Recipe, + }; } diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index 68b2a03a..44a894a1 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -25,7 +25,6 @@ log.workspace = true rustc-hash.workspace = true softbuffer.workspace = true tiny-skia.workspace = true -xxhash-rust.workspace = true resvg.workspace = true resvg.optional = true diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index f6162e0f..0b713784 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -32,6 +32,7 @@ glyphon.workspace = true guillotiere.workspace = true log.workspace = true once_cell.workspace = true +rustc-hash.workspace = true thiserror.workspace = true wgpu.workspace = true diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 5019191c..6ccf4111 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -1,3 +1,4 @@ +use crate::buffer; use crate::core::{Color, Size, Transformation}; use crate::graphics::backend; use crate::graphics::color; @@ -30,6 +31,7 @@ pub struct Backend { pipeline_storage: pipeline::Storage, #[cfg(any(feature = "image", feature = "svg"))] image_pipeline: image::Pipeline, + staging_belt: wgpu::util::StagingBelt, } impl Backend { @@ -61,6 +63,13 @@ impl Backend { #[cfg(any(feature = "image", feature = "svg"))] image_pipeline, + + // TODO: Resize belt smartly (?) + // It would be great if the `StagingBelt` API exposed methods + // for introspection to detect when a resize may be worth it. + staging_belt: wgpu::util::StagingBelt::new( + buffer::MAX_WRITE_SIZE as u64, + ), } } @@ -105,6 +114,8 @@ impl Backend { &layers, ); + self.staging_belt.finish(); + self.render( device, encoder, @@ -123,12 +134,20 @@ impl Backend { self.image_pipeline.end_frame(); } + /// Recalls staging memory for future uploads. + /// + /// This method should be called after the command encoder + /// has been submitted. + pub fn recall(&mut self) { + self.staging_belt.recall(); + } + fn prepare( &mut self, device: &wgpu::Device, queue: &wgpu::Queue, format: wgpu::TextureFormat, - _encoder: &mut wgpu::CommandEncoder, + encoder: &mut wgpu::CommandEncoder, scale_factor: f32, target_size: Size<u32>, transformation: Transformation, @@ -144,7 +163,8 @@ impl Backend { if !layer.quads.is_empty() { self.quad_pipeline.prepare( device, - queue, + encoder, + &mut self.staging_belt, &layer.quads, transformation, scale_factor, @@ -157,7 +177,8 @@ impl Backend { self.triangle_pipeline.prepare( device, - queue, + encoder, + &mut self.staging_belt, &layer.meshes, scaled, ); @@ -171,8 +192,8 @@ impl Backend { self.image_pipeline.prepare( device, - queue, - _encoder, + encoder, + &mut self.staging_belt, &layer.images, scaled, scale_factor, @@ -184,6 +205,7 @@ impl Backend { self.text_pipeline.prepare( device, queue, + encoder, &layer.text, layer.bounds, scale_factor, diff --git a/wgpu/src/buffer.rs b/wgpu/src/buffer.rs index ef00c58f..463ea24a 100644 --- a/wgpu/src/buffer.rs +++ b/wgpu/src/buffer.rs @@ -1,6 +1,13 @@ use std::marker::PhantomData; +use std::num::NonZeroU64; use std::ops::RangeBounds; +pub const MAX_WRITE_SIZE: usize = 100 * 1024; + +#[allow(unsafe_code)] +const MAX_WRITE_SIZE_U64: NonZeroU64 = + unsafe { NonZeroU64::new_unchecked(MAX_WRITE_SIZE as u64) }; + #[derive(Debug)] pub struct Buffer<T> { label: &'static str, @@ -61,12 +68,46 @@ impl<T: bytemuck::Pod> Buffer<T> { /// Returns the size of the written bytes. pub fn write( &mut self, - queue: &wgpu::Queue, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, offset: usize, contents: &[T], ) -> usize { let bytes: &[u8] = bytemuck::cast_slice(contents); - queue.write_buffer(&self.raw, offset as u64, bytes); + let mut bytes_written = 0; + + // Split write into multiple chunks if necessary + while bytes_written + MAX_WRITE_SIZE < bytes.len() { + belt.write_buffer( + encoder, + &self.raw, + (offset + bytes_written) as u64, + MAX_WRITE_SIZE_U64, + device, + ) + .copy_from_slice( + &bytes[bytes_written..bytes_written + MAX_WRITE_SIZE], + ); + + bytes_written += MAX_WRITE_SIZE; + } + + // There will always be some bytes left, since the previous + // loop guarantees `bytes_written < bytes.len()` + let bytes_left = ((bytes.len() - bytes_written) as u64) + .try_into() + .expect("non-empty write"); + + // Write them + belt.write_buffer( + encoder, + &self.raw, + (offset + bytes_written) as u64, + bytes_left, + device, + ) + .copy_from_slice(&bytes[bytes_written..]); self.offsets.push(offset as u64); diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 067b77ab..d0bf1182 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -83,21 +83,31 @@ impl Layer { fn prepare( &mut self, device: &wgpu::Device, - queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, nearest_instances: &[Instance], linear_instances: &[Instance], transformation: Transformation, ) { - queue.write_buffer( + let uniforms = Uniforms { + transform: transformation.into(), + }; + + let bytes = bytemuck::bytes_of(&uniforms); + + belt.write_buffer( + encoder, &self.uniforms, 0, - bytemuck::bytes_of(&Uniforms { - transform: transformation.into(), - }), - ); + (bytes.len() as u64).try_into().expect("Sized uniforms"), + device, + ) + .copy_from_slice(bytes); + + self.nearest + .upload(device, encoder, belt, nearest_instances); - self.nearest.upload(device, queue, nearest_instances); - self.linear.upload(device, queue, linear_instances); + self.linear.upload(device, encoder, belt, linear_instances); } fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { @@ -158,7 +168,8 @@ impl Data { fn upload( &mut self, device: &wgpu::Device, - queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, instances: &[Instance], ) { self.instance_count = instances.len(); @@ -168,7 +179,7 @@ impl Data { } let _ = self.instances.resize(device, instances.len()); - let _ = self.instances.write(queue, 0, instances); + let _ = self.instances.write(device, encoder, belt, 0, instances); } fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { @@ -383,8 +394,8 @@ impl Pipeline { pub fn prepare( &mut self, device: &wgpu::Device, - queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, images: &[layer::Image], transformation: Transformation, _scale: f32, @@ -501,7 +512,8 @@ impl Pipeline { layer.prepare( device, - queue, + encoder, + belt, nearest_instances, linear_instances, transformation, diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index a6cba76a..441b294f 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -4,7 +4,7 @@ use crate::graphics; use crate::graphics::image::image_rs; use crate::image::atlas::{self, Atlas}; -use std::collections::{HashMap, HashSet}; +use rustc_hash::{FxHashMap, FxHashSet}; /// Entry in cache corresponding to an image handle #[derive(Debug)] @@ -38,8 +38,8 @@ impl Memory { /// Caches image raster data #[derive(Debug, Default)] pub struct Cache { - map: HashMap<u64, Memory>, - hits: HashSet<u64>, + map: FxHashMap<u64, Memory>, + hits: FxHashSet<u64>, } impl Cache { diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index d9be50d7..d681b2e6 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -5,7 +5,7 @@ use crate::image::atlas::{self, Atlas}; use resvg::tiny_skia; use resvg::usvg::{self, TreeTextToPath}; -use std::collections::{HashMap, HashSet}; +use rustc_hash::{FxHashMap, FxHashSet}; use std::fs; /// Entry in cache corresponding to an svg handle @@ -33,10 +33,10 @@ impl Svg { /// Caches svg vector and raster data #[derive(Debug, Default)] pub struct Cache { - svgs: HashMap<u64, Svg>, - rasterized: HashMap<(u64, u32, u32, ColorFilter), atlas::Entry>, - svg_hits: HashSet<u64>, - rasterized_hits: HashSet<(u64, u32, u32, ColorFilter)>, + svgs: FxHashMap<u64, Svg>, + rasterized: FxHashMap<(u64, u32, u32, ColorFilter), atlas::Entry>, + svg_hits: FxHashSet<u64>, + rasterized_hits: FxHashSet<(u64, u32, u32, ColorFilter)>, } type ColorFilter = Option<[u8; 4]>; diff --git a/wgpu/src/primitive/pipeline.rs b/wgpu/src/primitive/pipeline.rs index 814440ba..59c54db9 100644 --- a/wgpu/src/primitive/pipeline.rs +++ b/wgpu/src/primitive/pipeline.rs @@ -1,8 +1,8 @@ //! Draw primitives using custom pipelines. use crate::core::{self, Rectangle, Size}; +use rustc_hash::FxHashMap; use std::any::{Any, TypeId}; -use std::collections::HashMap; use std::fmt::Debug; use std::sync::Arc; @@ -82,7 +82,7 @@ impl Renderer for crate::Renderer { /// Stores custom, user-provided pipelines. #[derive(Default, Debug)] pub struct Storage { - pipelines: HashMap<TypeId, Box<dyn Any + Send>>, + pipelines: FxHashMap<TypeId, Box<dyn Any + Send>>, } impl Storage { diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index b932f54f..0717a031 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -57,7 +57,8 @@ impl Pipeline { pub fn prepare( &mut self, device: &wgpu::Device, - queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, quads: &Batch, transformation: Transformation, scale: f32, @@ -67,7 +68,7 @@ impl Pipeline { } let layer = &mut self.layers[self.prepare_layer]; - layer.prepare(device, queue, quads, transformation, scale); + layer.prepare(device, encoder, belt, quads, transformation, scale); self.prepare_layer += 1; } @@ -162,7 +163,8 @@ impl Layer { pub fn prepare( &mut self, device: &wgpu::Device, - queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, quads: &Batch, transformation: Transformation, scale: f32, @@ -171,15 +173,25 @@ impl Layer { let _ = info_span!("Wgpu::Quad", "PREPARE").entered(); let uniforms = Uniforms::new(transformation, scale); + let bytes = bytemuck::bytes_of(&uniforms); - queue.write_buffer( + belt.write_buffer( + encoder, &self.constants_buffer, 0, - bytemuck::bytes_of(&uniforms), - ); + (bytes.len() as u64).try_into().expect("Sized uniforms"), + device, + ) + .copy_from_slice(bytes); - self.solid.prepare(device, queue, &quads.solids); - self.gradient.prepare(device, queue, &quads.gradients); + if !quads.solids.is_empty() { + self.solid.prepare(device, encoder, belt, &quads.solids); + } + + if !quads.gradients.is_empty() { + self.gradient + .prepare(device, encoder, belt, &quads.gradients); + } } } diff --git a/wgpu/src/quad/gradient.rs b/wgpu/src/quad/gradient.rs index 560fcad2..5b32c52a 100644 --- a/wgpu/src/quad/gradient.rs +++ b/wgpu/src/quad/gradient.rs @@ -46,11 +46,12 @@ impl Layer { pub fn prepare( &mut self, device: &wgpu::Device, - queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, instances: &[Gradient], ) { let _ = self.instances.resize(device, instances.len()); - let _ = self.instances.write(queue, 0, instances); + let _ = self.instances.write(device, encoder, belt, 0, instances); self.instance_count = instances.len(); } diff --git a/wgpu/src/quad/solid.rs b/wgpu/src/quad/solid.rs index 771eee34..1cead367 100644 --- a/wgpu/src/quad/solid.rs +++ b/wgpu/src/quad/solid.rs @@ -40,11 +40,12 @@ impl Layer { pub fn prepare( &mut self, device: &wgpu::Device, - queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, instances: &[Solid], ) { let _ = self.instances.resize(device, instances.len()); - let _ = self.instances.write(queue, 0, instances); + let _ = self.instances.write(device, encoder, belt, 0, instances); self.instance_count = instances.len(); } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 6fa1922d..97ff77f5 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -53,6 +53,7 @@ impl Pipeline { &mut self, device: &wgpu::Device, queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, sections: &[Text<'_>], layer_bounds: Rectangle, scale_factor: f32, @@ -262,6 +263,7 @@ impl Pipeline { let result = renderer.prepare( device, queue, + encoder, font_system, &mut self.atlas, glyphon::Resolution { diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 2bb6f307..b6be54d4 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -48,7 +48,8 @@ impl Layer { fn prepare( &mut self, device: &wgpu::Device, - queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, solid: &solid::Pipeline, gradient: &gradient::Pipeline, meshes: &[Mesh<'_>], @@ -103,33 +104,47 @@ impl Layer { let uniforms = Uniforms::new(transformation * mesh.transformation()); - index_offset += - self.index_buffer.write(queue, index_offset, indices); + index_offset += self.index_buffer.write( + device, + encoder, + belt, + index_offset, + indices, + ); + self.index_strides.push(indices.len() as u32); match mesh { Mesh::Solid { buffers, .. } => { solid_vertex_offset += self.solid.vertices.write( - queue, + device, + encoder, + belt, solid_vertex_offset, &buffers.vertices, ); solid_uniform_offset += self.solid.uniforms.write( - queue, + device, + encoder, + belt, solid_uniform_offset, &[uniforms], ); } Mesh::Gradient { buffers, .. } => { gradient_vertex_offset += self.gradient.vertices.write( - queue, + device, + encoder, + belt, gradient_vertex_offset, &buffers.vertices, ); gradient_uniform_offset += self.gradient.uniforms.write( - queue, + device, + encoder, + belt, gradient_uniform_offset, &[uniforms], ); @@ -237,7 +252,8 @@ impl Pipeline { pub fn prepare( &mut self, device: &wgpu::Device, - queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, meshes: &[Mesh<'_>], transformation: Transformation, ) { @@ -252,7 +268,8 @@ impl Pipeline { let layer = &mut self.layers[self.prepare_layer]; layer.prepare( device, - queue, + encoder, + belt, &self.solid, &self.gradient, meshes, diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 9a3e3b34..482d705b 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -243,6 +243,7 @@ pub fn present<T: AsRef<str>>( // Submit work let _submission = compositor.queue.submit(Some(encoder.finish())); + backend.recall(); frame.present(); Ok(()) diff --git a/widget/Cargo.toml b/widget/Cargo.toml index a45f47ef..84525935 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -28,6 +28,7 @@ iced_renderer.workspace = true iced_runtime.workspace = true num-traits.workspace = true +rustc-hash.workspace = true thiserror.workspace = true unicode-segmentation.workspace = true diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 48f6abf6..225c316d 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -340,7 +340,7 @@ where if self.is_checked { renderer.fill_text( text::Text { - content: &code_point.to_string(), + content: code_point.to_string(), font: *font, size, line_height: *line_height, diff --git a/widget/src/column.rs b/widget/src/column.rs index d37ef695..df7829b3 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -33,11 +33,18 @@ where Self::from_vec(Vec::new()) } + /// Creates a [`Column`] with the given capacity. + pub fn with_capacity(capacity: usize) -> Self { + Self::from_vec(Vec::with_capacity(capacity)) + } + /// Creates a [`Column`] with the given elements. pub fn with_children( children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>, ) -> Self { - Self::new().extend(children) + let iterator = children.into_iter(); + + Self::with_capacity(iterator.size_hint().0).extend(iterator) } /// Creates a [`Column`] from an already allocated [`Vec`]. diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index e4f4a41f..253850df 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -700,38 +700,47 @@ where .. } = tree.state.downcast_mut::<Menu<T>>(); - let bounds = layout.bounds(); - self.state.sync_filtered_options(filtered_options); - let mut menu = menu::Menu::new( - menu, - &filtered_options.options, - hovered_option, - |x| { - tree.children[0] - .state - .downcast_mut::<text_input::State<Renderer::Paragraph>>( - ) - .unfocus(); - - (self.on_selected)(x) - }, - self.on_option_hovered.as_deref(), - &self.menu_class, - ) - .width(bounds.width) - .padding(self.padding); - - if let Some(font) = self.font { - menu = menu.font(font); - } + if filtered_options.options.is_empty() { + None + } else { + let bounds = layout.bounds(); + + let mut menu = menu::Menu::new( + menu, + &filtered_options.options, + hovered_option, + |x| { + tree.children[0] + .state + .downcast_mut::<text_input::State<Renderer::Paragraph>>( + ) + .unfocus(); + + (self.on_selected)(x) + }, + self.on_option_hovered.as_deref(), + &self.menu_class, + ) + .width(bounds.width) + .padding(self.padding); + + if let Some(font) = self.font { + menu = menu.font(font); + } - if let Some(size) = self.size { - menu = menu.text_size(size); - } + if let Some(size) = self.size { + menu = menu.text_size(size); + } - Some(menu.overlay(layout.position() + translation, bounds.height)) + Some( + menu.overlay( + layout.position() + translation, + bounds.height, + ), + ) + } } else { None } diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 77b30882..61789c19 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -145,13 +145,26 @@ where /// /// [`Text`]: core::widget::Text pub fn text<'a, Theme, Renderer>( - text: impl ToString, + text: impl text::IntoFragment<'a>, ) -> Text<'a, Theme, Renderer> where Theme: text::Catalog + 'a, Renderer: core::text::Renderer, { - Text::new(text.to_string()) + Text::new(text) +} + +/// Creates a new [`Text`] widget that displays the provided value. +/// +/// [`Text`]: core::widget::Text +pub fn value<'a, Theme, Renderer>( + value: impl ToString, +) -> Text<'a, Theme, Renderer> +where + Theme: text::Catalog + 'a, + Renderer: core::text::Renderer, +{ + Text::new(value.to_string()) } /// Creates a new [`Checkbox`]. diff --git a/widget/src/keyed/column.rs b/widget/src/keyed/column.rs index 8a8d5fe7..a34ce9e6 100644 --- a/widget/src/keyed/column.rs +++ b/widget/src/keyed/column.rs @@ -40,27 +40,49 @@ where { /// Creates an empty [`Column`]. pub fn new() -> Self { - Column { + Self::from_vecs(Vec::new(), Vec::new()) + } + + /// Creates a [`Column`] from already allocated [`Vec`]s. + /// + /// Keep in mind that the [`Column`] will not inspect the [`Vec`]s, which means + /// it won't automatically adapt to the sizing strategy of its contents. + /// + /// If any of the children have a [`Length::Fill`] strategy, you will need to + /// call [`Column::width`] or [`Column::height`] accordingly. + pub fn from_vecs( + keys: Vec<Key>, + children: Vec<Element<'a, Message, Theme, Renderer>>, + ) -> Self { + Self { spacing: 0.0, padding: Padding::ZERO, width: Length::Shrink, height: Length::Shrink, max_width: f32::INFINITY, align_items: Alignment::Start, - keys: Vec::new(), - children: Vec::new(), + keys, + children, } } + /// Creates a [`Column`] with the given capacity. + pub fn with_capacity(capacity: usize) -> Self { + Self::from_vecs( + Vec::with_capacity(capacity), + Vec::with_capacity(capacity), + ) + } + /// Creates a [`Column`] with the given elements. pub fn with_children( children: impl IntoIterator< Item = (Key, Element<'a, Message, Theme, Renderer>), >, ) -> Self { - children - .into_iter() - .fold(Self::new(), |column, (key, child)| column.push(key, child)) + let iterator = children.into_iter(); + + Self::with_capacity(iterator.size_hint().0).extend(iterator) } /// Sets the vertical spacing _between_ elements. @@ -132,6 +154,18 @@ where self } } + + /// Extends the [`Column`] with the given children. + pub fn extend( + self, + children: impl IntoIterator< + Item = (Key, Element<'a, Message, Theme, Renderer>), + >, + ) -> Self { + children + .into_iter() + .fold(self, |column, (key, child)| column.push(key, child)) + } } impl<'a, Key, Message, Renderer> Default for Column<'a, Key, Message, Renderer> diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index eb663ea5..04783dbe 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -18,11 +18,12 @@ use crate::core::widget::tree::{self, Tree}; use crate::core::widget::{self, Widget}; use crate::core::Element; use crate::core::{ - self, Clipboard, Hasher, Length, Point, Rectangle, Shell, Size, Vector, + self, Clipboard, Length, Point, Rectangle, Shell, Size, Vector, }; use crate::runtime::overlay::Nested; use ouroboros::self_referencing; +use rustc_hash::FxHasher; use std::cell::RefCell; use std::hash::{Hash, Hasher as H}; use std::rc::Rc; @@ -106,9 +107,12 @@ where } fn state(&self) -> tree::State { - let mut hasher = Hasher::default(); - self.dependency.hash(&mut hasher); - let hash = hasher.finish(); + let hash = { + let mut hasher = FxHasher::default(); + self.dependency.hash(&mut hasher); + + hasher.finish() + }; let element = Rc::new(RefCell::new(Some((self.view)(&self.dependency).into()))); @@ -127,9 +131,12 @@ where .state .downcast_mut::<Internal<Message, Theme, Renderer>>(); - let mut hasher = Hasher::default(); - self.dependency.hash(&mut hasher); - let new_hash = hasher.finish(); + let new_hash = { + let mut hasher = FxHasher::default(); + self.dependency.hash(&mut hasher); + + hasher.finish() + }; if current.hash != new_hash { current.hash = new_hash; diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index d76caa8a..98efe305 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -526,7 +526,7 @@ where renderer.fill_text( Text { - content: &option.to_string(), + content: option.to_string(), bounds: Size::new(f32::INFINITY, bounds.height), size: text_size, line_height: self.text_line_height, diff --git a/widget/src/pane_grid/state.rs b/widget/src/pane_grid/state.rs index 481cd770..c20c3b9c 100644 --- a/widget/src/pane_grid/state.rs +++ b/widget/src/pane_grid/state.rs @@ -6,7 +6,7 @@ use crate::pane_grid::{ Axis, Configuration, Direction, Edge, Node, Pane, Region, Split, Target, }; -use std::collections::HashMap; +use rustc_hash::FxHashMap; /// The state of a [`PaneGrid`]. /// @@ -25,7 +25,7 @@ pub struct State<T> { /// The panes of the [`PaneGrid`]. /// /// [`PaneGrid`]: super::PaneGrid - pub panes: HashMap<Pane, T>, + pub panes: FxHashMap<Pane, T>, /// The internal state of the [`PaneGrid`]. /// @@ -52,7 +52,7 @@ impl<T> State<T> { /// Creates a new [`State`] with the given [`Configuration`]. pub fn with_configuration(config: impl Into<Configuration<T>>) -> Self { - let mut panes = HashMap::new(); + let mut panes = FxHashMap::default(); let internal = Internal::from_configuration(&mut panes, config.into(), 0); @@ -353,7 +353,7 @@ impl Internal { /// /// [`PaneGrid`]: super::PaneGrid pub fn from_configuration<T>( - panes: &mut HashMap<Pane, T>, + panes: &mut FxHashMap<Pane, T>, content: Configuration<T>, next_id: usize, ) -> Self { diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 801e792b..edccfdaa 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -479,7 +479,7 @@ where renderer.fill_text( Text { - content: &code_point.to_string(), + content: code_point.to_string(), size, line_height, font, @@ -502,7 +502,7 @@ where let label = selected.map(ToString::to_string); - if let Some(label) = label.as_deref().or(self.placeholder.as_deref()) { + if let Some(label) = label.or_else(|| self.placeholder.clone()) { let text_size = self.text_size.unwrap_or_else(|| renderer.default_size()); diff --git a/widget/src/row.rs b/widget/src/row.rs index 47feff9c..fa352171 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -31,11 +31,18 @@ where Self::from_vec(Vec::new()) } + /// Creates a [`Row`] with the given capacity. + pub fn with_capacity(capacity: usize) -> Self { + Self::from_vec(Vec::with_capacity(capacity)) + } + /// Creates a [`Row`] with the given elements. pub fn with_children( children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>, ) -> Self { - Self::new().extend(children) + let iterator = children.into_iter(); + + Self::with_capacity(iterator.size_hint().0).extend(iterator) } /// Creates a [`Row`] from an already allocated [`Vec`]. diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index dafe2fca..8cfb0408 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -232,7 +232,7 @@ where let placeholder_text = Text { font, line_height: self.line_height, - content: &self.placeholder, + content: self.placeholder.as_str(), bounds: Size::new(f32::INFINITY, text_bounds.height), size: text_size, horizontal_alignment: alignment::Horizontal::Left, @@ -251,9 +251,11 @@ where }); if let Some(icon) = &self.icon { + let mut content = [0; 4]; + let icon_text = Text { line_height: self.line_height, - content: &icon.code_point.to_string(), + content: icon.code_point.encode_utf8(&mut content) as &_, font: icon.font, size: icon.size.unwrap_or_else(|| renderer.default_size()), bounds: Size::new(f32::INFINITY, text_bounds.height), diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 9d65cc1b..29e744b2 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -26,6 +26,7 @@ iced_graphics.workspace = true iced_runtime.workspace = true log.workspace = true +rustc-hash.workspace = true thiserror.workspace = true tracing.workspace = true window_clipboard.workspace = true diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index b4c25411..e17cc180 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -27,7 +27,7 @@ use crate::{Clipboard, Error, Proxy, Settings}; pub use crate::application::{default, Appearance, DefaultStyle}; -use std::collections::HashMap; +use rustc_hash::FxHashMap; use std::mem::ManuallyDrop; use std::sync::Arc; use std::time::Instant; @@ -381,12 +381,12 @@ async fn run_instance<A, E, C>( )] }; - let mut ui_caches = HashMap::new(); + let mut ui_caches = FxHashMap::default(); let mut user_interfaces = ManuallyDrop::new(build_user_interfaces( &application, &mut debug, &mut window_manager, - HashMap::from_iter([( + FxHashMap::from_iter([( window::Id::MAIN, user_interface::Cache::default(), )]), @@ -759,7 +759,7 @@ async fn run_instance<A, E, C>( // TODO mw application update returns which window IDs to update if !messages.is_empty() || uis_stale { - let mut cached_interfaces: HashMap< + let mut cached_interfaces: FxHashMap< window::Id, user_interface::Cache, > = ManuallyDrop::into_inner(user_interfaces) @@ -849,7 +849,7 @@ fn update<A: Application, C, E: Executor>( debug: &mut Debug, messages: &mut Vec<A::Message>, window_manager: &mut WindowManager<A, C>, - ui_caches: &mut HashMap<window::Id, user_interface::Cache>, + ui_caches: &mut FxHashMap<window::Id, user_interface::Cache>, ) where C: Compositor<Renderer = A::Renderer> + 'static, A::Theme: DefaultStyle, @@ -890,7 +890,7 @@ fn run_command<A, C, E>( proxy: &mut winit::event_loop::EventLoopProxy<A::Message>, debug: &mut Debug, window_manager: &mut WindowManager<A, C>, - ui_caches: &mut HashMap<window::Id, user_interface::Cache>, + ui_caches: &mut FxHashMap<window::Id, user_interface::Cache>, ) where A: Application, E: Executor, @@ -1218,8 +1218,8 @@ pub fn build_user_interfaces<'a, A: Application, C: Compositor>( application: &'a A, debug: &mut Debug, window_manager: &mut WindowManager<A, C>, - mut cached_user_interfaces: HashMap<window::Id, user_interface::Cache>, -) -> HashMap<window::Id, UserInterface<'a, A::Message, A::Theme, A::Renderer>> + mut cached_user_interfaces: FxHashMap<window::Id, user_interface::Cache>, +) -> FxHashMap<window::Id, UserInterface<'a, A::Message, A::Theme, A::Renderer>> where C: Compositor<Renderer = A::Renderer>, A::Theme: DefaultStyle, |