summaryrefslogtreecommitdiffstats
path: root/native/src/widget
diff options
context:
space:
mode:
Diffstat (limited to 'native/src/widget')
-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
26 files changed, 1916 insertions, 1443 deletions
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(