summaryrefslogtreecommitdiffstats
path: root/native/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--core/src/element.rs (renamed from native/src/element.rs)139
-rw-r--r--core/src/event.rs (renamed from native/src/event.rs)2
-rw-r--r--core/src/hasher.rs (renamed from native/src/hasher.rs)0
-rw-r--r--core/src/image.rs (renamed from native/src/image.rs)12
-rw-r--r--core/src/layout.rs (renamed from native/src/layout.rs)0
-rw-r--r--core/src/layout/DRUID_LICENSE (renamed from native/src/layout/DRUID_LICENSE)0
-rw-r--r--core/src/layout/flex.rs (renamed from native/src/layout/flex.rs)0
-rw-r--r--core/src/layout/limits.rs (renamed from native/src/layout/limits.rs)0
-rw-r--r--core/src/layout/node.rs (renamed from native/src/layout/node.rs)0
-rw-r--r--core/src/mouse/click.rs (renamed from native/src/mouse/click.rs)0
-rw-r--r--core/src/overlay.rs (renamed from native/src/overlay.rs)25
-rw-r--r--core/src/overlay/element.rs (renamed from native/src/overlay/element.rs)83
-rw-r--r--core/src/overlay/group.rs (renamed from native/src/overlay/group.rs)47
-rw-r--r--core/src/renderer.rs (renamed from native/src/renderer.rs)26
-rw-r--r--core/src/renderer/null.rs (renamed from native/src/renderer/null.rs)24
-rw-r--r--core/src/shell.rs (renamed from native/src/shell.rs)0
-rw-r--r--core/src/svg.rs (renamed from native/src/svg.rs)4
-rw-r--r--core/src/touch.rs (renamed from native/src/touch.rs)0
-rw-r--r--core/src/widget.rs (renamed from native/src/widget.rs)103
-rw-r--r--core/src/widget/id.rs (renamed from native/src/widget/id.rs)2
-rw-r--r--core/src/widget/operation/focusable.rs (renamed from native/src/widget/operation/focusable.rs)0
-rw-r--r--core/src/widget/operation/scrollable.rs (renamed from native/src/widget/operation/scrollable.rs)41
-rw-r--r--core/src/widget/operation/text_input.rs (renamed from native/src/widget/operation/text_input.rs)0
-rw-r--r--core/src/widget/text.rs (renamed from native/src/widget/text.rs)115
-rw-r--r--core/src/widget/tree.rs (renamed from native/src/widget/tree.rs)0
-rw-r--r--core/src/window/event.rs (renamed from native/src/window/event.rs)0
-rw-r--r--core/src/window/id.rs (renamed from native/src/window/id.rs)0
-rw-r--r--core/src/window/mode.rs (renamed from native/src/window/mode.rs)0
-rw-r--r--core/src/window/redraw_request.rs (renamed from native/src/window/redraw_request.rs)0
-rw-r--r--core/src/window/settings.rs (renamed from native/src/window/settings.rs)32
-rw-r--r--core/src/window/user_attention.rs (renamed from native/src/window/user_attention.rs)0
-rw-r--r--native/src/mouse.rs6
-rw-r--r--native/src/runtime.rs18
-rw-r--r--native/src/subscription.rs248
-rw-r--r--native/src/text.rs114
-rw-r--r--native/src/widget/action.rs154
-rw-r--r--native/src/widget/helpers.rs317
-rw-r--r--native/src/widget/operation.rs112
-rw-r--r--native/src/window.rs38
-rw-r--r--native/src/window/icon.rs12
-rw-r--r--native/src/window/position.rs22
-rw-r--r--runtime/src/clipboard.rs (renamed from native/src/clipboard.rs)37
-rw-r--r--runtime/src/command.rs (renamed from native/src/command.rs)55
-rw-r--r--runtime/src/command/action.rs (renamed from native/src/command/action.rs)24
-rw-r--r--runtime/src/debug/basic.rs (renamed from native/src/debug/basic.rs)2
-rw-r--r--runtime/src/debug/null.rs (renamed from native/src/debug/null.rs)0
-rw-r--r--runtime/src/keyboard.rs (renamed from native/src/keyboard.rs)0
-rw-r--r--runtime/src/lib.rs (renamed from native/src/lib.rs)50
-rw-r--r--runtime/src/program.rs (renamed from native/src/program.rs)7
-rw-r--r--runtime/src/program/state.rs (renamed from native/src/program/state.rs)64
-rw-r--r--runtime/src/system.rs (renamed from native/src/system.rs)0
-rw-r--r--runtime/src/system/action.rs (renamed from native/src/system/action.rs)0
-rw-r--r--runtime/src/system/information.rs (renamed from native/src/system/information.rs)0
-rw-r--r--runtime/src/user_interface.rs (renamed from native/src/user_interface.rs)224
-rw-r--r--runtime/src/window/action.rs (renamed from native/src/window/action.rs)73
-rw-r--r--widget/src/button.rs (renamed from native/src/widget/button.rs)92
-rw-r--r--widget/src/checkbox.rs (renamed from native/src/widget/checkbox.rs)130
-rw-r--r--widget/src/column.rs (renamed from native/src/widget/column.rs)50
-rw-r--r--widget/src/container.rs (renamed from native/src/widget/container.rs)42
-rw-r--r--widget/src/image.rs (renamed from native/src/widget/image.rs)22
-rw-r--r--widget/src/image/viewer.rs (renamed from native/src/widget/image/viewer.rs)39
-rw-r--r--widget/src/overlay/menu.rs (renamed from native/src/overlay/menu.rs)219
-rw-r--r--widget/src/pane_grid.rs (renamed from native/src/widget/pane_grid.rs)500
-rw-r--r--widget/src/pane_grid/axis.rs (renamed from native/src/widget/pane_grid/axis.rs)2
-rw-r--r--widget/src/pane_grid/configuration.rs (renamed from native/src/widget/pane_grid/configuration.rs)2
-rw-r--r--widget/src/pane_grid/content.rs (renamed from native/src/widget/pane_grid/content.rs)102
-rw-r--r--widget/src/pane_grid/direction.rs (renamed from native/src/widget/pane_grid/direction.rs)0
-rw-r--r--widget/src/pane_grid/draggable.rs (renamed from native/src/widget/pane_grid/draggable.rs)8
-rw-r--r--widget/src/pane_grid/node.rs (renamed from native/src/widget/pane_grid/node.rs)14
-rw-r--r--widget/src/pane_grid/pane.rs (renamed from native/src/widget/pane_grid/pane.rs)0
-rw-r--r--widget/src/pane_grid/split.rs (renamed from native/src/widget/pane_grid/split.rs)0
-rw-r--r--widget/src/pane_grid/state.rs (renamed from native/src/widget/pane_grid/state.rs)107
-rw-r--r--widget/src/pane_grid/title_bar.rs (renamed from native/src/widget/pane_grid/title_bar.rs)42
-rw-r--r--widget/src/pick_list.rs (renamed from native/src/widget/pick_list.rs)228
-rw-r--r--widget/src/progress_bar.rs (renamed from native/src/widget/progress_bar.rs)31
-rw-r--r--widget/src/radio.rs (renamed from native/src/widget/radio.rs)131
-rw-r--r--widget/src/row.rs (renamed from native/src/widget/row.rs)50
-rw-r--r--widget/src/rule.rs (renamed from native/src/widget/rule.rs)27
-rw-r--r--widget/src/scrollable.rs (renamed from native/src/widget/scrollable.rs)843
-rw-r--r--widget/src/slider.rs (renamed from native/src/widget/slider.rs)155
-rw-r--r--widget/src/space.rs (renamed from native/src/widget/space.rs)16
-rw-r--r--widget/src/svg.rs (renamed from native/src/widget/svg.rs)19
-rw-r--r--widget/src/text_input.rs (renamed from native/src/widget/text_input.rs)374
-rw-r--r--widget/src/text_input/cursor.rs (renamed from native/src/widget/text_input/cursor.rs)2
-rw-r--r--widget/src/text_input/editor.rs (renamed from native/src/widget/text_input/editor.rs)2
-rw-r--r--widget/src/text_input/value.rs (renamed from native/src/widget/text_input/value.rs)0
-rw-r--r--widget/src/toggler.rs (renamed from native/src/widget/toggler.rs)89
-rw-r--r--widget/src/tooltip.rs (renamed from native/src/widget/tooltip.rs)313
-rw-r--r--widget/src/vertical_slider.rs (renamed from native/src/widget/vertical_slider.rs)155
89 files changed, 3042 insertions, 2996 deletions
diff --git a/native/src/element.rs b/core/src/element.rs
index 0a677d20..3268f14b 100644
--- a/native/src/element.rs
+++ b/core/src/element.rs
@@ -5,9 +5,7 @@ use crate::overlay;
use crate::renderer;
use crate::widget;
use crate::widget::tree::{self, Tree};
-use crate::{
- Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Widget,
-};
+use crate::{Clipboard, Color, Layout, Length, Rectangle, Shell, Widget};
use std::any::Any;
use std::borrow::Borrow;
@@ -90,41 +88,65 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> {
/// We compose the previous __messages__ with the index of the counter
/// producing them. Let's implement our __view logic__ now:
///
- /// ```
+ /// ```no_run
/// # mod counter {
- /// # type Text<'a> = iced_native::widget::Text<'a, iced_native::renderer::Null>;
- /// #
/// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {}
/// # pub struct Counter;
/// #
/// # impl Counter {
- /// # pub fn view(&mut self) -> Text {
- /// # Text::new("")
+ /// # pub fn view(
+ /// # &self,
+ /// # ) -> iced_core::Element<Message, iced_core::renderer::Null> {
+ /// # unimplemented!()
/// # }
/// # }
/// # }
/// #
- /// # mod iced_wgpu {
- /// # pub use iced_native::renderer::Null as Renderer;
- /// # }
+ /// # mod iced {
+ /// # pub use iced_core::renderer::Null as Renderer;
+ /// # pub use iced_core::Element;
/// #
- /// # use counter::Counter;
+ /// # pub mod widget {
+ /// # pub struct Row<Message> {
+ /// # _t: std::marker::PhantomData<Message>,
+ /// # }
/// #
- /// # struct ManyCounters {
- /// # counters: Vec<Counter>,
- /// # }
+ /// # impl<Message> Row<Message> {
+ /// # pub fn new() -> Self {
+ /// # unimplemented!()
+ /// # }
/// #
- /// # #[derive(Debug, Clone, Copy)]
- /// # pub enum Message {
- /// # Counter(usize, counter::Message)
+ /// # pub fn spacing(mut self, _: u32) -> Self {
+ /// # unimplemented!()
+ /// # }
+ /// #
+ /// # pub fn push(
+ /// # mut self,
+ /// # _: iced_core::Element<Message, iced_core::renderer::Null>,
+ /// # ) -> Self {
+ /// # unimplemented!()
+ /// # }
+ /// # }
+ /// # }
/// # }
- /// use iced_native::Element;
- /// use iced_native::widget::Row;
- /// use iced_wgpu::Renderer;
+ /// #
+ /// use counter::Counter;
+ ///
+ /// use iced::widget::Row;
+ /// use iced::{Element, Renderer};
+ ///
+ /// struct ManyCounters {
+ /// counters: Vec<Counter>,
+ /// }
+ ///
+ /// #[derive(Debug, Clone, Copy)]
+ /// pub enum Message {
+ /// Counter(usize, counter::Message),
+ /// }
///
/// impl ManyCounters {
- /// pub fn view(&mut self) -> Row<Message, Renderer> {
+ /// pub fn view(&mut self) -> Row<Message> {
/// // We can quickly populate a `Row` by folding over our counters
/// self.counters.iter_mut().enumerate().fold(
/// Row::new().spacing(20),
@@ -137,9 +159,10 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> {
/// // Here we turn our `Element<counter::Message>` into
/// // an `Element<Message>` by combining the `index` and the
/// // message of the `element`.
- /// element.map(move |message| Message::Counter(index, message))
+ /// element
+ /// .map(move |message| Message::Counter(index, message)),
/// )
- /// }
+ /// },
/// )
/// }
/// }
@@ -353,7 +376,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, B>,
@@ -365,7 +388,7 @@ where
tree,
event,
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
&mut local_shell,
@@ -383,35 +406,23 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
- self.widget.draw(
- tree,
- renderer,
- theme,
- style,
- layout,
- cursor_position,
- viewport,
- )
+ self.widget
+ .draw(tree, renderer, theme, style, layout, cursor, viewport)
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
- self.widget.mouse_interaction(
- tree,
- layout,
- cursor_position,
- viewport,
- renderer,
- )
+ self.widget
+ .mouse_interaction(tree, layout, cursor, viewport, renderer)
}
fn overlay<'b>(
@@ -496,20 +507,14 @@ where
state: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
- self.element.widget.on_event(
- state,
- event,
- layout,
- cursor_position,
- renderer,
- clipboard,
- shell,
- )
+ self.element
+ .widget
+ .on_event(state, event, layout, cursor, renderer, clipboard, shell)
}
fn draw(
@@ -519,7 +524,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
fn explain_layout<Renderer: crate::Renderer>(
@@ -542,15 +547,9 @@ where
}
}
- self.element.widget.draw(
- state,
- renderer,
- theme,
- style,
- layout,
- cursor_position,
- viewport,
- );
+ self.element
+ .widget
+ .draw(state, renderer, theme, style, layout, cursor, viewport);
explain_layout(renderer, self.color, layout);
}
@@ -559,17 +558,13 @@ where
&self,
state: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
- self.element.widget.mouse_interaction(
- state,
- layout,
- cursor_position,
- viewport,
- renderer,
- )
+ self.element
+ .widget
+ .mouse_interaction(state, layout, cursor, viewport, renderer)
}
fn overlay<'b>(
diff --git a/native/src/event.rs b/core/src/event.rs
index eb826399..870b3074 100644
--- a/native/src/event.rs
+++ b/core/src/event.rs
@@ -62,7 +62,7 @@ impl Status {
/// `Captured` takes precedence over `Ignored`:
///
/// ```
- /// use iced_native::event::Status;
+ /// use iced_core::event::Status;
///
/// assert_eq!(Status::Ignored.merge(Status::Ignored), Status::Ignored);
/// assert_eq!(Status::Ignored.merge(Status::Captured), Status::Captured);
diff --git a/native/src/hasher.rs b/core/src/hasher.rs
index fa52f16d..fa52f16d 100644
--- a/native/src/hasher.rs
+++ b/core/src/hasher.rs
diff --git a/native/src/image.rs b/core/src/image.rs
index 70fbade0..85d9d475 100644
--- a/native/src/image.rs
+++ b/core/src/image.rs
@@ -6,7 +6,7 @@ use std::path::PathBuf;
use std::sync::Arc;
/// A handle of some image data.
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Handle {
id: u64,
data: Data,
@@ -110,6 +110,14 @@ impl std::hash::Hash for Bytes {
}
}
+impl PartialEq for Bytes {
+ fn eq(&self, other: &Self) -> bool {
+ self.as_ref() == other.as_ref()
+ }
+}
+
+impl Eq for Bytes {}
+
impl AsRef<[u8]> for Bytes {
fn as_ref(&self) -> &[u8] {
self.0.as_ref().as_ref()
@@ -125,7 +133,7 @@ impl std::ops::Deref for Bytes {
}
/// The data of a raster image.
-#[derive(Clone, Hash)]
+#[derive(Clone, PartialEq, Eq, Hash)]
pub enum Data {
/// File data
Path(PathBuf),
diff --git a/native/src/layout.rs b/core/src/layout.rs
index 04954fb9..04954fb9 100644
--- a/native/src/layout.rs
+++ b/core/src/layout.rs
diff --git a/native/src/layout/DRUID_LICENSE b/core/src/layout/DRUID_LICENSE
index d6456956..d6456956 100644
--- a/native/src/layout/DRUID_LICENSE
+++ b/core/src/layout/DRUID_LICENSE
diff --git a/native/src/layout/flex.rs b/core/src/layout/flex.rs
index 8b967849..8b967849 100644
--- a/native/src/layout/flex.rs
+++ b/core/src/layout/flex.rs
diff --git a/native/src/layout/limits.rs b/core/src/layout/limits.rs
index 5d3c1556..5d3c1556 100644
--- a/native/src/layout/limits.rs
+++ b/core/src/layout/limits.rs
diff --git a/native/src/layout/node.rs b/core/src/layout/node.rs
index 2b44a7d5..2b44a7d5 100644
--- a/native/src/layout/node.rs
+++ b/core/src/layout/node.rs
diff --git a/native/src/mouse/click.rs b/core/src/mouse/click.rs
index 4a7d796c..4a7d796c 100644
--- a/native/src/mouse/click.rs
+++ b/core/src/mouse/click.rs
diff --git a/native/src/overlay.rs b/core/src/overlay.rs
index 6cada416..2e05db93 100644
--- a/native/src/overlay.rs
+++ b/core/src/overlay.rs
@@ -2,11 +2,8 @@
mod element;
mod group;
-pub mod menu;
-
pub use element::Element;
pub use group::Group;
-pub use menu::Menu;
use crate::event::{self, Event};
use crate::layout;
@@ -41,7 +38,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
);
/// Applies a [`widget::Operation`] to the [`Overlay`].
@@ -69,7 +66,7 @@ where
&mut self,
_event: Event,
_layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
_shell: &mut Shell<'_, Message>,
@@ -83,7 +80,7 @@ where
fn mouse_interaction(
&self,
_layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
@@ -94,9 +91,23 @@ where
///
/// By default, it returns true if the bounds of the `layout` contain
/// the `cursor_position`.
- fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
+ fn is_over(
+ &self,
+ layout: Layout<'_>,
+ _renderer: &Renderer,
+ cursor_position: Point,
+ ) -> bool {
layout.bounds().contains(cursor_position)
}
+
+ /// Returns the nested overlay of the [`Overlay`], if there is any.
+ fn overlay<'a>(
+ &'a mut self,
+ _layout: Layout<'_>,
+ _renderer: &Renderer,
+ ) -> Option<Element<'a, Message, Renderer>> {
+ None
+ }
}
/// Returns a [`Group`] of overlay [`Element`] children.
diff --git a/native/src/overlay/element.rs b/core/src/overlay/element.rs
index 237d25d1..c2134343 100644
--- a/native/src/overlay/element.rs
+++ b/core/src/overlay/element.rs
@@ -68,35 +68,25 @@ where
&mut self,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
- self.overlay.on_event(
- event,
- layout,
- cursor_position,
- renderer,
- clipboard,
- shell,
- )
+ self.overlay
+ .on_event(event, layout, cursor, renderer, clipboard, shell)
}
/// Returns the current [`mouse::Interaction`] of the [`Element`].
pub fn mouse_interaction(
&self,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
- self.overlay.mouse_interaction(
- layout,
- cursor_position,
- viewport,
- renderer,
- )
+ self.overlay
+ .mouse_interaction(layout, cursor, viewport, renderer)
}
/// Draws the [`Element`] and its children using the given [`Layout`].
@@ -106,10 +96,9 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) {
- self.overlay
- .draw(renderer, theme, style, layout, cursor_position)
+ self.overlay.draw(renderer, theme, style, layout, cursor)
}
/// Applies a [`widget::Operation`] to the [`Element`].
@@ -123,8 +112,22 @@ where
}
/// Returns true if the cursor is over the [`Element`].
- pub fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
- self.overlay.is_over(layout, cursor_position)
+ pub fn is_over(
+ &self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ cursor_position: Point,
+ ) -> bool {
+ self.overlay.is_over(layout, renderer, cursor_position)
+ }
+
+ /// Returns the nested overlay of the [`Element`], if there is any.
+ pub fn overlay<'b>(
+ &'b mut self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ ) -> Option<Element<'b, Message, Renderer>> {
+ self.overlay.overlay(layout, renderer)
}
}
@@ -215,7 +218,7 @@ where
&mut self,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, B>,
@@ -226,7 +229,7 @@ where
let event_status = self.content.on_event(
event,
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
&mut local_shell,
@@ -240,16 +243,12 @@ where
fn mouse_interaction(
&self,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
- self.content.mouse_interaction(
- layout,
- cursor_position,
- viewport,
- renderer,
- )
+ self.content
+ .mouse_interaction(layout, cursor, viewport, renderer)
}
fn draw(
@@ -258,13 +257,27 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) {
- self.content
- .draw(renderer, theme, style, layout, cursor_position)
+ self.content.draw(renderer, theme, style, layout, cursor)
}
- fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
- self.content.is_over(layout, cursor_position)
+ fn is_over(
+ &self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ cursor_position: Point,
+ ) -> bool {
+ self.content.is_over(layout, renderer, cursor_position)
+ }
+
+ fn overlay<'b>(
+ &'b mut self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ ) -> Option<Element<'b, B, Renderer>> {
+ self.content
+ .overlay(layout, renderer)
+ .map(|overlay| overlay.map(self.mapper))
}
}
diff --git a/native/src/overlay/group.rs b/core/src/overlay/group.rs
index 1126f0cf..deffaad0 100644
--- a/native/src/overlay/group.rs
+++ b/core/src/overlay/group.rs
@@ -1,12 +1,10 @@
-use iced_core::{Point, Rectangle, Size};
-
use crate::event;
use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::widget;
-use crate::{Clipboard, Event, Layout, Overlay, Shell};
+use crate::{Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size};
/// An [`Overlay`] container that displays multiple overlay [`overlay::Element`]
/// children.
@@ -83,7 +81,7 @@ where
&mut self,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -95,7 +93,7 @@ where
child.on_event(
event.clone(),
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -110,17 +108,17 @@ where
theme: &<Renderer as crate::Renderer>::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) {
for (child, layout) in self.children.iter().zip(layout.children()) {
- child.draw(renderer, theme, style, layout, cursor_position);
+ child.draw(renderer, theme, style, layout, cursor);
}
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
@@ -128,12 +126,7 @@ where
.iter()
.zip(layout.children())
.map(|(child, layout)| {
- child.mouse_interaction(
- layout,
- cursor_position,
- viewport,
- renderer,
- )
+ child.mouse_interaction(layout, cursor, viewport, renderer)
})
.max()
.unwrap_or_default()
@@ -154,11 +147,33 @@ where
});
}
- fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
+ fn is_over(
+ &self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ cursor_position: Point,
+ ) -> bool {
self.children
.iter()
.zip(layout.children())
- .any(|(child, layout)| child.is_over(layout, cursor_position))
+ .any(|(child, layout)| {
+ child.is_over(layout, renderer, cursor_position)
+ })
+ }
+
+ fn overlay<'b>(
+ &'b mut self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ ) -> Option<overlay::Element<'b, Message, Renderer>> {
+ let children = self
+ .children
+ .iter_mut()
+ .zip(layout.children())
+ .filter_map(|(child, layout)| child.overlay(layout, renderer))
+ .collect::<Vec<_>>();
+
+ (!children.is_empty()).then(|| Group::with_children(children).overlay())
}
}
diff --git a/native/src/renderer.rs b/core/src/renderer.rs
index 2ac78982..7c73d2e4 100644
--- a/native/src/renderer.rs
+++ b/core/src/renderer.rs
@@ -1,11 +1,12 @@
//! Write your own renderer.
#[cfg(debug_assertions)]
mod null;
+
#[cfg(debug_assertions)]
pub use null::Null;
use crate::layout;
-use crate::{Background, Color, Element, Rectangle, Vector};
+use crate::{Background, BorderRadius, Color, Element, Rectangle, Vector};
/// A component that can be used by widgets to draw themselves on a screen.
pub trait Renderer: Sized {
@@ -59,29 +60,6 @@ pub struct Quad {
pub border_color: Color,
}
-/// The border radi for the corners of a graphics primitive in the order:
-/// top-left, top-right, bottom-right, bottom-left.
-#[derive(Debug, Clone, Copy, PartialEq, Default)]
-pub struct BorderRadius([f32; 4]);
-
-impl From<f32> for BorderRadius {
- fn from(w: f32) -> Self {
- Self([w; 4])
- }
-}
-
-impl From<[f32; 4]> for BorderRadius {
- fn from(radi: [f32; 4]) -> Self {
- Self(radi)
- }
-}
-
-impl From<BorderRadius> for [f32; 4] {
- fn from(radi: BorderRadius) -> Self {
- radi.0
- }
-}
-
/// The styling attributes of a [`Renderer`].
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {
diff --git a/native/src/renderer/null.rs b/core/src/renderer/null.rs
index 9376d540..5d49699e 100644
--- a/native/src/renderer/null.rs
+++ b/core/src/renderer/null.rs
@@ -1,6 +1,8 @@
use crate::renderer::{self, Renderer};
use crate::text::{self, Text};
-use crate::{Background, Font, Point, Rectangle, Size, Theme, Vector};
+use crate::{Background, Font, Point, Rectangle, Size, Vector};
+
+use std::borrow::Cow;
/// A renderer that does nothing.
///
@@ -16,7 +18,7 @@ impl Null {
}
impl Renderer for Null {
- type Theme = Theme;
+ type Theme = ();
fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {}
@@ -40,30 +42,40 @@ impl Renderer for Null {
impl text::Renderer for Null {
type Font = Font;
- const ICON_FONT: Font = Font::Default;
+ const ICON_FONT: Font = Font::DEFAULT;
const CHECKMARK_ICON: char = '0';
const ARROW_DOWN_ICON: char = '0';
+ fn default_font(&self) -> Self::Font {
+ Font::default()
+ }
+
fn default_size(&self) -> f32 {
- 20.0
+ 16.0
}
+ fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
+
fn measure(
&self,
_content: &str,
_size: f32,
+ _line_height: text::LineHeight,
_font: Font,
_bounds: Size,
- ) -> (f32, f32) {
- (0.0, 20.0)
+ _shaping: text::Shaping,
+ ) -> Size {
+ Size::new(0.0, 20.0)
}
fn hit_test(
&self,
_contents: &str,
_size: f32,
+ _line_height: text::LineHeight,
_font: Self::Font,
_bounds: Size,
+ _shaping: text::Shaping,
_point: Point,
_nearest_only: bool,
) -> Option<text::Hit> {
diff --git a/native/src/shell.rs b/core/src/shell.rs
index 74a5c616..74a5c616 100644
--- a/native/src/shell.rs
+++ b/core/src/shell.rs
diff --git a/native/src/svg.rs b/core/src/svg.rs
index 9b98877a..54e9434e 100644
--- a/native/src/svg.rs
+++ b/core/src/svg.rs
@@ -7,7 +7,7 @@ use std::path::PathBuf;
use std::sync::Arc;
/// A handle of Svg data.
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Handle {
id: u64,
data: Arc<Data>,
@@ -57,7 +57,7 @@ impl Hash for Handle {
}
/// The data of a vectorial image.
-#[derive(Clone, Hash)]
+#[derive(Clone, Hash, PartialEq, Eq)]
pub enum Data {
/// File data
Path(PathBuf),
diff --git a/native/src/touch.rs b/core/src/touch.rs
index 18120644..18120644 100644
--- a/native/src/touch.rs
+++ b/core/src/touch.rs
diff --git a/native/src/widget.rs b/core/src/widget.rs
index 2b3ca7be..79d86444 100644
--- a/native/src/widget.rs
+++ b/core/src/widget.rs
@@ -1,98 +1,21 @@
-//! Use the built-in widgets or create your own.
-//!
-//! # Built-in widgets
-//! Every built-in drawable widget has its own module with a `Renderer` trait
-//! that must be implemented by a [renderer] before being able to use it as
-//! a [`Widget`].
-//!
-//! # Custom widgets
-//! If you want to implement a custom widget, you simply need to implement the
-//! [`Widget`] trait. You can use the API of the built-in widgets as a guide or
-//! source of inspiration.
-//!
-//! [renderer]: crate::renderer
-pub mod button;
-pub mod checkbox;
-pub mod column;
-pub mod container;
-pub mod helpers;
-pub mod image;
+//! Create custom widgets and operate on them.
pub mod operation;
-pub mod pane_grid;
-pub mod pick_list;
-pub mod progress_bar;
-pub mod radio;
-pub mod row;
-pub mod rule;
-pub mod scrollable;
-pub mod slider;
-pub mod space;
-pub mod svg;
pub mod text;
-pub mod text_input;
-pub mod toggler;
-pub mod tooltip;
pub mod tree;
-pub mod vertical_slider;
-mod action;
mod id;
-#[doc(no_inline)]
-pub use button::Button;
-#[doc(no_inline)]
-pub use checkbox::Checkbox;
-#[doc(no_inline)]
-pub use column::Column;
-#[doc(no_inline)]
-pub use container::Container;
-#[doc(no_inline)]
-pub use helpers::*;
-#[doc(no_inline)]
-pub use image::Image;
-#[doc(no_inline)]
-pub use pane_grid::PaneGrid;
-#[doc(no_inline)]
-pub use pick_list::PickList;
-#[doc(no_inline)]
-pub use progress_bar::ProgressBar;
-#[doc(no_inline)]
-pub use radio::Radio;
-#[doc(no_inline)]
-pub use row::Row;
-#[doc(no_inline)]
-pub use rule::Rule;
-#[doc(no_inline)]
-pub use scrollable::Scrollable;
-#[doc(no_inline)]
-pub use slider::Slider;
-#[doc(no_inline)]
-pub use space::Space;
-#[doc(no_inline)]
-pub use svg::Svg;
-#[doc(no_inline)]
-pub use text::Text;
-#[doc(no_inline)]
-pub use text_input::TextInput;
-#[doc(no_inline)]
-pub use toggler::Toggler;
-#[doc(no_inline)]
-pub use tooltip::Tooltip;
-#[doc(no_inline)]
-pub use tree::Tree;
-#[doc(no_inline)]
-pub use vertical_slider::VerticalSlider;
-
-pub use action::Action;
pub use id::Id;
pub use operation::Operation;
+pub use text::Text;
+pub use tree::Tree;
use crate::event::{self, Event};
-use crate::layout;
+use crate::layout::{self, Layout};
use crate::mouse;
use crate::overlay;
use crate::renderer;
-use crate::{Clipboard, Layout, Length, Point, Rectangle, Shell};
+use crate::{Clipboard, Length, Rectangle, Shell};
/// A component that displays information and allows interaction.
///
@@ -110,12 +33,12 @@ use crate::{Clipboard, Layout, Length, Point, Rectangle, Shell};
/// - [`geometry`], a custom widget showcasing how to draw geometry with the
/// `Mesh2D` primitive in [`iced_wgpu`].
///
-/// [examples]: https://github.com/iced-rs/iced/tree/0.8/examples
-/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.8/examples/bezier_tool
-/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.8/examples/custom_widget
-/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.8/examples/geometry
+/// [examples]: https://github.com/iced-rs/iced/tree/0.9/examples
+/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.9/examples/bezier_tool
+/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.9/examples/custom_widget
+/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.9/examples/geometry
/// [`lyon`]: https://github.com/nical/lyon
-/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.8/wgpu
+/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.9/wgpu
pub trait Widget<Message, Renderer>
where
Renderer: crate::Renderer,
@@ -144,7 +67,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
);
@@ -188,7 +111,7 @@ where
_state: &mut Tree,
_event: Event,
_layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
_shell: &mut Shell<'_, Message>,
@@ -203,7 +126,7 @@ where
&self,
_state: &Tree,
_layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
diff --git a/native/src/widget/id.rs b/core/src/widget/id.rs
index 4b8fedf1..ae739bb7 100644
--- a/native/src/widget/id.rs
+++ b/core/src/widget/id.rs
@@ -24,7 +24,7 @@ impl Id {
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub enum Internal {
+enum Internal {
Unique(usize),
Custom(borrow::Cow<'static, str>),
}
diff --git a/native/src/widget/operation/focusable.rs b/core/src/widget/operation/focusable.rs
index 312e4894..312e4894 100644
--- a/native/src/widget/operation/focusable.rs
+++ b/core/src/widget/operation/focusable.rs
diff --git a/native/src/widget/operation/scrollable.rs b/core/src/widget/operation/scrollable.rs
index 3b20631f..f947344d 100644
--- a/native/src/widget/operation/scrollable.rs
+++ b/core/src/widget/operation/scrollable.rs
@@ -5,6 +5,9 @@ use crate::widget::{Id, Operation};
pub trait Scrollable {
/// Snaps the scroll of the widget to the given `percentage` along the horizontal & vertical axis.
fn snap_to(&mut self, offset: RelativeOffset);
+
+ /// Scroll the widget to the given [`AbsoluteOffset`] along the horizontal & vertical axis.
+ fn scroll_to(&mut self, offset: AbsoluteOffset);
}
/// Produces an [`Operation`] that snaps the widget with the given [`Id`] to
@@ -34,7 +37,43 @@ pub fn snap_to<T>(target: Id, offset: RelativeOffset) -> impl Operation<T> {
SnapTo { target, offset }
}
-/// The amount of offset in each direction of a [`Scrollable`].
+/// Produces an [`Operation`] that scrolls the widget with the given [`Id`] to
+/// the provided [`AbsoluteOffset`].
+pub fn scroll_to<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> {
+ struct ScrollTo {
+ target: Id,
+ offset: AbsoluteOffset,
+ }
+
+ impl<T> Operation<T> for ScrollTo {
+ fn container(
+ &mut self,
+ _id: Option<&Id>,
+ operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
+ ) {
+ operate_on_children(self)
+ }
+
+ fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) {
+ if Some(&self.target) == id {
+ state.scroll_to(self.offset);
+ }
+ }
+ }
+
+ ScrollTo { target, offset }
+}
+
+/// The amount of absolute offset in each direction of a [`Scrollable`].
+#[derive(Debug, Clone, Copy, PartialEq, Default)]
+pub struct AbsoluteOffset {
+ /// The amount of horizontal offset
+ pub x: f32,
+ /// The amount of vertical offset
+ pub y: f32,
+}
+
+/// The amount of relative offset in each direction of a [`Scrollable`].
///
/// A value of `0.0` means start, while `1.0` means end.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
diff --git a/native/src/widget/operation/text_input.rs b/core/src/widget/operation/text_input.rs
index 4c773e99..4c773e99 100644
--- a/native/src/widget/operation/text_input.rs
+++ b/core/src/widget/operation/text_input.rs
diff --git a/native/src/widget/text.rs b/core/src/widget/text.rs
index 3fee48f2..79df2b02 100644
--- a/native/src/widget/text.rs
+++ b/core/src/widget/text.rs
@@ -1,30 +1,17 @@
//! Write some text for your users to read.
use crate::alignment;
use crate::layout;
+use crate::mouse;
use crate::renderer;
use crate::text;
use crate::widget::Tree;
-use crate::{Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget};
+use crate::{Color, Element, Layout, Length, Pixels, Rectangle, Widget};
use std::borrow::Cow;
-pub use iced_style::text::{Appearance, StyleSheet};
+pub use text::{LineHeight, Shaping};
/// A paragraph of text.
-///
-/// # Example
-///
-/// ```
-/// # use iced_native::Color;
-/// #
-/// # type Text<'a> = iced_native::widget::Text<'a, iced_native::renderer::Null>;
-/// #
-/// Text::new("I <3 iced!")
-/// .size(40)
-/// .style(Color::from([0.0, 0.0, 1.0]));
-/// ```
-///
-/// ![Text drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text.png?raw=true)
#[allow(missing_debug_implementations)]
pub struct Text<'a, Renderer>
where
@@ -33,11 +20,13 @@ where
{
content: Cow<'a, str>,
size: Option<f32>,
+ line_height: LineHeight,
width: Length,
height: Length,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
- font: Renderer::Font,
+ font: Option<Renderer::Font>,
+ shaping: Shaping,
style: <Renderer::Theme as StyleSheet>::Style,
}
@@ -51,11 +40,13 @@ where
Text {
content: content.into(),
size: None,
- font: Default::default(),
+ line_height: LineHeight::default(),
+ font: None,
width: Length::Shrink,
height: Length::Shrink,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
+ shaping: Shaping::Basic,
style: Default::default(),
}
}
@@ -66,11 +57,17 @@ where
self
}
+ /// Sets the [`LineHeight`] of the [`Text`].
+ pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
+ self.line_height = line_height.into();
+ self
+ }
+
/// Sets the [`Font`] of the [`Text`].
///
/// [`Font`]: crate::text::Renderer::Font
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
- self.font = font.into();
+ self.font = Some(font.into());
self
}
@@ -112,6 +109,12 @@ where
self.vertical_alignment = alignment;
self
}
+
+ /// Sets the [`Shaping`] strategy of the [`Text`].
+ pub fn shaping(mut self, shaping: Shaping) -> Self {
+ self.shaping = shaping;
+ self
+ }
}
impl<'a, Message, Renderer> Widget<Message, Renderer> for Text<'a, Renderer>
@@ -136,12 +139,16 @@ where
let size = self.size.unwrap_or_else(|| renderer.default_size());
- let bounds = limits.max();
-
- let (width, height) =
- renderer.measure(&self.content, size, self.font.clone(), bounds);
+ let bounds = renderer.measure(
+ &self.content,
+ size,
+ self.line_height,
+ self.font.unwrap_or_else(|| renderer.default_font()),
+ limits.max(),
+ self.shaping,
+ );
- let size = limits.resolve(Size::new(width, height));
+ let size = limits.resolve(bounds);
layout::Node::new(size)
}
@@ -153,7 +160,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor_position: mouse::Cursor,
_viewport: &Rectangle,
) {
draw(
@@ -162,10 +169,12 @@ where
layout,
&self.content,
self.size,
- self.font.clone(),
- theme.appearance(self.style),
+ self.line_height,
+ self.font,
+ theme.appearance(self.style.clone()),
self.horizontal_alignment,
self.vertical_alignment,
+ self.shaping,
);
}
}
@@ -186,10 +195,12 @@ pub fn draw<Renderer>(
layout: Layout<'_>,
content: &str,
size: Option<f32>,
- font: Renderer::Font,
+ line_height: LineHeight,
+ font: Option<Renderer::Font>,
appearance: Appearance,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
+ shaping: Shaping,
) where
Renderer: text::Renderer,
{
@@ -207,14 +218,18 @@ pub fn draw<Renderer>(
alignment::Vertical::Bottom => bounds.y + bounds.height,
};
- renderer.fill_text(crate::text::Text {
+ let size = size.unwrap_or_else(|| renderer.default_size());
+
+ renderer.fill_text(crate::Text {
content,
- size: size.unwrap_or_else(|| renderer.default_size()),
+ size,
+ line_height,
bounds: Rectangle { x, y, ..bounds },
color: appearance.color.unwrap_or(style.text_color),
- font,
+ font: font.unwrap_or_else(|| renderer.default_font()),
horizontal_alignment,
vertical_alignment,
+ shaping,
});
}
@@ -238,22 +253,52 @@ where
Self {
content: self.content.clone(),
size: self.size,
+ line_height: self.line_height,
width: self.width,
height: self.height,
horizontal_alignment: self.horizontal_alignment,
vertical_alignment: self.vertical_alignment,
- font: self.font.clone(),
- style: self.style,
+ font: self.font,
+ style: self.style.clone(),
+ shaping: self.shaping,
}
}
}
+impl<'a, Renderer> From<&'a str> for Text<'a, Renderer>
+where
+ Renderer: text::Renderer,
+ Renderer::Theme: StyleSheet,
+{
+ fn from(content: &'a str) -> Self {
+ Self::new(content)
+ }
+}
+
impl<'a, Message, Renderer> From<&'a str> for Element<'a, Message, Renderer>
where
Renderer: text::Renderer + 'a,
Renderer::Theme: StyleSheet,
{
- fn from(contents: &'a str) -> Self {
- Text::new(contents).into()
+ fn from(content: &'a str) -> Self {
+ Text::from(content).into()
}
}
+
+/// The style sheet of some text.
+pub trait StyleSheet {
+ /// The supported style of the [`StyleSheet`].
+ type Style: Default + Clone;
+
+ /// Produces the [`Appearance`] of some text.
+ fn appearance(&self, style: Self::Style) -> Appearance;
+}
+
+/// The apperance of some text.
+#[derive(Debug, Clone, Copy, Default)]
+pub struct Appearance {
+ /// The [`Color`] of the text.
+ ///
+ /// The default, `None`, means using the inherited color.
+ pub color: Option<Color>,
+}
diff --git a/native/src/widget/tree.rs b/core/src/widget/tree.rs
index da269632..da269632 100644
--- a/native/src/widget/tree.rs
+++ b/core/src/widget/tree.rs
diff --git a/native/src/window/event.rs b/core/src/window/event.rs
index e2fb5e66..e2fb5e66 100644
--- a/native/src/window/event.rs
+++ b/core/src/window/event.rs
diff --git a/native/src/window/id.rs b/core/src/window/id.rs
index 0a11b1aa..0a11b1aa 100644
--- a/native/src/window/id.rs
+++ b/core/src/window/id.rs
diff --git a/native/src/window/mode.rs b/core/src/window/mode.rs
index fdce8e23..fdce8e23 100644
--- a/native/src/window/mode.rs
+++ b/core/src/window/mode.rs
diff --git a/native/src/window/redraw_request.rs b/core/src/window/redraw_request.rs
index 3b4f0fd3..3b4f0fd3 100644
--- a/native/src/window/redraw_request.rs
+++ b/core/src/window/redraw_request.rs
diff --git a/native/src/window/settings.rs b/core/src/window/settings.rs
index 67798fbe..458b9232 100644
--- a/native/src/window/settings.rs
+++ b/core/src/window/settings.rs
@@ -1,4 +1,6 @@
-use crate::window::{Icon, Position};
+use crate::window::{Icon, Level, Position};
+
+pub use iced_winit::settings::PlatformSpecific;
/// The window settings of an application.
#[derive(Debug, Clone)]
@@ -27,11 +29,14 @@ pub struct Settings {
/// Whether the window should be transparent.
pub transparent: bool,
- /// Whether the window will always be on top of other windows.
- pub always_on_top: bool,
+ /// The window [`Level`].
+ pub level: Level,
/// The icon of the window.
pub icon: Option<Icon>,
+
+ /// Platform specific settings.
+ pub platform_specific: PlatformSpecific,
}
impl Default for Settings {
@@ -45,8 +50,27 @@ impl Default for Settings {
resizable: true,
decorations: true,
transparent: false,
- always_on_top: false,
+ level: Level::default(),
icon: None,
+ platform_specific: Default::default(),
+ }
+ }
+}
+
+impl From<Settings> for iced_winit::settings::Window {
+ fn from(settings: Settings) -> Self {
+ Self {
+ size: settings.size,
+ position: iced_winit::Position::from(settings.position),
+ min_size: settings.min_size,
+ max_size: settings.max_size,
+ visible: settings.visible,
+ resizable: settings.resizable,
+ decorations: settings.decorations,
+ transparent: settings.transparent,
+ level: settings.level,
+ icon: settings.icon.map(Icon::into),
+ platform_specific: settings.platform_specific,
}
}
}
diff --git a/native/src/window/user_attention.rs b/core/src/window/user_attention.rs
index b03dfeef..b03dfeef 100644
--- a/native/src/window/user_attention.rs
+++ b/core/src/window/user_attention.rs
diff --git a/native/src/mouse.rs b/native/src/mouse.rs
deleted file mode 100644
index 9ee406cf..00000000
--- a/native/src/mouse.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-//! Track mouse events.
-
-pub mod click;
-
-pub use click::Click;
-pub use iced_core::mouse::*;
diff --git a/native/src/runtime.rs b/native/src/runtime.rs
deleted file mode 100644
index 5b0a6925..00000000
--- a/native/src/runtime.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-//! Run commands and subscriptions.
-use crate::event::{self, Event};
-use crate::Hasher;
-
-/// A native runtime with a generic executor and receiver of results.
-///
-/// It can be used by shells to easily spawn a [`Command`] or track a
-/// [`Subscription`].
-///
-/// [`Command`]: crate::Command
-/// [`Subscription`]: crate::Subscription
-pub type Runtime<Executor, Receiver, Message> = iced_futures::Runtime<
- Hasher,
- (Event, event::Status),
- Executor,
- Receiver,
- Message,
->;
diff --git a/native/src/subscription.rs b/native/src/subscription.rs
index 2a216ebe..e69de29b 100644
--- a/native/src/subscription.rs
+++ b/native/src/subscription.rs
@@ -1,248 +0,0 @@
-//! Listen to external events in your application.
-use crate::event::{self, Event};
-use crate::window;
-use crate::Hasher;
-
-use iced_futures::futures::{self, Future, Stream};
-use iced_futures::{BoxStream, MaybeSend};
-
-use std::hash::Hash;
-
-/// A request to listen to external events.
-///
-/// Besides performing async actions on demand with [`Command`], most
-/// applications also need to listen to external events passively.
-///
-/// A [`Subscription`] is normally provided to some runtime, like a [`Command`],
-/// and it will generate events as long as the user keeps requesting it.
-///
-/// For instance, you can use a [`Subscription`] to listen to a WebSocket
-/// connection, keyboard presses, mouse events, time ticks, etc.
-///
-/// [`Command`]: crate::Command
-pub type Subscription<T> =
- iced_futures::Subscription<Hasher, (Event, event::Status), T>;
-
-/// A stream of runtime events.
-///
-/// It is the input of a [`Subscription`] in the native runtime.
-pub type EventStream = BoxStream<(Event, event::Status)>;
-
-/// A native [`Subscription`] tracker.
-pub type Tracker =
- iced_futures::subscription::Tracker<Hasher, (Event, event::Status)>;
-
-pub use iced_futures::subscription::Recipe;
-
-/// Returns a [`Subscription`] to all the ignored runtime events.
-///
-/// This subscription will notify your application of any [`Event`] that was
-/// not captured by any widget.
-pub fn events() -> Subscription<Event> {
- events_with(|event, status| match status {
- event::Status::Ignored => Some(event),
- event::Status::Captured => None,
- })
-}
-
-/// Returns a [`Subscription`] that filters all the runtime events with the
-/// provided function, producing messages accordingly.
-///
-/// This subscription will call the provided function for every [`Event`]
-/// handled by the runtime. If the function:
-///
-/// - Returns `None`, the [`Event`] will be discarded.
-/// - Returns `Some` message, the `Message` will be produced.
-pub fn events_with<Message>(
- f: fn(Event, event::Status) -> Option<Message>,
-) -> Subscription<Message>
-where
- Message: 'static + MaybeSend,
-{
- #[derive(Hash)]
- struct EventsWith;
-
- Subscription::from_recipe(Runner {
- id: (EventsWith, f),
- spawn: move |events| {
- use futures::future;
- use futures::stream::StreamExt;
-
- events.filter_map(move |(event, status)| {
- future::ready(match event {
- Event::Window(_, window::Event::RedrawRequested(_)) => None,
- _ => f(event, status),
- })
- })
- },
- })
-}
-
-pub(crate) fn raw_events<Message>(
- f: fn(Event, event::Status) -> Option<Message>,
-) -> Subscription<Message>
-where
- Message: 'static + MaybeSend,
-{
- #[derive(Hash)]
- struct RawEvents;
-
- Subscription::from_recipe(Runner {
- id: (RawEvents, f),
- spawn: move |events| {
- use futures::future;
- use futures::stream::StreamExt;
-
- events.filter_map(move |(event, status)| {
- future::ready(f(event, status))
- })
- },
- })
-}
-
-/// Returns a [`Subscription`] that will call the given function to create and
-/// asynchronously run the given [`Stream`].
-pub fn run<S, Message>(builder: fn() -> S) -> Subscription<Message>
-where
- S: Stream<Item = Message> + MaybeSend + 'static,
- Message: 'static,
-{
- Subscription::from_recipe(Runner {
- id: builder,
- spawn: move |_| builder(),
- })
-}
-
-/// Returns a [`Subscription`] that will create and asynchronously run the
-/// given [`Stream`].
-///
-/// The `id` will be used to uniquely identify the [`Subscription`].
-pub fn run_with_id<I, S, Message>(id: I, stream: S) -> Subscription<Message>
-where
- I: Hash + 'static,
- S: Stream<Item = Message> + MaybeSend + 'static,
- Message: 'static,
-{
- Subscription::from_recipe(Runner {
- id,
- spawn: move |_| stream,
- })
-}
-
-/// Returns a [`Subscription`] that will create and asynchronously run a
-/// [`Stream`] that will call the provided closure to produce every `Message`.
-///
-/// The `id` will be used to uniquely identify the [`Subscription`].
-///
-/// # Creating an asynchronous worker with bidirectional communication
-/// You can leverage this helper to create a [`Subscription`] that spawns
-/// an asynchronous worker in the background and establish a channel of
-/// communication with an `iced` application.
-///
-/// You can achieve this by creating an `mpsc` channel inside the closure
-/// and returning the `Sender` as a `Message` for the `Application`:
-///
-/// ```
-/// use iced_native::subscription::{self, Subscription};
-/// use iced_native::futures::channel::mpsc;
-///
-/// pub enum Event {
-/// Ready(mpsc::Sender<Input>),
-/// WorkFinished,
-/// // ...
-/// }
-///
-/// enum Input {
-/// DoSomeWork,
-/// // ...
-/// }
-///
-/// enum State {
-/// Starting,
-/// Ready(mpsc::Receiver<Input>),
-/// }
-///
-/// fn some_worker() -> Subscription<Event> {
-/// struct SomeWorker;
-///
-/// subscription::unfold(std::any::TypeId::of::<SomeWorker>(), State::Starting, |state| async move {
-/// match state {
-/// State::Starting => {
-/// // Create channel
-/// let (sender, receiver) = mpsc::channel(100);
-///
-/// (Some(Event::Ready(sender)), State::Ready(receiver))
-/// }
-/// State::Ready(mut receiver) => {
-/// use iced_native::futures::StreamExt;
-///
-/// // Read next input sent from `Application`
-/// let input = receiver.select_next_some().await;
-///
-/// match input {
-/// Input::DoSomeWork => {
-/// // Do some async work...
-///
-/// // Finally, we can optionally return a message to tell the
-/// // `Application` the work is done
-/// (Some(Event::WorkFinished), State::Ready(receiver))
-/// }
-/// }
-/// }
-/// }
-/// })
-/// }
-/// ```
-///
-/// Check out the [`websocket`] example, which showcases this pattern to maintain a WebSocket
-/// connection open.
-///
-/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.8/examples/websocket
-pub fn unfold<I, T, Fut, Message>(
- id: I,
- initial: T,
- mut f: impl FnMut(T) -> Fut + MaybeSend + Sync + 'static,
-) -> Subscription<Message>
-where
- I: Hash + 'static,
- T: MaybeSend + 'static,
- Fut: Future<Output = (Option<Message>, T)> + MaybeSend + 'static,
- Message: 'static + MaybeSend,
-{
- use futures::future::{self, FutureExt};
- use futures::stream::StreamExt;
-
- run_with_id(
- id,
- futures::stream::unfold(initial, move |state| f(state).map(Some))
- .filter_map(future::ready),
- )
-}
-
-struct Runner<I, F, S, Message>
-where
- F: FnOnce(EventStream) -> S,
- S: Stream<Item = Message>,
-{
- id: I,
- spawn: F,
-}
-
-impl<I, S, F, Message> Recipe<Hasher, (Event, event::Status)>
- for Runner<I, F, S, Message>
-where
- I: Hash + 'static,
- F: FnOnce(EventStream) -> S,
- S: Stream<Item = Message> + MaybeSend + 'static,
-{
- type Output = Message;
-
- fn hash(&self, state: &mut Hasher) {
- std::any::TypeId::of::<I>().hash(state);
- self.id.hash(state);
- }
-
- fn stream(self: Box<Self>, input: EventStream) -> BoxStream<Self::Output> {
- iced_futures::boxed_stream((self.spawn)(input))
- }
-}
diff --git a/native/src/text.rs b/native/src/text.rs
deleted file mode 100644
index 55c3cfd3..00000000
--- a/native/src/text.rs
+++ /dev/null
@@ -1,114 +0,0 @@
-//! Draw and interact with text.
-use crate::alignment;
-use crate::{Color, Point, Rectangle, Size, Vector};
-
-/// A paragraph.
-#[derive(Debug, Clone, Copy)]
-pub struct Text<'a, Font> {
- /// The content of the paragraph.
- pub content: &'a str,
-
- /// The bounds of the paragraph.
- pub bounds: Rectangle,
-
- /// The size of the [`Text`].
- pub size: f32,
-
- /// The color of the [`Text`].
- pub color: Color,
-
- /// The font of the [`Text`].
- pub font: Font,
-
- /// The horizontal alignment of the [`Text`].
- pub horizontal_alignment: alignment::Horizontal,
-
- /// The vertical alignment of the [`Text`].
- pub vertical_alignment: alignment::Vertical,
-}
-
-/// The result of hit testing on text.
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum Hit {
- /// The point was within the bounds of the returned character index.
- CharOffset(usize),
- /// The provided point was not within the bounds of a glyph. The index
- /// of the character with the closest centeroid position is returned,
- /// as well as its delta.
- NearestCharOffset(usize, Vector),
-}
-
-impl Hit {
- /// Computes the cursor position of the [`Hit`] .
- pub fn cursor(self) -> usize {
- match self {
- Self::CharOffset(i) => i,
- Self::NearestCharOffset(i, delta) => {
- if delta.x > f32::EPSILON {
- i + 1
- } else {
- i
- }
- }
- }
- }
-}
-
-/// A renderer capable of measuring and drawing [`Text`].
-pub trait Renderer: crate::Renderer {
- /// The font type used.
- type Font: Default + Clone;
-
- /// The icon font of the backend.
- const ICON_FONT: Self::Font;
-
- /// The `char` representing a ✔ icon in the [`ICON_FONT`].
- ///
- /// [`ICON_FONT`]: Self::ICON_FONT
- const CHECKMARK_ICON: char;
-
- /// The `char` representing a â–¼ icon in the built-in [`ICON_FONT`].
- ///
- /// [`ICON_FONT`]: Self::ICON_FONT
- const ARROW_DOWN_ICON: char;
-
- /// Returns the default size of [`Text`].
- fn default_size(&self) -> f32;
-
- /// Measures the text in the given bounds and returns the minimum boundaries
- /// that can fit the contents.
- fn measure(
- &self,
- content: &str,
- size: f32,
- font: Self::Font,
- bounds: Size,
- ) -> (f32, f32);
-
- /// Measures the width of the text as if it were laid out in a single line.
- fn measure_width(&self, content: &str, size: f32, font: Self::Font) -> f32 {
- let (width, _) = self.measure(content, size, font, Size::INFINITY);
-
- width
- }
-
- /// Tests whether the provided point is within the boundaries of text
- /// laid out with the given parameters, returning information about
- /// the nearest character.
- ///
- /// If `nearest_only` is true, the hit test does not consider whether the
- /// the point is interior to any glyph bounds, returning only the character
- /// with the nearest centeroid.
- fn hit_test(
- &self,
- contents: &str,
- size: f32,
- font: Self::Font,
- bounds: Size,
- point: Point,
- nearest_only: bool,
- ) -> Option<Hit>;
-
- /// Draws the given [`Text`].
- fn fill_text(&mut self, text: Text<'_, Self::Font>);
-}
diff --git a/native/src/widget/action.rs b/native/src/widget/action.rs
deleted file mode 100644
index 3f1b6b6c..00000000
--- a/native/src/widget/action.rs
+++ /dev/null
@@ -1,154 +0,0 @@
-use crate::widget::operation::{
- self, Focusable, Operation, Scrollable, TextInput,
-};
-use crate::widget::Id;
-
-use iced_futures::MaybeSend;
-
-use std::any::Any;
-use std::rc::Rc;
-
-/// An operation to be performed on the widget tree.
-#[allow(missing_debug_implementations)]
-pub struct Action<T>(Box<dyn Operation<T>>);
-
-impl<T> Action<T> {
- /// Creates a new [`Action`] with the given [`Operation`].
- pub fn new(operation: impl Operation<T> + 'static) -> Self {
- Self(Box::new(operation))
- }
-
- /// Maps the output of an [`Action`] using the given function.
- pub fn map<A>(
- self,
- f: impl Fn(T) -> A + 'static + MaybeSend + Sync,
- ) -> Action<A>
- where
- T: 'static,
- A: 'static,
- {
- Action(Box::new(Map {
- operation: self.0,
- f: Rc::new(f),
- }))
- }
-
- /// Consumes the [`Action`] and returns the internal [`Operation`].
- pub fn into_operation(self) -> Box<dyn Operation<T>> {
- self.0
- }
-}
-
-#[allow(missing_debug_implementations)]
-struct Map<A, B> {
- operation: Box<dyn Operation<A>>,
- f: Rc<dyn Fn(A) -> B>,
-}
-
-impl<A, B> Operation<B> for Map<A, B>
-where
- A: 'static,
- B: 'static,
-{
- fn container(
- &mut self,
- id: Option<&Id>,
- operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
- ) {
- struct MapRef<'a, A> {
- operation: &'a mut dyn Operation<A>,
- }
-
- impl<'a, A, B> Operation<B> for MapRef<'a, A> {
- fn container(
- &mut self,
- id: Option<&Id>,
- operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
- ) {
- let Self { operation, .. } = self;
-
- operation.container(id, &mut |operation| {
- operate_on_children(&mut MapRef { operation });
- });
- }
-
- fn scrollable(
- &mut self,
- state: &mut dyn Scrollable,
- id: Option<&Id>,
- ) {
- self.operation.scrollable(state, id);
- }
-
- fn focusable(
- &mut self,
- state: &mut dyn Focusable,
- id: Option<&Id>,
- ) {
- self.operation.focusable(state, id);
- }
-
- fn text_input(
- &mut self,
- state: &mut dyn TextInput,
- id: Option<&Id>,
- ) {
- self.operation.text_input(state, id);
- }
-
- fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) {
- self.operation.custom(state, id);
- }
- }
-
- let Self { operation, .. } = self;
-
- MapRef {
- operation: operation.as_mut(),
- }
- .container(id, operate_on_children);
- }
-
- fn focusable(
- &mut self,
- state: &mut dyn operation::Focusable,
- id: Option<&Id>,
- ) {
- self.operation.focusable(state, id);
- }
-
- fn scrollable(
- &mut self,
- state: &mut dyn operation::Scrollable,
- id: Option<&Id>,
- ) {
- self.operation.scrollable(state, id);
- }
-
- fn text_input(
- &mut self,
- state: &mut dyn operation::TextInput,
- id: Option<&Id>,
- ) {
- self.operation.text_input(state, id);
- }
-
- fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) {
- self.operation.custom(state, id);
- }
-
- fn finish(&self) -> operation::Outcome<B> {
- match self.operation.finish() {
- operation::Outcome::None => operation::Outcome::None,
- operation::Outcome::Some(output) => {
- operation::Outcome::Some((self.f)(output))
- }
- operation::Outcome::Chain(next) => {
- operation::Outcome::Chain(Box::new(Map {
- operation: next,
- f: self.f.clone(),
- }))
- }
- }
- }
-}
diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs
deleted file mode 100644
index d13eca75..00000000
--- a/native/src/widget/helpers.rs
+++ /dev/null
@@ -1,317 +0,0 @@
-//! Helper functions to create pure widgets.
-use crate::overlay;
-use crate::widget;
-use crate::{Element, Length, Pixels};
-
-use std::borrow::Cow;
-use std::ops::RangeInclusive;
-
-/// Creates a [`Column`] with the given children.
-///
-/// [`Column`]: widget::Column
-#[macro_export]
-macro_rules! column {
- () => (
- $crate::widget::Column::new()
- );
- ($($x:expr),+ $(,)?) => (
- $crate::widget::Column::with_children(vec![$($crate::Element::from($x)),+])
- );
-}
-
-/// Creates a [`Row`] with the given children.
-///
-/// [`Row`]: widget::Row
-#[macro_export]
-macro_rules! row {
- () => (
- $crate::widget::Row::new()
- );
- ($($x:expr),+ $(,)?) => (
- $crate::widget::Row::with_children(vec![$($crate::Element::from($x)),+])
- );
-}
-
-/// Creates a new [`Container`] with the provided content.
-///
-/// [`Container`]: widget::Container
-pub fn container<'a, Message, Renderer>(
- content: impl Into<Element<'a, Message, Renderer>>,
-) -> widget::Container<'a, Message, Renderer>
-where
- Renderer: crate::Renderer,
- Renderer::Theme: widget::container::StyleSheet,
-{
- widget::Container::new(content)
-}
-
-/// Creates a new [`Column`] with the given children.
-///
-/// [`Column`]: widget::Column
-pub fn column<Message, Renderer>(
- children: Vec<Element<'_, Message, Renderer>>,
-) -> widget::Column<'_, Message, Renderer> {
- widget::Column::with_children(children)
-}
-
-/// Creates a new [`Row`] with the given children.
-///
-/// [`Row`]: widget::Row
-pub fn row<Message, Renderer>(
- children: Vec<Element<'_, Message, Renderer>>,
-) -> widget::Row<'_, Message, Renderer> {
- widget::Row::with_children(children)
-}
-
-/// Creates a new [`Scrollable`] with the provided content.
-///
-/// [`Scrollable`]: widget::Scrollable
-pub fn scrollable<'a, Message, Renderer>(
- content: impl Into<Element<'a, Message, Renderer>>,
-) -> widget::Scrollable<'a, Message, Renderer>
-where
- Renderer: crate::Renderer,
- Renderer::Theme: widget::scrollable::StyleSheet,
-{
- widget::Scrollable::new(content)
-}
-
-/// Creates a new [`Button`] with the provided content.
-///
-/// [`Button`]: widget::Button
-pub fn button<'a, Message, Renderer>(
- content: impl Into<Element<'a, Message, Renderer>>,
-) -> widget::Button<'a, Message, Renderer>
-where
- Renderer: crate::Renderer,
- Renderer::Theme: widget::button::StyleSheet,
- <Renderer::Theme as widget::button::StyleSheet>::Style: Default,
-{
- widget::Button::new(content)
-}
-
-/// Creates a new [`Tooltip`] with the provided content, tooltip text, and [`tooltip::Position`].
-///
-/// [`Tooltip`]: widget::Tooltip
-/// [`tooltip::Position`]: widget::tooltip::Position
-pub fn tooltip<'a, Message, Renderer>(
- content: impl Into<Element<'a, Message, Renderer>>,
- tooltip: impl ToString,
- position: widget::tooltip::Position,
-) -> widget::Tooltip<'a, Message, Renderer>
-where
- Renderer: crate::text::Renderer,
- Renderer::Theme: widget::container::StyleSheet + widget::text::StyleSheet,
-{
- widget::Tooltip::new(content, tooltip.to_string(), position)
-}
-
-/// Creates a new [`Text`] widget with the provided content.
-///
-/// [`Text`]: widget::Text
-pub fn text<'a, Renderer>(text: impl ToString) -> widget::Text<'a, Renderer>
-where
- Renderer: crate::text::Renderer,
- Renderer::Theme: widget::text::StyleSheet,
-{
- widget::Text::new(text.to_string())
-}
-
-/// Creates a new [`Checkbox`].
-///
-/// [`Checkbox`]: widget::Checkbox
-pub fn checkbox<'a, Message, Renderer>(
- label: impl Into<String>,
- is_checked: bool,
- f: impl Fn(bool) -> Message + 'a,
-) -> widget::Checkbox<'a, Message, Renderer>
-where
- Renderer: crate::text::Renderer,
- Renderer::Theme: widget::checkbox::StyleSheet + widget::text::StyleSheet,
-{
- widget::Checkbox::new(label, is_checked, f)
-}
-
-/// Creates a new [`Radio`].
-///
-/// [`Radio`]: widget::Radio
-pub fn radio<Message, Renderer, V>(
- label: impl Into<String>,
- value: V,
- selected: Option<V>,
- on_click: impl FnOnce(V) -> Message,
-) -> widget::Radio<Message, Renderer>
-where
- Message: Clone,
- Renderer: crate::text::Renderer,
- Renderer::Theme: widget::radio::StyleSheet,
- V: Copy + Eq,
-{
- widget::Radio::new(value, label, selected, on_click)
-}
-
-/// Creates a new [`Toggler`].
-///
-/// [`Toggler`]: widget::Toggler
-pub fn toggler<'a, Message, Renderer>(
- label: impl Into<Option<String>>,
- is_checked: bool,
- f: impl Fn(bool) -> Message + 'a,
-) -> widget::Toggler<'a, Message, Renderer>
-where
- Renderer: crate::text::Renderer,
- Renderer::Theme: widget::toggler::StyleSheet,
-{
- widget::Toggler::new(label, is_checked, f)
-}
-
-/// Creates a new [`TextInput`].
-///
-/// [`TextInput`]: widget::TextInput
-pub fn text_input<'a, Message, Renderer>(
- placeholder: &str,
- value: &str,
- on_change: impl Fn(String) -> Message + 'a,
-) -> widget::TextInput<'a, Message, Renderer>
-where
- Message: Clone,
- Renderer: crate::text::Renderer,
- Renderer::Theme: widget::text_input::StyleSheet,
-{
- widget::TextInput::new(placeholder, value, on_change)
-}
-
-/// Creates a new [`Slider`].
-///
-/// [`Slider`]: widget::Slider
-pub fn slider<'a, T, Message, Renderer>(
- range: std::ops::RangeInclusive<T>,
- value: T,
- on_change: impl Fn(T) -> Message + 'a,
-) -> widget::Slider<'a, T, Message, Renderer>
-where
- T: Copy + From<u8> + std::cmp::PartialOrd,
- Message: Clone,
- Renderer: crate::Renderer,
- Renderer::Theme: widget::slider::StyleSheet,
-{
- widget::Slider::new(range, value, on_change)
-}
-
-/// Creates a new [`VerticalSlider`].
-///
-/// [`VerticalSlider`]: widget::VerticalSlider
-pub fn vertical_slider<'a, T, Message, Renderer>(
- range: std::ops::RangeInclusive<T>,
- value: T,
- on_change: impl Fn(T) -> Message + 'a,
-) -> widget::VerticalSlider<'a, T, Message, Renderer>
-where
- T: Copy + From<u8> + std::cmp::PartialOrd,
- Message: Clone,
- Renderer: crate::Renderer,
- Renderer::Theme: widget::slider::StyleSheet,
-{
- widget::VerticalSlider::new(range, value, on_change)
-}
-
-/// Creates a new [`PickList`].
-///
-/// [`PickList`]: widget::PickList
-pub fn pick_list<'a, Message, Renderer, T>(
- options: impl Into<Cow<'a, [T]>>,
- selected: Option<T>,
- on_selected: impl Fn(T) -> Message + 'a,
-) -> widget::PickList<'a, T, Message, Renderer>
-where
- T: ToString + Eq + 'static,
- [T]: ToOwned<Owned = Vec<T>>,
- Renderer: crate::text::Renderer,
- Renderer::Theme: widget::pick_list::StyleSheet
- + widget::scrollable::StyleSheet
- + overlay::menu::StyleSheet
- + widget::container::StyleSheet,
- <Renderer::Theme as overlay::menu::StyleSheet>::Style:
- From<<Renderer::Theme as widget::pick_list::StyleSheet>::Style>,
-{
- widget::PickList::new(options, selected, on_selected)
-}
-
-/// Creates a new [`Image`].
-///
-/// [`Image`]: widget::Image
-pub fn image<Handle>(handle: impl Into<Handle>) -> widget::Image<Handle> {
- widget::Image::new(handle.into())
-}
-
-/// Creates a new horizontal [`Space`] with the given [`Length`].
-///
-/// [`Space`]: widget::Space
-pub fn horizontal_space(width: impl Into<Length>) -> widget::Space {
- widget::Space::with_width(width)
-}
-
-/// Creates a new vertical [`Space`] with the given [`Length`].
-///
-/// [`Space`]: widget::Space
-pub fn vertical_space(height: impl Into<Length>) -> widget::Space {
- widget::Space::with_height(height)
-}
-
-/// Creates a horizontal [`Rule`] with the given height.
-///
-/// [`Rule`]: widget::Rule
-pub fn horizontal_rule<Renderer>(
- height: impl Into<Pixels>,
-) -> widget::Rule<Renderer>
-where
- Renderer: crate::Renderer,
- Renderer::Theme: widget::rule::StyleSheet,
-{
- widget::Rule::horizontal(height)
-}
-
-/// Creates a vertical [`Rule`] with the given width.
-///
-/// [`Rule`]: widget::Rule
-pub fn vertical_rule<Renderer>(
- width: impl Into<Pixels>,
-) -> widget::Rule<Renderer>
-where
- Renderer: crate::Renderer,
- Renderer::Theme: widget::rule::StyleSheet,
-{
- widget::Rule::vertical(width)
-}
-
-/// Creates a new [`ProgressBar`].
-///
-/// It expects:
-/// * an inclusive range of possible values, and
-/// * the current value of the [`ProgressBar`].
-///
-/// [`ProgressBar`]: widget::ProgressBar
-pub fn progress_bar<Renderer>(
- range: RangeInclusive<f32>,
- value: f32,
-) -> widget::ProgressBar<Renderer>
-where
- Renderer: crate::Renderer,
- Renderer::Theme: widget::progress_bar::StyleSheet,
-{
- widget::ProgressBar::new(range, value)
-}
-
-/// Creates a new [`Svg`] widget from the given [`Handle`].
-///
-/// [`Svg`]: widget::Svg
-/// [`Handle`]: widget::svg::Handle
-pub fn svg<Renderer>(
- handle: impl Into<widget::svg::Handle>,
-) -> widget::Svg<Renderer>
-where
- Renderer: crate::svg::Renderer,
- Renderer::Theme: widget::svg::StyleSheet,
-{
- widget::Svg::new(handle)
-}
diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs
deleted file mode 100644
index 53688a21..00000000
--- a/native/src/widget/operation.rs
+++ /dev/null
@@ -1,112 +0,0 @@
-//! Query or update internal widget state.
-pub mod focusable;
-pub mod scrollable;
-pub mod text_input;
-
-pub use focusable::Focusable;
-pub use scrollable::Scrollable;
-pub use text_input::TextInput;
-
-use crate::widget::Id;
-
-use std::any::Any;
-use std::fmt;
-
-/// A piece of logic that can traverse the widget tree of an application in
-/// order to query or update some widget state.
-pub trait Operation<T> {
- /// Operates on a widget that contains other widgets.
- ///
- /// The `operate_on_children` function can be called to return control to
- /// the widget tree and keep traversing it.
- fn container(
- &mut self,
- id: Option<&Id>,
- operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
- );
-
- /// Operates on a widget that can be focused.
- fn focusable(&mut self, _state: &mut dyn Focusable, _id: Option<&Id>) {}
-
- /// Operates on a widget that can be scrolled.
- fn scrollable(&mut self, _state: &mut dyn Scrollable, _id: Option<&Id>) {}
-
- /// Operates on a widget that has text input.
- fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {}
-
- /// Operates on a custom widget with some state.
- fn custom(&mut self, _state: &mut dyn Any, _id: Option<&Id>) {}
-
- /// Finishes the [`Operation`] and returns its [`Outcome`].
- fn finish(&self) -> Outcome<T> {
- Outcome::None
- }
-}
-
-/// The result of an [`Operation`].
-pub enum Outcome<T> {
- /// The [`Operation`] produced no result.
- None,
-
- /// The [`Operation`] produced some result.
- Some(T),
-
- /// The [`Operation`] needs to be followed by another [`Operation`].
- Chain(Box<dyn Operation<T>>),
-}
-
-impl<T> fmt::Debug for Outcome<T>
-where
- T: fmt::Debug,
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::None => write!(f, "Outcome::None"),
- Self::Some(output) => write!(f, "Outcome::Some({output:?})"),
- Self::Chain(_) => write!(f, "Outcome::Chain(...)"),
- }
- }
-}
-
-/// Produces an [`Operation`] that applies the given [`Operation`] to the
-/// children of a container with the given [`Id`].
-pub fn scoped<T: 'static>(
- target: Id,
- operation: impl Operation<T> + 'static,
-) -> impl Operation<T> {
- struct ScopedOperation<Message> {
- target: Id,
- operation: Box<dyn Operation<Message>>,
- }
-
- impl<Message: 'static> Operation<Message> for ScopedOperation<Message> {
- fn container(
- &mut self,
- id: Option<&Id>,
- operate_on_children: &mut dyn FnMut(&mut dyn Operation<Message>),
- ) {
- if id == Some(&self.target) {
- operate_on_children(self.operation.as_mut());
- } else {
- operate_on_children(self);
- }
- }
-
- fn finish(&self) -> Outcome<Message> {
- match self.operation.finish() {
- Outcome::Chain(next) => {
- Outcome::Chain(Box::new(ScopedOperation {
- target: self.target.clone(),
- operation: next,
- }))
- }
- outcome => outcome,
- }
- }
- }
-
- ScopedOperation {
- target,
- operation: Box::new(operation),
- }
-}
diff --git a/native/src/window.rs b/native/src/window.rs
index 660cd54f..e69de29b 100644
--- a/native/src/window.rs
+++ b/native/src/window.rs
@@ -1,38 +0,0 @@
-//! Build window-based GUI applications.
-mod action;
-mod event;
-mod icon;
-mod id;
-mod mode;
-mod position;
-mod redraw_request;
-mod settings;
-mod user_attention;
-
-pub use action::Action;
-pub use event::Event;
-pub use icon::Icon;
-pub use id::Id;
-pub use mode::Mode;
-pub use position::Position;
-pub use redraw_request::RedrawRequest;
-pub use settings::Settings;
-pub use user_attention::UserAttention;
-
-use crate::subscription::{self, Subscription};
-use crate::time::Instant;
-
-/// Subscribes to the frames of the window of the running application.
-///
-/// The resulting [`Subscription`] will produce items at a rate equal to the
-/// refresh rate of the window. Note that this rate may be variable, as it is
-/// normally managed by the graphics driver and/or the OS.
-///
-/// In any case, this [`Subscription`] is useful to smoothly draw application-driven
-/// animations without missing any frames.
-pub fn frames() -> Subscription<Instant> {
- subscription::raw_events(|event, _status| match event {
- crate::Event::Window(_, Event::RedrawRequested(at)) => Some(at),
- _ => None,
- })
-}
diff --git a/native/src/window/icon.rs b/native/src/window/icon.rs
deleted file mode 100644
index 08a6acfd..00000000
--- a/native/src/window/icon.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-//! Attach an icon to the window of your application.
-
-/// The icon of a window.
-#[derive(Debug, Clone)]
-pub struct Icon {
- /// The __rgba__ color data of the window [`Icon`].
- pub rgba: Vec<u8>,
- /// The width of the window [`Icon`].
- pub width: u32,
- /// The height of the window [`Icon`].
- pub height: u32,
-}
diff --git a/native/src/window/position.rs b/native/src/window/position.rs
deleted file mode 100644
index c260c29e..00000000
--- a/native/src/window/position.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-/// The position of a window in a given screen.
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum Position {
- /// The platform-specific default position for a new window.
- Default,
- /// The window is completely centered on the screen.
- Centered,
- /// The window is positioned with specific coordinates: `(X, Y)`.
- ///
- /// When the decorations of the window are enabled, Windows 10 will add some
- /// invisible padding to the window. This padding gets included in the
- /// position. So if you have decorations enabled and want the window to be
- /// at (0, 0) you would have to set the position to
- /// `(PADDING_X, PADDING_Y)`.
- Specific(i32, i32),
-}
-
-impl Default for Position {
- fn default() -> Self {
- Self::Default
- }
-}
diff --git a/native/src/clipboard.rs b/runtime/src/clipboard.rs
index c9105bc0..bc450912 100644
--- a/native/src/clipboard.rs
+++ b/runtime/src/clipboard.rs
@@ -1,30 +1,9 @@
//! Access the clipboard.
-use iced_futures::MaybeSend;
+use crate::command::{self, Command};
+use crate::futures::MaybeSend;
use std::fmt;
-/// A buffer for short-term storage and transfer within and between
-/// applications.
-pub trait Clipboard {
- /// Reads the current content of the [`Clipboard`] as text.
- fn read(&self) -> Option<String>;
-
- /// Writes the given text contents to the [`Clipboard`].
- fn write(&mut self, contents: String);
-}
-
-/// A null implementation of the [`Clipboard`] trait.
-#[derive(Debug, Clone, Copy)]
-pub struct Null;
-
-impl Clipboard for Null {
- fn read(&self) -> Option<String> {
- None
- }
-
- fn write(&mut self, _contents: String) {}
-}
-
/// A clipboard action to be performed by some [`Command`].
///
/// [`Command`]: crate::Command
@@ -60,3 +39,15 @@ impl<T> fmt::Debug for Action<T> {
}
}
}
+
+/// Read the current contents of the clipboard.
+pub fn read<Message>(
+ f: impl Fn(Option<String>) -> Message + 'static,
+) -> Command<Message> {
+ Command::single(command::Action::Clipboard(Action::Read(Box::new(f))))
+}
+
+/// Write the given contents to the clipboard.
+pub fn write<Message>(contents: String) -> Command<Message> {
+ Command::single(command::Action::Clipboard(Action::Write(contents)))
+}
diff --git a/native/src/command.rs b/runtime/src/command.rs
index ca9d0b64..cd4c51ff 100644
--- a/native/src/command.rs
+++ b/runtime/src/command.rs
@@ -3,35 +3,39 @@ mod action;
pub use action::Action;
-use crate::widget;
-
-use iced_futures::MaybeSend;
+use crate::core::widget;
+use crate::futures::MaybeSend;
use std::fmt;
use std::future::Future;
/// A set of asynchronous actions to be performed by some runtime.
#[must_use = "`Command` must be returned to runtime to take effect"]
-pub struct Command<T>(iced_futures::Command<Action<T>>);
+pub struct Command<T>(Internal<Action<T>>);
+
+#[derive(Debug)]
+enum Internal<T> {
+ None,
+ Single(T),
+ Batch(Vec<T>),
+}
impl<T> Command<T> {
/// Creates an empty [`Command`].
///
/// In other words, a [`Command`] that does nothing.
pub const fn none() -> Self {
- Self(iced_futures::Command::none())
+ Self(Internal::None)
}
/// Creates a [`Command`] that performs a single [`Action`].
pub const fn single(action: Action<T>) -> Self {
- Self(iced_futures::Command::single(action))
+ Self(Internal::Single(action))
}
/// Creates a [`Command`] that performs a [`widget::Operation`].
pub fn widget(operation: impl widget::Operation<T> + 'static) -> Self {
- Self(iced_futures::Command::single(Action::Widget(
- widget::Action::new(operation),
- )))
+ Self::single(Action::Widget(Box::new(operation)))
}
/// Creates a [`Command`] that performs the action of the given future.
@@ -49,9 +53,17 @@ impl<T> Command<T> {
///
/// Once this command is run, all the commands will be executed at once.
pub fn batch(commands: impl IntoIterator<Item = Command<T>>) -> Self {
- Self(iced_futures::Command::batch(
- commands.into_iter().map(|Command(command)| command),
- ))
+ let mut batch = Vec::new();
+
+ for Command(command) in commands {
+ match command {
+ Internal::None => {}
+ Internal::Single(command) => batch.push(command),
+ Internal::Batch(commands) => batch.extend(commands),
+ }
+ }
+
+ Self(Internal::Batch(batch))
}
/// Applies a transformation to the result of a [`Command`].
@@ -63,16 +75,27 @@ impl<T> Command<T> {
T: 'static,
A: 'static,
{
- let Command(command) = self;
-
- Command(command.map(move |action| action.map(f.clone())))
+ match self.0 {
+ Internal::None => Command::none(),
+ Internal::Single(action) => Command::single(action.map(f)),
+ Internal::Batch(batch) => Command(Internal::Batch(
+ batch
+ .into_iter()
+ .map(|action| action.map(f.clone()))
+ .collect(),
+ )),
+ }
}
/// Returns all of the actions of the [`Command`].
pub fn actions(self) -> Vec<Action<T>> {
let Command(command) = self;
- command.actions()
+ match command {
+ Internal::None => Vec::new(),
+ Internal::Single(action) => vec![action],
+ Internal::Batch(batch) => batch,
+ }
}
}
diff --git a/native/src/command/action.rs b/runtime/src/command/action.rs
index dcb79672..b2594379 100644
--- a/native/src/command/action.rs
+++ b/runtime/src/command/action.rs
@@ -1,10 +1,12 @@
use crate::clipboard;
+use crate::core::widget;
+use crate::font;
use crate::system;
-use crate::widget;
use crate::window;
use iced_futures::MaybeSend;
+use std::borrow::Cow;
use std::fmt;
/// An action that a [`Command`] can perform.
@@ -26,7 +28,16 @@ pub enum Action<T> {
System(system::Action<T>),
/// Run a widget action.
- Widget(widget::Action<T>),
+ Widget(Box<dyn widget::Operation<T>>),
+
+ /// Load a font from its bytes.
+ LoadFont {
+ /// The bytes of the font to load.
+ bytes: Cow<'static, [u8]>,
+
+ /// The message to produce when the font has been loaded.
+ tagger: Box<dyn Fn(Result<(), font::Error>) -> T>,
+ },
}
impl<T> Action<T> {
@@ -48,7 +59,13 @@ impl<T> Action<T> {
Self::Clipboard(action) => Action::Clipboard(action.map(f)),
Self::Window(id, window) => Action::Window(id, window.map(f)),
Self::System(system) => Action::System(system.map(f)),
- Self::Widget(widget) => Action::Widget(widget.map(f)),
+ Self::Widget(operation) => {
+ Action::Widget(Box::new(widget::operation::map(operation, f)))
+ }
+ Self::LoadFont { bytes, tagger } => Action::LoadFont {
+ bytes,
+ tagger: Box::new(move |result| f(tagger(result))),
+ },
}
}
}
@@ -65,6 +82,7 @@ impl<T> fmt::Debug for Action<T> {
}
Self::System(action) => write!(f, "Action::System({action:?})"),
Self::Widget(_action) => write!(f, "Action::Widget"),
+ Self::LoadFont { .. } => write!(f, "Action::LoadFont"),
}
}
}
diff --git a/native/src/debug/basic.rs b/runtime/src/debug/basic.rs
index 92f614da..32f725a1 100644
--- a/native/src/debug/basic.rs
+++ b/runtime/src/debug/basic.rs
@@ -1,5 +1,5 @@
#![allow(missing_docs)]
-use crate::time;
+use crate::core::time;
use std::collections::VecDeque;
diff --git a/native/src/debug/null.rs b/runtime/src/debug/null.rs
index 2db0eebb..2db0eebb 100644
--- a/native/src/debug/null.rs
+++ b/runtime/src/debug/null.rs
diff --git a/native/src/keyboard.rs b/runtime/src/keyboard.rs
index 012538e3..012538e3 100644
--- a/native/src/keyboard.rs
+++ b/runtime/src/keyboard.rs
diff --git a/native/src/lib.rs b/runtime/src/lib.rs
index ebdc8490..4bbf9687 100644
--- a/native/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -23,8 +23,8 @@
//! - Build a new renderer, see the [renderer] module.
//! - Build a custom widget, start at the [`Widget`] trait.
//!
-//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.8/core
-//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.8/winit
+//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.9/core
+//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.9/winit
//! [`druid`]: https://github.com/xi-editor/druid
//! [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle
//! [renderer]: crate::renderer
@@ -42,32 +42,17 @@
clippy::useless_conversion
)]
#![forbid(unsafe_code, rust_2018_idioms)]
-#![allow(clippy::inherent_to_string, clippy::type_complexity)]
-#![cfg_attr(docsrs, feature(doc_cfg))]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
pub mod clipboard;
pub mod command;
-pub mod event;
-pub mod image;
+pub mod font;
pub mod keyboard;
-pub mod layout;
-pub mod mouse;
pub mod overlay;
pub mod program;
-pub mod renderer;
-pub mod subscription;
-pub mod svg;
pub mod system;
-pub mod text;
-pub mod touch;
pub mod user_interface;
-pub mod widget;
pub mod window;
-mod element;
-mod hasher;
-mod runtime;
-mod shell;
-
// We disable debug capabilities on release builds unless the `debug` feature
// is explicitly enabled.
#[cfg(feature = "debug")]
@@ -77,32 +62,11 @@ mod debug;
#[path = "debug/null.rs"]
mod debug;
-pub use iced_core::alignment;
-pub use iced_core::time;
-pub use iced_core::{
- color, Alignment, Background, Color, ContentFit, Font, Length, Padding,
- Pixels, Point, Rectangle, Size, Vector,
-};
-pub use iced_futures::{executor, futures};
-pub use iced_style::application;
-pub use iced_style::theme;
-
-#[doc(no_inline)]
-pub use executor::Executor;
+pub use iced_core as core;
+pub use iced_futures as futures;
-pub use clipboard::Clipboard;
pub use command::Command;
pub use debug::Debug;
-pub use element::Element;
-pub use event::Event;
-pub use hasher::Hasher;
-pub use layout::Layout;
-pub use overlay::Overlay;
+pub use font::Font;
pub use program::Program;
-pub use renderer::Renderer;
-pub use runtime::Runtime;
-pub use shell::Shell;
-pub use subscription::Subscription;
-pub use theme::Theme;
pub use user_interface::UserInterface;
-pub use widget::Widget;
diff --git a/native/src/program.rs b/runtime/src/program.rs
index c71c237f..44585cc5 100644
--- a/native/src/program.rs
+++ b/runtime/src/program.rs
@@ -1,5 +1,8 @@
//! Build interactive programs using The Elm Architecture.
-use crate::{Command, Element, Renderer};
+use crate::Command;
+
+use iced_core::text;
+use iced_core::{Element, Renderer};
mod state;
@@ -8,7 +11,7 @@ pub use state::State;
/// The core of a user interface application following The Elm Architecture.
pub trait Program: Sized {
/// The graphics backend to use to draw the [`Program`].
- type Renderer: Renderer;
+ type Renderer: Renderer + text::Renderer;
/// The type of __messages__ your [`Program`] will produce.
type Message: std::fmt::Debug + Send;
diff --git a/native/src/program/state.rs b/runtime/src/program/state.rs
index 8ae1cacb..35df6078 100644
--- a/native/src/program/state.rs
+++ b/runtime/src/program/state.rs
@@ -1,9 +1,10 @@
-use crate::application;
-use crate::event::{self, Event};
-use crate::mouse;
-use crate::renderer;
+use crate::core::event::{self, Event};
+use crate::core::mouse;
+use crate::core::renderer;
+use crate::core::widget::operation::{self, Operation};
+use crate::core::{Clipboard, Size};
use crate::user_interface::{self, UserInterface};
-use crate::{Clipboard, Command, Debug, Point, Program, Size};
+use crate::{Command, Debug, Program};
/// The execution state of a [`Program`]. It leverages caching, event
/// processing, and rendering primitive storage.
@@ -22,7 +23,6 @@ where
impl<P> State<P>
where
P: Program + 'static,
- <P::Renderer as crate::Renderer>::Theme: application::StyleSheet,
{
/// Creates a new [`State`] with the provided [`Program`], initializing its
/// primitive with the given logical bounds and renderer.
@@ -89,9 +89,9 @@ where
pub fn update(
&mut self,
bounds: Size,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &mut P::Renderer,
- theme: &<P::Renderer as crate::Renderer>::Theme,
+ theme: &<P::Renderer as iced_core::Renderer>::Theme,
style: &renderer::Style,
clipboard: &mut dyn Clipboard,
debug: &mut Debug,
@@ -109,7 +109,7 @@ where
let (_, event_statuses) = user_interface.update(
&self.queued_events,
- cursor_position,
+ cursor,
renderer,
clipboard,
&mut messages,
@@ -132,7 +132,7 @@ where
let command = if messages.is_empty() {
debug.draw_started();
self.mouse_interaction =
- user_interface.draw(renderer, theme, style, cursor_position);
+ user_interface.draw(renderer, theme, style, cursor);
debug.draw_finished();
self.cache = Some(user_interface.into_cache());
@@ -164,7 +164,7 @@ where
debug.draw_started();
self.mouse_interaction =
- user_interface.draw(renderer, theme, style, cursor_position);
+ user_interface.draw(renderer, theme, style, cursor);
debug.draw_finished();
self.cache = Some(user_interface.into_cache());
@@ -174,6 +174,43 @@ where
(uncaptured_events, command)
}
+
+ /// Applies [`widget::Operation`]s to the [`State`]
+ pub fn operate(
+ &mut self,
+ renderer: &mut P::Renderer,
+ operations: impl Iterator<Item = Box<dyn Operation<P::Message>>>,
+ bounds: Size,
+ debug: &mut Debug,
+ ) {
+ let mut user_interface = build_user_interface(
+ &mut self.program,
+ self.cache.take().unwrap(),
+ renderer,
+ bounds,
+ debug,
+ );
+
+ for operation in operations {
+ let mut current_operation = Some(operation);
+
+ while let Some(mut operation) = current_operation.take() {
+ user_interface.operate(renderer, operation.as_mut());
+
+ match operation.finish() {
+ operation::Outcome::None => {}
+ operation::Outcome::Some(message) => {
+ self.queued_messages.push(message)
+ }
+ operation::Outcome::Chain(next) => {
+ current_operation = Some(next);
+ }
+ };
+ }
+ }
+
+ self.cache = Some(user_interface.into_cache());
+ }
}
fn build_user_interface<'a, P: Program>(
@@ -182,10 +219,7 @@ fn build_user_interface<'a, P: Program>(
renderer: &mut P::Renderer,
size: Size,
debug: &mut Debug,
-) -> UserInterface<'a, P::Message, P::Renderer>
-where
- <P::Renderer as crate::Renderer>::Theme: application::StyleSheet,
-{
+) -> UserInterface<'a, P::Message, P::Renderer> {
debug.view_started();
let view = program.view();
debug.view_finished();
diff --git a/native/src/system.rs b/runtime/src/system.rs
index 61c8ff29..61c8ff29 100644
--- a/native/src/system.rs
+++ b/runtime/src/system.rs
diff --git a/native/src/system/action.rs b/runtime/src/system/action.rs
index dea9536f..dea9536f 100644
--- a/native/src/system/action.rs
+++ b/runtime/src/system/action.rs
diff --git a/native/src/system/information.rs b/runtime/src/system/information.rs
index 93e7a5a4..93e7a5a4 100644
--- a/native/src/system/information.rs
+++ b/runtime/src/system/information.rs
diff --git a/native/src/user_interface.rs b/runtime/src/user_interface.rs
index 68ccda55..619423fd 100644
--- a/native/src/user_interface.rs
+++ b/runtime/src/user_interface.rs
@@ -1,14 +1,12 @@
//! Implement your own event loop to drive a user interface.
-use crate::application;
-use crate::event::{self, Event};
-use crate::layout;
-use crate::mouse;
-use crate::renderer;
-use crate::widget;
-use crate::window;
-use crate::{
- Clipboard, Element, Layout, Point, Rectangle, Shell, Size, Vector,
-};
+use crate::core::event::{self, Event};
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::renderer;
+use crate::core::widget;
+use crate::core::window;
+use crate::core::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
+use crate::overlay;
/// A set of interactive graphical elements with a specific [`Layout`].
///
@@ -18,11 +16,10 @@ use crate::{
/// charge of using this type in your system in any way you want.
///
/// # Example
-/// The [`integration_opengl`] & [`integration_wgpu`] examples use a
-/// [`UserInterface`] to integrate Iced in an existing graphical application.
+/// The [`integration`] example uses a [`UserInterface`] to integrate Iced in an
+/// existing graphical application.
///
-/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.8/examples/integration_opengl
-/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.8/examples/integration_wgpu
+/// [`integration`]: https://github.com/iced-rs/iced/tree/0.9/examples/integration
#[allow(missing_debug_implementations)]
pub struct UserInterface<'a, Message, Renderer> {
root: Element<'a, Message, Renderer>,
@@ -34,8 +31,7 @@ pub struct UserInterface<'a, Message, Renderer> {
impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer>
where
- Renderer: crate::Renderer,
- Renderer::Theme: application::StyleSheet,
+ Renderer: crate::core::Renderer,
{
/// Builds a user interface for an [`Element`].
///
@@ -48,24 +44,21 @@ where
/// is naive way to set up our application loop:
///
/// ```no_run
- /// use iced_native::Size;
- /// use iced_native::user_interface::{self, UserInterface};
- /// use iced_wgpu::Renderer;
- ///
/// # mod iced_wgpu {
- /// # pub use iced_native::renderer::Null as Renderer;
+ /// # pub use iced_runtime::core::renderer::Null as Renderer;
/// # }
/// #
- /// # use iced_native::widget::Column;
- /// #
/// # pub struct Counter;
/// #
/// # impl Counter {
/// # pub fn new() -> Self { Counter }
- /// # pub fn view(&self) -> Column<(), Renderer> {
- /// # Column::new()
- /// # }
+ /// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() }
+ /// # pub fn update(&mut self, _: ()) {}
/// # }
+ /// use iced_runtime::core::Size;
+ /// use iced_runtime::user_interface::{self, UserInterface};
+ /// use iced_wgpu::Renderer;
+ ///
/// // Initialization
/// let mut counter = Counter::new();
/// let mut cache = user_interface::Cache::new();
@@ -124,30 +117,28 @@ where
/// completing [the previous example](#example):
///
/// ```no_run
- /// use iced_native::{clipboard, Size, Point};
- /// use iced_native::user_interface::{self, UserInterface};
- /// use iced_wgpu::Renderer;
- ///
/// # mod iced_wgpu {
- /// # pub use iced_native::renderer::Null as Renderer;
+ /// # pub use iced_runtime::core::renderer::Null as Renderer;
/// # }
/// #
- /// # use iced_native::widget::Column;
- /// #
/// # pub struct Counter;
/// #
/// # impl Counter {
/// # pub fn new() -> Self { Counter }
- /// # pub fn view(&self) -> Column<(), Renderer> {
- /// # Column::new()
- /// # }
- /// # pub fn update(&mut self, message: ()) {}
+ /// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() }
+ /// # pub fn update(&mut self, _: ()) {}
/// # }
+ /// use iced_runtime::core::clipboard;
+ /// use iced_runtime::core::mouse;
+ /// use iced_runtime::core::Size;
+ /// use iced_runtime::user_interface::{self, UserInterface};
+ /// use iced_wgpu::Renderer;
+ ///
/// let mut counter = Counter::new();
/// let mut cache = user_interface::Cache::new();
/// let mut renderer = Renderer::new();
/// let mut window_size = Size::new(1024.0, 768.0);
- /// let mut cursor_position = Point::default();
+ /// let mut cursor = mouse::Cursor::default();
/// let mut clipboard = clipboard::Null;
///
/// // Initialize our event storage
@@ -167,7 +158,7 @@ where
/// // Update the user interface
/// let (state, event_statuses) = user_interface.update(
/// &events,
- /// cursor_position,
+ /// cursor,
/// &mut renderer,
/// &mut clipboard,
/// &mut messages
@@ -184,7 +175,7 @@ where
pub fn update(
&mut self,
events: &[Event],
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &mut Renderer,
clipboard: &mut dyn Clipboard,
messages: &mut Vec<Message>,
@@ -194,18 +185,18 @@ where
let mut outdated = false;
let mut redraw_request = None;
- let mut manual_overlay =
- ManuallyDrop::new(self.root.as_widget_mut().overlay(
- &mut self.state,
- Layout::new(&self.base),
- renderer,
- ));
+ let mut manual_overlay = ManuallyDrop::new(
+ self.root
+ .as_widget_mut()
+ .overlay(&mut self.state, Layout::new(&self.base), renderer)
+ .map(overlay::Nested::new),
+ );
let (base_cursor, overlay_statuses) = if manual_overlay.is_some() {
let bounds = self.bounds;
let mut overlay = manual_overlay.as_mut().unwrap();
- let mut layout = overlay.layout(renderer, bounds, Vector::ZERO);
+ let mut layout = overlay.layout(renderer, bounds, Point::ORIGIN);
let mut event_statuses = Vec::new();
for event in events.iter().cloned() {
@@ -214,7 +205,7 @@ where
let event_status = overlay.on_event(
event,
Layout::new(&layout),
- cursor_position,
+ cursor,
renderer,
clipboard,
&mut shell,
@@ -240,12 +231,16 @@ where
&layout::Limits::new(Size::ZERO, self.bounds),
);
- manual_overlay =
- ManuallyDrop::new(self.root.as_widget_mut().overlay(
- &mut self.state,
- Layout::new(&self.base),
- renderer,
- ));
+ manual_overlay = ManuallyDrop::new(
+ self.root
+ .as_widget_mut()
+ .overlay(
+ &mut self.state,
+ Layout::new(&self.base),
+ renderer,
+ )
+ .map(overlay::Nested::new),
+ );
if manual_overlay.is_none() {
break;
@@ -254,7 +249,8 @@ where
overlay = manual_overlay.as_mut().unwrap();
shell.revalidate_layout(|| {
- layout = overlay.layout(renderer, bounds, Vector::ZERO);
+ layout =
+ overlay.layout(renderer, bounds, Point::ORIGIN);
});
}
@@ -263,22 +259,29 @@ where
}
}
- let base_cursor = manual_overlay
- .as_ref()
- .filter(|overlay| {
- overlay.is_over(Layout::new(&layout), cursor_position)
- })
- .map(|_| {
- // TODO: Type-safe cursor availability
- Point::new(-1.0, -1.0)
+ let base_cursor = if manual_overlay
+ .as_mut()
+ .and_then(|overlay| {
+ cursor.position().map(|cursor_position| {
+ overlay.is_over(
+ Layout::new(&layout),
+ renderer,
+ cursor_position,
+ )
+ })
})
- .unwrap_or(cursor_position);
+ .unwrap_or_default()
+ {
+ mouse::Cursor::Unavailable
+ } else {
+ cursor
+ };
self.overlay = Some(layout);
(base_cursor, event_statuses)
} else {
- (cursor_position, vec![event::Status::Ignored; events.len()])
+ (cursor, vec![event::Status::Ignored; events.len()])
};
let _ = ManuallyDrop::into_inner(manual_overlay);
@@ -357,35 +360,34 @@ where
/// [completing the last example](#example-1):
///
/// ```no_run
- /// use iced_native::clipboard;
- /// use iced_native::renderer;
- /// use iced_native::user_interface::{self, UserInterface};
- /// use iced_native::{Size, Point, Theme};
- /// use iced_wgpu::Renderer;
- ///
/// # mod iced_wgpu {
- /// # pub use iced_native::renderer::Null as Renderer;
+ /// # pub use iced_runtime::core::renderer::Null as Renderer;
+ /// # pub type Theme = ();
/// # }
/// #
- /// # use iced_native::widget::Column;
- /// #
/// # pub struct Counter;
/// #
/// # impl Counter {
/// # pub fn new() -> Self { Counter }
- /// # pub fn view(&self) -> Column<(), Renderer> {
- /// # Column::new()
- /// # }
- /// # pub fn update(&mut self, message: ()) {}
+ /// # pub fn view(&self) -> Element<(), Renderer> { unimplemented!() }
+ /// # pub fn update(&mut self, _: ()) {}
/// # }
+ /// use iced_runtime::core::clipboard;
+ /// use iced_runtime::core::mouse;
+ /// use iced_runtime::core::renderer;
+ /// use iced_runtime::core::{Element, Size};
+ /// use iced_runtime::user_interface::{self, UserInterface};
+ /// use iced_wgpu::{Renderer, Theme};
+ ///
/// let mut counter = Counter::new();
/// let mut cache = user_interface::Cache::new();
/// let mut renderer = Renderer::new();
/// let mut window_size = Size::new(1024.0, 768.0);
- /// let mut cursor_position = Point::default();
+ /// let mut cursor = mouse::Cursor::default();
/// let mut clipboard = clipboard::Null;
/// let mut events = Vec::new();
/// let mut messages = Vec::new();
+ /// let mut theme = Theme::default();
///
/// loop {
/// // Obtain system events...
@@ -400,14 +402,14 @@ where
/// // Update the user interface
/// let event_statuses = user_interface.update(
/// &events,
- /// cursor_position,
+ /// cursor,
/// &mut renderer,
/// &mut clipboard,
/// &mut messages
/// );
///
/// // Draw the user interface
- /// let mouse_cursor = user_interface.draw(&mut renderer, &Theme::default(), &renderer::Style::default(), cursor_position);
+ /// let mouse_interaction = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor);
///
/// cache = user_interface.into_cache();
///
@@ -424,35 +426,44 @@ where
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) -> mouse::Interaction {
// TODO: Move to shell level (?)
renderer.clear();
let viewport = Rectangle::with_size(self.bounds);
- let base_cursor = if let Some(overlay) = self
+ let base_cursor = if let Some(mut overlay) = self
.root
.as_widget_mut()
.overlay(&mut self.state, Layout::new(&self.base), renderer)
+ .map(overlay::Nested::new)
{
let overlay_layout = self.overlay.take().unwrap_or_else(|| {
- overlay.layout(renderer, self.bounds, Vector::ZERO)
+ overlay.layout(renderer, self.bounds, Point::ORIGIN)
});
- let new_cursor_position = if overlay
- .is_over(Layout::new(&overlay_layout), cursor_position)
+ let cursor = if cursor
+ .position()
+ .map(|cursor_position| {
+ overlay.is_over(
+ Layout::new(&overlay_layout),
+ renderer,
+ cursor_position,
+ )
+ })
+ .unwrap_or_default()
{
- Point::new(-1.0, -1.0)
+ mouse::Cursor::Unavailable
} else {
- cursor_position
+ cursor
};
self.overlay = Some(overlay_layout);
- new_cursor_position
+ cursor
} else {
- cursor_position
+ cursor
};
self.root.as_widget().draw(
@@ -468,7 +479,7 @@ where
let base_interaction = self.root.as_widget().mouse_interaction(
&self.state,
Layout::new(&self.base),
- cursor_position,
+ base_cursor,
&viewport,
renderer,
);
@@ -490,10 +501,11 @@ where
.and_then(|layout| {
root.as_widget_mut()
.overlay(&mut self.state, Layout::new(base), renderer)
- .map(|overlay| {
+ .map(overlay::Nested::new)
+ .map(|mut overlay| {
let overlay_interaction = overlay.mouse_interaction(
Layout::new(layout),
- cursor_position,
+ cursor,
&viewport,
renderer,
);
@@ -506,11 +518,20 @@ where
theme,
style,
Layout::new(layout),
- cursor_position,
+ cursor,
);
});
- if overlay.is_over(Layout::new(layout), cursor_position)
+ if cursor
+ .position()
+ .map(|cursor_position| {
+ overlay.is_over(
+ Layout::new(layout),
+ renderer,
+ cursor_position,
+ )
+ })
+ .unwrap_or_default()
{
overlay_interaction
} else {
@@ -534,14 +555,15 @@ where
operation,
);
- if let Some(mut overlay) = self.root.as_widget_mut().overlay(
- &mut self.state,
- Layout::new(&self.base),
- renderer,
- ) {
+ if let Some(mut overlay) = self
+ .root
+ .as_widget_mut()
+ .overlay(&mut self.state, Layout::new(&self.base), renderer)
+ .map(overlay::Nested::new)
+ {
if self.overlay.is_none() {
self.overlay =
- Some(overlay.layout(renderer, self.bounds, Vector::ZERO));
+ Some(overlay.layout(renderer, self.bounds, Point::ORIGIN));
}
overlay.operate(
diff --git a/native/src/window/action.rs b/runtime/src/window/action.rs
index 5751bf97..cebec4ae 100644
--- a/native/src/window/action.rs
+++ b/runtime/src/window/action.rs
@@ -1,13 +1,15 @@
-use crate::window::{Mode, UserAttention, Settings};
+use crate::core::window::{Icon, Level, Mode, UserAttention, Settings};
+use crate::core::Size;
+use crate::futures::MaybeSend;
+use crate::window::Screenshot;
-use iced_futures::MaybeSend;
use std::fmt;
/// An operation to be performed on some window.
pub enum Action<T> {
- /// Closes the current window and exits the application.
+ /// Close the current window and exits the application.
Close,
- /// Moves the window with the left mouse button until the button is
+ /// Move the window with the left mouse button until the button is
/// released.
///
/// There’s no guarantee that this will work unless the left mouse
@@ -19,13 +21,10 @@ pub enum Action<T> {
settings: Settings,
},
/// Resize the window.
- Resize {
- /// The new logical width of the window
- width: u32,
- /// The new logical height of the window
- height: u32,
- },
- /// Sets the window to maximized or back
+ Resize(Size<u32>),
+ /// Fetch the current size of the window.
+ FetchSize(Box<dyn FnOnce(Size<u32>) -> T + 'static>),
+ /// Set the window to maximized or back
Maximize(bool),
/// Set the window to minimized or back
Minimize(bool),
@@ -75,14 +74,27 @@ pub enum Action<T> {
///
/// - **Web / Wayland:** Unsupported.
GainFocus,
- /// Change whether or not the window will always be on top of other windows.
+ /// Change the window [`Level`].
+ ChangeLevel(Level),
+ /// Fetch an identifier unique to the window.
+ FetchId(Box<dyn FnOnce(u64) -> T + 'static>),
+ /// Change the window [`Icon`].
+ ///
+ /// On Windows and X11, this is typically the small icon in the top-left
+ /// corner of the titlebar.
///
/// ## Platform-specific
///
- /// - **Web / Wayland:** Unsupported.
- ChangeAlwaysOnTop(bool),
- /// Fetch an identifier unique to the window.
- FetchId(Box<dyn FnOnce(u64) -> T + 'static>),
+ /// - **Web / Wayland / macOS:** Unsupported.
+ ///
+ /// - **Windows:** Sets `ICON_SMALL`. The base size for a window icon is 16x16, but it's
+ /// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32.
+ ///
+ /// - **X11:** Has no universal guidelines for icon sizes, so you're at the whims of the WM. That
+ /// said, it's usually in the same ballpark as on Windows.
+ ChangeIcon(Icon),
+ /// Screenshot the viewport of the window.
+ Screenshot(Box<dyn FnOnce(Screenshot) -> T + 'static>),
}
impl<T> Action<T> {
@@ -95,10 +107,11 @@ impl<T> Action<T> {
T: 'static,
{
match self {
- Self::Spawn { settings } => Action::Spawn { settings },
Self::Close => Action::Close,
Self::Drag => Action::Drag,
- Self::Resize { width, height } => Action::Resize { width, height },
+ Self::Spawn { settings } => Action::Spawn { settings },
+ Self::Resize(size) => Action::Resize(size),
+ Self::FetchSize(o) => Action::FetchSize(Box::new(move |s| f(o(s)))),
Self::Maximize(maximized) => Action::Maximize(maximized),
Self::Minimize(minimized) => Action::Minimize(minimized),
Self::Move { x, y } => Action::Move { x, y },
@@ -110,10 +123,14 @@ impl<T> Action<T> {
Action::RequestUserAttention(attention_type)
}
Self::GainFocus => Action::GainFocus,
- Self::ChangeAlwaysOnTop(on_top) => {
- Action::ChangeAlwaysOnTop(on_top)
- }
+ Self::ChangeLevel(level) => Action::ChangeLevel(level),
Self::FetchId(o) => Action::FetchId(Box::new(move |s| f(o(s)))),
+ Self::ChangeIcon(icon) => Action::ChangeIcon(icon),
+ Self::Screenshot(tag) => {
+ Action::Screenshot(Box::new(move |screenshot| {
+ f(tag(screenshot))
+ }))
+ }
}
}
}
@@ -126,10 +143,8 @@ impl<T> fmt::Debug for Action<T> {
Self::Spawn { settings } => {
write!(f, "Action::Spawn {{ settings: {:?} }}", settings)
}
- Self::Resize { width, height } => write!(
- f,
- "Action::Resize {{ widget: {width}, height: {height} }}"
- ),
+ Self::Resize(size) => write!(f, "Action::Resize({size:?})"),
+ Self::FetchSize(_) => write!(f, "Action::FetchSize"),
Self::Maximize(maximized) => {
write!(f, "Action::Maximize({maximized})")
}
@@ -147,10 +162,14 @@ impl<T> fmt::Debug for Action<T> {
write!(f, "Action::RequestUserAttention")
}
Self::GainFocus => write!(f, "Action::GainFocus"),
- Self::ChangeAlwaysOnTop(on_top) => {
- write!(f, "Action::AlwaysOnTop({on_top})")
+ Self::ChangeLevel(level) => {
+ write!(f, "Action::ChangeLevel({level:?})")
}
Self::FetchId(_) => write!(f, "Action::FetchId"),
+ Self::ChangeIcon(_icon) => {
+ write!(f, "Action::ChangeIcon(icon)")
+ }
+ Self::Screenshot(_) => write!(f, "Action::Screenshot"),
}
}
}
diff --git a/native/src/widget/button.rs b/widget/src/button.rs
index 39387173..8ebc9657 100644
--- a/native/src/widget/button.rs
+++ b/widget/src/button.rs
@@ -1,15 +1,15 @@
//! Allow your users to perform actions by pressing a button.
//!
//! A [`Button`] has some local [`State`].
-use crate::event::{self, Event};
-use crate::layout;
-use crate::mouse;
-use crate::overlay;
-use crate::renderer;
-use crate::touch;
-use crate::widget::tree::{self, Tree};
-use crate::widget::Operation;
-use crate::{
+use crate::core::event::{self, Event};
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::overlay;
+use crate::core::renderer;
+use crate::core::touch;
+use crate::core::widget::tree::{self, Tree};
+use crate::core::widget::Operation;
+use crate::core::{
Background, Clipboard, Color, Element, Layout, Length, Padding, Point,
Rectangle, Shell, Vector, Widget,
};
@@ -18,9 +18,9 @@ pub use iced_style::button::{Appearance, StyleSheet};
/// A generic widget that produces a message when pressed.
///
-/// ```
+/// ```no_run
/// # type Button<'a, Message> =
-/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>;
+/// # iced_widget::Button<'a, Message, iced_widget::renderer::Renderer<iced_widget::style::Theme>>;
/// #
/// #[derive(Clone)]
/// enum Message {
@@ -35,7 +35,7 @@ pub use iced_style::button::{Appearance, StyleSheet};
///
/// ```
/// # type Button<'a, Message> =
-/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>;
+/// # iced_widget::Button<'a, Message, iced_widget::renderer::Renderer<iced_widget::style::Theme>>;
/// #
/// #[derive(Clone)]
/// enum Message {
@@ -51,9 +51,9 @@ pub use iced_style::button::{Appearance, StyleSheet};
/// }
/// ```
#[allow(missing_debug_implementations)]
-pub struct Button<'a, Message, Renderer>
+pub struct Button<'a, Message, Renderer = crate::Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
content: Element<'a, Message, Renderer>,
@@ -66,7 +66,7 @@ where
impl<'a, Message, Renderer> Button<'a, Message, Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
/// Creates a new [`Button`] with the given content.
@@ -102,8 +102,17 @@ where
/// Sets the message that will be produced when the [`Button`] is pressed.
///
/// Unless `on_press` is called, the [`Button`] will be disabled.
- pub fn on_press(mut self, msg: Message) -> Self {
- self.on_press = Some(msg);
+ pub fn on_press(mut self, on_press: Message) -> Self {
+ self.on_press = Some(on_press);
+ self
+ }
+
+ /// Sets the message that will be produced when the [`Button`] is pressed,
+ /// if `Some`.
+ ///
+ /// If `None`, the [`Button`] will be disabled.
+ pub fn on_press_maybe(mut self, on_press: Option<Message>) -> Self {
+ self.on_press = on_press;
self
}
@@ -121,7 +130,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
for Button<'a, Message, Renderer>
where
Message: 'a + Clone,
- Renderer: 'a + crate::Renderer,
+ Renderer: 'a + crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
fn tag(&self) -> tree::Tag {
@@ -187,7 +196,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -196,7 +205,7 @@ where
&mut tree.children[0],
event.clone(),
layout.children().next().unwrap(),
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -204,14 +213,9 @@ where
return event::Status::Captured;
}
- update(
- event,
- layout,
- cursor_position,
- shell,
- &self.on_press,
- || tree.state.downcast_mut::<State>(),
- )
+ update(event, layout, cursor, shell, &self.on_press, || {
+ tree.state.downcast_mut::<State>()
+ })
}
fn draw(
@@ -221,7 +225,7 @@ where
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
let bounds = layout.bounds();
@@ -230,7 +234,7 @@ where
let styling = draw(
renderer,
bounds,
- cursor_position,
+ cursor,
self.on_press.is_some(),
theme,
&self.style,
@@ -245,7 +249,7 @@ where
text_color: styling.text_color,
},
content_layout,
- cursor_position,
+ cursor,
&bounds,
);
}
@@ -254,11 +258,11 @@ where
&self,
_tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- mouse_interaction(layout, cursor_position, self.on_press.is_some())
+ mouse_interaction(layout, cursor, self.on_press.is_some())
}
fn overlay<'b>(
@@ -279,7 +283,7 @@ impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: Clone + 'a,
- Renderer: crate::Renderer + 'a,
+ Renderer: crate::core::Renderer + 'a,
Renderer::Theme: StyleSheet,
{
fn from(button: Button<'a, Message, Renderer>) -> Self {
@@ -305,7 +309,7 @@ impl State {
pub fn update<'a, Message: Clone>(
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
shell: &mut Shell<'_, Message>,
on_press: &Option<Message>,
state: impl FnOnce() -> &'a mut State,
@@ -316,7 +320,7 @@ pub fn update<'a, Message: Clone>(
if on_press.is_some() {
let bounds = layout.bounds();
- if bounds.contains(cursor_position) {
+ if cursor.is_over(bounds) {
let state = state();
state.is_pressed = true;
@@ -335,7 +339,7 @@ pub fn update<'a, Message: Clone>(
let bounds = layout.bounds();
- if bounds.contains(cursor_position) {
+ if cursor.is_over(bounds) {
shell.publish(on_press);
}
@@ -355,10 +359,10 @@ pub fn update<'a, Message: Clone>(
}
/// Draws a [`Button`].
-pub fn draw<'a, Renderer: crate::Renderer>(
+pub fn draw<'a, Renderer: crate::core::Renderer>(
renderer: &mut Renderer,
bounds: Rectangle,
- cursor_position: Point,
+ cursor: mouse::Cursor,
is_enabled: bool,
style_sheet: &dyn StyleSheet<
Style = <Renderer::Theme as StyleSheet>::Style,
@@ -369,7 +373,7 @@ pub fn draw<'a, Renderer: crate::Renderer>(
where
Renderer::Theme: StyleSheet,
{
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(bounds);
let styling = if !is_enabled {
style_sheet.disabled(style)
@@ -395,7 +399,7 @@ where
y: bounds.y + styling.shadow_offset.y,
..bounds
},
- border_radius: styling.border_radius.into(),
+ border_radius: styling.border_radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
@@ -406,7 +410,7 @@ where
renderer.fill_quad(
renderer::Quad {
bounds,
- border_radius: styling.border_radius.into(),
+ border_radius: styling.border_radius,
border_width: styling.border_width,
border_color: styling.border_color,
},
@@ -442,10 +446,10 @@ pub fn layout<Renderer>(
/// Returns the [`mouse::Interaction`] of a [`Button`].
pub fn mouse_interaction(
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
is_enabled: bool,
) -> mouse::Interaction {
- let is_mouse_over = layout.bounds().contains(cursor_position);
+ let is_mouse_over = cursor.is_over(layout.bounds());
if is_mouse_over && is_enabled {
mouse::Interaction::Pointer
diff --git a/native/src/widget/checkbox.rs b/widget/src/checkbox.rs
index 9b69e574..aa0bff42 100644
--- a/native/src/widget/checkbox.rs
+++ b/widget/src/checkbox.rs
@@ -1,36 +1,27 @@
//! Show toggle controls using checkboxes.
-use crate::alignment;
-use crate::event::{self, Event};
-use crate::layout;
-use crate::mouse;
-use crate::renderer;
-use crate::text;
-use crate::touch;
-use crate::widget::{self, Row, Text, Tree};
-use crate::{
- Alignment, Clipboard, Element, Layout, Length, Pixels, Point, Rectangle,
- Shell, Widget,
+use crate::core::alignment;
+use crate::core::event::{self, Event};
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::renderer;
+use crate::core::text;
+use crate::core::touch;
+use crate::core::widget::Tree;
+use crate::core::{
+ Alignment, Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell,
+ Widget,
};
+use crate::{Row, Text};
pub use iced_style::checkbox::{Appearance, StyleSheet};
-/// The icon in a [`Checkbox`].
-#[derive(Debug, Clone, PartialEq)]
-pub struct Icon<Font> {
- /// Font that will be used to display the `code_point`,
- pub font: Font,
- /// The unicode code point that will be used as the icon.
- pub code_point: char,
- /// Font size of the content.
- pub size: Option<f32>,
-}
-
/// A box that can be checked.
///
/// # Example
///
-/// ```
-/// # type Checkbox<'a, Message> = iced_native::widget::Checkbox<'a, Message, iced_native::renderer::Null>;
+/// ```no_run
+/// # type Checkbox<'a, Message> =
+/// # iced_widget::Checkbox<'a, Message, iced_widget::renderer::Renderer<iced_widget::style::Theme>>;
/// #
/// pub enum Message {
/// CheckboxToggled(bool),
@@ -43,10 +34,10 @@ pub struct Icon<Font> {
///
/// ![Checkbox drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/checkbox.png?raw=true)
#[allow(missing_debug_implementations)]
-pub struct Checkbox<'a, Message, Renderer>
+pub struct Checkbox<'a, Message, Renderer = crate::Renderer>
where
Renderer: text::Renderer,
- Renderer::Theme: StyleSheet + widget::text::StyleSheet,
+ Renderer::Theme: StyleSheet + crate::text::StyleSheet,
{
is_checked: bool,
on_toggle: Box<dyn Fn(bool) -> Message + 'a>,
@@ -55,7 +46,9 @@ where
size: f32,
spacing: f32,
text_size: Option<f32>,
- font: Renderer::Font,
+ text_line_height: text::LineHeight,
+ text_shaping: text::Shaping,
+ font: Option<Renderer::Font>,
icon: Icon<Renderer::Font>,
style: <Renderer::Theme as StyleSheet>::Style,
}
@@ -63,7 +56,7 @@ where
impl<'a, Message, Renderer> Checkbox<'a, Message, Renderer>
where
Renderer: text::Renderer,
- Renderer::Theme: StyleSheet + widget::text::StyleSheet,
+ Renderer::Theme: StyleSheet + crate::text::StyleSheet,
{
/// The default size of a [`Checkbox`].
const DEFAULT_SIZE: f32 = 20.0;
@@ -91,11 +84,15 @@ where
size: Self::DEFAULT_SIZE,
spacing: Self::DEFAULT_SPACING,
text_size: None,
- font: Renderer::Font::default(),
+ text_line_height: text::LineHeight::default(),
+ text_shaping: text::Shaping::Basic,
+ font: None,
icon: Icon {
font: Renderer::ICON_FONT,
code_point: Renderer::CHECKMARK_ICON,
size: None,
+ line_height: text::LineHeight::default(),
+ shaping: text::Shaping::Basic,
},
style: Default::default(),
}
@@ -125,11 +122,26 @@ where
self
}
+ /// Sets the text [`LineHeight`] of the [`Checkbox`].
+ pub fn text_line_height(
+ mut self,
+ line_height: impl Into<text::LineHeight>,
+ ) -> Self {
+ self.text_line_height = line_height.into();
+ self
+ }
+
+ /// Sets the [`text::Shaping`] strategy of the [`Checkbox`].
+ pub fn text_shaping(mut self, shaping: text::Shaping) -> Self {
+ self.text_shaping = shaping;
+ self
+ }
+
/// Sets the [`Font`] of the text of the [`Checkbox`].
///
/// [`Font`]: crate::text::Renderer::Font
- pub fn font(mut self, font: Renderer::Font) -> Self {
- self.font = font;
+ pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
+ self.font = Some(font.into());
self
}
@@ -153,7 +165,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
for Checkbox<'a, Message, Renderer>
where
Renderer: text::Renderer,
- Renderer::Theme: StyleSheet + widget::text::StyleSheet,
+ Renderer::Theme: StyleSheet + crate::text::StyleSheet,
{
fn width(&self) -> Length {
self.width
@@ -175,12 +187,14 @@ where
.push(Row::new().width(self.size).height(self.size))
.push(
Text::new(&self.label)
- .font(self.font.clone())
+ .font(self.font.unwrap_or_else(|| renderer.default_font()))
.width(self.width)
.size(
self.text_size
.unwrap_or_else(|| renderer.default_size()),
- ),
+ )
+ .line_height(self.text_line_height)
+ .shaping(self.text_shaping),
)
.layout(renderer, limits)
}
@@ -190,7 +204,7 @@ where
_tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -198,7 +212,7 @@ where
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
- let mouse_over = layout.bounds().contains(cursor_position);
+ let mouse_over = cursor.is_over(layout.bounds());
if mouse_over {
shell.publish((self.on_toggle)(!self.is_checked));
@@ -216,11 +230,11 @@ where
&self,
_tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- if layout.bounds().contains(cursor_position) {
+ if cursor.is_over(layout.bounds()) {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
@@ -234,11 +248,10 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
- let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(layout.bounds());
let mut children = layout.children();
@@ -255,7 +268,7 @@ where
renderer.fill_quad(
renderer::Quad {
bounds,
- border_radius: custom_style.border_radius.into(),
+ border_radius: custom_style.border_radius,
border_width: custom_style.border_width,
border_color: custom_style.border_color,
},
@@ -266,14 +279,17 @@ where
font,
code_point,
size,
+ line_height,
+ shaping,
} = &self.icon;
- let size = size.map(f32::from).unwrap_or(bounds.height * 0.7);
+ let size = size.unwrap_or(bounds.height * 0.7);
if self.is_checked {
renderer.fill_text(text::Text {
content: &code_point.to_string(),
- font: font.clone(),
+ font: *font,
size,
+ line_height: *line_height,
bounds: Rectangle {
x: bounds.center_x(),
y: bounds.center_y(),
@@ -282,6 +298,7 @@ where
color: custom_style.icon_color,
horizontal_alignment: alignment::Horizontal::Center,
vertical_alignment: alignment::Vertical::Center,
+ shaping: *shaping,
});
}
}
@@ -289,18 +306,20 @@ where
{
let label_layout = children.next().unwrap();
- widget::text::draw(
+ crate::text::draw(
renderer,
style,
label_layout,
&self.label,
self.text_size,
- self.font.clone(),
- widget::text::Appearance {
+ self.text_line_height,
+ self.font,
+ crate::text::Appearance {
color: custom_style.text_color,
},
alignment::Horizontal::Left,
alignment::Vertical::Center,
+ self.text_shaping,
);
}
}
@@ -311,7 +330,7 @@ impl<'a, Message, Renderer> From<Checkbox<'a, Message, Renderer>>
where
Message: 'a,
Renderer: 'a + text::Renderer,
- Renderer::Theme: StyleSheet + widget::text::StyleSheet,
+ Renderer::Theme: StyleSheet + crate::text::StyleSheet,
{
fn from(
checkbox: Checkbox<'a, Message, Renderer>,
@@ -319,3 +338,18 @@ where
Element::new(checkbox)
}
}
+
+/// The icon in a [`Checkbox`].
+#[derive(Debug, Clone, PartialEq)]
+pub struct Icon<Font> {
+ /// Font that will be used to display the `code_point`,
+ pub font: Font,
+ /// The unicode code point that will be used as the icon.
+ pub code_point: char,
+ /// Font size of the content.
+ pub size: Option<f32>,
+ /// The line height of the icon.
+ pub line_height: text::LineHeight,
+ /// The shaping strategy of the icon.
+ pub shaping: text::Shaping,
+}
diff --git a/native/src/widget/column.rs b/widget/src/column.rs
index ebe579d5..d92d794b 100644
--- a/native/src/widget/column.rs
+++ b/widget/src/column.rs
@@ -1,18 +1,18 @@
//! Distribute content vertically.
-use crate::event::{self, Event};
-use crate::layout;
-use crate::mouse;
-use crate::overlay;
-use crate::renderer;
-use crate::widget::{Operation, Tree};
-use crate::{
- Alignment, Clipboard, Element, Layout, Length, Padding, Pixels, Point,
- Rectangle, Shell, Widget,
+use crate::core::event::{self, Event};
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::overlay;
+use crate::core::renderer;
+use crate::core::widget::{Operation, Tree};
+use crate::core::{
+ Alignment, Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle,
+ Shell, Widget,
};
/// A container that distributes its contents vertically.
#[allow(missing_debug_implementations)]
-pub struct Column<'a, Message, Renderer> {
+pub struct Column<'a, Message, Renderer = crate::Renderer> {
spacing: f32,
padding: Padding,
width: Length,
@@ -102,7 +102,7 @@ impl<'a, Message, Renderer> Default for Column<'a, Message, Renderer> {
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Column<'a, Message, Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
{
fn children(&self) -> Vec<Tree> {
self.children.iter().map(Tree::new).collect()
@@ -166,7 +166,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -180,7 +180,7 @@ where
state,
event.clone(),
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -193,7 +193,7 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
@@ -203,11 +203,7 @@ where
.zip(layout.children())
.map(|((child, state), layout)| {
child.as_widget().mouse_interaction(
- state,
- layout,
- cursor_position,
- viewport,
- renderer,
+ state, layout, cursor, viewport, renderer,
)
})
.max()
@@ -221,7 +217,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
for ((child, state), layout) in self
@@ -230,15 +226,9 @@ where
.zip(&tree.children)
.zip(layout.children())
{
- child.as_widget().draw(
- state,
- renderer,
- theme,
- style,
- layout,
- cursor_position,
- viewport,
- );
+ child
+ .as_widget()
+ .draw(state, renderer, theme, style, layout, cursor, viewport);
}
}
@@ -256,7 +246,7 @@ impl<'a, Message, Renderer> From<Column<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a,
- Renderer: crate::Renderer + 'a,
+ Renderer: crate::core::Renderer + 'a,
{
fn from(column: Column<'a, Message, Renderer>) -> Self {
Self::new(column)
diff --git a/native/src/widget/container.rs b/widget/src/container.rs
index b77bf50d..da9a31d6 100644
--- a/native/src/widget/container.rs
+++ b/widget/src/container.rs
@@ -1,12 +1,12 @@
//! Decorate content and apply alignment.
-use crate::alignment::{self, Alignment};
-use crate::event::{self, Event};
-use crate::layout;
-use crate::mouse;
-use crate::overlay;
-use crate::renderer;
-use crate::widget::{self, Operation, Tree};
-use crate::{
+use crate::core::alignment::{self, Alignment};
+use crate::core::event::{self, Event};
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::overlay;
+use crate::core::renderer;
+use crate::core::widget::{self, Operation, Tree};
+use crate::core::{
Background, Clipboard, Color, Element, Layout, Length, Padding, Pixels,
Point, Rectangle, Shell, Widget,
};
@@ -17,9 +17,9 @@ pub use iced_style::container::{Appearance, StyleSheet};
///
/// It is normally used for alignment purposes.
#[allow(missing_debug_implementations)]
-pub struct Container<'a, Message, Renderer>
+pub struct Container<'a, Message, Renderer = crate::Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
id: Option<Id>,
@@ -36,7 +36,7 @@ where
impl<'a, Message, Renderer> Container<'a, Message, Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
/// Creates an empty [`Container`].
@@ -131,7 +131,7 @@ where
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Container<'a, Message, Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
fn children(&self) -> Vec<Tree> {
@@ -196,7 +196,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -205,7 +205,7 @@ where
&mut tree.children[0],
event,
layout.children().next().unwrap(),
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -216,14 +216,14 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.as_widget().mouse_interaction(
&tree.children[0],
layout.children().next().unwrap(),
- cursor_position,
+ cursor,
viewport,
renderer,
)
@@ -236,7 +236,7 @@ where
theme: &Renderer::Theme,
renderer_style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
let style = theme.appearance(&self.style);
@@ -253,7 +253,7 @@ where
.unwrap_or(renderer_style.text_color),
},
layout.children().next().unwrap(),
- cursor_position,
+ cursor,
viewport,
);
}
@@ -276,7 +276,7 @@ impl<'a, Message, Renderer> From<Container<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a,
- Renderer: 'a + crate::Renderer,
+ Renderer: 'a + crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
fn from(
@@ -326,13 +326,13 @@ pub fn draw_background<Renderer>(
appearance: &Appearance,
bounds: Rectangle,
) where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
{
if appearance.background.is_some() || appearance.border_width > 0.0 {
renderer.fill_quad(
renderer::Quad {
bounds,
- border_radius: appearance.border_radius.into(),
+ border_radius: appearance.border_radius,
border_width: appearance.border_width,
border_color: appearance.border_color,
},
diff --git a/native/src/widget/image.rs b/widget/src/image.rs
index 73257a74..66bf2156 100644
--- a/native/src/widget/image.rs
+++ b/widget/src/image.rs
@@ -2,16 +2,19 @@
pub mod viewer;
pub use viewer::Viewer;
-use crate::image;
-use crate::layout;
-use crate::renderer;
-use crate::widget::Tree;
-use crate::{
- ContentFit, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget,
+use crate::core::image;
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::renderer;
+use crate::core::widget::Tree;
+use crate::core::{
+ ContentFit, Element, Layout, Length, Rectangle, Size, Vector, Widget,
};
use std::hash::Hash;
+pub use image::Handle;
+
/// Creates a new [`Viewer`] with the given image `Handle`.
pub fn viewer<Handle>(handle: Handle) -> Viewer<Handle> {
Viewer::new(handle)
@@ -21,9 +24,8 @@ pub fn viewer<Handle>(handle: Handle) -> Viewer<Handle> {
///
/// # Example
///
-/// ```
-/// # use iced_native::widget::Image;
-/// # use iced_native::image;
+/// ```no_run
+/// # use iced_widget::image::{self, Image};
/// #
/// let image = Image::<image::Handle>::new("resources/ferris.png");
/// ```
@@ -185,7 +187,7 @@ where
_theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
draw(renderer, layout, &self.handle, self.content_fit)
diff --git a/native/src/widget/image/viewer.rs b/widget/src/image/viewer.rs
index 1f8d5d7a..8040d6bd 100644
--- a/native/src/widget/image/viewer.rs
+++ b/widget/src/image/viewer.rs
@@ -1,11 +1,11 @@
//! Zoom and pan on an image.
-use crate::event::{self, Event};
-use crate::image;
-use crate::layout;
-use crate::mouse;
-use crate::renderer;
-use crate::widget::tree::{self, Tree};
-use crate::{
+use crate::core::event::{self, Event};
+use crate::core::image;
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::renderer;
+use crate::core::widget::tree::{self, Tree};
+use crate::core::{
Clipboard, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size,
Vector, Widget,
};
@@ -144,18 +144,19 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
_shell: &mut Shell<'_, Message>,
) -> event::Status {
let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
match event {
- Event::Mouse(mouse::Event::WheelScrolled { delta })
- if is_mouse_over =>
- {
+ Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
+ let Some(cursor_position) = cursor.position() else {
+ return event::Status::Ignored;
+ };
+
match delta {
mouse::ScrollDelta::Lines { y, .. }
| mouse::ScrollDelta::Pixels { y, .. } => {
@@ -205,9 +206,11 @@ where
event::Status::Captured
}
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
- if is_mouse_over =>
- {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+ let Some(cursor_position) = cursor.position() else {
+ return event::Status::Ignored;
+ };
+
let state = tree.state.downcast_mut::<State>();
state.cursor_grabbed_at = Some(cursor_position);
@@ -277,13 +280,13 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
let state = tree.state.downcast_ref::<State>();
let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(bounds);
if state.is_cursor_grabbed() {
mouse::Interaction::Grabbing
@@ -301,7 +304,7 @@ where
_theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
let state = tree.state.downcast_ref::<State>();
diff --git a/native/src/overlay/menu.rs b/widget/src/overlay/menu.rs
index 50f741ef..ccf4dfb5 100644
--- a/native/src/overlay/menu.rs
+++ b/widget/src/overlay/menu.rs
@@ -1,25 +1,25 @@
//! Build and show dropdown menus.
-use crate::alignment;
-use crate::event::{self, Event};
-use crate::layout;
-use crate::mouse;
-use crate::overlay;
-use crate::renderer;
-use crate::text::{self, Text};
-use crate::touch;
-use crate::widget::container::{self, Container};
-use crate::widget::scrollable::{self, Scrollable};
-use crate::widget::Tree;
-use crate::{
- Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point,
- Rectangle, Shell, Size, Vector, Widget,
+use crate::container::{self, Container};
+use crate::core::alignment;
+use crate::core::event::{self, Event};
+use crate::core::layout::{self, Layout};
+use crate::core::mouse;
+use crate::core::overlay;
+use crate::core::renderer;
+use crate::core::text::{self, Text};
+use crate::core::touch;
+use crate::core::widget::Tree;
+use crate::core::{
+ Clipboard, Color, Length, Padding, Pixels, Point, Rectangle, Size, Vector,
};
+use crate::core::{Element, Shell, Widget};
+use crate::scrollable::{self, Scrollable};
pub use iced_style::menu::{Appearance, StyleSheet};
/// A list of selectable options.
#[allow(missing_debug_implementations)]
-pub struct Menu<'a, T, Renderer>
+pub struct Menu<'a, T, Message, Renderer = crate::Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
@@ -27,17 +27,20 @@ where
state: &'a mut State,
options: &'a [T],
hovered_option: &'a mut Option<usize>,
- last_selection: &'a mut Option<T>,
+ on_selected: Box<dyn FnMut(T) -> Message + 'a>,
width: f32,
padding: Padding,
text_size: Option<f32>,
- font: Renderer::Font,
+ text_line_height: text::LineHeight,
+ text_shaping: text::Shaping,
+ font: Option<Renderer::Font>,
style: <Renderer::Theme as StyleSheet>::Style,
}
-impl<'a, T, Renderer> Menu<'a, T, Renderer>
+impl<'a, T, Message, Renderer> Menu<'a, T, Message, Renderer>
where
T: ToString + Clone,
+ Message: 'a,
Renderer: text::Renderer + 'a,
Renderer::Theme:
StyleSheet + container::StyleSheet + scrollable::StyleSheet,
@@ -48,17 +51,19 @@ where
state: &'a mut State,
options: &'a [T],
hovered_option: &'a mut Option<usize>,
- last_selection: &'a mut Option<T>,
+ on_selected: impl FnMut(T) -> Message + 'a,
) -> Self {
Menu {
state,
options,
hovered_option,
- last_selection,
+ on_selected: Box::new(on_selected),
width: 0.0,
padding: Padding::ZERO,
text_size: None,
- font: Default::default(),
+ text_line_height: text::LineHeight::default(),
+ text_shaping: text::Shaping::Basic,
+ font: None,
style: Default::default(),
}
}
@@ -81,9 +86,24 @@ where
self
}
+ /// Sets the text [`LineHeight`] of the [`Menu`].
+ pub fn text_line_height(
+ mut self,
+ line_height: impl Into<text::LineHeight>,
+ ) -> Self {
+ self.text_line_height = line_height.into();
+ self
+ }
+
+ /// Sets the [`text::Shaping`] strategy of the [`Menu`].
+ pub fn text_shaping(mut self, shaping: text::Shaping) -> Self {
+ self.text_shaping = shaping;
+ self
+ }
+
/// Sets the font of the [`Menu`].
- pub fn font(mut self, font: Renderer::Font) -> Self {
- self.font = font;
+ pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
+ self.font = Some(font.into());
self
}
@@ -102,7 +122,7 @@ where
/// The `target_height` will be used to display the menu either on top
/// of the target or under it, depending on the screen position and the
/// dimensions of the [`Menu`].
- pub fn overlay<Message: 'a>(
+ pub fn overlay(
self,
position: Point,
target_height: f32,
@@ -137,7 +157,7 @@ impl Default for State {
struct Overlay<'a, Message, Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet + container::StyleSheet,
{
state: &'a mut Tree,
@@ -155,7 +175,10 @@ where
Renderer::Theme:
StyleSheet + container::StyleSheet + scrollable::StyleSheet,
{
- pub fn new<T>(menu: Menu<'a, T, Renderer>, target_height: f32) -> Self
+ pub fn new<T>(
+ menu: Menu<'a, T, Message, Renderer>,
+ target_height: f32,
+ ) -> Self
where
T: Clone + ToString,
{
@@ -163,20 +186,24 @@ where
state,
options,
hovered_option,
- last_selection,
+ on_selected,
width,
padding,
font,
text_size,
+ text_line_height,
+ text_shaping,
style,
} = menu;
let container = Container::new(Scrollable::new(List {
options,
hovered_option,
- last_selection,
+ on_selected,
font,
text_size,
+ text_line_height,
+ text_shaping,
padding,
style: style.clone(),
}));
@@ -193,7 +220,7 @@ where
}
}
-impl<'a, Message, Renderer> crate::Overlay<Message, Renderer>
+impl<'a, Message, Renderer> crate::core::Overlay<Message, Renderer>
for Overlay<'a, Message, Renderer>
where
Renderer: text::Renderer,
@@ -236,36 +263,25 @@ where
&mut self,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.container.on_event(
- self.state,
- event,
- layout,
- cursor_position,
- renderer,
- clipboard,
- shell,
+ self.state, event, layout, cursor, renderer, clipboard, shell,
)
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
- self.container.mouse_interaction(
- self.state,
- layout,
- cursor_position,
- viewport,
- renderer,
- )
+ self.container
+ .mouse_interaction(self.state, layout, cursor, viewport, renderer)
}
fn draw(
@@ -274,7 +290,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) {
let appearance = theme.appearance(&self.style);
let bounds = layout.bounds();
@@ -284,39 +300,34 @@ where
bounds,
border_color: appearance.border_color,
border_width: appearance.border_width,
- border_radius: appearance.border_radius.into(),
+ border_radius: appearance.border_radius,
},
appearance.background,
);
- self.container.draw(
- self.state,
- renderer,
- theme,
- style,
- layout,
- cursor_position,
- &bounds,
- );
+ self.container
+ .draw(self.state, renderer, theme, style, layout, cursor, &bounds);
}
}
-struct List<'a, T, Renderer>
+struct List<'a, T, Message, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
options: &'a [T],
hovered_option: &'a mut Option<usize>,
- last_selection: &'a mut Option<T>,
+ on_selected: Box<dyn FnMut(T) -> Message + 'a>,
padding: Padding,
text_size: Option<f32>,
- font: Renderer::Font,
+ text_line_height: text::LineHeight,
+ text_shaping: text::Shaping,
+ font: Option<Renderer::Font>,
style: <Renderer::Theme as StyleSheet>::Style,
}
impl<'a, T, Message, Renderer> Widget<Message, Renderer>
- for List<'a, T, Renderer>
+ for List<'a, T, Message, Renderer>
where
T: Clone + ToString,
Renderer: text::Renderer,
@@ -341,10 +352,13 @@ where
let text_size =
self.text_size.unwrap_or_else(|| renderer.default_size());
+ let text_line_height =
+ self.text_line_height.to_absolute(Pixels(text_size));
+
let size = {
let intrinsic = Size::new(
0.0,
- (text_size + self.padding.vertical())
+ (f32::from(text_line_height) + self.padding.vertical())
* self.options.len() as f32,
);
@@ -359,55 +373,57 @@ where
_state: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
- _shell: &mut Shell<'_, Message>,
+ shell: &mut Shell<'_, Message>,
) -> event::Status {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
- let bounds = layout.bounds();
-
- if bounds.contains(cursor_position) {
+ if cursor.is_over(layout.bounds()) {
if let Some(index) = *self.hovered_option {
if let Some(option) = self.options.get(index) {
- *self.last_selection = Some(option.clone());
+ shell.publish((self.on_selected)(option.clone()));
+ return event::Status::Captured;
}
}
}
}
Event::Mouse(mouse::Event::CursorMoved { .. }) => {
- let bounds = layout.bounds();
-
- if bounds.contains(cursor_position) {
+ if let Some(cursor_position) =
+ cursor.position_in(layout.bounds())
+ {
let text_size = self
.text_size
.unwrap_or_else(|| renderer.default_size());
- *self.hovered_option = Some(
- ((cursor_position.y - bounds.y)
- / (text_size + self.padding.vertical()))
- as usize,
- );
+ let option_height = f32::from(
+ self.text_line_height.to_absolute(Pixels(text_size)),
+ ) + self.padding.vertical();
+
+ *self.hovered_option =
+ Some((cursor_position.y / option_height) as usize);
}
}
Event::Touch(touch::Event::FingerPressed { .. }) => {
- let bounds = layout.bounds();
-
- if bounds.contains(cursor_position) {
+ if let Some(cursor_position) =
+ cursor.position_in(layout.bounds())
+ {
let text_size = self
.text_size
.unwrap_or_else(|| renderer.default_size());
- *self.hovered_option = Some(
- ((cursor_position.y - bounds.y)
- / (text_size + self.padding.vertical()))
- as usize,
- );
+ let option_height = f32::from(
+ self.text_line_height.to_absolute(Pixels(text_size)),
+ ) + self.padding.vertical();
+
+ *self.hovered_option =
+ Some((cursor_position.y / option_height) as usize);
if let Some(index) = *self.hovered_option {
if let Some(option) = self.options.get(index) {
- *self.last_selection = Some(option.clone());
+ shell.publish((self.on_selected)(option.clone()));
+ return event::Status::Captured;
}
}
}
@@ -422,11 +438,11 @@ where
&self,
_state: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- let is_mouse_over = layout.bounds().contains(cursor_position);
+ let is_mouse_over = cursor.is_over(layout.bounds());
if is_mouse_over {
mouse::Interaction::Pointer
@@ -442,7 +458,7 @@ where
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
viewport: &Rectangle,
) {
let appearance = theme.appearance(&self.style);
@@ -450,12 +466,13 @@ where
let text_size =
self.text_size.unwrap_or_else(|| renderer.default_size());
- let option_height = (text_size + self.padding.vertical()) as usize;
+ let option_height =
+ f32::from(self.text_line_height.to_absolute(Pixels(text_size)))
+ + self.padding.vertical();
let offset = viewport.y - bounds.y;
- let start = (offset / option_height as f32) as usize;
- let end =
- ((offset + viewport.height) / option_height as f32).ceil() as usize;
+ let start = (offset / option_height) as usize;
+ let end = ((offset + viewport.height) / option_height).ceil() as usize;
let visible_options = &self.options[start..end.min(self.options.len())];
@@ -465,18 +482,22 @@ where
let bounds = Rectangle {
x: bounds.x,
- y: bounds.y + (option_height * i) as f32,
+ y: bounds.y + (option_height * i as f32),
width: bounds.width,
- height: text_size + self.padding.vertical(),
+ height: option_height,
};
if is_selected {
renderer.fill_quad(
renderer::Quad {
- bounds,
+ bounds: Rectangle {
+ x: bounds.x + appearance.border_width,
+ width: bounds.width - appearance.border_width * 2.0,
+ ..bounds
+ },
border_color: Color::TRANSPARENT,
border_width: 0.0,
- border_radius: appearance.border_radius.into(),
+ border_radius: appearance.border_radius,
},
appearance.selected_background,
);
@@ -491,7 +512,8 @@ where
..bounds
},
size: text_size,
- font: self.font.clone(),
+ line_height: self.text_line_height,
+ font: self.font.unwrap_or_else(|| renderer.default_font()),
color: if is_selected {
appearance.selected_text_color
} else {
@@ -499,12 +521,13 @@ where
},
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
+ shaping: self.text_shaping,
});
}
}
}
-impl<'a, T, Message, Renderer> From<List<'a, T, Renderer>>
+impl<'a, T, Message, Renderer> From<List<'a, T, Message, Renderer>>
for Element<'a, Message, Renderer>
where
T: ToString + Clone,
@@ -512,7 +535,7 @@ where
Renderer: 'a + text::Renderer,
Renderer::Theme: StyleSheet,
{
- fn from(list: List<'a, T, Renderer>) -> Self {
+ fn from(list: List<'a, T, Message, Renderer>) -> Self {
Element::new(list)
}
}
diff --git a/native/src/widget/pane_grid.rs b/widget/src/pane_grid.rs
index bcb17ebd..31bb0e86 100644
--- a/native/src/widget/pane_grid.rs
+++ b/widget/src/pane_grid.rs
@@ -6,7 +6,7 @@
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
//! drag and drop, and hotkey support.
//!
-//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.8/examples/pane_grid
+//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.9/examples/pane_grid
mod axis;
mod configuration;
mod content;
@@ -30,18 +30,18 @@ pub use split::Split;
pub use state::State;
pub use title_bar::TitleBar;
-pub use iced_style::pane_grid::{Line, StyleSheet};
-
-use crate::event::{self, Event};
-use crate::layout;
-use crate::mouse;
-use crate::overlay::{self, Group};
-use crate::renderer;
-use crate::touch;
-use crate::widget;
-use crate::widget::container;
-use crate::widget::tree::{self, Tree};
-use crate::{
+pub use crate::style::pane_grid::{Appearance, Line, StyleSheet};
+
+use crate::container;
+use crate::core::event::{self, Event};
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::overlay::{self, Group};
+use crate::core::renderer;
+use crate::core::touch;
+use crate::core::widget;
+use crate::core::widget::tree::{self, Tree};
+use crate::core::{
Clipboard, Color, Element, Layout, Length, Pixels, Point, Rectangle, Shell,
Size, Vector, Widget,
};
@@ -67,11 +67,11 @@ use crate::{
///
/// ## Example
///
-/// ```
-/// # use iced_native::widget::{pane_grid, text};
+/// ```no_run
+/// # use iced_widget::{pane_grid, text};
/// #
/// # type PaneGrid<'a, Message> =
-/// # iced_native::widget::PaneGrid<'a, Message, iced_native::renderer::Null>;
+/// # iced_widget::PaneGrid<'a, Message, iced_widget::renderer::Renderer<iced_widget::style::Theme>>;
/// #
/// enum PaneState {
/// SomePane,
@@ -96,9 +96,9 @@ use crate::{
/// .on_resize(10, Message::PaneResized);
/// ```
#[allow(missing_debug_implementations)]
-pub struct PaneGrid<'a, Message, Renderer>
+pub struct PaneGrid<'a, Message, Renderer = crate::Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet + container::StyleSheet,
{
contents: Contents<'a, Content<'a, Message, Renderer>>,
@@ -113,7 +113,7 @@ where
impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet + container::StyleSheet,
{
/// Creates a [`PaneGrid`] with the given [`State`] and view function.
@@ -232,7 +232,7 @@ where
impl<'a, Message, Renderer> Widget<Message, Renderer>
for PaneGrid<'a, Message, Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet + container::StyleSheet,
{
fn tag(&self) -> tree::Tag {
@@ -313,7 +313,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -331,7 +331,7 @@ where
self.contents.layout(),
&event,
layout,
- cursor_position,
+ cursor,
shell,
self.spacing,
self.contents.iter(),
@@ -353,7 +353,7 @@ where
tree,
event.clone(),
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -367,7 +367,7 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
@@ -375,7 +375,7 @@ where
tree.state.downcast_ref(),
self.contents.layout(),
layout,
- cursor_position,
+ cursor,
self.spacing,
self.on_resize.as_ref().map(|(leeway, _)| *leeway),
)
@@ -388,7 +388,7 @@ where
content.mouse_interaction(
tree,
layout,
- cursor_position,
+ cursor,
viewport,
renderer,
self.drag_enabled(),
@@ -406,14 +406,14 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
draw(
tree.state.downcast_ref(),
self.contents.layout(),
layout,
- cursor_position,
+ cursor,
renderer,
theme,
style,
@@ -425,20 +425,9 @@ where
.iter()
.zip(&tree.children)
.map(|((pane, content), tree)| (pane, (content, tree))),
- |(content, tree),
- renderer,
- style,
- layout,
- cursor_position,
- rectangle| {
+ |(content, tree), renderer, style, layout, cursor, rectangle| {
content.draw(
- tree,
- renderer,
- theme,
- style,
- layout,
- cursor_position,
- rectangle,
+ tree, renderer, theme, style, layout, cursor, rectangle,
);
},
)
@@ -468,7 +457,7 @@ impl<'a, Message, Renderer> From<PaneGrid<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a,
- Renderer: 'a + crate::Renderer,
+ Renderer: 'a + crate::core::Renderer,
Renderer::Theme: StyleSheet + container::StyleSheet,
{
fn from(
@@ -520,7 +509,7 @@ pub fn update<'a, Message, T: Draggable>(
node: &Node,
event: &Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
shell: &mut Shell<'_, Message>,
spacing: f32,
contents: impl Iterator<Item = (Pane, T)>,
@@ -535,7 +524,7 @@ pub fn update<'a, Message, T: Draggable>(
| Event::Touch(touch::Event::FingerPressed { .. }) => {
let bounds = layout.bounds();
- if bounds.contains(cursor_position) {
+ if let Some(cursor_position) = cursor.position_over(bounds) {
event_status = event::Status::Captured;
match on_resize {
@@ -592,30 +581,46 @@ pub fn update<'a, Message, T: Draggable>(
| Event::Touch(touch::Event::FingerLost { .. }) => {
if let Some((pane, _)) = action.picked_pane() {
if let Some(on_drag) = on_drag {
- let mut dropped_region = contents
- .zip(layout.children())
- .filter(|(_, layout)| {
- layout.bounds().contains(cursor_position)
- });
-
- let event = match dropped_region.next() {
- Some(((target, _), _)) if pane != target => {
- DragEvent::Dropped { pane, target }
- }
- _ => DragEvent::Canceled { pane },
- };
+ if let Some(cursor_position) = cursor.position() {
+ let event = if let Some(edge) =
+ in_edge(layout, cursor_position)
+ {
+ DragEvent::Dropped {
+ pane,
+ target: Target::Edge(edge),
+ }
+ } else {
+ let dropped_region = contents
+ .zip(layout.children())
+ .filter_map(|(target, layout)| {
+ layout_region(layout, cursor_position)
+ .map(|region| (target, region))
+ })
+ .next();
+
+ match dropped_region {
+ Some(((target, _), region))
+ if pane != target =>
+ {
+ DragEvent::Dropped {
+ pane,
+ target: Target::Pane(target, region),
+ }
+ }
+ _ => DragEvent::Canceled { pane },
+ }
+ };
- shell.publish(on_drag(event));
+ shell.publish(on_drag(event));
+ }
}
- *action = state::Action::Idle;
-
event_status = event::Status::Captured;
} else if action.picked_split().is_some() {
- *action = state::Action::Idle;
-
event_status = event::Status::Captured;
}
+
+ *action = state::Action::Idle;
}
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. }) => {
@@ -629,24 +634,32 @@ pub fn update<'a, Message, T: Draggable>(
);
if let Some((axis, rectangle, _)) = splits.get(&split) {
- let ratio = match axis {
- Axis::Horizontal => {
- let position =
- cursor_position.y - bounds.y - rectangle.y;
-
- (position / rectangle.height).clamp(0.1, 0.9)
- }
- Axis::Vertical => {
- let position =
- cursor_position.x - bounds.x - rectangle.x;
-
- (position / rectangle.width).clamp(0.1, 0.9)
- }
- };
-
- shell.publish(on_resize(ResizeEvent { split, ratio }));
-
- event_status = event::Status::Captured;
+ if let Some(cursor_position) = cursor.position() {
+ let ratio = match axis {
+ Axis::Horizontal => {
+ let position = cursor_position.y
+ - bounds.y
+ - rectangle.y;
+
+ (position / rectangle.height)
+ .clamp(0.1, 0.9)
+ }
+ Axis::Vertical => {
+ let position = cursor_position.x
+ - bounds.x
+ - rectangle.x;
+
+ (position / rectangle.width).clamp(0.1, 0.9)
+ }
+ };
+
+ shell.publish(on_resize(ResizeEvent {
+ split,
+ ratio,
+ }));
+
+ event_status = event::Status::Captured;
+ }
}
}
}
@@ -657,6 +670,28 @@ pub fn update<'a, Message, T: Draggable>(
event_status
}
+fn layout_region(layout: Layout<'_>, cursor_position: Point) -> Option<Region> {
+ let bounds = layout.bounds();
+
+ if !bounds.contains(cursor_position) {
+ return None;
+ }
+
+ let region = if cursor_position.x < (bounds.x + bounds.width / 3.0) {
+ Region::Edge(Edge::Left)
+ } else if cursor_position.x > (bounds.x + 2.0 * bounds.width / 3.0) {
+ Region::Edge(Edge::Right)
+ } else if cursor_position.y < (bounds.y + bounds.height / 3.0) {
+ Region::Edge(Edge::Top)
+ } else if cursor_position.y > (bounds.y + 2.0 * bounds.height / 3.0) {
+ Region::Edge(Edge::Bottom)
+ } else {
+ Region::Center
+ };
+
+ Some(region)
+}
+
fn click_pane<'a, Message, T>(
action: &mut state::Action,
layout: Layout<'_>,
@@ -697,7 +732,7 @@ pub fn mouse_interaction(
action: &state::Action,
node: &Node,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
spacing: f32,
resize_leeway: Option<f32>,
) -> Option<mouse::Interaction> {
@@ -708,6 +743,7 @@ pub fn mouse_interaction(
let resize_axis =
action.picked_split().map(|(_, axis)| axis).or_else(|| {
resize_leeway.and_then(|leeway| {
+ let cursor_position = cursor.position()?;
let bounds = layout.bounds();
let splits = node.split_regions(spacing, bounds.size());
@@ -737,7 +773,7 @@ pub fn draw<Renderer, T>(
action: &state::Action,
node: &Node,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &mut Renderer,
theme: &Renderer::Theme,
default_style: &renderer::Style,
@@ -751,11 +787,11 @@ pub fn draw<Renderer, T>(
&mut Renderer,
&renderer::Style,
Layout<'_>,
- Point,
+ mouse::Cursor,
&Rectangle,
),
) where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
let picked_pane = action.picked_pane();
@@ -775,6 +811,7 @@ pub fn draw<Renderer, T>(
})
.or_else(|| match resize_leeway {
Some(leeway) => {
+ let cursor_position = cursor.position()?;
let bounds = layout.bounds();
let relative_cursor = Point::new(
@@ -795,93 +832,234 @@ pub fn draw<Renderer, T>(
None => None,
});
- let pane_cursor_position = if picked_pane.is_some() {
- // TODO: Remove once cursor availability is encoded in the type
- // system
- Point::new(-1.0, -1.0)
+ let pane_cursor = if picked_pane.is_some() {
+ mouse::Cursor::Unavailable
} else {
- cursor_position
+ cursor
};
let mut render_picked_pane = None;
- for ((id, pane), layout) in contents.zip(layout.children()) {
+ let pane_in_edge = if picked_pane.is_some() {
+ cursor
+ .position()
+ .and_then(|cursor_position| in_edge(layout, cursor_position))
+ } else {
+ None
+ };
+
+ for ((id, pane), pane_layout) in contents.zip(layout.children()) {
match picked_pane {
Some((dragging, origin)) if id == dragging => {
- render_picked_pane = Some((pane, origin, layout));
+ render_picked_pane = Some((pane, origin, pane_layout));
+ }
+ Some((dragging, _)) if id != dragging => {
+ draw_pane(
+ pane,
+ renderer,
+ default_style,
+ pane_layout,
+ pane_cursor,
+ viewport,
+ );
+
+ if picked_pane.is_some() && pane_in_edge.is_none() {
+ if let Some(region) =
+ cursor.position().and_then(|cursor_position| {
+ layout_region(pane_layout, cursor_position)
+ })
+ {
+ let bounds = layout_region_bounds(pane_layout, region);
+ let hovered_region_style = theme.hovered_region(style);
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_radius: hovered_region_style
+ .border_radius,
+ border_width: hovered_region_style.border_width,
+ border_color: hovered_region_style.border_color,
+ },
+ theme.hovered_region(style).background,
+ );
+ }
+ }
}
_ => {
draw_pane(
pane,
renderer,
default_style,
- layout,
- pane_cursor_position,
+ pane_layout,
+ pane_cursor,
viewport,
);
}
}
}
- // Render picked pane last
- if let Some((pane, origin, layout)) = render_picked_pane {
- let bounds = layout.bounds();
-
- renderer.with_translation(
- cursor_position
- - Point::new(bounds.x + origin.x, bounds.y + origin.y),
- |renderer| {
- renderer.with_layer(bounds, |renderer| {
- draw_pane(
- pane,
- renderer,
- default_style,
- layout,
- pane_cursor_position,
- viewport,
- );
- });
+ if let Some(edge) = pane_in_edge {
+ let hovered_region_style = theme.hovered_region(style);
+ let bounds = edge_bounds(layout, edge);
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_radius: hovered_region_style.border_radius,
+ border_width: hovered_region_style.border_width,
+ border_color: hovered_region_style.border_color,
},
+ theme.hovered_region(style).background,
);
- };
+ }
- if let Some((axis, split_region, is_picked)) = picked_split {
- let highlight = if is_picked {
- theme.picked_split(style)
- } else {
- theme.hovered_split(style)
- };
+ // Render picked pane last
+ if let Some((pane, origin, layout)) = render_picked_pane {
+ if let Some(cursor_position) = cursor.position() {
+ let bounds = layout.bounds();
- if let Some(highlight) = highlight {
- renderer.fill_quad(
- renderer::Quad {
- bounds: match axis {
- Axis::Horizontal => Rectangle {
- x: split_region.x,
- y: (split_region.y
- + (split_region.height - highlight.width)
- / 2.0)
- .round(),
- width: split_region.width,
- height: highlight.width,
- },
- Axis::Vertical => Rectangle {
- x: (split_region.x
- + (split_region.width - highlight.width) / 2.0)
- .round(),
- y: split_region.y,
- width: highlight.width,
- height: split_region.height,
- },
- },
- border_radius: 0.0.into(),
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
+ renderer.with_translation(
+ cursor_position
+ - Point::new(bounds.x + origin.x, bounds.y + origin.y),
+ |renderer| {
+ renderer.with_layer(bounds, |renderer| {
+ draw_pane(
+ pane,
+ renderer,
+ default_style,
+ layout,
+ pane_cursor,
+ viewport,
+ );
+ });
},
- highlight.color,
);
}
}
+
+ if picked_pane.is_none() {
+ if let Some((axis, split_region, is_picked)) = picked_split {
+ let highlight = if is_picked {
+ theme.picked_split(style)
+ } else {
+ theme.hovered_split(style)
+ };
+
+ if let Some(highlight) = highlight {
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: match axis {
+ Axis::Horizontal => Rectangle {
+ x: split_region.x,
+ y: (split_region.y
+ + (split_region.height - highlight.width)
+ / 2.0)
+ .round(),
+ width: split_region.width,
+ height: highlight.width,
+ },
+ Axis::Vertical => Rectangle {
+ x: (split_region.x
+ + (split_region.width - highlight.width)
+ / 2.0)
+ .round(),
+ y: split_region.y,
+ width: highlight.width,
+ height: split_region.height,
+ },
+ },
+ border_radius: 0.0.into(),
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ highlight.color,
+ );
+ }
+ }
+ }
+}
+
+const THICKNESS_RATIO: f32 = 25.0;
+
+fn in_edge(layout: Layout<'_>, cursor: Point) -> Option<Edge> {
+ let bounds = layout.bounds();
+
+ let height_thickness = bounds.height / THICKNESS_RATIO;
+ let width_thickness = bounds.width / THICKNESS_RATIO;
+ let thickness = height_thickness.min(width_thickness);
+
+ if cursor.x > bounds.x && cursor.x < bounds.x + thickness {
+ Some(Edge::Left)
+ } else if cursor.x > bounds.x + bounds.width - thickness
+ && cursor.x < bounds.x + bounds.width
+ {
+ Some(Edge::Right)
+ } else if cursor.y > bounds.y && cursor.y < bounds.y + thickness {
+ Some(Edge::Top)
+ } else if cursor.y > bounds.y + bounds.height - thickness
+ && cursor.y < bounds.y + bounds.height
+ {
+ Some(Edge::Bottom)
+ } else {
+ None
+ }
+}
+
+fn edge_bounds(layout: Layout<'_>, edge: Edge) -> Rectangle {
+ let bounds = layout.bounds();
+
+ let height_thickness = bounds.height / THICKNESS_RATIO;
+ let width_thickness = bounds.width / THICKNESS_RATIO;
+ let thickness = height_thickness.min(width_thickness);
+
+ match edge {
+ Edge::Top => Rectangle {
+ height: thickness,
+ ..bounds
+ },
+ Edge::Left => Rectangle {
+ width: thickness,
+ ..bounds
+ },
+ Edge::Right => Rectangle {
+ x: bounds.x + bounds.width - thickness,
+ width: thickness,
+ ..bounds
+ },
+ Edge::Bottom => Rectangle {
+ y: bounds.y + bounds.height - thickness,
+ height: thickness,
+ ..bounds
+ },
+ }
+}
+
+fn layout_region_bounds(layout: Layout<'_>, region: Region) -> Rectangle {
+ let bounds = layout.bounds();
+
+ match region {
+ Region::Center => bounds,
+ Region::Edge(edge) => match edge {
+ Edge::Top => Rectangle {
+ height: bounds.height / 2.0,
+ ..bounds
+ },
+ Edge::Left => Rectangle {
+ width: bounds.width / 2.0,
+ ..bounds
+ },
+ Edge::Right => Rectangle {
+ x: bounds.x + bounds.width / 2.0,
+ width: bounds.width / 2.0,
+ ..bounds
+ },
+ Edge::Bottom => Rectangle {
+ y: bounds.y + bounds.height / 2.0,
+ height: bounds.height / 2.0,
+ ..bounds
+ },
+ },
+ }
}
/// An event produced during a drag and drop interaction of a [`PaneGrid`].
@@ -898,8 +1076,8 @@ pub enum DragEvent {
/// The picked [`Pane`].
pane: Pane,
- /// The [`Pane`] where the picked one was dropped on.
- target: Pane,
+ /// The [`Target`] where the picked [`Pane`] was dropped on.
+ target: Target,
},
/// A [`Pane`] was picked and then dropped outside of other [`Pane`]
@@ -910,6 +1088,38 @@ pub enum DragEvent {
},
}
+/// The [`Target`] area a pane can be dropped on.
+#[derive(Debug, Clone, Copy)]
+pub enum Target {
+ /// An [`Edge`] of the full [`PaneGrid`].
+ Edge(Edge),
+ /// A single [`Pane`] of the [`PaneGrid`].
+ Pane(Pane, Region),
+}
+
+/// The region of a [`Pane`].
+#[derive(Debug, Clone, Copy, Default)]
+pub enum Region {
+ /// Center region.
+ #[default]
+ Center,
+ /// Edge region.
+ Edge(Edge),
+}
+
+/// The edges of an area.
+#[derive(Debug, Clone, Copy)]
+pub enum Edge {
+ /// Top edge.
+ Top,
+ /// Left edge.
+ Left,
+ /// Right edge.
+ Right,
+ /// Bottom edge.
+ Bottom,
+}
+
/// An event produced during a resize interaction of a [`PaneGrid`].
#[derive(Debug, Clone, Copy)]
pub struct ResizeEvent {
diff --git a/native/src/widget/pane_grid/axis.rs b/widget/src/pane_grid/axis.rs
index 02bde064..a3049230 100644
--- a/native/src/widget/pane_grid/axis.rs
+++ b/widget/src/pane_grid/axis.rs
@@ -1,4 +1,4 @@
-use crate::Rectangle;
+use crate::core::Rectangle;
/// A fixed reference line for the measurement of coordinates.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
diff --git a/native/src/widget/pane_grid/configuration.rs b/widget/src/pane_grid/configuration.rs
index 7d68fb46..ddbc3bc2 100644
--- a/native/src/widget/pane_grid/configuration.rs
+++ b/widget/src/pane_grid/configuration.rs
@@ -1,4 +1,4 @@
-use crate::widget::pane_grid::Axis;
+use crate::pane_grid::Axis;
/// The arrangement of a [`PaneGrid`].
///
diff --git a/native/src/widget/pane_grid/content.rs b/widget/src/pane_grid/content.rs
index c9b0df07..c28ae6e3 100644
--- a/native/src/widget/pane_grid/content.rs
+++ b/widget/src/pane_grid/content.rs
@@ -1,20 +1,20 @@
-use crate::event::{self, Event};
-use crate::layout;
-use crate::mouse;
-use crate::overlay;
-use crate::renderer;
-use crate::widget::container;
-use crate::widget::pane_grid::{Draggable, TitleBar};
-use crate::widget::{self, Tree};
-use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
+use crate::container;
+use crate::core::event::{self, Event};
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::overlay;
+use crate::core::renderer;
+use crate::core::widget::{self, Tree};
+use crate::core::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
+use crate::pane_grid::{Draggable, TitleBar};
/// The content of a [`Pane`].
///
/// [`Pane`]: crate::widget::pane_grid::Pane
#[allow(missing_debug_implementations)]
-pub struct Content<'a, Message, Renderer>
+pub struct Content<'a, Message, Renderer = crate::Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: container::StyleSheet,
{
title_bar: Option<TitleBar<'a, Message, Renderer>>,
@@ -24,7 +24,7 @@ where
impl<'a, Message, Renderer> Content<'a, Message, Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: container::StyleSheet,
{
/// Creates a new [`Content`] with the provided body.
@@ -57,7 +57,7 @@ where
impl<'a, Message, Renderer> Content<'a, Message, Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: container::StyleSheet,
{
pub(super) fn state(&self) -> Tree {
@@ -95,7 +95,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
use container::StyleSheet;
@@ -113,7 +113,7 @@ where
let title_bar_layout = children.next().unwrap();
let body_layout = children.next().unwrap();
- let show_controls = bounds.contains(cursor_position);
+ let show_controls = cursor.is_over(bounds);
self.body.as_widget().draw(
&tree.children[0],
@@ -121,7 +121,7 @@ where
theme,
style,
body_layout,
- cursor_position,
+ cursor,
viewport,
);
@@ -131,7 +131,7 @@ where
theme,
style,
title_bar_layout,
- cursor_position,
+ cursor,
viewport,
show_controls,
);
@@ -142,7 +142,7 @@ where
theme,
style,
layout,
- cursor_position,
+ cursor,
viewport,
);
}
@@ -218,7 +218,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -233,7 +233,7 @@ where
&mut tree.children[1],
event.clone(),
children.next().unwrap(),
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -251,7 +251,7 @@ where
&mut tree.children[0],
event,
body_layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -265,42 +265,48 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
drag_enabled: bool,
) -> mouse::Interaction {
- let (body_layout, title_bar_interaction) =
- if let Some(title_bar) = &self.title_bar {
- let mut children = layout.children();
- let title_bar_layout = children.next().unwrap();
-
- let is_over_pick_area = title_bar
- .is_over_pick_area(title_bar_layout, cursor_position);
-
- if is_over_pick_area && drag_enabled {
- return mouse::Interaction::Grab;
- }
-
- let mouse_interaction = title_bar.mouse_interaction(
- &tree.children[1],
- title_bar_layout,
- cursor_position,
- viewport,
- renderer,
- );
+ let (body_layout, title_bar_interaction) = if let Some(title_bar) =
+ &self.title_bar
+ {
+ let mut children = layout.children();
+ let title_bar_layout = children.next().unwrap();
+
+ let is_over_pick_area = cursor
+ .position()
+ .map(|cursor_position| {
+ title_bar
+ .is_over_pick_area(title_bar_layout, cursor_position)
+ })
+ .unwrap_or_default();
+
+ if is_over_pick_area && drag_enabled {
+ return mouse::Interaction::Grab;
+ }
- (children.next().unwrap(), mouse_interaction)
- } else {
- (layout, mouse::Interaction::default())
- };
+ let mouse_interaction = title_bar.mouse_interaction(
+ &tree.children[1],
+ title_bar_layout,
+ cursor,
+ viewport,
+ renderer,
+ );
+
+ (children.next().unwrap(), mouse_interaction)
+ } else {
+ (layout, mouse::Interaction::default())
+ };
self.body
.as_widget()
.mouse_interaction(
&tree.children[0],
body_layout,
- cursor_position,
+ cursor,
viewport,
renderer,
)
@@ -342,7 +348,7 @@ where
impl<'a, Message, Renderer> Draggable for &Content<'a, Message, Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: container::StyleSheet,
{
fn can_be_dragged_at(
@@ -364,7 +370,7 @@ where
impl<'a, T, Message, Renderer> From<T> for Content<'a, Message, Renderer>
where
T: Into<Element<'a, Message, Renderer>>,
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: container::StyleSheet,
{
fn from(element: T) -> Self {
diff --git a/native/src/widget/pane_grid/direction.rs b/widget/src/pane_grid/direction.rs
index b31a8737..b31a8737 100644
--- a/native/src/widget/pane_grid/direction.rs
+++ b/widget/src/pane_grid/direction.rs
diff --git a/native/src/widget/pane_grid/draggable.rs b/widget/src/pane_grid/draggable.rs
index 6044871d..9d31feb5 100644
--- a/native/src/widget/pane_grid/draggable.rs
+++ b/widget/src/pane_grid/draggable.rs
@@ -1,12 +1,8 @@
-use crate::{Layout, Point};
+use crate::core::{Layout, Point};
/// A pane that can be dragged.
pub trait Draggable {
/// Returns whether the [`Draggable`] with the given [`Layout`] can be picked
/// at the provided cursor position.
- fn can_be_dragged_at(
- &self,
- layout: Layout<'_>,
- cursor_position: Point,
- ) -> bool;
+ fn can_be_dragged_at(&self, layout: Layout<'_>, cursor: Point) -> bool;
}
diff --git a/native/src/widget/pane_grid/node.rs b/widget/src/pane_grid/node.rs
index cc304b96..6de5920f 100644
--- a/native/src/widget/pane_grid/node.rs
+++ b/widget/src/pane_grid/node.rs
@@ -1,5 +1,5 @@
-use crate::widget::pane_grid::{Axis, Pane, Split};
-use crate::{Rectangle, Size};
+use crate::core::{Rectangle, Size};
+use crate::pane_grid::{Axis, Pane, Split};
use std::collections::BTreeMap;
@@ -120,6 +120,16 @@ impl Node {
};
}
+ pub(crate) fn split_inverse(&mut self, id: Split, axis: Axis, pane: Pane) {
+ *self = Node::Split {
+ id,
+ axis,
+ ratio: 0.5,
+ a: Box::new(Node::Pane(pane)),
+ b: Box::new(self.clone()),
+ };
+ }
+
pub(crate) fn update(&mut self, f: &impl Fn(&mut Node)) {
if let Node::Split { a, b, .. } = self {
a.update(f);
diff --git a/native/src/widget/pane_grid/pane.rs b/widget/src/pane_grid/pane.rs
index d6fbab83..d6fbab83 100644
--- a/native/src/widget/pane_grid/pane.rs
+++ b/widget/src/pane_grid/pane.rs
diff --git a/native/src/widget/pane_grid/split.rs b/widget/src/pane_grid/split.rs
index 8132272a..8132272a 100644
--- a/native/src/widget/pane_grid/split.rs
+++ b/widget/src/pane_grid/split.rs
diff --git a/native/src/widget/pane_grid/state.rs b/widget/src/pane_grid/state.rs
index c4ae0a0e..6fd15890 100644
--- a/native/src/widget/pane_grid/state.rs
+++ b/widget/src/pane_grid/state.rs
@@ -1,10 +1,10 @@
//! The state of a [`PaneGrid`].
//!
//! [`PaneGrid`]: crate::widget::PaneGrid
-use crate::widget::pane_grid::{
- Axis, Configuration, Direction, Node, Pane, Split,
+use crate::core::{Point, Size};
+use crate::pane_grid::{
+ Axis, Configuration, Direction, Edge, Node, Pane, Region, Split, Target,
};
-use crate::{Point, Size};
use std::collections::HashMap;
@@ -145,7 +145,55 @@ impl<T> State<T> {
pane: &Pane,
state: T,
) -> Option<(Pane, Split)> {
- let node = self.internal.layout.find(pane)?;
+ self.split_node(axis, Some(pane), state, false)
+ }
+
+ /// Split a target [`Pane`] with a given [`Pane`] on a given [`Region`].
+ ///
+ /// Panes will be swapped by default for [`Region::Center`].
+ pub fn split_with(&mut self, target: &Pane, pane: &Pane, region: Region) {
+ match region {
+ Region::Center => self.swap(pane, target),
+ Region::Edge(edge) => match edge {
+ Edge::Top => {
+ self.split_and_swap(Axis::Horizontal, target, pane, true)
+ }
+ Edge::Bottom => {
+ self.split_and_swap(Axis::Horizontal, target, pane, false)
+ }
+ Edge::Left => {
+ self.split_and_swap(Axis::Vertical, target, pane, true)
+ }
+ Edge::Right => {
+ self.split_and_swap(Axis::Vertical, target, pane, false)
+ }
+ },
+ }
+ }
+
+ /// Drops the given [`Pane`] into the provided [`Target`].
+ pub fn drop(&mut self, pane: &Pane, target: Target) {
+ match target {
+ Target::Edge(edge) => self.move_to_edge(pane, edge),
+ Target::Pane(target, region) => {
+ self.split_with(&target, pane, region)
+ }
+ }
+ }
+
+ fn split_node(
+ &mut self,
+ axis: Axis,
+ pane: Option<&Pane>,
+ state: T,
+ inverse: bool,
+ ) -> Option<(Pane, Split)> {
+ let node = if let Some(pane) = pane {
+ self.internal.layout.find(pane)?
+ } else {
+ // Major node
+ &mut self.internal.layout
+ };
let new_pane = {
self.internal.last_id = self.internal.last_id.checked_add(1)?;
@@ -159,7 +207,11 @@ impl<T> State<T> {
Split(self.internal.last_id)
};
- node.split(new_split, axis, new_pane);
+ if inverse {
+ node.split_inverse(new_split, axis, new_pane);
+ } else {
+ node.split(new_split, axis, new_pane);
+ }
let _ = self.panes.insert(new_pane, state);
let _ = self.maximized.take();
@@ -167,6 +219,51 @@ impl<T> State<T> {
Some((new_pane, new_split))
}
+ fn split_and_swap(
+ &mut self,
+ axis: Axis,
+ target: &Pane,
+ pane: &Pane,
+ swap: bool,
+ ) {
+ if let Some((state, _)) = self.close(pane) {
+ if let Some((new_pane, _)) = self.split(axis, target, state) {
+ if swap {
+ self.swap(target, &new_pane);
+ }
+ }
+ }
+ }
+
+ /// Move [`Pane`] to an [`Edge`] of the [`PaneGrid`].
+ pub fn move_to_edge(&mut self, pane: &Pane, edge: Edge) {
+ match edge {
+ Edge::Top => {
+ self.split_major_node_and_swap(Axis::Horizontal, pane, true)
+ }
+ Edge::Bottom => {
+ self.split_major_node_and_swap(Axis::Horizontal, pane, false)
+ }
+ Edge::Left => {
+ self.split_major_node_and_swap(Axis::Vertical, pane, true)
+ }
+ Edge::Right => {
+ self.split_major_node_and_swap(Axis::Vertical, pane, false)
+ }
+ }
+ }
+
+ fn split_major_node_and_swap(
+ &mut self,
+ axis: Axis,
+ pane: &Pane,
+ swap: bool,
+ ) {
+ if let Some((state, _)) = self.close(pane) {
+ let _ = self.split_node(axis, None, state, swap);
+ }
+ }
+
/// Swaps the position of the provided panes in the [`State`].
///
/// If you want to swap panes on drag and drop in your [`PaneGrid`], you
diff --git a/native/src/widget/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs
index 107078ef..2fe79f80 100644
--- a/native/src/widget/pane_grid/title_bar.rs
+++ b/widget/src/pane_grid/title_bar.rs
@@ -1,11 +1,11 @@
-use crate::event::{self, Event};
-use crate::layout;
-use crate::mouse;
-use crate::overlay;
-use crate::renderer;
-use crate::widget::container;
-use crate::widget::{self, Tree};
-use crate::{
+use crate::container;
+use crate::core::event::{self, Event};
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::overlay;
+use crate::core::renderer;
+use crate::core::widget::{self, Tree};
+use crate::core::{
Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size,
};
@@ -13,9 +13,9 @@ use crate::{
///
/// [`Pane`]: crate::widget::pane_grid::Pane
#[allow(missing_debug_implementations)]
-pub struct TitleBar<'a, Message, Renderer>
+pub struct TitleBar<'a, Message, Renderer = crate::Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: container::StyleSheet,
{
content: Element<'a, Message, Renderer>,
@@ -27,7 +27,7 @@ where
impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: container::StyleSheet,
{
/// Creates a new [`TitleBar`] with the given content.
@@ -84,7 +84,7 @@ where
impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: container::StyleSheet,
{
pub(super) fn state(&self) -> Tree {
@@ -122,7 +122,7 @@ where
theme: &Renderer::Theme,
inherited_style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
show_controls: bool,
) {
@@ -158,7 +158,7 @@ where
theme,
&inherited_style,
controls_layout,
- cursor_position,
+ cursor,
viewport,
);
}
@@ -171,7 +171,7 @@ where
theme,
&inherited_style,
title_layout,
- cursor_position,
+ cursor,
viewport,
);
}
@@ -300,7 +300,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -324,7 +324,7 @@ where
&mut tree.children[1],
event.clone(),
controls_layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -338,7 +338,7 @@ where
&mut tree.children[0],
event,
title_layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -354,7 +354,7 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
@@ -367,7 +367,7 @@ where
let title_interaction = self.content.as_widget().mouse_interaction(
&tree.children[0],
title_layout,
- cursor_position,
+ cursor,
viewport,
renderer,
);
@@ -377,7 +377,7 @@ where
let controls_interaction = controls.as_widget().mouse_interaction(
&tree.children[1],
controls_layout,
- cursor_position,
+ cursor,
viewport,
renderer,
);
diff --git a/native/src/widget/pick_list.rs b/widget/src/pick_list.rs
index 17528db4..832aae6b 100644
--- a/native/src/widget/pick_list.rs
+++ b/widget/src/pick_list.rs
@@ -1,28 +1,29 @@
//! Display a dropdown list of selectable values.
-use crate::alignment;
-use crate::event::{self, Event};
-use crate::keyboard;
-use crate::layout;
-use crate::mouse;
-use crate::overlay;
-use crate::overlay::menu::{self, Menu};
-use crate::renderer;
-use crate::text::{self, Text};
-use crate::touch;
-use crate::widget::container;
-use crate::widget::scrollable;
-use crate::widget::tree::{self, Tree};
-use crate::{
- Clipboard, Element, Layout, Length, Padding, Pixels, Point, Rectangle,
- Shell, Size, Widget,
+use crate::container;
+use crate::core::alignment;
+use crate::core::event::{self, Event};
+use crate::core::keyboard;
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::overlay;
+use crate::core::renderer;
+use crate::core::text::{self, Text};
+use crate::core::touch;
+use crate::core::widget::tree::{self, Tree};
+use crate::core::{
+ Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell,
+ Size, Widget,
};
+use crate::overlay::menu::{self, Menu};
+use crate::scrollable;
+
use std::borrow::Cow;
-pub use iced_style::pick_list::{Appearance, StyleSheet};
+pub use crate::style::pick_list::{Appearance, StyleSheet};
/// A widget for selecting a single value from a list of options.
#[allow(missing_debug_implementations)]
-pub struct PickList<'a, T, Message, Renderer>
+pub struct PickList<'a, T, Message, Renderer = crate::Renderer>
where
[T]: ToOwned<Owned = Vec<T>>,
Renderer: text::Renderer,
@@ -35,7 +36,9 @@ where
width: Length,
padding: Padding,
text_size: Option<f32>,
- font: Renderer::Font,
+ text_line_height: text::LineHeight,
+ text_shaping: text::Shaping,
+ font: Option<Renderer::Font>,
handle: Handle<Renderer::Font>,
style: <Renderer::Theme as StyleSheet>::Style,
}
@@ -70,7 +73,9 @@ where
width: Length::Shrink,
padding: Self::DEFAULT_PADDING,
text_size: None,
- font: Default::default(),
+ text_line_height: text::LineHeight::default(),
+ text_shaping: text::Shaping::Basic,
+ font: None,
handle: Default::default(),
style: Default::default(),
}
@@ -100,9 +105,24 @@ where
self
}
+ /// Sets the text [`LineHeight`] of the [`PickList`].
+ pub fn text_line_height(
+ mut self,
+ line_height: impl Into<text::LineHeight>,
+ ) -> Self {
+ self.text_line_height = line_height.into();
+ self
+ }
+
+ /// Sets the [`text::Shaping`] strategy of the [`PickList`].
+ pub fn text_shaping(mut self, shaping: text::Shaping) -> Self {
+ self.text_shaping = shaping;
+ self
+ }
+
/// Sets the font of the [`PickList`].
- pub fn font(mut self, font: Renderer::Font) -> Self {
- self.font = font;
+ pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
+ self.font = Some(font.into());
self
}
@@ -137,11 +157,11 @@ where
From<<Renderer::Theme as StyleSheet>::Style>,
{
fn tag(&self) -> tree::Tag {
- tree::Tag::of::<State<T>>()
+ tree::Tag::of::<State>()
}
fn state(&self) -> tree::State {
- tree::State::new(State::<T>::new())
+ tree::State::new(State::new())
}
fn width(&self) -> Length {
@@ -163,7 +183,9 @@ where
self.width,
self.padding,
self.text_size,
- &self.font,
+ self.text_line_height,
+ self.text_shaping,
+ self.font,
self.placeholder.as_deref(),
&self.options,
)
@@ -174,7 +196,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -182,12 +204,12 @@ where
update(
event,
layout,
- cursor_position,
+ cursor,
shell,
self.on_selected.as_ref(),
self.selected.as_ref(),
&self.options,
- || tree.state.downcast_mut::<State<T>>(),
+ || tree.state.downcast_mut::<State>(),
)
}
@@ -195,11 +217,11 @@ where
&self,
_tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- mouse_interaction(layout, cursor_position)
+ mouse_interaction(layout, cursor)
}
fn draw(
@@ -209,22 +231,25 @@ where
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
+ let font = self.font.unwrap_or_else(|| renderer.default_font());
draw(
renderer,
theme,
layout,
- cursor_position,
+ cursor,
self.padding,
self.text_size,
- &self.font,
+ self.text_line_height,
+ self.text_shaping,
+ font,
self.placeholder.as_deref(),
self.selected.as_ref(),
&self.handle,
&self.style,
- || tree.state.downcast_ref::<State<T>>(),
+ || tree.state.downcast_ref::<State>(),
)
}
@@ -232,17 +257,19 @@ where
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
- _renderer: &Renderer,
+ renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
- let state = tree.state.downcast_mut::<State<T>>();
+ let state = tree.state.downcast_mut::<State>();
overlay(
layout,
state,
self.padding,
self.text_size,
- self.font.clone(),
+ self.text_shaping,
+ self.font.unwrap_or_else(|| renderer.default_font()),
&self.options,
+ &self.on_selected,
self.style.clone(),
)
}
@@ -269,15 +296,14 @@ where
/// The local state of a [`PickList`].
#[derive(Debug)]
-pub struct State<T> {
+pub struct State {
menu: menu::State,
keyboard_modifiers: keyboard::Modifiers,
is_open: bool,
hovered_option: Option<usize>,
- last_selection: Option<T>,
}
-impl<T> State<T> {
+impl State {
/// Creates a new [`State`] for a [`PickList`].
pub fn new() -> Self {
Self {
@@ -285,12 +311,11 @@ impl<T> State<T> {
keyboard_modifiers: keyboard::Modifiers::default(),
is_open: bool::default(),
hovered_option: Option::default(),
- last_selection: Option::default(),
}
}
}
-impl<T> Default for State<T> {
+impl Default for State {
fn default() -> Self {
Self::new()
}
@@ -334,6 +359,10 @@ pub struct Icon<Font> {
pub code_point: char,
/// Font size of the content.
pub size: Option<f32>,
+ /// Line height of the content.
+ pub line_height: text::LineHeight,
+ /// The shaping strategy of the icon.
+ pub shaping: text::Shaping,
}
/// Computes the layout of a [`PickList`].
@@ -343,7 +372,9 @@ pub fn layout<Renderer, T>(
width: Length,
padding: Padding,
text_size: Option<f32>,
- font: &Renderer::Font,
+ text_line_height: text::LineHeight,
+ text_shaping: text::Shaping,
+ font: Option<Renderer::Font>,
placeholder: Option<&str>,
options: &[T],
) -> layout::Node
@@ -359,11 +390,11 @@ where
let max_width = match width {
Length::Shrink => {
let measure = |label: &str| -> f32 {
- let (width, _) = renderer.measure(
+ let width = renderer.measure_width(
label,
text_size,
- font.clone(),
- Size::new(f32::INFINITY, f32::INFINITY),
+ font.unwrap_or_else(|| renderer.default_font()),
+ text_shaping,
);
width.round()
@@ -383,8 +414,10 @@ where
};
let size = {
- let intrinsic =
- Size::new(max_width + text_size + padding.left, text_size);
+ let intrinsic = Size::new(
+ max_width + text_size + padding.left,
+ f32::from(text_line_height.to_absolute(Pixels(text_size))),
+ );
limits.resolve(intrinsic).pad(padding)
};
@@ -397,12 +430,12 @@ where
pub fn update<'a, T, Message>(
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
shell: &mut Shell<'_, Message>,
on_selected: &dyn Fn(T) -> Message,
selected: Option<&T>,
options: &[T],
- state: impl FnOnce() -> &'a mut State<T>,
+ state: impl FnOnce() -> &'a mut State,
) -> event::Status
where
T: PartialEq + Clone + 'a,
@@ -412,13 +445,13 @@ where
| Event::Touch(touch::Event::FingerPressed { .. }) => {
let state = state();
- let event_status = if state.is_open {
+ if state.is_open {
// Event wasn't processed by overlay, so cursor was clicked either outside it's
// bounds or on the drop-down, either way we close the overlay.
state.is_open = false;
event::Status::Captured
- } else if layout.bounds().contains(cursor_position) {
+ } else if cursor.is_over(layout.bounds()) {
state.is_open = true;
state.hovered_option =
options.iter().position(|option| Some(option) == selected);
@@ -426,16 +459,6 @@ where
event::Status::Captured
} else {
event::Status::Ignored
- };
-
- if let Some(last_selection) = state.last_selection.take() {
- shell.publish((on_selected)(last_selection));
-
- state.is_open = false;
-
- event::Status::Captured
- } else {
- event_status
}
}
Event::Mouse(mouse::Event::WheelScrolled {
@@ -444,7 +467,7 @@ where
let state = state();
if state.keyboard_modifiers.command()
- && layout.bounds().contains(cursor_position)
+ && cursor.is_over(layout.bounds())
&& !state.is_open
{
fn find_next<'a, T: PartialEq>(
@@ -495,10 +518,10 @@ where
/// Returns the current [`mouse::Interaction`] of a [`PickList`].
pub fn mouse_interaction(
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) -> mouse::Interaction {
let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(bounds);
if is_mouse_over {
mouse::Interaction::Pointer
@@ -510,11 +533,13 @@ pub fn mouse_interaction(
/// Returns the current overlay of a [`PickList`].
pub fn overlay<'a, T, Message, Renderer>(
layout: Layout<'_>,
- state: &'a mut State<T>,
+ state: &'a mut State,
padding: Padding,
text_size: Option<f32>,
+ text_shaping: text::Shaping,
font: Renderer::Font,
options: &'a [T],
+ on_selected: &'a dyn Fn(T) -> Message,
style: <Renderer::Theme as StyleSheet>::Style,
) -> Option<overlay::Element<'a, Message, Renderer>>
where
@@ -535,11 +560,16 @@ where
&mut state.menu,
options,
&mut state.hovered_option,
- &mut state.last_selection,
+ |option| {
+ state.is_open = false;
+
+ (on_selected)(option)
+ },
)
.width(bounds.width)
.padding(padding)
.font(font)
+ .text_shaping(text_shaping)
.style(style);
if let Some(text_size) = text_size {
@@ -557,22 +587,24 @@ pub fn draw<'a, T, Renderer>(
renderer: &mut Renderer,
theme: &Renderer::Theme,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
padding: Padding,
text_size: Option<f32>,
- font: &Renderer::Font,
+ text_line_height: text::LineHeight,
+ text_shaping: text::Shaping,
+ font: Renderer::Font,
placeholder: Option<&str>,
selected: Option<&T>,
handle: &Handle<Renderer::Font>,
style: &<Renderer::Theme as StyleSheet>::Style,
- state: impl FnOnce() -> &'a State<T>,
+ state: impl FnOnce() -> &'a State,
) where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
T: ToString + 'a,
{
let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(bounds);
let is_selected = selected.is_some();
let style = if is_mouse_over {
@@ -586,46 +618,66 @@ pub fn draw<'a, T, Renderer>(
bounds,
border_color: style.border_color,
border_width: style.border_width,
- border_radius: style.border_radius.into(),
+ border_radius: style.border_radius,
},
style.background,
);
let handle = match handle {
- Handle::Arrow { size } => {
- Some((Renderer::ICON_FONT, Renderer::ARROW_DOWN_ICON, *size))
- }
+ Handle::Arrow { size } => Some((
+ Renderer::ICON_FONT,
+ Renderer::ARROW_DOWN_ICON,
+ *size,
+ text::LineHeight::default(),
+ text::Shaping::Basic,
+ )),
Handle::Static(Icon {
font,
code_point,
size,
- }) => Some((font.clone(), *code_point, *size)),
+ line_height,
+ shaping,
+ }) => Some((*font, *code_point, *size, *line_height, *shaping)),
Handle::Dynamic { open, closed } => {
if state().is_open {
- Some((open.font.clone(), open.code_point, open.size))
+ Some((
+ open.font,
+ open.code_point,
+ open.size,
+ open.line_height,
+ open.shaping,
+ ))
} else {
- Some((closed.font.clone(), closed.code_point, closed.size))
+ Some((
+ closed.font,
+ closed.code_point,
+ closed.size,
+ closed.line_height,
+ closed.shaping,
+ ))
}
}
Handle::None => None,
};
- if let Some((font, code_point, size)) = handle {
+ if let Some((font, code_point, size, line_height, shaping)) = handle {
let size = size.unwrap_or_else(|| renderer.default_size());
renderer.fill_text(Text {
content: &code_point.to_string(),
size,
+ line_height,
font,
color: style.handle_color,
bounds: Rectangle {
x: bounds.x + bounds.width - padding.horizontal(),
- y: bounds.center_y() - size / 2.0,
- height: size,
+ y: bounds.center_y(),
+ height: f32::from(line_height.to_absolute(Pixels(size))),
..bounds
},
horizontal_alignment: alignment::Horizontal::Right,
- vertical_alignment: alignment::Vertical::Top,
+ vertical_alignment: alignment::Vertical::Center,
+ shaping,
});
}
@@ -637,7 +689,8 @@ pub fn draw<'a, T, Renderer>(
renderer.fill_text(Text {
content: label,
size: text_size,
- font: font.clone(),
+ line_height: text_line_height,
+ font,
color: if is_selected {
style.text_color
} else {
@@ -645,12 +698,15 @@ pub fn draw<'a, T, Renderer>(
},
bounds: Rectangle {
x: bounds.x + padding.left,
- y: bounds.center_y() - text_size / 2.0,
+ y: bounds.center_y(),
width: bounds.width - padding.horizontal(),
- height: text_size,
+ height: f32::from(
+ text_line_height.to_absolute(Pixels(text_size)),
+ ),
},
horizontal_alignment: alignment::Horizontal::Left,
- vertical_alignment: alignment::Vertical::Top,
+ vertical_alignment: alignment::Vertical::Center,
+ shaping: text_shaping,
});
}
}
diff --git a/native/src/widget/progress_bar.rs b/widget/src/progress_bar.rs
index dd46fa76..37c6bc72 100644
--- a/native/src/widget/progress_bar.rs
+++ b/widget/src/progress_bar.rs
@@ -1,8 +1,9 @@
//! Provide progress feedback to your users.
-use crate::layout;
-use crate::renderer;
-use crate::widget::Tree;
-use crate::{Color, Element, Layout, Length, Point, Rectangle, Size, Widget};
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::renderer;
+use crate::core::widget::Tree;
+use crate::core::{Color, Element, Layout, Length, Rectangle, Size, Widget};
use std::ops::RangeInclusive;
@@ -11,8 +12,10 @@ pub use iced_style::progress_bar::{Appearance, StyleSheet};
/// A bar that displays progress.
///
/// # Example
-/// ```
-/// # type ProgressBar = iced_native::widget::ProgressBar<iced_native::renderer::Null>;
+/// ```no_run
+/// # type ProgressBar =
+/// # iced_widget::ProgressBar<iced_widget::renderer::Renderer<iced_widget::style::Theme>>;
+/// #
/// let value = 50.0;
///
/// ProgressBar::new(0.0..=100.0, value);
@@ -20,9 +23,9 @@ pub use iced_style::progress_bar::{Appearance, StyleSheet};
///
/// ![Progress bar drawn with `iced_wgpu`](https://user-images.githubusercontent.com/18618951/71662391-a316c200-2d51-11ea-9cef-52758cab85e3.png)
#[allow(missing_debug_implementations)]
-pub struct ProgressBar<Renderer>
+pub struct ProgressBar<Renderer = crate::Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
range: RangeInclusive<f32>,
@@ -34,7 +37,7 @@ where
impl<Renderer> ProgressBar<Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
/// The default height of a [`ProgressBar`].
@@ -79,7 +82,7 @@ where
impl<Message, Renderer> Widget<Message, Renderer> for ProgressBar<Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
fn width(&self) -> Length {
@@ -111,7 +114,7 @@ where
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
let bounds = layout.bounds();
@@ -129,7 +132,7 @@ where
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle { ..bounds },
- border_radius: style.border_radius.into(),
+ border_radius: style.border_radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
@@ -143,7 +146,7 @@ where
width: active_progress_width,
..bounds
},
- border_radius: style.border_radius.into(),
+ border_radius: style.border_radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
@@ -157,7 +160,7 @@ impl<'a, Message, Renderer> From<ProgressBar<Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a,
- Renderer: 'a + crate::Renderer,
+ Renderer: 'a + crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
fn from(
diff --git a/native/src/widget/radio.rs b/widget/src/radio.rs
index 9daddfbc..5b883147 100644
--- a/native/src/widget/radio.rs
+++ b/widget/src/radio.rs
@@ -1,30 +1,34 @@
//! Create choices using radio buttons.
-use crate::alignment;
-use crate::event::{self, Event};
-use crate::layout;
-use crate::mouse;
-use crate::renderer;
-use crate::text;
-use crate::touch;
-use crate::widget::{self, Row, Text, Tree};
-use crate::{
- Alignment, Clipboard, Color, Element, Layout, Length, Pixels, Point,
- Rectangle, Shell, Widget,
+use crate::core::alignment;
+use crate::core::event::{self, Event};
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::renderer;
+use crate::core::text;
+use crate::core::touch;
+use crate::core::widget::Tree;
+use crate::core::{
+ Alignment, Clipboard, Color, Element, Layout, Length, Pixels, Rectangle,
+ Shell, Widget,
};
+use crate::{Row, Text};
pub use iced_style::radio::{Appearance, StyleSheet};
/// A circular button representing a choice.
///
/// # Example
-/// ```
+/// ```no_run
/// # type Radio<Message> =
-/// # iced_native::widget::Radio<Message, iced_native::renderer::Null>;
+/// # iced_widget::Radio<Message, iced_widget::renderer::Renderer<iced_widget::style::Theme>>;
/// #
+/// # use iced_widget::column;
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// pub enum Choice {
/// A,
/// B,
+/// C,
+/// All,
/// }
///
/// #[derive(Debug, Clone, Copy)]
@@ -34,14 +38,38 @@ pub use iced_style::radio::{Appearance, StyleSheet};
///
/// let selected_choice = Some(Choice::A);
///
-/// Radio::new(Choice::A, "This is A", selected_choice, Message::RadioSelected);
+/// let a = Radio::new(
+/// "A",
+/// Choice::A,
+/// selected_choice,
+/// Message::RadioSelected,
+/// );
///
-/// Radio::new(Choice::B, "This is B", selected_choice, Message::RadioSelected);
-/// ```
+/// let b = Radio::new(
+/// "B",
+/// Choice::B,
+/// selected_choice,
+/// Message::RadioSelected,
+/// );
+///
+/// let c = Radio::new(
+/// "C",
+/// Choice::C,
+/// selected_choice,
+/// Message::RadioSelected,
+/// );
+///
+/// let all = Radio::new(
+/// "All of the above",
+/// Choice::All,
+/// selected_choice,
+/// Message::RadioSelected
+/// );
///
-/// ![Radio buttons drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/radio.png?raw=true)
+/// let content = column![a, b, c, all];
+/// ```
#[allow(missing_debug_implementations)]
-pub struct Radio<Message, Renderer>
+pub struct Radio<Message, Renderer = crate::Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
@@ -53,7 +81,9 @@ where
size: f32,
spacing: f32,
text_size: Option<f32>,
- font: Renderer::Font,
+ text_line_height: text::LineHeight,
+ text_shaping: text::Shaping,
+ font: Option<Renderer::Font>,
style: <Renderer::Theme as StyleSheet>::Style,
}
@@ -78,8 +108,8 @@ where
/// * a function that will be called when the [`Radio`] is selected. It
/// receives the value of the radio and must produce a `Message`.
pub fn new<F, V>(
- value: V,
label: impl Into<String>,
+ value: V,
selected: Option<V>,
f: F,
) -> Self
@@ -95,7 +125,9 @@ where
size: Self::DEFAULT_SIZE,
spacing: Self::DEFAULT_SPACING, //15
text_size: None,
- font: Default::default(),
+ text_line_height: text::LineHeight::default(),
+ text_shaping: text::Shaping::Basic,
+ font: None,
style: Default::default(),
}
}
@@ -124,9 +156,24 @@ where
self
}
+ /// Sets the text [`LineHeight`] of the [`Radio`] button.
+ pub fn text_line_height(
+ mut self,
+ line_height: impl Into<text::LineHeight>,
+ ) -> Self {
+ self.text_line_height = line_height.into();
+ self
+ }
+
+ /// Sets the [`text::Shaping`] strategy of the [`Radio`] button.
+ pub fn text_shaping(mut self, shaping: text::Shaping) -> Self {
+ self.text_shaping = shaping;
+ self
+ }
+
/// Sets the text font of the [`Radio`] button.
- pub fn font(mut self, font: Renderer::Font) -> Self {
- self.font = font;
+ pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
+ self.font = Some(font.into());
self
}
@@ -144,7 +191,7 @@ impl<Message, Renderer> Widget<Message, Renderer> for Radio<Message, Renderer>
where
Message: Clone,
Renderer: text::Renderer,
- Renderer::Theme: StyleSheet + widget::text::StyleSheet,
+ Renderer::Theme: StyleSheet + crate::text::StyleSheet,
{
fn width(&self) -> Length {
self.width
@@ -164,9 +211,16 @@ where
.spacing(self.spacing)
.align_items(Alignment::Center)
.push(Row::new().width(self.size).height(self.size))
- .push(Text::new(&self.label).width(self.width).size(
- self.text_size.unwrap_or_else(|| renderer.default_size()),
- ))
+ .push(
+ Text::new(&self.label)
+ .width(self.width)
+ .size(
+ self.text_size
+ .unwrap_or_else(|| renderer.default_size()),
+ )
+ .line_height(self.text_line_height)
+ .shaping(self.text_shaping),
+ )
.layout(renderer, limits)
}
@@ -175,7 +229,7 @@ where
_state: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -183,7 +237,7 @@ where
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
- if layout.bounds().contains(cursor_position) {
+ if cursor.is_over(layout.bounds()) {
shell.publish(self.on_click.clone());
return event::Status::Captured;
@@ -199,11 +253,11 @@ where
&self,
_state: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- if layout.bounds().contains(cursor_position) {
+ if cursor.is_over(layout.bounds()) {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
@@ -217,11 +271,10 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
- let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(layout.bounds());
let mut children = layout.children();
@@ -269,18 +322,20 @@ where
{
let label_layout = children.next().unwrap();
- widget::text::draw(
+ crate::text::draw(
renderer,
style,
label_layout,
&self.label,
self.text_size,
- self.font.clone(),
- widget::text::Appearance {
+ self.text_line_height,
+ self.font,
+ crate::text::Appearance {
color: custom_style.text_color,
},
alignment::Horizontal::Left,
alignment::Vertical::Center,
+ self.text_shaping,
);
}
}
@@ -291,7 +346,7 @@ impl<'a, Message, Renderer> From<Radio<Message, Renderer>>
where
Message: 'a + Clone,
Renderer: 'a + text::Renderer,
- Renderer::Theme: StyleSheet + widget::text::StyleSheet,
+ Renderer::Theme: StyleSheet + crate::text::StyleSheet,
{
fn from(radio: Radio<Message, Renderer>) -> Element<'a, Message, Renderer> {
Element::new(radio)
diff --git a/native/src/widget/row.rs b/widget/src/row.rs
index 286c1c2d..1db22416 100644
--- a/native/src/widget/row.rs
+++ b/widget/src/row.rs
@@ -1,18 +1,18 @@
//! Distribute content horizontally.
-use crate::event::{self, Event};
-use crate::layout::{self, Layout};
-use crate::mouse;
-use crate::overlay;
-use crate::renderer;
-use crate::widget::{Operation, Tree};
-use crate::{
- Alignment, Clipboard, Element, Length, Padding, Pixels, Point, Rectangle,
- Shell, Widget,
+use crate::core::event::{self, Event};
+use crate::core::layout::{self, Layout};
+use crate::core::mouse;
+use crate::core::overlay;
+use crate::core::renderer;
+use crate::core::widget::{Operation, Tree};
+use crate::core::{
+ Alignment, Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell,
+ Widget,
};
/// A container that distributes its contents horizontally.
#[allow(missing_debug_implementations)]
-pub struct Row<'a, Message, Renderer> {
+pub struct Row<'a, Message, Renderer = crate::Renderer> {
spacing: f32,
padding: Padding,
width: Length,
@@ -94,7 +94,7 @@ impl<'a, Message, Renderer> Default for Row<'a, Message, Renderer> {
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Row<'a, Message, Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
{
fn children(&self) -> Vec<Tree> {
self.children.iter().map(Tree::new).collect()
@@ -155,7 +155,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -169,7 +169,7 @@ where
state,
event.clone(),
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -182,7 +182,7 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
@@ -192,11 +192,7 @@ where
.zip(layout.children())
.map(|((child, state), layout)| {
child.as_widget().mouse_interaction(
- state,
- layout,
- cursor_position,
- viewport,
- renderer,
+ state, layout, cursor, viewport, renderer,
)
})
.max()
@@ -210,7 +206,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
for ((child, state), layout) in self
@@ -219,15 +215,9 @@ where
.zip(&tree.children)
.zip(layout.children())
{
- child.as_widget().draw(
- state,
- renderer,
- theme,
- style,
- layout,
- cursor_position,
- viewport,
- );
+ child
+ .as_widget()
+ .draw(state, renderer, theme, style, layout, cursor, viewport);
}
}
@@ -245,7 +235,7 @@ impl<'a, Message, Renderer> From<Row<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a,
- Renderer: crate::Renderer + 'a,
+ Renderer: crate::core::Renderer + 'a,
{
fn from(row: Row<'a, Message, Renderer>) -> Self {
Self::new(row)
diff --git a/native/src/widget/rule.rs b/widget/src/rule.rs
index 1ab6a0d3..d703e6ae 100644
--- a/native/src/widget/rule.rs
+++ b/widget/src/rule.rs
@@ -1,18 +1,19 @@
//! Display a horizontal or vertical rule for dividing content.
-use crate::layout;
-use crate::renderer;
-use crate::widget::Tree;
-use crate::{
- Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget,
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::renderer;
+use crate::core::widget::Tree;
+use crate::core::{
+ Color, Element, Layout, Length, Pixels, Rectangle, Size, Widget,
};
-pub use iced_style::rule::{Appearance, FillMode, StyleSheet};
+pub use crate::style::rule::{Appearance, FillMode, StyleSheet};
/// Display a horizontal or vertical rule for dividing content.
#[allow(missing_debug_implementations)]
-pub struct Rule<Renderer>
+pub struct Rule<Renderer = crate::Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
width: Length,
@@ -23,7 +24,7 @@ where
impl<Renderer> Rule<Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
/// Creates a horizontal [`Rule`] with the given height.
@@ -58,7 +59,7 @@ where
impl<Message, Renderer> Widget<Message, Renderer> for Rule<Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
fn width(&self) -> Length {
@@ -86,7 +87,7 @@ where
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
let bounds = layout.bounds();
@@ -125,7 +126,7 @@ where
renderer.fill_quad(
renderer::Quad {
bounds,
- border_radius: style.radius.into(),
+ border_radius: style.radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
@@ -138,7 +139,7 @@ impl<'a, Message, Renderer> From<Rule<Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a,
- Renderer: 'a + crate::Renderer,
+ Renderer: 'a + crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
fn from(rule: Rule<Renderer>) -> Element<'a, Message, Renderer> {
diff --git a/native/src/widget/scrollable.rs b/widget/src/scrollable.rs
index c1df8c39..88746ac4 100644
--- a/native/src/widget/scrollable.rs
+++ b/widget/src/scrollable.rs
@@ -1,58 +1,52 @@
//! Navigate an endless amount of content with a scrollbar.
-use crate::event::{self, Event};
-use crate::keyboard;
-use crate::layout;
-use crate::mouse;
-use crate::overlay;
-use crate::renderer;
-use crate::touch;
-use crate::widget;
-use crate::widget::operation::{self, Operation};
-use crate::widget::tree::{self, Tree};
-use crate::{
- Background, Clipboard, Color, Command, Element, Layout, Length, Pixels,
- Point, Rectangle, Shell, Size, Vector, Widget,
+use crate::core::event::{self, Event};
+use crate::core::keyboard;
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::overlay;
+use crate::core::renderer;
+use crate::core::touch;
+use crate::core::widget;
+use crate::core::widget::operation::{self, Operation};
+use crate::core::widget::tree::{self, Tree};
+use crate::core::{
+ Background, Clipboard, Color, Element, Layout, Length, Pixels, Point,
+ Rectangle, Shell, Size, Vector, Widget,
};
+use crate::runtime::Command;
-pub use iced_style::scrollable::StyleSheet;
-pub use operation::scrollable::RelativeOffset;
-
-pub mod style {
- //! The styles of a [`Scrollable`].
- //!
- //! [`Scrollable`]: crate::widget::Scrollable
- pub use iced_style::scrollable::{Scrollbar, Scroller};
-}
+pub use crate::style::scrollable::{Scrollbar, Scroller, StyleSheet};
+pub use operation::scrollable::{AbsoluteOffset, RelativeOffset};
/// A widget that can vertically display an infinite amount of content with a
/// scrollbar.
#[allow(missing_debug_implementations)]
-pub struct Scrollable<'a, Message, Renderer>
+pub struct Scrollable<'a, Message, Renderer = crate::Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
id: Option<Id>,
+ width: Length,
height: Length,
- vertical: Properties,
- horizontal: Option<Properties>,
+ direction: Direction,
content: Element<'a, Message, Renderer>,
- on_scroll: Option<Box<dyn Fn(RelativeOffset) -> Message + 'a>>,
+ on_scroll: Option<Box<dyn Fn(Viewport) -> Message + 'a>>,
style: <Renderer::Theme as StyleSheet>::Style,
}
impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
/// Creates a new [`Scrollable`].
pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self {
Scrollable {
id: None,
+ width: Length::Shrink,
height: Length::Shrink,
- vertical: Properties::default(),
- horizontal: None,
+ direction: Default::default(),
content: content.into(),
on_scroll: None,
style: Default::default(),
@@ -65,32 +59,28 @@ where
self
}
- /// Sets the height of the [`Scrollable`].
- pub fn height(mut self, height: impl Into<Length>) -> Self {
- self.height = height.into();
+ /// Sets the width of the [`Scrollable`].
+ pub fn width(mut self, width: impl Into<Length>) -> Self {
+ self.width = width.into();
self
}
- /// Configures the vertical scrollbar of the [`Scrollable`] .
- pub fn vertical_scroll(mut self, properties: Properties) -> Self {
- self.vertical = properties;
+ /// Sets the height of the [`Scrollable`].
+ pub fn height(mut self, height: impl Into<Length>) -> Self {
+ self.height = height.into();
self
}
- /// Configures the horizontal scrollbar of the [`Scrollable`] .
- pub fn horizontal_scroll(mut self, properties: Properties) -> Self {
- self.horizontal = Some(properties);
+ /// Sets the [`Direction`] of the [`Scrollable`] .
+ pub fn direction(mut self, direction: Direction) -> Self {
+ self.direction = direction;
self
}
/// Sets a function to call when the [`Scrollable`] is scrolled.
///
- /// The function takes the new relative x & y offset of the [`Scrollable`]
- /// (e.g. `0` means beginning, while `1` means end).
- pub fn on_scroll(
- mut self,
- f: impl Fn(RelativeOffset) -> Message + 'a,
- ) -> Self {
+ /// The function takes the [`Viewport`] of the [`Scrollable`]
+ pub fn on_scroll(mut self, f: impl Fn(Viewport) -> Message + 'a) -> Self {
self.on_scroll = Some(Box::new(f));
self
}
@@ -105,12 +95,55 @@ where
}
}
+/// The direction of [`Scrollable`].
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum Direction {
+ /// Vertical scrolling
+ Vertical(Properties),
+ /// Horizontal scrolling
+ Horizontal(Properties),
+ /// Both vertical and horizontal scrolling
+ Both {
+ /// The properties of the vertical scrollbar.
+ vertical: Properties,
+ /// The properties of the horizontal scrollbar.
+ horizontal: Properties,
+ },
+}
+
+impl Direction {
+ /// Returns the [`Properties`] of the horizontal scrollbar, if any.
+ pub fn horizontal(&self) -> Option<&Properties> {
+ match self {
+ Self::Horizontal(properties) => Some(properties),
+ Self::Both { horizontal, .. } => Some(horizontal),
+ _ => None,
+ }
+ }
+
+ /// Returns the [`Properties`] of the vertical scrollbar, if any.
+ pub fn vertical(&self) -> Option<&Properties> {
+ match self {
+ Self::Vertical(properties) => Some(properties),
+ Self::Both { vertical, .. } => Some(vertical),
+ _ => None,
+ }
+ }
+}
+
+impl Default for Direction {
+ fn default() -> Self {
+ Self::Vertical(Properties::default())
+ }
+}
+
/// Properties of a scrollbar within a [`Scrollable`].
-#[derive(Debug)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Properties {
width: f32,
margin: f32,
scroller_width: f32,
+ alignment: Alignment,
}
impl Default for Properties {
@@ -119,6 +152,7 @@ impl Default for Properties {
width: 10.0,
margin: 0.0,
scroller_width: 10.0,
+ alignment: Alignment::Start,
}
}
}
@@ -130,9 +164,8 @@ impl Properties {
}
/// Sets the scrollbar width of the [`Scrollable`] .
- /// Silently enforces a minimum width of 1.
pub fn width(mut self, width: impl Into<Pixels>) -> Self {
- self.width = width.into().0.max(1.0);
+ self.width = width.into().0.max(0.0);
self
}
@@ -143,17 +176,32 @@ impl Properties {
}
/// Sets the scroller width of the [`Scrollable`] .
- /// Silently enforces a minimum width of 1.
pub fn scroller_width(mut self, scroller_width: impl Into<Pixels>) -> Self {
- self.scroller_width = scroller_width.into().0.max(1.0);
+ self.scroller_width = scroller_width.into().0.max(0.0);
+ self
+ }
+
+ /// Sets the alignment of the [`Scrollable`] .
+ pub fn alignment(mut self, alignment: Alignment) -> Self {
+ self.alignment = alignment;
self
}
}
+/// Alignment of the scrollable's content relative to it's [`Viewport`] in one direction.
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
+pub enum Alignment {
+ /// Content is aligned to the start of the [`Viewport`].
+ #[default]
+ Start,
+ /// Content is aligned to the end of the [`Viewport`]
+ End,
+}
+
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Scrollable<'a, Message, Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
fn tag(&self) -> tree::Tag {
@@ -173,7 +221,7 @@ where
}
fn width(&self) -> Length {
- self.content.as_widget().width()
+ self.width
}
fn height(&self) -> Length {
@@ -188,9 +236,9 @@ where
layout(
renderer,
limits,
- Widget::<Message, Renderer>::width(self),
+ self.width,
self.height,
- self.horizontal.is_some(),
+ &self.direction,
|renderer, limits| {
self.content.as_widget().layout(renderer, limits)
},
@@ -226,7 +274,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -235,18 +283,17 @@ where
tree.state.downcast_mut::<State>(),
event,
layout,
- cursor_position,
+ cursor,
clipboard,
shell,
- &self.vertical,
- self.horizontal.as_ref(),
+ self.direction,
&self.on_scroll,
- |event, layout, cursor_position, clipboard, shell| {
+ |event, layout, cursor, clipboard, shell| {
self.content.as_widget_mut().on_event(
&mut tree.children[0],
event,
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -262,7 +309,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
draw(
@@ -270,18 +317,17 @@ where
renderer,
theme,
layout,
- cursor_position,
- &self.vertical,
- self.horizontal.as_ref(),
+ cursor,
+ self.direction,
&self.style,
- |renderer, layout, cursor_position, viewport| {
+ |renderer, layout, cursor, viewport| {
self.content.as_widget().draw(
&tree.children[0],
renderer,
theme,
style,
layout,
- cursor_position,
+ cursor,
viewport,
)
},
@@ -292,21 +338,20 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
mouse_interaction(
tree.state.downcast_ref::<State>(),
layout,
- cursor_position,
- &self.vertical,
- self.horizontal.as_ref(),
- |layout, cursor_position, viewport| {
+ cursor,
+ self.direction,
+ |layout, cursor, viewport| {
self.content.as_widget().mouse_interaction(
&tree.children[0],
layout,
- cursor_position,
+ cursor,
viewport,
renderer,
)
@@ -331,12 +376,12 @@ where
let bounds = layout.bounds();
let content_layout = layout.children().next().unwrap();
let content_bounds = content_layout.bounds();
- let offset = tree
+ let translation = tree
.state
.downcast_ref::<State>()
- .offset(bounds, content_bounds);
+ .translation(self.direction, bounds, content_bounds);
- overlay.translate(Vector::new(-offset.x, -offset.y))
+ overlay.translate(Vector::new(-translation.x, -translation.y))
})
}
}
@@ -345,7 +390,7 @@ impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a,
- Renderer: 'a + crate::Renderer,
+ Renderer: 'a + crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
fn from(
@@ -388,34 +433,39 @@ pub fn snap_to<Message: 'static>(
Command::widget(operation::scrollable::snap_to(id.0, offset))
}
+/// Produces a [`Command`] that scrolls the [`Scrollable`] with the given [`Id`]
+/// to the provided [`AbsoluteOffset`] along the x & y axis.
+pub fn scroll_to<Message: 'static>(
+ id: Id,
+ offset: AbsoluteOffset,
+) -> Command<Message> {
+ Command::widget(operation::scrollable::scroll_to(id.0, offset))
+}
+
/// Computes the layout of a [`Scrollable`].
pub fn layout<Renderer>(
renderer: &Renderer,
limits: &layout::Limits,
width: Length,
height: Length,
- horizontal_enabled: bool,
+ direction: &Direction,
layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
) -> layout::Node {
- let limits = limits
- .max_height(f32::INFINITY)
- .max_width(if horizontal_enabled {
- f32::INFINITY
- } else {
- limits.max().width
- })
- .width(width)
- .height(height);
+ let limits = limits.width(width).height(height);
let child_limits = layout::Limits::new(
- Size::new(limits.min().width, 0.0),
+ Size::new(limits.min().width, limits.min().height),
Size::new(
- if horizontal_enabled {
+ if direction.horizontal().is_some() {
f32::INFINITY
} else {
limits.max().width
},
- f32::MAX,
+ if direction.vertical().is_some() {
+ f32::MAX
+ } else {
+ limits.max().height
+ },
),
);
@@ -431,52 +481,44 @@ pub fn update<Message>(
state: &mut State,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
- vertical: &Properties,
- horizontal: Option<&Properties>,
- on_scroll: &Option<Box<dyn Fn(RelativeOffset) -> Message + '_>>,
+ direction: Direction,
+ on_scroll: &Option<Box<dyn Fn(Viewport) -> Message + '_>>,
update_content: impl FnOnce(
Event,
Layout<'_>,
- Point,
+ mouse::Cursor,
&mut dyn Clipboard,
&mut Shell<'_, Message>,
) -> event::Status,
) -> event::Status {
let bounds = layout.bounds();
- let mouse_over_scrollable = bounds.contains(cursor_position);
+ let cursor_over_scrollable = cursor.position_over(bounds);
let content = layout.children().next().unwrap();
let content_bounds = content.bounds();
- let scrollbars =
- Scrollbars::new(state, vertical, horizontal, bounds, content_bounds);
+ let scrollbars = Scrollbars::new(state, direction, bounds, content_bounds);
let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) =
- scrollbars.is_mouse_over(cursor_position);
+ scrollbars.is_mouse_over(cursor);
let event_status = {
- let cursor_position = if mouse_over_scrollable
- && !(mouse_over_y_scrollbar || mouse_over_x_scrollbar)
- {
- cursor_position + state.offset(bounds, content_bounds)
- } else {
- // TODO: Make `cursor_position` an `Option<Point>` so we can encode
- // cursor availability.
- // This will probably happen naturally once we add multi-window
- // support.
- Point::new(-1.0, -1.0)
+ let cursor = match cursor_over_scrollable {
+ Some(cursor_position)
+ if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) =>
+ {
+ mouse::Cursor::Available(
+ cursor_position
+ + state.translation(direction, bounds, content_bounds),
+ )
+ }
+ _ => mouse::Cursor::Unavailable,
};
- update_content(
- event.clone(),
- content,
- cursor_position,
- clipboard,
- shell,
- )
+ update_content(event.clone(), content, cursor, clipboard, shell)
};
if let event::Status::Captured = event_status {
@@ -490,76 +532,79 @@ pub fn update<Message>(
return event::Status::Ignored;
}
- if mouse_over_scrollable {
- match event {
- Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
- let delta = match delta {
- mouse::ScrollDelta::Lines { x, y } => {
- // TODO: Configurable speed/friction (?)
- let movement = if state.keyboard_modifiers.shift() {
- Vector::new(y, x)
- } else {
- Vector::new(x, y)
- };
+ match event {
+ Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
+ if cursor_over_scrollable.is_none() {
+ return event::Status::Ignored;
+ }
- movement * 60.0
- }
- mouse::ScrollDelta::Pixels { x, y } => Vector::new(x, y),
- };
+ let delta = match delta {
+ mouse::ScrollDelta::Lines { x, y } => {
+ // TODO: Configurable speed/friction (?)
+ let movement = if state.keyboard_modifiers.shift() {
+ Vector::new(y, x)
+ } else {
+ Vector::new(x, y)
+ };
- state.scroll(delta, bounds, content_bounds);
+ movement * 60.0
+ }
+ mouse::ScrollDelta::Pixels { x, y } => Vector::new(x, y),
+ };
- notify_on_scroll(
- state,
- on_scroll,
- bounds,
- content_bounds,
- shell,
- );
+ state.scroll(delta, direction, bounds, content_bounds);
+
+ notify_on_scroll(state, on_scroll, bounds, content_bounds, shell);
+
+ return event::Status::Captured;
+ }
+ Event::Touch(event)
+ if state.scroll_area_touched_at.is_some()
+ || !mouse_over_y_scrollbar && !mouse_over_x_scrollbar =>
+ {
+ match event {
+ touch::Event::FingerPressed { .. } => {
+ let Some(cursor_position) = cursor.position() else {
+ return event::Status::Ignored
+ };
+
+ state.scroll_area_touched_at = Some(cursor_position);
+ }
+ touch::Event::FingerMoved { .. } => {
+ if let Some(scroll_box_touched_at) =
+ state.scroll_area_touched_at
+ {
+ let Some(cursor_position) = cursor.position() else {
+ return event::Status::Ignored
+ };
+
+ let delta = Vector::new(
+ cursor_position.x - scroll_box_touched_at.x,
+ cursor_position.y - scroll_box_touched_at.y,
+ );
+
+ state.scroll(delta, direction, bounds, content_bounds);
- return event::Status::Captured;
- }
- Event::Touch(event)
- if state.scroll_area_touched_at.is_some()
- || !mouse_over_y_scrollbar && !mouse_over_x_scrollbar =>
- {
- match event {
- touch::Event::FingerPressed { .. } => {
state.scroll_area_touched_at = Some(cursor_position);
- }
- touch::Event::FingerMoved { .. } => {
- if let Some(scroll_box_touched_at) =
- state.scroll_area_touched_at
- {
- let delta = Vector::new(
- cursor_position.x - scroll_box_touched_at.x,
- cursor_position.y - scroll_box_touched_at.y,
- );
-
- state.scroll(delta, bounds, content_bounds);
-
- state.scroll_area_touched_at =
- Some(cursor_position);
-
- notify_on_scroll(
- state,
- on_scroll,
- bounds,
- content_bounds,
- shell,
- );
- }
- }
- touch::Event::FingerLifted { .. }
- | touch::Event::FingerLost { .. } => {
- state.scroll_area_touched_at = None;
+
+ notify_on_scroll(
+ state,
+ on_scroll,
+ bounds,
+ content_bounds,
+ shell,
+ );
}
}
-
- return event::Status::Captured;
+ touch::Event::FingerLifted { .. }
+ | touch::Event::FingerLost { .. } => {
+ state.scroll_area_touched_at = None;
+ }
}
- _ => {}
+
+ return event::Status::Captured;
}
+ _ => {}
}
if let Some(scroller_grabbed_at) = state.y_scroller_grabbed_at {
@@ -574,6 +619,10 @@ pub fn update<Message>(
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. }) => {
if let Some(scrollbar) = scrollbars.y {
+ let Some(cursor_position) = cursor.position() else {
+ return event::Status::Ignored
+ };
+
state.scroll_y_to(
scrollbar.scroll_percentage_y(
scroller_grabbed_at,
@@ -600,6 +649,10 @@ pub fn update<Message>(
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
+ let Some(cursor_position) = cursor.position() else {
+ return event::Status::Ignored
+ };
+
if let (Some(scroller_grabbed_at), Some(scrollbar)) =
(scrollbars.grab_y_scroller(cursor_position), scrollbars.y)
{
@@ -640,6 +693,10 @@ pub fn update<Message>(
}
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. }) => {
+ let Some(cursor_position) = cursor.position() else {
+ return event::Status::Ignored
+ };
+
if let Some(scrollbar) = scrollbars.x {
state.scroll_x_to(
scrollbar.scroll_percentage_x(
@@ -667,6 +724,10 @@ pub fn update<Message>(
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
+ let Some(cursor_position) = cursor.position() else {
+ return event::Status::Ignored
+ };
+
if let (Some(scroller_grabbed_at), Some(scrollbar)) =
(scrollbars.grab_x_scroller(cursor_position), scrollbars.x)
{
@@ -703,48 +764,47 @@ pub fn update<Message>(
pub fn mouse_interaction(
state: &State,
layout: Layout<'_>,
- cursor_position: Point,
- vertical: &Properties,
- horizontal: Option<&Properties>,
+ cursor: mouse::Cursor,
+ direction: Direction,
content_interaction: impl FnOnce(
Layout<'_>,
- Point,
+ mouse::Cursor,
&Rectangle,
) -> mouse::Interaction,
) -> mouse::Interaction {
let bounds = layout.bounds();
- let mouse_over_scrollable = bounds.contains(cursor_position);
+ let cursor_over_scrollable = cursor.position_over(bounds);
let content_layout = layout.children().next().unwrap();
let content_bounds = content_layout.bounds();
- let scrollbars =
- Scrollbars::new(state, vertical, horizontal, bounds, content_bounds);
+ let scrollbars = Scrollbars::new(state, direction, bounds, content_bounds);
let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) =
- scrollbars.is_mouse_over(cursor_position);
+ scrollbars.is_mouse_over(cursor);
if (mouse_over_x_scrollbar || mouse_over_y_scrollbar)
|| state.scrollers_grabbed()
{
mouse::Interaction::Idle
} else {
- let offset = state.offset(bounds, content_bounds);
+ let translation = state.translation(direction, bounds, content_bounds);
- let cursor_position = if mouse_over_scrollable
- && !(mouse_over_y_scrollbar || mouse_over_x_scrollbar)
- {
- cursor_position + offset
- } else {
- Point::new(-1.0, -1.0)
+ let cursor = match cursor_over_scrollable {
+ Some(cursor_position)
+ if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) =>
+ {
+ mouse::Cursor::Available(cursor_position + translation)
+ }
+ _ => mouse::Cursor::Unavailable,
};
content_interaction(
content_layout,
- cursor_position,
+ cursor,
&Rectangle {
- y: bounds.y + offset.y,
- x: bounds.x + offset.x,
+ y: bounds.y + translation.y,
+ x: bounds.x + translation.x,
..bounds
},
)
@@ -757,49 +817,48 @@ pub fn draw<Renderer>(
renderer: &mut Renderer,
theme: &Renderer::Theme,
layout: Layout<'_>,
- cursor_position: Point,
- vertical: &Properties,
- horizontal: Option<&Properties>,
+ cursor: mouse::Cursor,
+ direction: Direction,
style: &<Renderer::Theme as StyleSheet>::Style,
- draw_content: impl FnOnce(&mut Renderer, Layout<'_>, Point, &Rectangle),
+ draw_content: impl FnOnce(&mut Renderer, Layout<'_>, mouse::Cursor, &Rectangle),
) where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
let bounds = layout.bounds();
let content_layout = layout.children().next().unwrap();
let content_bounds = content_layout.bounds();
- let scrollbars =
- Scrollbars::new(state, vertical, horizontal, bounds, content_bounds);
+ let scrollbars = Scrollbars::new(state, direction, bounds, content_bounds);
- let mouse_over_scrollable = bounds.contains(cursor_position);
+ let cursor_over_scrollable = cursor.position_over(bounds);
let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) =
- scrollbars.is_mouse_over(cursor_position);
+ scrollbars.is_mouse_over(cursor);
- let offset = state.offset(bounds, content_bounds);
+ let translation = state.translation(direction, bounds, content_bounds);
- let cursor_position = if mouse_over_scrollable
- && !(mouse_over_x_scrollbar || mouse_over_y_scrollbar)
- {
- cursor_position + offset
- } else {
- Point::new(-1.0, -1.0)
+ let cursor = match cursor_over_scrollable {
+ Some(cursor_position)
+ if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) =>
+ {
+ mouse::Cursor::Available(cursor_position + translation)
+ }
+ _ => mouse::Cursor::Unavailable,
};
// Draw inner content
if scrollbars.active() {
renderer.with_layer(bounds, |renderer| {
renderer.with_translation(
- Vector::new(-offset.x, -offset.y),
+ Vector::new(-translation.x, -translation.y),
|renderer| {
draw_content(
renderer,
content_layout,
- cursor_position,
+ cursor,
&Rectangle {
- y: bounds.y + offset.y,
- x: bounds.x + offset.x,
+ y: bounds.y + translation.y,
+ x: bounds.x + translation.x,
..bounds
},
);
@@ -809,17 +868,19 @@ pub fn draw<Renderer>(
let draw_scrollbar =
|renderer: &mut Renderer,
- style: style::Scrollbar,
- scrollbar: &Scrollbar| {
+ style: Scrollbar,
+ scrollbar: &internals::Scrollbar| {
//track
- if style.background.is_some()
- || (style.border_color != Color::TRANSPARENT
- && style.border_width > 0.0)
+ if scrollbar.bounds.width > 0.0
+ && scrollbar.bounds.height > 0.0
+ && (style.background.is_some()
+ || (style.border_color != Color::TRANSPARENT
+ && style.border_width > 0.0))
{
renderer.fill_quad(
renderer::Quad {
bounds: scrollbar.bounds,
- border_radius: style.border_radius.into(),
+ border_radius: style.border_radius,
border_width: style.border_width,
border_color: style.border_color,
},
@@ -830,14 +891,16 @@ pub fn draw<Renderer>(
}
//thumb
- if style.scroller.color != Color::TRANSPARENT
- || (style.scroller.border_color != Color::TRANSPARENT
- && style.scroller.border_width > 0.0)
+ if scrollbar.scroller.bounds.width > 0.0
+ && scrollbar.scroller.bounds.height > 0.0
+ && (style.scroller.color != Color::TRANSPARENT
+ || (style.scroller.border_color != Color::TRANSPARENT
+ && style.scroller.border_width > 0.0))
{
renderer.fill_quad(
renderer::Quad {
bounds: scrollbar.scroller.bounds,
- border_radius: style.scroller.border_radius.into(),
+ border_radius: style.scroller.border_radius,
border_width: style.scroller.border_width,
border_color: style.scroller.border_color,
},
@@ -857,8 +920,8 @@ pub fn draw<Renderer>(
if let Some(scrollbar) = scrollbars.y {
let style = if state.y_scroller_grabbed_at.is_some() {
theme.dragging(style)
- } else if mouse_over_y_scrollbar {
- theme.hovered(style)
+ } else if cursor_over_scrollable.is_some() {
+ theme.hovered(style, mouse_over_y_scrollbar)
} else {
theme.active(style)
};
@@ -870,8 +933,8 @@ pub fn draw<Renderer>(
if let Some(scrollbar) = scrollbars.x {
let style = if state.x_scroller_grabbed_at.is_some() {
theme.dragging_horizontal(style)
- } else if mouse_over_x_scrollbar {
- theme.hovered_horizontal(style)
+ } else if cursor_over_scrollable.is_some() {
+ theme.hovered_horizontal(style, mouse_over_x_scrollbar)
} else {
theme.active_horizontal(style)
};
@@ -884,10 +947,10 @@ pub fn draw<Renderer>(
draw_content(
renderer,
content_layout,
- cursor_position,
+ cursor,
&Rectangle {
- x: bounds.x + offset.x,
- y: bounds.y + offset.y,
+ x: bounds.x + translation.x,
+ y: bounds.y + translation.y,
..bounds
},
);
@@ -895,8 +958,8 @@ pub fn draw<Renderer>(
}
fn notify_on_scroll<Message>(
- state: &State,
- on_scroll: &Option<Box<dyn Fn(RelativeOffset) -> Message + '_>>,
+ state: &mut State,
+ on_scroll: &Option<Box<dyn Fn(Viewport) -> Message + '_>>,
bounds: Rectangle,
content_bounds: Rectangle,
shell: &mut Shell<'_, Message>,
@@ -908,15 +971,36 @@ fn notify_on_scroll<Message>(
return;
}
- let x = state.offset_x.absolute(bounds.width, content_bounds.width)
- / (content_bounds.width - bounds.width);
+ let viewport = Viewport {
+ offset_x: state.offset_x,
+ offset_y: state.offset_y,
+ bounds,
+ content_bounds,
+ };
- let y = state
- .offset_y
- .absolute(bounds.height, content_bounds.height)
- / (content_bounds.height - bounds.height);
+ // Don't publish redundant viewports to shell
+ if let Some(last_notified) = state.last_notified {
+ let last_relative_offset = last_notified.relative_offset();
+ let current_relative_offset = viewport.relative_offset();
+
+ let last_absolute_offset = last_notified.absolute_offset();
+ let current_absolute_offset = viewport.absolute_offset();
- shell.publish(on_scroll(RelativeOffset { x, y }))
+ let unchanged = |a: f32, b: f32| {
+ (a - b).abs() <= f32::EPSILON || (a.is_nan() && b.is_nan())
+ };
+
+ if unchanged(last_relative_offset.x, current_relative_offset.x)
+ && unchanged(last_relative_offset.y, current_relative_offset.y)
+ && unchanged(last_absolute_offset.x, current_absolute_offset.x)
+ && unchanged(last_absolute_offset.y, current_absolute_offset.y)
+ {
+ return;
+ }
+ }
+
+ shell.publish(on_scroll(viewport));
+ state.last_notified = Some(viewport);
}
}
@@ -929,6 +1013,7 @@ pub struct State {
offset_x: Offset,
x_scroller_grabbed_at: Option<f32>,
keyboard_modifiers: keyboard::Modifiers,
+ last_notified: Option<Viewport>,
}
impl Default for State {
@@ -940,6 +1025,7 @@ impl Default for State {
offset_x: Offset::Absolute(0.0),
x_scroller_grabbed_at: None,
keyboard_modifiers: keyboard::Modifiers::default(),
+ last_notified: None,
}
}
}
@@ -948,6 +1034,10 @@ impl operation::Scrollable for State {
fn snap_to(&mut self, offset: RelativeOffset) {
State::snap_to(self, offset);
}
+
+ fn scroll_to(&mut self, offset: AbsoluteOffset) {
+ State::scroll_to(self, offset)
+ }
}
#[derive(Debug, Clone, Copy)]
@@ -957,16 +1047,63 @@ enum Offset {
}
impl Offset {
- fn absolute(self, window: f32, content: f32) -> f32 {
+ fn absolute(self, viewport: f32, content: f32) -> f32 {
match self {
Offset::Absolute(absolute) => {
- absolute.min((content - window).max(0.0))
+ absolute.min((content - viewport).max(0.0))
}
Offset::Relative(percentage) => {
- ((content - window) * percentage).max(0.0)
+ ((content - viewport) * percentage).max(0.0)
}
}
}
+
+ fn translation(
+ self,
+ viewport: f32,
+ content: f32,
+ alignment: Alignment,
+ ) -> f32 {
+ let offset = self.absolute(viewport, content);
+
+ match alignment {
+ Alignment::Start => offset,
+ Alignment::End => ((content - viewport).max(0.0) - offset).max(0.0),
+ }
+ }
+}
+
+/// The current [`Viewport`] of the [`Scrollable`].
+#[derive(Debug, Clone, Copy)]
+pub struct Viewport {
+ offset_x: Offset,
+ offset_y: Offset,
+ bounds: Rectangle,
+ content_bounds: Rectangle,
+}
+
+impl Viewport {
+ /// Returns the [`AbsoluteOffset`] of the current [`Viewport`].
+ pub fn absolute_offset(&self) -> AbsoluteOffset {
+ let x = self
+ .offset_x
+ .absolute(self.bounds.width, self.content_bounds.width);
+ let y = self
+ .offset_y
+ .absolute(self.bounds.height, self.content_bounds.height);
+
+ AbsoluteOffset { x, y }
+ }
+
+ /// Returns the [`RelativeOffset`] of the current [`Viewport`].
+ pub fn relative_offset(&self) -> RelativeOffset {
+ let AbsoluteOffset { x, y } = self.absolute_offset();
+
+ let x = x / (self.content_bounds.width - self.bounds.width);
+ let y = y / (self.content_bounds.height - self.bounds.height);
+
+ RelativeOffset { x, y }
+ }
}
impl State {
@@ -980,9 +1117,30 @@ impl State {
pub fn scroll(
&mut self,
delta: Vector<f32>,
+ direction: Direction,
bounds: Rectangle,
content_bounds: Rectangle,
) {
+ let horizontal_alignment = direction
+ .horizontal()
+ .map(|p| p.alignment)
+ .unwrap_or_default();
+
+ let vertical_alignment = direction
+ .vertical()
+ .map(|p| p.alignment)
+ .unwrap_or_default();
+
+ let align = |alignment: Alignment, delta: f32| match alignment {
+ Alignment::Start => delta,
+ Alignment::End => -delta,
+ };
+
+ let delta = Vector::new(
+ align(horizontal_alignment, delta.x),
+ align(vertical_alignment, delta.y),
+ );
+
if bounds.height < content_bounds.height {
self.offset_y = Offset::Absolute(
(self.offset_y.absolute(bounds.height, content_bounds.height)
@@ -1034,6 +1192,12 @@ impl State {
self.offset_y = Offset::Relative(offset.y.clamp(0.0, 1.0));
}
+ /// Scroll to the provided [`AbsoluteOffset`].
+ pub fn scroll_to(&mut self, offset: AbsoluteOffset) {
+ self.offset_x = Offset::Absolute(offset.x.max(0.0));
+ self.offset_y = Offset::Absolute(offset.y.max(0.0));
+ }
+
/// Unsnaps the current scroll position, if snapped, given the bounds of the
/// [`Scrollable`] and its contents.
pub fn unsnap(&mut self, bounds: Rectangle, content_bounds: Rectangle) {
@@ -1045,16 +1209,33 @@ impl State {
);
}
- /// Returns the scrolling offset of the [`State`], given the bounds of the
- /// [`Scrollable`] and its contents.
- pub fn offset(
+ /// Returns the scrolling translation of the [`State`], given a [`Direction`],
+ /// the bounds of the [`Scrollable`] and its contents.
+ fn translation(
&self,
+ direction: Direction,
bounds: Rectangle,
content_bounds: Rectangle,
) -> Vector {
Vector::new(
- self.offset_x.absolute(bounds.width, content_bounds.width),
- self.offset_y.absolute(bounds.height, content_bounds.height),
+ if let Some(horizontal) = direction.horizontal() {
+ self.offset_x.translation(
+ bounds.width,
+ content_bounds.width,
+ horizontal.alignment,
+ )
+ } else {
+ 0.0
+ },
+ if let Some(vertical) = direction.vertical() {
+ self.offset_y.translation(
+ bounds.height,
+ content_bounds.height,
+ vertical.alignment,
+ )
+ } else {
+ 0.0
+ },
)
}
@@ -1068,34 +1249,34 @@ impl State {
#[derive(Debug)]
/// State of both [`Scrollbar`]s.
struct Scrollbars {
- y: Option<Scrollbar>,
- x: Option<Scrollbar>,
+ y: Option<internals::Scrollbar>,
+ x: Option<internals::Scrollbar>,
}
impl Scrollbars {
/// Create y and/or x scrollbar(s) if content is overflowing the [`Scrollable`] bounds.
fn new(
state: &State,
- vertical: &Properties,
- horizontal: Option<&Properties>,
+ direction: Direction,
bounds: Rectangle,
content_bounds: Rectangle,
) -> Self {
- let offset = state.offset(bounds, content_bounds);
+ let translation = state.translation(direction, bounds, content_bounds);
- let show_scrollbar_x = horizontal.and_then(|h| {
- if content_bounds.width > bounds.width {
- Some(h)
- } else {
- None
- }
- });
+ let show_scrollbar_x = direction
+ .horizontal()
+ .filter(|_| content_bounds.width > bounds.width);
+
+ let show_scrollbar_y = direction
+ .vertical()
+ .filter(|_| content_bounds.height > bounds.height);
- let y_scrollbar = if content_bounds.height > bounds.height {
+ let y_scrollbar = if let Some(vertical) = show_scrollbar_y {
let Properties {
width,
margin,
scroller_width,
+ ..
} = *vertical;
// Adjust the height of the vertical scrollbar if the horizontal scrollbar
@@ -1127,7 +1308,7 @@ impl Scrollbars {
let ratio = bounds.height / content_bounds.height;
// min height for easier grabbing with super tall content
let scroller_height = (bounds.height * ratio).max(2.0);
- let scroller_offset = offset.y * ratio;
+ let scroller_offset = translation.y * ratio;
let scroller_bounds = Rectangle {
x: bounds.x + bounds.width
@@ -1139,12 +1320,13 @@ impl Scrollbars {
height: scroller_height,
};
- Some(Scrollbar {
+ Some(internals::Scrollbar {
total_bounds: total_scrollbar_bounds,
bounds: scrollbar_bounds,
- scroller: Scroller {
+ scroller: internals::Scroller {
bounds: scroller_bounds,
},
+ alignment: vertical.alignment,
})
} else {
None
@@ -1155,13 +1337,13 @@ impl Scrollbars {
width,
margin,
scroller_width,
+ ..
} = *horizontal;
// Need to adjust the width of the horizontal scrollbar if the vertical scrollbar
// is present
- let scrollbar_y_width = y_scrollbar.map_or(0.0, |_| {
- vertical.width.max(vertical.scroller_width) + vertical.margin
- });
+ let scrollbar_y_width = show_scrollbar_y
+ .map_or(0.0, |v| v.width.max(v.scroller_width) + v.margin);
let total_scrollbar_height =
width.max(scroller_width) + 2.0 * margin;
@@ -1187,7 +1369,7 @@ impl Scrollbars {
let ratio = bounds.width / content_bounds.width;
// min width for easier grabbing with extra wide content
let scroller_length = (bounds.width * ratio).max(2.0);
- let scroller_offset = offset.x * ratio;
+ let scroller_offset = translation.x * ratio;
let scroller_bounds = Rectangle {
x: (scrollbar_bounds.x + scroller_offset - scrollbar_y_width)
@@ -1199,12 +1381,13 @@ impl Scrollbars {
height: scroller_width,
};
- Some(Scrollbar {
+ Some(internals::Scrollbar {
total_bounds: total_scrollbar_bounds,
bounds: scrollbar_bounds,
- scroller: Scroller {
+ scroller: internals::Scroller {
bounds: scroller_bounds,
},
+ alignment: horizontal.alignment,
})
} else {
None
@@ -1216,17 +1399,21 @@ impl Scrollbars {
}
}
- fn is_mouse_over(&self, cursor_position: Point) -> (bool, bool) {
- (
- self.y
- .as_ref()
- .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
- .unwrap_or(false),
- self.x
- .as_ref()
- .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
- .unwrap_or(false),
- )
+ fn is_mouse_over(&self, cursor: mouse::Cursor) -> (bool, bool) {
+ if let Some(cursor_position) = cursor.position() {
+ (
+ self.y
+ .as_ref()
+ .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
+ .unwrap_or(false),
+ self.x
+ .as_ref()
+ .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
+ .unwrap_or(false),
+ )
+ } else {
+ (false, false)
+ }
}
fn grab_y_scroller(&self, cursor_position: Point) -> Option<f32> {
@@ -1264,64 +1451,64 @@ impl Scrollbars {
}
}
-/// The scrollbar of a [`Scrollable`].
-#[derive(Debug, Copy, Clone)]
-struct Scrollbar {
- /// The total bounds of the [`Scrollbar`], including the scrollbar, the scroller,
- /// and the scrollbar margin.
- total_bounds: Rectangle,
-
- /// The bounds of just the [`Scrollbar`].
- bounds: Rectangle,
+pub(super) mod internals {
+ use crate::core::{Point, Rectangle};
- /// The state of this scrollbar's [`Scroller`].
- scroller: Scroller,
-}
+ use super::Alignment;
-impl Scrollbar {
- /// Returns whether the mouse is over the scrollbar or not.
- fn is_mouse_over(&self, cursor_position: Point) -> bool {
- self.total_bounds.contains(cursor_position)
+ #[derive(Debug, Copy, Clone)]
+ pub struct Scrollbar {
+ pub total_bounds: Rectangle,
+ pub bounds: Rectangle,
+ pub scroller: Scroller,
+ pub alignment: Alignment,
}
- /// Returns the y-axis scrolled percentage from the cursor position.
- fn scroll_percentage_y(
- &self,
- grabbed_at: f32,
- cursor_position: Point,
- ) -> f32 {
- if cursor_position.x < 0.0 && cursor_position.y < 0.0 {
- // cursor position is unavailable! Set to either end or beginning of scrollbar depending
- // on where the thumb currently is in the track
- (self.scroller.bounds.y / self.total_bounds.height).round()
- } else {
- (cursor_position.y
+ impl Scrollbar {
+ /// Returns whether the mouse is over the scrollbar or not.
+ pub fn is_mouse_over(&self, cursor_position: Point) -> bool {
+ self.total_bounds.contains(cursor_position)
+ }
+
+ /// Returns the y-axis scrolled percentage from the cursor position.
+ pub fn scroll_percentage_y(
+ &self,
+ grabbed_at: f32,
+ cursor_position: Point,
+ ) -> f32 {
+ let percentage = (cursor_position.y
- self.bounds.y
- self.scroller.bounds.height * grabbed_at)
- / (self.bounds.height - self.scroller.bounds.height)
+ / (self.bounds.height - self.scroller.bounds.height);
+
+ match self.alignment {
+ Alignment::Start => percentage,
+ Alignment::End => 1.0 - percentage,
+ }
}
- }
- /// Returns the x-axis scrolled percentage from the cursor position.
- fn scroll_percentage_x(
- &self,
- grabbed_at: f32,
- cursor_position: Point,
- ) -> f32 {
- if cursor_position.x < 0.0 && cursor_position.y < 0.0 {
- (self.scroller.bounds.x / self.total_bounds.width).round()
- } else {
- (cursor_position.x
+ /// Returns the x-axis scrolled percentage from the cursor position.
+ pub fn scroll_percentage_x(
+ &self,
+ grabbed_at: f32,
+ cursor_position: Point,
+ ) -> f32 {
+ let percentage = (cursor_position.x
- self.bounds.x
- self.scroller.bounds.width * grabbed_at)
- / (self.bounds.width - self.scroller.bounds.width)
+ / (self.bounds.width - self.scroller.bounds.width);
+
+ match self.alignment {
+ Alignment::Start => percentage,
+ Alignment::End => 1.0 - percentage,
+ }
}
}
-}
-/// The handle of a [`Scrollbar`].
-#[derive(Debug, Clone, Copy)]
-struct Scroller {
- /// The bounds of the [`Scroller`].
- bounds: Rectangle,
+ /// The handle of a [`Scrollbar`].
+ #[derive(Debug, Clone, Copy)]
+ pub struct Scroller {
+ /// The bounds of the [`Scroller`].
+ pub bounds: Rectangle,
+ }
}
diff --git a/native/src/widget/slider.rs b/widget/src/slider.rs
index d3715b1c..3ea4391b 100644
--- a/native/src/widget/slider.rs
+++ b/widget/src/slider.rs
@@ -1,20 +1,22 @@
//! Display an interactive selector of a single value from a range of values.
//!
//! A [`Slider`] has some local [`State`].
-use crate::event::{self, Event};
-use crate::layout;
-use crate::mouse;
-use crate::renderer;
-use crate::touch;
-use crate::widget::tree::{self, Tree};
-use crate::{
- Background, Clipboard, Color, Element, Layout, Length, Pixels, Point,
- Rectangle, Shell, Size, Widget,
+use crate::core::event::{self, Event};
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::renderer;
+use crate::core::touch;
+use crate::core::widget::tree::{self, Tree};
+use crate::core::{
+ Clipboard, Color, Element, Layout, Length, Pixels, Point, Rectangle, Shell,
+ Size, Widget,
};
use std::ops::RangeInclusive;
-pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet};
+pub use iced_style::slider::{
+ Appearance, Handle, HandleShape, Rail, StyleSheet,
+};
/// An horizontal bar and a handle that selects a single value from a range of
/// values.
@@ -25,11 +27,9 @@ pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet};
/// to 1 unit.
///
/// # Example
-/// ```
-/// # use iced_native::widget::slider;
-/// # use iced_native::renderer::Null;
-/// #
-/// # type Slider<'a, T, Message> = slider::Slider<'a, T, Message, Null>;
+/// ```no_run
+/// # type Slider<'a, T, Message> =
+/// # iced_widget::Slider<'a, Message, T, iced_widget::renderer::Renderer<iced_widget::style::Theme>>;
/// #
/// #[derive(Clone)]
/// pub enum Message {
@@ -43,9 +43,9 @@ pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet};
///
/// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true)
#[allow(missing_debug_implementations)]
-pub struct Slider<'a, T, Message, Renderer>
+pub struct Slider<'a, T, Message, Renderer = crate::Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
range: RangeInclusive<T>,
@@ -62,7 +62,7 @@ impl<'a, T, Message, Renderer> Slider<'a, T, Message, Renderer>
where
T: Copy + From<u8> + std::cmp::PartialOrd,
Message: Clone,
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
/// The default height of a [`Slider`].
@@ -148,7 +148,7 @@ impl<'a, T, Message, Renderer> Widget<Message, Renderer>
where
T: Copy + Into<f64> + num_traits::FromPrimitive,
Message: Clone,
- Renderer: crate::Renderer,
+ Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
fn tag(&self) -> tree::Tag {
@@ -183,7 +183,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -191,7 +191,7 @@ where
update(
event,
layout,
- cursor_position,
+ cursor,
shell,
tree.state.downcast_mut::<State>(),
&mut self.value,
@@ -209,13 +209,13 @@ where
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
draw(
renderer,
layout,
- cursor_position,
+ cursor,
tree.state.downcast_ref::<State>(),
self.value,
&self.range,
@@ -228,15 +228,11 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- mouse_interaction(
- layout,
- cursor_position,
- tree.state.downcast_ref::<State>(),
- )
+ mouse_interaction(layout, cursor, tree.state.downcast_ref::<State>())
}
}
@@ -245,7 +241,7 @@ impl<'a, T, Message, Renderer> From<Slider<'a, T, Message, Renderer>>
where
T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,
Message: 'a + Clone,
- Renderer: 'a + crate::Renderer,
+ Renderer: 'a + crate::core::Renderer,
Renderer::Theme: StyleSheet,
{
fn from(
@@ -260,7 +256,7 @@ where
pub fn update<Message, T>(
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
shell: &mut Shell<'_, Message>,
state: &mut State,
value: &mut T,
@@ -275,7 +271,7 @@ where
{
let is_dragging = state.is_dragging;
- let mut change = || {
+ let mut change = |cursor_position: Point| {
let bounds = layout.bounds();
let new_value = if cursor_position.x <= bounds.x {
*range.start()
@@ -309,8 +305,9 @@ where
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
- if layout.bounds().contains(cursor_position) {
- change();
+ if let Some(cursor_position) = cursor.position_over(layout.bounds())
+ {
+ change(cursor_position);
state.is_dragging = true;
return event::Status::Captured;
@@ -331,7 +328,7 @@ where
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. }) => {
if is_dragging {
- change();
+ let _ = cursor.position().map(change);
return event::Status::Captured;
}
@@ -346,7 +343,7 @@ where
pub fn draw<T, R>(
renderer: &mut R,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
state: &State,
value: T,
range: &RangeInclusive<T>,
@@ -354,11 +351,11 @@ pub fn draw<T, R>(
style: &<R::Theme as StyleSheet>::Style,
) where
T: Into<f64> + Copy,
- R: crate::Renderer,
+ R: crate::core::Renderer,
R::Theme: StyleSheet,
{
let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(bounds);
let style = if state.is_dragging {
style_sheet.dragging(style)
@@ -368,72 +365,72 @@ pub fn draw<T, R>(
style_sheet.active(style)
};
- let rail_y = bounds.y + (bounds.height / 2.0).round();
+ let (handle_width, handle_height, handle_border_radius) =
+ match style.handle.shape {
+ HandleShape::Circle { radius } => {
+ (radius * 2.0, radius * 2.0, radius.into())
+ }
+ HandleShape::Rectangle {
+ width,
+ border_radius,
+ } => (f32::from(width), bounds.height, border_radius),
+ };
+
+ let value = value.into() as f32;
+ let (range_start, range_end) = {
+ let (start, end) = range.clone().into_inner();
+
+ (start.into() as f32, end.into() as f32)
+ };
+
+ let offset = if range_start >= range_end {
+ 0.0
+ } else {
+ (bounds.width - handle_width) * (value - range_start)
+ / (range_end - range_start)
+ };
+
+ let rail_y = bounds.y + bounds.height / 2.0;
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
x: bounds.x,
- y: rail_y - 1.0,
- width: bounds.width,
- height: 2.0,
+ y: rail_y - style.rail.width / 2.0,
+ width: offset + handle_width / 2.0,
+ height: style.rail.width,
},
- border_radius: 0.0.into(),
+ border_radius: style.rail.border_radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
- style.rail_colors.0,
+ style.rail.colors.0,
);
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
- x: bounds.x,
- y: rail_y + 1.0,
- width: bounds.width,
- height: 2.0,
+ x: bounds.x + offset + handle_width / 2.0,
+ y: rail_y - style.rail.width / 2.0,
+ width: bounds.width - offset - handle_width / 2.0,
+ height: style.rail.width,
},
- border_radius: 0.0.into(),
+ border_radius: style.rail.border_radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
- Background::Color(style.rail_colors.1),
+ style.rail.colors.1,
);
- let (handle_width, handle_height, handle_border_radius) = match style
- .handle
- .shape
- {
- HandleShape::Circle { radius } => (radius * 2.0, radius * 2.0, radius),
- HandleShape::Rectangle {
- width,
- border_radius,
- } => (f32::from(width), bounds.height, border_radius),
- };
-
- let value = value.into() as f32;
- let (range_start, range_end) = {
- let (start, end) = range.clone().into_inner();
-
- (start.into() as f32, end.into() as f32)
- };
-
- let handle_offset = if range_start >= range_end {
- 0.0
- } else {
- (bounds.width - handle_width) * (value - range_start)
- / (range_end - range_start)
- };
-
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
- x: bounds.x + handle_offset.round(),
+ x: bounds.x + offset,
y: rail_y - handle_height / 2.0,
width: handle_width,
height: handle_height,
},
- border_radius: handle_border_radius.into(),
+ border_radius: handle_border_radius,
border_width: style.handle.border_width,
border_color: style.handle.border_color,
},
@@ -444,11 +441,11 @@ pub fn draw<T, R>(
/// Computes the current [`mouse::Interaction`] of a [`Slider`].
pub fn mouse_interaction(
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
state: &State,
) -> mouse::Interaction {
let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(bounds);
if state.is_dragging {
mouse::Interaction::Grabbing
diff --git a/native/src/widget/space.rs b/widget/src/space.rs
index a6fc977e..9a5385e8 100644
--- a/native/src/widget/space.rs
+++ b/widget/src/space.rs
@@ -1,8 +1,10 @@
//! Distribute content vertically.
-use crate::layout;
-use crate::renderer;
-use crate::widget::Tree;
-use crate::{Element, Layout, Length, Point, Rectangle, Size, Widget};
+use crate::core;
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::renderer;
+use crate::core::widget::Tree;
+use crate::core::{Element, Layout, Length, Rectangle, Size, Widget};
/// An amount of empty space.
///
@@ -41,7 +43,7 @@ impl Space {
impl<Message, Renderer> Widget<Message, Renderer> for Space
where
- Renderer: crate::Renderer,
+ Renderer: core::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -68,7 +70,7 @@ where
_theme: &Renderer::Theme,
_style: &renderer::Style,
_layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
}
@@ -76,7 +78,7 @@ where
impl<'a, Message, Renderer> From<Space> for Element<'a, Message, Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: core::Renderer,
Message: 'a,
{
fn from(space: Space) -> Element<'a, Message, Renderer> {
diff --git a/native/src/widget/svg.rs b/widget/src/svg.rs
index f5ed0a6c..1ccc5d62 100644
--- a/native/src/widget/svg.rs
+++ b/widget/src/svg.rs
@@ -1,15 +1,16 @@
//! Display vector graphics in your application.
-use crate::layout;
-use crate::renderer;
-use crate::svg;
-use crate::widget::Tree;
-use crate::{
- ContentFit, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget,
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::renderer;
+use crate::core::svg;
+use crate::core::widget::Tree;
+use crate::core::{
+ ContentFit, Element, Layout, Length, Rectangle, Size, Vector, Widget,
};
use std::path::PathBuf;
-pub use iced_style::svg::{Appearance, StyleSheet};
+pub use crate::style::svg::{Appearance, StyleSheet};
pub use svg::Handle;
/// A vector graphics image.
@@ -19,7 +20,7 @@ pub use svg::Handle;
/// [`Svg`] images can have a considerable rendering cost when resized,
/// specially when they are complex.
#[allow(missing_debug_implementations)]
-pub struct Svg<Renderer>
+pub struct Svg<Renderer = crate::Renderer>
where
Renderer: svg::Renderer,
Renderer::Theme: StyleSheet,
@@ -143,7 +144,7 @@ where
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
let Size { width, height } = renderer.dimensions(&self.handle);
diff --git a/native/src/widget/text_input.rs b/widget/src/text_input.rs
index 00e871e7..03bcb86a 100644
--- a/native/src/widget/text_input.rs
+++ b/widget/src/text_input.rs
@@ -11,31 +11,34 @@ pub use value::Value;
use editor::Editor;
-use crate::alignment;
-use crate::event::{self, Event};
-use crate::keyboard;
-use crate::layout;
-use crate::mouse::{self, click};
-use crate::renderer;
-use crate::text::{self, Text};
-use crate::time::{Duration, Instant};
-use crate::touch;
-use crate::widget;
-use crate::widget::operation::{self, Operation};
-use crate::widget::tree::{self, Tree};
-use crate::window;
-use crate::{
- Clipboard, Color, Command, Element, Layout, Length, Padding, Pixels, Point,
+use crate::core::alignment;
+use crate::core::event::{self, Event};
+use crate::core::keyboard;
+use crate::core::layout;
+use crate::core::mouse::{self, click};
+use crate::core::renderer;
+use crate::core::text::{self, Text};
+use crate::core::time::{Duration, Instant};
+use crate::core::touch;
+use crate::core::widget;
+use crate::core::widget::operation::{self, Operation};
+use crate::core::widget::tree::{self, Tree};
+use crate::core::window;
+use crate::core::{
+ Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point,
Rectangle, Shell, Size, Vector, Widget,
};
+use crate::runtime::Command;
pub use iced_style::text_input::{Appearance, StyleSheet};
/// A field that can be filled with text.
///
/// # Example
-/// ```
-/// # pub type TextInput<'a, Message> = iced_native::widget::TextInput<'a, Message, iced_native::renderer::Null>;
+/// ```no_run
+/// # pub type TextInput<'a, Message> =
+/// # iced_widget::TextInput<'a, Message, iced_widget::renderer::Renderer<iced_widget::style::Theme>>;
+/// #
/// #[derive(Debug, Clone)]
/// enum Message {
/// TextInputChanged(String),
@@ -46,13 +49,13 @@ pub use iced_style::text_input::{Appearance, StyleSheet};
/// let input = TextInput::new(
/// "This is the placeholder...",
/// value,
-/// Message::TextInputChanged,
/// )
+/// .on_input(Message::TextInputChanged)
/// .padding(10);
/// ```
/// ![Text input drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text_input.png?raw=true)
#[allow(missing_debug_implementations)]
-pub struct TextInput<'a, Message, Renderer>
+pub struct TextInput<'a, Message, Renderer = crate::Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
@@ -61,13 +64,15 @@ where
placeholder: String,
value: Value,
is_secure: bool,
- font: Renderer::Font,
+ font: Option<Renderer::Font>,
width: Length,
padding: Padding,
size: Option<f32>,
- on_change: Box<dyn Fn(String) -> Message + 'a>,
+ line_height: text::LineHeight,
+ on_input: Option<Box<dyn Fn(String) -> Message + 'a>>,
on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>,
on_submit: Option<Message>,
+ icon: Option<Icon<Renderer::Font>>,
style: <Renderer::Theme as StyleSheet>::Style,
}
@@ -81,24 +86,22 @@ where
///
/// It expects:
/// - a placeholder,
- /// - the current value, and
- /// - a function that produces a message when the [`TextInput`] changes.
- pub fn new<F>(placeholder: &str, value: &str, on_change: F) -> Self
- where
- F: 'a + Fn(String) -> Message,
- {
+ /// - the current value
+ pub fn new(placeholder: &str, value: &str) -> Self {
TextInput {
id: None,
placeholder: String::from(placeholder),
value: Value::new(value),
is_secure: false,
- font: Default::default(),
+ font: None,
width: Length::Fill,
padding: Padding::new(5.0),
size: None,
- on_change: Box::new(on_change),
+ line_height: text::LineHeight::default(),
+ on_input: None,
on_paste: None,
on_submit: None,
+ icon: None,
style: Default::default(),
}
}
@@ -115,6 +118,25 @@ where
self
}
+ /// Sets the message that should be produced when some text is typed into
+ /// the [`TextInput`].
+ ///
+ /// If this method is not called, the [`TextInput`] will be disabled.
+ pub fn on_input<F>(mut self, callback: F) -> Self
+ where
+ F: 'a + Fn(String) -> Message,
+ {
+ self.on_input = Some(Box::new(callback));
+ self
+ }
+
+ /// Sets the message that should be produced when the [`TextInput`] is
+ /// focused and the enter key is pressed.
+ pub fn on_submit(mut self, message: Message) -> Self {
+ self.on_submit = Some(message);
+ self
+ }
+
/// Sets the message that should be produced when some text is pasted into
/// the [`TextInput`].
pub fn on_paste(
@@ -129,9 +151,16 @@ where
///
/// [`Font`]: text::Renderer::Font
pub fn font(mut self, font: Renderer::Font) -> Self {
- self.font = font;
+ self.font = Some(font);
self
}
+
+ /// Sets the [`Icon`] of the [`TextInput`].
+ pub fn icon(mut self, icon: Icon<Renderer::Font>) -> Self {
+ self.icon = Some(icon);
+ self
+ }
+
/// Sets the width of the [`TextInput`].
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
@@ -150,10 +179,12 @@ where
self
}
- /// Sets the message that should be produced when the [`TextInput`] is
- /// focused and the enter key is pressed.
- pub fn on_submit(mut self, message: Message) -> Self {
- self.on_submit = Some(message);
+ /// Sets the [`LineHeight`] of the [`TextInput`].
+ pub fn line_height(
+ mut self,
+ line_height: impl Into<text::LineHeight>,
+ ) -> Self {
+ self.line_height = line_height.into();
self
}
@@ -176,20 +207,23 @@ where
renderer: &mut Renderer,
theme: &Renderer::Theme,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
value: Option<&Value>,
) {
draw(
renderer,
theme,
layout,
- cursor_position,
+ cursor,
tree.state.downcast_ref::<State>(),
value.unwrap_or(&self.value),
&self.placeholder,
self.size,
- &self.font,
+ self.line_height,
+ self.font,
+ self.on_input.is_none(),
self.is_secure,
+ self.icon.as_ref(),
&self.style,
)
}
@@ -210,6 +244,18 @@ where
tree::State::new(State::new())
}
+ fn diff(&self, tree: &mut Tree) {
+ let state = tree.state.downcast_mut::<State>();
+
+ // Unfocus text input if it becomes disabled
+ if self.on_input.is_none() {
+ state.last_click = None;
+ state.is_focused = None;
+ state.is_pasting = None;
+ state.is_dragging = false;
+ }
+ }
+
fn width(&self) -> Length {
self.width
}
@@ -223,7 +269,15 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- layout(renderer, limits, self.width, self.padding, self.size)
+ layout(
+ renderer,
+ limits,
+ self.width,
+ self.padding,
+ self.size,
+ self.line_height,
+ self.icon.as_ref(),
+ )
}
fn operate(
@@ -244,7 +298,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -252,15 +306,16 @@ where
update(
event,
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
&mut self.value,
self.size,
- &self.font,
+ self.line_height,
+ self.font,
self.is_secure,
- self.on_change.as_ref(),
+ self.on_input.as_deref(),
self.on_paste.as_deref(),
&self.on_submit,
|| tree.state.downcast_mut::<State>(),
@@ -274,20 +329,23 @@ where
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
draw(
renderer,
theme,
layout,
- cursor_position,
+ cursor,
tree.state.downcast_ref::<State>(),
&self.value,
&self.placeholder,
self.size,
- &self.font,
+ self.line_height,
+ self.font,
+ self.on_input.is_none(),
self.is_secure,
+ self.icon.as_ref(),
&self.style,
)
}
@@ -296,11 +354,11 @@ where
&self,
_state: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- mouse_interaction(layout, cursor_position)
+ mouse_interaction(layout, cursor, self.on_input.is_none())
}
}
@@ -318,6 +376,30 @@ where
}
}
+/// The content of the [`Icon`].
+#[derive(Debug, Clone)]
+pub struct Icon<Font> {
+ /// The font that will be used to display the `code_point`.
+ pub font: Font,
+ /// The unicode code point that will be used as the icon.
+ pub code_point: char,
+ /// The font size of the content.
+ pub size: Option<f32>,
+ /// The spacing between the [`Icon`] and the text in a [`TextInput`].
+ pub spacing: f32,
+ /// The side of a [`TextInput`] where to display the [`Icon`].
+ pub side: Side,
+}
+
+/// The side of a [`TextInput`].
+#[derive(Debug, Clone)]
+pub enum Side {
+ /// The left side of a [`TextInput`].
+ Left,
+ /// The right side of a [`TextInput`].
+ Right,
+}
+
/// The identifier of a [`TextInput`].
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Id(widget::Id);
@@ -380,19 +462,65 @@ pub fn layout<Renderer>(
width: Length,
padding: Padding,
size: Option<f32>,
+ line_height: text::LineHeight,
+ icon: Option<&Icon<Renderer::Font>>,
) -> layout::Node
where
Renderer: text::Renderer,
{
let text_size = size.unwrap_or_else(|| renderer.default_size());
-
let padding = padding.fit(Size::ZERO, limits.max());
- let limits = limits.width(width).pad(padding).height(text_size);
+ let limits = limits
+ .width(width)
+ .pad(padding)
+ .height(line_height.to_absolute(Pixels(text_size)));
+
+ let text_bounds = limits.resolve(Size::ZERO);
+
+ if let Some(icon) = icon {
+ let icon_width = renderer.measure_width(
+ &icon.code_point.to_string(),
+ icon.size.unwrap_or_else(|| renderer.default_size()),
+ icon.font,
+ text::Shaping::Advanced,
+ );
+
+ let mut text_node = layout::Node::new(
+ text_bounds - Size::new(icon_width + icon.spacing, 0.0),
+ );
+
+ let mut icon_node =
+ layout::Node::new(Size::new(icon_width, text_bounds.height));
+
+ match icon.side {
+ Side::Left => {
+ text_node.move_to(Point::new(
+ padding.left + icon_width + icon.spacing,
+ padding.top,
+ ));
- let mut text = layout::Node::new(limits.resolve(Size::ZERO));
- text.move_to(Point::new(padding.left, padding.top));
+ icon_node.move_to(Point::new(padding.left, padding.top));
+ }
+ Side::Right => {
+ text_node.move_to(Point::new(padding.left, padding.top));
+
+ icon_node.move_to(Point::new(
+ padding.left + text_bounds.width - icon_width,
+ padding.top,
+ ));
+ }
+ };
- layout::Node::with_children(text.size().pad(padding), vec![text])
+ layout::Node::with_children(
+ text_bounds.pad(padding),
+ vec![text_node, icon_node],
+ )
+ } else {
+ let mut text = layout::Node::new(text_bounds);
+ text.move_to(Point::new(padding.left, padding.top));
+
+ layout::Node::with_children(text_bounds.pad(padding), vec![text])
+ }
}
/// Processes an [`Event`] and updates the [`State`] of a [`TextInput`]
@@ -400,15 +528,16 @@ where
pub fn update<'a, Message, Renderer>(
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
value: &mut Value,
size: Option<f32>,
- font: &Renderer::Font,
+ line_height: text::LineHeight,
+ font: Option<Renderer::Font>,
is_secure: bool,
- on_change: &dyn Fn(String) -> Message,
+ on_input: Option<&dyn Fn(String) -> Message>,
on_paste: Option<&dyn Fn(String) -> Message>,
on_submit: &Option<Message>,
state: impl FnOnce() -> &'a mut State,
@@ -421,9 +550,14 @@ where
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
let state = state();
- let is_clicked = layout.bounds().contains(cursor_position);
- state.is_focused = if is_clicked {
+ let click_position = if on_input.is_some() {
+ cursor.position_over(layout.bounds())
+ } else {
+ None
+ };
+
+ state.is_focused = if click_position.is_some() {
state.is_focused.or_else(|| {
let now = Instant::now();
@@ -436,7 +570,7 @@ where
None
};
- if is_clicked {
+ if let Some(cursor_position) = click_position {
let text_layout = layout.children().next().unwrap();
let target = cursor_position.x - text_layout.bounds().x;
@@ -455,8 +589,9 @@ where
find_cursor_position(
renderer,
text_layout.bounds(),
- font.clone(),
+ font,
size,
+ line_height,
&value,
state,
target,
@@ -483,8 +618,9 @@ where
let position = find_cursor_position(
renderer,
text_layout.bounds(),
- font.clone(),
+ font,
size,
+ line_height,
value,
state,
target,
@@ -532,8 +668,9 @@ where
let position = find_cursor_position(
renderer,
text_layout.bounds(),
- font.clone(),
+ font,
size,
+ line_height,
&value,
state,
target,
@@ -551,6 +688,8 @@ where
let state = state();
if let Some(focus) = &mut state.is_focused {
+ let Some(on_input) = on_input else { return event::Status::Ignored };
+
if state.is_pasting.is_none()
&& !state.keyboard_modifiers.command()
&& !c.is_control()
@@ -559,7 +698,7 @@ where
editor.insert(c);
- let message = (on_change)(editor.contents());
+ let message = (on_input)(editor.contents());
shell.publish(message);
focus.updated_at = Instant::now();
@@ -572,6 +711,8 @@ where
let state = state();
if let Some(focus) = &mut state.is_focused {
+ let Some(on_input) = on_input else { return event::Status::Ignored };
+
let modifiers = state.keyboard_modifiers;
focus.updated_at = Instant::now();
@@ -597,7 +738,7 @@ where
let mut editor = Editor::new(value, &mut state.cursor);
editor.backspace();
- let message = (on_change)(editor.contents());
+ let message = (on_input)(editor.contents());
shell.publish(message);
}
keyboard::KeyCode::Delete => {
@@ -617,7 +758,7 @@ where
let mut editor = Editor::new(value, &mut state.cursor);
editor.delete();
- let message = (on_change)(editor.contents());
+ let message = (on_input)(editor.contents());
shell.publish(message);
}
keyboard::KeyCode::Left => {
@@ -692,7 +833,7 @@ where
let mut editor = Editor::new(value, &mut state.cursor);
editor.delete();
- let message = (on_change)(editor.contents());
+ let message = (on_input)(editor.contents());
shell.publish(message);
}
keyboard::KeyCode::V => {
@@ -719,7 +860,7 @@ where
let message = if let Some(paste) = &on_paste {
(paste)(editor.contents())
} else {
- (on_change)(editor.contents())
+ (on_input)(editor.contents())
};
shell.publish(message);
@@ -807,13 +948,16 @@ pub fn draw<Renderer>(
renderer: &mut Renderer,
theme: &Renderer::Theme,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
state: &State,
value: &Value,
placeholder: &str,
size: Option<f32>,
- font: &Renderer::Font,
+ line_height: text::LineHeight,
+ font: Option<Renderer::Font>,
+ is_disabled: bool,
is_secure: bool,
+ icon: Option<&Icon<Renderer::Font>>,
style: &<Renderer::Theme as StyleSheet>::Style,
) where
Renderer: text::Renderer,
@@ -823,11 +967,15 @@ pub fn draw<Renderer>(
let value = secure_value.as_ref().unwrap_or(value);
let bounds = layout.bounds();
- let text_bounds = layout.children().next().unwrap().bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let mut children_layout = layout.children();
+ let text_bounds = children_layout.next().unwrap().bounds();
- let appearance = if state.is_focused() {
+ let is_mouse_over = cursor.is_over(bounds);
+
+ let appearance = if is_disabled {
+ theme.disabled(style)
+ } else if state.is_focused() {
theme.focused(style)
} else if is_mouse_over {
theme.hovered(style)
@@ -838,14 +986,34 @@ pub fn draw<Renderer>(
renderer.fill_quad(
renderer::Quad {
bounds,
- border_radius: appearance.border_radius.into(),
+ border_radius: appearance.border_radius,
border_width: appearance.border_width,
border_color: appearance.border_color,
},
appearance.background,
);
+ if let Some(icon) = icon {
+ let icon_layout = children_layout.next().unwrap();
+
+ renderer.fill_text(Text {
+ content: &icon.code_point.to_string(),
+ size: icon.size.unwrap_or_else(|| renderer.default_size()),
+ line_height: text::LineHeight::default(),
+ font: icon.font,
+ color: appearance.icon_color,
+ bounds: Rectangle {
+ y: text_bounds.center_y(),
+ ..icon_layout.bounds()
+ },
+ horizontal_alignment: alignment::Horizontal::Left,
+ vertical_alignment: alignment::Vertical::Center,
+ shaping: text::Shaping::Advanced,
+ });
+ }
+
let text = value.to_string();
+ let font = font.unwrap_or_else(|| renderer.default_font());
let size = size.unwrap_or_else(|| renderer.default_size());
let (cursor, offset) = if let Some(focus) = &state.is_focused {
@@ -858,7 +1026,7 @@ pub fn draw<Renderer>(
value,
size,
position,
- font.clone(),
+ font,
);
let is_cursor_visible = ((focus.now - focus.updated_at)
@@ -899,7 +1067,7 @@ pub fn draw<Renderer>(
value,
size,
left,
- font.clone(),
+ font,
);
let (right_position, right_offset) =
@@ -909,7 +1077,7 @@ pub fn draw<Renderer>(
value,
size,
right,
- font.clone(),
+ font,
);
let width = right_position - left_position;
@@ -944,30 +1112,37 @@ pub fn draw<Renderer>(
let text_width = renderer.measure_width(
if text.is_empty() { placeholder } else { &text },
size,
- font.clone(),
+ font,
+ text::Shaping::Advanced,
);
let render = |renderer: &mut Renderer| {
if let Some((cursor, color)) = cursor {
renderer.fill_quad(cursor, color);
+ } else {
+ renderer.with_translation(Vector::ZERO, |_| {});
}
renderer.fill_text(Text {
content: if text.is_empty() { placeholder } else { &text },
color: if text.is_empty() {
theme.placeholder_color(style)
+ } else if is_disabled {
+ theme.disabled_color(style)
} else {
theme.value_color(style)
},
- font: font.clone(),
+ font,
bounds: Rectangle {
y: text_bounds.center_y(),
width: f32::INFINITY,
..text_bounds
},
size,
+ line_height,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
+ shaping: text::Shaping::Advanced,
});
};
@@ -983,10 +1158,15 @@ pub fn draw<Renderer>(
/// Computes the current [`mouse::Interaction`] of the [`TextInput`].
pub fn mouse_interaction(
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
+ is_disabled: bool,
) -> mouse::Interaction {
- if layout.bounds().contains(cursor_position) {
- mouse::Interaction::Text
+ if cursor.is_over(layout.bounds()) {
+ if is_disabled {
+ mouse::Interaction::NotAllowed
+ } else {
+ mouse::Interaction::Text
+ }
} else {
mouse::Interaction::default()
}
@@ -1109,7 +1289,7 @@ impl operation::TextInput for State {
}
mod platform {
- use crate::keyboard;
+ use crate::core::keyboard;
pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool {
if cfg!(target_os = "macos") {
@@ -1167,8 +1347,12 @@ where
{
let text_before_cursor = value.until(cursor_index).to_string();
- let text_value_width =
- renderer.measure_width(&text_before_cursor, size, font);
+ 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);
@@ -1180,8 +1364,9 @@ where
fn find_cursor_position<Renderer>(
renderer: &Renderer,
text_bounds: Rectangle,
- font: Renderer::Font,
+ font: Option<Renderer::Font>,
size: Option<f32>,
+ line_height: text::LineHeight,
value: &Value,
state: &State,
x: f32,
@@ -1189,21 +1374,32 @@ fn find_cursor_position<Renderer>(
where
Renderer: text::Renderer,
{
+ let font = font.unwrap_or_else(|| renderer.default_font());
let size = size.unwrap_or_else(|| renderer.default_size());
- let offset =
- offset(renderer, text_bounds, font.clone(), size, value, state);
+ let offset = offset(renderer, text_bounds, font, size, value, state);
+ let value = value.to_string();
- renderer
+ let char_offset = renderer
.hit_test(
- &value.to_string(),
+ &value,
size,
+ line_height,
font,
Size::INFINITY,
+ text::Shaping::Advanced,
Point::new(x + offset, text_bounds.height / 2.0),
true,
)
- .map(text::Hit::cursor)
+ .map(text::Hit::cursor)?;
+
+ Some(
+ unicode_segmentation::UnicodeSegmentation::graphemes(
+ &value[..char_offset],
+ true,
+ )
+ .count(),
+ )
}
const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
diff --git a/native/src/widget/text_input/cursor.rs b/widget/src/text_input/cursor.rs
index 4f3b159b..9680dfd7 100644
--- a/native/src/widget/text_input/cursor.rs
+++ b/widget/src/text_input/cursor.rs
@@ -1,5 +1,5 @@
//! Track the cursor of a text input.
-use crate::widget::text_input::Value;
+use crate::text_input::Value;
/// The cursor of a text input.
#[derive(Debug, Copy, Clone)]
diff --git a/native/src/widget/text_input/editor.rs b/widget/src/text_input/editor.rs
index d53fa8d9..f1fd641f 100644
--- a/native/src/widget/text_input/editor.rs
+++ b/widget/src/text_input/editor.rs
@@ -1,4 +1,4 @@
-use crate::widget::text_input::{Cursor, Value};
+use crate::text_input::{Cursor, Value};
pub struct Editor<'a> {
value: &'a mut Value,
diff --git a/native/src/widget/text_input/value.rs b/widget/src/text_input/value.rs
index cf4da562..cf4da562 100644
--- a/native/src/widget/text_input/value.rs
+++ b/widget/src/text_input/value.rs
diff --git a/native/src/widget/toggler.rs b/widget/src/toggler.rs
index a434af65..1b31765f 100644
--- a/native/src/widget/toggler.rs
+++ b/widget/src/toggler.rs
@@ -1,24 +1,27 @@
//! Show toggle controls using togglers.
-use crate::alignment;
-use crate::event;
-use crate::layout;
-use crate::mouse;
-use crate::renderer;
-use crate::text;
-use crate::widget::{self, Row, Text, Tree};
-use crate::{
- Alignment, Clipboard, Element, Event, Layout, Length, Pixels, Point,
- Rectangle, Shell, Widget,
+use crate::core::alignment;
+use crate::core::event;
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::renderer;
+use crate::core::text;
+use crate::core::touch;
+use crate::core::widget::Tree;
+use crate::core::{
+ Alignment, Clipboard, Element, Event, Layout, Length, Pixels, Rectangle,
+ Shell, Widget,
};
+use crate::{Row, Text};
-pub use iced_style::toggler::{Appearance, StyleSheet};
+pub use crate::style::toggler::{Appearance, StyleSheet};
/// A toggler widget.
///
/// # Example
///
-/// ```
-/// # type Toggler<'a, Message> = iced_native::widget::Toggler<'a, Message, iced_native::renderer::Null>;
+/// ```no_run
+/// # type Toggler<'a, Message> =
+/// # iced_widget::Toggler<'a, Message, iced_widget::renderer::Renderer<iced_widget::style::Theme>>;
/// #
/// pub enum Message {
/// TogglerToggled(bool),
@@ -29,7 +32,7 @@ pub use iced_style::toggler::{Appearance, StyleSheet};
/// Toggler::new(String::from("Toggle me!"), is_toggled, |b| Message::TogglerToggled(b));
/// ```
#[allow(missing_debug_implementations)]
-pub struct Toggler<'a, Message, Renderer>
+pub struct Toggler<'a, Message, Renderer = crate::Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
@@ -40,9 +43,11 @@ where
width: Length,
size: f32,
text_size: Option<f32>,
+ text_line_height: text::LineHeight,
text_alignment: alignment::Horizontal,
+ text_shaping: text::Shaping,
spacing: f32,
- font: Renderer::Font,
+ font: Option<Renderer::Font>,
style: <Renderer::Theme as StyleSheet>::Style,
}
@@ -77,9 +82,11 @@ where
width: Length::Fill,
size: Self::DEFAULT_SIZE,
text_size: None,
+ text_line_height: text::LineHeight::default(),
text_alignment: alignment::Horizontal::Left,
+ text_shaping: text::Shaping::Basic,
spacing: 0.0,
- font: Renderer::Font::default(),
+ font: None,
style: Default::default(),
}
}
@@ -102,12 +109,27 @@ where
self
}
+ /// Sets the text [`LineHeight`] of the [`Toggler`].
+ pub fn text_line_height(
+ mut self,
+ line_height: impl Into<text::LineHeight>,
+ ) -> Self {
+ self.text_line_height = line_height.into();
+ self
+ }
+
/// Sets the horizontal alignment of the text of the [`Toggler`]
pub fn text_alignment(mut self, alignment: alignment::Horizontal) -> Self {
self.text_alignment = alignment;
self
}
+ /// Sets the [`text::Shaping`] strategy of the [`Toggler`].
+ pub fn text_shaping(mut self, shaping: text::Shaping) -> Self {
+ self.text_shaping = shaping;
+ self
+ }
+
/// Sets the spacing between the [`Toggler`] and the text.
pub fn spacing(mut self, spacing: impl Into<Pixels>) -> Self {
self.spacing = spacing.into().0;
@@ -117,8 +139,8 @@ where
/// Sets the [`Font`] of the text of the [`Toggler`]
///
/// [`Font`]: crate::text::Renderer::Font
- pub fn font(mut self, font: Renderer::Font) -> Self {
- self.font = font;
+ pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
+ self.font = Some(font.into());
self
}
@@ -136,7 +158,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
for Toggler<'a, Message, Renderer>
where
Renderer: text::Renderer,
- Renderer::Theme: StyleSheet + widget::text::StyleSheet,
+ Renderer::Theme: StyleSheet + crate::text::StyleSheet,
{
fn width(&self) -> Length {
self.width
@@ -160,12 +182,14 @@ where
row = row.push(
Text::new(label)
.horizontal_alignment(self.text_alignment)
- .font(self.font.clone())
+ .font(self.font.unwrap_or_else(|| renderer.default_font()))
.width(self.width)
.size(
self.text_size
.unwrap_or_else(|| renderer.default_size()),
- ),
+ )
+ .line_height(self.text_line_height)
+ .shaping(self.text_shaping),
);
}
@@ -179,14 +203,15 @@ where
_state: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
- let mouse_over = layout.bounds().contains(cursor_position);
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
+ let mouse_over = cursor.is_over(layout.bounds());
if mouse_over {
shell.publish((self.on_toggle)(!self.is_toggled));
@@ -204,11 +229,11 @@ where
&self,
_state: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- if layout.bounds().contains(cursor_position) {
+ if cursor.is_over(layout.bounds()) {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
@@ -222,7 +247,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
/// Makes sure that the border radius of the toggler looks good at every size.
@@ -237,23 +262,25 @@ where
if let Some(label) = &self.label {
let label_layout = children.next().unwrap();
- crate::widget::text::draw(
+ crate::text::draw(
renderer,
style,
label_layout,
label,
self.text_size,
- self.font.clone(),
+ self.text_line_height,
+ self.font,
Default::default(),
self.text_alignment,
alignment::Vertical::Center,
+ self.text_shaping,
);
}
let toggler_layout = children.next().unwrap();
let bounds = toggler_layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(layout.bounds());
let style = if is_mouse_over {
theme.hovered(&self.style, self.is_toggled)
@@ -314,7 +341,7 @@ impl<'a, Message, Renderer> From<Toggler<'a, Message, Renderer>>
where
Message: 'a,
Renderer: 'a + text::Renderer,
- Renderer::Theme: StyleSheet + widget::text::StyleSheet,
+ Renderer::Theme: StyleSheet + crate::text::StyleSheet,
{
fn from(
toggler: Toggler<'a, Message, Renderer>,
diff --git a/native/src/widget/tooltip.rs b/widget/src/tooltip.rs
index 2a24c055..2dc3da01 100644
--- a/native/src/widget/tooltip.rs
+++ b/widget/src/tooltip.rs
@@ -1,26 +1,26 @@
//! Display a widget over another.
-use crate::event;
-use crate::layout;
-use crate::mouse;
-use crate::renderer;
-use crate::text;
-use crate::widget;
-use crate::widget::container;
-use crate::widget::overlay;
-use crate::widget::{Text, Tree};
-use crate::{
- Clipboard, Element, Event, Layout, Length, Padding, Pixels, Point,
- Rectangle, Shell, Size, Vector, Widget,
+use crate::container;
+use crate::core::event::{self, Event};
+use crate::core::layout::{self, Layout};
+use crate::core::mouse;
+use crate::core::overlay;
+use crate::core::renderer;
+use crate::core::text;
+use crate::core::widget::{self, Widget};
+use crate::core::{
+ Clipboard, Element, Length, Padding, Pixels, Point, Rectangle, Shell, Size,
+ Vector,
};
+use crate::Text;
use std::borrow::Cow;
/// An element to display a widget over another.
#[allow(missing_debug_implementations)]
-pub struct Tooltip<'a, Message, Renderer: text::Renderer>
+pub struct Tooltip<'a, Message, Renderer = crate::Renderer>
where
Renderer: text::Renderer,
- Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
+ Renderer::Theme: container::StyleSheet + crate::text::StyleSheet,
{
content: Element<'a, Message, Renderer>,
tooltip: Text<'a, Renderer>,
@@ -34,7 +34,7 @@ where
impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer>
where
Renderer: text::Renderer,
- Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
+ Renderer::Theme: container::StyleSheet + crate::text::StyleSheet,
{
/// The default padding of a [`Tooltip`] drawn by this renderer.
const DEFAULT_PADDING: f32 = 5.0;
@@ -104,16 +104,24 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
for Tooltip<'a, Message, Renderer>
where
Renderer: text::Renderer,
- Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
+ Renderer::Theme: container::StyleSheet + crate::text::StyleSheet,
{
- fn children(&self) -> Vec<Tree> {
- vec![Tree::new(&self.content)]
+ fn children(&self) -> Vec<widget::Tree> {
+ vec![widget::Tree::new(&self.content)]
}
- fn diff(&self, tree: &mut Tree) {
+ fn diff(&self, tree: &mut widget::Tree) {
tree.diff_children(std::slice::from_ref(&self.content))
}
+ fn state(&self) -> widget::tree::State {
+ widget::tree::State::new(State::default())
+ }
+
+ fn tag(&self) -> widget::tree::Tag {
+ widget::tree::Tag::of::<State>()
+ }
+
fn width(&self) -> Length {
self.content.as_widget().width()
}
@@ -132,19 +140,26 @@ where
fn on_event(
&mut self,
- tree: &mut Tree,
+ tree: &mut widget::Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
+ let state = tree.state.downcast_mut::<State>();
+
+ *state = cursor
+ .position_over(layout.bounds())
+ .map(|cursor_position| State::Hovered { cursor_position })
+ .unwrap_or_default();
+
self.content.as_widget_mut().on_event(
&mut tree.children[0],
event,
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -153,16 +168,16 @@ where
fn mouse_interaction(
&self,
- tree: &Tree,
+ tree: &widget::Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.as_widget().mouse_interaction(
&tree.children[0],
layout,
- cursor_position,
+ cursor,
viewport,
renderer,
)
@@ -170,12 +185,12 @@ where
fn draw(
&self,
- tree: &Tree,
+ tree: &widget::Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
inherited_style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
self.content.as_widget().draw(
@@ -184,53 +199,53 @@ where
theme,
inherited_style,
layout,
- cursor_position,
- viewport,
- );
-
- let tooltip = &self.tooltip;
-
- draw(
- renderer,
- theme,
- inherited_style,
- layout,
- cursor_position,
+ cursor,
viewport,
- self.position,
- self.gap,
- self.padding,
- self.snap_within_viewport,
- &self.style,
- |renderer, limits| {
- Widget::<(), Renderer>::layout(tooltip, renderer, limits)
- },
- |renderer, defaults, layout, cursor_position, viewport| {
- Widget::<(), Renderer>::draw(
- tooltip,
- &Tree::empty(),
- renderer,
- theme,
- defaults,
- layout,
- cursor_position,
- viewport,
- );
- },
);
}
fn overlay<'b>(
&'b mut self,
- tree: &'b mut Tree,
+ tree: &'b mut widget::Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
- self.content.as_widget_mut().overlay(
+ let state = tree.state.downcast_ref::<State>();
+
+ let content = self.content.as_widget_mut().overlay(
&mut tree.children[0],
layout,
renderer,
- )
+ );
+
+ let tooltip = if let State::Hovered { cursor_position } = *state {
+ Some(overlay::Element::new(
+ layout.position(),
+ Box::new(Overlay {
+ tooltip: &self.tooltip,
+ cursor_position,
+ content_bounds: layout.bounds(),
+ snap_within_viewport: self.snap_within_viewport,
+ position: self.position,
+ gap: self.gap,
+ padding: self.padding,
+ style: &self.style,
+ }),
+ ))
+ } else {
+ None
+ };
+
+ if content.is_some() || tooltip.is_some() {
+ Some(
+ overlay::Group::with_children(
+ content.into_iter().chain(tooltip).collect(),
+ )
+ .overlay(),
+ )
+ } else {
+ None
+ }
}
}
@@ -239,7 +254,7 @@ impl<'a, Message, Renderer> From<Tooltip<'a, Message, Renderer>>
where
Message: 'a,
Renderer: 'a + text::Renderer,
- Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
+ Renderer::Theme: container::StyleSheet + crate::text::StyleSheet,
{
fn from(
tooltip: Tooltip<'a, Message, Renderer>,
@@ -263,90 +278,107 @@ pub enum Position {
Right,
}
-/// Draws a [`Tooltip`].
-pub fn draw<Renderer>(
- renderer: &mut Renderer,
- theme: &Renderer::Theme,
- inherited_style: &renderer::Style,
- layout: Layout<'_>,
+#[derive(Debug, Clone, Copy, Default)]
+enum State {
+ #[default]
+ Idle,
+ Hovered {
+ cursor_position: Point,
+ },
+}
+
+struct Overlay<'a, 'b, Renderer>
+where
+ Renderer: text::Renderer,
+ Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
+{
+ tooltip: &'b Text<'a, Renderer>,
cursor_position: Point,
- viewport: &Rectangle,
+ content_bounds: Rectangle,
+ snap_within_viewport: bool,
position: Position,
gap: f32,
padding: f32,
- snap_within_viewport: bool,
- style: &<Renderer::Theme as container::StyleSheet>::Style,
- layout_text: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
- draw_text: impl FnOnce(
- &mut Renderer,
- &renderer::Style,
- Layout<'_>,
- Point,
- &Rectangle,
- ),
-) where
- Renderer: crate::Renderer,
- Renderer::Theme: container::StyleSheet,
-{
- use container::StyleSheet;
-
- let bounds = layout.bounds();
-
- if bounds.contains(cursor_position) {
- let style = theme.appearance(style);
+ style: &'b <Renderer::Theme as container::StyleSheet>::Style,
+}
- let defaults = renderer::Style {
- text_color: style.text_color.unwrap_or(inherited_style.text_color),
- };
+impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer>
+ for Overlay<'a, 'b, Renderer>
+where
+ Renderer: text::Renderer,
+ Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
+{
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ bounds: Size,
+ _position: Point,
+ ) -> layout::Node {
+ let viewport = Rectangle::with_size(bounds);
- let text_layout = layout_text(
+ let text_layout = Widget::<(), Renderer>::layout(
+ self.tooltip,
renderer,
&layout::Limits::new(
Size::ZERO,
- snap_within_viewport
+ self.snap_within_viewport
.then(|| viewport.size())
.unwrap_or(Size::INFINITY),
)
- .pad(Padding::new(padding)),
+ .pad(Padding::new(self.padding)),
);
let text_bounds = text_layout.bounds();
- let x_center = bounds.x + (bounds.width - text_bounds.width) / 2.0;
- let y_center = bounds.y + (bounds.height - text_bounds.height) / 2.0;
+ let x_center = self.content_bounds.x
+ + (self.content_bounds.width - text_bounds.width) / 2.0;
+ let y_center = self.content_bounds.y
+ + (self.content_bounds.height - text_bounds.height) / 2.0;
let mut tooltip_bounds = {
- let offset = match position {
+ let offset = match self.position {
Position::Top => Vector::new(
x_center,
- bounds.y - text_bounds.height - gap - padding,
+ self.content_bounds.y
+ - text_bounds.height
+ - self.gap
+ - self.padding,
),
Position::Bottom => Vector::new(
x_center,
- bounds.y + bounds.height + gap + padding,
+ self.content_bounds.y
+ + self.content_bounds.height
+ + self.gap
+ + self.padding,
),
Position::Left => Vector::new(
- bounds.x - text_bounds.width - gap - padding,
+ self.content_bounds.x
+ - text_bounds.width
+ - self.gap
+ - self.padding,
y_center,
),
Position::Right => Vector::new(
- bounds.x + bounds.width + gap + padding,
+ self.content_bounds.x
+ + self.content_bounds.width
+ + self.gap
+ + self.padding,
y_center,
),
Position::FollowCursor => Vector::new(
- cursor_position.x,
- cursor_position.y - text_bounds.height,
+ self.cursor_position.x,
+ self.cursor_position.y - text_bounds.height,
),
};
Rectangle {
- x: offset.x - padding,
- y: offset.y - padding,
- width: text_bounds.width + padding * 2.0,
- height: text_bounds.height + padding * 2.0,
+ x: offset.x - self.padding,
+ y: offset.y - self.padding,
+ width: text_bounds.width + self.padding * 2.0,
+ height: text_bounds.height + self.padding * 2.0,
}
};
- if snap_within_viewport {
+ if self.snap_within_viewport {
if tooltip_bounds.x < viewport.x {
tooltip_bounds.x = viewport.x;
} else if viewport.x + viewport.width
@@ -366,22 +398,49 @@ pub fn draw<Renderer>(
}
}
- renderer.with_layer(Rectangle::with_size(Size::INFINITY), |renderer| {
- container::draw_background(renderer, &style, tooltip_bounds);
-
- draw_text(
- renderer,
- &defaults,
- Layout::with_offset(
- Vector::new(
- tooltip_bounds.x + padding,
- tooltip_bounds.y + padding,
- ),
- &text_layout,
- ),
- cursor_position,
- viewport,
- )
- });
+ layout::Node::with_children(
+ tooltip_bounds.size(),
+ vec![text_layout.translate(Vector::new(self.padding, self.padding))],
+ )
+ .translate(Vector::new(tooltip_bounds.x, tooltip_bounds.y))
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ theme: &<Renderer as renderer::Renderer>::Theme,
+ inherited_style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: mouse::Cursor,
+ ) {
+ let style = <Renderer::Theme as container::StyleSheet>::appearance(
+ theme, self.style,
+ );
+
+ container::draw_background(renderer, &style, layout.bounds());
+
+ let defaults = renderer::Style {
+ text_color: style.text_color.unwrap_or(inherited_style.text_color),
+ };
+
+ Widget::<(), Renderer>::draw(
+ self.tooltip,
+ &widget::Tree::empty(),
+ renderer,
+ theme,
+ &defaults,
+ layout.children().next().unwrap(),
+ cursor_position,
+ &Rectangle::with_size(Size::INFINITY),
+ );
+ }
+
+ fn is_over(
+ &self,
+ _layout: Layout<'_>,
+ _renderer: &Renderer,
+ _cursor_position: Point,
+ ) -> bool {
+ false
}
}
diff --git a/native/src/widget/vertical_slider.rs b/widget/src/vertical_slider.rs
index f1687e38..91f2b466 100644
--- a/native/src/widget/vertical_slider.rs
+++ b/widget/src/vertical_slider.rs
@@ -3,13 +3,18 @@
//! A [`VerticalSlider`] has some local [`State`].
use std::ops::RangeInclusive;
-pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet};
-
-use crate::event::{self, Event};
-use crate::widget::tree::{self, Tree};
-use crate::{
- layout, mouse, renderer, touch, Background, Clipboard, Color, Element,
- Layout, Length, Pixels, Point, Rectangle, Shell, Size, Widget,
+pub use crate::style::slider::{Appearance, Handle, HandleShape, StyleSheet};
+
+use crate::core;
+use crate::core::event::{self, Event};
+use crate::core::layout::{self, Layout};
+use crate::core::mouse;
+use crate::core::renderer;
+use crate::core::touch;
+use crate::core::widget::tree::{self, Tree};
+use crate::core::{
+ Clipboard, Color, Element, Length, Pixels, Point, Rectangle, Shell, Size,
+ Widget,
};
/// An vertical bar and a handle that selects a single value from a range of
@@ -21,11 +26,9 @@ use crate::{
/// to 1 unit.
///
/// # Example
-/// ```
-/// # use iced_native::widget::vertical_slider;
-/// # use iced_native::renderer::Null;
-/// #
-/// # type VerticalSlider<'a, T, Message> = vertical_slider::VerticalSlider<'a, T, Message, Null>;
+/// ```no_run
+/// # type VerticalSlider<'a, T, Message> =
+/// # iced_widget::VerticalSlider<'a, T, Message, iced_widget::renderer::Renderer<iced_widget::style::Theme>>;
/// #
/// #[derive(Clone)]
/// pub enum Message {
@@ -37,9 +40,9 @@ use crate::{
/// VerticalSlider::new(0.0..=100.0, value, Message::SliderChanged);
/// ```
#[allow(missing_debug_implementations)]
-pub struct VerticalSlider<'a, T, Message, Renderer>
+pub struct VerticalSlider<'a, T, Message, Renderer = crate::Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: core::Renderer,
Renderer::Theme: StyleSheet,
{
range: RangeInclusive<T>,
@@ -56,7 +59,7 @@ impl<'a, T, Message, Renderer> VerticalSlider<'a, T, Message, Renderer>
where
T: Copy + From<u8> + std::cmp::PartialOrd,
Message: Clone,
- Renderer: crate::Renderer,
+ Renderer: core::Renderer,
Renderer::Theme: StyleSheet,
{
/// The default width of a [`VerticalSlider`].
@@ -142,7 +145,7 @@ impl<'a, T, Message, Renderer> Widget<Message, Renderer>
where
T: Copy + Into<f64> + num_traits::FromPrimitive,
Message: Clone,
- Renderer: crate::Renderer,
+ Renderer: core::Renderer,
Renderer::Theme: StyleSheet,
{
fn tag(&self) -> tree::Tag {
@@ -177,7 +180,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -185,7 +188,7 @@ where
update(
event,
layout,
- cursor_position,
+ cursor,
shell,
tree.state.downcast_mut::<State>(),
&mut self.value,
@@ -203,13 +206,13 @@ where
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
draw(
renderer,
layout,
- cursor_position,
+ cursor,
tree.state.downcast_ref::<State>(),
self.value,
&self.range,
@@ -222,15 +225,11 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- mouse_interaction(
- layout,
- cursor_position,
- tree.state.downcast_ref::<State>(),
- )
+ mouse_interaction(layout, cursor, tree.state.downcast_ref::<State>())
}
}
@@ -239,7 +238,7 @@ impl<'a, T, Message, Renderer> From<VerticalSlider<'a, T, Message, Renderer>>
where
T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,
Message: 'a + Clone,
- Renderer: 'a + crate::Renderer,
+ Renderer: 'a + core::Renderer,
Renderer::Theme: StyleSheet,
{
fn from(
@@ -254,7 +253,7 @@ where
pub fn update<Message, T>(
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
shell: &mut Shell<'_, Message>,
state: &mut State,
value: &mut T,
@@ -269,8 +268,9 @@ where
{
let is_dragging = state.is_dragging;
- let mut change = || {
+ let mut change = |cursor_position: Point| {
let bounds = layout.bounds();
+
let new_value = if cursor_position.y >= bounds.y + bounds.height {
*range.start()
} else if cursor_position.y <= bounds.y {
@@ -304,8 +304,9 @@ where
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
- if layout.bounds().contains(cursor_position) {
- change();
+ if let Some(cursor_position) = cursor.position_over(layout.bounds())
+ {
+ change(cursor_position);
state.is_dragging = true;
return event::Status::Captured;
@@ -326,7 +327,7 @@ where
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. }) => {
if is_dragging {
- change();
+ let _ = cursor.position().map(change);
return event::Status::Captured;
}
@@ -341,7 +342,7 @@ where
pub fn draw<T, R>(
renderer: &mut R,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
state: &State,
value: T,
range: &RangeInclusive<T>,
@@ -349,11 +350,11 @@ pub fn draw<T, R>(
style: &<R::Theme as StyleSheet>::Style,
) where
T: Into<f64> + Copy,
- R: crate::Renderer,
+ R: core::Renderer,
R::Theme: StyleSheet,
{
let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(bounds);
let style = if state.is_dragging {
style_sheet.dragging(style)
@@ -363,72 +364,72 @@ pub fn draw<T, R>(
style_sheet.active(style)
};
- let rail_x = bounds.x + (bounds.width / 2.0).round();
+ let (handle_width, handle_height, handle_border_radius) =
+ match style.handle.shape {
+ HandleShape::Circle { radius } => {
+ (radius * 2.0, radius * 2.0, radius.into())
+ }
+ HandleShape::Rectangle {
+ width,
+ border_radius,
+ } => (f32::from(width), bounds.width, border_radius),
+ };
+
+ let value = value.into() as f32;
+ let (range_start, range_end) = {
+ let (start, end) = range.clone().into_inner();
+
+ (start.into() as f32, end.into() as f32)
+ };
+
+ let offset = if range_start >= range_end {
+ 0.0
+ } else {
+ (bounds.height - handle_width) * (value - range_end)
+ / (range_start - range_end)
+ };
+
+ let rail_x = bounds.x + bounds.width / 2.0;
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
- x: rail_x - 1.0,
+ x: rail_x - style.rail.width / 2.0,
y: bounds.y,
- width: 2.0,
- height: bounds.height,
+ width: style.rail.width,
+ height: offset + handle_width / 2.0,
},
- border_radius: 0.0.into(),
+ border_radius: style.rail.border_radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
- style.rail_colors.0,
+ style.rail.colors.1,
);
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
- x: rail_x + 1.0,
- y: bounds.y,
- width: 2.0,
- height: bounds.height,
+ x: rail_x - style.rail.width / 2.0,
+ y: bounds.y + offset + handle_width / 2.0,
+ width: style.rail.width,
+ height: bounds.height - offset - handle_width / 2.0,
},
- border_radius: 0.0.into(),
+ border_radius: style.rail.border_radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
- Background::Color(style.rail_colors.1),
+ style.rail.colors.0,
);
- let (handle_width, handle_height, handle_border_radius) = match style
- .handle
- .shape
- {
- HandleShape::Circle { radius } => (radius * 2.0, radius * 2.0, radius),
- HandleShape::Rectangle {
- width,
- border_radius,
- } => (f32::from(width), bounds.width, border_radius),
- };
-
- let value = value.into() as f32;
- let (range_start, range_end) = {
- let (start, end) = range.clone().into_inner();
-
- (start.into() as f32, end.into() as f32)
- };
-
- let handle_offset = if range_start >= range_end {
- 0.0
- } else {
- (bounds.height - handle_width) * (value - range_end)
- / (range_start - range_end)
- };
-
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
- x: rail_x - (handle_height / 2.0),
- y: bounds.y + handle_offset.round(),
+ x: rail_x - handle_height / 2.0,
+ y: bounds.y + offset,
width: handle_height,
height: handle_width,
},
- border_radius: handle_border_radius.into(),
+ border_radius: handle_border_radius,
border_width: style.handle.border_width,
border_color: style.handle.border_color,
},
@@ -439,11 +440,11 @@ pub fn draw<T, R>(
/// Computes the current [`mouse::Interaction`] of a [`VerticalSlider`].
pub fn mouse_interaction(
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
state: &State,
) -> mouse::Interaction {
let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(bounds);
if state.is_dragging {
mouse::Interaction::Grabbing