summaryrefslogtreecommitdiffstats
path: root/widget
diff options
context:
space:
mode:
authorLibravatar Bingus <shankern@protonmail.com>2023-07-12 12:23:18 -0700
committerLibravatar Bingus <shankern@protonmail.com>2023-07-12 12:23:18 -0700
commit633f405f3f78bc7f82d2b2061491b0e011137451 (patch)
tree5ebfc1f45d216a5c14a90492563599e6969eab4d /widget
parent41836dd80d0534608e7aedfbf2319c540a23de1a (diff)
parent21bd51426d900e271206f314e0c915dd41065521 (diff)
downloadiced-633f405f3f78bc7f82d2b2061491b0e011137451.tar.gz
iced-633f405f3f78bc7f82d2b2061491b0e011137451.tar.bz2
iced-633f405f3f78bc7f82d2b2061491b0e011137451.zip
Merge remote-tracking branch 'origin/master' into feat/multi-window-support
# Conflicts: # Cargo.toml # core/src/window/icon.rs # core/src/window/id.rs # core/src/window/position.rs # core/src/window/settings.rs # examples/integration/src/main.rs # examples/integration_opengl/src/main.rs # glutin/src/application.rs # native/src/subscription.rs # native/src/window.rs # runtime/src/window/action.rs # src/lib.rs # src/window.rs # winit/Cargo.toml # winit/src/application.rs # winit/src/icon.rs # winit/src/settings.rs # winit/src/window.rs
Diffstat (limited to '')
-rw-r--r--widget/Cargo.toml37
-rw-r--r--widget/src/button.rs (renamed from native/src/widget/button.rs)92
-rw-r--r--widget/src/canvas.rs (renamed from graphics/src/widget/canvas.rs)145
-rw-r--r--widget/src/canvas/event.rs (renamed from graphics/src/widget/canvas/event.rs)8
-rw-r--r--widget/src/canvas/program.rs (renamed from graphics/src/widget/canvas/program.rs)42
-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/helpers.rs368
-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/lazy.rs (renamed from lazy/src/lazy.rs)134
-rw-r--r--widget/src/lazy/cache.rs (renamed from lazy/src/cache.rs)4
-rw-r--r--widget/src/lazy/component.rs (renamed from lazy/src/component.rs)356
-rw-r--r--widget/src/lazy/helpers.rs (renamed from lazy/src/lib.rs)45
-rw-r--r--widget/src/lazy/responsive.rs (renamed from lazy/src/responsive.rs)181
-rw-r--r--widget/src/lib.rs126
-rw-r--r--widget/src/mouse_area.rs311
-rw-r--r--widget/src/overlay.rs (renamed from graphics/src/overlay.rs)0
-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/qr_code.rs (renamed from graphics/src/widget/qr_code.rs)104
-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.rs6
-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
49 files changed, 3768 insertions, 1905 deletions
diff --git a/widget/Cargo.toml b/widget/Cargo.toml
new file mode 100644
index 00000000..14aae72e
--- /dev/null
+++ b/widget/Cargo.toml
@@ -0,0 +1,37 @@
+[package]
+name = "iced_widget"
+version = "0.1.0"
+edition = "2021"
+
+[features]
+lazy = ["ouroboros"]
+image = ["iced_renderer/image"]
+svg = ["iced_renderer/svg"]
+canvas = ["iced_renderer/geometry"]
+qr_code = ["canvas", "qrcode"]
+
+[dependencies]
+unicode-segmentation = "1.6"
+num-traits = "0.2"
+thiserror = "1"
+
+[dependencies.iced_runtime]
+version = "0.1"
+path = "../runtime"
+
+[dependencies.iced_renderer]
+version = "0.1"
+path = "../renderer"
+
+[dependencies.iced_style]
+version = "0.8"
+path = "../style"
+
+[dependencies.ouroboros]
+version = "0.17"
+optional = true
+
+[dependencies.qrcode]
+version = "0.12"
+optional = true
+default-features = false
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/graphics/src/widget/canvas.rs b/widget/src/canvas.rs
index a8d050f5..96062038 100644
--- a/graphics/src/widget/canvas.rs
+++ b/widget/src/canvas.rs
@@ -1,43 +1,22 @@
//! Draw 2D graphics for your users.
-//!
-//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a
-//! [`Frame`]. It can be used for animation, data visualization, game graphics,
-//! and more!
pub mod event;
-pub mod fill;
-pub mod path;
-pub mod stroke;
-mod cache;
-mod cursor;
-mod frame;
-mod geometry;
mod program;
-mod style;
-mod text;
-pub use crate::gradient::{self, Gradient};
-pub use cache::Cache;
-pub use cursor::Cursor;
pub use event::Event;
-pub use fill::{Fill, FillRule};
-pub use frame::Frame;
-pub use geometry::Geometry;
-pub use path::Path;
pub use program::Program;
-pub use stroke::{LineCap, LineDash, LineJoin, Stroke};
-pub use style::Style;
-pub use text::Text;
-use crate::{Backend, Primitive, Renderer};
+pub use crate::graphics::geometry::*;
+pub use crate::renderer::geometry::*;
-use iced_native::layout::{self, Layout};
-use iced_native::mouse;
-use iced_native::renderer;
-use iced_native::widget::tree::{self, Tree};
-use iced_native::{
- Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector, Widget,
-};
+use crate::core;
+use crate::core::layout::{self, Layout};
+use crate::core::mouse;
+use crate::core::renderer;
+use crate::core::widget::tree::{self, Tree};
+use crate::core::{Clipboard, Element, Shell, Widget};
+use crate::core::{Length, Rectangle, Size, Vector};
+use crate::graphics::geometry;
use std::marker::PhantomData;
@@ -47,15 +26,12 @@ use std::marker::PhantomData;
/// If you want to get a quick overview, here's how we can draw a simple circle:
///
/// ```no_run
-/// # mod iced {
-/// # pub mod widget {
-/// # pub use iced_graphics::widget::canvas;
-/// # }
-/// # pub use iced_native::{Color, Rectangle, Theme};
-/// # }
-/// use iced::widget::canvas::{self, Canvas, Cursor, Fill, Frame, Geometry, Path, Program};
-/// use iced::{Color, Rectangle, Theme};
-///
+/// # use iced_widget::canvas::{self, Canvas, Fill, Frame, Geometry, Path, Program};
+/// # use iced_widget::core::{Color, Rectangle};
+/// # use iced_widget::core::mouse;
+/// # use iced_widget::style::Theme;
+/// #
+/// # pub type Renderer = iced_widget::renderer::Renderer<Theme>;
/// // First, we define the data we need for drawing
/// #[derive(Debug)]
/// struct Circle {
@@ -66,9 +42,9 @@ use std::marker::PhantomData;
/// impl Program<()> for Circle {
/// type State = ();
///
-/// fn draw(&self, _state: &(), _theme: &Theme, bounds: Rectangle, _cursor: Cursor) -> Vec<Geometry>{
+/// fn draw(&self, _state: &(), renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) -> Vec<Geometry>{
/// // We prepare a new `Frame`
-/// let mut frame = Frame::new(bounds.size());
+/// let mut frame = Frame::new(renderer, bounds.size());
///
/// // We create a `Path` representing a simple circle
/// let circle = Path::circle(frame.center(), self.radius);
@@ -85,20 +61,22 @@ use std::marker::PhantomData;
/// let canvas = Canvas::new(Circle { radius: 50.0 });
/// ```
#[derive(Debug)]
-pub struct Canvas<Message, Theme, P>
+pub struct Canvas<P, Message, Renderer = crate::Renderer>
where
- P: Program<Message, Theme>,
+ Renderer: geometry::Renderer,
+ P: Program<Message, Renderer>,
{
width: Length,
height: Length,
program: P,
message_: PhantomData<Message>,
- theme_: PhantomData<Theme>,
+ theme_: PhantomData<Renderer>,
}
-impl<Message, Theme, P> Canvas<Message, Theme, P>
+impl<P, Message, Renderer> Canvas<P, Message, Renderer>
where
- P: Program<Message, Theme>,
+ Renderer: geometry::Renderer,
+ P: Program<Message, Renderer>,
{
const DEFAULT_SIZE: f32 = 100.0;
@@ -126,10 +104,11 @@ where
}
}
-impl<Message, P, B, T> Widget<Message, Renderer<B, T>> for Canvas<Message, T, P>
+impl<P, Message, Renderer> Widget<Message, Renderer>
+ for Canvas<P, Message, Renderer>
where
- P: Program<Message, T>,
- B: Backend,
+ Renderer: geometry::Renderer,
+ P: Program<Message, Renderer>,
{
fn tag(&self) -> tree::Tag {
struct Tag<T>(T);
@@ -150,7 +129,7 @@ where
fn layout(
&self,
- _renderer: &Renderer<B, T>,
+ _renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.width(self.width).height(self.height);
@@ -162,30 +141,24 @@ where
fn on_event(
&mut self,
tree: &mut Tree,
- event: iced_native::Event,
+ event: core::Event,
layout: Layout<'_>,
- cursor_position: Point,
- _renderer: &Renderer<B, T>,
+ cursor: mouse::Cursor,
+ _renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
let bounds = layout.bounds();
let canvas_event = match event {
- iced_native::Event::Mouse(mouse_event) => {
- Some(Event::Mouse(mouse_event))
- }
- iced_native::Event::Touch(touch_event) => {
- Some(Event::Touch(touch_event))
- }
- iced_native::Event::Keyboard(keyboard_event) => {
+ core::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)),
+ core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)),
+ core::Event::Keyboard(keyboard_event) => {
Some(Event::Keyboard(keyboard_event))
}
_ => None,
};
- let cursor = Cursor::from_window_position(cursor_position);
-
if let Some(canvas_event) = canvas_event {
let state = tree.state.downcast_mut::<P::State>();
@@ -206,12 +179,11 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
- _renderer: &Renderer<B, T>,
+ _renderer: &Renderer,
) -> mouse::Interaction {
let bounds = layout.bounds();
- let cursor = Cursor::from_window_position(cursor_position);
let state = tree.state.downcast_ref::<P::State>();
self.program.mouse_interaction(state, bounds, cursor)
@@ -220,49 +192,42 @@ where
fn draw(
&self,
tree: &Tree,
- renderer: &mut Renderer<B, T>,
- theme: &T,
+ renderer: &mut Renderer,
+ theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
- use iced_native::Renderer as _;
-
let bounds = layout.bounds();
if bounds.width < 1.0 || bounds.height < 1.0 {
return;
}
- let translation = Vector::new(bounds.x, bounds.y);
- let cursor = Cursor::from_window_position(cursor_position);
let state = tree.state.downcast_ref::<P::State>();
- renderer.with_translation(translation, |renderer| {
- renderer.draw_primitive(Primitive::Group {
- primitives: self
- .program
- .draw(state, theme, bounds, cursor)
- .into_iter()
- .map(Geometry::into_primitive)
- .collect(),
- });
- });
+ renderer.with_translation(
+ Vector::new(bounds.x, bounds.y),
+ |renderer| {
+ renderer.draw(
+ self.program.draw(state, renderer, theme, bounds, cursor),
+ );
+ },
+ );
}
}
-impl<'a, Message, P, B, T> From<Canvas<Message, T, P>>
- for Element<'a, Message, Renderer<B, T>>
+impl<'a, P, Message, Renderer> From<Canvas<P, Message, Renderer>>
+ for Element<'a, Message, Renderer>
where
Message: 'a,
- P: Program<Message, T> + 'a,
- B: Backend,
- T: 'a,
+ Renderer: 'a + geometry::Renderer,
+ P: Program<Message, Renderer> + 'a,
{
fn from(
- canvas: Canvas<Message, T, P>,
- ) -> Element<'a, Message, Renderer<B, T>> {
+ canvas: Canvas<P, Message, Renderer>,
+ ) -> Element<'a, Message, Renderer> {
Element::new(canvas)
}
}
diff --git a/graphics/src/widget/canvas/event.rs b/widget/src/canvas/event.rs
index 7c733a4d..4508c184 100644
--- a/graphics/src/widget/canvas/event.rs
+++ b/widget/src/canvas/event.rs
@@ -1,9 +1,9 @@
//! Handle events of a canvas.
-use iced_native::keyboard;
-use iced_native::mouse;
-use iced_native::touch;
+use crate::core::keyboard;
+use crate::core::mouse;
+use crate::core::touch;
-pub use iced_native::event::Status;
+pub use crate::core::event::Status;
/// A [`Canvas`] event.
///
diff --git a/graphics/src/widget/canvas/program.rs b/widget/src/canvas/program.rs
index 656dbfa6..b3f6175e 100644
--- a/graphics/src/widget/canvas/program.rs
+++ b/widget/src/canvas/program.rs
@@ -1,7 +1,7 @@
-use crate::widget::canvas::event::{self, Event};
-use crate::widget::canvas::mouse;
-use crate::widget::canvas::{Cursor, Geometry};
-use crate::Rectangle;
+use crate::canvas::event::{self, Event};
+use crate::canvas::mouse;
+use crate::core::Rectangle;
+use crate::graphics::geometry;
/// The state and logic of a [`Canvas`].
///
@@ -9,7 +9,10 @@ use crate::Rectangle;
/// application.
///
/// [`Canvas`]: crate::widget::Canvas
-pub trait Program<Message, Theme = iced_native::Theme> {
+pub trait Program<Message, Renderer = crate::Renderer>
+where
+ Renderer: geometry::Renderer,
+{
/// The internal state mutated by the [`Program`].
type State: Default + 'static;
@@ -29,7 +32,7 @@ pub trait Program<Message, Theme = iced_native::Theme> {
_state: &mut Self::State,
_event: Event,
_bounds: Rectangle,
- _cursor: Cursor,
+ _cursor: mouse::Cursor,
) -> (event::Status, Option<Message>) {
(event::Status::Ignored, None)
}
@@ -44,10 +47,11 @@ pub trait Program<Message, Theme = iced_native::Theme> {
fn draw(
&self,
state: &Self::State,
- theme: &Theme,
+ renderer: &Renderer,
+ theme: &Renderer::Theme,
bounds: Rectangle,
- cursor: Cursor,
- ) -> Vec<Geometry>;
+ cursor: mouse::Cursor,
+ ) -> Vec<Renderer::Geometry>;
/// Returns the current mouse interaction of the [`Program`].
///
@@ -59,15 +63,16 @@ pub trait Program<Message, Theme = iced_native::Theme> {
&self,
_state: &Self::State,
_bounds: Rectangle,
- _cursor: Cursor,
+ _cursor: mouse::Cursor,
) -> mouse::Interaction {
mouse::Interaction::default()
}
}
-impl<Message, Theme, T> Program<Message, Theme> for &T
+impl<Message, Renderer, T> Program<Message, Renderer> for &T
where
- T: Program<Message, Theme>,
+ Renderer: geometry::Renderer,
+ T: Program<Message, Renderer>,
{
type State = T::State;
@@ -76,7 +81,7 @@ where
state: &mut Self::State,
event: Event,
bounds: Rectangle,
- cursor: Cursor,
+ cursor: mouse::Cursor,
) -> (event::Status, Option<Message>) {
T::update(self, state, event, bounds, cursor)
}
@@ -84,18 +89,19 @@ where
fn draw(
&self,
state: &Self::State,
- theme: &Theme,
+ renderer: &Renderer,
+ theme: &Renderer::Theme,
bounds: Rectangle,
- cursor: Cursor,
- ) -> Vec<Geometry> {
- T::draw(self, state, theme, bounds, cursor)
+ cursor: mouse::Cursor,
+ ) -> Vec<Renderer::Geometry> {
+ T::draw(self, state, renderer, theme, bounds, cursor)
}
fn mouse_interaction(
&self,
state: &Self::State,
bounds: Rectangle,
- cursor: Cursor,
+ cursor: mouse::Cursor,
) -> mouse::Interaction {
T::mouse_interaction(self, state, bounds, cursor)
}
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/widget/src/helpers.rs b/widget/src/helpers.rs
new file mode 100644
index 00000000..3f5136f8
--- /dev/null
+++ b/widget/src/helpers.rs
@@ -0,0 +1,368 @@
+//! Helper functions to create pure widgets.
+use crate::button::{self, Button};
+use crate::checkbox::{self, Checkbox};
+use crate::container::{self, Container};
+use crate::core;
+use crate::core::widget::operation;
+use crate::core::{Element, Length, Pixels};
+use crate::overlay;
+use crate::pick_list::{self, PickList};
+use crate::progress_bar::{self, ProgressBar};
+use crate::radio::{self, Radio};
+use crate::rule::{self, Rule};
+use crate::runtime::Command;
+use crate::scrollable::{self, Scrollable};
+use crate::slider::{self, Slider};
+use crate::text::{self, Text};
+use crate::text_input::{self, TextInput};
+use crate::toggler::{self, Toggler};
+use crate::tooltip::{self, Tooltip};
+use crate::{Column, MouseArea, Row, Space, VerticalSlider};
+
+use std::borrow::Cow;
+use std::ops::RangeInclusive;
+
+/// Creates a [`Column`] with the given children.
+///
+/// [`Column`]: widget::Column
+#[macro_export]
+macro_rules! column {
+ () => (
+ $crate::Column::new()
+ );
+ ($($x:expr),+ $(,)?) => (
+ $crate::Column::with_children(vec![$($crate::core::Element::from($x)),+])
+ );
+}
+
+/// Creates a [`Row`] with the given children.
+///
+/// [`Row`]: widget::Row
+#[macro_export]
+macro_rules! row {
+ () => (
+ $crate::Row::new()
+ );
+ ($($x:expr),+ $(,)?) => (
+ $crate::Row::with_children(vec![$($crate::core::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>>,
+) -> Container<'a, Message, Renderer>
+where
+ Renderer: core::Renderer,
+ Renderer::Theme: container::StyleSheet,
+{
+ Container::new(content)
+}
+
+/// Creates a new [`Column`] with the given children.
+///
+/// [`Column`]: widget::Column
+pub fn column<Message, Renderer>(
+ children: Vec<Element<'_, Message, Renderer>>,
+) -> Column<'_, Message, Renderer> {
+ 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>>,
+) -> Row<'_, Message, Renderer> {
+ 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>>,
+) -> Scrollable<'a, Message, Renderer>
+where
+ Renderer: core::Renderer,
+ Renderer::Theme: scrollable::StyleSheet,
+{
+ 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>>,
+) -> Button<'a, Message, Renderer>
+where
+ Renderer: core::Renderer,
+ Renderer::Theme: button::StyleSheet,
+ <Renderer::Theme as button::StyleSheet>::Style: Default,
+{
+ 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: tooltip::Position,
+) -> crate::Tooltip<'a, Message, Renderer>
+where
+ Renderer: core::text::Renderer,
+ Renderer::Theme: container::StyleSheet + text::StyleSheet,
+{
+ 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) -> Text<'a, Renderer>
+where
+ Renderer: core::text::Renderer,
+ Renderer::Theme: text::StyleSheet,
+{
+ 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,
+) -> Checkbox<'a, Message, Renderer>
+where
+ Renderer: core::text::Renderer,
+ Renderer::Theme: checkbox::StyleSheet + text::StyleSheet,
+{
+ 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,
+) -> Radio<Message, Renderer>
+where
+ Message: Clone,
+ Renderer: core::text::Renderer,
+ Renderer::Theme: radio::StyleSheet,
+ V: Copy + Eq,
+{
+ Radio::new(label, value, 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,
+) -> Toggler<'a, Message, Renderer>
+where
+ Renderer: core::text::Renderer,
+ Renderer::Theme: toggler::StyleSheet,
+{
+ Toggler::new(label, is_checked, f)
+}
+
+/// Creates a new [`TextInput`].
+///
+/// [`TextInput`]: widget::TextInput
+pub fn text_input<'a, Message, Renderer>(
+ placeholder: &str,
+ value: &str,
+) -> TextInput<'a, Message, Renderer>
+where
+ Message: Clone,
+ Renderer: core::text::Renderer,
+ Renderer::Theme: text_input::StyleSheet,
+{
+ TextInput::new(placeholder, value)
+}
+
+/// 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,
+) -> Slider<'a, T, Message, Renderer>
+where
+ T: Copy + From<u8> + std::cmp::PartialOrd,
+ Message: Clone,
+ Renderer: core::Renderer,
+ Renderer::Theme: slider::StyleSheet,
+{
+ 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,
+) -> VerticalSlider<'a, T, Message, Renderer>
+where
+ T: Copy + From<u8> + std::cmp::PartialOrd,
+ Message: Clone,
+ Renderer: core::Renderer,
+ Renderer::Theme: slider::StyleSheet,
+{
+ 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,
+) -> PickList<'a, T, Message, Renderer>
+where
+ T: ToString + Eq + 'static,
+ [T]: ToOwned<Owned = Vec<T>>,
+ Renderer: core::text::Renderer,
+ Renderer::Theme: pick_list::StyleSheet
+ + scrollable::StyleSheet
+ + overlay::menu::StyleSheet
+ + container::StyleSheet,
+ <Renderer::Theme as overlay::menu::StyleSheet>::Style:
+ From<<Renderer::Theme as pick_list::StyleSheet>::Style>,
+{
+ PickList::new(options, selected, on_selected)
+}
+
+/// Creates a new horizontal [`Space`] with the given [`Length`].
+///
+/// [`Space`]: widget::Space
+pub fn horizontal_space(width: impl Into<Length>) -> Space {
+ Space::with_width(width)
+}
+
+/// Creates a new vertical [`Space`] with the given [`Length`].
+///
+/// [`Space`]: widget::Space
+pub fn vertical_space(height: impl Into<Length>) -> Space {
+ Space::with_height(height)
+}
+
+/// Creates a horizontal [`Rule`] with the given height.
+///
+/// [`Rule`]: widget::Rule
+pub fn horizontal_rule<Renderer>(height: impl Into<Pixels>) -> Rule<Renderer>
+where
+ Renderer: core::Renderer,
+ Renderer::Theme: rule::StyleSheet,
+{
+ Rule::horizontal(height)
+}
+
+/// Creates a vertical [`Rule`] with the given width.
+///
+/// [`Rule`]: widget::Rule
+pub fn vertical_rule<Renderer>(width: impl Into<Pixels>) -> Rule<Renderer>
+where
+ Renderer: core::Renderer,
+ Renderer::Theme: rule::StyleSheet,
+{
+ 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,
+) -> ProgressBar<Renderer>
+where
+ Renderer: core::Renderer,
+ Renderer::Theme: progress_bar::StyleSheet,
+{
+ ProgressBar::new(range, value)
+}
+
+/// Creates a new [`Image`].
+///
+/// [`Image`]: widget::Image
+#[cfg(feature = "image")]
+pub fn image<Handle>(handle: impl Into<Handle>) -> crate::Image<Handle> {
+ crate::Image::new(handle.into())
+}
+
+/// Creates a new [`Svg`] widget from the given [`Handle`].
+///
+/// [`Svg`]: widget::Svg
+/// [`Handle`]: widget::svg::Handle
+#[cfg(feature = "svg")]
+pub fn svg<Renderer>(
+ handle: impl Into<core::svg::Handle>,
+) -> crate::Svg<Renderer>
+where
+ Renderer: core::svg::Renderer,
+ Renderer::Theme: crate::svg::StyleSheet,
+{
+ crate::Svg::new(handle)
+}
+
+/// Creates a new [`Canvas`].
+#[cfg(feature = "canvas")]
+pub fn canvas<P, Message, Renderer>(
+ program: P,
+) -> crate::Canvas<P, Message, Renderer>
+where
+ Renderer: crate::graphics::geometry::Renderer,
+ P: crate::canvas::Program<Message, Renderer>,
+{
+ crate::Canvas::new(program)
+}
+
+/// Focuses the previous focusable widget.
+pub fn focus_previous<Message>() -> Command<Message>
+where
+ Message: 'static,
+{
+ Command::widget(operation::focusable::focus_previous())
+}
+
+/// Focuses the next focusable widget.
+pub fn focus_next<Message>() -> Command<Message>
+where
+ Message: 'static,
+{
+ Command::widget(operation::focusable::focus_next())
+}
+
+/// A container intercepting mouse events.
+pub fn mouse_area<'a, Message, Renderer>(
+ widget: impl Into<Element<'a, Message, Renderer>>,
+) -> MouseArea<'a, Message, Renderer>
+where
+ Renderer: core::Renderer,
+{
+ MouseArea::new(widget)
+}
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/lazy/src/lazy.rs b/widget/src/lazy.rs
index 5e909a49..da287f06 100644
--- a/lazy/src/lazy.rs
+++ b/widget/src/lazy.rs
@@ -1,18 +1,33 @@
-use iced_native::event;
-use iced_native::layout::{self, Layout};
-use iced_native::mouse;
-use iced_native::overlay;
-use iced_native::renderer;
-use iced_native::widget::tree::{self, Tree};
-use iced_native::widget::{self, Widget};
-use iced_native::Element;
-use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell, Size};
+#![allow(clippy::await_holding_refcell_ref, clippy::type_complexity)]
+pub(crate) mod helpers;
+
+pub mod component;
+pub mod responsive;
+
+pub use component::Component;
+pub use responsive::Responsive;
+
+mod cache;
+
+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::tree::{self, Tree};
+use crate::core::widget::{self, Widget};
+use crate::core::Element;
+use crate::core::{
+ self, Clipboard, Hasher, Length, Point, Rectangle, Shell, Size,
+};
+use crate::runtime::overlay::Nested;
use ouroboros::self_referencing;
use std::cell::RefCell;
use std::hash::{Hash, Hasher as H};
use std::rc::Rc;
+/// A widget that only rebuilds its contents when necessary.
#[allow(missing_debug_implementations)]
pub struct Lazy<'a, Message, Renderer, Dependency, View> {
dependency: Dependency,
@@ -28,6 +43,8 @@ where
Dependency: Hash + 'a,
View: Into<Element<'static, Message, Renderer>>,
{
+ /// Creates a new [`Lazy`] widget with the given data `Dependency` and a
+ /// closure that can turn this data into a widget tree.
pub fn new(
dependency: Dependency,
view: impl Fn(&Dependency) -> View + 'a,
@@ -41,7 +58,7 @@ where
fn with_element<T>(
&self,
- f: impl FnOnce(&Element<Message, Renderer>) -> T,
+ f: impl FnOnce(&Element<'_, Message, Renderer>) -> T,
) -> T {
f(self
.element
@@ -55,7 +72,7 @@ where
fn with_element_mut<T>(
&self,
- f: impl FnOnce(&mut Element<Message, Renderer>) -> T,
+ f: impl FnOnce(&mut Element<'_, Message, Renderer>) -> T,
) -> T {
f(self
.element
@@ -79,7 +96,7 @@ where
View: Into<Element<'static, Message, Renderer>> + 'static,
Dependency: Hash + 'a,
Message: 'static,
- Renderer: iced_native::Renderer + 'static,
+ Renderer: core::Renderer + 'static,
{
fn tag(&self) -> tree::Tag {
struct Tag<T>(T);
@@ -163,9 +180,9 @@ where
fn on_event(
&mut self,
tree: &mut Tree,
- event: iced_native::Event,
+ event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -175,7 +192,7 @@ where
&mut tree.children[0],
event,
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -187,7 +204,7 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
@@ -195,7 +212,7 @@ where
element.as_widget().mouse_interaction(
&tree.children[0],
layout,
- cursor_position,
+ cursor,
viewport,
renderer,
)
@@ -209,7 +226,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
self.with_element(|element| {
@@ -219,7 +236,7 @@ where
theme,
style,
layout,
- cursor_position,
+ cursor,
viewport,
)
})
@@ -244,14 +261,17 @@ where
.unwrap(),
tree: &mut tree.children[0],
overlay_builder: |element, tree| {
- element.as_widget_mut().overlay(tree, layout, renderer)
+ element
+ .as_widget_mut()
+ .overlay(tree, layout, renderer)
+ .map(|overlay| RefCell::new(Nested::new(overlay)))
},
}
.build(),
));
- let has_overlay = overlay
- .with_overlay_maybe(|overlay| overlay::Element::position(overlay));
+ let has_overlay =
+ overlay.with_overlay_maybe(|overlay| overlay.position());
has_overlay
.map(|position| overlay::Element::new(position, Box::new(overlay)))
@@ -259,18 +279,14 @@ where
}
#[self_referencing]
-struct Inner<'a, Message, Renderer>
-where
- Message: 'a,
- Renderer: 'a,
-{
+struct Inner<'a, Message: 'a, Renderer: 'a> {
cell: Rc<RefCell<Option<Element<'static, Message, Renderer>>>>,
element: Element<'static, Message, Renderer>,
tree: &'a mut Tree,
#[borrows(mut element, mut tree)]
- #[covariant]
- overlay: Option<overlay::Element<'this, Message, Renderer>>,
+ #[not_covariant]
+ overlay: Option<RefCell<Nested<'this, Message, Renderer>>>,
}
struct Overlay<'a, Message, Renderer>(Option<Inner<'a, Message, Renderer>>);
@@ -285,26 +301,27 @@ impl<'a, Message, Renderer> Drop for Overlay<'a, Message, Renderer> {
impl<'a, Message, Renderer> Overlay<'a, Message, Renderer> {
fn with_overlay_maybe<T>(
&self,
- f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T,
+ f: impl FnOnce(&mut Nested<'_, Message, Renderer>) -> T,
) -> Option<T> {
- self.0.as_ref().unwrap().borrow_overlay().as_ref().map(f)
+ self.0.as_ref().unwrap().with_overlay(|overlay| {
+ overlay.as_ref().map(|nested| (f)(&mut nested.borrow_mut()))
+ })
}
fn with_overlay_mut_maybe<T>(
&mut self,
- f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T,
+ f: impl FnOnce(&mut Nested<'_, Message, Renderer>) -> T,
) -> Option<T> {
- self.0
- .as_mut()
- .unwrap()
- .with_overlay_mut(|overlay| overlay.as_mut().map(f))
+ self.0.as_mut().unwrap().with_overlay_mut(|overlay| {
+ overlay.as_mut().map(|nested| (f)(nested.get_mut()))
+ })
}
}
impl<'a, Message, Renderer> overlay::Overlay<Message, Renderer>
for Overlay<'a, Message, Renderer>
where
- Renderer: iced_native::Renderer,
+ Renderer: core::Renderer,
{
fn layout(
&self,
@@ -313,9 +330,7 @@ where
position: Point,
) -> layout::Node {
self.with_overlay_maybe(|overlay| {
- let translation = position - overlay.position();
-
- overlay.layout(renderer, bounds, translation)
+ overlay.layout(renderer, bounds, position)
})
.unwrap_or_default()
}
@@ -326,56 +341,49 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) {
let _ = self.with_overlay_maybe(|overlay| {
- overlay.draw(renderer, theme, style, layout, cursor_position);
+ overlay.draw(renderer, theme, style, layout, cursor);
});
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.with_overlay_maybe(|overlay| {
- overlay.mouse_interaction(
- layout,
- cursor_position,
- viewport,
- renderer,
- )
+ overlay.mouse_interaction(layout, cursor, viewport, renderer)
})
.unwrap_or_default()
}
fn on_event(
&mut self,
- event: iced_native::Event,
+ event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.with_overlay_mut_maybe(|overlay| {
- overlay.on_event(
- event,
- layout,
- cursor_position,
- renderer,
- clipboard,
- shell,
- )
+ overlay.on_event(event, layout, cursor, renderer, clipboard, shell)
})
- .unwrap_or(iced_native::event::Status::Ignored)
+ .unwrap_or(event::Status::Ignored)
}
- fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
+ fn is_over(
+ &self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ cursor_position: Point,
+ ) -> bool {
self.with_overlay_maybe(|overlay| {
- overlay.is_over(layout, cursor_position)
+ overlay.is_over(layout, renderer, cursor_position)
})
.unwrap_or_default()
}
@@ -386,7 +394,7 @@ impl<'a, Message, Renderer, Dependency, View>
for Element<'a, Message, Renderer>
where
View: Into<Element<'static, Message, Renderer>> + 'static,
- Renderer: iced_native::Renderer + 'static,
+ Renderer: core::Renderer + 'static,
Message: 'static,
Dependency: Hash + 'a,
{
diff --git a/lazy/src/cache.rs b/widget/src/lazy/cache.rs
index 5b4a39f6..e7b87614 100644
--- a/lazy/src/cache.rs
+++ b/widget/src/lazy/cache.rs
@@ -1,5 +1,5 @@
-use iced_native::overlay;
-use iced_native::Element;
+use crate::core::overlay;
+use crate::core::Element;
use ouroboros::self_referencing;
diff --git a/lazy/src/component.rs b/widget/src/lazy/component.rs
index b23da9f7..c7814966 100644
--- a/lazy/src/component.rs
+++ b/widget/src/lazy/component.rs
@@ -1,18 +1,20 @@
//! Build and reuse custom widgets using The Elm Architecture.
-use iced_native::event;
-use iced_native::layout::{self, Layout};
-use iced_native::mouse;
-use iced_native::overlay;
-use iced_native::renderer;
-use iced_native::widget;
-use iced_native::widget::tree::{self, Tree};
-use iced_native::{
- Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget,
+use crate::core::event;
+use crate::core::layout::{self, Layout};
+use crate::core::mouse;
+use crate::core::overlay;
+use crate::core::renderer;
+use crate::core::widget;
+use crate::core::widget::tree::{self, Tree};
+use crate::core::{
+ self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget,
};
+use crate::runtime::overlay::Nested;
use ouroboros::self_referencing;
use std::cell::RefCell;
use std::marker::PhantomData;
+use std::rc::Rc;
/// A reusable, custom widget that uses The Elm Architecture.
///
@@ -58,6 +60,8 @@ pub trait Component<Message, Renderer> {
}
}
+struct Tag<T>(T);
+
/// Turns an implementor of [`Component`] into an [`Element`] that can be
/// embedded in any application.
pub fn view<'a, C, Message, Renderer>(
@@ -67,7 +71,7 @@ where
C: Component<Message, Renderer> + 'a,
C::State: 'static,
Message: 'a,
- Renderer: iced_native::Renderer + 'a,
+ Renderer: core::Renderer + 'a,
{
Element::new(Instance {
state: RefCell::new(Some(
@@ -79,11 +83,13 @@ where
}
.build(),
)),
+ tree: RefCell::new(Rc::new(RefCell::new(None))),
})
}
struct Instance<'a, Message, Renderer, Event, S> {
state: RefCell<Option<State<'a, Message, Renderer, Event, S>>>,
+ tree: RefCell<Rc<RefCell<Option<Tree>>>>,
}
#[self_referencing]
@@ -100,40 +106,91 @@ struct State<'a, Message: 'a, Renderer: 'a, Event: 'a, S: 'a> {
impl<'a, Message, Renderer, Event, S> Instance<'a, Message, Renderer, Event, S>
where
- S: Default,
+ S: Default + 'static,
+ Renderer: renderer::Renderer,
{
- fn rebuild_element(&self, state: &S) {
- let heads = self.state.borrow_mut().take().unwrap().into_heads();
+ fn diff_self(&self) {
+ self.with_element(|element| {
+ self.tree
+ .borrow_mut()
+ .borrow_mut()
+ .as_mut()
+ .unwrap()
+ .diff_children(std::slice::from_ref(&element));
+ });
+ }
- *self.state.borrow_mut() = Some(
- StateBuilder {
- component: heads.component,
- message: PhantomData,
- state: PhantomData,
- element_builder: |component| Some(component.view(state)),
- }
- .build(),
- );
+ fn rebuild_element_if_necessary(&self) {
+ let inner = self.state.borrow_mut().take().unwrap();
+ if inner.borrow_element().is_none() {
+ let heads = inner.into_heads();
+
+ *self.state.borrow_mut() = Some(
+ StateBuilder {
+ component: heads.component,
+ message: PhantomData,
+ state: PhantomData,
+ element_builder: |component| {
+ Some(
+ component.view(
+ self.tree
+ .borrow()
+ .borrow()
+ .as_ref()
+ .unwrap()
+ .state
+ .downcast_ref::<S>(),
+ ),
+ )
+ },
+ }
+ .build(),
+ );
+ self.diff_self();
+ } else {
+ *self.state.borrow_mut() = Some(inner);
+ }
}
fn rebuild_element_with_operation(
&self,
- state: &mut S,
operation: &mut dyn widget::Operation<Message>,
) {
let heads = self.state.borrow_mut().take().unwrap().into_heads();
- heads.component.operate(state, operation);
+ heads.component.operate(
+ self.tree
+ .borrow_mut()
+ .borrow_mut()
+ .as_mut()
+ .unwrap()
+ .state
+ .downcast_mut(),
+ operation,
+ );
*self.state.borrow_mut() = Some(
StateBuilder {
component: heads.component,
message: PhantomData,
state: PhantomData,
- element_builder: |component| Some(component.view(state)),
+ element_builder: |component| {
+ Some(
+ component.view(
+ self.tree
+ .borrow()
+ .borrow()
+ .as_ref()
+ .unwrap()
+ .state
+ .downcast_ref(),
+ ),
+ )
+ },
}
.build(),
);
+ self.diff_self();
}
fn with_element<T>(
@@ -147,6 +204,7 @@ where
&self,
f: impl FnOnce(&mut Element<'_, Event, Renderer>) -> T,
) -> T {
+ self.rebuild_element_if_necessary();
self.state
.borrow_mut()
.as_mut()
@@ -159,27 +217,30 @@ impl<'a, Message, Renderer, Event, S> Widget<Message, Renderer>
for Instance<'a, Message, Renderer, Event, S>
where
S: 'static + Default,
- Renderer: iced_native::Renderer,
+ Renderer: core::Renderer,
{
fn tag(&self) -> tree::Tag {
- struct Tag<T>(T);
tree::Tag::of::<Tag<S>>()
}
fn state(&self) -> tree::State {
- tree::State::new(S::default())
+ let state = Rc::new(RefCell::new(Some(Tree {
+ tag: tree::Tag::of::<Tag<S>>(),
+ state: tree::State::new(S::default()),
+ children: vec![Tree::empty()],
+ })));
+ *self.tree.borrow_mut() = state.clone();
+ tree::State::new(state)
}
fn children(&self) -> Vec<Tree> {
- self.rebuild_element(&S::default());
- self.with_element(|element| vec![Tree::new(element)])
+ vec![]
}
fn diff(&self, tree: &mut Tree) {
- self.rebuild_element(tree.state.downcast_ref());
- self.with_element(|element| {
- tree.diff_children(std::slice::from_ref(&element))
- })
+ let tree = tree.state.downcast_ref::<Rc<RefCell<Option<Tree>>>>();
+ *self.tree.borrow_mut() = tree.clone();
+ self.rebuild_element_if_necessary();
}
fn width(&self) -> Length {
@@ -203,9 +264,9 @@ where
fn on_event(
&mut self,
tree: &mut Tree,
- event: iced_native::Event,
+ event: core::Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -213,12 +274,13 @@ where
let mut local_messages = Vec::new();
let mut local_shell = Shell::new(&mut local_messages);
+ let t = tree.state.downcast_mut::<Rc<RefCell<Option<Tree>>>>();
let event_status = self.with_element_mut(|element| {
element.as_widget_mut().on_event(
- &mut tree.children[0],
+ &mut t.borrow_mut().as_mut().unwrap().children[0],
event,
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
&mut local_shell,
@@ -235,9 +297,10 @@ where
let mut heads = self.state.take().unwrap().into_heads();
for message in local_messages.into_iter().filter_map(|message| {
- heads
- .component
- .update(tree.state.downcast_mut::<S>(), message)
+ heads.component.update(
+ t.borrow_mut().as_mut().unwrap().state.downcast_mut(),
+ message,
+ )
}) {
shell.publish(message);
}
@@ -247,17 +310,11 @@ where
component: heads.component,
message: PhantomData,
state: PhantomData,
- element_builder: |state| {
- Some(state.view(tree.state.downcast_ref::<S>()))
- },
+ element_builder: |_| None,
}
.build(),
));
- self.with_element(|element| {
- tree.diff_children(std::slice::from_ref(&element))
- });
-
shell.invalidate_layout();
}
@@ -271,10 +328,7 @@ where
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
) {
- self.rebuild_element_with_operation(
- tree.state.downcast_mut(),
- operation,
- );
+ self.rebuild_element_with_operation(operation);
struct MapOperation<'a, B> {
operation: &'a mut dyn widget::Operation<B>,
@@ -308,13 +362,28 @@ where
) {
self.operation.text_input(state, id);
}
+
+ fn scrollable(
+ &mut self,
+ state: &mut dyn widget::operation::Scrollable,
+ id: Option<&widget::Id>,
+ ) {
+ self.operation.scrollable(state, id);
+ }
+
+ fn custom(
+ &mut self,
+ state: &mut dyn std::any::Any,
+ id: Option<&widget::Id>,
+ ) {
+ self.operation.custom(state, id);
+ }
}
+ let tree = tree.state.downcast_mut::<Rc<RefCell<Option<Tree>>>>();
self.with_element(|element| {
- tree.diff_children(std::slice::from_ref(&element));
-
element.as_widget().operate(
- &mut tree.children[0],
+ &mut tree.borrow_mut().as_mut().unwrap().children[0],
layout,
renderer,
&mut MapOperation { operation },
@@ -329,17 +398,18 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
+ let tree = tree.state.downcast_ref::<Rc<RefCell<Option<Tree>>>>();
self.with_element(|element| {
element.as_widget().draw(
- &tree.children[0],
+ &tree.borrow().as_ref().unwrap().children[0],
renderer,
theme,
style,
layout,
- cursor_position,
+ cursor,
viewport,
);
});
@@ -349,15 +419,16 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
+ let tree = tree.state.downcast_ref::<Rc<RefCell<Option<Tree>>>>();
self.with_element(|element| {
element.as_widget().mouse_interaction(
- &tree.children[0],
+ &tree.borrow().as_ref().unwrap().children[0],
layout,
- cursor_position,
+ cursor,
viewport,
renderer,
)
@@ -370,26 +441,42 @@ where
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
- let overlay = OverlayBuilder {
- instance: self,
- tree,
- types: PhantomData,
- overlay_builder: |instance, tree| {
- instance.state.get_mut().as_mut().unwrap().with_element_mut(
- move |element| {
- element.as_mut().unwrap().as_widget_mut().overlay(
- &mut tree.children[0],
- layout,
- renderer,
- )
- },
- )
- },
- }
- .build();
+ self.rebuild_element_if_necessary();
+ let tree = tree
+ .state
+ .downcast_mut::<Rc<RefCell<Option<Tree>>>>()
+ .borrow_mut()
+ .take()
+ .unwrap();
+ let overlay = Overlay(Some(
+ InnerBuilder {
+ instance: self,
+ tree,
+ types: PhantomData,
+ overlay_builder: |instance, tree| {
+ instance.state.get_mut().as_mut().unwrap().with_element_mut(
+ move |element| {
+ element
+ .as_mut()
+ .unwrap()
+ .as_widget_mut()
+ .overlay(
+ &mut tree.children[0],
+ layout,
+ renderer,
+ )
+ .map(|overlay| {
+ RefCell::new(Nested::new(overlay))
+ })
+ },
+ )
+ },
+ }
+ .build(),
+ ));
- let has_overlay = overlay.with_overlay(|overlay| {
- overlay.as_ref().map(overlay::Element::position)
+ let has_overlay = overlay.0.as_ref().unwrap().with_overlay(|overlay| {
+ overlay.as_ref().map(|nested| nested.borrow().position())
});
has_overlay.map(|position| {
@@ -403,15 +490,29 @@ where
}
}
+struct Overlay<'a, 'b, Message, Renderer, Event, S>(
+ Option<Inner<'a, 'b, Message, Renderer, Event, S>>,
+);
+
+impl<'a, 'b, Message, Renderer, Event, S> Drop
+ for Overlay<'a, 'b, Message, Renderer, Event, S>
+{
+ fn drop(&mut self) {
+ if let Some(heads) = self.0.take().map(|inner| inner.into_heads()) {
+ *heads.instance.tree.borrow_mut().borrow_mut() = Some(heads.tree);
+ }
+ }
+}
+
#[self_referencing]
-struct Overlay<'a, 'b, Message, Renderer, Event, S> {
+struct Inner<'a, 'b, Message, Renderer, Event, S> {
instance: &'a mut Instance<'b, Message, Renderer, Event, S>,
- tree: &'a mut Tree,
+ tree: Tree,
types: PhantomData<(Message, Event, S)>,
#[borrows(mut instance, mut tree)]
- #[covariant]
- overlay: Option<overlay::Element<'this, Event, Renderer>>,
+ #[not_covariant]
+ overlay: Option<RefCell<Nested<'this, Event, Renderer>>>,
}
struct OverlayInstance<'a, 'b, Message, Renderer, Event, S> {
@@ -423,31 +524,39 @@ impl<'a, 'b, Message, Renderer, Event, S>
{
fn with_overlay_maybe<T>(
&self,
- f: impl FnOnce(&overlay::Element<'_, Event, Renderer>) -> T,
+ f: impl FnOnce(&mut Nested<'_, Event, Renderer>) -> T,
) -> Option<T> {
self.overlay
.as_ref()
.unwrap()
- .borrow_overlay()
+ .0
.as_ref()
- .map(f)
+ .unwrap()
+ .with_overlay(|overlay| {
+ overlay.as_ref().map(|nested| (f)(&mut nested.borrow_mut()))
+ })
}
fn with_overlay_mut_maybe<T>(
&mut self,
- f: impl FnOnce(&mut overlay::Element<'_, Event, Renderer>) -> T,
+ f: impl FnOnce(&mut Nested<'_, Event, Renderer>) -> T,
) -> Option<T> {
self.overlay
.as_mut()
.unwrap()
- .with_overlay_mut(|overlay| overlay.as_mut().map(f))
+ .0
+ .as_mut()
+ .unwrap()
+ .with_overlay_mut(|overlay| {
+ overlay.as_mut().map(|nested| (f)(nested.get_mut()))
+ })
}
}
impl<'a, 'b, Message, Renderer, Event, S> overlay::Overlay<Message, Renderer>
for OverlayInstance<'a, 'b, Message, Renderer, Event, S>
where
- Renderer: iced_native::Renderer,
+ Renderer: core::Renderer,
S: 'static + Default,
{
fn layout(
@@ -457,9 +566,7 @@ where
position: Point,
) -> layout::Node {
self.with_overlay_maybe(|overlay| {
- let translation = position - overlay.position();
-
- overlay.layout(renderer, bounds, translation)
+ overlay.layout(renderer, bounds, position)
})
.unwrap_or_default()
}
@@ -470,40 +577,35 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) {
let _ = self.with_overlay_maybe(|overlay| {
- overlay.draw(renderer, theme, style, layout, cursor_position);
+ overlay.draw(renderer, theme, style, layout, cursor);
});
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.with_overlay_maybe(|overlay| {
- overlay.mouse_interaction(
- layout,
- cursor_position,
- viewport,
- renderer,
- )
+ overlay.mouse_interaction(layout, cursor, viewport, renderer)
})
.unwrap_or_default()
}
fn on_event(
&mut self,
- event: iced_native::Event,
+ event: core::Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
- ) -> iced_native::event::Status {
+ ) -> event::Status {
let mut local_messages = Vec::new();
let mut local_shell = Shell::new(&mut local_messages);
@@ -512,53 +614,52 @@ where
overlay.on_event(
event,
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
&mut local_shell,
)
})
- .unwrap_or(iced_native::event::Status::Ignored);
+ .unwrap_or(event::Status::Ignored);
local_shell.revalidate_layout(|| shell.invalidate_layout());
+ if let Some(redraw_request) = local_shell.redraw_request() {
+ shell.request_redraw(redraw_request);
+ }
+
if !local_messages.is_empty() {
- let overlay = self.overlay.take().unwrap().into_heads();
- let mut heads = overlay.instance.state.take().unwrap().into_heads();
+ let mut inner =
+ self.overlay.take().unwrap().0.take().unwrap().into_heads();
+ let mut heads = inner.instance.state.take().unwrap().into_heads();
for message in local_messages.into_iter().filter_map(|message| {
heads
.component
- .update(overlay.tree.state.downcast_mut::<S>(), message)
+ .update(inner.tree.state.downcast_mut(), message)
}) {
shell.publish(message);
}
- *overlay.instance.state.borrow_mut() = Some(
+ *inner.instance.state.borrow_mut() = Some(
StateBuilder {
component: heads.component,
message: PhantomData,
state: PhantomData,
- element_builder: |state| {
- Some(state.view(overlay.tree.state.downcast_ref::<S>()))
- },
+ element_builder: |_| None,
}
.build(),
);
- overlay.instance.with_element(|element| {
- overlay.tree.diff_children(std::slice::from_ref(&element))
- });
-
- self.overlay = Some(
- OverlayBuilder {
- instance: overlay.instance,
- tree: overlay.tree,
+ self.overlay = Some(Overlay(Some(
+ InnerBuilder {
+ instance: inner.instance,
+ tree: inner.tree,
types: PhantomData,
overlay_builder: |_, _| None,
}
.build(),
- );
+ )));
shell.invalidate_layout();
}
@@ -566,9 +667,14 @@ where
event_status
}
- fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
+ fn is_over(
+ &self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ cursor_position: Point,
+ ) -> bool {
self.with_overlay_maybe(|overlay| {
- overlay.is_over(layout, cursor_position)
+ overlay.is_over(layout, renderer, cursor_position)
})
.unwrap_or_default()
}
diff --git a/lazy/src/lib.rs b/widget/src/lazy/helpers.rs
index 41a28773..8ca9cb86 100644
--- a/lazy/src/lib.rs
+++ b/widget/src/lazy/helpers.rs
@@ -1,36 +1,11 @@
-#![doc(
- html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
-)]
-#![deny(
- missing_debug_implementations,
- unused_results,
- clippy::extra_unused_lifetimes,
- clippy::from_over_into,
- clippy::needless_borrow,
- clippy::new_without_default,
- clippy::useless_conversion
-)]
-#![forbid(unsafe_code)]
-#![allow(
- clippy::await_holding_refcell_ref,
- clippy::inherent_to_string,
- clippy::type_complexity
-)]
-#![cfg_attr(docsrs, feature(doc_cfg))]
-mod lazy;
+use crate::core::{self, Element, Size};
+use crate::lazy::component::{self, Component};
+use crate::lazy::{Lazy, Responsive};
-pub mod component;
-pub mod responsive;
-
-pub use component::Component;
-pub use lazy::Lazy;
-pub use responsive::Responsive;
-
-mod cache;
-
-use iced_native::{Element, Size};
use std::hash::Hash;
+/// Creates a new [`Lazy`] widget with the given data `Dependency` and a
+/// closure that can turn this data into a widget tree.
pub fn lazy<'a, Message, Renderer, Dependency, View>(
dependency: Dependency,
view: impl Fn(&Dependency) -> View + 'a,
@@ -51,16 +26,22 @@ where
C: Component<Message, Renderer> + 'a,
C::State: 'static,
Message: 'a,
- Renderer: iced_native::Renderer + 'a,
+ Renderer: core::Renderer + 'a,
{
component::view(component)
}
+/// Creates a new [`Responsive`] widget with a closure that produces its
+/// contents.
+///
+/// The `view` closure will be provided with the current [`Size`] of
+/// the [`Responsive`] widget and, therefore, can be used to build the
+/// contents of the widget in a responsive way.
pub fn responsive<'a, Message, Renderer>(
f: impl Fn(Size) -> Element<'a, Message, Renderer> + 'a,
) -> Responsive<'a, Message, Renderer>
where
- Renderer: iced_native::Renderer,
+ Renderer: core::Renderer,
{
Responsive::new(f)
}
diff --git a/lazy/src/responsive.rs b/widget/src/lazy/responsive.rs
index 57c07de1..07300857 100644
--- a/lazy/src/responsive.rs
+++ b/widget/src/lazy/responsive.rs
@@ -1,13 +1,15 @@
-use iced_native::event;
-use iced_native::layout::{self, Layout};
-use iced_native::mouse;
-use iced_native::overlay;
-use iced_native::renderer;
-use iced_native::widget::tree::{self, Tree};
-use iced_native::widget::{self, horizontal_space};
-use iced_native::{
- Clipboard, Element, Length, Point, Rectangle, Shell, Size, 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;
+use crate::core::widget::tree::{self, Tree};
+use crate::core::{
+ self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget,
};
+use crate::horizontal_space;
+use crate::runtime::overlay::Nested;
use ouroboros::self_referencing;
use std::cell::{RefCell, RefMut};
@@ -19,14 +21,14 @@ use std::ops::Deref;
/// A [`Responsive`] widget will always try to fill all the available space of
/// its parent.
#[allow(missing_debug_implementations)]
-pub struct Responsive<'a, Message, Renderer> {
+pub struct Responsive<'a, Message, Renderer = crate::Renderer> {
view: Box<dyn Fn(Size) -> Element<'a, Message, Renderer> + 'a>,
content: RefCell<Content<'a, Message, Renderer>>,
}
impl<'a, Message, Renderer> Responsive<'a, Message, Renderer>
where
- Renderer: iced_native::Renderer,
+ Renderer: core::Renderer,
{
/// Creates a new [`Responsive`] widget with a closure that produces its
/// contents.
@@ -41,7 +43,7 @@ where
view: Box::new(view),
content: RefCell::new(Content {
size: Size::ZERO,
- layout: layout::Node::new(Size::ZERO),
+ layout: None,
element: Element::new(horizontal_space(0)),
}),
}
@@ -50,18 +52,27 @@ where
struct Content<'a, Message, Renderer> {
size: Size,
- layout: layout::Node,
+ layout: Option<layout::Node>,
element: Element<'a, Message, Renderer>,
}
impl<'a, Message, Renderer> Content<'a, Message, Renderer>
where
- Renderer: iced_native::Renderer,
+ Renderer: core::Renderer,
{
+ fn layout(&mut self, renderer: &Renderer) {
+ if self.layout.is_none() {
+ self.layout =
+ Some(self.element.as_widget().layout(
+ renderer,
+ &layout::Limits::new(Size::ZERO, self.size),
+ ));
+ }
+ }
+
fn update(
&mut self,
tree: &mut Tree,
- renderer: &Renderer,
new_size: Size,
view: &dyn Fn(Size) -> Element<'a, Message, Renderer>,
) {
@@ -71,13 +82,9 @@ where
self.element = view(new_size);
self.size = new_size;
+ self.layout = None;
tree.diff(&self.element);
-
- self.layout = self
- .element
- .as_widget()
- .layout(renderer, &layout::Limits::new(Size::ZERO, self.size));
}
fn resolve<R, T>(
@@ -96,11 +103,12 @@ where
where
R: Deref<Target = Renderer>,
{
- self.update(tree, renderer.deref(), layout.bounds().size(), view);
+ self.update(tree, layout.bounds().size(), view);
+ self.layout(renderer.deref());
let content_layout = Layout::with_offset(
layout.position() - Point::ORIGIN,
- &self.layout,
+ self.layout.as_ref().unwrap(),
);
f(tree, renderer, content_layout, &mut self.element)
@@ -114,7 +122,7 @@ struct State {
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Responsive<'a, Message, Renderer>
where
- Renderer: iced_native::Renderer,
+ Renderer: core::Renderer,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>()
@@ -168,9 +176,9 @@ where
fn on_event(
&mut self,
tree: &mut Tree,
- event: iced_native::Event,
+ event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -178,7 +186,10 @@ where
let state = tree.state.downcast_mut::<State>();
let mut content = self.content.borrow_mut();
- content.resolve(
+ let mut local_messages = vec![];
+ let mut local_shell = Shell::new(&mut local_messages);
+
+ let status = content.resolve(
&mut state.tree.borrow_mut(),
renderer,
layout,
@@ -188,13 +199,21 @@ where
tree,
event,
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
- shell,
+ &mut local_shell,
)
},
- )
+ );
+
+ if local_shell.is_layout_invalid() {
+ content.layout = None;
+ }
+
+ shell.merge(local_shell, std::convert::identity);
+
+ status
}
fn draw(
@@ -204,7 +223,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>();
@@ -217,13 +236,7 @@ where
&self.view,
|tree, renderer, layout, element| {
element.as_widget().draw(
- tree,
- renderer,
- theme,
- style,
- layout,
- cursor_position,
- viewport,
+ tree, renderer, theme, style, layout, cursor, viewport,
)
},
)
@@ -233,7 +246,7 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
@@ -246,13 +259,9 @@ where
layout,
&self.view,
|tree, renderer, layout, element| {
- element.as_widget().mouse_interaction(
- tree,
- layout,
- cursor_position,
- viewport,
- renderer,
- )
+ element
+ .as_widget()
+ .mouse_interaction(tree, layout, cursor, viewport, renderer)
},
)
}
@@ -271,35 +280,32 @@ where
content: self.content.borrow_mut(),
tree: state.tree.borrow_mut(),
types: PhantomData,
- overlay_builder: |content: &mut RefMut<Content<_, _>>, tree| {
- content.update(
- tree,
- renderer,
- layout.bounds().size(),
- &self.view,
- );
+ overlay_builder: |content: &mut RefMut<'_, Content<'_, _, _>>,
+ tree| {
+ content.update(tree, layout.bounds().size(), &self.view);
+ content.layout(renderer);
let Content {
element,
- layout: content_layout,
+ layout: content_layout_node,
..
} = content.deref_mut();
let content_layout = Layout::with_offset(
layout.bounds().position() - Point::ORIGIN,
- content_layout,
+ content_layout_node.as_ref().unwrap(),
);
element
.as_widget_mut()
.overlay(tree, content_layout, renderer)
+ .map(|overlay| RefCell::new(Nested::new(overlay)))
},
}
.build();
- let has_overlay = overlay.with_overlay(|overlay| {
- overlay.as_ref().map(overlay::Element::position)
- });
+ let has_overlay =
+ overlay.with_overlay_maybe(|overlay| overlay.position());
has_overlay
.map(|position| overlay::Element::new(position, Box::new(overlay)))
@@ -309,7 +315,7 @@ where
impl<'a, Message, Renderer> From<Responsive<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: iced_native::Renderer + 'a,
+ Renderer: core::Renderer + 'a,
Message: 'a,
{
fn from(responsive: Responsive<'a, Message, Renderer>) -> Self {
@@ -324,30 +330,34 @@ struct Overlay<'a, 'b, Message, Renderer> {
types: PhantomData<Message>,
#[borrows(mut content, mut tree)]
- #[covariant]
- overlay: Option<overlay::Element<'this, Message, Renderer>>,
+ #[not_covariant]
+ overlay: Option<RefCell<Nested<'this, Message, Renderer>>>,
}
impl<'a, 'b, Message, Renderer> Overlay<'a, 'b, Message, Renderer> {
fn with_overlay_maybe<T>(
&self,
- f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T,
+ f: impl FnOnce(&mut Nested<'_, Message, Renderer>) -> T,
) -> Option<T> {
- self.borrow_overlay().as_ref().map(f)
+ self.with_overlay(|overlay| {
+ overlay.as_ref().map(|nested| (f)(&mut nested.borrow_mut()))
+ })
}
fn with_overlay_mut_maybe<T>(
&mut self,
- f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T,
+ f: impl FnOnce(&mut Nested<'_, Message, Renderer>) -> T,
) -> Option<T> {
- self.with_overlay_mut(|overlay| overlay.as_mut().map(f))
+ self.with_overlay_mut(|overlay| {
+ overlay.as_mut().map(|nested| (f)(nested.get_mut()))
+ })
}
}
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer>
for Overlay<'a, 'b, Message, Renderer>
where
- Renderer: iced_native::Renderer,
+ Renderer: core::Renderer,
{
fn layout(
&self,
@@ -356,9 +366,7 @@ where
position: Point,
) -> layout::Node {
self.with_overlay_maybe(|overlay| {
- let translation = position - overlay.position();
-
- overlay.layout(renderer, bounds, translation)
+ overlay.layout(renderer, bounds, position)
})
.unwrap_or_default()
}
@@ -369,56 +377,49 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) {
let _ = self.with_overlay_maybe(|overlay| {
- overlay.draw(renderer, theme, style, layout, cursor_position);
+ overlay.draw(renderer, theme, style, layout, cursor);
});
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.with_overlay_maybe(|overlay| {
- overlay.mouse_interaction(
- layout,
- cursor_position,
- viewport,
- renderer,
- )
+ overlay.mouse_interaction(layout, cursor, viewport, renderer)
})
.unwrap_or_default()
}
fn on_event(
&mut self,
- event: iced_native::Event,
+ event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.with_overlay_mut_maybe(|overlay| {
- overlay.on_event(
- event,
- layout,
- cursor_position,
- renderer,
- clipboard,
- shell,
- )
+ overlay.on_event(event, layout, cursor, renderer, clipboard, shell)
})
- .unwrap_or(iced_native::event::Status::Ignored)
+ .unwrap_or(event::Status::Ignored)
}
- fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
+ fn is_over(
+ &self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ cursor_position: Point,
+ ) -> bool {
self.with_overlay_maybe(|overlay| {
- overlay.is_over(layout, cursor_position)
+ overlay.is_over(layout, renderer, cursor_position)
})
.unwrap_or_default()
}
diff --git a/widget/src/lib.rs b/widget/src/lib.rs
new file mode 100644
index 00000000..9da13f9b
--- /dev/null
+++ b/widget/src/lib.rs
@@ -0,0 +1,126 @@
+//! Use the built-in widgets or create your own.
+#![doc(
+ html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
+)]
+#![deny(
+ missing_debug_implementations,
+ missing_docs,
+ unused_results,
+ clippy::extra_unused_lifetimes,
+ clippy::from_over_into,
+ clippy::needless_borrow,
+ clippy::new_without_default,
+ clippy::useless_conversion
+)]
+#![forbid(unsafe_code, rust_2018_idioms)]
+#![allow(clippy::inherent_to_string, clippy::type_complexity)]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
+pub use iced_renderer as renderer;
+pub use iced_renderer::graphics;
+pub use iced_runtime as runtime;
+pub use iced_runtime::core;
+pub use iced_style as style;
+
+mod column;
+mod mouse_area;
+mod row;
+
+pub mod button;
+pub mod checkbox;
+pub mod container;
+pub mod overlay;
+pub mod pane_grid;
+pub mod pick_list;
+pub mod progress_bar;
+pub mod radio;
+pub mod rule;
+pub mod scrollable;
+pub mod slider;
+pub mod space;
+pub mod text;
+pub mod text_input;
+pub mod toggler;
+pub mod tooltip;
+pub mod vertical_slider;
+
+mod helpers;
+
+pub use helpers::*;
+
+#[cfg(feature = "lazy")]
+mod lazy;
+
+#[cfg(feature = "lazy")]
+pub use crate::lazy::{Component, Lazy, Responsive};
+
+#[cfg(feature = "lazy")]
+pub use crate::lazy::helpers::*;
+
+#[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 mouse_area::MouseArea;
+#[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 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 vertical_slider::VerticalSlider;
+
+#[cfg(feature = "svg")]
+pub mod svg;
+
+#[cfg(feature = "svg")]
+#[doc(no_inline)]
+pub use svg::Svg;
+
+#[cfg(feature = "image")]
+pub mod image;
+
+#[cfg(feature = "image")]
+#[doc(no_inline)]
+pub use image::Image;
+
+#[cfg(feature = "canvas")]
+pub mod canvas;
+
+#[cfg(feature = "canvas")]
+#[doc(no_inline)]
+pub use canvas::Canvas;
+
+#[cfg(feature = "qr_code")]
+pub mod qr_code;
+
+#[cfg(feature = "qr_code")]
+#[doc(no_inline)]
+pub use qr_code::QRCode;
+
+type Renderer<Theme = style::Theme> = renderer::Renderer<Theme>;
diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs
new file mode 100644
index 00000000..da7dc88f
--- /dev/null
+++ b/widget/src/mouse_area.rs
@@ -0,0 +1,311 @@
+//! A container for capturing mouse events.
+
+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, Operation, Tree};
+use crate::core::{
+ Clipboard, Element, Layout, Length, Rectangle, Shell, Widget,
+};
+
+/// Emit messages on mouse events.
+#[allow(missing_debug_implementations)]
+pub struct MouseArea<'a, Message, Renderer> {
+ content: Element<'a, Message, Renderer>,
+ on_press: Option<Message>,
+ on_release: Option<Message>,
+ on_right_press: Option<Message>,
+ on_right_release: Option<Message>,
+ on_middle_press: Option<Message>,
+ on_middle_release: Option<Message>,
+}
+
+impl<'a, Message, Renderer> MouseArea<'a, Message, Renderer> {
+ /// The message to emit on a left button press.
+ #[must_use]
+ pub fn on_press(mut self, message: Message) -> Self {
+ self.on_press = Some(message);
+ self
+ }
+
+ /// The message to emit on a left button release.
+ #[must_use]
+ pub fn on_release(mut self, message: Message) -> Self {
+ self.on_release = Some(message);
+ self
+ }
+
+ /// The message to emit on a right button press.
+ #[must_use]
+ pub fn on_right_press(mut self, message: Message) -> Self {
+ self.on_right_press = Some(message);
+ self
+ }
+
+ /// The message to emit on a right button release.
+ #[must_use]
+ pub fn on_right_release(mut self, message: Message) -> Self {
+ self.on_right_release = Some(message);
+ self
+ }
+
+ /// The message to emit on a middle button press.
+ #[must_use]
+ pub fn on_middle_press(mut self, message: Message) -> Self {
+ self.on_middle_press = Some(message);
+ self
+ }
+
+ /// The message to emit on a middle button release.
+ #[must_use]
+ pub fn on_middle_release(mut self, message: Message) -> Self {
+ self.on_middle_release = Some(message);
+ self
+ }
+}
+
+/// Local state of the [`MouseArea`].
+#[derive(Default)]
+struct State {
+ // TODO: Support on_mouse_enter and on_mouse_exit
+}
+
+impl<'a, Message, Renderer> MouseArea<'a, Message, Renderer> {
+ /// Creates a [`MouseArea`] with the given content.
+ pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self {
+ MouseArea {
+ content: content.into(),
+ on_press: None,
+ on_release: None,
+ on_right_press: None,
+ on_right_release: None,
+ on_middle_press: None,
+ on_middle_release: None,
+ }
+ }
+}
+
+impl<'a, Message, Renderer> Widget<Message, Renderer>
+ for MouseArea<'a, Message, Renderer>
+where
+ Renderer: renderer::Renderer,
+ Message: Clone,
+{
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::of::<State>()
+ }
+
+ fn state(&self) -> tree::State {
+ tree::State::new(State::default())
+ }
+
+ fn children(&self) -> Vec<Tree> {
+ vec![Tree::new(&self.content)]
+ }
+
+ fn diff(&self, tree: &mut Tree) {
+ tree.diff_children(std::slice::from_ref(&self.content));
+ }
+
+ fn width(&self) -> Length {
+ self.content.as_widget().width()
+ }
+
+ fn height(&self) -> Length {
+ self.content.as_widget().height()
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ self.content.as_widget().layout(renderer, limits)
+ }
+
+ fn operate(
+ &self,
+ tree: &mut Tree,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ operation: &mut dyn Operation<Message>,
+ ) {
+ self.content.as_widget().operate(
+ &mut tree.children[0],
+ layout,
+ renderer,
+ operation,
+ );
+ }
+
+ fn on_event(
+ &mut self,
+ tree: &mut Tree,
+ event: Event,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ if let event::Status::Captured = self.content.as_widget_mut().on_event(
+ &mut tree.children[0],
+ event.clone(),
+ layout,
+ cursor,
+ renderer,
+ clipboard,
+ shell,
+ ) {
+ return event::Status::Captured;
+ }
+
+ update(self, &event, layout, cursor, shell)
+ }
+
+ fn mouse_interaction(
+ &self,
+ tree: &Tree,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ viewport: &Rectangle,
+ renderer: &Renderer,
+ ) -> mouse::Interaction {
+ self.content.as_widget().mouse_interaction(
+ &tree.children[0],
+ layout,
+ cursor,
+ viewport,
+ renderer,
+ )
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut Renderer,
+ theme: &Renderer::Theme,
+ renderer_style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ viewport: &Rectangle,
+ ) {
+ self.content.as_widget().draw(
+ &tree.children[0],
+ renderer,
+ theme,
+ renderer_style,
+ layout,
+ cursor,
+ viewport,
+ );
+ }
+
+ fn overlay<'b>(
+ &'b mut self,
+ tree: &'b mut Tree,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ ) -> Option<overlay::Element<'b, Message, Renderer>> {
+ self.content.as_widget_mut().overlay(
+ &mut tree.children[0],
+ layout,
+ renderer,
+ )
+ }
+}
+
+impl<'a, Message, Renderer> From<MouseArea<'a, Message, Renderer>>
+ for Element<'a, Message, Renderer>
+where
+ Message: 'a + Clone,
+ Renderer: 'a + renderer::Renderer,
+{
+ fn from(
+ area: MouseArea<'a, Message, Renderer>,
+ ) -> Element<'a, Message, Renderer> {
+ Element::new(area)
+ }
+}
+
+/// Processes the given [`Event`] and updates the [`State`] of an [`MouseArea`]
+/// accordingly.
+fn update<Message: Clone, Renderer>(
+ widget: &mut MouseArea<'_, Message, Renderer>,
+ event: &Event,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ shell: &mut Shell<'_, Message>,
+) -> event::Status {
+ if !cursor.is_over(layout.bounds()) {
+ return event::Status::Ignored;
+ }
+
+ if let Some(message) = widget.on_press.as_ref() {
+ if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) = event
+ {
+ shell.publish(message.clone());
+
+ return event::Status::Captured;
+ }
+ }
+
+ if let Some(message) = widget.on_release.as_ref() {
+ if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerLifted { .. }) = event
+ {
+ shell.publish(message.clone());
+
+ return event::Status::Captured;
+ }
+ }
+
+ if let Some(message) = widget.on_right_press.as_ref() {
+ if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) =
+ event
+ {
+ shell.publish(message.clone());
+
+ return event::Status::Captured;
+ }
+ }
+
+ if let Some(message) = widget.on_right_release.as_ref() {
+ if let Event::Mouse(mouse::Event::ButtonReleased(
+ mouse::Button::Right,
+ )) = event
+ {
+ shell.publish(message.clone());
+
+ return event::Status::Captured;
+ }
+ }
+
+ if let Some(message) = widget.on_middle_press.as_ref() {
+ if let Event::Mouse(mouse::Event::ButtonPressed(
+ mouse::Button::Middle,
+ )) = event
+ {
+ shell.publish(message.clone());
+
+ return event::Status::Captured;
+ }
+ }
+
+ if let Some(message) = widget.on_middle_release.as_ref() {
+ if let Event::Mouse(mouse::Event::ButtonReleased(
+ mouse::Button::Middle,
+ )) = event
+ {
+ shell.publish(message.clone());
+
+ return event::Status::Captured;
+ }
+ }
+
+ event::Status::Ignored
+}
diff --git a/graphics/src/overlay.rs b/widget/src/overlay.rs
index bc0ed744..bc0ed744 100644
--- a/graphics/src/overlay.rs
+++ b/widget/src/overlay.rs
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/graphics/src/widget/qr_code.rs b/widget/src/qr_code.rs
index 12ce5b1f..51a541fd 100644
--- a/graphics/src/widget/qr_code.rs
+++ b/widget/src/qr_code.rs
@@ -1,13 +1,14 @@
//! Encode and display information in a QR code.
-use crate::renderer::{self, Renderer};
-use crate::widget::canvas;
-use crate::Backend;
-
-use iced_native::layout;
-use iced_native::widget::Tree;
-use iced_native::{
+use crate::canvas;
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::renderer::{self, Renderer as _};
+use crate::core::widget::Tree;
+use crate::core::{
Color, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget,
};
+use crate::graphics::geometry::Renderer as _;
+use crate::Renderer;
use thiserror::Error;
const DEFAULT_CELL_SIZE: u16 = 4;
@@ -48,10 +49,7 @@ impl<'a> QRCode<'a> {
}
}
-impl<'a, Message, B, T> Widget<Message, Renderer<B, T>> for QRCode<'a>
-where
- B: Backend,
-{
+impl<'a, Message, Theme> Widget<Message, Renderer<Theme>> for QRCode<'a> {
fn width(&self) -> Length {
Length::Shrink
}
@@ -62,7 +60,7 @@ where
fn layout(
&self,
- _renderer: &Renderer<B, T>,
+ _renderer: &Renderer<Theme>,
_limits: &layout::Limits,
) -> layout::Node {
let side_length = (self.state.width + 2 * QUIET_ZONE) as f32
@@ -74,63 +72,63 @@ where
fn draw(
&self,
_state: &Tree,
- renderer: &mut Renderer<B, T>,
- _theme: &T,
+ renderer: &mut Renderer<Theme>,
+ _theme: &Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
- use iced_native::Renderer as _;
-
let bounds = layout.bounds();
let side_length = self.state.width + 2 * QUIET_ZONE;
// Reuse cache if possible
- let geometry = self.state.cache.draw(bounds.size(), |frame| {
- // Scale units to cell size
- frame.scale(f32::from(self.cell_size));
-
- // Draw background
- frame.fill_rectangle(
- Point::ORIGIN,
- Size::new(side_length as f32, side_length as f32),
- self.light,
- );
-
- // Avoid drawing on the quiet zone
- frame.translate(Vector::new(QUIET_ZONE as f32, QUIET_ZONE as f32));
-
- // Draw contents
- self.state
- .contents
- .iter()
- .enumerate()
- .filter(|(_, value)| **value == qrcode::Color::Dark)
- .for_each(|(index, _)| {
- let row = index / self.state.width;
- let column = index % self.state.width;
-
- frame.fill_rectangle(
- Point::new(column as f32, row as f32),
- Size::UNIT,
- self.dark,
- );
- });
- });
+ let geometry =
+ self.state.cache.draw(renderer, bounds.size(), |frame| {
+ // Scale units to cell size
+ frame.scale(f32::from(self.cell_size));
+
+ // Draw background
+ frame.fill_rectangle(
+ Point::ORIGIN,
+ Size::new(side_length as f32, side_length as f32),
+ self.light,
+ );
+
+ // Avoid drawing on the quiet zone
+ frame.translate(Vector::new(
+ QUIET_ZONE as f32,
+ QUIET_ZONE as f32,
+ ));
+
+ // Draw contents
+ self.state
+ .contents
+ .iter()
+ .enumerate()
+ .filter(|(_, value)| **value == qrcode::Color::Dark)
+ .for_each(|(index, _)| {
+ let row = index / self.state.width;
+ let column = index % self.state.width;
+
+ frame.fill_rectangle(
+ Point::new(column as f32, row as f32),
+ Size::UNIT,
+ self.dark,
+ );
+ });
+ });
let translation = Vector::new(bounds.x, bounds.y);
renderer.with_translation(translation, |renderer| {
- renderer.draw_primitive(geometry.into_primitive());
+ renderer.draw(vec![geometry]);
});
}
}
-impl<'a, Message, B, T> From<QRCode<'a>>
- for Element<'a, Message, Renderer<B, T>>
-where
- B: Backend,
+impl<'a, Message, Theme> From<QRCode<'a>>
+ for Element<'a, Message, Renderer<Theme>>
{
fn from(qr_code: QRCode<'a>) -> Self {
Self::new(qr_code)
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/widget/src/text.rs b/widget/src/text.rs
new file mode 100644
index 00000000..ce4f44bd
--- /dev/null
+++ b/widget/src/text.rs
@@ -0,0 +1,6 @@
+//! Draw and interact with text.
+pub use crate::core::widget::text::*;
+
+/// A paragraph.
+pub type Text<'a, Renderer = crate::Renderer> =
+ crate::core::widget::Text<'a, Renderer>;
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