summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/src/window/event.rs2
-rw-r--r--examples/websocket/Cargo.toml2
-rw-r--r--graphics/src/text/paragraph.rs25
-rw-r--r--renderer/src/backend.rs100
-rw-r--r--runtime/src/window.rs22
-rw-r--r--runtime/src/window/action.rs23
-rw-r--r--tiny_skia/src/vector.rs2
-rw-r--r--wgpu/src/image/vector.rs26
-rw-r--r--wgpu/src/primitive/pipeline.rs4
-rw-r--r--widget/src/lazy/helpers.rs3
-rw-r--r--widget/src/pane_grid.rs69
-rw-r--r--widget/src/pane_grid/state.rs17
-rw-r--r--widget/src/text_editor.rs9
-rw-r--r--widget/src/tooltip.rs6
-rw-r--r--winit/src/application.rs10
-rw-r--r--winit/src/multi_window.rs14
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);