summaryrefslogtreecommitdiffstats
path: root/native
diff options
context:
space:
mode:
Diffstat (limited to 'native')
-rw-r--r--native/Cargo.toml4
-rw-r--r--native/src/element.rs90
-rw-r--r--native/src/image.rs124
-rw-r--r--native/src/layout.rs2
-rw-r--r--native/src/layout/debugger.rs24
-rw-r--r--native/src/lib.rs5
-rw-r--r--native/src/overlay.rs20
-rw-r--r--native/src/overlay/element.rs39
-rw-r--r--native/src/overlay/menu.rs204
-rw-r--r--native/src/program/state.rs30
-rw-r--r--native/src/renderer.rs69
-rw-r--r--native/src/renderer/null.rs291
-rw-r--r--native/src/svg.rs88
-rw-r--r--native/src/text.rs114
-rw-r--r--native/src/user_interface.rs97
-rw-r--r--native/src/widget.rs26
-rw-r--r--native/src/widget/button.rs150
-rw-r--r--native/src/widget/checkbox.rs178
-rw-r--r--native/src/widget/column.rs63
-rw-r--r--native/src/widget/container.rs106
-rw-r--r--native/src/widget/image.rs140
-rw-r--r--native/src/widget/image/viewer.rs84
-rw-r--r--native/src/widget/pane_grid.rs229
-rw-r--r--native/src/widget/pane_grid/configuration.rs2
-rw-r--r--native/src/widget/pane_grid/content.rs100
-rw-r--r--native/src/widget/pane_grid/node.rs6
-rw-r--r--native/src/widget/pane_grid/state.rs6
-rw-r--r--native/src/widget/pane_grid/title_bar.rs94
-rw-r--r--native/src/widget/pick_list.rs153
-rw-r--r--native/src/widget/progress_bar.rs126
-rw-r--r--native/src/widget/radio.rs184
-rw-r--r--native/src/widget/row.rs63
-rw-r--r--native/src/widget/rule.rs105
-rw-r--r--native/src/widget/scrollable.rs294
-rw-r--r--native/src/widget/slider.rs194
-rw-r--r--native/src/widget/space.rs29
-rw-r--r--native/src/widget/svg.rs104
-rw-r--r--native/src/widget/text.rs133
-rw-r--r--native/src/widget/text_input.rs435
-rw-r--r--native/src/widget/text_input/editor.rs2
-rw-r--r--native/src/widget/toggler.rs193
-rw-r--r--native/src/widget/tooltip.rs186
42 files changed, 2649 insertions, 1937 deletions
diff --git a/native/Cargo.toml b/native/Cargo.toml
index a3134ef4..5de99b2e 100644
--- a/native/Cargo.toml
+++ b/native/Cargo.toml
@@ -23,3 +23,7 @@ path = "../core"
version = "0.3"
path = "../futures"
features = ["thread-pool"]
+
+[dependencies.iced_style]
+version = "0.3"
+path = "../style"
diff --git a/native/src/element.rs b/native/src/element.rs
index 5c84a388..ee404a1c 100644
--- a/native/src/element.rs
+++ b/native/src/element.rs
@@ -1,6 +1,8 @@
use crate::event::{self, Event};
use crate::layout;
+use crate::mouse;
use crate::overlay;
+use crate::renderer;
use crate::{
Clipboard, Color, Hasher, Layout, Length, Point, Rectangle, Widget,
};
@@ -77,7 +79,7 @@ where
///
/// ```
/// # mod counter {
- /// # type Text = iced_native::Text<iced_native::renderer::Null>;
+ /// # type Text = iced_native::widget::Text<iced_native::renderer::Null>;
/// #
/// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {}
@@ -104,7 +106,8 @@ where
/// # pub enum Message {
/// # Counter(usize, counter::Message)
/// # }
- /// use iced_native::{Element, Row};
+ /// use iced_native::Element;
+ /// use iced_native::widget::Row;
/// use iced_wgpu::Renderer;
///
/// impl ManyCounters {
@@ -189,7 +192,7 @@ where
) -> Element<'a, Message, Renderer>
where
Message: 'static,
- Renderer: 'a + layout::Debugger,
+ Renderer: 'a,
{
Element {
widget: Box::new(Explain::new(self, color.into())),
@@ -241,13 +244,24 @@ where
pub fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) {
+ self.widget
+ .draw(renderer, style, layout, cursor_position, viewport)
+ }
+
+ /// Returns the current [`mouse::Interaction`] of the [`Element`].
+ pub fn mouse_interaction(
+ &self,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
- ) -> Renderer::Output {
+ ) -> mouse::Interaction {
self.widget
- .draw(renderer, defaults, layout, cursor_position, viewport)
+ .mouse_interaction(layout, cursor_position, viewport)
}
/// Computes the _layout_ hash of the [`Element`].
@@ -336,13 +350,23 @@ where
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) {
+ self.widget
+ .draw(renderer, style, layout, cursor_position, viewport)
+ }
+
+ fn mouse_interaction(
+ &self,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
- ) -> Renderer::Output {
+ ) -> mouse::Interaction {
self.widget
- .draw(renderer, defaults, layout, cursor_position, viewport)
+ .mouse_interaction(layout, cursor_position, viewport)
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -378,7 +402,7 @@ where
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Explain<'a, Message, Renderer>
where
- Renderer: crate::Renderer + layout::Debugger,
+ Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.element.widget.width()
@@ -418,19 +442,51 @@ where
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
- ) -> Renderer::Output {
- renderer.explain(
- defaults,
- self.element.widget.as_ref(),
+ ) {
+ fn explain_layout<Renderer: crate::Renderer>(
+ renderer: &mut Renderer,
+ color: Color,
+ layout: Layout<'_>,
+ ) {
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: layout.bounds(),
+ border_color: color,
+ border_width: 1.0,
+ border_radius: 0.0,
+ },
+ Color::TRANSPARENT,
+ );
+
+ for child in layout.children() {
+ explain_layout(renderer, color, child);
+ }
+ }
+
+ self.element.widget.draw(
+ renderer,
+ style,
layout,
cursor_position,
viewport,
- self.color,
- )
+ );
+
+ explain_layout(renderer, self.color, layout);
+ }
+
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ self.element
+ .widget
+ .mouse_interaction(layout, cursor_position, viewport)
}
fn hash_layout(&self, state: &mut Hasher) {
diff --git a/native/src/image.rs b/native/src/image.rs
new file mode 100644
index 00000000..00379417
--- /dev/null
+++ b/native/src/image.rs
@@ -0,0 +1,124 @@
+//! Load and draw raster graphics.
+use crate::{Hasher, Rectangle};
+
+use std::hash::{Hash, Hasher as _};
+use std::path::PathBuf;
+use std::sync::Arc;
+
+/// An [`Image`] handle.
+#[derive(Debug, Clone)]
+pub struct Handle {
+ id: u64,
+ data: Arc<Data>,
+}
+
+impl Handle {
+ /// Creates an image [`Handle`] pointing to the image of the given path.
+ ///
+ /// Makes an educated guess about the image format by examining the data in the file.
+ pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle {
+ Self::from_data(Data::Path(path.into()))
+ }
+
+ /// Creates an image [`Handle`] containing the image pixels directly. This
+ /// function expects the input data to be provided as a `Vec<u8>` of BGRA
+ /// pixels.
+ ///
+ /// This is useful if you have already decoded your image.
+ pub fn from_pixels(width: u32, height: u32, pixels: Vec<u8>) -> Handle {
+ Self::from_data(Data::Pixels {
+ width,
+ height,
+ pixels,
+ })
+ }
+
+ /// Creates an image [`Handle`] containing the image data directly.
+ ///
+ /// Makes an educated guess about the image format by examining the given data.
+ ///
+ /// This is useful if you already have your image loaded in-memory, maybe
+ /// because you downloaded or generated it procedurally.
+ pub fn from_memory(bytes: Vec<u8>) -> Handle {
+ Self::from_data(Data::Bytes(bytes))
+ }
+
+ fn from_data(data: Data) -> Handle {
+ let mut hasher = Hasher::default();
+ data.hash(&mut hasher);
+
+ Handle {
+ id: hasher.finish(),
+ data: Arc::new(data),
+ }
+ }
+
+ /// Returns the unique identifier of the [`Handle`].
+ pub fn id(&self) -> u64 {
+ self.id
+ }
+
+ /// Returns a reference to the image [`Data`].
+ pub fn data(&self) -> &Data {
+ &self.data
+ }
+}
+
+impl<T> From<T> for Handle
+where
+ T: Into<PathBuf>,
+{
+ fn from(path: T) -> Handle {
+ Handle::from_path(path.into())
+ }
+}
+
+impl Hash for Handle {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ self.id.hash(state);
+ }
+}
+
+/// The data of an [`Image`].
+#[derive(Clone, Hash)]
+pub enum Data {
+ /// File data
+ Path(PathBuf),
+
+ /// In-memory data
+ Bytes(Vec<u8>),
+
+ /// Decoded image pixels in BGRA format.
+ Pixels {
+ /// The width of the image.
+ width: u32,
+ /// The height of the image.
+ height: u32,
+ /// The pixels.
+ pixels: Vec<u8>,
+ },
+}
+
+impl std::fmt::Debug for Data {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Data::Path(path) => write!(f, "Path({:?})", path),
+ Data::Bytes(_) => write!(f, "Bytes(...)"),
+ Data::Pixels { width, height, .. } => {
+ write!(f, "Pixels({} * {})", width, height)
+ }
+ }
+ }
+}
+
+/// A [`Renderer`] that can render raster graphics.
+///
+/// [renderer]: crate::renderer
+pub trait Renderer: crate::Renderer {
+ /// Returns the dimensions of an image for the given [`Handle`].
+ fn dimensions(&self, handle: &Handle) -> (u32, u32);
+
+ /// Draws an image with the given [`Handle`] and inside the provided
+ /// `bounds`.
+ fn draw(&mut self, handle: Handle, bounds: Rectangle);
+}
diff --git a/native/src/layout.rs b/native/src/layout.rs
index b4b4a021..04954fb9 100644
--- a/native/src/layout.rs
+++ b/native/src/layout.rs
@@ -1,11 +1,9 @@
//! Position your widgets properly.
-mod debugger;
mod limits;
mod node;
pub mod flex;
-pub use debugger::Debugger;
pub use limits::Limits;
pub use node::Node;
diff --git a/native/src/layout/debugger.rs b/native/src/layout/debugger.rs
deleted file mode 100644
index 0759613f..00000000
--- a/native/src/layout/debugger.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-use crate::{Color, Layout, Point, Rectangle, Renderer, Widget};
-
-/// A renderer able to graphically explain a [`Layout`].
-pub trait Debugger: Renderer {
- /// Explains the [`Layout`] of an [`Element`] for debugging purposes.
- ///
- /// This will be called when [`Element::explain`] has been used. It should
- /// _explain_ the given [`Layout`] graphically.
- ///
- /// A common approach consists in recursively rendering the bounds of the
- /// [`Layout`] and its children.
- ///
- /// [`Element`]: crate::Element
- /// [`Element::explain`]: crate::Element::explain
- fn explain<Message>(
- &mut self,
- defaults: &Self::Defaults,
- widget: &dyn Widget<Message, Self>,
- layout: Layout<'_>,
- cursor_position: Point,
- viewport: &Rectangle,
- color: Color,
- ) -> Self::Output;
-}
diff --git a/native/src/lib.rs b/native/src/lib.rs
index 170a588b..51b232e9 100644
--- a/native/src/lib.rs
+++ b/native/src/lib.rs
@@ -36,6 +36,7 @@
pub mod clipboard;
pub mod command;
pub mod event;
+pub mod image;
pub mod keyboard;
pub mod layout;
pub mod mouse;
@@ -43,6 +44,8 @@ pub mod overlay;
pub mod program;
pub mod renderer;
pub mod subscription;
+pub mod svg;
+pub mod text;
pub mod touch;
pub mod widget;
pub mod window;
@@ -84,4 +87,4 @@ pub use renderer::Renderer;
pub use runtime::Runtime;
pub use subscription::Subscription;
pub use user_interface::{Cache, UserInterface};
-pub use widget::*;
+pub use widget::Widget;
diff --git a/native/src/overlay.rs b/native/src/overlay.rs
index 84145e7f..1ac3cea5 100644
--- a/native/src/overlay.rs
+++ b/native/src/overlay.rs
@@ -8,7 +8,9 @@ pub use menu::Menu;
use crate::event::{self, Event};
use crate::layout;
-use crate::{Clipboard, Hasher, Layout, Point, Size};
+use crate::mouse;
+use crate::renderer;
+use crate::{Clipboard, Hasher, Layout, Point, Rectangle, Size};
/// An interactive component that can be displayed on top of other widgets.
pub trait Overlay<Message, Renderer>
@@ -32,10 +34,10 @@ where
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
- ) -> Renderer::Output;
+ );
/// Computes the _layout_ hash of the [`Overlay`].
///
@@ -73,4 +75,16 @@ where
) -> event::Status {
event::Status::Ignored
}
+
+ /// Returns the current [`mouse::Interaction`] of the [`Widget`].
+ ///
+ /// By default, it returns [`mouse::Interaction::Idle`].
+ fn mouse_interaction(
+ &self,
+ _layout: Layout<'_>,
+ _cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ mouse::Interaction::Idle
+ }
}
diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs
index e4819037..f418a518 100644
--- a/native/src/overlay/element.rs
+++ b/native/src/overlay/element.rs
@@ -2,7 +2,9 @@ pub use crate::Overlay;
use crate::event::{self, Event};
use crate::layout;
-use crate::{Clipboard, Hasher, Layout, Point, Size, Vector};
+use crate::mouse;
+use crate::renderer;
+use crate::{Clipboard, Hasher, Layout, Point, Rectangle, Size, Vector};
/// A generic [`Overlay`].
#[allow(missing_debug_implementations)]
@@ -67,16 +69,26 @@ where
)
}
+ /// Returns the current [`mouse::Interaction`] of the [`Element`].
+ pub fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ self.overlay
+ .mouse_interaction(layout, cursor_position, viewport)
+ }
+
/// Draws the [`Element`] and its children using the given [`Layout`].
pub fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
- ) -> Renderer::Output {
- self.overlay
- .draw(renderer, defaults, layout, cursor_position)
+ ) {
+ self.overlay.draw(renderer, style, layout, cursor_position)
}
/// Computes the _layout_ hash of the [`Element`].
@@ -139,15 +151,24 @@ where
event_status
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ self.content
+ .mouse_interaction(layout, cursor_position, viewport)
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
- ) -> Renderer::Output {
- self.content
- .draw(renderer, defaults, layout, cursor_position)
+ ) {
+ self.content.draw(renderer, style, layout, cursor_position)
}
fn hash_layout(&self, state: &mut Hasher, position: Point) {
diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs
index f62dcb46..ee3bee6e 100644
--- a/native/src/overlay/menu.rs
+++ b/native/src/overlay/menu.rs
@@ -1,20 +1,24 @@
//! Build and show dropdown menus.
-use crate::container;
+use crate::alignment;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
-use crate::scrollable;
-use crate::text;
+use crate::renderer;
+use crate::text::{self, Text};
use crate::touch;
+use crate::widget::scrollable::{self, Scrollable};
+use crate::widget::Container;
use crate::{
- Clipboard, Container, Element, Hasher, Layout, Length, Padding, Point,
- Rectangle, Scrollable, Size, Vector, Widget,
+ Clipboard, Color, Element, Hasher, Layout, Length, Padding, Point,
+ Rectangle, Size, Vector, Widget,
};
+pub use iced_style::menu::Style;
+
/// A list of selectable options.
#[allow(missing_debug_implementations)]
-pub struct Menu<'a, T, Renderer: self::Renderer> {
+pub struct Menu<'a, T, Renderer: text::Renderer> {
state: &'a mut State,
options: &'a [T],
hovered_option: &'a mut Option<usize>,
@@ -23,13 +27,13 @@ pub struct Menu<'a, T, Renderer: self::Renderer> {
padding: Padding,
text_size: Option<u16>,
font: Renderer::Font,
- style: <Renderer as self::Renderer>::Style,
+ style: Style,
}
impl<'a, T, Renderer> Menu<'a, T, Renderer>
where
T: ToString + Clone,
- Renderer: self::Renderer + 'a,
+ Renderer: text::Renderer + 'a,
{
/// Creates a new [`Menu`] with the given [`State`], a list of options, and
/// the message to produced when an option is selected.
@@ -77,10 +81,7 @@ where
}
/// Sets the style of the [`Menu`].
- pub fn style(
- mut self,
- style: impl Into<<Renderer as self::Renderer>::Style>,
- ) -> Self {
+ pub fn style(mut self, style: impl Into<Style>) -> Self {
self.style = style.into();
self
}
@@ -116,14 +117,14 @@ impl State {
}
}
-struct Overlay<'a, Message, Renderer: self::Renderer> {
+struct Overlay<'a, Message, Renderer: text::Renderer> {
container: Container<'a, Message, Renderer>,
width: u16,
target_height: f32,
- style: <Renderer as self::Renderer>::Style,
+ style: Style,
}
-impl<'a, Message, Renderer: self::Renderer> Overlay<'a, Message, Renderer>
+impl<'a, Message, Renderer: text::Renderer> Overlay<'a, Message, Renderer>
where
Message: 'a,
Renderer: 'a,
@@ -168,7 +169,7 @@ where
impl<'a, Message, Renderer> crate::Overlay<Message, Renderer>
for Overlay<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: text::Renderer,
{
fn layout(
&self,
@@ -233,45 +234,55 @@ where
)
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ self.container
+ .mouse_interaction(layout, cursor_position, viewport)
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
- ) -> Renderer::Output {
- let primitives = self.container.draw(
- renderer,
- defaults,
- layout,
- cursor_position,
- &layout.bounds(),
+ ) {
+ let bounds = layout.bounds();
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_color: self.style.border_color,
+ border_width: self.style.border_width,
+ border_radius: 0.0,
+ },
+ self.style.background,
);
- renderer.decorate(
- layout.bounds(),
- cursor_position,
- &self.style,
- primitives,
- )
+ self.container
+ .draw(renderer, style, layout, cursor_position, &bounds);
}
}
-struct List<'a, T, Renderer: self::Renderer> {
+struct List<'a, T, Renderer: text::Renderer> {
options: &'a [T],
hovered_option: &'a mut Option<usize>,
last_selection: &'a mut Option<T>,
padding: Padding,
text_size: Option<u16>,
font: Renderer::Font,
- style: <Renderer as self::Renderer>::Style,
+ style: Style,
}
-impl<'a, T, Message, Renderer: self::Renderer> Widget<Message, Renderer>
+impl<'a, T, Message, Renderer> Widget<Message, Renderer>
for List<'a, T, Renderer>
where
T: Clone + ToString,
- Renderer: self::Renderer,
+ Renderer: text::Renderer,
{
fn width(&self) -> Length {
Length::Fill
@@ -376,65 +387,84 @@ where
event::Status::Ignored
}
- fn draw(
+ fn mouse_interaction(
&self,
- renderer: &mut Renderer,
- _defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
- viewport: &Rectangle,
- ) -> Renderer::Output {
- self::Renderer::draw(
- renderer,
- layout.bounds(),
- cursor_position,
- viewport,
- self.options,
- *self.hovered_option,
- self.padding,
- self.text_size.unwrap_or(renderer.default_size()),
- self.font,
- &self.style,
- )
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ let is_mouse_over = layout.bounds().contains(cursor_position);
+
+ if is_mouse_over {
+ mouse::Interaction::Pointer
+ } else {
+ mouse::Interaction::default()
+ }
}
-}
-/// The renderer of a [`Menu`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Menu`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer:
- scrollable::Renderer + container::Renderer + text::Renderer
-{
- /// The [`Menu`] style supported by this renderer.
- type Style: Default + Clone;
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ _style: &renderer::Style,
+ layout: Layout<'_>,
+ _cursor_position: Point,
+ viewport: &Rectangle,
+ ) {
+ let bounds = layout.bounds();
- /// Decorates a the list of options of a [`Menu`].
- ///
- /// This method can be used to draw a background for the [`Menu`].
- fn decorate(
- &mut self,
- bounds: Rectangle,
- cursor_position: Point,
- style: &<Self as Renderer>::Style,
- primitive: Self::Output,
- ) -> Self::Output;
+ let text_size = self.text_size.unwrap_or(renderer.default_size());
+ let option_height = (text_size + self.padding.vertical()) as usize;
+
+ 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 visible_options = &self.options[start..end.min(self.options.len())];
+
+ for (i, option) in visible_options.iter().enumerate() {
+ let i = start + i;
+ let is_selected = *self.hovered_option == Some(i);
+
+ let bounds = Rectangle {
+ x: bounds.x,
+ y: bounds.y + (option_height * i) as f32,
+ width: bounds.width,
+ height: f32::from(text_size + self.padding.vertical()),
+ };
+
+ if is_selected {
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_color: Color::TRANSPARENT,
+ border_width: 0.0,
+ border_radius: 0.0,
+ },
+ self.style.selected_background,
+ );
+ }
- /// Draws the list of options of a [`Menu`].
- fn draw<T: ToString>(
- &mut self,
- bounds: Rectangle,
- cursor_position: Point,
- viewport: &Rectangle,
- options: &[T],
- hovered_option: Option<usize>,
- padding: Padding,
- text_size: u16,
- font: Self::Font,
- style: &<Self as Renderer>::Style,
- ) -> Self::Output;
+ renderer.fill_text(Text {
+ content: &option.to_string(),
+ bounds: Rectangle {
+ x: bounds.x + self.padding.left as f32,
+ y: bounds.center_y(),
+ width: f32::INFINITY,
+ ..bounds
+ },
+ size: f32::from(text_size),
+ font: self.font,
+ color: if is_selected {
+ self.style.selected_text_color
+ } else {
+ self.style.text_color
+ },
+ horizontal_alignment: alignment::Horizontal::Left,
+ vertical_alignment: alignment::Vertical::Center,
+ });
+ }
+ }
}
impl<'a, T, Message, Renderer> Into<Element<'a, Message, Renderer>>
@@ -442,7 +472,7 @@ impl<'a, T, Message, Renderer> Into<Element<'a, Message, Renderer>>
where
T: ToString + Clone,
Message: 'a,
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + text::Renderer,
{
fn into(self) -> Element<'a, Message, Renderer> {
Element::new(self)
diff --git a/native/src/program/state.rs b/native/src/program/state.rs
index 3f5f6069..26c0eb21 100644
--- a/native/src/program/state.rs
+++ b/native/src/program/state.rs
@@ -1,5 +1,6 @@
+use crate::mouse;
use crate::{
- Cache, Clipboard, Command, Debug, Event, Point, Program, Renderer, Size,
+ Cache, Clipboard, Command, Debug, Event, Point, Program, Size,
UserInterface,
};
@@ -12,9 +13,9 @@ where
{
program: P,
cache: Option<Cache>,
- primitive: <P::Renderer as Renderer>::Output,
queued_events: Vec<Event>,
queued_messages: Vec<P::Message>,
+ mouse_interaction: mouse::Interaction,
}
impl<P> State<P>
@@ -26,11 +27,10 @@ where
pub fn new(
mut program: P,
bounds: Size,
- cursor_position: Point,
renderer: &mut P::Renderer,
debug: &mut Debug,
) -> Self {
- let mut user_interface = build_user_interface(
+ let user_interface = build_user_interface(
&mut program,
Cache::default(),
renderer,
@@ -38,18 +38,14 @@ where
debug,
);
- debug.draw_started();
- let primitive = user_interface.draw(renderer, cursor_position);
- debug.draw_finished();
-
let cache = Some(user_interface.into_cache());
State {
program,
cache,
- primitive,
queued_events: Vec::new(),
queued_messages: Vec::new(),
+ mouse_interaction: mouse::Interaction::Idle,
}
}
@@ -58,11 +54,6 @@ where
&self.program
}
- /// Returns a reference to the current rendering primitive of the [`State`].
- pub fn primitive(&self) -> &<P::Renderer as Renderer>::Output {
- &self.primitive
- }
-
/// Queues an event in the [`State`] for processing during an [`update`].
///
/// [`update`]: Self::update
@@ -82,6 +73,11 @@ where
self.queued_events.is_empty() && self.queued_messages.is_empty()
}
+ /// Returns the current [`mouse::Interaction`] of the [`State`].
+ pub fn mouse_interaction(&self) -> mouse::Interaction {
+ self.mouse_interaction
+ }
+
/// Processes all the queued events and messages, rebuilding and redrawing
/// the widgets of the linked [`Program`] if necessary.
///
@@ -120,7 +116,8 @@ where
if messages.is_empty() {
debug.draw_started();
- self.primitive = user_interface.draw(renderer, cursor_position);
+ self.mouse_interaction =
+ user_interface.draw(renderer, cursor_position);
debug.draw_finished();
self.cache = Some(user_interface.into_cache());
@@ -151,7 +148,8 @@ where
);
debug.draw_started();
- self.primitive = user_interface.draw(renderer, cursor_position);
+ self.mouse_interaction =
+ user_interface.draw(renderer, cursor_position);
debug.draw_finished();
self.cache = Some(user_interface.into_cache());
diff --git a/native/src/renderer.rs b/native/src/renderer.rs
index 39a6cff1..ca7ad5a2 100644
--- a/native/src/renderer.rs
+++ b/native/src/renderer.rs
@@ -19,28 +19,17 @@
//! [`text::Renderer`]: crate::widget::text::Renderer
//! [`Checkbox`]: crate::widget::Checkbox
//! [`checkbox::Renderer`]: crate::widget::checkbox::Renderer
-
#[cfg(debug_assertions)]
mod null;
#[cfg(debug_assertions)]
pub use null::Null;
-use crate::{layout, Element, Rectangle};
+use crate::layout;
+use crate::{Background, Color, Element, Rectangle, Vector};
/// A component that can take the state of a user interface and produce an
/// output for its users.
pub trait Renderer: Sized {
- /// The type of output of the [`Renderer`].
- ///
- /// If you are implementing a graphical renderer, your output will most
- /// likely be a tree of visual primitives.
- type Output;
-
- /// The default styling attributes of the [`Renderer`].
- ///
- /// This type can be leveraged to implement style inheritance.
- type Defaults: Default;
-
/// Lays out the elements of a user interface.
///
/// You should override this if you need to perform any operations before or
@@ -53,12 +42,52 @@ pub trait Renderer: Sized {
element.layout(self, limits)
}
- /// Overlays the `overlay` output with the given bounds on top of the `base`
- /// output.
- fn overlay(
+ /// Draws the primitives recorded in the given closure in a new layer.
+ ///
+ /// The layer will clip its contents to the provided `bounds`.
+ fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self));
+
+ /// Applies a `translation` to the primitives recorded in the given closure.
+ fn with_translation(
&mut self,
- base: Self::Output,
- overlay: Self::Output,
- overlay_bounds: Rectangle,
- ) -> Self::Output;
+ translation: Vector,
+ f: impl FnOnce(&mut Self),
+ );
+
+ /// Clears all of the recorded primitives in the [`Renderer`].
+ fn clear(&mut self);
+
+ /// Fills a [`Quad`] with the provided [`Background`].
+ fn fill_quad(&mut self, quad: Quad, background: impl Into<Background>);
+}
+
+/// A polygon with four sides.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct Quad {
+ /// The bounds of the [`Quad`].
+ pub bounds: Rectangle,
+
+ /// The border radius of the [`Quad`].
+ pub border_radius: f32,
+
+ /// The border width of the [`Quad`].
+ pub border_width: f32,
+
+ /// The border color of the [`Quad`].
+ pub border_color: Color,
+}
+
+/// The styling attributes of a [`Renderer`].
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct Style {
+ /// The text color
+ pub text_color: Color,
+}
+
+impl Default for Style {
+ fn default() -> Self {
+ Style {
+ text_color: Color::BLACK,
+ }
+ }
}
diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs
index b5921582..a5b2f277 100644
--- a/native/src/renderer/null.rs
+++ b/native/src/renderer/null.rs
@@ -1,20 +1,6 @@
-use crate::alignment;
-use crate::button;
-use crate::checkbox;
-use crate::column;
-use crate::container;
-use crate::pane_grid;
-use crate::progress_bar;
-use crate::radio;
-use crate::row;
-use crate::scrollable;
-use crate::slider;
-use crate::text;
-use crate::text_input;
-use crate::toggler;
-use crate::{
- Color, Element, Font, Layout, Padding, Point, Rectangle, Renderer, Size,
-};
+use crate::renderer::{self, Renderer};
+use crate::text::{self, Text};
+use crate::{Background, Font, Point, Rectangle, Size, Vector};
/// A renderer that does nothing.
///
@@ -30,33 +16,21 @@ impl Null {
}
impl Renderer for Null {
- type Output = ();
- type Defaults = ();
+ fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {}
- fn overlay(&mut self, _base: (), _overlay: (), _overlay_bounds: Rectangle) {
- }
-}
-
-impl column::Renderer for Null {
- fn draw<Message>(
+ fn with_translation(
&mut self,
- _defaults: &Self::Defaults,
- _content: &[Element<'_, Message, Self>],
- _layout: Layout<'_>,
- _cursor_position: Point,
- _viewport: &Rectangle,
+ _translation: Vector,
+ _f: impl FnOnce(&mut Self),
) {
}
-}
-impl row::Renderer for Null {
- fn draw<Message>(
+ fn clear(&mut self) {}
+
+ fn fill_quad(
&mut self,
- _defaults: &Self::Defaults,
- _content: &[Element<'_, Message, Self>],
- _layout: Layout<'_>,
- _cursor_position: Point,
- _viewport: &Rectangle,
+ _quad: renderer::Quad,
+ _background: impl Into<Background>,
) {
}
}
@@ -64,6 +38,10 @@ impl row::Renderer for Null {
impl text::Renderer for Null {
type Font = Font;
+ const ICON_FONT: Font = Font::Default;
+ const CHECKMARK_ICON: char = '0';
+ const ARROW_DOWN_ICON: char = '0';
+
fn default_size(&self) -> u16 {
20
}
@@ -90,240 +68,5 @@ impl text::Renderer for Null {
None
}
- fn draw(
- &mut self,
- _defaults: &Self::Defaults,
- _bounds: Rectangle,
- _content: &str,
- _size: u16,
- _font: Font,
- _color: Option<Color>,
- _horizontal_alignment: alignment::Horizontal,
- _vertical_alignment: alignment::Vertical,
- ) {
- }
-}
-
-impl scrollable::Renderer for Null {
- type Style = ();
-
- fn scrollbar(
- &self,
- _bounds: Rectangle,
- _content_bounds: Rectangle,
- _offset: u32,
- _scrollbar_width: u16,
- _scrollbar_margin: u16,
- _scroller_width: u16,
- ) -> Option<scrollable::Scrollbar> {
- None
- }
-
- fn draw(
- &mut self,
- _scrollable: &scrollable::State,
- _bounds: Rectangle,
- _content_bounds: Rectangle,
- _is_mouse_over: bool,
- _is_mouse_over_scrollbar: bool,
- _scrollbar: Option<scrollable::Scrollbar>,
- _offset: u32,
- _style: &Self::Style,
- _content: Self::Output,
- ) {
- }
-}
-
-impl text_input::Renderer for Null {
- type Style = ();
-
- fn measure_value(&self, _value: &str, _size: u16, _font: Font) -> f32 {
- 0.0
- }
-
- fn offset(
- &self,
- _text_bounds: Rectangle,
- _font: Font,
- _size: u16,
- _value: &text_input::Value,
- _state: &text_input::State,
- ) -> f32 {
- 0.0
- }
-
- fn draw(
- &mut self,
- _bounds: Rectangle,
- _text_bounds: Rectangle,
- _cursor_position: Point,
- _font: Font,
- _size: u16,
- _placeholder: &str,
- _value: &text_input::Value,
- _state: &text_input::State,
- _style: &Self::Style,
- ) -> Self::Output {
- }
-}
-
-impl button::Renderer for Null {
- const DEFAULT_PADDING: Padding = Padding::ZERO;
-
- type Style = ();
-
- fn draw<Message>(
- &mut self,
- _defaults: &Self::Defaults,
- _bounds: Rectangle,
- _cursor_position: Point,
- _is_disabled: bool,
- _is_pressed: bool,
- _style: &Self::Style,
- _content: &Element<'_, Message, Self>,
- _content_layout: Layout<'_>,
- ) -> Self::Output {
- }
-}
-
-impl radio::Renderer for Null {
- type Style = ();
-
- const DEFAULT_SIZE: u16 = 20;
- const DEFAULT_SPACING: u16 = 15;
-
- fn draw(
- &mut self,
- _bounds: Rectangle,
- _is_selected: bool,
- _is_mouse_over: bool,
- _label: Self::Output,
- _style: &Self::Style,
- ) {
- }
-}
-
-impl checkbox::Renderer for Null {
- type Style = ();
-
- const DEFAULT_SIZE: u16 = 20;
- const DEFAULT_SPACING: u16 = 15;
-
- fn draw(
- &mut self,
- _bounds: Rectangle,
- _is_checked: bool,
- _is_mouse_over: bool,
- _label: Self::Output,
- _style: &Self::Style,
- ) {
- }
-}
-
-impl slider::Renderer for Null {
- type Style = ();
-
- const DEFAULT_HEIGHT: u16 = 30;
-
- fn draw(
- &mut self,
- _bounds: Rectangle,
- _cursor_position: Point,
- _range: std::ops::RangeInclusive<f32>,
- _value: f32,
- _is_dragging: bool,
- _style_sheet: &Self::Style,
- ) {
- }
-}
-
-impl progress_bar::Renderer for Null {
- type Style = ();
-
- const DEFAULT_HEIGHT: u16 = 30;
-
- fn draw(
- &self,
- _bounds: Rectangle,
- _range: std::ops::RangeInclusive<f32>,
- _value: f32,
- _style: &Self::Style,
- ) {
- }
-}
-
-impl container::Renderer for Null {
- type Style = ();
-
- fn draw<Message>(
- &mut self,
- _defaults: &Self::Defaults,
- _bounds: Rectangle,
- _cursor_position: Point,
- _viewport: &Rectangle,
- _style: &Self::Style,
- _content: &Element<'_, Message, Self>,
- _content_layout: Layout<'_>,
- ) {
- }
-}
-
-impl pane_grid::Renderer for Null {
- type Style = ();
-
- fn draw<Message>(
- &mut self,
- _defaults: &Self::Defaults,
- _content: &[(pane_grid::Pane, pane_grid::Content<'_, Message, Self>)],
- _dragging: Option<(pane_grid::Pane, Point)>,
- _resizing: Option<(pane_grid::Axis, Rectangle, bool)>,
- _layout: Layout<'_>,
- _style: &<Self as pane_grid::Renderer>::Style,
- _cursor_position: Point,
- _viewport: &Rectangle,
- ) {
- }
-
- fn draw_pane<Message>(
- &mut self,
- _defaults: &Self::Defaults,
- _bounds: Rectangle,
- _style: &<Self as container::Renderer>::Style,
- _title_bar: Option<(
- &pane_grid::TitleBar<'_, Message, Self>,
- Layout<'_>,
- )>,
- _body: (&Element<'_, Message, Self>, Layout<'_>),
- _cursor_position: Point,
- _viewport: &Rectangle,
- ) {
- }
-
- fn draw_title_bar<Message>(
- &mut self,
- _defaults: &Self::Defaults,
- _bounds: Rectangle,
- _style: &<Self as container::Renderer>::Style,
- _content: (&Element<'_, Message, Self>, Layout<'_>),
- _controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>,
- _cursor_position: Point,
- _viewport: &Rectangle,
- ) {
- }
-}
-
-impl toggler::Renderer for Null {
- type Style = ();
-
- const DEFAULT_SIZE: u16 = 20;
-
- fn draw(
- &mut self,
- _bounds: Rectangle,
- _is_checked: bool,
- _is_mouse_over: bool,
- _label: Option<Self::Output>,
- _style: &Self::Style,
- ) {
- }
+ fn fill_text(&mut self, _text: Text<'_, Self::Font>) {}
}
diff --git a/native/src/svg.rs b/native/src/svg.rs
new file mode 100644
index 00000000..90eff87e
--- /dev/null
+++ b/native/src/svg.rs
@@ -0,0 +1,88 @@
+//! Load and draw vector graphics.
+use crate::{Hasher, Rectangle};
+
+use std::hash::{Hash, Hasher as _};
+use std::path::PathBuf;
+use std::sync::Arc;
+
+/// An [`Svg`] handle.
+#[derive(Debug, Clone)]
+pub struct Handle {
+ id: u64,
+ data: Arc<Data>,
+}
+
+impl Handle {
+ /// Creates an SVG [`Handle`] pointing to the vector image of the given
+ /// path.
+ pub fn from_path(path: impl Into<PathBuf>) -> Handle {
+ Self::from_data(Data::Path(path.into()))
+ }
+
+ /// Creates an SVG [`Handle`] from raw bytes containing either an SVG string
+ /// or gzip compressed data.
+ ///
+ /// This is useful if you already have your SVG data in-memory, maybe
+ /// because you downloaded or generated it procedurally.
+ pub fn from_memory(bytes: impl Into<Vec<u8>>) -> Handle {
+ Self::from_data(Data::Bytes(bytes.into()))
+ }
+
+ fn from_data(data: Data) -> Handle {
+ let mut hasher = Hasher::default();
+ data.hash(&mut hasher);
+
+ Handle {
+ id: hasher.finish(),
+ data: Arc::new(data),
+ }
+ }
+
+ /// Returns the unique identifier of the [`Handle`].
+ pub fn id(&self) -> u64 {
+ self.id
+ }
+
+ /// Returns a reference to the SVG [`Data`].
+ pub fn data(&self) -> &Data {
+ &self.data
+ }
+}
+
+impl Hash for Handle {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ self.id.hash(state);
+ }
+}
+
+/// The data of an [`Svg`].
+#[derive(Clone, Hash)]
+pub enum Data {
+ /// File data
+ Path(PathBuf),
+
+ /// In-memory data
+ ///
+ /// Can contain an SVG string or a gzip compressed data.
+ Bytes(Vec<u8>),
+}
+
+impl std::fmt::Debug for Data {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Data::Path(path) => write!(f, "Path({:?})", path),
+ Data::Bytes(_) => write!(f, "Bytes(...)"),
+ }
+ }
+}
+
+/// A [`Renderer`] that can render vector graphics.
+///
+/// [renderer]: crate::renderer
+pub trait Renderer: crate::Renderer {
+ /// Returns the default dimensions of an SVG for the given [`Handle`].
+ fn dimensions(&self, handle: &Handle) -> (u32, u32);
+
+ /// Draws an SVG with the given [`Handle`] and inside the provided `bounds`.
+ fn draw(&mut self, handle: Handle, bounds: Rectangle);
+}
diff --git a/native/src/text.rs b/native/src/text.rs
new file mode 100644
index 00000000..8b9205e3
--- /dev/null
+++ b/native/src/text.rs
@@ -0,0 +1,114 @@
+//! Draw and interact with text.
+use crate::alignment;
+use crate::{Color, Point, Rectangle, Size, Vector};
+
+/// A paragraph.
+#[derive(Debug, Clone, Copy)]
+pub struct Text<'a, Font> {
+ /// The content of the paragraph.
+ pub content: &'a str,
+
+ /// The bounds of the paragraph.
+ pub bounds: Rectangle,
+
+ /// The size of the [`Text`].
+ pub size: f32,
+
+ /// The color of the [`Text`].
+ pub color: Color,
+
+ /// The font of the [`Text`].
+ pub font: Font,
+
+ /// The horizontal alignment of the [`Text`].
+ pub horizontal_alignment: alignment::Horizontal,
+
+ /// The vertical alignment of the [`Text`].
+ pub vertical_alignment: alignment::Vertical,
+}
+
+/// The result of hit testing on text.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum Hit {
+ /// The point was within the bounds of the returned character index.
+ CharOffset(usize),
+ /// The provided point was not within the bounds of a glyph. The index
+ /// of the character with the closest centeroid position is returned,
+ /// as well as its delta.
+ NearestCharOffset(usize, Vector),
+}
+
+impl Hit {
+ /// Computes the cursor position corresponding to this [`HitTestResult`] .
+ pub fn cursor(self) -> usize {
+ match self {
+ Self::CharOffset(i) => i,
+ Self::NearestCharOffset(i, delta) => {
+ if delta.x > f32::EPSILON {
+ i + 1
+ } else {
+ i
+ }
+ }
+ }
+ }
+}
+
+/// A renderer capable of measuring and drawing [`Text`].
+pub trait Renderer: crate::Renderer {
+ /// The font type used.
+ type Font: Default + Copy;
+
+ /// The icon font of the backend.
+ const ICON_FONT: Self::Font;
+
+ /// The `char` representing a ✔ icon in the [`ICON_FONT`].
+ ///
+ /// [`ICON_FONT`]: Self::ICON_FONT
+ const CHECKMARK_ICON: char;
+
+ /// The `char` representing a â–¼ icon in the built-in [`ICON_FONT`].
+ ///
+ /// [`ICON_FONT`]: Self::ICON_FONT
+ const ARROW_DOWN_ICON: char;
+
+ /// Returns the default size of [`Text`].
+ fn default_size(&self) -> u16;
+
+ /// Measures the text in the given bounds and returns the minimum boundaries
+ /// that can fit the contents.
+ fn measure(
+ &self,
+ content: &str,
+ size: u16,
+ font: Self::Font,
+ bounds: Size,
+ ) -> (f32, f32);
+
+ /// Measures the width of the text as if it were laid out in a single line.
+ fn measure_width(&self, content: &str, size: u16, font: Self::Font) -> f32 {
+ let (width, _) = self.measure(content, size, font, Size::INFINITY);
+
+ width
+ }
+
+ /// Tests whether the provided point is within the boundaries of text
+ /// laid out with the given parameters, returning information about
+ /// the nearest character.
+ ///
+ /// If `nearest_only` is true, the hit test does not consider whether the
+ /// the point is interior to any glyph bounds, returning only the character
+ /// with the nearest centeroid.
+ fn hit_test(
+ &self,
+ contents: &str,
+ size: f32,
+ font: Self::Font,
+ bounds: Size,
+ point: Point,
+ nearest_only: bool,
+ ) -> Option<Hit>;
+
+ /// Draws the given [`Text`].
+ fn fill_text(&mut self, text: Text<'_, Self::Font>);
+}
diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs
index 8e0d7d1c..39cac559 100644
--- a/native/src/user_interface.rs
+++ b/native/src/user_interface.rs
@@ -1,6 +1,8 @@
use crate::event::{self, Event};
use crate::layout;
+use crate::mouse;
use crate::overlay;
+use crate::renderer;
use crate::{Clipboard, Element, Layout, Point, Rectangle, Size};
use std::hash::Hasher;
@@ -47,7 +49,7 @@ where
/// # pub use iced_native::renderer::Null as Renderer;
/// # }
/// #
- /// # use iced_native::Column;
+ /// # use iced_native::widget::Column;
/// #
/// # pub struct Counter;
/// #
@@ -141,7 +143,7 @@ where
/// # pub use iced_native::renderer::Null as Renderer;
/// # }
/// #
- /// # use iced_native::Column;
+ /// # use iced_native::widget::Column;
/// #
/// # pub struct Counter;
/// #
@@ -277,7 +279,7 @@ where
/// # pub use iced_native::renderer::Null as Renderer;
/// # }
/// #
- /// # use iced_native::Column;
+ /// # use iced_native::widget::Column;
/// #
/// # pub struct Counter;
/// #
@@ -333,10 +335,13 @@ where
&mut self,
renderer: &mut Renderer,
cursor_position: Point,
- ) -> Renderer::Output {
+ ) -> mouse::Interaction {
+ // TODO: Move to shell level (?)
+ renderer.clear();
+
let viewport = Rectangle::with_size(self.bounds);
- let overlay = if let Some(mut overlay) =
+ if let Some(mut overlay) =
self.root.overlay(Layout::new(&self.base.layout))
{
let layer = Self::overlay_layer(
@@ -346,51 +351,81 @@ where
renderer,
);
- let overlay_bounds = layer.layout.bounds();
-
- let overlay_primitives = overlay.draw(
- renderer,
- &Renderer::Defaults::default(),
- Layout::new(&layer.layout),
- cursor_position,
- );
-
self.overlay = Some(layer);
-
- Some((overlay_primitives, overlay_bounds))
- } else {
- None
};
- if let Some((overlay_primitives, overlay_bounds)) = overlay {
- let base_cursor = if overlay_bounds.contains(cursor_position) {
+ if let Some(layer) = &self.overlay {
+ let base_cursor = if layer.layout.bounds().contains(cursor_position)
+ {
Point::new(-1.0, -1.0)
} else {
cursor_position
};
- let base_primitives = self.root.widget.draw(
+ self.root.widget.draw(
renderer,
- &Renderer::Defaults::default(),
+ &renderer::Style::default(),
Layout::new(&self.base.layout),
base_cursor,
&viewport,
);
-
- renderer.overlay(
- base_primitives,
- overlay_primitives,
- overlay_bounds,
- )
} else {
self.root.widget.draw(
renderer,
- &Renderer::Defaults::default(),
+ &renderer::Style::default(),
Layout::new(&self.base.layout),
cursor_position,
&viewport,
- )
- }
+ );
+ };
+
+ let base_interaction = self.root.widget.mouse_interaction(
+ Layout::new(&self.base.layout),
+ cursor_position,
+ &viewport,
+ );
+
+ let Self {
+ overlay,
+ root,
+ base,
+ ..
+ } = self;
+
+ // TODO: Currently, we need to call Widget::overlay twice to
+ // implement the painter's algorithm properly.
+ //
+ // Once we have a proper persistent widget tree, we should be able to
+ // avoid this additional call.
+ overlay
+ .as_ref()
+ .and_then(|layer| {
+ root.overlay(Layout::new(&base.layout)).map(|overlay| {
+ let overlay_interaction = overlay.mouse_interaction(
+ Layout::new(&layer.layout),
+ cursor_position,
+ &viewport,
+ );
+
+ let overlay_bounds = layer.layout.bounds();
+
+ renderer.with_layer(viewport, |renderer| {
+ overlay.draw(
+ renderer,
+ &renderer::Style::default(),
+ Layout::new(&layer.layout),
+ cursor_position,
+ );
+ });
+
+ if overlay_bounds.contains(cursor_position) {
+ overlay_interaction
+ } else {
+ base_interaction
+ }
+ })
+ })
+ .unwrap_or(base_interaction)
}
/// Relayouts and returns a new [`UserInterface`] using the provided
diff --git a/native/src/widget.rs b/native/src/widget.rs
index 43c1b023..07214b16 100644
--- a/native/src/widget.rs
+++ b/native/src/widget.rs
@@ -10,14 +10,6 @@
//! [`Widget`] trait. You can use the API of the built-in widgets as a guide or
//! source of inspiration.
//!
-//! # Re-exports
-//! For convenience, the contents of this module are available at the root
-//! module. Therefore, you can directly type:
-//!
-//! ```
-//! use iced_native::{button, Button, Widget};
-//! ```
-//!
//! [renderer]: crate::renderer
pub mod button;
pub mod checkbox;
@@ -80,7 +72,9 @@ pub use tooltip::Tooltip;
use crate::event::{self, Event};
use crate::layout;
+use crate::mouse;
use crate::overlay;
+use crate::renderer;
use crate::{Clipboard, Hasher, Layout, Length, Point, Rectangle};
/// A component that displays information and allows interaction.
@@ -131,11 +125,11 @@ where
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
- ) -> Renderer::Output;
+ );
/// Computes the _layout_ hash of the [`Widget`].
///
@@ -174,6 +168,18 @@ where
event::Status::Ignored
}
+ /// Returns the current [`mouse::Interaction`] of the [`Widget`].
+ ///
+ /// By default, it returns [`mouse::Interaction::Idle`].
+ fn mouse_interaction(
+ &self,
+ _layout: Layout<'_>,
+ _cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ mouse::Interaction::Idle
+ }
+
/// Returns the overlay of the [`Widget`], if there is any.
fn overlay(
&mut self,
diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs
index c469a0e5..1d785f35 100644
--- a/native/src/widget/button.rs
+++ b/native/src/widget/button.rs
@@ -5,20 +5,24 @@ use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
+use crate::renderer;
use crate::touch;
use crate::{
- Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
- Widget,
+ Background, Clipboard, Color, Element, Hasher, Layout, Length, Padding,
+ Point, Rectangle, Vector, Widget,
};
+
use std::hash::Hash;
+pub use iced_style::button::{Style, StyleSheet};
+
/// A generic widget that produces a message when pressed.
///
/// ```
-/// # use iced_native::{button, Text};
+/// # use iced_native::widget::{button, Text};
/// #
/// # type Button<'a, Message> =
-/// # iced_native::Button<'a, Message, iced_native::renderer::Null>;
+/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>;
/// #
/// #[derive(Clone)]
/// enum Message {
@@ -34,10 +38,10 @@ use std::hash::Hash;
/// be disabled:
///
/// ```
-/// # use iced_native::{button, Text};
+/// # use iced_native::widget::{button, Text};
/// #
/// # type Button<'a, Message> =
-/// # iced_native::Button<'a, Message, iced_native::renderer::Null>;
+/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>;
/// #
/// #[derive(Clone)]
/// enum Message {
@@ -53,7 +57,7 @@ use std::hash::Hash;
/// }
/// ```
#[allow(missing_debug_implementations)]
-pub struct Button<'a, Message, Renderer: self::Renderer> {
+pub struct Button<'a, Message, Renderer> {
state: &'a mut State,
content: Element<'a, Message, Renderer>,
on_press: Option<Message>,
@@ -62,13 +66,13 @@ pub struct Button<'a, Message, Renderer: self::Renderer> {
min_width: u32,
min_height: u32,
padding: Padding,
- style: Renderer::Style,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
impl<'a, Message, Renderer> Button<'a, Message, Renderer>
where
Message: Clone,
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
/// Creates a new [`Button`] with some local [`State`] and the given
/// content.
@@ -84,8 +88,8 @@ where
height: Length::Shrink,
min_width: 0,
min_height: 0,
- padding: Renderer::DEFAULT_PADDING,
- style: Renderer::Style::default(),
+ padding: Padding::new(5),
+ style_sheet: Default::default(),
}
}
@@ -127,8 +131,11 @@ where
}
/// Sets the style of the [`Button`].
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
}
@@ -150,7 +157,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
for Button<'a, Message, Renderer>
where
Message: Clone,
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -241,24 +248,88 @@ where
event::Status::Ignored
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ let is_mouse_over = layout.bounds().contains(cursor_position);
+ let is_disabled = self.on_press.is_none();
+
+ if is_mouse_over && !is_disabled {
+ mouse::Interaction::Pointer
+ } else {
+ mouse::Interaction::default()
+ }
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ _style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
- renderer.draw(
- defaults,
- layout.bounds(),
+ ) {
+ let bounds = layout.bounds();
+ let content_layout = layout.children().next().unwrap();
+
+ let is_mouse_over = bounds.contains(cursor_position);
+ let is_disabled = self.on_press.is_none();
+
+ let styling = if is_disabled {
+ self.style_sheet.disabled()
+ } else if is_mouse_over {
+ if self.state.is_pressed {
+ self.style_sheet.pressed()
+ } else {
+ self.style_sheet.hovered()
+ }
+ } else {
+ self.style_sheet.active()
+ };
+
+ if styling.background.is_some() || styling.border_width > 0.0 {
+ if styling.shadow_offset != Vector::default() {
+ // TODO: Implement proper shadow support
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: bounds.x + styling.shadow_offset.x,
+ y: bounds.y + styling.shadow_offset.y,
+ ..bounds
+ },
+ border_radius: styling.border_radius,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ Background::Color([0.0, 0.0, 0.0, 0.5].into()),
+ );
+ }
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_radius: styling.border_radius,
+ border_width: styling.border_width,
+ border_color: styling.border_color,
+ },
+ styling
+ .background
+ .unwrap_or(Background::Color(Color::TRANSPARENT)),
+ );
+ }
+
+ self.content.draw(
+ renderer,
+ &renderer::Style {
+ text_color: styling.text_color,
+ },
+ content_layout,
cursor_position,
- self.on_press.is_none(),
- self.state.is_pressed,
- &self.style,
- &self.content,
- layout.children().next().unwrap(),
- )
+ &bounds,
+ );
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -277,38 +348,11 @@ where
}
}
-/// The renderer of a [`Button`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Button`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: crate::Renderer + Sized {
- /// The default padding of a [`Button`].
- const DEFAULT_PADDING: Padding;
-
- /// The style supported by this renderer.
- type Style: Default;
-
- /// Draws a [`Button`].
- fn draw<Message>(
- &mut self,
- defaults: &Self::Defaults,
- bounds: Rectangle,
- cursor_position: Point,
- is_disabled: bool,
- is_pressed: bool,
- style: &Self::Style,
- content: &Element<'_, Message, Self>,
- content_layout: Layout<'_>,
- ) -> Self::Output;
-}
-
impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a + Clone,
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + crate::Renderer,
{
fn from(
button: Button<'a, Message, Renderer>,
diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs
index 8bdb6b78..0d4a43ec 100644
--- a/native/src/widget/checkbox.rs
+++ b/native/src/widget/checkbox.rs
@@ -1,24 +1,27 @@
//! Show toggle controls using checkboxes.
use std::hash::Hash;
-use crate::alignment::{self, Alignment};
+use crate::alignment;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
-use crate::row;
+use crate::renderer;
use crate::text;
use crate::touch;
+use crate::widget::{self, Row, Text};
use crate::{
- Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Row,
- Text, Widget,
+ Alignment, Clipboard, Color, Element, Hasher, Layout, Length, Point,
+ Rectangle, Widget,
};
+pub use iced_style::checkbox::{Style, StyleSheet};
+
/// A box that can be checked.
///
/// # Example
///
/// ```
-/// # type Checkbox<Message> = iced_native::Checkbox<Message, iced_native::renderer::Null>;
+/// # type Checkbox<'a, Message> = iced_native::widget::Checkbox<'a, Message, iced_native::renderer::Null>;
/// #
/// pub enum Message {
/// CheckboxToggled(bool),
@@ -31,7 +34,7 @@ use crate::{
///
/// ![Checkbox drawn by `iced_wgpu`](https://github.com/hecrj/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/checkbox.png?raw=true)
#[allow(missing_debug_implementations)]
-pub struct Checkbox<Message, Renderer: self::Renderer + text::Renderer> {
+pub struct Checkbox<'a, Message, Renderer: text::Renderer> {
is_checked: bool,
on_toggle: Box<dyn Fn(bool) -> Message>,
label: String,
@@ -41,12 +44,16 @@ pub struct Checkbox<Message, Renderer: self::Renderer + text::Renderer> {
text_size: Option<u16>,
font: Renderer::Font,
text_color: Option<Color>,
- style: Renderer::Style,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
-impl<Message, Renderer: self::Renderer + text::Renderer>
- Checkbox<Message, Renderer>
-{
+impl<'a, Message, Renderer: text::Renderer> Checkbox<'a, Message, Renderer> {
+ /// The default size of a [`Checkbox`].
+ const DEFAULT_SIZE: u16 = 20;
+
+ /// The default spacing of a [`Checkbox`].
+ const DEFAULT_SPACING: u16 = 15;
+
/// Creates a new [`Checkbox`].
///
/// It expects:
@@ -64,12 +71,12 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
on_toggle: Box::new(f),
label: label.into(),
width: Length::Shrink,
- size: <Renderer as self::Renderer>::DEFAULT_SIZE,
- spacing: Renderer::DEFAULT_SPACING,
+ size: Self::DEFAULT_SIZE,
+ spacing: Self::DEFAULT_SPACING,
text_size: None,
font: Renderer::Font::default(),
text_color: None,
- style: Renderer::Style::default(),
+ style_sheet: Default::default(),
}
}
@@ -112,16 +119,19 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
}
/// Sets the style of the [`Checkbox`].
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
}
-impl<Message, Renderer> Widget<Message, Renderer>
- for Checkbox<Message, Renderer>
+impl<'a, Message, Renderer> Widget<Message, Renderer>
+ for Checkbox<'a, Message, Renderer>
where
- Renderer: self::Renderer + text::Renderer + row::Renderer,
+ Renderer: text::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -180,43 +190,84 @@ where
event::Status::Ignored
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ if layout.bounds().contains(cursor_position) {
+ mouse::Interaction::Pointer
+ } else {
+ mouse::Interaction::default()
+ }
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
+ ) {
let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+
let mut children = layout.children();
- let checkbox_layout = children.next().unwrap();
- let label_layout = children.next().unwrap();
- let checkbox_bounds = checkbox_layout.bounds();
-
- let label = text::Renderer::draw(
- renderer,
- defaults,
- label_layout.bounds(),
- &self.label,
- self.text_size.unwrap_or(renderer.default_size()),
- self.font,
- self.text_color,
- alignment::Horizontal::Left,
- alignment::Vertical::Center,
- );
+ {
+ let layout = children.next().unwrap();
+ let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let style = if is_mouse_over {
+ self.style_sheet.hovered(self.is_checked)
+ } else {
+ self.style_sheet.active(self.is_checked)
+ };
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_radius: style.border_radius,
+ border_width: style.border_width,
+ border_color: style.border_color,
+ },
+ style.background,
+ );
- self::Renderer::draw(
- renderer,
- checkbox_bounds,
- self.is_checked,
- is_mouse_over,
- label,
- &self.style,
- )
+ if self.is_checked {
+ renderer.fill_text(text::Text {
+ content: &Renderer::CHECKMARK_ICON.to_string(),
+ font: Renderer::ICON_FONT,
+ size: bounds.height * 0.7,
+ bounds: Rectangle {
+ x: bounds.center_x(),
+ y: bounds.center_y(),
+ ..bounds
+ },
+ color: style.checkmark_color,
+ horizontal_alignment: alignment::Horizontal::Center,
+ vertical_alignment: alignment::Vertical::Center,
+ });
+ }
+ }
+
+ {
+ let label_layout = children.next().unwrap();
+
+ widget::text::draw(
+ renderer,
+ style,
+ label_layout,
+ &self.label,
+ self.font,
+ self.text_size,
+ self.text_color,
+ alignment::Horizontal::Left,
+ alignment::Vertical::Center,
+ );
+ }
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -227,47 +278,14 @@ where
}
}
-/// The renderer of a [`Checkbox`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Checkbox`] in your user interface.
-///
-/// [renderer]: crate::Renderer
-pub trait Renderer: crate::Renderer {
- /// The style supported by this renderer.
- type Style: Default;
-
- /// The default size of a [`Checkbox`].
- const DEFAULT_SIZE: u16;
-
- /// The default spacing of a [`Checkbox`].
- const DEFAULT_SPACING: u16;
-
- /// Draws a [`Checkbox`].
- ///
- /// It receives:
- /// * the bounds of the [`Checkbox`]
- /// * whether the [`Checkbox`] is selected or not
- /// * whether the mouse is over the [`Checkbox`] or not
- /// * the drawn label of the [`Checkbox`]
- fn draw(
- &mut self,
- bounds: Rectangle,
- is_checked: bool,
- is_mouse_over: bool,
- label: Self::Output,
- style: &Self::Style,
- ) -> Self::Output;
-}
-
-impl<'a, Message, Renderer> From<Checkbox<Message, Renderer>>
+impl<'a, Message, Renderer> From<Checkbox<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer + text::Renderer + row::Renderer,
+ Renderer: 'a + text::Renderer,
Message: 'a,
{
fn from(
- checkbox: Checkbox<Message, Renderer>,
+ checkbox: Checkbox<'a, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(checkbox)
}
diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs
index 30cf0781..0d4d6fa7 100644
--- a/native/src/widget/column.rs
+++ b/native/src/widget/column.rs
@@ -3,7 +3,9 @@ use std::hash::Hash;
use crate::event::{self, Event};
use crate::layout;
+use crate::mouse;
use crate::overlay;
+use crate::renderer;
use crate::{
Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
Rectangle, Widget,
@@ -105,7 +107,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Column<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -162,21 +164,37 @@ where
.fold(event::Status::Ignored, event::Status::merge)
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ self.children
+ .iter()
+ .zip(layout.children())
+ .map(|(child, layout)| {
+ child.widget.mouse_interaction(
+ layout,
+ cursor_position,
+ viewport,
+ )
+ })
+ .max()
+ .unwrap_or_default()
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
- ) -> Renderer::Output {
- renderer.draw(
- defaults,
- &self.children,
- layout,
- cursor_position,
- viewport,
- )
+ ) {
+ for (child, layout) in self.children.iter().zip(layout.children()) {
+ child.draw(renderer, style, layout, cursor_position, viewport);
+ }
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -208,33 +226,10 @@ where
}
}
-/// The renderer of a [`Column`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Column`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: crate::Renderer + Sized {
- /// Draws a [`Column`].
- ///
- /// It receives:
- /// - the children of the [`Column`]
- /// - the [`Layout`] of the [`Column`] and its children
- /// - the cursor position
- fn draw<Message>(
- &mut self,
- defaults: &Self::Defaults,
- content: &[Element<'_, Message, Self>],
- layout: Layout<'_>,
- cursor_position: Point,
- viewport: &Rectangle,
- ) -> Self::Output;
-}
-
impl<'a, Message, Renderer> From<Column<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + crate::Renderer,
Message: 'a,
{
fn from(
diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs
index 0e86ab62..596af7fd 100644
--- a/native/src/widget/container.rs
+++ b/native/src/widget/container.rs
@@ -4,19 +4,23 @@ use std::hash::Hash;
use crate::alignment::{self, Alignment};
use crate::event::{self, Event};
use crate::layout;
+use crate::mouse;
use crate::overlay;
+use crate::renderer;
use crate::{
- Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
- Widget,
+ Background, Clipboard, Color, Element, Hasher, Layout, Length, Padding,
+ Point, Rectangle, Widget,
};
use std::u32;
+pub use iced_style::container::{Style, StyleSheet};
+
/// An element decorating some content.
///
/// It is normally used for alignment purposes.
#[allow(missing_debug_implementations)]
-pub struct Container<'a, Message, Renderer: self::Renderer> {
+pub struct Container<'a, Message, Renderer> {
padding: Padding,
width: Length,
height: Length,
@@ -24,13 +28,13 @@ pub struct Container<'a, Message, Renderer: self::Renderer> {
max_height: u32,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
- style: Renderer::Style,
+ style_sheet: Box<dyn StyleSheet + 'a>,
content: Element<'a, Message, Renderer>,
}
impl<'a, Message, Renderer> Container<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
/// Creates an empty [`Container`].
pub fn new<T>(content: T) -> Self
@@ -45,7 +49,7 @@ where
max_height: u32::MAX,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
- style: Renderer::Style::default(),
+ style_sheet: Default::default(),
content: content.into(),
}
}
@@ -105,8 +109,11 @@ where
}
/// Sets the style of the [`Container`].
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
}
@@ -114,7 +121,7 @@ where
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Container<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -172,23 +179,42 @@ where
)
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ self.content.widget.mouse_interaction(
+ layout.children().next().unwrap(),
+ cursor_position,
+ viewport,
+ )
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ renderer_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
- ) -> Renderer::Output {
- renderer.draw(
- defaults,
- layout.bounds(),
+ ) {
+ let style = self.style_sheet.style();
+
+ draw_background(renderer, &style, layout.bounds());
+
+ self.content.draw(
+ renderer,
+ &renderer::Style {
+ text_color: style
+ .text_color
+ .unwrap_or(renderer_style.text_color),
+ },
+ layout.children().next().unwrap(),
cursor_position,
viewport,
- &self.style,
- &self.content,
- layout.children().next().unwrap(),
- )
+ );
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -212,33 +238,33 @@ where
}
}
-/// The renderer of a [`Container`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Container`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: crate::Renderer {
- /// The style supported by this renderer.
- type Style: Default;
-
- /// Draws a [`Container`].
- fn draw<Message>(
- &mut self,
- defaults: &Self::Defaults,
- bounds: Rectangle,
- cursor_position: Point,
- viewport: &Rectangle,
- style: &Self::Style,
- content: &Element<'_, Message, Self>,
- content_layout: Layout<'_>,
- ) -> Self::Output;
+/// Draws the background of a [`Container`] given its [`Style`] and its `bounds`.
+pub fn draw_background<Renderer>(
+ renderer: &mut Renderer,
+ style: &Style,
+ bounds: Rectangle,
+) where
+ Renderer: crate::Renderer,
+{
+ if style.background.is_some() || style.border_width > 0.0 {
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_radius: style.border_radius,
+ border_width: style.border_width,
+ border_color: style.border_color,
+ },
+ style
+ .background
+ .unwrap_or(Background::Color(Color::TRANSPARENT)),
+ );
+ }
}
impl<'a, Message, Renderer> From<Container<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + crate::Renderer,
Message: 'a,
{
fn from(
diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs
index 4d8e0a3f..66e95265 100644
--- a/native/src/widget/image.rs
+++ b/native/src/widget/image.rs
@@ -2,21 +2,19 @@
pub mod viewer;
pub use viewer::Viewer;
+use crate::image::{self, Handle};
use crate::layout;
+use crate::renderer;
use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
-use std::{
- hash::{Hash, Hasher as _},
- path::PathBuf,
- sync::Arc,
-};
+use std::hash::Hash;
/// A frame that displays an image while keeping aspect ratio.
///
/// # Example
///
/// ```
-/// # use iced_native::Image;
+/// # use iced_native::widget::Image;
/// #
/// let image = Image::new("resources/ferris.png");
/// ```
@@ -54,7 +52,7 @@ impl Image {
impl<Message, Renderer> Widget<Message, Renderer> for Image
where
- Renderer: self::Renderer,
+ Renderer: image::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -92,12 +90,12 @@ where
fn draw(
&self,
renderer: &mut Renderer,
- _defaults: &Renderer::Defaults,
+ _style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
- renderer.draw(self.handle.clone(), layout)
+ ) {
+ renderer.draw(self.handle.clone(), layout.bounds());
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -110,129 +108,9 @@ where
}
}
-/// An [`Image`] handle.
-#[derive(Debug, Clone)]
-pub struct Handle {
- id: u64,
- data: Arc<Data>,
-}
-
-impl Handle {
- /// Creates an image [`Handle`] pointing to the image of the given path.
- ///
- /// Makes an educated guess about the image format by examining the data in the file.
- pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle {
- Self::from_data(Data::Path(path.into()))
- }
-
- /// Creates an image [`Handle`] containing the image pixels directly. This
- /// function expects the input data to be provided as a `Vec<u8>` of BGRA
- /// pixels.
- ///
- /// This is useful if you have already decoded your image.
- pub fn from_pixels(width: u32, height: u32, pixels: Vec<u8>) -> Handle {
- Self::from_data(Data::Pixels {
- width,
- height,
- pixels,
- })
- }
-
- /// Creates an image [`Handle`] containing the image data directly.
- ///
- /// Makes an educated guess about the image format by examining the given data.
- ///
- /// This is useful if you already have your image loaded in-memory, maybe
- /// because you downloaded or generated it procedurally.
- pub fn from_memory(bytes: Vec<u8>) -> Handle {
- Self::from_data(Data::Bytes(bytes))
- }
-
- fn from_data(data: Data) -> Handle {
- let mut hasher = Hasher::default();
- data.hash(&mut hasher);
-
- Handle {
- id: hasher.finish(),
- data: Arc::new(data),
- }
- }
-
- /// Returns the unique identifier of the [`Handle`].
- pub fn id(&self) -> u64 {
- self.id
- }
-
- /// Returns a reference to the image [`Data`].
- pub fn data(&self) -> &Data {
- &self.data
- }
-}
-
-impl<T> From<T> for Handle
-where
- T: Into<PathBuf>,
-{
- fn from(path: T) -> Handle {
- Handle::from_path(path.into())
- }
-}
-
-impl Hash for Handle {
- fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
- self.id.hash(state);
- }
-}
-
-/// The data of an [`Image`].
-#[derive(Clone, Hash)]
-pub enum Data {
- /// File data
- Path(PathBuf),
-
- /// In-memory data
- Bytes(Vec<u8>),
-
- /// Decoded image pixels in BGRA format.
- Pixels {
- /// The width of the image.
- width: u32,
- /// The height of the image.
- height: u32,
- /// The pixels.
- pixels: Vec<u8>,
- },
-}
-
-impl std::fmt::Debug for Data {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- Data::Path(path) => write!(f, "Path({:?})", path),
- Data::Bytes(_) => write!(f, "Bytes(...)"),
- Data::Pixels { width, height, .. } => {
- write!(f, "Pixels({} * {})", width, height)
- }
- }
- }
-}
-
-/// The renderer of an [`Image`].
-///
-/// Your [renderer] will need to implement this trait before being able to use
-/// an [`Image`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: crate::Renderer {
- /// Returns the dimensions of an [`Image`] located on the given path.
- fn dimensions(&self, handle: &Handle) -> (u32, u32);
-
- /// Draws an [`Image`].
- fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output;
-}
-
impl<'a, Message, Renderer> From<Image> for Element<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: image::Renderer,
{
fn from(image: Image) -> Element<'a, Message, Renderer> {
Element::new(image)
diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs
index 405daf00..95e5c6e4 100644
--- a/native/src/widget/image/viewer.rs
+++ b/native/src/widget/image/viewer.rs
@@ -3,6 +3,7 @@ use crate::event::{self, Event};
use crate::image;
use crate::layout;
use crate::mouse;
+use crate::renderer;
use crate::{
Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
Widget,
@@ -88,7 +89,7 @@ impl<'a> Viewer<'a> {
/// will be respected.
fn image_size<Renderer>(&self, renderer: &Renderer, bounds: Size) -> Size
where
- Renderer: self::Renderer + image::Renderer,
+ Renderer: image::Renderer,
{
let (width, height) = renderer.dimensions(&self.handle);
@@ -115,7 +116,7 @@ impl<'a> Viewer<'a> {
impl<'a, Message, Renderer> Widget<Message, Renderer> for Viewer<'a>
where
- Renderer: self::Renderer + image::Renderer,
+ Renderer: image::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -280,14 +281,32 @@ where
}
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ if self.state.is_cursor_grabbed() {
+ mouse::Interaction::Grabbing
+ } else if is_mouse_over {
+ mouse::Interaction::Grab
+ } else {
+ mouse::Interaction::Idle
+ }
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- _defaults: &Renderer::Defaults,
+ _style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ _cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
+ ) {
let bounds = layout.bounds();
let image_size = self.image_size(renderer, bounds.size());
@@ -301,17 +320,19 @@ where
image_top_left - self.state.offset(bounds, image_size)
};
- let is_mouse_over = bounds.contains(cursor_position);
-
- self::Renderer::draw(
- renderer,
- &self.state,
- bounds,
- image_size,
- translation,
- self.handle.clone(),
- is_mouse_over,
- )
+ renderer.with_layer(bounds, |renderer| {
+ renderer.with_translation(translation, |renderer| {
+ image::Renderer::draw(
+ renderer,
+ self.handle.clone(),
+ Rectangle {
+ x: bounds.x,
+ y: bounds.y,
+ ..Rectangle::with_size(image_size)
+ },
+ )
+ });
+ });
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -373,38 +394,9 @@ impl State {
}
}
-/// The renderer of an [`Viewer`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Viewer`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: crate::Renderer + Sized {
- /// Draws the [`Viewer`].
- ///
- /// It receives:
- /// - the [`State`] of the [`Viewer`]
- /// - the bounds of the [`Viewer`] widget
- /// - the [`Size`] of the scaled [`Viewer`] image
- /// - the translation of the clipped image
- /// - the [`Handle`] to the underlying image
- /// - whether the mouse is over the [`Viewer`] or not
- ///
- /// [`Handle`]: image::Handle
- fn draw(
- &mut self,
- state: &State,
- bounds: Rectangle,
- image_size: Size,
- translation: Vector,
- handle: image::Handle,
- is_mouse_over: bool,
- ) -> Self::Output;
-}
-
impl<'a, Message, Renderer> From<Viewer<'a>> for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer + image::Renderer,
+ Renderer: 'a + image::Renderer,
Message: 'a,
{
fn from(viewer: Viewer<'a>) -> Element<'a, Message, Renderer> {
diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs
index 26a72409..20616ed4 100644
--- a/native/src/widget/pane_grid.rs
+++ b/native/src/widget/pane_grid.rs
@@ -27,18 +27,19 @@ pub use split::Split;
pub use state::State;
pub use title_bar::TitleBar;
-use crate::container;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
-use crate::row;
+use crate::renderer;
use crate::touch;
use crate::{
- Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
- Widget,
+ Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Size,
+ Vector, Widget,
};
+pub use iced_style::pane_grid::{Line, StyleSheet};
+
/// A collection of panes distributed using either vertical or horizontal splits
/// to completely fill the space available.
///
@@ -61,10 +62,10 @@ use crate::{
/// ## Example
///
/// ```
-/// # use iced_native::{pane_grid, Text};
+/// # use iced_native::widget::{pane_grid, Text};
/// #
/// # type PaneGrid<'a, Message> =
-/// # iced_native::PaneGrid<'a, Message, iced_native::renderer::Null>;
+/// # iced_native::widget::PaneGrid<'a, Message, iced_native::renderer::Null>;
/// #
/// enum PaneState {
/// SomePane,
@@ -89,7 +90,7 @@ use crate::{
/// .on_resize(10, Message::PaneResized);
/// ```
#[allow(missing_debug_implementations)]
-pub struct PaneGrid<'a, Message, Renderer: self::Renderer> {
+pub struct PaneGrid<'a, Message, Renderer> {
state: &'a mut state::Internal,
elements: Vec<(Pane, Content<'a, Message, Renderer>)>,
width: Length,
@@ -98,12 +99,12 @@ pub struct PaneGrid<'a, Message, Renderer: self::Renderer> {
on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>,
on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
on_resize: Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
- style: <Renderer as self::Renderer>::Style,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
/// Creates a [`PaneGrid`] with the given [`State`] and view function.
///
@@ -130,7 +131,7 @@ where
on_click: None,
on_drag: None,
on_resize: None,
- style: Default::default(),
+ style_sheet: Default::default(),
}
}
@@ -190,18 +191,15 @@ where
}
/// Sets the style of the [`PaneGrid`].
- pub fn style(
- mut self,
- style: impl Into<<Renderer as self::Renderer>::Style>,
- ) -> Self {
- self.style = style.into();
+ pub fn style(mut self, style: impl Into<Box<dyn StyleSheet + 'a>>) -> Self {
+ self.style_sheet = style.into();
self
}
}
impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
fn click_pane(
&mut self,
@@ -318,7 +316,7 @@ pub struct ResizeEvent {
impl<'a, Message, Renderer> Widget<Message, Renderer>
for PaneGrid<'a, Message, Renderer>
where
- Renderer: self::Renderer + container::Renderer,
+ Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -473,14 +471,43 @@ where
.fold(event_status, event::Status::merge)
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ if self.state.picked_pane().is_some() {
+ return mouse::Interaction::Grab;
+ }
+
+ if let Some((_, axis)) = self.state.picked_split() {
+ return match axis {
+ Axis::Horizontal => mouse::Interaction::ResizingHorizontally,
+ Axis::Vertical => mouse::Interaction::ResizingVertically,
+ };
+ }
+
+ self.elements
+ .iter()
+ .zip(layout.children())
+ .map(|((_pane, content), layout)| {
+ content.mouse_interaction(layout, cursor_position, viewport)
+ })
+ .max()
+ .unwrap_or_default()
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
- ) -> Renderer::Output {
+ ) {
+ let picked_pane = self.state.picked_pane();
+
let picked_split = self
.state
.picked_split()
@@ -529,17 +556,89 @@ where
None => None,
});
- self::Renderer::draw(
- renderer,
- defaults,
- &self.elements,
- self.state.picked_pane(),
- picked_split,
- layout,
- &self.style,
- cursor_position,
- viewport,
- )
+ 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)
+ } else {
+ cursor_position
+ };
+
+ for ((id, pane), layout) in self.elements.iter().zip(layout.children())
+ {
+ match picked_pane {
+ Some((dragging, origin)) if *id == dragging => {
+ 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| {
+ pane.draw(
+ renderer,
+ style,
+ layout,
+ pane_cursor_position,
+ viewport,
+ );
+ });
+ },
+ );
+ }
+ _ => {
+ pane.draw(
+ renderer,
+ style,
+ layout,
+ pane_cursor_position,
+ viewport,
+ );
+ }
+ }
+ }
+
+ if let Some((axis, split_region, is_picked)) = picked_split {
+ let highlight = if is_picked {
+ self.style_sheet.picked_split()
+ } else {
+ self.style_sheet.hovered_split()
+ };
+
+ 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,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ highlight.color,
+ );
+ }
+ }
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -569,78 +668,10 @@ where
}
}
-/// The renderer of a [`PaneGrid`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`PaneGrid`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: crate::Renderer + container::Renderer + Sized {
- /// The style supported by this renderer.
- type Style: Default;
-
- /// Draws a [`PaneGrid`].
- ///
- /// It receives:
- /// - the elements of the [`PaneGrid`]
- /// - the [`Pane`] that is currently being dragged
- /// - the [`Axis`] that is currently being resized
- /// - the [`Layout`] of the [`PaneGrid`] and its elements
- /// - the cursor position
- fn draw<Message>(
- &mut self,
- defaults: &Self::Defaults,
- content: &[(Pane, Content<'_, Message, Self>)],
- dragging: Option<(Pane, Point)>,
- resizing: Option<(Axis, Rectangle, bool)>,
- layout: Layout<'_>,
- style: &<Self as self::Renderer>::Style,
- cursor_position: Point,
- viewport: &Rectangle,
- ) -> Self::Output;
-
- /// Draws a [`Pane`].
- ///
- /// It receives:
- /// - the [`TitleBar`] of the [`Pane`], if any
- /// - the [`Content`] of the [`Pane`]
- /// - the [`Layout`] of the [`Pane`] and its elements
- /// - the cursor position
- fn draw_pane<Message>(
- &mut self,
- defaults: &Self::Defaults,
- bounds: Rectangle,
- style: &<Self as container::Renderer>::Style,
- title_bar: Option<(&TitleBar<'_, Message, Self>, Layout<'_>)>,
- body: (&Element<'_, Message, Self>, Layout<'_>),
- cursor_position: Point,
- viewport: &Rectangle,
- ) -> Self::Output;
-
- /// Draws a [`TitleBar`].
- ///
- /// It receives:
- /// - the bounds, style of the [`TitleBar`]
- /// - the style of the [`TitleBar`]
- /// - the content of the [`TitleBar`] with its layout
- /// - the controls of the [`TitleBar`] with their [`Layout`], if any
- /// - the cursor position
- fn draw_title_bar<Message>(
- &mut self,
- defaults: &Self::Defaults,
- bounds: Rectangle,
- style: &<Self as container::Renderer>::Style,
- content: (&Element<'_, Message, Self>, Layout<'_>),
- controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>,
- cursor_position: Point,
- viewport: &Rectangle,
- ) -> Self::Output;
-}
-
impl<'a, Message, Renderer> From<PaneGrid<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer + row::Renderer,
+ Renderer: 'a + crate::Renderer,
Message: 'a,
{
fn from(
diff --git a/native/src/widget/pane_grid/configuration.rs b/native/src/widget/pane_grid/configuration.rs
index 4c43826e..4c52bad4 100644
--- a/native/src/widget/pane_grid/configuration.rs
+++ b/native/src/widget/pane_grid/configuration.rs
@@ -1,4 +1,4 @@
-use crate::pane_grid::Axis;
+use crate::widget::pane_grid::Axis;
/// The arrangement of a [`PaneGrid`].
///
diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs
index bac9fdd4..c44506dd 100644
--- a/native/src/widget/pane_grid/content.rs
+++ b/native/src/widget/pane_grid/content.rs
@@ -1,30 +1,32 @@
-use crate::container;
use crate::event::{self, Event};
use crate::layout;
+use crate::mouse;
use crate::overlay;
-use crate::pane_grid::{self, TitleBar};
+use crate::renderer;
+use crate::widget::container;
+use crate::widget::pane_grid::TitleBar;
use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size};
/// The content of a [`Pane`].
///
/// [`Pane`]: crate::widget::pane_grid::Pane
#[allow(missing_debug_implementations)]
-pub struct Content<'a, Message, Renderer: pane_grid::Renderer> {
+pub struct Content<'a, Message, Renderer> {
title_bar: Option<TitleBar<'a, Message, Renderer>>,
body: Element<'a, Message, Renderer>,
- style: <Renderer as container::Renderer>::Style,
+ style_sheet: Box<dyn container::StyleSheet + 'a>,
}
impl<'a, Message, Renderer> Content<'a, Message, Renderer>
where
- Renderer: pane_grid::Renderer,
+ Renderer: crate::Renderer,
{
/// Creates a new [`Content`] with the provided body.
pub fn new(body: impl Into<Element<'a, Message, Renderer>>) -> Self {
Self {
title_bar: None,
body: body.into(),
- style: Default::default(),
+ style_sheet: Default::default(),
}
}
@@ -40,16 +42,16 @@ where
/// Sets the style of the [`Content`].
pub fn style(
mut self,
- style: impl Into<<Renderer as container::Renderer>::Style>,
+ style_sheet: impl Into<Box<dyn container::StyleSheet + 'a>>,
) -> Self {
- self.style = style.into();
+ self.style_sheet = style_sheet.into();
self
}
}
impl<'a, Message, Renderer> Content<'a, Message, Renderer>
where
- Renderer: pane_grid::Renderer,
+ Renderer: crate::Renderer,
{
/// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`].
///
@@ -57,35 +59,45 @@ where
pub fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
- ) -> Renderer::Output {
+ ) {
+ let bounds = layout.bounds();
+
+ {
+ let style = self.style_sheet.style();
+
+ container::draw_background(renderer, &style, bounds);
+ }
+
if let Some(title_bar) = &self.title_bar {
let mut children = layout.children();
let title_bar_layout = children.next().unwrap();
let body_layout = children.next().unwrap();
- renderer.draw_pane(
- defaults,
- layout.bounds(),
- &self.style,
- Some((title_bar, title_bar_layout)),
- (&self.body, body_layout),
+ let show_controls = bounds.contains(cursor_position);
+
+ title_bar.draw(
+ renderer,
+ style,
+ title_bar_layout,
cursor_position,
viewport,
- )
- } else {
- renderer.draw_pane(
- defaults,
- layout.bounds(),
- &self.style,
- None,
- (&self.body, layout),
+ show_controls,
+ );
+
+ self.body.draw(
+ renderer,
+ style,
+ body_layout,
cursor_position,
viewport,
- )
+ );
+ } else {
+ self.body
+ .draw(renderer, style, layout, cursor_position, viewport);
}
}
@@ -186,6 +198,40 @@ where
event_status.merge(body_status)
}
+ pub(crate) fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> 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 {
+ return mouse::Interaction::Grab;
+ }
+
+ let mouse_interaction = title_bar.mouse_interaction(
+ title_bar_layout,
+ cursor_position,
+ viewport,
+ );
+
+ (children.next().unwrap(), mouse_interaction)
+ } else {
+ (layout, mouse::Interaction::default())
+ };
+
+ self.body
+ .mouse_interaction(body_layout, cursor_position, viewport)
+ .max(title_bar_interaction)
+ }
+
pub(crate) fn hash_layout(&self, state: &mut Hasher) {
if let Some(title_bar) = &self.title_bar {
title_bar.hash_layout(state);
@@ -215,7 +261,7 @@ where
impl<'a, T, Message, Renderer> From<T> for Content<'a, Message, Renderer>
where
T: Into<Element<'a, Message, Renderer>>,
- Renderer: pane_grid::Renderer + container::Renderer,
+ Renderer: crate::Renderer,
{
fn from(element: T) -> Self {
Self::new(element)
diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs
index 84714e00..af6573a0 100644
--- a/native/src/widget/pane_grid/node.rs
+++ b/native/src/widget/pane_grid/node.rs
@@ -1,7 +1,5 @@
-use crate::{
- pane_grid::{Axis, Pane, Split},
- Rectangle, Size,
-};
+use crate::widget::pane_grid::{Axis, Pane, Split};
+use crate::{Rectangle, Size};
use std::collections::BTreeMap;
diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs
index fb96f89f..bcc724a8 100644
--- a/native/src/widget/pane_grid/state.rs
+++ b/native/src/widget/pane_grid/state.rs
@@ -1,7 +1,7 @@
-use crate::{
- pane_grid::{Axis, Configuration, Direction, Node, Pane, Split},
- Hasher, Point, Rectangle, Size,
+use crate::widget::pane_grid::{
+ Axis, Configuration, Direction, Node, Pane, Split,
};
+use crate::{Hasher, Point, Rectangle, Size};
use std::collections::{BTreeMap, HashMap};
diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs
index 070010f8..070cf404 100644
--- a/native/src/widget/pane_grid/title_bar.rs
+++ b/native/src/widget/pane_grid/title_bar.rs
@@ -1,8 +1,9 @@
-use crate::container;
use crate::event::{self, Event};
use crate::layout;
+use crate::mouse;
use crate::overlay;
-use crate::pane_grid;
+use crate::renderer;
+use crate::widget::container;
use crate::{
Clipboard, Element, Hasher, Layout, Padding, Point, Rectangle, Size,
};
@@ -11,17 +12,17 @@ use crate::{
///
/// [`Pane`]: crate::widget::pane_grid::Pane
#[allow(missing_debug_implementations)]
-pub struct TitleBar<'a, Message, Renderer: pane_grid::Renderer> {
+pub struct TitleBar<'a, Message, Renderer> {
content: Element<'a, Message, Renderer>,
controls: Option<Element<'a, Message, Renderer>>,
padding: Padding,
always_show_controls: bool,
- style: <Renderer as container::Renderer>::Style,
+ style_sheet: Box<dyn container::StyleSheet + 'a>,
}
impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
where
- Renderer: pane_grid::Renderer,
+ Renderer: crate::Renderer,
{
/// Creates a new [`TitleBar`] with the given content.
pub fn new<E>(content: E) -> Self
@@ -33,7 +34,7 @@ where
controls: None,
padding: Padding::ZERO,
always_show_controls: false,
- style: Default::default(),
+ style_sheet: Default::default(),
}
}
@@ -55,9 +56,9 @@ where
/// Sets the style of the [`TitleBar`].
pub fn style(
mut self,
- style: impl Into<<Renderer as container::Renderer>::Style>,
+ style: impl Into<Box<dyn container::StyleSheet + 'a>>,
) -> Self {
- self.style = style.into();
+ self.style_sheet = style.into();
self
}
@@ -77,7 +78,7 @@ where
impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
where
- Renderer: pane_grid::Renderer,
+ Renderer: crate::Renderer,
{
/// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`].
///
@@ -85,39 +86,47 @@ where
pub fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ inherited_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
show_controls: bool,
- ) -> Renderer::Output {
+ ) {
+ let bounds = layout.bounds();
+ let style = self.style_sheet.style();
+ let inherited_style = renderer::Style {
+ text_color: style.text_color.unwrap_or(inherited_style.text_color),
+ };
+
+ container::draw_background(renderer, &style, bounds);
+
let mut children = layout.children();
let padded = children.next().unwrap();
let mut children = padded.children();
let title_layout = children.next().unwrap();
- let controls = if let Some(controls) = &self.controls {
+ self.content.draw(
+ renderer,
+ &inherited_style,
+ title_layout,
+ cursor_position,
+ viewport,
+ );
+
+ if let Some(controls) = &self.controls {
let controls_layout = children.next().unwrap();
if show_controls || self.always_show_controls {
- Some((controls, controls_layout))
- } else {
- None
+ controls.draw(
+ renderer,
+ &inherited_style,
+ controls_layout,
+ cursor_position,
+ viewport,
+ );
}
- } else {
- None
- };
-
- renderer.draw_title_bar(
- defaults,
- layout.bounds(),
- &self.style,
- (&self.content, title_layout),
- controls,
- cursor_position,
- viewport,
- )
+ }
}
/// Returns whether the mouse cursor is over the pick area of the
@@ -244,6 +253,35 @@ where
control_status.merge(title_status)
}
+ pub(crate) fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ let mut children = layout.children();
+ let padded = children.next().unwrap();
+
+ let mut children = padded.children();
+ let title_layout = children.next().unwrap();
+
+ let title_interaction = self.content.mouse_interaction(
+ title_layout,
+ cursor_position,
+ viewport,
+ );
+
+ if let Some(controls) = &self.controls {
+ let controls_layout = children.next().unwrap();
+
+ controls
+ .mouse_interaction(controls_layout, cursor_position, viewport)
+ .max(title_interaction)
+ } else {
+ title_interaction
+ }
+ }
+
pub(crate) fn overlay(
&mut self,
layout: Layout<'_>,
diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs
index d7792000..9d1a86ec 100644
--- a/native/src/widget/pick_list.rs
+++ b/native/src/widget/pick_list.rs
@@ -1,12 +1,13 @@
//! 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::scrollable;
-use crate::text;
+use crate::renderer;
+use crate::text::{self, Text};
use crate::touch;
use crate::{
Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
@@ -14,9 +15,11 @@ use crate::{
};
use std::borrow::Cow;
+pub use iced_style::pick_list::{Style, StyleSheet};
+
/// A widget for selecting a single value from a list of options.
#[allow(missing_debug_implementations)]
-pub struct PickList<'a, T, Message, Renderer: self::Renderer>
+pub struct PickList<'a, T, Message, Renderer: text::Renderer>
where
[T]: ToOwned<Owned = Vec<T>>,
{
@@ -33,7 +36,7 @@ where
padding: Padding,
text_size: Option<u16>,
font: Renderer::Font,
- style: <Renderer as self::Renderer>::Style,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
/// The local state of a [`PickList`].
@@ -58,12 +61,15 @@ impl<T> Default for State<T> {
}
}
-impl<'a, T: 'a, Message, Renderer: self::Renderer>
+impl<'a, T: 'a, Message, Renderer: text::Renderer>
PickList<'a, T, Message, Renderer>
where
T: ToString + Eq,
[T]: ToOwned<Owned = Vec<T>>,
{
+ /// The default padding of a [`PickList`].
+ pub const DEFAULT_PADDING: Padding = Padding::new(5);
+
/// Creates a new [`PickList`] with the given [`State`], a list of options,
/// the current selected value, and the message to produce when an option is
/// selected.
@@ -93,9 +99,9 @@ where
selected,
width: Length::Shrink,
text_size: None,
- padding: Renderer::DEFAULT_PADDING,
+ padding: Self::DEFAULT_PADDING,
font: Default::default(),
- style: Default::default(),
+ style_sheet: Default::default(),
}
}
@@ -132,9 +138,9 @@ where
/// Sets the style of the [`PickList`].
pub fn style(
mut self,
- style: impl Into<<Renderer as self::Renderer>::Style>,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
) -> Self {
- self.style = style.into();
+ self.style_sheet = style_sheet.into();
self
}
}
@@ -145,7 +151,7 @@ where
T: Clone + ToString + Eq,
[T]: ToOwned<Owned = Vec<T>>,
Message: 'static,
- Renderer: self::Renderer + scrollable::Renderer + 'a,
+ Renderer: text::Renderer + 'a,
{
fn width(&self) -> Length {
self.width
@@ -320,25 +326,90 @@ where
}
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ if is_mouse_over {
+ mouse::Interaction::Pointer
+ } else {
+ mouse::Interaction::default()
+ }
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- _defaults: &Renderer::Defaults,
+ _style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
- self::Renderer::draw(
- renderer,
- layout.bounds(),
- cursor_position,
- self.selected.as_ref().map(ToString::to_string),
- self.placeholder.as_ref().map(String::as_str),
- self.padding,
- self.text_size.unwrap_or(renderer.default_size()),
- self.font,
- &self.style,
- )
+ ) {
+ let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+ let is_selected = self.selected.is_some();
+
+ let style = if is_mouse_over {
+ self.style_sheet.hovered()
+ } else {
+ self.style_sheet.active()
+ };
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_color: style.border_color,
+ border_width: style.border_width,
+ border_radius: style.border_radius,
+ },
+ style.background,
+ );
+
+ renderer.fill_text(Text {
+ content: &Renderer::ARROW_DOWN_ICON.to_string(),
+ font: Renderer::ICON_FONT,
+ size: bounds.height * style.icon_size,
+ bounds: Rectangle {
+ x: bounds.x + bounds.width
+ - f32::from(self.padding.horizontal()),
+ y: bounds.center_y(),
+ ..bounds
+ },
+ color: style.text_color,
+ horizontal_alignment: alignment::Horizontal::Right,
+ vertical_alignment: alignment::Vertical::Center,
+ });
+
+ if let Some(label) = self
+ .selected
+ .as_ref()
+ .map(ToString::to_string)
+ .as_ref()
+ .or_else(|| self.placeholder.as_ref())
+ {
+ renderer.fill_text(Text {
+ content: label,
+ size: f32::from(
+ self.text_size.unwrap_or(renderer.default_size()),
+ ),
+ font: self.font,
+ color: is_selected
+ .then(|| style.text_color)
+ .unwrap_or(style.placeholder_color),
+ bounds: Rectangle {
+ x: bounds.x + f32::from(self.padding.left),
+ y: bounds.center_y(),
+ ..bounds
+ },
+ horizontal_alignment: alignment::Horizontal::Left,
+ vertical_alignment: alignment::Vertical::Center,
+ })
+ }
}
fn overlay(
@@ -357,7 +428,7 @@ where
.width(bounds.width.round() as u16)
.padding(self.padding)
.font(self.font)
- .style(Renderer::menu_style(&self.style));
+ .style(self.style_sheet.menu());
if let Some(text_size) = self.text_size {
menu = menu.text_size(text_size);
@@ -370,44 +441,12 @@ where
}
}
-/// The renderer of a [`PickList`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`PickList`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: text::Renderer + menu::Renderer {
- /// The default padding of a [`PickList`].
- const DEFAULT_PADDING: Padding;
-
- /// The [`PickList`] style supported by this renderer.
- type Style: Default;
-
- /// Returns the style of the [`Menu`] of the [`PickList`].
- fn menu_style(
- style: &<Self as Renderer>::Style,
- ) -> <Self as menu::Renderer>::Style;
-
- /// Draws a [`PickList`].
- fn draw(
- &mut self,
- bounds: Rectangle,
- cursor_position: Point,
- selected: Option<String>,
- placeholder: Option<&str>,
- padding: Padding,
- text_size: u16,
- font: Self::Font,
- style: &<Self as Renderer>::Style,
- ) -> Self::Output;
-}
-
impl<'a, T: 'a, Message, Renderer> Into<Element<'a, Message, Renderer>>
for PickList<'a, T, Message, Renderer>
where
T: Clone + ToString + Eq,
[T]: ToOwned<Owned = Vec<T>>,
- Renderer: self::Renderer + 'a,
+ Renderer: text::Renderer + 'a,
Message: 'static,
{
fn into(self) -> Element<'a, Message, Renderer> {
diff --git a/native/src/widget/progress_bar.rs b/native/src/widget/progress_bar.rs
index d294f198..69eb8c09 100644
--- a/native/src/widget/progress_bar.rs
+++ b/native/src/widget/progress_bar.rs
@@ -1,17 +1,19 @@
//! Provide progress feedback to your users.
+use crate::layout;
+use crate::renderer;
use crate::{
- layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
+ Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
};
use std::{hash::Hash, ops::RangeInclusive};
+pub use iced_style::progress_bar::{Style, StyleSheet};
+
/// A bar that displays progress.
///
/// # Example
/// ```
-/// # use iced_native::renderer::Null;
-/// #
-/// # pub type ProgressBar = iced_native::ProgressBar<Null>;
+/// # use iced_native::widget::ProgressBar;
/// let value = 50.0;
///
/// ProgressBar::new(0.0..=100.0, value);
@@ -19,15 +21,18 @@ use std::{hash::Hash, ops::RangeInclusive};
///
/// ![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: self::Renderer> {
+pub struct ProgressBar<'a> {
range: RangeInclusive<f32>,
value: f32,
width: Length,
height: Option<Length>,
- style: Renderer::Style,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
-impl<Renderer: self::Renderer> ProgressBar<Renderer> {
+impl<'a> ProgressBar<'a> {
+ /// The default height of a [`ProgressBar`].
+ pub const DEFAULT_HEIGHT: u16 = 30;
+
/// Creates a new [`ProgressBar`].
///
/// It expects:
@@ -39,7 +44,7 @@ impl<Renderer: self::Renderer> ProgressBar<Renderer> {
range,
width: Length::Fill,
height: None,
- style: Renderer::Style::default(),
+ style_sheet: Default::default(),
}
}
@@ -56,23 +61,25 @@ impl<Renderer: self::Renderer> ProgressBar<Renderer> {
}
/// Sets the style of the [`ProgressBar`].
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
}
-impl<Message, Renderer> Widget<Message, Renderer> for ProgressBar<Renderer>
+impl<'a, Message, Renderer> Widget<Message, Renderer> for ProgressBar<'a>
where
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
- self.height
- .unwrap_or(Length::Units(Renderer::DEFAULT_HEIGHT))
+ self.height.unwrap_or(Length::Units(Self::DEFAULT_HEIGHT))
}
fn layout(
@@ -80,10 +87,9 @@ where
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let limits = limits.width(self.width).height(
- self.height
- .unwrap_or(Length::Units(Renderer::DEFAULT_HEIGHT)),
- );
+ let limits = limits
+ .width(self.width)
+ .height(self.height.unwrap_or(Length::Units(Self::DEFAULT_HEIGHT)));
let size = limits.resolve(Size::ZERO);
@@ -93,17 +99,47 @@ where
fn draw(
&self,
renderer: &mut Renderer,
- _defaults: &Renderer::Defaults,
+ _style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
- renderer.draw(
- layout.bounds(),
- self.range.clone(),
- self.value,
- &self.style,
- )
+ ) {
+ let bounds = layout.bounds();
+ let (range_start, range_end) = self.range.clone().into_inner();
+
+ let active_progress_width = if range_start >= range_end {
+ 0.0
+ } else {
+ bounds.width * (self.value - range_start)
+ / (range_end - range_start)
+ };
+
+ let style = self.style_sheet.style();
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle { ..bounds },
+ border_radius: style.border_radius,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ style.background,
+ );
+
+ if active_progress_width > 0.0 {
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ width: active_progress_width,
+ ..bounds
+ },
+ border_radius: style.border_radius,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ style.bar,
+ );
+ }
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -115,45 +151,13 @@ where
}
}
-/// The renderer of a [`ProgressBar`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`ProgressBar`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: crate::Renderer {
- /// The style supported by this renderer.
- type Style: Default;
-
- /// The default height of a [`ProgressBar`].
- const DEFAULT_HEIGHT: u16;
-
- /// Draws a [`ProgressBar`].
- ///
- /// It receives:
- /// * the bounds of the [`ProgressBar`]
- /// * the range of values of the [`ProgressBar`]
- /// * the current value of the [`ProgressBar`]
- /// * maybe a specific background of the [`ProgressBar`]
- /// * maybe a specific active color of the [`ProgressBar`]
- fn draw(
- &self,
- bounds: Rectangle,
- range: RangeInclusive<f32>,
- value: f32,
- style: &Self::Style,
- ) -> Self::Output;
-}
-
-impl<'a, Message, Renderer> From<ProgressBar<Renderer>>
+impl<'a, Message, Renderer> From<ProgressBar<'a>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + crate::Renderer,
Message: 'a,
{
- fn from(
- progress_bar: ProgressBar<Renderer>,
- ) -> Element<'a, Message, Renderer> {
+ fn from(progress_bar: ProgressBar<'a>) -> Element<'a, Message, Renderer> {
Element::new(progress_bar)
}
}
diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs
index 513b2fce..86ad4c4e 100644
--- a/native/src/widget/radio.rs
+++ b/native/src/widget/radio.rs
@@ -1,24 +1,27 @@
//! Create choices using radio buttons.
use std::hash::Hash;
-use crate::alignment::{self, Alignment};
+use crate::alignment;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
-use crate::row;
+use crate::renderer;
use crate::text;
use crate::touch;
+use crate::widget::{self, Row, Text};
use crate::{
- Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Row,
- Text, Widget,
+ Alignment, Clipboard, Color, Element, Hasher, Layout, Length, Point,
+ Rectangle, Widget,
};
+pub use iced_style::radio::{Style, StyleSheet};
+
/// A circular button representing a choice.
///
/// # Example
/// ```
-/// # type Radio<Message> =
-/// # iced_native::Radio<Message, iced_native::renderer::Null>;
+/// # type Radio<'a, Message> =
+/// # iced_native::widget::Radio<'a, Message, iced_native::renderer::Null>;
/// #
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// pub enum Choice {
@@ -40,7 +43,7 @@ use crate::{
///
/// ![Radio buttons drawn by `iced_wgpu`](https://github.com/hecrj/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/radio.png?raw=true)
#[allow(missing_debug_implementations)]
-pub struct Radio<Message, Renderer: self::Renderer + text::Renderer> {
+pub struct Radio<'a, Message, Renderer: text::Renderer> {
is_selected: bool,
on_click: Message,
label: String,
@@ -50,14 +53,19 @@ pub struct Radio<Message, Renderer: self::Renderer + text::Renderer> {
text_size: Option<u16>,
text_color: Option<Color>,
font: Renderer::Font,
- style: Renderer::Style,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
-impl<Message, Renderer: self::Renderer + text::Renderer>
- Radio<Message, Renderer>
+impl<'a, Message, Renderer: text::Renderer> Radio<'a, Message, Renderer>
where
Message: Clone,
{
+ /// The default size of a [`Radio`] button.
+ pub const DEFAULT_SIZE: u16 = 28;
+
+ /// The default spacing of a [`Radio`] button.
+ pub const DEFAULT_SPACING: u16 = 15;
+
/// Creates a new [`Radio`] button.
///
/// It expects:
@@ -81,12 +89,12 @@ where
on_click: f(value),
label: label.into(),
width: Length::Shrink,
- size: <Renderer as self::Renderer>::DEFAULT_SIZE,
- spacing: Renderer::DEFAULT_SPACING, //15
+ size: Self::DEFAULT_SIZE,
+ spacing: Self::DEFAULT_SPACING, //15
text_size: None,
text_color: None,
font: Default::default(),
- style: Renderer::Style::default(),
+ style_sheet: Default::default(),
}
}
@@ -127,16 +135,20 @@ where
}
/// Sets the style of the [`Radio`] button.
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
}
-impl<Message, Renderer> Widget<Message, Renderer> for Radio<Message, Renderer>
+impl<'a, Message, Renderer> Widget<Message, Renderer>
+ for Radio<'a, Message, Renderer>
where
Message: Clone,
- Renderer: self::Renderer + text::Renderer + row::Renderer,
+ Renderer: text::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -192,43 +204,88 @@ where
event::Status::Ignored
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ if layout.bounds().contains(cursor_position) {
+ mouse::Interaction::Pointer
+ } else {
+ mouse::Interaction::default()
+ }
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
+ ) {
let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+
let mut children = layout.children();
- let radio_layout = children.next().unwrap();
- let label_layout = children.next().unwrap();
- let radio_bounds = radio_layout.bounds();
-
- let label = text::Renderer::draw(
- renderer,
- defaults,
- label_layout.bounds(),
- &self.label,
- self.text_size.unwrap_or(renderer.default_size()),
- self.font,
- self.text_color,
- alignment::Horizontal::Left,
- alignment::Vertical::Center,
- );
+ {
+ let layout = children.next().unwrap();
+ let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let size = bounds.width;
+ let dot_size = size / 2.0;
+
+ let style = if is_mouse_over {
+ self.style_sheet.hovered()
+ } else {
+ self.style_sheet.active()
+ };
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_radius: size / 2.0,
+ border_width: style.border_width,
+ border_color: style.border_color,
+ },
+ style.background,
+ );
- self::Renderer::draw(
- renderer,
- radio_bounds,
- self.is_selected,
- is_mouse_over,
- label,
- &self.style,
- )
+ if self.is_selected {
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: bounds.x + dot_size / 2.0,
+ y: bounds.y + dot_size / 2.0,
+ width: bounds.width - dot_size,
+ height: bounds.height - dot_size,
+ },
+ border_radius: dot_size / 2.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ style.dot_color,
+ );
+ }
+ }
+
+ {
+ let label_layout = children.next().unwrap();
+
+ widget::text::draw(
+ renderer,
+ style,
+ label_layout,
+ &self.label,
+ self.font,
+ self.text_size,
+ self.text_color,
+ alignment::Horizontal::Left,
+ alignment::Vertical::Center,
+ );
+ }
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -239,46 +296,15 @@ where
}
}
-/// The renderer of a [`Radio`] button.
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Radio`] button in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: crate::Renderer {
- /// The style supported by this renderer.
- type Style: Default;
-
- /// The default size of a [`Radio`] button.
- const DEFAULT_SIZE: u16;
-
- /// The default spacing of a [`Radio`] button.
- const DEFAULT_SPACING: u16;
-
- /// Draws a [`Radio`] button.
- ///
- /// It receives:
- /// * the bounds of the [`Radio`]
- /// * whether the [`Radio`] is selected or not
- /// * whether the mouse is over the [`Radio`] or not
- /// * the drawn label of the [`Radio`]
- fn draw(
- &mut self,
- bounds: Rectangle,
- is_selected: bool,
- is_mouse_over: bool,
- label: Self::Output,
- style: &Self::Style,
- ) -> Self::Output;
-}
-
-impl<'a, Message, Renderer> From<Radio<Message, Renderer>>
+impl<'a, Message, Renderer> From<Radio<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a + Clone,
- Renderer: 'a + self::Renderer + row::Renderer + text::Renderer,
+ Renderer: 'a + text::Renderer,
{
- fn from(radio: Radio<Message, Renderer>) -> Element<'a, Message, Renderer> {
+ fn from(
+ radio: Radio<'a, Message, Renderer>,
+ ) -> Element<'a, Message, Renderer> {
Element::new(radio)
}
}
diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs
index 1923f213..6fe3284b 100644
--- a/native/src/widget/row.rs
+++ b/native/src/widget/row.rs
@@ -1,7 +1,9 @@
//! Distribute content horizontally.
use crate::event::{self, Event};
use crate::layout;
+use crate::mouse;
use crate::overlay;
+use crate::renderer;
use crate::{
Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
Rectangle, Widget,
@@ -104,7 +106,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Row<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -161,21 +163,37 @@ where
.fold(event::Status::Ignored, event::Status::merge)
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ self.children
+ .iter()
+ .zip(layout.children())
+ .map(|(child, layout)| {
+ child.widget.mouse_interaction(
+ layout,
+ cursor_position,
+ viewport,
+ )
+ })
+ .max()
+ .unwrap_or_default()
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
- ) -> Renderer::Output {
- renderer.draw(
- defaults,
- &self.children,
- layout,
- cursor_position,
- viewport,
- )
+ ) {
+ for (child, layout) in self.children.iter().zip(layout.children()) {
+ child.draw(renderer, style, layout, cursor_position, viewport);
+ }
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -207,33 +225,10 @@ where
}
}
-/// The renderer of a [`Row`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Row`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: crate::Renderer + Sized {
- /// Draws a [`Row`].
- ///
- /// It receives:
- /// - the children of the [`Row`]
- /// - the [`Layout`] of the [`Row`] and its children
- /// - the cursor position
- fn draw<Message>(
- &mut self,
- defaults: &Self::Defaults,
- children: &[Element<'_, Message, Self>],
- layout: Layout<'_>,
- cursor_position: Point,
- viewport: &Rectangle,
- ) -> Self::Output;
-}
-
impl<'a, Message, Renderer> From<Row<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + crate::Renderer,
Message: 'a,
{
fn from(row: Row<'a, Message, Renderer>) -> Element<'a, Message, Renderer> {
diff --git a/native/src/widget/rule.rs b/native/src/widget/rule.rs
index 18c88658..7c8c5dbc 100644
--- a/native/src/widget/rule.rs
+++ b/native/src/widget/rule.rs
@@ -1,28 +1,31 @@
//! Display a horizontal or vertical rule for dividing content.
+use crate::layout;
+use crate::renderer;
+use crate::{
+ Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
+};
use std::hash::Hash;
-use crate::{
- layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
-};
+pub use iced_style::rule::{FillMode, Style, StyleSheet};
/// Display a horizontal or vertical rule for dividing content.
-#[derive(Debug, Copy, Clone)]
-pub struct Rule<Renderer: self::Renderer> {
+#[allow(missing_debug_implementations)]
+pub struct Rule<'a> {
width: Length,
height: Length,
- style: Renderer::Style,
is_horizontal: bool,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
-impl<Renderer: self::Renderer> Rule<Renderer> {
+impl<'a> Rule<'a> {
/// Creates a horizontal [`Rule`] for dividing content by the given vertical spacing.
pub fn horizontal(spacing: u16) -> Self {
Rule {
width: Length::Fill,
height: Length::from(Length::Units(spacing)),
- style: Renderer::Style::default(),
is_horizontal: true,
+ style_sheet: Default::default(),
}
}
@@ -31,21 +34,24 @@ impl<Renderer: self::Renderer> Rule<Renderer> {
Rule {
width: Length::from(Length::Units(spacing)),
height: Length::Fill,
- style: Renderer::Style::default(),
is_horizontal: false,
+ style_sheet: Default::default(),
}
}
/// Sets the style of the [`Rule`].
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
}
-impl<Message, Renderer> Widget<Message, Renderer> for Rule<Renderer>
+impl<'a, Message, Renderer> Widget<Message, Renderer> for Rule<'a>
where
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -68,12 +74,53 @@ where
fn draw(
&self,
renderer: &mut Renderer,
- _defaults: &Renderer::Defaults,
+ _style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
- renderer.draw(layout.bounds(), &self.style, self.is_horizontal)
+ ) {
+ let bounds = layout.bounds();
+ let style = self.style_sheet.style();
+
+ let bounds = if self.is_horizontal {
+ let line_y = (bounds.y + (bounds.height / 2.0)
+ - (style.width as f32 / 2.0))
+ .round();
+
+ let (offset, line_width) = style.fill_mode.fill(bounds.width);
+ let line_x = bounds.x + offset;
+
+ Rectangle {
+ x: line_x,
+ y: line_y,
+ width: line_width,
+ height: style.width as f32,
+ }
+ } else {
+ let line_x = (bounds.x + (bounds.width / 2.0)
+ - (style.width as f32 / 2.0))
+ .round();
+
+ let (offset, line_height) = style.fill_mode.fill(bounds.height);
+ let line_y = bounds.y + offset;
+
+ Rectangle {
+ x: line_x,
+ y: line_y,
+ width: style.width as f32,
+ height: line_height,
+ }
+ };
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_radius: style.radius,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ style.color,
+ );
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -85,32 +132,12 @@ where
}
}
-/// The renderer of a [`Rule`].
-pub trait Renderer: crate::Renderer {
- /// The style supported by this renderer.
- type Style: Default;
-
- /// Draws a [`Rule`].
- ///
- /// It receives:
- /// * the bounds of the [`Rule`]
- /// * the style of the [`Rule`]
- /// * whether the [`Rule`] is horizontal (true) or vertical (false)
- fn draw(
- &mut self,
- bounds: Rectangle,
- style: &Self::Style,
- is_horizontal: bool,
- ) -> Self::Output;
-}
-
-impl<'a, Message, Renderer> From<Rule<Renderer>>
- for Element<'a, Message, Renderer>
+impl<'a, Message, Renderer> From<Rule<'a>> for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + crate::Renderer,
Message: 'a,
{
- fn from(rule: Rule<Renderer>) -> Element<'a, Message, Renderer> {
+ fn from(rule: Rule<'a>) -> Element<'a, Message, Renderer> {
Element::new(rule)
}
}
diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs
index a8e467d3..2bf2ea5e 100644
--- a/native/src/widget/scrollable.rs
+++ b/native/src/widget/scrollable.rs
@@ -1,21 +1,24 @@
//! Navigate an endless amount of content with a scrollbar.
-use crate::column;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
+use crate::renderer;
use crate::touch;
+use crate::widget::Column;
use crate::{
- Alignment, Clipboard, Column, Element, Hasher, Layout, Length, Padding,
- Point, Rectangle, Size, Vector, Widget,
+ Alignment, Background, Clipboard, Color, Element, Hasher, Layout, Length,
+ Padding, Point, Rectangle, Size, Vector, Widget,
};
use std::{f32, hash::Hash, u32};
+pub use iced_style::scrollable::StyleSheet;
+
/// A widget that can vertically display an infinite amount of content with a
/// scrollbar.
#[allow(missing_debug_implementations)]
-pub struct Scrollable<'a, Message, Renderer: self::Renderer> {
+pub struct Scrollable<'a, Message, Renderer> {
state: &'a mut State,
height: Length,
max_height: u32,
@@ -24,10 +27,10 @@ pub struct Scrollable<'a, Message, Renderer: self::Renderer> {
scroller_width: u16,
content: Column<'a, Message, Renderer>,
on_scroll: Option<Box<dyn Fn(f32) -> Message>>,
- style: Renderer::Style,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
-impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
+impl<'a, Message, Renderer: crate::Renderer> Scrollable<'a, Message, Renderer> {
/// Creates a new [`Scrollable`] with the given [`State`].
pub fn new(state: &'a mut State) -> Self {
Scrollable {
@@ -39,7 +42,7 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
scroller_width: 10,
content: Column::new(),
on_scroll: None,
- style: Renderer::Style::default(),
+ style_sheet: Default::default(),
}
}
@@ -120,8 +123,11 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
}
/// Sets the style of the [`Scrollable`] .
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
@@ -151,12 +157,63 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
));
}
}
+
+ fn scrollbar(
+ &self,
+ bounds: Rectangle,
+ content_bounds: Rectangle,
+ ) -> Option<Scrollbar> {
+ let offset = self.state.offset(bounds, content_bounds);
+
+ if content_bounds.height > bounds.height {
+ let outer_width = self.scrollbar_width.max(self.scroller_width)
+ + 2 * self.scrollbar_margin;
+
+ let outer_bounds = Rectangle {
+ x: bounds.x + bounds.width - outer_width as f32,
+ y: bounds.y,
+ width: outer_width as f32,
+ height: bounds.height,
+ };
+
+ let scrollbar_bounds = Rectangle {
+ x: bounds.x + bounds.width
+ - f32::from(outer_width / 2 + self.scrollbar_width / 2),
+ y: bounds.y,
+ width: self.scrollbar_width as f32,
+ height: bounds.height,
+ };
+
+ let ratio = bounds.height / content_bounds.height;
+ let scroller_height = bounds.height * ratio;
+ let y_offset = offset as f32 * ratio;
+
+ let scroller_bounds = Rectangle {
+ x: bounds.x + bounds.width
+ - f32::from(outer_width / 2 + self.scroller_width / 2),
+ y: scrollbar_bounds.y + y_offset,
+ width: self.scroller_width as f32,
+ height: scroller_height,
+ };
+
+ Some(Scrollbar {
+ outer_bounds,
+ bounds: scrollbar_bounds,
+ margin: self.scrollbar_margin,
+ scroller: Scroller {
+ bounds: scroller_bounds,
+ },
+ })
+ } else {
+ None
+ }
+ }
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Scrollable<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
fn width(&self) -> Length {
Widget::<Message, Renderer>::width(&self.content)
@@ -202,15 +259,7 @@ where
let content = layout.children().next().unwrap();
let content_bounds = content.bounds();
- let offset = self.state.offset(bounds, content_bounds);
- let scrollbar = renderer.scrollbar(
- bounds,
- content_bounds,
- offset,
- self.scrollbar_width,
- self.scrollbar_margin,
- self.scroller_width,
- );
+ let scrollbar = self.scrollbar(bounds, content_bounds);
let is_mouse_over_scrollbar = scrollbar
.as_ref()
.map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
@@ -374,26 +423,16 @@ where
event::Status::Ignored
}
- fn draw(
+ fn mouse_interaction(
&self,
- renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
+ ) -> mouse::Interaction {
let bounds = layout.bounds();
let content_layout = layout.children().next().unwrap();
let content_bounds = content_layout.bounds();
- let offset = self.state.offset(bounds, content_bounds);
- let scrollbar = renderer.scrollbar(
- bounds,
- content_bounds,
- offset,
- self.scrollbar_width,
- self.scrollbar_margin,
- self.scroller_width,
- );
+ let scrollbar = self.scrollbar(bounds, content_bounds);
let is_mouse_over = bounds.contains(cursor_position);
let is_mouse_over_scrollbar = scrollbar
@@ -401,16 +440,18 @@ where
.map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
.unwrap_or(false);
- let content = {
+ if is_mouse_over_scrollbar || self.state.is_scroller_grabbed() {
+ mouse::Interaction::Idle
+ } else {
+ let offset = self.state.offset(bounds, content_bounds);
+
let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
Point::new(cursor_position.x, cursor_position.y + offset as f32)
} else {
Point::new(cursor_position.x, -1.0)
};
- self.content.draw(
- renderer,
- defaults,
+ self.content.mouse_interaction(
content_layout,
cursor_position,
&Rectangle {
@@ -418,20 +459,114 @@ where
..bounds
},
)
+ }
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) {
+ let bounds = layout.bounds();
+ let content_layout = layout.children().next().unwrap();
+ let content_bounds = content_layout.bounds();
+ let offset = self.state.offset(bounds, content_bounds);
+ let scrollbar = self.scrollbar(bounds, content_bounds);
+
+ let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over_scrollbar = scrollbar
+ .as_ref()
+ .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
+ .unwrap_or(false);
+
+ let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
+ Point::new(cursor_position.x, cursor_position.y + offset as f32)
+ } else {
+ Point::new(cursor_position.x, -1.0)
};
- self::Renderer::draw(
- renderer,
- &self.state,
- bounds,
- content_layout.bounds(),
- is_mouse_over,
- is_mouse_over_scrollbar,
- scrollbar,
- offset,
- &self.style,
- content,
- )
+ if let Some(scrollbar) = scrollbar {
+ renderer.with_layer(bounds, |renderer| {
+ renderer.with_translation(
+ Vector::new(0.0, -(offset as f32)),
+ |renderer| {
+ self.content.draw(
+ renderer,
+ style,
+ content_layout,
+ cursor_position,
+ &Rectangle {
+ y: bounds.y + offset as f32,
+ ..bounds
+ },
+ );
+ },
+ );
+ });
+
+ let style = if self.state.is_scroller_grabbed() {
+ self.style_sheet.dragging()
+ } else if is_mouse_over_scrollbar {
+ self.style_sheet.hovered()
+ } else {
+ self.style_sheet.active()
+ };
+
+ let is_scrollbar_visible =
+ style.background.is_some() || style.border_width > 0.0;
+
+ renderer.with_layer(
+ Rectangle {
+ width: bounds.width + 2.0,
+ height: bounds.height + 2.0,
+ ..bounds
+ },
+ |renderer| {
+ if is_scrollbar_visible {
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: scrollbar.bounds,
+ border_radius: style.border_radius,
+ border_width: style.border_width,
+ border_color: style.border_color,
+ },
+ style.background.unwrap_or(Background::Color(
+ Color::TRANSPARENT,
+ )),
+ );
+ }
+
+ if is_mouse_over
+ || self.state.is_scroller_grabbed()
+ || is_scrollbar_visible
+ {
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: scrollbar.scroller.bounds,
+ border_radius: style.scroller.border_radius,
+ border_width: style.scroller.border_width,
+ border_color: style.scroller.border_color,
+ },
+ style.scroller.color,
+ );
+ }
+ },
+ );
+ } else {
+ self.content.draw(
+ renderer,
+ style,
+ content_layout,
+ cursor_position,
+ &Rectangle {
+ y: bounds.y + offset as f32,
+ ..bounds
+ },
+ );
+ }
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -577,19 +712,19 @@ impl State {
/// The scrollbar of a [`Scrollable`].
#[derive(Debug)]
-pub struct Scrollbar {
+struct Scrollbar {
/// The outer bounds of the scrollable, including the [`Scrollbar`] and
/// [`Scroller`].
- pub outer_bounds: Rectangle,
+ outer_bounds: Rectangle,
/// The bounds of the [`Scrollbar`].
- pub bounds: Rectangle,
+ bounds: Rectangle,
/// The margin within the [`Scrollbar`].
- pub margin: u16,
+ margin: u16,
/// The bounds of the [`Scroller`].
- pub scroller: Scroller,
+ scroller: Scroller,
}
impl Scrollbar {
@@ -624,62 +759,15 @@ impl Scrollbar {
/// The handle of a [`Scrollbar`].
#[derive(Debug, Clone, Copy)]
-pub struct Scroller {
+struct Scroller {
/// The bounds of the [`Scroller`].
- pub bounds: Rectangle,
-}
-
-/// The renderer of a [`Scrollable`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Scrollable`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: column::Renderer + Sized {
- /// The style supported by this renderer.
- type Style: Default;
-
- /// Returns the [`Scrollbar`] given the bounds and content bounds of a
- /// [`Scrollable`].
- fn scrollbar(
- &self,
- bounds: Rectangle,
- content_bounds: Rectangle,
- offset: u32,
- scrollbar_width: u16,
- scrollbar_margin: u16,
- scroller_width: u16,
- ) -> Option<Scrollbar>;
-
- /// Draws the [`Scrollable`].
- ///
- /// It receives:
- /// - the [`State`] of the [`Scrollable`]
- /// - the bounds of the [`Scrollable`] widget
- /// - the bounds of the [`Scrollable`] content
- /// - whether the mouse is over the [`Scrollable`] or not
- /// - whether the mouse is over the [`Scrollbar`] or not
- /// - a optional [`Scrollbar`] to be rendered
- /// - the scrolling offset
- /// - the drawn content
- fn draw(
- &mut self,
- scrollable: &State,
- bounds: Rectangle,
- content_bounds: Rectangle,
- is_mouse_over: bool,
- is_mouse_over_scrollbar: bool,
- scrollbar: Option<Scrollbar>,
- offset: u32,
- style: &Self::Style,
- content: Self::Output,
- ) -> Self::Output;
+ bounds: Rectangle,
}
impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + crate::Renderer,
Message: 'a,
{
fn from(
diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs
index 2a74d5a3..49bafab4 100644
--- a/native/src/widget/slider.rs
+++ b/native/src/widget/slider.rs
@@ -4,12 +4,17 @@
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
+use crate::renderer;
use crate::touch;
use crate::{
- Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
+ Background, Clipboard, Color, Element, Hasher, Layout, Length, Point,
+ Rectangle, Size, Widget,
};
-use std::{hash::Hash, ops::RangeInclusive};
+use std::hash::Hash;
+use std::ops::RangeInclusive;
+
+pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet};
/// An horizontal bar and a handle that selects a single value from a range of
/// values.
@@ -21,9 +26,8 @@ use std::{hash::Hash, ops::RangeInclusive};
///
/// # Example
/// ```
-/// # use iced_native::{slider, renderer::Null};
+/// # use iced_native::widget::slider::{self, Slider};
/// #
-/// # pub type Slider<'a, T, Message> = iced_native::Slider<'a, T, Message, Null>;
/// #[derive(Clone)]
/// pub enum Message {
/// SliderChanged(f32),
@@ -37,7 +41,7 @@ use std::{hash::Hash, ops::RangeInclusive};
///
/// ![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: self::Renderer> {
+pub struct Slider<'a, T, Message> {
state: &'a mut State,
range: RangeInclusive<T>,
step: T,
@@ -46,15 +50,17 @@ pub struct Slider<'a, T, Message, Renderer: self::Renderer> {
on_release: Option<Message>,
width: Length,
height: u16,
- style: Renderer::Style,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
-impl<'a, T, Message, Renderer> Slider<'a, T, Message, Renderer>
+impl<'a, T, Message> Slider<'a, T, Message>
where
T: Copy + From<u8> + std::cmp::PartialOrd,
Message: Clone,
- Renderer: self::Renderer,
{
+ /// The default height of a [`Slider`].
+ pub const DEFAULT_HEIGHT: u16 = 22;
+
/// Creates a new [`Slider`].
///
/// It expects:
@@ -93,8 +99,8 @@ where
on_change: Box::new(on_change),
on_release: None,
width: Length::Fill,
- height: Renderer::DEFAULT_HEIGHT,
- style: Renderer::Style::default(),
+ height: Self::DEFAULT_HEIGHT,
+ style_sheet: Default::default(),
}
}
@@ -122,8 +128,11 @@ where
}
/// Sets the style of the [`Slider`].
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
@@ -148,11 +157,11 @@ impl State {
}
impl<'a, T, Message, Renderer> Widget<Message, Renderer>
- for Slider<'a, T, Message, Renderer>
+ for Slider<'a, T, Message>
where
T: Copy + Into<f64> + num_traits::FromPrimitive,
Message: Clone,
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -246,22 +255,113 @@ where
fn draw(
&self,
renderer: &mut Renderer,
- _defaults: &Renderer::Defaults,
+ _style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
- let start = *self.range.start();
- let end = *self.range.end();
-
- renderer.draw(
- layout.bounds(),
- cursor_position,
- start.into() as f32..=end.into() as f32,
- self.value.into() as f32,
- self.state.is_dragging,
- &self.style,
- )
+ ) {
+ let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ let style = if self.state.is_dragging {
+ self.style_sheet.dragging()
+ } else if is_mouse_over {
+ self.style_sheet.hovered()
+ } else {
+ self.style_sheet.active()
+ };
+
+ let rail_y = bounds.y + (bounds.height / 2.0).round();
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: bounds.x,
+ y: rail_y,
+ width: bounds.width,
+ height: 2.0,
+ },
+ border_radius: 0.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ style.rail_colors.0,
+ );
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: bounds.x,
+ y: rail_y + 2.0,
+ width: bounds.width,
+ height: 2.0,
+ },
+ border_radius: 0.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ Background::Color(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), f32::from(bounds.height), border_radius),
+ };
+
+ let value = self.value.into() as f32;
+ let (range_start, range_end) = {
+ let (start, end) = self.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(),
+ y: rail_y - handle_height / 2.0,
+ width: handle_width,
+ height: handle_height,
+ },
+ border_radius: handle_border_radius,
+ border_width: style.handle.border_width,
+ border_color: style.handle.border_color,
+ },
+ style.handle.color,
+ );
+ }
+
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ if self.state.is_dragging {
+ mouse::Interaction::Grabbing
+ } else if is_mouse_over {
+ mouse::Interaction::Grab
+ } else {
+ mouse::Interaction::default()
+ }
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -272,48 +372,14 @@ where
}
}
-/// The renderer of a [`Slider`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Slider`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: crate::Renderer {
- /// The style supported by this renderer.
- type Style: Default;
-
- /// The default height of a [`Slider`].
- const DEFAULT_HEIGHT: u16;
-
- /// Draws a [`Slider`].
- ///
- /// It receives:
- /// * the current cursor position
- /// * the bounds of the [`Slider`]
- /// * the local state of the [`Slider`]
- /// * the range of values of the [`Slider`]
- /// * the current value of the [`Slider`]
- fn draw(
- &mut self,
- bounds: Rectangle,
- cursor_position: Point,
- range: RangeInclusive<f32>,
- value: f32,
- is_dragging: bool,
- style: &Self::Style,
- ) -> Self::Output;
-}
-
-impl<'a, T, Message, Renderer> From<Slider<'a, T, Message, Renderer>>
+impl<'a, T, Message, Renderer> From<Slider<'a, T, Message>>
for Element<'a, Message, Renderer>
where
T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,
Message: 'a + Clone,
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + crate::Renderer,
{
- fn from(
- slider: Slider<'a, T, Message, Renderer>,
- ) -> Element<'a, Message, Renderer> {
+ fn from(slider: Slider<'a, T, Message>) -> Element<'a, Message, Renderer> {
Element::new(slider)
}
}
diff --git a/native/src/widget/space.rs b/native/src/widget/space.rs
index 6b34ece8..3373f3b7 100644
--- a/native/src/widget/space.rs
+++ b/native/src/widget/space.rs
@@ -1,9 +1,9 @@
//! Distribute content vertically.
-use std::hash::Hash;
+use crate::layout;
+use crate::renderer;
+use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
-use crate::{
- layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
-};
+use std::hash::Hash;
/// An amount of empty space.
///
@@ -39,7 +39,7 @@ impl Space {
impl<Message, Renderer> Widget<Message, Renderer> for Space
where
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -61,13 +61,12 @@ where
fn draw(
&self,
- renderer: &mut Renderer,
- _defaults: &Renderer::Defaults,
- layout: Layout<'_>,
+ _renderer: &mut Renderer,
+ _style: &renderer::Style,
+ _layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
- renderer.draw(layout.bounds())
+ ) {
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -78,17 +77,9 @@ where
}
}
-/// The renderer of an amount of [`Space`].
-pub trait Renderer: crate::Renderer {
- /// Draws an amount of empty [`Space`].
- ///
- /// You should most likely return an empty primitive here.
- fn draw(&mut self, bounds: Rectangle) -> Self::Output;
-}
-
impl<'a, Message, Renderer> From<Space> for Element<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
Message: 'a,
{
fn from(space: Space) -> Element<'a, Message, Renderer> {
diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs
index 9cd61918..f212dfcb 100644
--- a/native/src/widget/svg.rs
+++ b/native/src/widget/svg.rs
@@ -1,12 +1,11 @@
//! Display vector graphics in your application.
use crate::layout;
+use crate::renderer;
+use crate::svg::{self, Handle};
use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
-use std::{
- hash::{Hash, Hasher as _},
- path::PathBuf,
- sync::Arc,
-};
+use std::hash::Hash;
+use std::path::PathBuf;
/// A vector graphics image.
///
@@ -52,7 +51,7 @@ impl Svg {
impl<Message, Renderer> Widget<Message, Renderer> for Svg
where
- Renderer: self::Renderer,
+ Renderer: svg::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -90,12 +89,12 @@ where
fn draw(
&self,
renderer: &mut Renderer,
- _defaults: &Renderer::Defaults,
+ _style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
- renderer.draw(self.handle.clone(), layout)
+ ) {
+ renderer.draw(self.handle.clone(), layout.bounds())
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -107,94 +106,9 @@ where
}
}
-/// An [`Svg`] handle.
-#[derive(Debug, Clone)]
-pub struct Handle {
- id: u64,
- data: Arc<Data>,
-}
-
-impl Handle {
- /// Creates an SVG [`Handle`] pointing to the vector image of the given
- /// path.
- pub fn from_path(path: impl Into<PathBuf>) -> Handle {
- Self::from_data(Data::Path(path.into()))
- }
-
- /// Creates an SVG [`Handle`] from raw bytes containing either an SVG string
- /// or gzip compressed data.
- ///
- /// This is useful if you already have your SVG data in-memory, maybe
- /// because you downloaded or generated it procedurally.
- pub fn from_memory(bytes: impl Into<Vec<u8>>) -> Handle {
- Self::from_data(Data::Bytes(bytes.into()))
- }
-
- fn from_data(data: Data) -> Handle {
- let mut hasher = Hasher::default();
- data.hash(&mut hasher);
-
- Handle {
- id: hasher.finish(),
- data: Arc::new(data),
- }
- }
-
- /// Returns the unique identifier of the [`Handle`].
- pub fn id(&self) -> u64 {
- self.id
- }
-
- /// Returns a reference to the SVG [`Data`].
- pub fn data(&self) -> &Data {
- &self.data
- }
-}
-
-impl Hash for Handle {
- fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
- self.id.hash(state);
- }
-}
-
-/// The data of an [`Svg`].
-#[derive(Clone, Hash)]
-pub enum Data {
- /// File data
- Path(PathBuf),
-
- /// In-memory data
- ///
- /// Can contain an SVG string or a gzip compressed data.
- Bytes(Vec<u8>),
-}
-
-impl std::fmt::Debug for Data {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- Data::Path(path) => write!(f, "Path({:?})", path),
- Data::Bytes(_) => write!(f, "Bytes(...)"),
- }
- }
-}
-
-/// The renderer of an [`Svg`].
-///
-/// Your [renderer] will need to implement this trait before being able to use
-/// an [`Svg`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: crate::Renderer {
- /// Returns the default dimensions of an [`Svg`] for the given [`Handle`].
- fn dimensions(&self, handle: &Handle) -> (u32, u32);
-
- /// Draws an [`Svg`].
- fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output;
-}
-
impl<'a, Message, Renderer> From<Svg> for Element<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: svg::Renderer,
{
fn from(icon: Svg) -> Element<'a, Message, Renderer> {
Element::new(icon)
diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs
index 168d49c2..4dbc4a65 100644
--- a/native/src/widget/text.rs
+++ b/native/src/widget/text.rs
@@ -1,12 +1,12 @@
//! Write some text for your users to read.
use crate::alignment;
use crate::layout;
+use crate::renderer;
+use crate::text;
use crate::{
Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
};
-pub use iced_core::text::Hit;
-
use std::hash::Hash;
/// A paragraph of text.
@@ -14,7 +14,7 @@ use std::hash::Hash;
/// # Example
///
/// ```
-/// # type Text = iced_native::Text<iced_native::renderer::Null>;
+/// # type Text = iced_native::widget::Text<iced_native::renderer::Null>;
/// #
/// Text::new("I <3 iced!")
/// .color([0.0, 0.0, 1.0])
@@ -23,7 +23,7 @@ use std::hash::Hash;
///
/// ![Text drawn by `iced_wgpu`](https://github.com/hecrj/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text.png?raw=true)
#[derive(Debug)]
-pub struct Text<Renderer: self::Renderer> {
+pub struct Text<Renderer: text::Renderer> {
content: String,
size: Option<u16>,
color: Option<Color>,
@@ -34,7 +34,7 @@ pub struct Text<Renderer: self::Renderer> {
vertical_alignment: alignment::Vertical,
}
-impl<Renderer: self::Renderer> Text<Renderer> {
+impl<Renderer: text::Renderer> Text<Renderer> {
/// Create a new fragment of [`Text`] with the given contents.
pub fn new<T: Into<String>>(label: T) -> Self {
Text {
@@ -102,7 +102,7 @@ impl<Renderer: self::Renderer> Text<Renderer> {
impl<Message, Renderer> Widget<Message, Renderer> for Text<Renderer>
where
- Renderer: self::Renderer,
+ Renderer: text::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -134,21 +134,22 @@ where
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
- renderer.draw(
- defaults,
- layout.bounds(),
+ ) {
+ draw(
+ renderer,
+ style,
+ layout,
&self.content,
- self.size.unwrap_or(renderer.default_size()),
self.font,
+ self.size,
self.color,
self.horizontal_alignment,
self.vertical_alignment,
- )
+ );
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -162,79 +163,65 @@ where
}
}
-/// The renderer of a [`Text`] fragment.
+/// Draws text using the same logic as the [`Text`] widget.
///
-/// Your [renderer] will need to implement this trait before being
-/// able to use [`Text`] in your user interface.
+/// Specifically:
///
-/// [renderer]: crate::Renderer
-pub trait Renderer: crate::Renderer {
- /// The font type used for [`Text`].
- type Font: Default + Copy;
-
- /// Returns the default size of [`Text`].
- fn default_size(&self) -> u16;
-
- /// Measures the [`Text`] in the given bounds and returns the minimum
- /// boundaries that can fit the contents.
- fn measure(
- &self,
- content: &str,
- size: u16,
- font: Self::Font,
- bounds: Size,
- ) -> (f32, f32);
-
- /// Tests whether the provided point is within the boundaries of [`Text`]
- /// laid out with the given parameters, returning information about
- /// the nearest character.
- ///
- /// If `nearest_only` is true, the hit test does not consider whether the
- /// the point is interior to any glyph bounds, returning only the character
- /// with the nearest centeroid.
- fn hit_test(
- &self,
- contents: &str,
- size: f32,
- font: Self::Font,
- bounds: Size,
- point: Point,
- nearest_only: bool,
- ) -> Option<Hit>;
-
- /// Draws a [`Text`] fragment.
- ///
- /// It receives:
- /// * the bounds of the [`Text`]
- /// * the contents of the [`Text`]
- /// * the size of the [`Text`]
- /// * the color of the [`Text`]
- /// * the [`HorizontalAlignment`] of the [`Text`]
- /// * the [`VerticalAlignment`] of the [`Text`]
- fn draw(
- &mut self,
- defaults: &Self::Defaults,
- bounds: Rectangle,
- content: &str,
- size: u16,
- font: Self::Font,
- color: Option<Color>,
- horizontal_alignment: alignment::Horizontal,
- vertical_alignment: alignment::Vertical,
- ) -> Self::Output;
+/// * If no `size` is provided, the default text size of the `Renderer` will be
+/// used.
+/// * If no `color` is provided, the [`renderer::Style::text_color`] will be
+/// used.
+/// * The alignment attributes do not affect the position of the bounds of the
+/// [`Layout`].
+pub fn draw<Renderer>(
+ renderer: &mut Renderer,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ content: &str,
+ font: Renderer::Font,
+ size: Option<u16>,
+ color: Option<Color>,
+ horizontal_alignment: alignment::Horizontal,
+ vertical_alignment: alignment::Vertical,
+) where
+ Renderer: text::Renderer,
+{
+ let bounds = layout.bounds();
+
+ let x = match horizontal_alignment {
+ alignment::Horizontal::Left => bounds.x,
+ alignment::Horizontal::Center => bounds.center_x(),
+ alignment::Horizontal::Right => bounds.x + bounds.width,
+ };
+
+ let y = match vertical_alignment {
+ alignment::Vertical::Top => bounds.y,
+ alignment::Vertical::Center => bounds.center_y(),
+ alignment::Vertical::Bottom => bounds.y + bounds.height,
+ };
+
+ renderer.fill_text(crate::text::Text {
+ content,
+ size: f32::from(size.unwrap_or(renderer.default_size())),
+ bounds: Rectangle { x, y, ..bounds },
+ color: color.unwrap_or(style.text_color),
+ font,
+ horizontal_alignment,
+ vertical_alignment,
+ });
}
impl<'a, Message, Renderer> From<Text<Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: self::Renderer + 'a,
+ Renderer: text::Renderer + 'a,
{
fn from(text: Text<Renderer>) -> Element<'a, Message, Renderer> {
Element::new(text)
}
}
-impl<Renderer: self::Renderer> Clone for Text<Renderer> {
+impl<Renderer: text::Renderer> Clone for Text<Renderer> {
fn clone(&self) -> Self {
Self {
content: self.content.clone(),
diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs
index d4d197d3..40c6c573 100644
--- a/native/src/widget/text_input.rs
+++ b/native/src/widget/text_input.rs
@@ -11,26 +11,31 @@ 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::text;
+use crate::renderer;
+use crate::text::{self, Text};
use crate::touch;
use crate::{
- Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
- Size, Widget,
+ Clipboard, Color, Element, Hasher, Layout, Length, Padding, Point,
+ Rectangle, Size, Vector, Widget,
};
use std::u32;
+pub use iced_style::text_input::{Style, StyleSheet};
+
/// A field that can be filled with text.
///
/// # Example
/// ```
-/// # use iced_native::{text_input, renderer::Null};
+/// # use iced_native::renderer::Null;
+/// # use iced_native::widget::text_input;
/// #
-/// # pub type TextInput<'a, Message> = iced_native::TextInput<'a, Message, Null>;
+/// # pub type TextInput<'a, Message> = iced_native::widget::TextInput<'a, Message, Null>;
/// #[derive(Debug, Clone)]
/// enum Message {
/// TextInputChanged(String),
@@ -49,7 +54,7 @@ use std::u32;
/// ```
/// ![Text input drawn by `iced_wgpu`](https://github.com/hecrj/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text_input.png?raw=true)
#[allow(missing_debug_implementations)]
-pub struct TextInput<'a, Message, Renderer: self::Renderer> {
+pub struct TextInput<'a, Message, Renderer: text::Renderer> {
state: &'a mut State,
placeholder: String,
value: Value,
@@ -61,13 +66,13 @@ pub struct TextInput<'a, Message, Renderer: self::Renderer> {
size: Option<u16>,
on_change: Box<dyn Fn(String) -> Message>,
on_submit: Option<Message>,
- style: Renderer::Style,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>
where
Message: Clone,
- Renderer: self::Renderer,
+ Renderer: text::Renderer,
{
/// Creates a new [`TextInput`].
///
@@ -97,7 +102,7 @@ where
size: None,
on_change: Box::new(on_change),
on_submit: None,
- style: Renderer::Style::default(),
+ style_sheet: Default::default(),
}
}
@@ -147,8 +152,11 @@ where
}
/// Sets the style of the [`TextInput`].
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
@@ -160,7 +168,7 @@ where
impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: text::Renderer,
{
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
/// [`Value`] if provided.
@@ -170,37 +178,165 @@ where
layout: Layout<'_>,
cursor_position: Point,
value: Option<&Value>,
- ) -> Renderer::Output {
+ ) {
let value = value.unwrap_or(&self.value);
+ let secure_value = self.is_secure.then(|| value.secure());
+ let value = secure_value.as_ref().unwrap_or(&value);
+
let bounds = layout.bounds();
let text_bounds = layout.children().next().unwrap().bounds();
- if self.is_secure {
- self::Renderer::draw(
- renderer,
- bounds,
- text_bounds,
- cursor_position,
- self.font,
- self.size.unwrap_or(renderer.default_size()),
- &self.placeholder,
- &value.secure(),
- &self.state,
- &self.style,
- )
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ let style = if self.state.is_focused() {
+ self.style_sheet.focused()
+ } else if is_mouse_over {
+ self.style_sheet.hovered()
} else {
- self::Renderer::draw(
- renderer,
+ self.style_sheet.active()
+ };
+
+ renderer.fill_quad(
+ renderer::Quad {
bounds,
- text_bounds,
- cursor_position,
- self.font,
- self.size.unwrap_or(renderer.default_size()),
- &self.placeholder,
- value,
- &self.state,
- &self.style,
- )
+ border_radius: style.border_radius,
+ border_width: style.border_width,
+ border_color: style.border_color,
+ },
+ style.background,
+ );
+
+ let text = value.to_string();
+ let size = self.size.unwrap_or(renderer.default_size());
+
+ let (cursor, offset) = if self.state.is_focused() {
+ match self.state.cursor.state(&value) {
+ cursor::State::Index(position) => {
+ let (text_value_width, offset) =
+ measure_cursor_and_scroll_offset(
+ renderer,
+ text_bounds,
+ &value,
+ size,
+ position,
+ self.font,
+ );
+
+ (
+ Some((
+ renderer::Quad {
+ bounds: Rectangle {
+ x: text_bounds.x + text_value_width,
+ y: text_bounds.y,
+ width: 1.0,
+ height: text_bounds.height,
+ },
+ border_radius: 0.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ self.style_sheet.value_color(),
+ )),
+ offset,
+ )
+ }
+ cursor::State::Selection { start, end } => {
+ let left = start.min(end);
+ let right = end.max(start);
+
+ let (left_position, left_offset) =
+ measure_cursor_and_scroll_offset(
+ renderer,
+ text_bounds,
+ &value,
+ size,
+ left,
+ self.font,
+ );
+
+ let (right_position, right_offset) =
+ measure_cursor_and_scroll_offset(
+ renderer,
+ text_bounds,
+ &value,
+ size,
+ right,
+ self.font,
+ );
+
+ let width = right_position - left_position;
+
+ (
+ Some((
+ renderer::Quad {
+ bounds: Rectangle {
+ x: text_bounds.x + left_position,
+ y: text_bounds.y,
+ width,
+ height: text_bounds.height,
+ },
+ border_radius: 0.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ self.style_sheet.selection_color(),
+ )),
+ if end == right {
+ right_offset
+ } else {
+ left_offset
+ },
+ )
+ }
+ }
+ } else {
+ (None, 0.0)
+ };
+
+ let text_width = renderer.measure_width(
+ if text.is_empty() {
+ &self.placeholder
+ } else {
+ &text
+ },
+ size,
+ self.font,
+ );
+
+ let render = |renderer: &mut Renderer| {
+ if let Some((cursor, color)) = cursor {
+ renderer.fill_quad(cursor, color);
+ }
+
+ renderer.fill_text(Text {
+ content: if text.is_empty() {
+ &self.placeholder
+ } else {
+ &text
+ },
+ color: if text.is_empty() {
+ self.style_sheet.placeholder_color()
+ } else {
+ self.style_sheet.value_color()
+ },
+ font: self.font,
+ bounds: Rectangle {
+ y: text_bounds.center_y(),
+ width: f32::INFINITY,
+ ..text_bounds
+ },
+ size: f32::from(size),
+ horizontal_alignment: alignment::Horizontal::Left,
+ vertical_alignment: alignment::Vertical::Center,
+ });
+ };
+
+ if text_width > text_bounds.width {
+ renderer.with_layer(text_bounds, |renderer| {
+ renderer.with_translation(Vector::new(-offset, 0.0), render)
+ });
+ } else {
+ render(renderer);
}
}
}
@@ -209,7 +345,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
for TextInput<'a, Message, Renderer>
where
Message: Clone,
- Renderer: self::Renderer,
+ Renderer: text::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -275,7 +411,8 @@ where
self.value.clone()
};
- renderer.find_cursor_position(
+ find_cursor_position(
+ renderer,
text_layout.bounds(),
self.font,
self.size,
@@ -294,16 +431,16 @@ where
if self.is_secure {
self.state.cursor.select_all(&self.value);
} else {
- let position = renderer
- .find_cursor_position(
- text_layout.bounds(),
- self.font,
- self.size,
- &self.value,
- &self.state,
- target,
- )
- .unwrap_or(0);
+ let position = find_cursor_position(
+ renderer,
+ text_layout.bounds(),
+ self.font,
+ self.size,
+ &self.value,
+ &self.state,
+ target,
+ )
+ .unwrap_or(0);
self.state.cursor.select_range(
self.value.previous_start_of_word(position),
@@ -341,16 +478,16 @@ where
self.value.clone()
};
- let position = renderer
- .find_cursor_position(
- text_layout.bounds(),
- self.font,
- self.size,
- &value,
- &self.state,
- target,
- )
- .unwrap_or(0);
+ let position = find_cursor_position(
+ renderer,
+ text_layout.bounds(),
+ self.font,
+ self.size,
+ &value,
+ &self.state,
+ target,
+ )
+ .unwrap_or(0);
self.state.cursor.select_range(
self.state.cursor.start(&value),
@@ -621,14 +758,27 @@ where
event::Status::Ignored
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ if layout.bounds().contains(cursor_position) {
+ mouse::Interaction::Text
+ } else {
+ mouse::Interaction::default()
+ }
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- _defaults: &Renderer::Defaults,
+ _style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
+ ) {
self.draw(renderer, layout, cursor_position, None)
}
@@ -644,87 +794,11 @@ where
}
}
-/// The renderer of a [`TextInput`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`TextInput`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: text::Renderer + Sized {
- /// The style supported by this renderer.
- type Style: Default;
-
- /// Returns the width of the value of the [`TextInput`].
- fn measure_value(&self, value: &str, size: u16, font: Self::Font) -> f32;
-
- /// Returns the current horizontal offset of the value of the
- /// [`TextInput`].
- ///
- /// This is the amount of horizontal scrolling applied when the [`Value`]
- /// does not fit the [`TextInput`].
- fn offset(
- &self,
- text_bounds: Rectangle,
- font: Self::Font,
- size: u16,
- value: &Value,
- state: &State,
- ) -> f32;
-
- /// Draws a [`TextInput`].
- ///
- /// It receives:
- /// - the bounds of the [`TextInput`]
- /// - the bounds of the text (i.e. the current value)
- /// - the cursor position
- /// - the placeholder to show when the value is empty
- /// - the current [`Value`]
- /// - the current [`State`]
- fn draw(
- &mut self,
- bounds: Rectangle,
- text_bounds: Rectangle,
- cursor_position: Point,
- font: Self::Font,
- size: u16,
- placeholder: &str,
- value: &Value,
- state: &State,
- style: &Self::Style,
- ) -> Self::Output;
-
- /// Computes the position of the text cursor at the given X coordinate of
- /// a [`TextInput`].
- fn find_cursor_position(
- &self,
- text_bounds: Rectangle,
- font: Self::Font,
- size: Option<u16>,
- value: &Value,
- state: &State,
- x: f32,
- ) -> Option<usize> {
- let size = size.unwrap_or(self.default_size());
-
- let offset = self.offset(text_bounds, font, size, &value, &state);
-
- self.hit_test(
- &value.to_string(),
- size.into(),
- font,
- Size::INFINITY,
- Point::new(x + offset, text_bounds.height / 2.0),
- true,
- )
- .map(text::Hit::cursor)
- }
-}
-
impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a + Clone,
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + text::Renderer,
{
fn from(
text_input: TextInput<'a, Message, Renderer>,
@@ -815,3 +889,88 @@ mod platform {
}
}
}
+
+fn offset<Renderer>(
+ renderer: &Renderer,
+ text_bounds: Rectangle,
+ font: Renderer::Font,
+ size: u16,
+ value: &Value,
+ state: &State,
+) -> f32
+where
+ Renderer: text::Renderer,
+{
+ if state.is_focused() {
+ let cursor = state.cursor();
+
+ let focus_position = match cursor.state(value) {
+ cursor::State::Index(i) => i,
+ cursor::State::Selection { end, .. } => end,
+ };
+
+ let (_, offset) = measure_cursor_and_scroll_offset(
+ renderer,
+ text_bounds,
+ value,
+ size,
+ focus_position,
+ font,
+ );
+
+ offset
+ } else {
+ 0.0
+ }
+}
+
+fn measure_cursor_and_scroll_offset<Renderer>(
+ renderer: &Renderer,
+ text_bounds: Rectangle,
+ value: &Value,
+ size: u16,
+ cursor_index: usize,
+ font: Renderer::Font,
+) -> (f32, f32)
+where
+ Renderer: text::Renderer,
+{
+ let text_before_cursor = value.until(cursor_index).to_string();
+
+ let text_value_width =
+ renderer.measure_width(&text_before_cursor, size, font);
+
+ let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0);
+
+ (text_value_width, offset)
+}
+
+/// Computes the position of the text cursor at the given X coordinate of
+/// a [`TextInput`].
+fn find_cursor_position<Renderer>(
+ renderer: &Renderer,
+ text_bounds: Rectangle,
+ font: Renderer::Font,
+ size: Option<u16>,
+ value: &Value,
+ state: &State,
+ x: f32,
+) -> Option<usize>
+where
+ Renderer: text::Renderer,
+{
+ let size = size.unwrap_or(renderer.default_size());
+
+ let offset = offset(renderer, text_bounds, font, size, &value, &state);
+
+ renderer
+ .hit_test(
+ &value.to_string(),
+ size.into(),
+ font,
+ Size::INFINITY,
+ Point::new(x + offset, text_bounds.height / 2.0),
+ true,
+ )
+ .map(text::Hit::cursor)
+}
diff --git a/native/src/widget/text_input/editor.rs b/native/src/widget/text_input/editor.rs
index 0b50a382..bac530e1 100644
--- a/native/src/widget/text_input/editor.rs
+++ b/native/src/widget/text_input/editor.rs
@@ -1,4 +1,4 @@
-use crate::text_input::{Cursor, Value};
+use crate::widget::text_input::{Cursor, Value};
pub struct Editor<'a> {
value: &'a mut Value,
diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs
index c624be4c..2dcc3ffe 100644
--- a/native/src/widget/toggler.rs
+++ b/native/src/widget/toggler.rs
@@ -5,19 +5,22 @@ use crate::alignment;
use crate::event;
use crate::layout;
use crate::mouse;
-use crate::row;
+use crate::renderer;
use crate::text;
+use crate::widget::{Row, Text};
use crate::{
Alignment, Clipboard, Element, Event, Hasher, Layout, Length, Point,
- Rectangle, Row, Text, Widget,
+ Rectangle, Widget,
};
+pub use iced_style::toggler::{Style, StyleSheet};
+
/// A toggler widget
///
/// # Example
///
/// ```
-/// # type Toggler<Message> = iced_native::Toggler<Message, iced_native::renderer::Null>;
+/// # type Toggler<'a, Message> = iced_native::widget::Toggler<'a, Message, iced_native::renderer::Null>;
/// #
/// pub enum Message {
/// TogglerToggled(bool),
@@ -28,7 +31,7 @@ use crate::{
/// Toggler::new(is_active, String::from("Toggle me!"), |b| Message::TogglerToggled(b));
/// ```
#[allow(missing_debug_implementations)]
-pub struct Toggler<Message, Renderer: self::Renderer + text::Renderer> {
+pub struct Toggler<'a, Message, Renderer: text::Renderer> {
is_active: bool,
on_toggle: Box<dyn Fn(bool) -> Message>,
label: Option<String>,
@@ -38,12 +41,13 @@ pub struct Toggler<Message, Renderer: self::Renderer + text::Renderer> {
text_alignment: alignment::Horizontal,
spacing: u16,
font: Renderer::Font,
- style: Renderer::Style,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
-impl<Message, Renderer: self::Renderer + text::Renderer>
- Toggler<Message, Renderer>
-{
+impl<'a, Message, Renderer: text::Renderer> Toggler<'a, Message, Renderer> {
+ /// The default size of a [`Toggler`].
+ pub const DEFAULT_SIZE: u16 = 20;
+
/// Creates a new [`Toggler`].
///
/// It expects:
@@ -65,12 +69,12 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
on_toggle: Box::new(f),
label: label.into(),
width: Length::Fill,
- size: <Renderer as self::Renderer>::DEFAULT_SIZE,
+ size: Self::DEFAULT_SIZE,
text_size: None,
text_alignment: alignment::Horizontal::Left,
spacing: 0,
font: Renderer::Font::default(),
- style: Renderer::Style::default(),
+ style_sheet: Default::default(),
}
}
@@ -111,15 +115,19 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
}
/// Sets the style of the [`Toggler`].
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
}
-impl<Message, Renderer> Widget<Message, Renderer> for Toggler<Message, Renderer>
+impl<'a, Message, Renderer> Widget<Message, Renderer>
+ for Toggler<'a, Message, Renderer>
where
- Renderer: self::Renderer + text::Renderer + row::Renderer,
+ Renderer: text::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -183,50 +191,108 @@ where
}
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ if layout.bounds().contains(cursor_position) {
+ mouse::Interaction::Pointer
+ } else {
+ mouse::Interaction::default()
+ }
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
- let bounds = layout.bounds();
- let mut children = layout.children();
+ ) {
+ /// Makes sure that the border radius of the toggler looks good at every size.
+ const BORDER_RADIUS_RATIO: f32 = 32.0 / 13.0;
- let label = match &self.label {
- Some(label) => {
- let label_layout = children.next().unwrap();
-
- Some(text::Renderer::draw(
- renderer,
- defaults,
- label_layout.bounds(),
- &label,
- self.text_size.unwrap_or(renderer.default_size()),
- self.font,
- None,
- self.text_alignment,
- alignment::Vertical::Center,
- ))
- }
+ /// The space ratio between the background Quad and the Toggler bounds, and
+ /// between the background Quad and foreground Quad.
+ const SPACE_RATIO: f32 = 0.05;
- None => None,
- };
+ let mut children = layout.children();
+
+ if let Some(label) = &self.label {
+ let label_layout = children.next().unwrap();
+
+ crate::widget::text::draw(
+ renderer,
+ style,
+ label_layout,
+ &label,
+ self.font,
+ self.text_size,
+ None,
+ self.text_alignment,
+ alignment::Vertical::Center,
+ );
+ }
let toggler_layout = children.next().unwrap();
- let toggler_bounds = toggler_layout.bounds();
+ let bounds = toggler_layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
- self::Renderer::draw(
- renderer,
- toggler_bounds,
- self.is_active,
- is_mouse_over,
- label,
- &self.style,
- )
+ let style = if is_mouse_over {
+ self.style_sheet.hovered(self.is_active)
+ } else {
+ self.style_sheet.active(self.is_active)
+ };
+
+ let border_radius = bounds.height as f32 / BORDER_RADIUS_RATIO;
+ let space = SPACE_RATIO * bounds.height as f32;
+
+ let toggler_background_bounds = Rectangle {
+ x: bounds.x + space,
+ y: bounds.y + space,
+ width: bounds.width - (2.0 * space),
+ height: bounds.height - (2.0 * space),
+ };
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: toggler_background_bounds,
+ border_radius,
+ border_width: 1.0,
+ border_color: style
+ .background_border
+ .unwrap_or(style.background),
+ },
+ style.background,
+ );
+
+ let toggler_foreground_bounds = Rectangle {
+ x: bounds.x
+ + if self.is_active {
+ bounds.width - 2.0 * space - (bounds.height - (4.0 * space))
+ } else {
+ 2.0 * space
+ },
+ y: bounds.y + (2.0 * space),
+ width: bounds.height - (4.0 * space),
+ height: bounds.height - (4.0 * space),
+ };
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: toggler_foreground_bounds,
+ border_radius,
+ border_width: 1.0,
+ border_color: style
+ .foreground_border
+ .unwrap_or(style.foreground),
+ },
+ style.foreground,
+ );
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -237,45 +303,14 @@ where
}
}
-/// The renderer of a [`Toggler`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Toggler`] in your user interface.
-///
-/// [renderer]: ../../renderer/index.html
-pub trait Renderer: crate::Renderer {
- /// The style supported by this renderer.
- type Style: Default;
-
- /// The default size of a [`Toggler`].
- const DEFAULT_SIZE: u16;
-
- /// Draws a [`Toggler`].
- ///
- /// It receives:
- /// * the bounds of the [`Toggler`]
- /// * whether the [`Toggler`] is activated or not
- /// * whether the mouse is over the [`Toggler`] or not
- /// * the drawn label of the [`Toggler`]
- /// * the style of the [`Toggler`]
- fn draw(
- &mut self,
- bounds: Rectangle,
- is_active: bool,
- is_mouse_over: bool,
- label: Option<Self::Output>,
- style: &Self::Style,
- ) -> Self::Output;
-}
-
-impl<'a, Message, Renderer> From<Toggler<Message, Renderer>>
+impl<'a, Message, Renderer> From<Toggler<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer + text::Renderer + row::Renderer,
+ Renderer: 'a + text::Renderer,
Message: 'a,
{
fn from(
- toggler: Toggler<Message, Renderer>,
+ toggler: Toggler<'a, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(toggler)
}
diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs
index 276afd41..c35005e0 100644
--- a/native/src/widget/tooltip.rs
+++ b/native/src/widget/tooltip.rs
@@ -3,28 +3,36 @@ use std::hash::Hash;
use iced_core::Rectangle;
+use crate::event;
+use crate::layout;
+use crate::mouse;
+use crate::renderer;
+use crate::text;
use crate::widget::container;
-use crate::widget::text::{self, Text};
+use crate::widget::text::Text;
use crate::{
- event, layout, Clipboard, Element, Event, Hasher, Layout, Length, Point,
- Widget,
+ Clipboard, Element, Event, Hasher, Layout, Length, Padding, Point, Size,
+ Vector, Widget,
};
/// An element to display a widget over another.
#[allow(missing_debug_implementations)]
-pub struct Tooltip<'a, Message, Renderer: self::Renderer> {
+pub struct Tooltip<'a, Message, Renderer: text::Renderer> {
content: Element<'a, Message, Renderer>,
tooltip: Text<Renderer>,
position: Position,
- style: <Renderer as container::Renderer>::Style,
+ style_sheet: Box<dyn container::StyleSheet + 'a>,
gap: u16,
padding: u16,
}
impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: text::Renderer,
{
+ /// The default padding of a [`Tooltip`] drawn by this renderer.
+ const DEFAULT_PADDING: u16 = 5;
+
/// Creates an empty [`Tooltip`].
///
/// [`Tooltip`]: struct.Tooltip.html
@@ -37,9 +45,9 @@ where
content: content.into(),
tooltip: Text::new(tooltip.to_string()),
position,
- style: Default::default(),
+ style_sheet: Default::default(),
gap: 0,
- padding: Renderer::DEFAULT_PADDING,
+ padding: Self::DEFAULT_PADDING,
}
}
@@ -72,9 +80,9 @@ where
/// Sets the style of the [`Tooltip`].
pub fn style(
mut self,
- style: impl Into<<Renderer as container::Renderer>::Style>,
+ style_sheet: impl Into<Box<dyn container::StyleSheet + 'a>>,
) -> Self {
- self.style = style.into();
+ self.style_sheet = style_sheet.into();
self
}
}
@@ -97,7 +105,7 @@ pub enum Position {
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Tooltip<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: text::Renderer,
{
fn width(&self) -> Length {
self.content.width()
@@ -134,27 +142,126 @@ where
)
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ self.content
+ .mouse_interaction(layout, cursor_position, viewport)
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ inherited_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
- ) -> Renderer::Output {
- self::Renderer::draw(
+ ) {
+ self.content.draw(
renderer,
- defaults,
- cursor_position,
+ inherited_style,
layout,
+ cursor_position,
viewport,
- &self.content,
- &self.tooltip,
- self.position,
- &self.style,
- self.gap,
- self.padding,
- )
+ );
+
+ let bounds = layout.bounds();
+
+ if bounds.contains(cursor_position) {
+ let gap = f32::from(self.gap);
+ let style = self.style_sheet.style();
+
+ let defaults = renderer::Style {
+ text_color: style
+ .text_color
+ .unwrap_or(inherited_style.text_color),
+ };
+
+ let text_layout = Widget::<(), Renderer>::layout(
+ &self.tooltip,
+ renderer,
+ &layout::Limits::new(Size::ZERO, viewport.size())
+ .pad(Padding::new(self.padding)),
+ );
+
+ let padding = f32::from(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 mut tooltip_bounds = {
+ let offset = match self.position {
+ Position::Top => Vector::new(
+ x_center,
+ bounds.y - text_bounds.height - gap - padding,
+ ),
+ Position::Bottom => Vector::new(
+ x_center,
+ bounds.y + bounds.height + gap + padding,
+ ),
+ Position::Left => Vector::new(
+ bounds.x - text_bounds.width - gap - padding,
+ y_center,
+ ),
+ Position::Right => Vector::new(
+ bounds.x + bounds.width + gap + padding,
+ y_center,
+ ),
+ Position::FollowCursor => Vector::new(
+ cursor_position.x,
+ 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,
+ }
+ };
+
+ if tooltip_bounds.x < viewport.x {
+ tooltip_bounds.x = viewport.x;
+ } else if viewport.x + viewport.width
+ < tooltip_bounds.x + tooltip_bounds.width
+ {
+ tooltip_bounds.x =
+ viewport.x + viewport.width - tooltip_bounds.width;
+ }
+
+ if tooltip_bounds.y < viewport.y {
+ tooltip_bounds.y = viewport.y;
+ } else if viewport.y + viewport.height
+ < tooltip_bounds.y + tooltip_bounds.height
+ {
+ tooltip_bounds.y =
+ viewport.y + viewport.height - tooltip_bounds.height;
+ }
+
+ renderer.with_layer(*viewport, |renderer| {
+ container::draw_background(renderer, &style, tooltip_bounds);
+
+ Widget::<(), Renderer>::draw(
+ &self.tooltip,
+ renderer,
+ &defaults,
+ Layout::with_offset(
+ Vector::new(
+ tooltip_bounds.x + padding,
+ tooltip_bounds.y + padding,
+ ),
+ &text_layout,
+ ),
+ cursor_position,
+ viewport,
+ );
+ });
+ }
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -165,41 +272,10 @@ where
}
}
-/// The renderer of a [`Tooltip`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Tooltip`] in your user interface.
-///
-/// [`Tooltip`]: struct.Tooltip.html
-/// [renderer]: ../../renderer/index.html
-pub trait Renderer:
- crate::Renderer + text::Renderer + container::Renderer
-{
- /// The default padding of a [`Tooltip`] drawn by this renderer.
- const DEFAULT_PADDING: u16;
-
- /// Draws a [`Tooltip`].
- ///
- /// [`Tooltip`]: struct.Tooltip.html
- fn draw<Message>(
- &mut self,
- defaults: &Self::Defaults,
- cursor_position: Point,
- content_layout: Layout<'_>,
- viewport: &Rectangle,
- content: &Element<'_, Message, Self>,
- tooltip: &Text<Self>,
- position: Position,
- style: &<Self as container::Renderer>::Style,
- gap: u16,
- padding: u16,
- ) -> Self::Output;
-}
-
impl<'a, Message, Renderer> From<Tooltip<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + text::Renderer,
Message: 'a,
{
fn from(