diff options
-rw-r--r-- | core/src/window/event.rs | 2 | ||||
-rw-r--r-- | examples/websocket/Cargo.toml | 2 | ||||
-rw-r--r-- | graphics/src/text/paragraph.rs | 25 | ||||
-rw-r--r-- | renderer/src/backend.rs | 100 | ||||
-rw-r--r-- | runtime/src/window.rs | 22 | ||||
-rw-r--r-- | runtime/src/window/action.rs | 23 | ||||
-rw-r--r-- | tiny_skia/src/vector.rs | 2 | ||||
-rw-r--r-- | wgpu/src/image/vector.rs | 26 | ||||
-rw-r--r-- | wgpu/src/primitive/pipeline.rs | 4 | ||||
-rw-r--r-- | widget/src/lazy/helpers.rs | 3 | ||||
-rw-r--r-- | widget/src/pane_grid.rs | 69 | ||||
-rw-r--r-- | widget/src/pane_grid/state.rs | 17 | ||||
-rw-r--r-- | widget/src/text_editor.rs | 9 | ||||
-rw-r--r-- | widget/src/tooltip.rs | 6 | ||||
-rw-r--r-- | winit/src/application.rs | 10 | ||||
-rw-r--r-- | winit/src/multi_window.rs | 14 |
16 files changed, 193 insertions, 141 deletions
diff --git a/core/src/window/event.rs b/core/src/window/event.rs index b9ee7aca..a14d127f 100644 --- a/core/src/window/event.rs +++ b/core/src/window/event.rs @@ -58,7 +58,7 @@ pub enum Event { /// for each file separately. FileHovered(PathBuf), - /// A file has beend dropped into the window. + /// A file has been dropped into the window. /// /// When the user drops multiple files at once, this event will be emitted /// for each file separately. diff --git a/examples/websocket/Cargo.toml b/examples/websocket/Cargo.toml index 2756e8e0..8f1b876a 100644 --- a/examples/websocket/Cargo.toml +++ b/examples/websocket/Cargo.toml @@ -13,7 +13,7 @@ once_cell.workspace = true warp = "0.3" [dependencies.async-tungstenite] -version = "0.23" +version = "0.24" features = ["tokio-rustls-webpki-roots"] [dependencies.tokio] diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index 4a08a8f4..5d027542 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -187,38 +187,43 @@ impl core::text::Paragraph for Paragraph { } fn grapheme_position(&self, line: usize, index: usize) -> Option<Point> { + use unicode_segmentation::UnicodeSegmentation; + let run = self.internal().buffer.layout_runs().nth(line)?; // index represents a grapheme, not a glyph // Let's find the first glyph for the given grapheme cluster let mut last_start = None; + let mut last_grapheme_count = 0; let mut graphemes_seen = 0; let glyph = run .glyphs .iter() .find(|glyph| { - if graphemes_seen == index { - return true; - } - if Some(glyph.start) != last_start { + last_grapheme_count = run.text[glyph.start..glyph.end] + .graphemes(false) + .count(); last_start = Some(glyph.start); - graphemes_seen += 1; + graphemes_seen += last_grapheme_count; } - false + graphemes_seen >= index }) .or_else(|| run.glyphs.last())?; - let advance_last = if index == run.glyphs.len() { - glyph.w - } else { + let advance = if index == 0 { 0.0 + } else { + glyph.w + * (1.0 + - graphemes_seen.saturating_sub(index) as f32 + / last_grapheme_count.max(1) as f32) }; Some(Point::new( - glyph.x + glyph.x_offset * glyph.font_size + advance_last, + glyph.x + glyph.x_offset * glyph.font_size + advance, glyph.y - glyph.y_offset * glyph.font_size, )) } diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs deleted file mode 100644 index 3f229b52..00000000 --- a/renderer/src/backend.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crate::core::text; -use crate::core::{Font, Point, Size}; -use crate::graphics::backend; - -use std::borrow::Cow; - -#[allow(clippy::large_enum_variant)] -pub enum Backend { - TinySkia(iced_tiny_skia::Backend), - #[cfg(feature = "wgpu")] - Wgpu(iced_wgpu::Backend), -} - -macro_rules! delegate { - ($backend:expr, $name:ident, $body:expr) => { - match $backend { - Self::TinySkia($name) => $body, - #[cfg(feature = "wgpu")] - Self::Wgpu($name) => $body, - } - }; -} - -impl backend::Text for Backend { - const ICON_FONT: Font = Font::with_name("Iced-Icons"); - const CHECKMARK_ICON: char = '\u{f00c}'; - const ARROW_DOWN_ICON: char = '\u{e800}'; - - fn default_font(&self) -> Font { - delegate!(self, backend, backend.default_font()) - } - - fn default_size(&self) -> f32 { - delegate!(self, backend, backend.default_size()) - } - - fn measure( - &self, - contents: &str, - size: f32, - line_height: text::LineHeight, - font: Font, - bounds: Size, - shaping: text::Shaping, - ) -> Size { - delegate!( - self, - backend, - backend.measure(contents, size, line_height, font, bounds, shaping) - ) - } - - fn hit_test( - &self, - contents: &str, - size: f32, - line_height: text::LineHeight, - font: Font, - bounds: Size, - shaping: text::Shaping, - position: Point, - nearest_only: bool, - ) -> Option<text::Hit> { - delegate!( - self, - backend, - backend.hit_test( - contents, - size, - line_height, - font, - bounds, - shaping, - position, - nearest_only, - ) - ) - } - - fn load_font(&mut self, font: Cow<'static, [u8]>) { - delegate!(self, backend, backend.load_font(font)); - } -} - -#[cfg(feature = "image")] -impl backend::Image for Backend { - fn dimensions(&self, handle: &crate::core::image::Handle) -> Size<u32> { - delegate!(self, backend, backend.dimensions(handle)) - } -} - -#[cfg(feature = "svg")] -impl backend::Svg for Backend { - fn viewport_dimensions( - &self, - handle: &crate::core::svg::Handle, - ) -> Size<u32> { - delegate!(self, backend, backend.viewport_dimensions(handle)) - } -} diff --git a/runtime/src/window.rs b/runtime/src/window.rs index f9d943f6..2136d64d 100644 --- a/runtime/src/window.rs +++ b/runtime/src/window.rs @@ -65,11 +65,33 @@ pub fn fetch_size<Message>( Command::single(command::Action::Window(Action::FetchSize(id, Box::new(f)))) } +/// Fetches if the window is maximized. +pub fn fetch_maximized<Message>( + id: Id, + f: impl FnOnce(bool) -> Message + 'static, +) -> Command<Message> { + Command::single(command::Action::Window(Action::FetchMaximized( + id, + Box::new(f), + ))) +} + /// Maximizes the window. pub fn maximize<Message>(id: Id, maximized: bool) -> Command<Message> { Command::single(command::Action::Window(Action::Maximize(id, maximized))) } +/// Fetches if the window is minimized. +pub fn fetch_minimized<Message>( + id: Id, + f: impl FnOnce(Option<bool>) -> Message + 'static, +) -> Command<Message> { + Command::single(command::Action::Window(Action::FetchMinimized( + id, + Box::new(f), + ))) +} + /// Minimizes the window. pub fn minimize<Message>(id: Id, minimized: bool) -> Command<Message> { Command::single(command::Action::Window(Action::Minimize(id, minimized))) diff --git a/runtime/src/window/action.rs b/runtime/src/window/action.rs index 2d98b607..8b532569 100644 --- a/runtime/src/window/action.rs +++ b/runtime/src/window/action.rs @@ -21,8 +21,19 @@ pub enum Action<T> { Resize(Id, Size), /// Fetch the current logical dimensions of the window. FetchSize(Id, Box<dyn FnOnce(Size) -> T + 'static>), + /// Fetch if the current window is maximized or not. + /// + /// ## Platform-specific + /// - **iOS / Android / Web:** Unsupported. + FetchMaximized(Id, Box<dyn FnOnce(bool) -> T + 'static>), /// Set the window to maximized or back Maximize(Id, bool), + /// Fetch if the current window is minimized or not. + /// + /// ## Platform-specific + /// - **Wayland:** Always `None`. + /// - **iOS / Android / Web:** Unsupported. + FetchMinimized(Id, Box<dyn FnOnce(Option<bool>) -> T + 'static>), /// Set the window to minimized or back Minimize(Id, bool), /// Move the window to the given logical coordinates. @@ -106,7 +117,13 @@ impl<T> Action<T> { Self::FetchSize(id, o) => { Action::FetchSize(id, Box::new(move |s| f(o(s)))) } + Self::FetchMaximized(id, o) => { + Action::FetchMaximized(id, Box::new(move |s| f(o(s)))) + } Self::Maximize(id, maximized) => Action::Maximize(id, maximized), + Self::FetchMinimized(id, o) => { + Action::FetchMinimized(id, Box::new(move |s| f(o(s)))) + } Self::Minimize(id, minimized) => Action::Minimize(id, minimized), Self::Move(id, position) => Action::Move(id, position), Self::ChangeMode(id, mode) => Action::ChangeMode(id, mode), @@ -144,9 +161,15 @@ impl<T> fmt::Debug for Action<T> { write!(f, "Action::Resize({id:?}, {size:?})") } Self::FetchSize(id, _) => write!(f, "Action::FetchSize({id:?})"), + Self::FetchMaximized(id, _) => { + write!(f, "Action::FetchMaximized({id:?})") + } Self::Maximize(id, maximized) => { write!(f, "Action::Maximize({id:?}, {maximized})") } + Self::FetchMinimized(id, _) => { + write!(f, "Action::FetchMinimized({id:?})") + } Self::Minimize(id, minimized) => { write!(f, "Action::Minimize({id:?}, {minimized}") } diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs index 9c2893a2..fd1ab3de 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -96,7 +96,7 @@ impl Cache { if let Some(svg) = &mut svg { if svg.has_text_nodes() { let mut font_system = - text::font_system().write().expect("Read font system"); + text::font_system().write().expect("Write font system"); svg.convert_text(font_system.raw().db_mut()); } diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 6582bb82..d9be50d7 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -1,9 +1,10 @@ use crate::core::svg; use crate::core::{Color, Size}; +use crate::graphics::text; use crate::image::atlas::{self, Atlas}; use resvg::tiny_skia; -use resvg::usvg; +use resvg::usvg::{self, TreeTextToPath}; use std::collections::{HashMap, HashSet}; use std::fs; @@ -49,15 +50,15 @@ impl Cache { return self.svgs.get(&handle.id()).unwrap(); } - let svg = match handle.data() { - svg::Data::Path(path) => { - let tree = fs::read_to_string(path).ok().and_then(|contents| { + let mut svg = match handle.data() { + svg::Data::Path(path) => fs::read_to_string(path) + .ok() + .and_then(|contents| { usvg::Tree::from_str(&contents, &usvg::Options::default()) .ok() - }); - - tree.map(Svg::Loaded).unwrap_or(Svg::NotFound) - } + }) + .map(Svg::Loaded) + .unwrap_or(Svg::NotFound), svg::Data::Bytes(bytes) => { match usvg::Tree::from_data(bytes, &usvg::Options::default()) { Ok(tree) => Svg::Loaded(tree), @@ -66,6 +67,15 @@ impl Cache { } }; + if let Svg::Loaded(svg) = &mut svg { + if svg.has_text_nodes() { + let mut font_system = + text::font_system().write().expect("Write font system"); + + svg.convert_text(font_system.raw().db_mut()); + } + } + let _ = self.svgs.insert(handle.id(), svg); self.svgs.get(&handle.id()).unwrap() } diff --git a/wgpu/src/primitive/pipeline.rs b/wgpu/src/primitive/pipeline.rs index 302e38f6..c8e45458 100644 --- a/wgpu/src/primitive/pipeline.rs +++ b/wgpu/src/primitive/pipeline.rs @@ -82,7 +82,7 @@ impl<Theme> Renderer for crate::Renderer<Theme> { /// Stores custom, user-provided pipelines. #[derive(Default, Debug)] pub struct Storage { - pipelines: HashMap<TypeId, Box<dyn Any>>, + pipelines: HashMap<TypeId, Box<dyn Any + Send>>, } impl Storage { @@ -92,7 +92,7 @@ impl Storage { } /// Inserts the pipeline `T` in to [`Storage`]. - pub fn store<T: 'static>(&mut self, pipeline: T) { + pub fn store<T: 'static + Send>(&mut self, pipeline: T) { let _ = self.pipelines.insert(TypeId::of::<T>(), Box::new(pipeline)); } diff --git a/widget/src/lazy/helpers.rs b/widget/src/lazy/helpers.rs index 8ca9cb86..5dc60d52 100644 --- a/widget/src/lazy/helpers.rs +++ b/widget/src/lazy/helpers.rs @@ -6,6 +6,7 @@ use std::hash::Hash; /// Creates a new [`Lazy`] widget with the given data `Dependency` and a /// closure that can turn this data into a widget tree. +#[cfg(feature = "lazy")] pub fn lazy<'a, Message, Renderer, Dependency, View>( dependency: Dependency, view: impl Fn(&Dependency) -> View + 'a, @@ -19,6 +20,7 @@ where /// Turns an implementor of [`Component`] into an [`Element`] that can be /// embedded in any application. +#[cfg(feature = "lazy")] pub fn component<'a, C, Message, Renderer>( component: C, ) -> Element<'a, Message, Renderer> @@ -37,6 +39,7 @@ where /// The `view` closure will be provided with the current [`Size`] of /// the [`Responsive`] widget and, therefore, can be used to build the /// contents of the widget in a responsive way. +#[cfg(feature = "lazy")] pub fn responsive<'a, Message, Renderer>( f: impl Fn(Size) -> Element<'a, Message, Renderer> + 'a, ) -> Responsive<'a, Message, Renderer> diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 2d25a543..7057fe59 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -531,6 +531,8 @@ pub fn update<'a, Message, T: Draggable>( on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, on_resize: &Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>, ) -> event::Status { + const DRAG_DEADBAND_DISTANCE: f32 = 10.0; + let mut event_status = event::Status::Ignored; match event { @@ -572,7 +574,6 @@ pub fn update<'a, Message, T: Draggable>( shell, contents, on_click, - on_drag, ); } } @@ -584,7 +585,6 @@ pub fn update<'a, Message, T: Draggable>( shell, contents, on_click, - on_drag, ); } } @@ -637,7 +637,49 @@ pub fn update<'a, Message, T: Draggable>( } Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(touch::Event::FingerMoved { .. }) => { - if let Some((_, on_resize)) = on_resize { + if let Some((_, origin)) = action.clicked_pane() { + if let Some(on_drag) = &on_drag { + let bounds = layout.bounds(); + + if let Some(cursor_position) = cursor.position_over(bounds) + { + let mut clicked_region = contents + .zip(layout.children()) + .filter(|(_, layout)| { + layout.bounds().contains(cursor_position) + }); + + if let Some(((pane, content), layout)) = + clicked_region.next() + { + if content + .can_be_dragged_at(layout, cursor_position) + { + let pane_position = layout.position(); + + let new_origin = cursor_position + - Vector::new( + pane_position.x, + pane_position.y, + ); + + if new_origin.distance(origin) + > DRAG_DEADBAND_DISTANCE + { + *action = state::Action::Dragging { + pane, + origin, + }; + + shell.publish(on_drag(DragEvent::Picked { + pane, + })); + } + } + } + } + } + } else if let Some((_, on_resize)) = on_resize { if let Some((split, _)) = action.picked_split() { let bounds = layout.bounds(); @@ -712,7 +754,6 @@ fn click_pane<'a, Message, T>( shell: &mut Shell<'_, Message>, contents: impl Iterator<Item = (Pane, T)>, on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>, - on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, ) where T: Draggable, { @@ -720,23 +761,15 @@ fn click_pane<'a, Message, T>( .zip(layout.children()) .filter(|(_, layout)| layout.bounds().contains(cursor_position)); - if let Some(((pane, content), layout)) = clicked_region.next() { + if let Some(((pane, _), layout)) = clicked_region.next() { if let Some(on_click) = &on_click { shell.publish(on_click(pane)); } - if let Some(on_drag) = &on_drag { - if content.can_be_dragged_at(layout, cursor_position) { - let pane_position = layout.position(); - - let origin = cursor_position - - Vector::new(pane_position.x, pane_position.y); - - *action = state::Action::Dragging { pane, origin }; - - shell.publish(on_drag(DragEvent::Picked { pane })); - } - } + let pane_position = layout.position(); + let origin = + cursor_position - Vector::new(pane_position.x, pane_position.y); + *action = state::Action::Clicking { pane, origin }; } } @@ -749,7 +782,7 @@ pub fn mouse_interaction( spacing: f32, resize_leeway: Option<f32>, ) -> Option<mouse::Interaction> { - if action.picked_pane().is_some() { + if action.clicked_pane().is_some() || action.picked_pane().is_some() { return Some(mouse::Interaction::Grabbing); } diff --git a/widget/src/pane_grid/state.rs b/widget/src/pane_grid/state.rs index 481cd770..5d1fe254 100644 --- a/widget/src/pane_grid/state.rs +++ b/widget/src/pane_grid/state.rs @@ -403,6 +403,15 @@ pub enum Action { /// /// [`PaneGrid`]: super::PaneGrid Idle, + /// A [`Pane`] in the [`PaneGrid`] is being clicked. + /// + /// [`PaneGrid`]: super::PaneGrid + Clicking { + /// The [`Pane`] being clicked. + pane: Pane, + /// The starting [`Point`] of the click interaction. + origin: Point, + }, /// A [`Pane`] in the [`PaneGrid`] is being dragged. /// /// [`PaneGrid`]: super::PaneGrid @@ -432,6 +441,14 @@ impl Action { } } + /// Returns the current [`Pane`] that is being clicked, if any. + pub fn clicked_pane(&self) -> Option<(Pane, Point)> { + match *self { + Action::Clicking { pane, origin, .. } => Some((pane, origin)), + _ => None, + } + } + /// Returns the current [`Split`] that is being dragged, if any. pub fn picked_split(&self) -> Option<(Split, Axis)> { match *self { diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 63d48868..a2a186f0 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -128,6 +128,15 @@ where highlighter_format: to_format, } } + + /// Sets the style of the [`TextEditor`]. + pub fn style( + mut self, + style: impl Into<<Renderer::Theme as StyleSheet>::Style>, + ) -> Self { + self.style = style.into(); + self + } } /// The content of a [`TextEditor`]. diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index 9e102c56..b888980a 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -64,6 +64,12 @@ where self } + /// Sets the [`text::Shaping`] strategy of the [`Tooltip`]. + pub fn text_shaping(mut self, shaping: text::Shaping) -> Self { + self.tooltip = self.tooltip.shaping(shaping); + self + } + /// Sets the font of the [`Tooltip`]. /// /// [`Font`]: Renderer::Font diff --git a/winit/src/application.rs b/winit/src/application.rs index d9700075..35a35872 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -742,9 +742,19 @@ pub fn run_command<A, C, E>( ))) .expect("Send message to event loop"); } + window::Action::FetchMaximized(_id, callback) => { + proxy + .send_event(callback(window.is_maximized())) + .expect("Send message to event loop"); + } window::Action::Maximize(_id, maximized) => { window.set_maximized(maximized); } + window::Action::FetchMinimized(_id, callback) => { + proxy + .send_event(callback(window.is_minimized())) + .expect("Send message to event loop"); + } window::Action::Minimize(_id, minimized) => { window.set_minimized(minimized); } diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 84651d40..1550b94b 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -942,11 +942,25 @@ fn run_command<A, C, E>( .expect("Send message to event loop"); } } + window::Action::FetchMaximized(id, callback) => { + if let Some(window) = window_manager.get_mut(id) { + proxy + .send_event(callback(window.raw.is_maximized())) + .expect("Send message to event loop"); + } + } window::Action::Maximize(id, maximized) => { if let Some(window) = window_manager.get_mut(id) { window.raw.set_maximized(maximized); } } + window::Action::FetchMinimized(id, callback) => { + if let Some(window) = window_manager.get_mut(id) { + proxy + .send_event(callback(window.raw.is_minimized())) + .expect("Send message to event loop"); + } + } window::Action::Minimize(id, minimized) => { if let Some(window) = window_manager.get_mut(id) { window.raw.set_minimized(minimized); |