summaryrefslogtreecommitdiffstats
path: root/native/src/widget
diff options
context:
space:
mode:
Diffstat (limited to 'native/src/widget')
-rw-r--r--native/src/widget/button.rs143
-rw-r--r--native/src/widget/checkbox.rs59
-rw-r--r--native/src/widget/column.rs76
-rw-r--r--native/src/widget/container.rs66
-rw-r--r--native/src/widget/image.rs34
-rw-r--r--native/src/widget/image/viewer.rs413
-rw-r--r--native/src/widget/pane_grid.rs451
-rw-r--r--native/src/widget/pane_grid/configuration.rs12
-rw-r--r--native/src/widget/pane_grid/content.rs85
-rw-r--r--native/src/widget/pane_grid/node.rs36
-rw-r--r--native/src/widget/pane_grid/pane.rs4
-rw-r--r--native/src/widget/pane_grid/split.rs4
-rw-r--r--native/src/widget/pane_grid/state.rs193
-rw-r--r--native/src/widget/pane_grid/title_bar.rs238
-rw-r--r--native/src/widget/pick_list.rs185
-rw-r--r--native/src/widget/progress_bar.rs16
-rw-r--r--native/src/widget/radio.rs76
-rw-r--r--native/src/widget/row.rs81
-rw-r--r--native/src/widget/rule.rs11
-rw-r--r--native/src/widget/scrollable.rs373
-rw-r--r--native/src/widget/slider.rs96
-rw-r--r--native/src/widget/space.rs11
-rw-r--r--native/src/widget/svg.rs35
-rw-r--r--native/src/widget/text.rs37
-rw-r--r--native/src/widget/text_input.rs534
-rw-r--r--native/src/widget/text_input/cursor.rs30
-rw-r--r--native/src/widget/text_input/editor.rs4
-rw-r--r--native/src/widget/text_input/value.rs34
-rw-r--r--native/src/widget/toggler.rs277
-rw-r--r--native/src/widget/tooltip.rs210
30 files changed, 2312 insertions, 1512 deletions
diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs
index c932da2b..c469a0e5 100644
--- a/native/src/widget/button.rs
+++ b/native/src/widget/button.rs
@@ -1,12 +1,14 @@
//! Allow your users to perform actions by pressing a button.
//!
//! A [`Button`] has some local [`State`].
-//!
-//! [`Button`]: struct.Button.html
-//! [`State`]: struct.State.html
+use crate::event::{self, Event};
+use crate::layout;
+use crate::mouse;
+use crate::overlay;
+use crate::touch;
use crate::{
- layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, Point,
- Rectangle, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
+ Widget,
};
use std::hash::Hash;
@@ -18,6 +20,7 @@ use std::hash::Hash;
/// # type Button<'a, Message> =
/// # iced_native::Button<'a, Message, iced_native::renderer::Null>;
/// #
+/// #[derive(Clone)]
/// enum Message {
/// ButtonPressed,
/// }
@@ -26,6 +29,29 @@ use std::hash::Hash;
/// let button = Button::new(&mut state, Text::new("Press me!"))
/// .on_press(Message::ButtonPressed);
/// ```
+///
+/// If a [`Button::on_press`] handler is not set, the resulting [`Button`] will
+/// be disabled:
+///
+/// ```
+/// # use iced_native::{button, Text};
+/// #
+/// # type Button<'a, Message> =
+/// # iced_native::Button<'a, Message, iced_native::renderer::Null>;
+/// #
+/// #[derive(Clone)]
+/// enum Message {
+/// ButtonPressed,
+/// }
+///
+/// fn disabled_button(state: &mut button::State) -> Button<'_, Message> {
+/// Button::new(state, Text::new("I'm disabled!"))
+/// }
+///
+/// fn enabled_button(state: &mut button::State) -> Button<'_, Message> {
+/// disabled_button(state).on_press(Message::ButtonPressed)
+/// }
+/// ```
#[allow(missing_debug_implementations)]
pub struct Button<'a, Message, Renderer: self::Renderer> {
state: &'a mut State,
@@ -35,19 +61,17 @@ pub struct Button<'a, Message, Renderer: self::Renderer> {
height: Length,
min_width: u32,
min_height: u32,
- padding: u16,
+ padding: Padding,
style: Renderer::Style,
}
impl<'a, Message, Renderer> Button<'a, Message, Renderer>
where
+ Message: Clone,
Renderer: self::Renderer,
{
/// Creates a new [`Button`] with some local [`State`] and the given
/// content.
- ///
- /// [`Button`]: struct.Button.html
- /// [`State`]: struct.State.html
pub fn new<E>(state: &'a mut State, content: E) -> Self
where
E: Into<Element<'a, Message, Renderer>>,
@@ -66,56 +90,43 @@ where
}
/// Sets the width of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the minimum width of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
pub fn min_width(mut self, min_width: u32) -> Self {
self.min_width = min_width;
self
}
/// Sets the minimum height of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
pub fn min_height(mut self, min_height: u32) -> Self {
self.min_height = min_height;
self
}
- /// Sets the padding of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
- pub fn padding(mut self, padding: u16) -> Self {
- self.padding = padding;
+ /// Sets the [`Padding`] of the [`Button`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
/// Sets the message that will be produced when the [`Button`] is pressed.
- ///
- /// [`Button`]: struct.Button.html
+ /// If on_press isn't set, button will be disabled.
pub fn on_press(mut self, msg: Message) -> Self {
self.on_press = Some(msg);
self
}
/// Sets the style of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
@@ -123,8 +134,6 @@ where
}
/// The local state of a [`Button`].
-///
-/// [`Button`]: struct.Button.html
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct State {
is_pressed: bool,
@@ -132,8 +141,6 @@ pub struct State {
impl State {
/// Creates a new [`State`].
- ///
- /// [`State`]: struct.State.html
pub fn new() -> State {
State::default()
}
@@ -142,8 +149,8 @@ impl State {
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Button<'a, Message, Renderer>
where
- Renderer: self::Renderer,
Message: Clone,
+ Renderer: self::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -158,18 +165,20 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let padding = f32::from(self.padding);
let limits = limits
.min_width(self.min_width)
.min_height(self.min_height)
.width(self.width)
.height(self.height)
- .pad(padding);
+ .pad(self.padding);
let mut content = self.content.layout(renderer, &limits);
- content.move_to(Point::new(padding, padding));
+ content.move_to(Point::new(
+ self.padding.left.into(),
+ self.padding.top.into(),
+ ));
- let size = limits.resolve(content.size()).pad(padding);
+ let size = limits.resolve(content.size()).pad(self.padding);
layout::Node::with_children(size, vec![content])
}
@@ -179,34 +188,57 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
messages: &mut Vec<Message>,
- _renderer: &Renderer,
- _clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
+ if let event::Status::Captured = self.content.on_event(
+ event.clone(),
+ layout.children().next().unwrap(),
+ cursor_position,
+ renderer,
+ clipboard,
+ messages,
+ ) {
+ return event::Status::Captured;
+ }
+
match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
if self.on_press.is_some() {
let bounds = layout.bounds();
- self.state.is_pressed = bounds.contains(cursor_position);
+ if bounds.contains(cursor_position) {
+ self.state.is_pressed = true;
+
+ return event::Status::Captured;
+ }
}
}
- Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerLifted { .. }) => {
if let Some(on_press) = self.on_press.clone() {
let bounds = layout.bounds();
- let is_clicked = self.state.is_pressed
- && bounds.contains(cursor_position);
+ if self.state.is_pressed {
+ self.state.is_pressed = false;
- self.state.is_pressed = false;
+ if bounds.contains(cursor_position) {
+ messages.push(on_press);
+ }
- if is_clicked {
- messages.push(on_press);
+ return event::Status::Captured;
}
}
}
+ Event::Touch(touch::Event::FingerLost { .. }) => {
+ self.state.is_pressed = false;
+ }
_ => {}
}
+
+ event::Status::Ignored
}
fn draw(
@@ -215,6 +247,7 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(
defaults,
@@ -235,6 +268,13 @@ where
self.width.hash(state);
self.content.hash_layout(state);
}
+
+ fn overlay(
+ &mut self,
+ layout: Layout<'_>,
+ ) -> Option<overlay::Element<'_, Message, Renderer>> {
+ self.content.overlay(layout.children().next().unwrap())
+ }
}
/// The renderer of a [`Button`].
@@ -242,20 +282,15 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Button`] in your user interface.
///
-/// [`Button`]: struct.Button.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer + Sized {
/// The default padding of a [`Button`].
- ///
- /// [`Button`]: struct.Button.html
- const DEFAULT_PADDING: u16;
+ const DEFAULT_PADDING: Padding;
/// The style supported by this renderer.
type Style: Default;
/// Draws a [`Button`].
- ///
- /// [`Button`]: struct.Button.html
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
@@ -272,8 +307,8 @@ pub trait Renderer: crate::Renderer + Sized {
impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer,
Message: 'a + Clone,
+ Renderer: 'a + self::Renderer,
{
fn from(
button: Button<'a, Message, Renderer>,
diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs
index 99178aae..0f21c873 100644
--- a/native/src/widget/checkbox.rs
+++ b/native/src/widget/checkbox.rs
@@ -1,10 +1,15 @@
//! Show toggle controls using checkboxes.
use std::hash::Hash;
+use crate::event::{self, Event};
+use crate::layout;
+use crate::mouse;
+use crate::row;
+use crate::text;
+use crate::touch;
use crate::{
- layout, mouse, row, text, Align, Clipboard, Element, Event, Hasher,
- HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text,
- VerticalAlignment, Widget,
+ Align, Clipboard, Color, Element, Hasher, HorizontalAlignment, Layout,
+ Length, Point, Rectangle, Row, Text, VerticalAlignment, Widget,
};
/// A box that can be checked.
@@ -34,6 +39,7 @@ pub struct Checkbox<Message, Renderer: self::Renderer + text::Renderer> {
spacing: u16,
text_size: Option<u16>,
font: Renderer::Font,
+ text_color: Option<Color>,
style: Renderer::Style,
}
@@ -48,8 +54,6 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
/// * a function that will be called when the [`Checkbox`] is toggled. It
/// will receive the new state of the [`Checkbox`] and must produce a
/// `Message`.
- ///
- /// [`Checkbox`]: struct.Checkbox.html
pub fn new<F>(is_checked: bool, label: impl Into<String>, f: F) -> Self
where
F: 'static + Fn(bool) -> Message,
@@ -63,37 +67,30 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
spacing: Renderer::DEFAULT_SPACING,
text_size: None,
font: Renderer::Font::default(),
+ text_color: None,
style: Renderer::Style::default(),
}
}
/// Sets the size of the [`Checkbox`].
- ///
- /// [`Checkbox`]: struct.Checkbox.html
pub fn size(mut self, size: u16) -> Self {
self.size = size;
self
}
/// Sets the width of the [`Checkbox`].
- ///
- /// [`Checkbox`]: struct.Checkbox.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the spacing between the [`Checkbox`] and the text.
- ///
- /// [`Checkbox`]: struct.Checkbox.html
pub fn spacing(mut self, spacing: u16) -> Self {
self.spacing = spacing;
self
}
/// Sets the text size of the [`Checkbox`].
- ///
- /// [`Checkbox`]: struct.Checkbox.html
pub fn text_size(mut self, text_size: u16) -> Self {
self.text_size = Some(text_size);
self
@@ -101,16 +98,19 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
/// Sets the [`Font`] of the text of the [`Checkbox`].
///
- /// [`Checkbox`]: struct.Checkbox.html
- /// [`Font`]: ../../struct.Font.html
+ /// [`Font`]: crate::widget::text::Renderer::Font
pub fn font(mut self, font: Renderer::Font) -> Self {
self.font = font;
self
}
+ /// Sets the text color of the [`Checkbox`] button.
+ pub fn text_color(mut self, color: Color) -> Self {
+ self.text_color = Some(color);
+ self
+ }
+
/// Sets the style of the [`Checkbox`].
- ///
- /// [`Checkbox`]: struct.Checkbox.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
@@ -158,20 +158,25 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
_renderer: &Renderer,
- _clipboard: Option<&dyn Clipboard>,
- ) {
+ _clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
let mouse_over = layout.bounds().contains(cursor_position);
if mouse_over {
messages.push((self.on_toggle)(!self.is_checked));
+
+ return event::Status::Captured;
}
}
_ => {}
}
+
+ event::Status::Ignored
}
fn draw(
@@ -180,6 +185,7 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
let bounds = layout.bounds();
let mut children = layout.children();
@@ -195,7 +201,7 @@ where
&self.label,
self.text_size.unwrap_or(renderer.default_size()),
self.font,
- None,
+ self.text_color,
HorizontalAlignment::Left,
VerticalAlignment::Center,
);
@@ -225,20 +231,15 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Checkbox`] in your user interface.
///
-/// [`Checkbox`]: struct.Checkbox.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::Renderer
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
/// The default size of a [`Checkbox`].
- ///
- /// [`Checkbox`]: struct.Checkbox.html
const DEFAULT_SIZE: u16;
/// The default spacing of a [`Checkbox`].
- ///
- /// [`Checkbox`]: struct.Checkbox.html
const DEFAULT_SPACING: u16;
/// Draws a [`Checkbox`].
@@ -248,8 +249,6 @@ pub trait Renderer: crate::Renderer {
/// * whether the [`Checkbox`] is selected or not
/// * whether the mouse is over the [`Checkbox`] or not
/// * the drawn label of the [`Checkbox`]
- ///
- /// [`Checkbox`]: struct.Checkbox.html
fn draw(
&mut self,
bounds: Rectangle,
diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs
index 42cfe9b9..52a2e80c 100644
--- a/native/src/widget/column.rs
+++ b/native/src/widget/column.rs
@@ -1,22 +1,21 @@
//! Distribute content vertically.
use std::hash::Hash;
+use crate::event::{self, Event};
+use crate::layout;
+use crate::overlay;
use crate::{
- layout, overlay, Align, Clipboard, Element, Event, Hasher, Layout, Length,
- Point, Widget,
+ Align, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
+ Rectangle, Widget,
};
use std::u32;
/// A container that distributes its contents vertically.
-///
-/// A [`Column`] will try to fill the horizontal space of its container.
-///
-/// [`Column`]: struct.Column.html
#[allow(missing_debug_implementations)]
pub struct Column<'a, Message, Renderer> {
spacing: u16,
- padding: u16,
+ padding: Padding,
width: Length,
height: Length,
max_width: u32,
@@ -27,21 +26,17 @@ pub struct Column<'a, Message, Renderer> {
impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
/// Creates an empty [`Column`].
- ///
- /// [`Column`]: struct.Column.html
pub fn new() -> Self {
Self::with_children(Vec::new())
}
/// Creates a [`Column`] with the given elements.
- ///
- /// [`Column`]: struct.Column.html
pub fn with_children(
children: Vec<Element<'a, Message, Renderer>>,
) -> Self {
Column {
spacing: 0,
- padding: 0,
+ padding: Padding::ZERO,
width: Length::Shrink,
height: Length::Shrink,
max_width: u32::MAX,
@@ -61,57 +56,43 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
self
}
- /// Sets the padding of the [`Column`].
- ///
- /// [`Column`]: struct.Column.html
- pub fn padding(mut self, units: u16) -> Self {
- self.padding = units;
+ /// Sets the [`Padding`] of the [`Column`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
/// Sets the width of the [`Column`].
- ///
- /// [`Column`]: struct.Column.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Column`].
- ///
- /// [`Column`]: struct.Column.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Column`].
- ///
- /// [`Column`]: struct.Column.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
self
}
/// Sets the maximum height of the [`Column`] in pixels.
- ///
- /// [`Column`]: struct.Column.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the horizontal alignment of the contents of the [`Column`] .
- ///
- /// [`Column`]: struct.Column.html
pub fn align_items(mut self, align: Align) -> Self {
self.align_items = align;
self
}
/// Adds an element to the [`Column`].
- ///
- /// [`Column`]: struct.Column.html
pub fn push<E>(mut self, child: E) -> Self
where
E: Into<Element<'a, Message, Renderer>>,
@@ -149,7 +130,7 @@ where
layout::flex::Axis::Vertical,
renderer,
&limits,
- self.padding as f32,
+ self.padding,
self.spacing as f32,
self.align_items,
&self.children,
@@ -161,22 +142,24 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
renderer: &Renderer,
- clipboard: Option<&dyn Clipboard>,
- ) {
- self.children.iter_mut().zip(layout.children()).for_each(
- |(child, layout)| {
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
+ self.children
+ .iter_mut()
+ .zip(layout.children())
+ .map(|(child, layout)| {
child.widget.on_event(
event.clone(),
layout,
cursor_position,
- messages,
renderer,
clipboard,
+ messages,
)
- },
- );
+ })
+ .fold(event::Status::Ignored, event::Status::merge)
}
fn draw(
@@ -185,8 +168,15 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Renderer::Output {
- renderer.draw(defaults, &self.children, layout, cursor_position)
+ renderer.draw(
+ defaults,
+ &self.children,
+ layout,
+ cursor_position,
+ viewport,
+ )
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -199,6 +189,7 @@ where
self.max_height.hash(state);
self.align_items.hash(state);
self.spacing.hash(state);
+ self.padding.hash(state);
for child in &self.children {
child.widget.hash_layout(state);
@@ -222,8 +213,7 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Column`] in your user interface.
///
-/// [`Column`]: struct.Column.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer + Sized {
/// Draws a [`Column`].
///
@@ -231,15 +221,13 @@ pub trait Renderer: crate::Renderer + Sized {
/// - the children of the [`Column`]
/// - the [`Layout`] of the [`Column`] and its children
/// - the cursor position
- ///
- /// [`Column`]: struct.Column.html
- /// [`Layout`]: ../layout/struct.Layout.html
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
content: &[Element<'_, Message, Self>],
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Self::Output;
}
diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs
index b8316e62..69aee64d 100644
--- a/native/src/widget/container.rs
+++ b/native/src/widget/container.rs
@@ -1,9 +1,12 @@
//! Decorate content and apply alignment.
use std::hash::Hash;
+use crate::event::{self, Event};
+use crate::layout;
+use crate::overlay;
use crate::{
- layout, overlay, Align, Clipboard, Element, Event, Hasher, Layout, Length,
- Point, Rectangle, Widget,
+ Align, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
+ Rectangle, Widget,
};
use std::u32;
@@ -13,7 +16,7 @@ use std::u32;
/// It is normally used for alignment purposes.
#[allow(missing_debug_implementations)]
pub struct Container<'a, Message, Renderer: self::Renderer> {
- padding: u16,
+ padding: Padding,
width: Length,
height: Length,
max_width: u32,
@@ -29,14 +32,12 @@ where
Renderer: self::Renderer,
{
/// Creates an empty [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn new<T>(content: T) -> Self
where
T: Into<Element<'a, Message, Renderer>>,
{
Container {
- padding: 0,
+ padding: Padding::ZERO,
width: Length::Shrink,
height: Length::Shrink,
max_width: u32::MAX,
@@ -48,81 +49,61 @@ where
}
}
- /// Sets the padding of the [`Container`].
- ///
- /// [`Container`]: struct.Column.html
- pub fn padding(mut self, units: u16) -> Self {
- self.padding = units;
+ /// Sets the [`Padding`] of the [`Container`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
/// Sets the width of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
self
}
/// Sets the maximum height of the [`Container`] in pixels.
- ///
- /// [`Container`]: struct.Container.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the content alignment for the horizontal axis of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn align_x(mut self, alignment: Align) -> Self {
self.horizontal_alignment = alignment;
self
}
/// Sets the content alignment for the vertical axis of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn align_y(mut self, alignment: Align) -> Self {
self.vertical_alignment = alignment;
self
}
/// Centers the contents in the horizontal axis of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn center_x(mut self) -> Self {
self.horizontal_alignment = Align::Center;
self
}
/// Centers the contents in the vertical axis of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn center_y(mut self) -> Self {
self.vertical_alignment = Align::Center;
self
}
/// Sets the style of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
@@ -147,23 +128,24 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let padding = f32::from(self.padding);
-
let limits = limits
.loose()
.max_width(self.max_width)
.max_height(self.max_height)
.width(self.width)
.height(self.height)
- .pad(padding);
+ .pad(self.padding);
let mut content = self.content.layout(renderer, &limits.loose());
let size = limits.resolve(content.size());
- content.move_to(Point::new(padding, padding));
+ content.move_to(Point::new(
+ self.padding.left.into(),
+ self.padding.top.into(),
+ ));
content.align(self.horizontal_alignment, self.vertical_alignment, size);
- layout::Node::with_children(size.pad(padding), vec![content])
+ layout::Node::with_children(size.pad(self.padding), vec![content])
}
fn on_event(
@@ -171,17 +153,17 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
renderer: &Renderer,
- clipboard: Option<&dyn Clipboard>,
- ) {
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
self.content.widget.on_event(
event,
layout.children().next().unwrap(),
cursor_position,
- messages,
renderer,
clipboard,
+ messages,
)
}
@@ -191,11 +173,13 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(
defaults,
layout.bounds(),
cursor_position,
+ viewport,
&self.style,
&self.content,
layout.children().next().unwrap(),
@@ -228,20 +212,18 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Container`] in your user interface.
///
-/// [`Container`]: struct.Container.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
/// Draws a [`Container`].
- ///
- /// [`Container`]: struct.Container.html
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<'_>,
diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs
index 132f249d..4d8e0a3f 100644
--- a/native/src/widget/image.rs
+++ b/native/src/widget/image.rs
@@ -1,5 +1,9 @@
//! Display images in your user interface.
-use crate::{layout, Element, Hasher, Layout, Length, Point, Size, Widget};
+pub mod viewer;
+pub use viewer::Viewer;
+
+use crate::layout;
+use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
use std::{
hash::{Hash, Hasher as _},
@@ -27,8 +31,6 @@ pub struct Image {
impl Image {
/// Creates a new [`Image`] with the given path.
- ///
- /// [`Image`]: struct.Image.html
pub fn new<T: Into<Handle>>(handle: T) -> Self {
Image {
handle: handle.into(),
@@ -38,16 +40,12 @@ impl Image {
}
/// Sets the width of the [`Image`] boundaries.
- ///
- /// [`Image`]: struct.Image.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Image`] boundaries.
- ///
- /// [`Image`]: struct.Image.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
@@ -97,6 +95,7 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(self.handle.clone(), layout)
}
@@ -112,8 +111,6 @@ where
}
/// An [`Image`] handle.
-///
-/// [`Image`]: struct.Image.html
#[derive(Debug, Clone)]
pub struct Handle {
id: u64,
@@ -124,8 +121,6 @@ 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.
- ///
- /// [`Handle`]: struct.Handle.html
pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle {
Self::from_data(Data::Path(path.into()))
}
@@ -135,8 +130,6 @@ impl Handle {
/// pixels.
///
/// This is useful if you have already decoded your image.
- ///
- /// [`Handle`]: struct.Handle.html
pub fn from_pixels(width: u32, height: u32, pixels: Vec<u8>) -> Handle {
Self::from_data(Data::Pixels {
width,
@@ -151,8 +144,6 @@ impl Handle {
///
/// This is useful if you already have your image loaded in-memory, maybe
/// because you downloaded or generated it procedurally.
- ///
- /// [`Handle`]: struct.Handle.html
pub fn from_memory(bytes: Vec<u8>) -> Handle {
Self::from_data(Data::Bytes(bytes))
}
@@ -168,15 +159,11 @@ impl Handle {
}
/// Returns the unique identifier of the [`Handle`].
- ///
- /// [`Handle`]: struct.Handle.html
pub fn id(&self) -> u64 {
self.id
}
/// Returns a reference to the image [`Data`].
- ///
- /// [`Data`]: enum.Data.html
pub fn data(&self) -> &Data {
&self.data
}
@@ -198,8 +185,6 @@ impl Hash for Handle {
}
/// The data of an [`Image`].
-///
-/// [`Image`]: struct.Image.html
#[derive(Clone, Hash)]
pub enum Data {
/// File data
@@ -236,17 +221,12 @@ impl std::fmt::Debug for Data {
/// Your [renderer] will need to implement this trait before being able to use
/// an [`Image`] in your user interface.
///
-/// [`Image`]: struct.Image.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// Returns the dimensions of an [`Image`] located on the given path.
- ///
- /// [`Image`]: struct.Image.html
fn dimensions(&self, handle: &Handle) -> (u32, u32);
/// Draws an [`Image`].
- ///
- /// [`Image`]: struct.Image.html
fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output;
}
diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs
new file mode 100644
index 00000000..405daf00
--- /dev/null
+++ b/native/src/widget/image/viewer.rs
@@ -0,0 +1,413 @@
+//! Zoom and pan on an image.
+use crate::event::{self, Event};
+use crate::image;
+use crate::layout;
+use crate::mouse;
+use crate::{
+ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
+ Widget,
+};
+
+use std::hash::Hash;
+
+/// A frame that displays an image with the ability to zoom in/out and pan.
+#[allow(missing_debug_implementations)]
+pub struct Viewer<'a> {
+ state: &'a mut State,
+ padding: u16,
+ width: Length,
+ height: Length,
+ min_scale: f32,
+ max_scale: f32,
+ scale_step: f32,
+ handle: image::Handle,
+}
+
+impl<'a> Viewer<'a> {
+ /// Creates a new [`Viewer`] with the given [`State`] and [`Handle`].
+ ///
+ /// [`Handle`]: image::Handle
+ pub fn new(state: &'a mut State, handle: image::Handle) -> Self {
+ Viewer {
+ state,
+ padding: 0,
+ width: Length::Shrink,
+ height: Length::Shrink,
+ min_scale: 0.25,
+ max_scale: 10.0,
+ scale_step: 0.10,
+ handle,
+ }
+ }
+
+ /// Sets the padding of the [`Viewer`].
+ pub fn padding(mut self, units: u16) -> Self {
+ self.padding = units;
+ self
+ }
+
+ /// Sets the width of the [`Viewer`].
+ pub fn width(mut self, width: Length) -> Self {
+ self.width = width;
+ self
+ }
+
+ /// Sets the height of the [`Viewer`].
+ pub fn height(mut self, height: Length) -> Self {
+ self.height = height;
+ self
+ }
+
+ /// Sets the max scale applied to the image of the [`Viewer`].
+ ///
+ /// Default is `10.0`
+ pub fn max_scale(mut self, max_scale: f32) -> Self {
+ self.max_scale = max_scale;
+ self
+ }
+
+ /// Sets the min scale applied to the image of the [`Viewer`].
+ ///
+ /// Default is `0.25`
+ pub fn min_scale(mut self, min_scale: f32) -> Self {
+ self.min_scale = min_scale;
+ self
+ }
+
+ /// Sets the percentage the image of the [`Viewer`] will be scaled by
+ /// when zoomed in / out.
+ ///
+ /// Default is `0.10`
+ pub fn scale_step(mut self, scale_step: f32) -> Self {
+ self.scale_step = scale_step;
+ self
+ }
+
+ /// Returns the bounds of the underlying image, given the bounds of
+ /// the [`Viewer`]. Scaling will be applied and original aspect ratio
+ /// will be respected.
+ fn image_size<Renderer>(&self, renderer: &Renderer, bounds: Size) -> Size
+ where
+ Renderer: self::Renderer + image::Renderer,
+ {
+ let (width, height) = renderer.dimensions(&self.handle);
+
+ let (width, height) = {
+ let dimensions = (width as f32, height as f32);
+
+ let width_ratio = bounds.width / dimensions.0;
+ let height_ratio = bounds.height / dimensions.1;
+
+ let ratio = width_ratio.min(height_ratio);
+
+ let scale = self.state.scale;
+
+ if ratio < 1.0 {
+ (dimensions.0 * ratio * scale, dimensions.1 * ratio * scale)
+ } else {
+ (dimensions.0 * scale, dimensions.1 * scale)
+ }
+ };
+
+ Size::new(width, height)
+ }
+}
+
+impl<'a, Message, Renderer> Widget<Message, Renderer> for Viewer<'a>
+where
+ Renderer: self::Renderer + image::Renderer,
+{
+ fn width(&self) -> Length {
+ self.width
+ }
+
+ fn height(&self) -> Length {
+ self.height
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ let (width, height) = renderer.dimensions(&self.handle);
+
+ let mut size = limits
+ .width(self.width)
+ .height(self.height)
+ .resolve(Size::new(width as f32, height as f32));
+
+ let expansion_size = if height > width {
+ self.width
+ } else {
+ self.height
+ };
+
+ // Only calculate viewport sizes if the images are constrained to a limited space.
+ // If they are Fill|Portion let them expand within their alotted space.
+ match expansion_size {
+ Length::Shrink | Length::Units(_) => {
+ let aspect_ratio = width as f32 / height as f32;
+ let viewport_aspect_ratio = size.width / size.height;
+ if viewport_aspect_ratio > aspect_ratio {
+ size.width = width as f32 * size.height / height as f32;
+ } else {
+ size.height = height as f32 * size.width / width as f32;
+ }
+ }
+ Length::Fill | Length::FillPortion(_) => {}
+ }
+
+ layout::Node::new(size)
+ }
+
+ fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ _clipboard: &mut dyn Clipboard,
+ _messages: &mut Vec<Message>,
+ ) -> event::Status {
+ let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ match event {
+ Event::Mouse(mouse::Event::WheelScrolled { delta })
+ if is_mouse_over =>
+ {
+ match delta {
+ mouse::ScrollDelta::Lines { y, .. }
+ | mouse::ScrollDelta::Pixels { y, .. } => {
+ let previous_scale = self.state.scale;
+
+ if y < 0.0 && previous_scale > self.min_scale
+ || y > 0.0 && previous_scale < self.max_scale
+ {
+ self.state.scale = (if y > 0.0 {
+ self.state.scale * (1.0 + self.scale_step)
+ } else {
+ self.state.scale / (1.0 + self.scale_step)
+ })
+ .max(self.min_scale)
+ .min(self.max_scale);
+
+ let image_size =
+ self.image_size(renderer, bounds.size());
+
+ let factor =
+ self.state.scale / previous_scale - 1.0;
+
+ let cursor_to_center =
+ cursor_position - bounds.center();
+
+ let adjustment = cursor_to_center * factor
+ + self.state.current_offset * factor;
+
+ self.state.current_offset = Vector::new(
+ if image_size.width > bounds.width {
+ self.state.current_offset.x + adjustment.x
+ } else {
+ 0.0
+ },
+ if image_size.height > bounds.height {
+ self.state.current_offset.y + adjustment.y
+ } else {
+ 0.0
+ },
+ );
+ }
+ }
+ }
+
+ event::Status::Captured
+ }
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ if is_mouse_over =>
+ {
+ self.state.cursor_grabbed_at = Some(cursor_position);
+ self.state.starting_offset = self.state.current_offset;
+
+ event::Status::Captured
+ }
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
+ if self.state.cursor_grabbed_at.is_some() =>
+ {
+ self.state.cursor_grabbed_at = None;
+
+ event::Status::Captured
+ }
+ Event::Mouse(mouse::Event::CursorMoved { position }) => {
+ if let Some(origin) = self.state.cursor_grabbed_at {
+ let image_size = self.image_size(renderer, bounds.size());
+
+ let hidden_width = (image_size.width - bounds.width / 2.0)
+ .max(0.0)
+ .round();
+
+ let hidden_height = (image_size.height
+ - bounds.height / 2.0)
+ .max(0.0)
+ .round();
+
+ let delta = position - origin;
+
+ let x = if bounds.width < image_size.width {
+ (self.state.starting_offset.x - delta.x)
+ .min(hidden_width)
+ .max(-hidden_width)
+ } else {
+ 0.0
+ };
+
+ let y = if bounds.height < image_size.height {
+ (self.state.starting_offset.y - delta.y)
+ .min(hidden_height)
+ .max(-hidden_height)
+ } else {
+ 0.0
+ };
+
+ self.state.current_offset = Vector::new(x, y);
+
+ event::Status::Captured
+ } else {
+ event::Status::Ignored
+ }
+ }
+ _ => event::Status::Ignored,
+ }
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ _defaults: &Renderer::Defaults,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> Renderer::Output {
+ let bounds = layout.bounds();
+
+ let image_size = self.image_size(renderer, bounds.size());
+
+ let translation = {
+ let image_top_left = Vector::new(
+ bounds.width / 2.0 - image_size.width / 2.0,
+ bounds.height / 2.0 - image_size.height / 2.0,
+ );
+
+ 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,
+ )
+ }
+
+ fn hash_layout(&self, state: &mut Hasher) {
+ struct Marker;
+ std::any::TypeId::of::<Marker>().hash(state);
+
+ self.width.hash(state);
+ self.height.hash(state);
+ self.padding.hash(state);
+
+ self.handle.hash(state);
+ }
+}
+
+/// The local state of a [`Viewer`].
+#[derive(Debug, Clone, Copy)]
+pub struct State {
+ scale: f32,
+ starting_offset: Vector,
+ current_offset: Vector,
+ cursor_grabbed_at: Option<Point>,
+}
+
+impl Default for State {
+ fn default() -> Self {
+ Self {
+ scale: 1.0,
+ starting_offset: Vector::default(),
+ current_offset: Vector::default(),
+ cursor_grabbed_at: None,
+ }
+ }
+}
+
+impl State {
+ /// Creates a new [`State`].
+ pub fn new() -> Self {
+ State::default()
+ }
+
+ /// Returns the current offset of the [`State`], given the bounds
+ /// of the [`Viewer`] and its image.
+ fn offset(&self, bounds: Rectangle, image_size: Size) -> Vector {
+ let hidden_width =
+ (image_size.width - bounds.width / 2.0).max(0.0).round();
+
+ let hidden_height =
+ (image_size.height - bounds.height / 2.0).max(0.0).round();
+
+ Vector::new(
+ self.current_offset.x.min(hidden_width).max(-hidden_width),
+ self.current_offset.y.min(hidden_height).max(-hidden_height),
+ )
+ }
+
+ /// Returns if the cursor is currently grabbed by the [`Viewer`].
+ pub fn is_cursor_grabbed(&self) -> bool {
+ self.cursor_grabbed_at.is_some()
+ }
+}
+
+/// 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,
+ Message: 'a,
+{
+ fn from(viewer: Viewer<'a>) -> Element<'a, Message, Renderer> {
+ Element::new(viewer)
+ }
+}
diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs
index 5180fd3b..26a72409 100644
--- a/native/src/widget/pane_grid.rs
+++ b/native/src/widget/pane_grid.rs
@@ -6,8 +6,7 @@
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
//! drag and drop, and hotkey support.
//!
-//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.1/examples/pane_grid
-//! [`PaneGrid`]: struct.PaneGrid.html
+//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
mod axis;
mod configuration;
mod content;
@@ -25,12 +24,19 @@ pub use direction::Direction;
pub use node::Node;
pub use pane::Pane;
pub use split::Split;
-pub use state::{Focus, State};
+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::touch;
use crate::{
- container, keyboard, layout, mouse, overlay, row, text, Clipboard, Element,
- Event, Hasher, Layout, Length, Point, Rectangle, Size, Vector, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
+ Widget,
};
/// A collection of panes distributed using either vertical or horizontal splits
@@ -73,7 +79,7 @@ use crate::{
/// let (mut state, _) = pane_grid::State::new(PaneState::SomePane);
///
/// let pane_grid =
-/// PaneGrid::new(&mut state, |pane, state, focus| {
+/// PaneGrid::new(&mut state, |pane, state| {
/// pane_grid::Content::new(match state {
/// PaneState::SomePane => Text::new("This is some pane"),
/// PaneState::AnotherKindOfPane => Text::new("This is another kind of pane"),
@@ -82,9 +88,6 @@ use crate::{
/// .on_drag(Message::PaneDragged)
/// .on_resize(10, Message::PaneResized);
/// ```
-///
-/// [`PaneGrid`]: struct.PaneGrid.html
-/// [`State`]: struct.State.html
#[allow(missing_debug_implementations)]
pub struct PaneGrid<'a, Message, Renderer: self::Renderer> {
state: &'a mut state::Internal,
@@ -92,10 +95,10 @@ pub struct PaneGrid<'a, Message, Renderer: self::Renderer> {
width: Length,
height: Length,
spacing: u16,
- modifier_keys: keyboard::ModifiersState,
+ 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>)>,
- on_key_press: Option<Box<dyn Fn(KeyPressEvent) -> Option<Message> + 'a>>,
+ style: <Renderer as self::Renderer>::Style,
}
impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
@@ -106,37 +109,15 @@ where
///
/// The view function will be called to display each [`Pane`] present in the
/// [`State`].
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
- /// [`State`]: struct.State.html
- /// [`Pane`]: struct.Pane.html
pub fn new<T>(
state: &'a mut State<T>,
- view: impl Fn(
- Pane,
- &'a mut T,
- Option<Focus>,
- ) -> Content<'a, Message, Renderer>,
+ view: impl Fn(Pane, &'a mut T) -> Content<'a, Message, Renderer>,
) -> Self {
let elements = {
- let action = state.internal.action();
- let current_focus = action.focus();
-
state
.panes
.iter_mut()
- .map(move |(pane, pane_state)| {
- let focus = match current_focus {
- Some((focused_pane, focus))
- if *pane == focused_pane =>
- {
- Some(focus)
- }
- _ => None,
- };
-
- (*pane, view(*pane, pane_state, focus))
- })
+ .map(|(pane, pane_state)| (*pane, view(*pane, pane_state)))
.collect()
};
@@ -146,59 +127,43 @@ where
width: Length::Fill,
height: Length::Fill,
spacing: 0,
- modifier_keys: keyboard::ModifiersState {
- control: true,
- ..Default::default()
- },
+ on_click: None,
on_drag: None,
on_resize: None,
- on_key_press: None,
+ style: Default::default(),
}
}
/// Sets the width of the [`PaneGrid`].
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`PaneGrid`].
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the spacing _between_ the panes of the [`PaneGrid`].
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
pub fn spacing(mut self, units: u16) -> Self {
self.spacing = units;
self
}
- /// Sets the modifier keys of the [`PaneGrid`].
- ///
- /// The modifier keys will need to be pressed to trigger key events.
- ///
- /// The default modifier key is `Ctrl`.
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
- pub fn modifier_keys(
- mut self,
- modifier_keys: keyboard::ModifiersState,
- ) -> Self {
- self.modifier_keys = modifier_keys;
+ /// Sets the message that will be produced when a [`Pane`] of the
+ /// [`PaneGrid`] is clicked.
+ pub fn on_click<F>(mut self, f: F) -> Self
+ where
+ F: 'a + Fn(Pane) -> Message,
+ {
+ self.on_click = Some(Box::new(f));
self
}
/// Enables the drag and drop interactions of the [`PaneGrid`], which will
/// use the provided function to produce messages.
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
pub fn on_drag<F>(mut self, f: F) -> Self
where
F: 'a + Fn(DragEvent) -> Message,
@@ -216,8 +181,6 @@ where
/// The grabbable area of a split will have a length of `spacing + leeway`,
/// properly centered. In other words, a length of
/// `(spacing + leeway) / 2.0` on either side of the split line.
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
pub fn on_resize<F>(mut self, leeway: u16, f: F) -> Self
where
F: 'a + Fn(ResizeEvent) -> Message,
@@ -226,28 +189,12 @@ where
self
}
- /// Captures hotkey interactions with the [`PaneGrid`], using the provided
- /// function to produce messages.
- ///
- /// The function will be called when:
- /// - a [`Pane`] is focused
- /// - a key is pressed
- /// - all the modifier keys are pressed
- ///
- /// If the function returns `None`, the key press event will be discarded
- /// without producing any message.
- ///
- /// This method is particularly useful to implement hotkey interactions.
- /// For instance, you can use it to enable splitting, swapping, or resizing
- /// panes by pressing combinations of keys.
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
- /// [`Pane`]: struct.Pane.html
- pub fn on_key_press<F>(mut self, f: F) -> Self
- where
- F: 'a + Fn(KeyPressEvent) -> Option<Message>,
- {
- self.on_key_press = Some(Box::new(f));
+ /// Sets the style of the [`PaneGrid`].
+ pub fn style(
+ mut self,
+ style: impl Into<<Renderer as self::Renderer>::Style>,
+ ) -> Self {
+ self.style = style.into();
self
}
}
@@ -268,24 +215,20 @@ where
);
if let Some(((pane, content), layout)) = clicked_region.next() {
- match &self.on_drag {
- Some(on_drag) => {
- if content.can_be_picked_at(layout, cursor_position) {
- let pane_position = layout.position();
+ if let Some(on_click) = &self.on_click {
+ messages.push(on_click(*pane));
+ }
- let origin = cursor_position
- - Vector::new(pane_position.x, pane_position.y);
+ if let Some(on_drag) = &self.on_drag {
+ if content.can_be_picked_at(layout, cursor_position) {
+ let pane_position = layout.position();
- self.state.pick_pane(pane, origin);
+ let origin = cursor_position
+ - Vector::new(pane_position.x, pane_position.y);
- messages
- .push(on_drag(DragEvent::Picked { pane: *pane }));
- } else {
- self.state.focus(pane);
- }
- }
- None => {
- self.state.focus(pane);
+ self.state.pick_pane(pane, origin);
+
+ messages.push(on_drag(DragEvent::Picked { pane: *pane }));
}
}
}
@@ -296,7 +239,7 @@ where
layout: Layout<'_>,
cursor_position: Point,
messages: &mut Vec<Message>,
- ) {
+ ) -> event::Status {
if let Some((_, on_resize)) = &self.on_resize {
if let Some((split, _)) = self.state.picked_split() {
let bounds = layout.bounds();
@@ -323,85 +266,55 @@ where
};
messages.push(on_resize(ResizeEvent { split, ratio }));
+
+ return event::Status::Captured;
}
}
}
+
+ event::Status::Ignored
}
}
/// An event produced during a drag and drop interaction of a [`PaneGrid`].
-///
-/// [`PaneGrid`]: struct.PaneGrid.html
#[derive(Debug, Clone, Copy)]
pub enum DragEvent {
/// A [`Pane`] was picked for dragging.
- ///
- /// [`Pane`]: struct.Pane.html
Picked {
/// The picked [`Pane`].
- ///
- /// [`Pane`]: struct.Pane.html
pane: Pane,
},
/// A [`Pane`] was dropped on top of another [`Pane`].
- ///
- /// [`Pane`]: struct.Pane.html
Dropped {
/// The picked [`Pane`].
- ///
- /// [`Pane`]: struct.Pane.html
pane: Pane,
/// The [`Pane`] where the picked one was dropped on.
- ///
- /// [`Pane`]: struct.Pane.html
target: Pane,
},
/// A [`Pane`] was picked and then dropped outside of other [`Pane`]
/// boundaries.
- ///
- /// [`Pane`]: struct.Pane.html
Canceled {
/// The picked [`Pane`].
- ///
- /// [`Pane`]: struct.Pane.html
pane: Pane,
},
}
/// An event produced during a resize interaction of a [`PaneGrid`].
-///
-/// [`PaneGrid`]: struct.PaneGrid.html
#[derive(Debug, Clone, Copy)]
pub struct ResizeEvent {
/// The [`Split`] that is being dragged for resizing.
- ///
- /// [`Split`]: struct.Split.html
pub split: Split,
/// The new ratio of the [`Split`].
///
/// The ratio is a value in [0, 1], representing the exact position of a
/// [`Split`] between two panes.
- ///
- /// [`Split`]: struct.Split.html
pub ratio: f32,
}
-/// An event produced during a key press interaction of a [`PaneGrid`].
-///
-/// [`PaneGrid`]: struct.PaneGrid.html
-#[derive(Debug, Clone, Copy)]
-pub struct KeyPressEvent {
- /// The key that was pressed.
- pub key_code: keyboard::KeyCode,
-
- /// The state of the modifier keys when the key was pressed.
- pub modifiers: keyboard::ModifiersState,
-}
-
impl<'a, Message, Renderer> Widget<Message, Renderer>
for PaneGrid<'a, Message, Renderer>
where
@@ -449,45 +362,41 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
renderer: &Renderer,
- clipboard: Option<&dyn Clipboard>,
- ) {
- match event {
- Event::Mouse(mouse_event) => match mouse_event {
- mouse::Event::ButtonPressed(mouse::Button::Left) => {
- let bounds = layout.bounds();
-
- if bounds.contains(cursor_position) {
- match self.on_resize {
- Some((leeway, _)) => {
- let relative_cursor = Point::new(
- cursor_position.x - bounds.x,
- cursor_position.y - bounds.y,
- );
-
- let splits = self.state.split_regions(
- f32::from(self.spacing),
- Size::new(bounds.width, bounds.height),
- );
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
+ let mut event_status = event::Status::Ignored;
- let clicked_split = hovered_split(
- splits.iter(),
- f32::from(self.spacing + leeway),
- relative_cursor,
- );
+ match event {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
+ let bounds = layout.bounds();
- if let Some((split, axis)) = clicked_split {
- self.state.pick_split(&split, axis);
- } else {
- self.click_pane(
- layout,
- cursor_position,
- messages,
- );
- }
- }
- None => {
+ if bounds.contains(cursor_position) {
+ event_status = event::Status::Captured;
+
+ match self.on_resize {
+ Some((leeway, _)) => {
+ let relative_cursor = Point::new(
+ cursor_position.x - bounds.x,
+ cursor_position.y - bounds.y,
+ );
+
+ let splits = self.state.split_regions(
+ f32::from(self.spacing),
+ Size::new(bounds.width, bounds.height),
+ );
+
+ let clicked_split = hovered_split(
+ splits.iter(),
+ f32::from(self.spacing + leeway),
+ relative_cursor,
+ );
+
+ if let Some((split, axis, _)) = clicked_split {
+ self.state.pick_split(&split, axis);
+ } else {
self.click_pane(
layout,
cursor_position,
@@ -495,91 +404,73 @@ where
);
}
}
- } else {
- // TODO: Encode cursor availability in the type system
- if cursor_position.x > 0.0 && cursor_position.y > 0.0 {
- self.state.unfocus();
+ None => {
+ self.click_pane(layout, cursor_position, messages);
}
}
}
- mouse::Event::ButtonReleased(mouse::Button::Left) => {
- if let Some((pane, _)) = self.state.picked_pane() {
- self.state.focus(&pane);
-
- if let Some(on_drag) = &self.on_drag {
- let mut dropped_region = self
- .elements
- .iter()
- .zip(layout.children())
- .filter(|(_, layout)| {
+ }
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerLifted { .. })
+ | Event::Touch(touch::Event::FingerLost { .. }) => {
+ if let Some((pane, _)) = self.state.picked_pane() {
+ if let Some(on_drag) = &self.on_drag {
+ let mut dropped_region =
+ self.elements.iter().zip(layout.children()).filter(
+ |(_, layout)| {
layout.bounds().contains(cursor_position)
- });
-
- let event = match dropped_region.next() {
- Some(((target, _), _)) if pane != *target => {
- DragEvent::Dropped {
- pane,
- target: *target,
- }
+ },
+ );
+
+ let event = match dropped_region.next() {
+ Some(((target, _), _)) if pane != *target => {
+ DragEvent::Dropped {
+ pane,
+ target: *target,
}
- _ => DragEvent::Canceled { pane },
- };
+ }
+ _ => DragEvent::Canceled { pane },
+ };
- messages.push(on_drag(event));
- }
- } else if self.state.picked_split().is_some() {
- self.state.drop_split();
+ messages.push(on_drag(event));
}
+
+ self.state.idle();
+
+ event_status = event::Status::Captured;
+ } else if self.state.picked_split().is_some() {
+ self.state.idle();
+
+ event_status = event::Status::Captured;
}
- mouse::Event::CursorMoved { .. } => {
+ }
+ Event::Mouse(mouse::Event::CursorMoved { .. })
+ | Event::Touch(touch::Event::FingerMoved { .. }) => {
+ event_status =
self.trigger_resize(layout, cursor_position, messages);
- }
- _ => {}
- },
- Event::Keyboard(keyboard_event) => {
- match keyboard_event {
- keyboard::Event::KeyPressed {
- modifiers,
- key_code,
- } => {
- if let Some(on_key_press) = &self.on_key_press {
- // TODO: Discard when event is captured
- if let Some(_) = self.state.active_pane() {
- if modifiers.matches(self.modifier_keys) {
- if let Some(message) =
- on_key_press(KeyPressEvent {
- key_code,
- modifiers,
- })
- {
- messages.push(message);
- }
- }
- }
- }
- }
- _ => {}
- }
}
_ => {}
}
- if self.state.picked_pane().is_none() {
- {
- self.elements.iter_mut().zip(layout.children()).for_each(
- |((_, pane), layout)| {
- pane.on_event(
- event.clone(),
- layout,
- cursor_position,
- messages,
- renderer,
- clipboard,
- )
- },
- );
- }
- }
+ let picked_pane = self.state.picked_pane().map(|(pane, _)| pane);
+
+ self.elements
+ .iter_mut()
+ .zip(layout.children())
+ .map(|((pane, content), layout)| {
+ let is_picked = picked_pane == Some(*pane);
+
+ content.on_event(
+ event.clone(),
+ layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ messages,
+ is_picked,
+ )
+ })
+ .fold(event_status, event::Status::merge)
}
fn draw(
@@ -588,10 +479,28 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Renderer::Output {
let picked_split = self
.state
.picked_split()
+ .and_then(|(split, axis)| {
+ let bounds = layout.bounds();
+
+ let splits = self
+ .state
+ .split_regions(f32::from(self.spacing), bounds.size());
+
+ let (_axis, region, ratio) = splits.get(&split)?;
+
+ let region = axis.split_line_bounds(
+ *region,
+ *ratio,
+ f32::from(self.spacing),
+ );
+
+ Some((axis, region + Vector::new(bounds.x, bounds.y), true))
+ })
.or_else(|| match self.on_resize {
Some((leeway, _)) => {
let bounds = layout.bounds();
@@ -605,15 +514,20 @@ where
.state
.split_regions(f32::from(self.spacing), bounds.size());
- hovered_split(
+ let (_split, axis, region) = hovered_split(
splits.iter(),
f32::from(self.spacing + leeway),
relative_cursor,
- )
+ )?;
+
+ Some((
+ axis,
+ region + Vector::new(bounds.x, bounds.y),
+ false,
+ ))
}
None => None,
- })
- .map(|(_, axis)| axis);
+ });
self::Renderer::draw(
renderer,
@@ -622,7 +536,9 @@ where
self.state.picked_pane(),
picked_split,
layout,
+ &self.style,
cursor_position,
+ viewport,
)
}
@@ -658,11 +574,11 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`PaneGrid`] in your user interface.
///
-/// [`PaneGrid`]: struct.PaneGrid.html
-/// [renderer]: ../../renderer/index.html
-pub trait Renderer:
- crate::Renderer + container::Renderer + text::Renderer + Sized
-{
+/// [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:
@@ -671,18 +587,16 @@ pub trait Renderer:
/// - the [`Axis`] that is currently being resized
/// - the [`Layout`] of the [`PaneGrid`] and its elements
/// - the cursor position
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
- /// [`Pane`]: struct.Pane.html
- /// [`Layout`]: ../layout/struct.Layout.html
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
content: &[(Pane, Content<'_, Message, Self>)],
dragging: Option<(Pane, Point)>,
- resizing: Option<Axis>,
+ resizing: Option<(Axis, Rectangle, bool)>,
layout: Layout<'_>,
+ style: &<Self as self::Renderer>::Style,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Self::Output;
/// Draws a [`Pane`].
@@ -692,17 +606,15 @@ pub trait Renderer:
/// - the [`Content`] of the [`Pane`]
/// - the [`Layout`] of the [`Pane`] and its elements
/// - the cursor position
- ///
- /// [`Pane`]: struct.Pane.html
- /// [`Layout`]: ../layout/struct.Layout.html
fn draw_pane<Message>(
&mut self,
defaults: &Self::Defaults,
bounds: Rectangle,
- style: &Self::Style,
+ 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`].
@@ -710,23 +622,18 @@ pub trait Renderer:
/// It receives:
/// - the bounds, style of the [`TitleBar`]
/// - the style of the [`TitleBar`]
- /// - the title of the [`TitleBar`] with its size, font, and bounds
- /// - the controls of the [`TitleBar`] with their [`Layout`+, if any
+ /// - the content of the [`TitleBar`] with its layout
+ /// - the controls of the [`TitleBar`] with their [`Layout`], if any
/// - the cursor position
- ///
- /// [`TitleBar`]: struct.TitleBar.html
- /// [`Layout`]: ../layout/struct.Layout.html
fn draw_title_bar<Message>(
&mut self,
defaults: &Self::Defaults,
bounds: Rectangle,
- style: &Self::Style,
- title: &str,
- title_size: u16,
- title_font: Self::Font,
- title_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;
}
@@ -750,14 +657,14 @@ fn hovered_split<'a>(
splits: impl Iterator<Item = (&'a Split, &'a (Axis, Rectangle, f32))>,
spacing: f32,
cursor_position: Point,
-) -> Option<(Split, Axis)> {
+) -> Option<(Split, Axis, Rectangle)> {
splits
.filter_map(|(split, (axis, region, ratio))| {
let bounds =
axis.split_line_bounds(*region, *ratio, f32::from(spacing));
if bounds.contains(cursor_position) {
- Some((*split, *axis))
+ Some((*split, *axis, bounds))
} else {
None
}
diff --git a/native/src/widget/pane_grid/configuration.rs b/native/src/widget/pane_grid/configuration.rs
index 1fed98b7..4c43826e 100644
--- a/native/src/widget/pane_grid/configuration.rs
+++ b/native/src/widget/pane_grid/configuration.rs
@@ -2,7 +2,7 @@ use crate::pane_grid::Axis;
/// The arrangement of a [`PaneGrid`].
///
-/// [`PaneGrid`]: struct.PaneGrid.html
+/// [`PaneGrid`]: crate::pane_grid::PaneGrid
#[derive(Debug, Clone)]
pub enum Configuration<T> {
/// A split of the available space.
@@ -13,18 +13,14 @@ pub enum Configuration<T> {
/// The ratio of the split in [0.0, 1.0].
ratio: f32,
- /// The left/top [`Content`] of the split.
- ///
- /// [`Configuration`]: enum.Node.html
+ /// The left/top [`Configuration`] of the split.
a: Box<Configuration<T>>,
- /// The right/bottom [`Content`] of the split.
- ///
- /// [`Configuration`]: enum.Node.html
+ /// The right/bottom [`Configuration`] of the split.
b: Box<Configuration<T>>,
},
/// A [`Pane`].
///
- /// [`Pane`]: struct.Pane.html
+ /// [`Pane`]: crate::pane_grid::Pane
Pane(T),
}
diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs
index 1d339b75..bac9fdd4 100644
--- a/native/src/widget/pane_grid/content.rs
+++ b/native/src/widget/pane_grid/content.rs
@@ -1,17 +1,18 @@
use crate::container;
+use crate::event::{self, Event};
use crate::layout;
use crate::overlay;
use crate::pane_grid::{self, TitleBar};
-use crate::{Clipboard, Element, Event, Hasher, Layout, Point, Size};
+use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size};
/// The content of a [`Pane`].
///
-/// [`Pane`]: struct.Pane.html
+/// [`Pane`]: crate::widget::pane_grid::Pane
#[allow(missing_debug_implementations)]
pub struct Content<'a, Message, Renderer: pane_grid::Renderer> {
title_bar: Option<TitleBar<'a, Message, Renderer>>,
body: Element<'a, Message, Renderer>,
- style: Renderer::Style,
+ style: <Renderer as container::Renderer>::Style,
}
impl<'a, Message, Renderer> Content<'a, Message, Renderer>
@@ -19,20 +20,15 @@ where
Renderer: pane_grid::Renderer,
{
/// Creates a new [`Content`] with the provided body.
- ///
- /// [`Content`]: struct.Content.html
pub fn new(body: impl Into<Element<'a, Message, Renderer>>) -> Self {
Self {
title_bar: None,
body: body.into(),
- style: Renderer::Style::default(),
+ style: Default::default(),
}
}
/// Sets the [`TitleBar`] of this [`Content`].
- ///
- /// [`TitleBar`]: struct.TitleBar.html
- /// [`Content`]: struct.Content.html
pub fn title_bar(
mut self,
title_bar: TitleBar<'a, Message, Renderer>,
@@ -41,10 +37,11 @@ where
self
}
- /// Sets the style of the [`TitleBar`].
- ///
- /// [`TitleBar`]: struct.TitleBar.html
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
+ /// Sets the style of the [`Content`].
+ pub fn style(
+ mut self,
+ style: impl Into<<Renderer as container::Renderer>::Style>,
+ ) -> Self {
self.style = style.into();
self
}
@@ -56,15 +53,14 @@ where
{
/// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`].
///
- /// [`Content`]: struct.Content.html
- /// [`Renderer`]: trait.Renderer.html
- /// [`Layout`]: ../layout/struct.Layout.html
+ /// [`Renderer`]: crate::widget::pane_grid::Renderer
pub fn draw(
&self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Renderer::Output {
if let Some(title_bar) = &self.title_bar {
let mut children = layout.children();
@@ -78,6 +74,7 @@ where
Some((title_bar, title_bar_layout)),
(&self.body, body_layout),
cursor_position,
+ viewport,
)
} else {
renderer.draw_pane(
@@ -87,15 +84,13 @@ where
None,
(&self.body, layout),
cursor_position,
+ viewport,
)
}
}
/// Returns whether the [`Content`] with the given [`Layout`] can be picked
/// at the provided cursor position.
- ///
- /// [`Content`]: struct.Content.html
- /// [`Layout`]: ../layout/struct.Layout.html
pub fn can_be_picked_at(
&self,
layout: Layout<'_>,
@@ -151,20 +146,23 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
renderer: &Renderer,
- clipboard: Option<&dyn Clipboard>,
- ) {
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ is_picked: bool,
+ ) -> event::Status {
+ let mut event_status = event::Status::Ignored;
+
let body_layout = if let Some(title_bar) = &mut self.title_bar {
let mut children = layout.children();
- title_bar.on_event(
+ event_status = title_bar.on_event(
event.clone(),
children.next().unwrap(),
cursor_position,
- messages,
renderer,
clipboard,
+ messages,
);
children.next().unwrap()
@@ -172,14 +170,20 @@ where
layout
};
- self.body.on_event(
- event,
- body_layout,
- cursor_position,
- messages,
- renderer,
- clipboard,
- );
+ let body_status = if is_picked {
+ event::Status::Ignored
+ } else {
+ self.body.on_event(
+ event,
+ body_layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ messages,
+ )
+ };
+
+ event_status.merge(body_status)
}
pub(crate) fn hash_layout(&self, state: &mut Hasher) {
@@ -194,18 +198,17 @@ where
&mut self,
layout: Layout<'_>,
) -> Option<overlay::Element<'_, Message, Renderer>> {
- let body_layout = if self.title_bar.is_some() {
+ if let Some(title_bar) = self.title_bar.as_mut() {
let mut children = layout.children();
+ let title_bar_layout = children.next()?;
- // Overlays only allowed in the pane body, for now at least.
- let _title_bar_layout = children.next();
-
- children.next()?
+ match title_bar.overlay(title_bar_layout) {
+ Some(overlay) => Some(overlay),
+ None => self.body.overlay(children.next()?),
+ }
} else {
- layout
- };
-
- self.body.overlay(body_layout)
+ self.body.overlay(layout)
+ }
}
}
diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs
index cbfd8a43..84714e00 100644
--- a/native/src/widget/pane_grid/node.rs
+++ b/native/src/widget/pane_grid/node.rs
@@ -3,21 +3,16 @@ use crate::{
Rectangle, Size,
};
-use std::collections::HashMap;
+use std::collections::BTreeMap;
/// A layout node of a [`PaneGrid`].
///
-/// [`PaneGrid`]: struct.PaneGrid.html
+/// [`PaneGrid`]: crate::widget::PaneGrid
#[derive(Debug, Clone)]
pub enum Node {
/// The region of this [`Node`] is split into two.
- ///
- /// [`Node`]: enum.Node.html
Split {
/// The [`Split`] of this [`Node`].
- ///
- /// [`Split`]: struct.Split.html
- /// [`Node`]: enum.Node.html
id: Split,
/// The direction of the split.
@@ -27,26 +22,17 @@ pub enum Node {
ratio: f32,
/// The left/top [`Node`] of the split.
- ///
- /// [`Node`]: enum.Node.html
a: Box<Node>,
/// The right/bottom [`Node`] of the split.
- ///
- /// [`Node`]: enum.Node.html
b: Box<Node>,
},
/// The region of this [`Node`] is taken by a [`Pane`].
- ///
- /// [`Pane`]: struct.Pane.html
Pane(Pane),
}
impl Node {
/// Returns an iterator over each [`Split`] in this [`Node`].
- ///
- /// [`Split`]: struct.Split.html
- /// [`Node`]: enum.Node.html
pub fn splits(&self) -> impl Iterator<Item = &Split> {
let mut unvisited_nodes = vec![self];
@@ -69,15 +55,12 @@ impl Node {
/// Returns the rectangular region for each [`Pane`] in the [`Node`] given
/// the spacing between panes and the total available space.
- ///
- /// [`Pane`]: struct.Pane.html
- /// [`Node`]: enum.Node.html
pub fn pane_regions(
&self,
spacing: f32,
size: Size,
- ) -> HashMap<Pane, Rectangle> {
- let mut regions = HashMap::new();
+ ) -> BTreeMap<Pane, Rectangle> {
+ let mut regions = BTreeMap::new();
self.compute_regions(
spacing,
@@ -96,15 +79,12 @@ impl Node {
/// Returns the axis, rectangular region, and ratio for each [`Split`] in
/// the [`Node`] given the spacing between panes and the total available
/// space.
- ///
- /// [`Split`]: struct.Split.html
- /// [`Node`]: enum.Node.html
pub fn split_regions(
&self,
spacing: f32,
size: Size,
- ) -> HashMap<Split, (Axis, Rectangle, f32)> {
- let mut splits = HashMap::new();
+ ) -> BTreeMap<Split, (Axis, Rectangle, f32)> {
+ let mut splits = BTreeMap::new();
self.compute_splits(
spacing,
@@ -211,7 +191,7 @@ impl Node {
&self,
spacing: f32,
current: &Rectangle,
- regions: &mut HashMap<Pane, Rectangle>,
+ regions: &mut BTreeMap<Pane, Rectangle>,
) {
match self {
Node::Split {
@@ -232,7 +212,7 @@ impl Node {
&self,
spacing: f32,
current: &Rectangle,
- splits: &mut HashMap<Split, (Axis, Rectangle, f32)>,
+ splits: &mut BTreeMap<Split, (Axis, Rectangle, f32)>,
) {
match self {
Node::Split {
diff --git a/native/src/widget/pane_grid/pane.rs b/native/src/widget/pane_grid/pane.rs
index f9866407..d6fbab83 100644
--- a/native/src/widget/pane_grid/pane.rs
+++ b/native/src/widget/pane_grid/pane.rs
@@ -1,5 +1,5 @@
/// A rectangular region in a [`PaneGrid`] used to display widgets.
///
-/// [`PaneGrid`]: struct.PaneGrid.html
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+/// [`PaneGrid`]: crate::widget::PaneGrid
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Pane(pub(super) usize);
diff --git a/native/src/widget/pane_grid/split.rs b/native/src/widget/pane_grid/split.rs
index d020c510..8132272a 100644
--- a/native/src/widget/pane_grid/split.rs
+++ b/native/src/widget/pane_grid/split.rs
@@ -1,5 +1,5 @@
/// A divider that splits a region in a [`PaneGrid`] into two different panes.
///
-/// [`PaneGrid`]: struct.PaneGrid.html
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+/// [`PaneGrid`]: crate::widget::PaneGrid
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Split(pub(super) usize);
diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs
index fb59c846..fb96f89f 100644
--- a/native/src/widget/pane_grid/state.rs
+++ b/native/src/widget/pane_grid/state.rs
@@ -3,7 +3,7 @@ use crate::{
Hasher, Point, Rectangle, Size,
};
-use std::collections::HashMap;
+use std::collections::{BTreeMap, HashMap};
/// The state of a [`PaneGrid`].
///
@@ -15,41 +15,19 @@ use std::collections::HashMap;
/// provided to the view function of [`PaneGrid::new`] for displaying each
/// [`Pane`].
///
-/// [`PaneGrid`]: struct.PaneGrid.html
-/// [`PaneGrid::new`]: struct.PaneGrid.html#method.new
-/// [`Pane`]: struct.Pane.html
-/// [`Split`]: struct.Split.html
-/// [`State`]: struct.State.html
+/// [`PaneGrid`]: crate::widget::PaneGrid
+/// [`PaneGrid::new`]: crate::widget::PaneGrid::new
#[derive(Debug, Clone)]
pub struct State<T> {
pub(super) panes: HashMap<Pane, T>,
pub(super) internal: Internal,
}
-/// The current focus of a [`Pane`].
-///
-/// [`Pane`]: struct.Pane.html
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum Focus {
- /// The [`Pane`] is just focused.
- ///
- /// [`Pane`]: struct.Pane.html
- Idle,
-
- /// The [`Pane`] is being dragged.
- ///
- /// [`Pane`]: struct.Pane.html
- Dragging,
-}
-
impl<T> State<T> {
/// Creates a new [`State`], initializing the first pane with the provided
/// state.
///
/// Alongside the [`State`], it returns the first [`Pane`] identifier.
- ///
- /// [`State`]: struct.State.html
- /// [`Pane`]: struct.Pane.html
pub fn new(first_pane_state: T) -> (Self, Pane) {
(
Self::with_configuration(Configuration::Pane(first_pane_state)),
@@ -58,9 +36,6 @@ impl<T> State<T> {
}
/// Creates a new [`State`] with the given [`Configuration`].
- ///
- /// [`State`]: struct.State.html
- /// [`Configuration`]: enum.Configuration.html
pub fn with_configuration(config: impl Into<Configuration<T>>) -> Self {
let mut panes = HashMap::new();
@@ -72,95 +47,46 @@ impl<T> State<T> {
internal: Internal {
layout,
last_id,
- action: Action::Idle { focus: None },
+ action: Action::Idle,
},
}
}
/// Returns the total amount of panes in the [`State`].
- ///
- /// [`State`]: struct.State.html
pub fn len(&self) -> usize {
self.panes.len()
}
/// Returns the internal state of the given [`Pane`], if it exists.
- ///
- /// [`Pane`]: struct.Pane.html
pub fn get(&self, pane: &Pane) -> Option<&T> {
self.panes.get(pane)
}
/// Returns the internal state of the given [`Pane`] with mutability, if it
/// exists.
- ///
- /// [`Pane`]: struct.Pane.html
pub fn get_mut(&mut self, pane: &Pane) -> Option<&mut T> {
self.panes.get_mut(pane)
}
/// Returns an iterator over all the panes of the [`State`], alongside its
/// internal state.
- ///
- /// [`State`]: struct.State.html
pub fn iter(&self) -> impl Iterator<Item = (&Pane, &T)> {
self.panes.iter()
}
/// Returns a mutable iterator over all the panes of the [`State`],
/// alongside its internal state.
- ///
- /// [`State`]: struct.State.html
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Pane, &mut T)> {
self.panes.iter_mut()
}
/// Returns the layout of the [`State`].
- ///
- /// [`State`]: struct.State.html
pub fn layout(&self) -> &Node {
&self.internal.layout
}
- /// Returns the focused [`Pane`] of the [`State`], if there is one.
- ///
- /// [`Pane`]: struct.Pane.html
- /// [`State`]: struct.State.html
- pub fn focused(&self) -> Option<Pane> {
- self.internal.focused_pane()
- }
-
- /// Returns the active [`Pane`] of the [`State`], if there is one.
- ///
- /// A [`Pane`] is active if it is focused and is __not__ being dragged.
- ///
- /// [`Pane`]: struct.Pane.html
- /// [`State`]: struct.State.html
- pub fn active(&self) -> Option<Pane> {
- self.internal.active_pane()
- }
-
/// Returns the adjacent [`Pane`] of another [`Pane`] in the given
/// direction, if there is one.
- ///
- /// ## Example
- /// You can combine this with [`State::active`] to find the pane that is
- /// adjacent to the current active one, and then swap them. For instance:
- ///
- /// ```
- /// # use iced_native::pane_grid;
- /// #
- /// # let (mut state, _) = pane_grid::State::new(());
- /// #
- /// if let Some(active) = state.active() {
- /// if let Some(adjacent) = state.adjacent(&active, pane_grid::Direction::Right) {
- /// state.swap(&active, &adjacent);
- /// }
- /// }
- /// ```
- ///
- /// [`Pane`]: struct.Pane.html
- /// [`State::active`]: struct.State.html#method.active
pub fn adjacent(&self, pane: &Pane, direction: Direction) -> Option<Pane> {
let regions = self
.internal
@@ -194,25 +120,8 @@ impl<T> State<T> {
Some(*pane)
}
- /// Focuses the given [`Pane`].
- ///
- /// [`Pane`]: struct.Pane.html
- pub fn focus(&mut self, pane: &Pane) {
- self.internal.focus(pane);
- }
-
- /// Unfocuses the current focused [`Pane`].
- ///
- /// [`Pane`]: struct.Pane.html
- pub fn unfocus(&mut self) {
- self.internal.unfocus();
- }
-
/// Splits the given [`Pane`] into two in the given [`Axis`] and
/// initializing the new [`Pane`] with the provided internal state.
- ///
- /// [`Pane`]: struct.Pane.html
- /// [`Axis`]: enum.Axis.html
pub fn split(
&mut self,
axis: Axis,
@@ -236,7 +145,6 @@ impl<T> State<T> {
node.split(new_split, axis, new_pane);
let _ = self.panes.insert(new_pane, state);
- self.focus(&new_pane);
Some((new_pane, new_split))
}
@@ -246,9 +154,8 @@ impl<T> State<T> {
/// If you want to swap panes on drag and drop in your [`PaneGrid`], you
/// will need to call this method when handling a [`DragEvent`].
///
- /// [`State`]: struct.State.html
- /// [`PaneGrid`]: struct.PaneGrid.html
- /// [`DragEvent`]: struct.DragEvent.html
+ /// [`PaneGrid`]: crate::widget::PaneGrid
+ /// [`DragEvent`]: crate::widget::pane_grid::DragEvent
pub fn swap(&mut self, a: &Pane, b: &Pane) {
self.internal.layout.update(&|node| match node {
Node::Split { .. } => {}
@@ -270,20 +177,17 @@ impl<T> State<T> {
/// If you want to enable resize interactions in your [`PaneGrid`], you will
/// need to call this method when handling a [`ResizeEvent`].
///
- /// [`Split`]: struct.Split.html
- /// [`PaneGrid`]: struct.PaneGrid.html
- /// [`ResizeEvent`]: struct.ResizeEvent.html
+ /// [`PaneGrid`]: crate::widget::PaneGrid
+ /// [`ResizeEvent`]: crate::widget::pane_grid::ResizeEvent
pub fn resize(&mut self, split: &Split, ratio: f32) {
let _ = self.internal.layout.resize(split, ratio);
}
- /// Closes the given [`Pane`] and returns its internal state, if it exists.
- ///
- /// [`Pane`]: struct.Pane.html
- pub fn close(&mut self, pane: &Pane) -> Option<T> {
+ /// Closes the given [`Pane`] and returns its internal state and its closest
+ /// sibling, if it exists.
+ pub fn close(&mut self, pane: &Pane) -> Option<(T, Pane)> {
if let Some(sibling) = self.internal.layout.remove(pane) {
- self.focus(&sibling);
- self.panes.remove(pane)
+ self.panes.remove(pane).map(|state| (state, sibling))
} else {
None
}
@@ -329,52 +233,12 @@ pub struct Internal {
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Action {
- Idle {
- focus: Option<Pane>,
- },
- Dragging {
- pane: Pane,
- origin: Point,
- focus: Option<Pane>,
- },
- Resizing {
- split: Split,
- axis: Axis,
- focus: Option<Pane>,
- },
-}
-
-impl Action {
- pub fn focus(&self) -> Option<(Pane, Focus)> {
- match self {
- Action::Idle { focus } | Action::Resizing { focus, .. } => {
- focus.map(|pane| (pane, Focus::Idle))
- }
- Action::Dragging { pane, .. } => Some((*pane, Focus::Dragging)),
- }
- }
+ Idle,
+ Dragging { pane: Pane, origin: Point },
+ Resizing { split: Split, axis: Axis },
}
impl Internal {
- pub fn action(&self) -> Action {
- self.action
- }
-
- pub fn focused_pane(&self) -> Option<Pane> {
- match self.action {
- Action::Idle { focus } => focus,
- Action::Dragging { focus, .. } => focus,
- Action::Resizing { focus, .. } => focus,
- }
- }
-
- pub fn active_pane(&self) -> Option<Pane> {
- match self.action {
- Action::Idle { focus } => focus,
- _ => None,
- }
- }
-
pub fn picked_pane(&self) -> Option<(Pane, Point)> {
match self.action {
Action::Dragging { pane, origin, .. } => Some((pane, origin)),
@@ -393,7 +257,7 @@ impl Internal {
&self,
spacing: f32,
size: Size,
- ) -> HashMap<Pane, Rectangle> {
+ ) -> BTreeMap<Pane, Rectangle> {
self.layout.pane_regions(spacing, size)
}
@@ -401,21 +265,14 @@ impl Internal {
&self,
spacing: f32,
size: Size,
- ) -> HashMap<Split, (Axis, Rectangle, f32)> {
+ ) -> BTreeMap<Split, (Axis, Rectangle, f32)> {
self.layout.split_regions(spacing, size)
}
- pub fn focus(&mut self, pane: &Pane) {
- self.action = Action::Idle { focus: Some(*pane) };
- }
-
pub fn pick_pane(&mut self, pane: &Pane, origin: Point) {
- let focus = self.focused_pane();
-
self.action = Action::Dragging {
pane: *pane,
origin,
- focus,
};
}
@@ -426,26 +283,14 @@ impl Internal {
return;
}
- let focus = self.action.focus().map(|(pane, _)| pane);
-
self.action = Action::Resizing {
split: *split,
axis,
- focus,
};
}
- pub fn drop_split(&mut self) {
- match self.action {
- Action::Resizing { focus, .. } => {
- self.action = Action::Idle { focus };
- }
- _ => {}
- }
- }
-
- pub fn unfocus(&mut self) {
- self.action = Action::Idle { focus: None };
+ pub fn idle(&mut self) {
+ self.action = Action::Idle;
}
pub fn hash_layout(&self, hasher: &mut Hasher) {
diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs
index 9dfb9ae4..070010f8 100644
--- a/native/src/widget/pane_grid/title_bar.rs
+++ b/native/src/widget/pane_grid/title_bar.rs
@@ -1,51 +1,43 @@
+use crate::container;
+use crate::event::{self, Event};
use crate::layout;
+use crate::overlay;
use crate::pane_grid;
use crate::{
- Clipboard, Element, Event, Hasher, Layout, Point, Rectangle, Size,
+ Clipboard, Element, Hasher, Layout, Padding, Point, Rectangle, Size,
};
/// The title bar of a [`Pane`].
///
-/// [`Pane`]: struct.Pane.html
+/// [`Pane`]: crate::widget::pane_grid::Pane
#[allow(missing_debug_implementations)]
pub struct TitleBar<'a, Message, Renderer: pane_grid::Renderer> {
- title: String,
- title_size: Option<u16>,
+ content: Element<'a, Message, Renderer>,
controls: Option<Element<'a, Message, Renderer>>,
- padding: u16,
+ padding: Padding,
always_show_controls: bool,
- style: Renderer::Style,
+ style: <Renderer as container::Renderer>::Style,
}
impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
where
Renderer: pane_grid::Renderer,
{
- /// Creates a new [`TitleBar`] with the given title.
- ///
- /// [`TitleBar`]: struct.TitleBar.html
- pub fn new(title: impl Into<String>) -> Self {
+ /// Creates a new [`TitleBar`] with the given content.
+ pub fn new<E>(content: E) -> Self
+ where
+ E: Into<Element<'a, Message, Renderer>>,
+ {
Self {
- title: title.into(),
- title_size: None,
+ content: content.into(),
controls: None,
- padding: 0,
+ padding: Padding::ZERO,
always_show_controls: false,
- style: Renderer::Style::default(),
+ style: Default::default(),
}
}
- /// Sets the size of the title of the [`TitleBar`].
- ///
- /// [`TitleBar`]: struct.TitleBar.html
- pub fn title_size(mut self, size: u16) -> Self {
- self.title_size = Some(size);
- self
- }
-
/// Sets the controls of the [`TitleBar`].
- ///
- /// [`TitleBar`]: struct.TitleBar.html
pub fn controls(
mut self,
controls: impl Into<Element<'a, Message, Renderer>>,
@@ -54,18 +46,17 @@ where
self
}
- /// Sets the padding of the [`TitleBar`].
- ///
- /// [`TitleBar`]: struct.TitleBar.html
- pub fn padding(mut self, units: u16) -> Self {
- self.padding = units;
+ /// Sets the [`Padding`] of the [`TitleBar`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
/// Sets the style of the [`TitleBar`].
- ///
- /// [`TitleBar`]: struct.TitleBar.html
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
+ pub fn style(
+ mut self,
+ style: impl Into<<Renderer as container::Renderer>::Style>,
+ ) -> Self {
self.style = style.into();
self
}
@@ -76,9 +67,8 @@ where
/// By default, the controls are only visible when the [`Pane`] of this
/// [`TitleBar`] is hovered.
///
- /// [`TitleBar`]: struct.TitleBar.html
- /// [`controls`]: struct.TitleBar.html#method.controls
- /// [`Pane`]: struct.Pane.html
+ /// [`controls`]: Self::controls
+ /// [`Pane`]: crate::widget::pane_grid::Pane
pub fn always_show_controls(mut self) -> Self {
self.always_show_controls = true;
self
@@ -91,70 +81,49 @@ where
{
/// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`].
///
- /// [`TitleBar`]: struct.TitleBar.html
- /// [`Renderer`]: trait.Renderer.html
- /// [`Layout`]: ../layout/struct.Layout.html
+ /// [`Renderer`]: crate::widget::pane_grid::Renderer
pub fn draw(
&self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
show_controls: bool,
) -> Renderer::Output {
let mut children = layout.children();
let padded = children.next().unwrap();
- if let Some(controls) = &self.controls {
- let mut children = padded.children();
- let title_layout = children.next().unwrap();
+ let mut children = padded.children();
+ let title_layout = children.next().unwrap();
+
+ let controls = if let Some(controls) = &self.controls {
let controls_layout = children.next().unwrap();
- let (title_bounds, controls) =
- if show_controls || self.always_show_controls {
- (title_layout.bounds(), Some((controls, controls_layout)))
- } else {
- (
- Rectangle {
- width: padded.bounds().width,
- ..title_layout.bounds()
- },
- None,
- )
- };
-
- renderer.draw_title_bar(
- defaults,
- layout.bounds(),
- &self.style,
- &self.title,
- self.title_size.unwrap_or(renderer.default_size()),
- Renderer::Font::default(),
- title_bounds,
- controls,
- cursor_position,
- )
+ if show_controls || self.always_show_controls {
+ Some((controls, controls_layout))
+ } else {
+ None
+ }
} else {
- renderer.draw_title_bar::<()>(
- defaults,
- layout.bounds(),
- &self.style,
- &self.title,
- self.title_size.unwrap_or(renderer.default_size()),
- Renderer::Font::default(),
- padded.bounds(),
- None,
- cursor_position,
- )
- }
+ 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
/// [`TitleBar`] or not.
///
/// The whole [`TitleBar`] is a pick area, except its controls.
- ///
- /// [`TitleBar`]: struct.TitleBar.html
pub fn is_over_pick_area(
&self,
layout: Layout<'_>,
@@ -163,15 +132,16 @@ where
if layout.bounds().contains(cursor_position) {
let mut children = layout.children();
let padded = children.next().unwrap();
+ let mut children = padded.children();
+ let title_layout = children.next().unwrap();
if self.controls.is_some() {
- let mut children = padded.children();
- let _ = children.next().unwrap();
let controls_layout = children.next().unwrap();
!controls_layout.bounds().contains(cursor_position)
+ && !title_layout.bounds().contains(cursor_position)
} else {
- true
+ !title_layout.bounds().contains(cursor_position)
}
} else {
false
@@ -181,9 +151,12 @@ where
pub(crate) fn hash_layout(&self, hasher: &mut Hasher) {
use std::hash::Hash;
- self.title.hash(hasher);
- self.title_size.hash(hasher);
+ self.content.hash_layout(hasher);
self.padding.hash(hasher);
+
+ if let Some(controls) = &self.controls {
+ controls.hash_layout(hasher);
+ }
}
pub(crate) fn layout(
@@ -191,19 +164,13 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let padding = f32::from(self.padding);
- let limits = limits.pad(padding);
+ let limits = limits.pad(self.padding);
let max_size = limits.max();
- let title_size = self.title_size.unwrap_or(renderer.default_size());
- let title_font = Renderer::Font::default();
-
- let (title_width, title_height) = renderer.measure(
- &self.title,
- title_size,
- title_font,
- Size::new(f32::INFINITY, max_size.height),
- );
+ let title_layout = self
+ .content
+ .layout(renderer, &layout::Limits::new(Size::ZERO, max_size));
+ let title_size = title_layout.size();
let mut node = if let Some(controls) = &self.controls {
let mut controls_layout = controls
@@ -212,16 +179,8 @@ where
let controls_size = controls_layout.size();
let space_before_controls = max_size.width - controls_size.width;
- let mut title_layout = layout::Node::new(Size::new(
- title_width.min(space_before_controls),
- title_height,
- ));
-
- let title_size = title_layout.size();
let height = title_size.height.max(controls_size.height);
- title_layout
- .move_to(Point::new(0.0, (height - title_size.height) / 2.0));
controls_layout.move_to(Point::new(space_before_controls, 0.0));
layout::Node::with_children(
@@ -229,12 +188,18 @@ where
vec![title_layout, controls_layout],
)
} else {
- layout::Node::new(Size::new(max_size.width, title_height))
+ layout::Node::with_children(
+ Size::new(max_size.width, title_size.height),
+ vec![title_layout],
+ )
};
- node.move_to(Point::new(padding, padding));
+ node.move_to(Point::new(
+ self.padding.left.into(),
+ self.padding.top.into(),
+ ));
- layout::Node::with_children(node.size().pad(padding), vec![node])
+ layout::Node::with_children(node.size().pad(self.padding), vec![node])
}
pub(crate) fn on_event(
@@ -242,26 +207,63 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
renderer: &Renderer,
- clipboard: Option<&dyn Clipboard>,
- ) {
- if let Some(controls) = &mut self.controls {
- let mut children = layout.children();
- let padded = children.next().unwrap();
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
+ let mut children = layout.children();
+ let padded = children.next().unwrap();
- let mut children = padded.children();
- let _ = children.next();
+ let mut children = padded.children();
+ let title_layout = children.next().unwrap();
+
+ let control_status = if let Some(controls) = &mut self.controls {
let controls_layout = children.next().unwrap();
controls.on_event(
- event,
+ event.clone(),
controls_layout,
cursor_position,
- messages,
renderer,
clipboard,
- );
- }
+ messages,
+ )
+ } else {
+ event::Status::Ignored
+ };
+
+ let title_status = self.content.on_event(
+ event,
+ title_layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ messages,
+ );
+
+ control_status.merge(title_status)
+ }
+
+ pub(crate) fn overlay(
+ &mut self,
+ layout: Layout<'_>,
+ ) -> Option<overlay::Element<'_, Message, Renderer>> {
+ let mut children = layout.children();
+ let padded = children.next()?;
+
+ let mut children = padded.children();
+ let title_layout = children.next()?;
+
+ let Self {
+ content, controls, ..
+ } = self;
+
+ content.overlay(title_layout).or_else(move || {
+ controls.as_mut().and_then(|controls| {
+ let controls_layout = children.next()?;
+
+ controls.overlay(controls_layout)
+ })
+ })
}
}
diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs
index 04478225..d7792000 100644
--- a/native/src/widget/pick_list.rs
+++ b/native/src/widget/pick_list.rs
@@ -1,9 +1,16 @@
//! Display a dropdown list of selectable values.
+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::touch;
use crate::{
- layout, mouse, overlay,
- overlay::menu::{self, Menu},
- scrollable, text, Clipboard, Element, Event, Hasher, Layout, Length, Point,
- Rectangle, Size, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
+ Size, Widget,
};
use std::borrow::Cow;
@@ -14,25 +21,26 @@ where
[T]: ToOwned<Owned = Vec<T>>,
{
menu: &'a mut menu::State,
+ keyboard_modifiers: &'a mut keyboard::Modifiers,
is_open: &'a mut bool,
hovered_option: &'a mut Option<usize>,
last_selection: &'a mut Option<T>,
on_selected: Box<dyn Fn(T) -> Message>,
options: Cow<'a, [T]>,
+ placeholder: Option<String>,
selected: Option<T>,
width: Length,
- padding: u16,
+ padding: Padding,
text_size: Option<u16>,
font: Renderer::Font,
style: <Renderer as self::Renderer>::Style,
}
/// The local state of a [`PickList`].
-///
-/// [`PickList`]: struct.PickList.html
#[derive(Debug, Clone)]
pub struct State<T> {
menu: menu::State,
+ keyboard_modifiers: keyboard::Modifiers,
is_open: bool,
hovered_option: Option<usize>,
last_selection: Option<T>,
@@ -42,6 +50,7 @@ impl<T> Default for State<T> {
fn default() -> Self {
Self {
menu: menu::State::default(),
+ keyboard_modifiers: keyboard::Modifiers::default(),
is_open: bool::default(),
hovered_option: Option::default(),
last_selection: Option::default(),
@@ -52,15 +61,12 @@ impl<T> Default for State<T> {
impl<'a, T: 'a, Message, Renderer: self::Renderer>
PickList<'a, T, Message, Renderer>
where
- T: ToString,
+ T: ToString + Eq,
[T]: ToOwned<Owned = Vec<T>>,
{
/// 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.
- ///
- /// [`PickList`]: struct.PickList.html
- /// [`State`]: struct.State.html
pub fn new(
state: &'a mut State<T>,
options: impl Into<Cow<'a, [T]>>,
@@ -69,6 +75,7 @@ where
) -> Self {
let State {
menu,
+ keyboard_modifiers,
is_open,
hovered_option,
last_selection,
@@ -76,11 +83,13 @@ where
Self {
menu,
+ keyboard_modifiers,
is_open,
hovered_option,
last_selection,
on_selected: Box::new(on_selected),
options: options.into(),
+ placeholder: None,
selected,
width: Length::Shrink,
text_size: None,
@@ -90,41 +99,37 @@ where
}
}
+ /// Sets the placeholder of the [`PickList`].
+ pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
+ self.placeholder = Some(placeholder.into());
+ self
+ }
+
/// Sets the width of the [`PickList`].
- ///
- /// [`PickList`]: struct.PickList.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
- /// Sets the padding of the [`PickList`].
- ///
- /// [`PickList`]: struct.PickList.html
- pub fn padding(mut self, padding: u16) -> Self {
- self.padding = padding;
+ /// Sets the [`Padding`] of the [`PickList`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
/// Sets the text size of the [`PickList`].
- ///
- /// [`PickList`]: struct.PickList.html
pub fn text_size(mut self, size: u16) -> Self {
self.text_size = Some(size);
self
}
/// Sets the font of the [`PickList`].
- ///
- /// [`PickList`]: struct.PickList.html
pub fn font(mut self, font: Renderer::Font) -> Self {
self.font = font;
self
}
/// Sets the style of the [`PickList`].
- ///
- /// [`PickList`]: struct.PickList.html
pub fn style(
mut self,
style: impl Into<<Renderer as self::Renderer>::Style>,
@@ -143,7 +148,7 @@ where
Renderer: self::Renderer + scrollable::Renderer + 'a,
{
fn width(&self) -> Length {
- Length::Shrink
+ self.width
}
fn height(&self) -> Length {
@@ -160,27 +165,37 @@ where
let limits = limits
.width(self.width)
.height(Length::Shrink)
- .pad(f32::from(self.padding));
+ .pad(self.padding);
let text_size = self.text_size.unwrap_or(renderer.default_size());
+ let font = self.font;
let max_width = match self.width {
Length::Shrink => {
+ let measure = |label: &str| -> u32 {
+ let (width, _) = renderer.measure(
+ label,
+ text_size,
+ font,
+ Size::new(f32::INFINITY, f32::INFINITY),
+ );
+
+ width.round() as u32
+ };
+
let labels = self.options.iter().map(ToString::to_string);
- labels
- .map(|label| {
- let (width, _) = renderer.measure(
- &label,
- text_size,
- Renderer::Font::default(),
- Size::new(f32::INFINITY, f32::INFINITY),
- );
-
- width.round() as u32
- })
- .max()
- .unwrap_or(100)
+ let labels_width =
+ labels.map(|label| measure(&label)).max().unwrap_or(100);
+
+ let placeholder_width = self
+ .placeholder
+ .as_ref()
+ .map(String::as_str)
+ .map(measure)
+ .unwrap_or(100);
+
+ labels_width.max(placeholder_width)
}
_ => 0,
};
@@ -189,11 +204,11 @@ where
let intrinsic = Size::new(
max_width as f32
+ f32::from(text_size)
- + f32::from(self.padding),
+ + f32::from(self.padding.left),
f32::from(text_size),
);
- limits.resolve(intrinsic).pad(f32::from(self.padding))
+ limits.resolve(intrinsic).pad(self.padding)
};
layout::Node::new(size)
@@ -204,6 +219,8 @@ where
match self.width {
Length::Shrink => {
+ self.placeholder.hash(state);
+
self.options
.iter()
.map(ToString::to_string)
@@ -220,16 +237,19 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
_renderer: &Renderer,
- _clipboard: Option<&dyn Clipboard>,
- ) {
+ _clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
- if *self.is_open {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
+ let event_status = if *self.is_open {
// TODO: Encode cursor availability in the type system
*self.is_open =
cursor_position.x < 0.0 || cursor_position.y < 0.0;
+
+ event::Status::Captured
} else if layout.bounds().contains(cursor_position) {
let selected = self.selected.as_ref();
@@ -238,15 +258,65 @@ where
.options
.iter()
.position(|option| Some(option) == selected);
- }
+
+ event::Status::Captured
+ } else {
+ event::Status::Ignored
+ };
if let Some(last_selection) = self.last_selection.take() {
messages.push((self.on_selected)(last_selection));
*self.is_open = false;
+
+ event::Status::Captured
+ } else {
+ event_status
+ }
+ }
+ Event::Mouse(mouse::Event::WheelScrolled {
+ delta: mouse::ScrollDelta::Lines { y, .. },
+ }) if self.keyboard_modifiers.command()
+ && layout.bounds().contains(cursor_position)
+ && !*self.is_open =>
+ {
+ fn find_next<'a, T: PartialEq>(
+ selected: &'a T,
+ mut options: impl Iterator<Item = &'a T>,
+ ) -> Option<&'a T> {
+ let _ = options.find(|&option| option == selected);
+
+ options.next()
}
+
+ let next_option = if y < 0.0 {
+ if let Some(selected) = self.selected.as_ref() {
+ find_next(selected, self.options.iter())
+ } else {
+ self.options.first()
+ }
+ } else if y > 0.0 {
+ if let Some(selected) = self.selected.as_ref() {
+ find_next(selected, self.options.iter().rev())
+ } else {
+ self.options.last()
+ }
+ } else {
+ None
+ };
+
+ if let Some(next_option) = next_option {
+ messages.push((self.on_selected)(next_option.clone()));
+ }
+
+ event::Status::Captured
+ }
+ Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
+ *self.keyboard_modifiers = modifiers;
+
+ event::Status::Ignored
}
- _ => {}
+ _ => event::Status::Ignored,
}
}
@@ -256,12 +326,14 @@ where
_defaults: &Renderer::Defaults,
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,
@@ -303,36 +375,27 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`PickList`] in your user interface.
///
-/// [`PickList`]: struct.PickList.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: text::Renderer + menu::Renderer {
/// The default padding of a [`PickList`].
- ///
- /// [`PickList`]: struct.PickList.html
- const DEFAULT_PADDING: u16;
+ const DEFAULT_PADDING: Padding;
/// The [`PickList`] style supported by this renderer.
- ///
- /// [`PickList`]: struct.PickList.html
type Style: Default;
/// Returns the style of the [`Menu`] of the [`PickList`].
- ///
- /// [`Menu`]: ../../overlay/menu/struct.Menu.html
- /// [`PickList`]: struct.PickList.html
fn menu_style(
style: &<Self as Renderer>::Style,
) -> <Self as menu::Renderer>::Style;
/// Draws a [`PickList`].
- ///
- /// [`PickList`]: struct.PickList.html
fn draw(
&mut self,
bounds: Rectangle,
cursor_position: Point,
selected: Option<String>,
- padding: u16,
+ placeholder: Option<&str>,
+ padding: Padding,
text_size: u16,
font: Self::Font,
style: &<Self as Renderer>::Style,
diff --git a/native/src/widget/progress_bar.rs b/native/src/widget/progress_bar.rs
index 5ab76d47..d294f198 100644
--- a/native/src/widget/progress_bar.rs
+++ b/native/src/widget/progress_bar.rs
@@ -33,8 +33,6 @@ impl<Renderer: self::Renderer> ProgressBar<Renderer> {
/// It expects:
/// * an inclusive range of possible values
/// * the current value of the [`ProgressBar`]
- ///
- /// [`ProgressBar`]: struct.ProgressBar.html
pub fn new(range: RangeInclusive<f32>, value: f32) -> Self {
ProgressBar {
value: value.max(*range.start()).min(*range.end()),
@@ -46,24 +44,18 @@ impl<Renderer: self::Renderer> ProgressBar<Renderer> {
}
/// Sets the width of the [`ProgressBar`].
- ///
- /// [`ProgressBar`]: struct.ProgressBar.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`ProgressBar`].
- ///
- /// [`ProgressBar`]: struct.ProgressBar.html
pub fn height(mut self, height: Length) -> Self {
self.height = Some(height);
self
}
/// Sets the style of the [`ProgressBar`].
- ///
- /// [`ProgressBar`]: struct.ProgressBar.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
@@ -104,6 +96,7 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(
layout.bounds(),
@@ -127,15 +120,12 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`ProgressBar`] in your user interface.
///
-/// [`ProgressBar`]: struct.ProgressBar.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
/// The default height of a [`ProgressBar`].
- ///
- /// [`ProgressBar`]: struct.ProgressBar.html
const DEFAULT_HEIGHT: u16;
/// Draws a [`ProgressBar`].
@@ -146,8 +136,6 @@ pub trait Renderer: crate::Renderer {
/// * the current value of the [`ProgressBar`]
/// * maybe a specific background of the [`ProgressBar`]
/// * maybe a specific active color of the [`ProgressBar`]
- ///
- /// [`ProgressBar`]: struct.ProgressBar.html
fn draw(
&self,
bounds: Rectangle,
diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs
index 5b8d00e9..dee82d1f 100644
--- a/native/src/widget/radio.rs
+++ b/native/src/widget/radio.rs
@@ -1,12 +1,17 @@
//! Create choices using radio buttons.
+use std::hash::Hash;
+
+use crate::event::{self, Event};
+use crate::mouse;
+use crate::row;
+use crate::text;
+use crate::touch;
+use crate::{layout, Color};
use crate::{
- layout, mouse, row, text, Align, Clipboard, Element, Event, Hasher,
- HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text,
- VerticalAlignment, Widget,
+ Align, Clipboard, Element, Hasher, HorizontalAlignment, Layout, Length,
+ Point, Rectangle, Row, Text, VerticalAlignment, Widget,
};
-use std::hash::Hash;
-
/// A circular button representing a choice.
///
/// # Example
@@ -42,11 +47,15 @@ pub struct Radio<Message, Renderer: self::Renderer + text::Renderer> {
size: u16,
spacing: u16,
text_size: Option<u16>,
+ text_color: Option<Color>,
+ font: Renderer::Font,
style: Renderer::Style,
}
impl<Message, Renderer: self::Renderer + text::Renderer>
Radio<Message, Renderer>
+where
+ Message: Clone,
{
/// Creates a new [`Radio`] button.
///
@@ -56,8 +65,6 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
/// * the current selected value
/// * a function that will be called when the [`Radio`] is selected. It
/// receives the value of the radio and must produce a `Message`.
- ///
- /// [`Radio`]: struct.Radio.html
pub fn new<F, V>(
value: V,
label: impl Into<String>,
@@ -76,45 +83,49 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
size: <Renderer as self::Renderer>::DEFAULT_SIZE,
spacing: Renderer::DEFAULT_SPACING, //15
text_size: None,
+ text_color: None,
+ font: Default::default(),
style: Renderer::Style::default(),
}
}
/// Sets the size of the [`Radio`] button.
- ///
- /// [`Radio`]: struct.Radio.html
pub fn size(mut self, size: u16) -> Self {
self.size = size;
self
}
/// Sets the width of the [`Radio`] button.
- ///
- /// [`Radio`]: struct.Radio.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the spacing between the [`Radio`] button and the text.
- ///
- /// [`Radio`]: struct.Radio.html
pub fn spacing(mut self, spacing: u16) -> Self {
self.spacing = spacing;
self
}
/// Sets the text size of the [`Radio`] button.
- ///
- /// [`Radio`]: struct.Radio.html
pub fn text_size(mut self, text_size: u16) -> Self {
self.text_size = Some(text_size);
self
}
+ /// Sets the text color of the [`Radio`] button.
+ pub fn text_color(mut self, color: Color) -> Self {
+ self.text_color = Some(color);
+ self
+ }
+
+ /// Sets the text font of the [`Radio`] button.
+ pub fn font(mut self, font: Renderer::Font) -> Self {
+ self.font = font;
+ self
+ }
+
/// Sets the style of the [`Radio`] button.
- ///
- /// [`Radio`]: struct.Radio.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
@@ -123,8 +134,8 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
impl<Message, Renderer> Widget<Message, Renderer> for Radio<Message, Renderer>
where
- Renderer: self::Renderer + text::Renderer + row::Renderer,
Message: Clone,
+ Renderer: self::Renderer + text::Renderer + row::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -161,18 +172,23 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
_renderer: &Renderer,
- _clipboard: Option<&dyn Clipboard>,
- ) {
+ _clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
if layout.bounds().contains(cursor_position) {
messages.push(self.on_click.clone());
+
+ return event::Status::Captured;
}
}
_ => {}
}
+
+ event::Status::Ignored
}
fn draw(
@@ -181,6 +197,7 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
let bounds = layout.bounds();
let mut children = layout.children();
@@ -195,8 +212,8 @@ where
label_layout.bounds(),
&self.label,
self.text_size.unwrap_or(renderer.default_size()),
- Default::default(),
- None,
+ self.font,
+ self.text_color,
HorizontalAlignment::Left,
VerticalAlignment::Center,
);
@@ -226,20 +243,15 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Radio`] button in your user interface.
///
-/// [`Radio`]: struct.Radio.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
/// The default size of a [`Radio`] button.
- ///
- /// [`Radio`]: struct.Radio.html
const DEFAULT_SIZE: u16;
/// The default spacing of a [`Radio`] button.
- ///
- /// [`Radio`]: struct.Radio.html
const DEFAULT_SPACING: u16;
/// Draws a [`Radio`] button.
@@ -249,8 +261,6 @@ pub trait Renderer: crate::Renderer {
/// * whether the [`Radio`] is selected or not
/// * whether the mouse is over the [`Radio`] or not
/// * the drawn label of the [`Radio`]
- ///
- /// [`Radio`]: struct.Radio.html
fn draw(
&mut self,
bounds: Rectangle,
@@ -264,8 +274,8 @@ pub trait Renderer: crate::Renderer {
impl<'a, Message, Renderer> From<Radio<Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer + row::Renderer + text::Renderer,
Message: 'a + Clone,
+ Renderer: 'a + self::Renderer + row::Renderer + text::Renderer,
{
fn from(radio: Radio<Message, Renderer>) -> Element<'a, Message, Renderer> {
Element::new(radio)
diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs
index 2b6db224..9ebc9145 100644
--- a/native/src/widget/row.rs
+++ b/native/src/widget/row.rs
@@ -1,22 +1,20 @@
//! Distribute content horizontally.
-use std::hash::Hash;
-
+use crate::event::{self, Event};
+use crate::layout;
+use crate::overlay;
use crate::{
- layout, overlay, Align, Clipboard, Element, Event, Hasher, Layout, Length,
- Point, Widget,
+ Align, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
+ Rectangle, Widget,
};
+use std::hash::Hash;
use std::u32;
/// A container that distributes its contents horizontally.
-///
-/// A [`Row`] will try to fill the horizontal space of its container.
-///
-/// [`Row`]: struct.Row.html
#[allow(missing_debug_implementations)]
pub struct Row<'a, Message, Renderer> {
spacing: u16,
- padding: u16,
+ padding: Padding,
width: Length,
height: Length,
max_width: u32,
@@ -27,21 +25,17 @@ pub struct Row<'a, Message, Renderer> {
impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
/// Creates an empty [`Row`].
- ///
- /// [`Row`]: struct.Row.html
pub fn new() -> Self {
Self::with_children(Vec::new())
}
/// Creates a [`Row`] with the given elements.
- ///
- /// [`Row`]: struct.Row.html
pub fn with_children(
children: Vec<Element<'a, Message, Renderer>>,
) -> Self {
Row {
spacing: 0,
- padding: 0,
+ padding: Padding::ZERO,
width: Length::Shrink,
height: Length::Shrink,
max_width: u32::MAX,
@@ -61,58 +55,43 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
self
}
- /// Sets the padding of the [`Row`].
- ///
- /// [`Row`]: struct.Row.html
- pub fn padding(mut self, units: u16) -> Self {
- self.padding = units;
+ /// Sets the [`Padding`] of the [`Row`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
/// Sets the width of the [`Row`].
- ///
- /// [`Row`]: struct.Row.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Row`].
- ///
- /// [`Row`]: struct.Row.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Row`].
- ///
- /// [`Row`]: struct.Row.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
self
}
/// Sets the maximum height of the [`Row`].
- ///
- /// [`Row`]: struct.Row.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the vertical alignment of the contents of the [`Row`] .
- ///
- /// [`Row`]: struct.Row.html
pub fn align_items(mut self, align: Align) -> Self {
self.align_items = align;
self
}
/// Adds an [`Element`] to the [`Row`].
- ///
- /// [`Element`]: ../struct.Element.html
- /// [`Row`]: struct.Row.html
pub fn push<E>(mut self, child: E) -> Self
where
E: Into<Element<'a, Message, Renderer>>,
@@ -150,7 +129,7 @@ where
layout::flex::Axis::Horizontal,
renderer,
&limits,
- self.padding as f32,
+ self.padding,
self.spacing as f32,
self.align_items,
&self.children,
@@ -162,22 +141,24 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
renderer: &Renderer,
- clipboard: Option<&dyn Clipboard>,
- ) {
- self.children.iter_mut().zip(layout.children()).for_each(
- |(child, layout)| {
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
+ self.children
+ .iter_mut()
+ .zip(layout.children())
+ .map(|(child, layout)| {
child.widget.on_event(
event.clone(),
layout,
cursor_position,
- messages,
renderer,
clipboard,
+ messages,
)
- },
- );
+ })
+ .fold(event::Status::Ignored, event::Status::merge)
}
fn draw(
@@ -186,8 +167,15 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Renderer::Output {
- renderer.draw(defaults, &self.children, layout, cursor_position)
+ renderer.draw(
+ defaults,
+ &self.children,
+ layout,
+ cursor_position,
+ viewport,
+ )
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -200,7 +188,7 @@ where
self.max_height.hash(state);
self.align_items.hash(state);
self.spacing.hash(state);
- self.spacing.hash(state);
+ self.padding.hash(state);
for child in &self.children {
child.widget.hash_layout(state);
@@ -224,8 +212,7 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Row`] in your user interface.
///
-/// [`Row`]: struct.Row.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer + Sized {
/// Draws a [`Row`].
///
@@ -233,15 +220,13 @@ pub trait Renderer: crate::Renderer + Sized {
/// - the children of the [`Row`]
/// - the [`Layout`] of the [`Row`] and its children
/// - the cursor position
- ///
- /// [`Row`]: struct.Row.html
- /// [`Layout`]: ../layout/struct.Layout.html
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
children: &[Element<'_, Message, Self>],
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Self::Output;
}
diff --git a/native/src/widget/rule.rs b/native/src/widget/rule.rs
index 25cec53b..18c88658 100644
--- a/native/src/widget/rule.rs
+++ b/native/src/widget/rule.rs
@@ -17,8 +17,6 @@ pub struct Rule<Renderer: self::Renderer> {
impl<Renderer: self::Renderer> Rule<Renderer> {
/// Creates a horizontal [`Rule`] for dividing content by the given vertical spacing.
- ///
- /// [`Rule`]: struct.Rule.html
pub fn horizontal(spacing: u16) -> Self {
Rule {
width: Length::Fill,
@@ -29,8 +27,6 @@ impl<Renderer: self::Renderer> Rule<Renderer> {
}
/// Creates a vertical [`Rule`] for dividing content by the given horizontal spacing.
- ///
- /// [`Rule`]: struct.Rule.html
pub fn vertical(spacing: u16) -> Self {
Rule {
width: Length::from(Length::Units(spacing)),
@@ -41,8 +37,6 @@ impl<Renderer: self::Renderer> Rule<Renderer> {
}
/// Sets the style of the [`Rule`].
- ///
- /// [`Rule`]: struct.Rule.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
@@ -77,6 +71,7 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(layout.bounds(), &self.style, self.is_horizontal)
}
@@ -91,8 +86,6 @@ where
}
/// The renderer of a [`Rule`].
-///
-/// [`Rule`]: struct.Rule.html
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
@@ -103,8 +96,6 @@ pub trait Renderer: crate::Renderer {
/// * the bounds of the [`Rule`]
/// * the style of the [`Rule`]
/// * whether the [`Rule`] is horizontal (true) or vertical (false)
- ///
- /// [`Rule`]: struct.Rule.html
fn draw(
&mut self,
bounds: Rectangle,
diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs
index 75e97027..68da2e67 100644
--- a/native/src/widget/scrollable.rs
+++ b/native/src/widget/scrollable.rs
@@ -1,7 +1,13 @@
//! 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::touch;
use crate::{
- column, layout, mouse, overlay, Align, Clipboard, Column, Element, Event,
- Hasher, Layout, Length, Point, Rectangle, Size, Vector, Widget,
+ Align, Clipboard, Column, Element, Hasher, Layout, Length, Padding, Point,
+ Rectangle, Size, Vector, Widget,
};
use std::{f32, hash::Hash, u32};
@@ -13,21 +19,26 @@ pub struct Scrollable<'a, Message, Renderer: self::Renderer> {
state: &'a mut State,
height: Length,
max_height: u32,
+ scrollbar_width: u16,
+ scrollbar_margin: u16,
+ scroller_width: u16,
content: Column<'a, Message, Renderer>,
+ on_scroll: Option<Box<dyn Fn(f32) -> Message>>,
style: Renderer::Style,
}
impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
/// Creates a new [`Scrollable`] with the given [`State`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
- /// [`State`]: struct.State.html
pub fn new(state: &'a mut State) -> Self {
Scrollable {
state,
height: Length::Shrink,
max_height: u32::MAX,
+ scrollbar_width: 10,
+ scrollbar_margin: 0,
+ scroller_width: 10,
content: Column::new(),
+ on_scroll: None,
style: Renderer::Style::default(),
}
}
@@ -42,65 +53,79 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
self
}
- /// Sets the padding of the [`Scrollable`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
- pub fn padding(mut self, units: u16) -> Self {
- self.content = self.content.padding(units);
+ /// Sets the [`Padding`] of the [`Scrollable`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.content = self.content.padding(padding);
self
}
/// Sets the width of the [`Scrollable`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn width(mut self, width: Length) -> Self {
self.content = self.content.width(width);
self
}
/// Sets the height of the [`Scrollable`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Scrollable`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.content = self.content.max_width(max_width);
self
}
/// Sets the maximum height of the [`Scrollable`] in pixels.
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the horizontal alignment of the contents of the [`Scrollable`] .
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn align_items(mut self, align_items: Align) -> Self {
self.content = self.content.align_items(align_items);
self
}
- /// Sets the style of the [`Scrollable`] .
+ /// Sets the scrollbar width of the [`Scrollable`] .
+ /// Silently enforces a minimum value of 1.
+ pub fn scrollbar_width(mut self, scrollbar_width: u16) -> Self {
+ self.scrollbar_width = scrollbar_width.max(1);
+ self
+ }
+
+ /// Sets the scrollbar margin of the [`Scrollable`] .
+ pub fn scrollbar_margin(mut self, scrollbar_margin: u16) -> Self {
+ self.scrollbar_margin = scrollbar_margin;
+ self
+ }
+
+ /// Sets the scroller width of the [`Scrollable`] .
///
- /// [`Scrollable`]: struct.Scrollable.html
+ /// It silently enforces a minimum value of 1.
+ pub fn scroller_width(mut self, scroller_width: u16) -> Self {
+ self.scroller_width = scroller_width.max(1);
+ self
+ }
+
+ /// Sets a function to call when the [`Scrollable`] is scrolled.
+ ///
+ /// The function takes the new relative offset of the [`Scrollable`]
+ /// (e.g. `0` means top, while `1` means bottom).
+ pub fn on_scroll(mut self, f: impl Fn(f32) -> Message + 'static) -> Self {
+ self.on_scroll = Some(Box::new(f));
+ self
+ }
+
+ /// Sets the style of the [`Scrollable`] .
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
}
/// Adds an element to the [`Scrollable`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn push<E>(mut self, child: E) -> Self
where
E: Into<Element<'a, Message, Renderer>>,
@@ -108,6 +133,24 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
self.content = self.content.push(child);
self
}
+
+ fn notify_on_scroll(
+ &self,
+ bounds: Rectangle,
+ content_bounds: Rectangle,
+ messages: &mut Vec<Message>,
+ ) {
+ if content_bounds.height <= bounds.height {
+ return;
+ }
+
+ if let Some(on_scroll) = &self.on_scroll {
+ messages.push(on_scroll(
+ self.state.offset.absolute(bounds, content_bounds)
+ / (content_bounds.height - bounds.height),
+ ));
+ }
+ }
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
@@ -149,17 +192,59 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
renderer: &Renderer,
- clipboard: Option<&dyn Clipboard>,
- ) {
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
let content = layout.children().next().unwrap();
let content_bounds = content.bounds();
- // TODO: Event capture. Nested scrollables should capture scroll events.
+ 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 is_mouse_over_scrollbar = scrollbar
+ .as_ref()
+ .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
+ .unwrap_or(false);
+
+ let event_status = {
+ let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
+ Point::new(
+ cursor_position.x,
+ cursor_position.y
+ + self.state.offset(bounds, content_bounds) as f32,
+ )
+ } else {
+ // TODO: Make `cursor_position` an `Option<Point>` so we can encode
+ // cursor availability.
+ // This will probably happen naturally once we add multi-window
+ // support.
+ Point::new(cursor_position.x, -1.0)
+ };
+
+ self.content.on_event(
+ event.clone(),
+ content,
+ cursor_position,
+ renderer,
+ clipboard,
+ messages,
+ )
+ };
+
+ if let event::Status::Captured = event_status {
+ return event::Status::Captured;
+ }
+
if is_mouse_over {
match event {
Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
@@ -172,26 +257,65 @@ where
self.state.scroll(y, bounds, content_bounds);
}
}
+
+ self.notify_on_scroll(bounds, content_bounds, messages);
+
+ return event::Status::Captured;
+ }
+ Event::Touch(event) => {
+ match event {
+ touch::Event::FingerPressed { .. } => {
+ self.state.scroll_box_touched_at =
+ Some(cursor_position);
+ }
+ touch::Event::FingerMoved { .. } => {
+ if let Some(scroll_box_touched_at) =
+ self.state.scroll_box_touched_at
+ {
+ let delta =
+ cursor_position.y - scroll_box_touched_at.y;
+
+ self.state.scroll(
+ delta,
+ bounds,
+ content_bounds,
+ );
+
+ self.state.scroll_box_touched_at =
+ Some(cursor_position);
+
+ self.notify_on_scroll(
+ bounds,
+ content_bounds,
+ messages,
+ );
+ }
+ }
+ touch::Event::FingerLifted { .. }
+ | touch::Event::FingerLost { .. } => {
+ self.state.scroll_box_touched_at = None;
+ }
+ }
+
+ return event::Status::Captured;
}
_ => {}
}
}
- let offset = self.state.offset(bounds, content_bounds);
- let scrollbar = renderer.scrollbar(bounds, content_bounds, offset);
- let is_mouse_over_scrollbar = scrollbar
- .as_ref()
- .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
- .unwrap_or(false);
-
if self.state.is_scroller_grabbed() {
match event {
Event::Mouse(mouse::Event::ButtonReleased(
mouse::Button::Left,
- )) => {
+ ))
+ | Event::Touch(touch::Event::FingerLifted { .. })
+ | Event::Touch(touch::Event::FingerLost { .. }) => {
self.state.scroller_grabbed_at = None;
+
+ return event::Status::Captured;
}
- Event::Mouse(mouse::Event::CursorMoved { .. }) => {
+ Event::Mouse(mouse::Event::CursorMoved { .. })
+ | Event::Touch(touch::Event::FingerMoved { .. }) => {
if let (Some(scrollbar), Some(scroller_grabbed_at)) =
(scrollbar, self.state.scroller_grabbed_at)
{
@@ -203,6 +327,10 @@ where
bounds,
content_bounds,
);
+
+ self.notify_on_scroll(bounds, content_bounds, messages);
+
+ return event::Status::Captured;
}
}
_ => {}
@@ -211,7 +339,8 @@ where
match event {
Event::Mouse(mouse::Event::ButtonPressed(
mouse::Button::Left,
- )) => {
+ ))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
if let Some(scrollbar) = scrollbar {
if let Some(scroller_grabbed_at) =
scrollbar.grab_scroller(cursor_position)
@@ -227,6 +356,14 @@ where
self.state.scroller_grabbed_at =
Some(scroller_grabbed_at);
+
+ self.notify_on_scroll(
+ bounds,
+ content_bounds,
+ messages,
+ );
+
+ return event::Status::Captured;
}
}
}
@@ -234,28 +371,7 @@ where
}
}
- let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
- Point::new(
- cursor_position.x,
- cursor_position.y
- + self.state.offset(bounds, content_bounds) as f32,
- )
- } else {
- // TODO: Make `cursor_position` an `Option<Point>` so we can encode
- // cursor availability.
- // This will probably happen naturally once we add multi-window
- // support.
- Point::new(cursor_position.x, -1.0)
- };
-
- self.content.on_event(
- event,
- content,
- cursor_position,
- messages,
- renderer,
- clipboard,
- )
+ event::Status::Ignored
}
fn draw(
@@ -264,12 +380,20 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
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);
+ let scrollbar = renderer.scrollbar(
+ bounds,
+ content_bounds,
+ offset,
+ self.scrollbar_width,
+ self.scrollbar_margin,
+ self.scroller_width,
+ );
let is_mouse_over = bounds.contains(cursor_position);
let is_mouse_over_scrollbar = scrollbar
@@ -289,6 +413,10 @@ where
defaults,
content_layout,
cursor_position,
+ &Rectangle {
+ y: bounds.y + offset as f32,
+ ..bounds
+ },
)
};
@@ -336,27 +464,54 @@ where
}
/// The local state of a [`Scrollable`].
-///
-/// [`Scrollable`]: struct.Scrollable.html
-#[derive(Debug, Clone, Copy, Default)]
+#[derive(Debug, Clone, Copy)]
pub struct State {
scroller_grabbed_at: Option<f32>,
- offset: f32,
+ scroll_box_touched_at: Option<Point>,
+ offset: Offset,
+}
+
+impl Default for State {
+ fn default() -> Self {
+ Self {
+ scroller_grabbed_at: None,
+ scroll_box_touched_at: None,
+ offset: Offset::Absolute(0.0),
+ }
+ }
+}
+
+/// The local state of a [`Scrollable`].
+#[derive(Debug, Clone, Copy)]
+enum Offset {
+ Absolute(f32),
+ Relative(f32),
+}
+
+impl Offset {
+ fn absolute(self, bounds: Rectangle, content_bounds: Rectangle) -> f32 {
+ match self {
+ Self::Absolute(absolute) => {
+ let hidden_content =
+ (content_bounds.height - bounds.height).max(0.0);
+
+ absolute.min(hidden_content)
+ }
+ Self::Relative(percentage) => {
+ ((content_bounds.height - bounds.height) * percentage).max(0.0)
+ }
+ }
+ }
}
impl State {
/// Creates a new [`State`] with the scrollbar located at the top.
- ///
- /// [`State`]: struct.State.html
pub fn new() -> Self {
State::default()
}
/// Apply a scrolling offset to the current [`State`], given the bounds of
/// the [`Scrollable`] and its contents.
- ///
- /// [`Scrollable`]: struct.Scrollable.html
- /// [`State`]: struct.State.html
pub fn scroll(
&mut self,
delta_y: f32,
@@ -367,70 +522,83 @@ impl State {
return;
}
- self.offset = (self.offset - delta_y)
- .max(0.0)
- .min((content_bounds.height - bounds.height) as f32);
+ self.offset = Offset::Absolute(
+ (self.offset.absolute(bounds, content_bounds) - delta_y)
+ .max(0.0)
+ .min((content_bounds.height - bounds.height) as f32),
+ );
}
- /// Moves the scroll position to a relative amount, given the bounds of
- /// the [`Scrollable`] and its contents.
+ /// Scrolls the [`Scrollable`] to a relative amount.
///
/// `0` represents scrollbar at the top, while `1` represents scrollbar at
/// the bottom.
- ///
- /// [`Scrollable`]: struct.Scrollable.html
- /// [`State`]: struct.State.html
pub fn scroll_to(
&mut self,
percentage: f32,
bounds: Rectangle,
content_bounds: Rectangle,
) {
+ self.snap_to(percentage);
+ self.unsnap(bounds, content_bounds);
+ }
+
+ /// Snaps the scroll position to a relative amount.
+ ///
+ /// `0` represents scrollbar at the top, while `1` represents scrollbar at
+ /// the bottom.
+ pub fn snap_to(&mut self, percentage: f32) {
+ self.offset = Offset::Relative(percentage.max(0.0).min(1.0));
+ }
+
+ /// Unsnaps the current scroll position, if snapped, given the bounds of the
+ /// [`Scrollable`] and its contents.
+ pub fn unsnap(&mut self, bounds: Rectangle, content_bounds: Rectangle) {
self.offset =
- ((content_bounds.height - bounds.height) * percentage).max(0.0);
+ Offset::Absolute(self.offset.absolute(bounds, content_bounds));
}
/// Returns the current scrolling offset of the [`State`], given the bounds
/// of the [`Scrollable`] and its contents.
- ///
- /// [`Scrollable`]: struct.Scrollable.html
- /// [`State`]: struct.State.html
pub fn offset(&self, bounds: Rectangle, content_bounds: Rectangle) -> u32 {
- let hidden_content =
- (content_bounds.height - bounds.height).max(0.0).round() as u32;
-
- self.offset.min(hidden_content as f32) as u32
+ self.offset.absolute(bounds, content_bounds) as u32
}
/// Returns whether the scroller is currently grabbed or not.
pub fn is_scroller_grabbed(&self) -> bool {
self.scroller_grabbed_at.is_some()
}
+
+ /// Returns whether the scroll box is currently touched or not.
+ pub fn is_scroll_box_touched(&self) -> bool {
+ self.scroll_box_touched_at.is_some()
+ }
}
/// The scrollbar of a [`Scrollable`].
-///
-/// [`Scrollable`]: struct.Scrollable.html
#[derive(Debug)]
pub struct Scrollbar {
+ /// The outer bounds of the scrollable, including the [`Scrollbar`] and
+ /// [`Scroller`].
+ pub outer_bounds: Rectangle,
+
/// The bounds of the [`Scrollbar`].
- ///
- /// [`Scrollbar`]: struct.Scrollbar.html
pub bounds: Rectangle,
+ /// The margin within the [`Scrollbar`].
+ pub margin: u16,
+
/// The bounds of the [`Scroller`].
- ///
- /// [`Scroller`]: struct.Scroller.html
pub scroller: Scroller,
}
impl Scrollbar {
fn is_mouse_over(&self, cursor_position: Point) -> bool {
- self.bounds.contains(cursor_position)
+ self.outer_bounds.contains(cursor_position)
}
fn grab_scroller(&self, cursor_position: Point) -> Option<f32> {
- if self.bounds.contains(cursor_position) {
+ if self.outer_bounds.contains(cursor_position) {
Some(if self.scroller.bounds.contains(cursor_position) {
(cursor_position.y - self.scroller.bounds.y)
/ self.scroller.bounds.height
@@ -455,13 +623,9 @@ impl Scrollbar {
}
/// The handle of a [`Scrollbar`].
-///
-/// [`Scrollbar`]: struct.Scrollbar.html
#[derive(Debug, Clone, Copy)]
pub struct Scroller {
/// The bounds of the [`Scroller`].
- ///
- /// [`Scroller`]: struct.Scrollbar.html
pub bounds: Rectangle,
}
@@ -470,22 +634,21 @@ pub struct Scroller {
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Scrollable`] in your user interface.
///
-/// [`Scrollable`]: struct.Scrollable.html
-/// [renderer]: ../../renderer/index.html
+/// [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`].
- ///
- /// [`Scrollbar`]: struct.Scrollbar.html
- /// [`Scrollable`]: struct.Scrollable.html
fn scrollbar(
&self,
bounds: Rectangle,
content_bounds: Rectangle,
offset: u32,
+ scrollbar_width: u16,
+ scrollbar_margin: u16,
+ scroller_width: u16,
) -> Option<Scrollbar>;
/// Draws the [`Scrollable`].
@@ -499,10 +662,6 @@ pub trait Renderer: column::Renderer + Sized {
/// - a optional [`Scrollbar`] to be rendered
/// - the scrolling offset
/// - the drawn content
- ///
- /// [`Scrollbar`]: struct.Scrollbar.html
- /// [`Scrollable`]: struct.Scrollable.html
- /// [`State`]: struct.State.html
fn draw(
&mut self,
scrollable: &State,
diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs
index c49053f1..2a74d5a3 100644
--- a/native/src/widget/slider.rs
+++ b/native/src/widget/slider.rs
@@ -1,12 +1,12 @@
//! Display an interactive selector of a single value from a range of values.
//!
//! A [`Slider`] has some local [`State`].
-//!
-//! [`Slider`]: struct.Slider.html
-//! [`State`]: struct.State.html
+use crate::event::{self, Event};
+use crate::layout;
+use crate::mouse;
+use crate::touch;
use crate::{
- layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, Point,
- Rectangle, Size, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
};
use std::{hash::Hash, ops::RangeInclusive};
@@ -19,13 +19,12 @@ use std::{hash::Hash, ops::RangeInclusive};
/// The [`Slider`] range of numeric values is generic and its step size defaults
/// to 1 unit.
///
-/// [`Slider`]: struct.Slider.html
-///
/// # Example
/// ```
/// # use iced_native::{slider, renderer::Null};
/// #
/// # pub type Slider<'a, T, Message> = iced_native::Slider<'a, T, Message, Null>;
+/// #[derive(Clone)]
/// pub enum Message {
/// SliderChanged(f32),
/// }
@@ -53,6 +52,7 @@ pub struct Slider<'a, T, Message, Renderer: self::Renderer> {
impl<'a, T, Message, Renderer> Slider<'a, T, Message, Renderer>
where
T: Copy + From<u8> + std::cmp::PartialOrd,
+ Message: Clone,
Renderer: self::Renderer,
{
/// Creates a new [`Slider`].
@@ -64,9 +64,6 @@ where
/// * a function that will be called when the [`Slider`] is dragged.
/// It receives the new value of the [`Slider`] and must produce a
/// `Message`.
- ///
- /// [`Slider`]: struct.Slider.html
- /// [`State`]: struct.State.html
pub fn new<F>(
state: &'a mut State,
range: RangeInclusive<T>,
@@ -107,40 +104,30 @@ where
/// Typically, the user's interaction with the slider is finished when this message is produced.
/// This is useful if you need to spawn a long-running task from the slider's result, where
/// the default on_change message could create too many events.
- ///
- /// [`Slider`]: struct.Slider.html
pub fn on_release(mut self, on_release: Message) -> Self {
self.on_release = Some(on_release);
self
}
/// Sets the width of the [`Slider`].
- ///
- /// [`Slider`]: struct.Slider.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Slider`].
- ///
- /// [`Slider`]: struct.Slider.html
pub fn height(mut self, height: u16) -> Self {
self.height = height;
self
}
/// Sets the style of the [`Slider`].
- ///
- /// [`Slider`]: struct.Slider.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
}
/// Sets the step size of the [`Slider`].
- ///
- /// [`Slider`]: struct.Slider.html
pub fn step(mut self, step: T) -> Self {
self.step = step;
self
@@ -148,8 +135,6 @@ where
}
/// The local state of a [`Slider`].
-///
-/// [`Slider`]: struct.Slider.html
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct State {
is_dragging: bool,
@@ -157,8 +142,6 @@ pub struct State {
impl State {
/// Creates a new [`State`].
- ///
- /// [`State`]: struct.State.html
pub fn new() -> State {
State::default()
}
@@ -168,8 +151,8 @@ impl<'a, T, Message, Renderer> Widget<Message, Renderer>
for Slider<'a, T, Message, Renderer>
where
T: Copy + Into<f64> + num_traits::FromPrimitive,
- Renderer: self::Renderer,
Message: Clone,
+ Renderer: self::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -197,10 +180,10 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
_renderer: &Renderer,
- _clipboard: Option<&dyn Clipboard>,
- ) {
+ _clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
let mut change = || {
let bounds = layout.bounds();
if cursor_position.x <= bounds.x {
@@ -225,30 +208,39 @@ where
};
match event {
- Event::Mouse(mouse_event) => match mouse_event {
- mouse::Event::ButtonPressed(mouse::Button::Left) => {
- if layout.bounds().contains(cursor_position) {
- change();
- self.state.is_dragging = true;
- }
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
+ if layout.bounds().contains(cursor_position) {
+ change();
+ self.state.is_dragging = true;
+
+ return event::Status::Captured;
}
- mouse::Event::ButtonReleased(mouse::Button::Left) => {
- if self.state.is_dragging {
- if let Some(on_release) = self.on_release.clone() {
- messages.push(on_release);
- }
- self.state.is_dragging = false;
+ }
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerLifted { .. })
+ | Event::Touch(touch::Event::FingerLost { .. }) => {
+ if self.state.is_dragging {
+ if let Some(on_release) = self.on_release.clone() {
+ messages.push(on_release);
}
+ self.state.is_dragging = false;
+
+ return event::Status::Captured;
}
- mouse::Event::CursorMoved { .. } => {
- if self.state.is_dragging {
- change();
- }
+ }
+ Event::Mouse(mouse::Event::CursorMoved { .. })
+ | Event::Touch(touch::Event::FingerMoved { .. }) => {
+ if self.state.is_dragging {
+ change();
+
+ return event::Status::Captured;
}
- _ => {}
- },
+ }
_ => {}
}
+
+ event::Status::Ignored
}
fn draw(
@@ -257,6 +249,7 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
let start = *self.range.start();
let end = *self.range.end();
@@ -284,15 +277,12 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Slider`] in your user interface.
///
-/// [`Slider`]: struct.Slider.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
/// The default height of a [`Slider`].
- ///
- /// [`Slider`]: struct.Slider.html
const DEFAULT_HEIGHT: u16;
/// Draws a [`Slider`].
@@ -303,10 +293,6 @@ pub trait Renderer: crate::Renderer {
/// * the local state of the [`Slider`]
/// * the range of values of the [`Slider`]
/// * the current value of the [`Slider`]
- ///
- /// [`Slider`]: struct.Slider.html
- /// [`State`]: struct.State.html
- /// [`Class`]: enum.Class.html
fn draw(
&mut self,
bounds: Rectangle,
@@ -322,8 +308,8 @@ impl<'a, T, Message, Renderer> From<Slider<'a, T, Message, Renderer>>
for Element<'a, Message, Renderer>
where
T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,
- Renderer: 'a + self::Renderer,
Message: 'a + Clone,
+ Renderer: 'a + self::Renderer,
{
fn from(
slider: Slider<'a, T, Message, Renderer>,
diff --git a/native/src/widget/space.rs b/native/src/widget/space.rs
index f1576ffb..6b34ece8 100644
--- a/native/src/widget/space.rs
+++ b/native/src/widget/space.rs
@@ -16,15 +16,11 @@ pub struct Space {
impl Space {
/// Creates an amount of empty [`Space`] with the given width and height.
- ///
- /// [`Space`]: struct.Space.html
pub fn new(width: Length, height: Length) -> Self {
Space { width, height }
}
/// Creates an amount of horizontal [`Space`].
- ///
- /// [`Space`]: struct.Space.html
pub fn with_width(width: Length) -> Self {
Space {
width,
@@ -33,8 +29,6 @@ impl Space {
}
/// Creates an amount of vertical [`Space`].
- ///
- /// [`Space`]: struct.Space.html
pub fn with_height(height: Length) -> Self {
Space {
width: Length::Shrink,
@@ -71,6 +65,7 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(layout.bounds())
}
@@ -84,14 +79,10 @@ where
}
/// The renderer of an amount of [`Space`].
-///
-/// [`Space`]: struct.Space.html
pub trait Renderer: crate::Renderer {
/// Draws an amount of empty [`Space`].
///
/// You should most likely return an empty primitive here.
- ///
- /// [`Space`]: struct.Space.html
fn draw(&mut self, bounds: Rectangle) -> Self::Output;
}
diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs
index 114d5e41..9cd61918 100644
--- a/native/src/widget/svg.rs
+++ b/native/src/widget/svg.rs
@@ -1,5 +1,6 @@
//! Display vector graphics in your application.
-use crate::{layout, Element, Hasher, Layout, Length, Point, Size, Widget};
+use crate::layout;
+use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
use std::{
hash::{Hash, Hasher as _},
@@ -13,8 +14,6 @@ use std::{
///
/// [`Svg`] images can have a considerable rendering cost when resized,
/// specially when they are complex.
-///
-/// [`Svg`]: struct.Svg.html
#[derive(Debug, Clone)]
pub struct Svg {
handle: Handle,
@@ -24,9 +23,6 @@ pub struct Svg {
impl Svg {
/// Creates a new [`Svg`] from the given [`Handle`].
- ///
- /// [`Svg`]: struct.Svg.html
- /// [`Handle`]: struct.Handle.html
pub fn new(handle: impl Into<Handle>) -> Self {
Svg {
handle: handle.into(),
@@ -37,23 +33,17 @@ impl Svg {
/// Creates a new [`Svg`] that will display the contents of the file at the
/// provided path.
- ///
- /// [`Svg`]: struct.Svg.html
pub fn from_path(path: impl Into<PathBuf>) -> Self {
Self::new(Handle::from_path(path))
}
/// Sets the width of the [`Svg`].
- ///
- /// [`Svg`]: struct.Svg.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Svg`].
- ///
- /// [`Svg`]: struct.Svg.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
@@ -103,6 +93,7 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(self.handle.clone(), layout)
}
@@ -117,8 +108,6 @@ where
}
/// An [`Svg`] handle.
-///
-/// [`Svg`]: struct.Svg.html
#[derive(Debug, Clone)]
pub struct Handle {
id: u64,
@@ -128,8 +117,6 @@ pub struct Handle {
impl Handle {
/// Creates an SVG [`Handle`] pointing to the vector image of the given
/// path.
- ///
- /// [`Handle`]: struct.Handle.html
pub fn from_path(path: impl Into<PathBuf>) -> Handle {
Self::from_data(Data::Path(path.into()))
}
@@ -139,8 +126,6 @@ impl Handle {
///
/// This is useful if you already have your SVG data in-memory, maybe
/// because you downloaded or generated it procedurally.
- ///
- /// [`Handle`]: struct.Handle.html
pub fn from_memory(bytes: impl Into<Vec<u8>>) -> Handle {
Self::from_data(Data::Bytes(bytes.into()))
}
@@ -156,15 +141,11 @@ impl Handle {
}
/// Returns the unique identifier of the [`Handle`].
- ///
- /// [`Handle`]: struct.Handle.html
pub fn id(&self) -> u64 {
self.id
}
/// Returns a reference to the SVG [`Data`].
- ///
- /// [`Data`]: enum.Data.html
pub fn data(&self) -> &Data {
&self.data
}
@@ -177,8 +158,6 @@ impl Hash for Handle {
}
/// The data of an [`Svg`].
-///
-/// [`Svg`]: struct.Svg.html
#[derive(Clone, Hash)]
pub enum Data {
/// File data
@@ -204,18 +183,12 @@ impl std::fmt::Debug for Data {
/// Your [renderer] will need to implement this trait before being able to use
/// an [`Svg`] in your user interface.
///
-/// [`Svg`]: struct.Svg.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// Returns the default dimensions of an [`Svg`] for the given [`Handle`].
- ///
- /// [`Svg`]: struct.Svg.html
- /// [`Handle`]: struct.Handle.html
fn dimensions(&self, handle: &Handle) -> (u32, u32);
/// Draws an [`Svg`].
- ///
- /// [`Svg`]: struct.Svg.html
fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output;
}
diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs
index 48a69e34..6cc18e6c 100644
--- a/native/src/widget/text.rs
+++ b/native/src/widget/text.rs
@@ -33,8 +33,6 @@ pub struct Text<Renderer: self::Renderer> {
impl<Renderer: self::Renderer> Text<Renderer> {
/// Create a new fragment of [`Text`] with the given contents.
- ///
- /// [`Text`]: struct.Text.html
pub fn new<T: Into<String>>(label: T) -> Self {
Text {
content: label.into(),
@@ -49,17 +47,12 @@ impl<Renderer: self::Renderer> Text<Renderer> {
}
/// Sets the size of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
pub fn size(mut self, size: u16) -> Self {
self.size = Some(size);
self
}
/// Sets the [`Color`] of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
- /// [`Color`]: ../../struct.Color.html
pub fn color<C: Into<Color>>(mut self, color: C) -> Self {
self.color = Some(color.into());
self
@@ -67,33 +60,25 @@ impl<Renderer: self::Renderer> Text<Renderer> {
/// Sets the [`Font`] of the [`Text`].
///
- /// [`Text`]: struct.Text.html
- /// [`Font`]: ../../struct.Font.html
+ /// [`Font`]: Renderer::Font
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
self.font = font.into();
self
}
/// Sets the width of the [`Text`] boundaries.
- ///
- /// [`Text`]: struct.Text.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Text`] boundaries.
- ///
- /// [`Text`]: struct.Text.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the [`HorizontalAlignment`] of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
- /// [`HorizontalAlignment`]: enum.HorizontalAlignment.html
pub fn horizontal_alignment(
mut self,
alignment: HorizontalAlignment,
@@ -103,9 +88,6 @@ impl<Renderer: self::Renderer> Text<Renderer> {
}
/// Sets the [`VerticalAlignment`] of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
- /// [`VerticalAlignment`]: enum.VerticalAlignment.html
pub fn vertical_alignment(mut self, alignment: VerticalAlignment) -> Self {
self.vertical_alignment = alignment;
self
@@ -149,6 +131,7 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(
defaults,
@@ -176,26 +159,18 @@ where
/// The renderer of a [`Text`] fragment.
///
/// Your [renderer] will need to implement this trait before being
-/// able to use [`Text`] in your [`UserInterface`].
+/// able to use [`Text`] in your user interface.
///
-/// [`Text`]: struct.Text.html
-/// [renderer]: ../../renderer/index.html
-/// [`UserInterface`]: ../../struct.UserInterface.html
+/// [renderer]: crate::Renderer
pub trait Renderer: crate::Renderer {
/// The font type used for [`Text`].
- ///
- /// [`Text`]: struct.Text.html
type Font: Default + Copy;
/// Returns the default size of [`Text`].
- ///
- /// [`Text`]: struct.Text.html
fn default_size(&self) -> u16;
/// Measures the [`Text`] in the given bounds and returns the minimum
/// boundaries that can fit the contents.
- ///
- /// [`Text`]: struct.Text.html
fn measure(
&self,
content: &str,
@@ -213,10 +188,6 @@ pub trait Renderer: crate::Renderer {
/// * the color of the [`Text`]
/// * the [`HorizontalAlignment`] of the [`Text`]
/// * the [`VerticalAlignment`] of the [`Text`]
- ///
- /// [`Text`]: struct.Text.html
- /// [`HorizontalAlignment`]: enum.HorizontalAlignment.html
- /// [`VerticalAlignment`]: enum.VerticalAlignment.html
fn draw(
&mut self,
defaults: &Self::Defaults,
diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs
index 9e15f4be..cec1e485 100644
--- a/native/src/widget/text_input.rs
+++ b/native/src/widget/text_input.rs
@@ -1,9 +1,6 @@
//! Display fields that can be filled with text.
//!
//! A [`TextInput`] has some local [`State`].
-//!
-//! [`TextInput`]: struct.TextInput.html
-//! [`State`]: struct.State.html
mod editor;
mod value;
@@ -14,10 +11,14 @@ pub use value::Value;
use editor::Editor;
+use crate::event::{self, Event};
+use crate::keyboard;
+use crate::layout;
+use crate::mouse::{self, click};
+use crate::text;
+use crate::touch;
use crate::{
- keyboard, layout,
- mouse::{self, click},
- text, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle,
+ Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
Size, Widget,
};
@@ -56,14 +57,18 @@ pub struct TextInput<'a, Message, Renderer: self::Renderer> {
font: Renderer::Font,
width: Length,
max_width: u32,
- padding: u16,
+ padding: Padding,
size: Option<u16>,
on_change: Box<dyn Fn(String) -> Message>,
on_submit: Option<Message>,
style: Renderer::Style,
}
-impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {
+impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>
+where
+ Message: Clone,
+ Renderer: self::Renderer,
+{
/// Creates a new [`TextInput`].
///
/// It expects:
@@ -71,9 +76,6 @@ impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {
/// - a placeholder
/// - the current value
/// - a function that produces a message when the [`TextInput`] changes
- ///
- /// [`TextInput`]: struct.TextInput.html
- /// [`State`]: struct.State.html
pub fn new<F>(
state: &'a mut State,
placeholder: &str,
@@ -91,7 +93,7 @@ impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {
font: Default::default(),
width: Length::Fill,
max_width: u32::MAX,
- padding: 0,
+ padding: Padding::ZERO,
size: None,
on_change: Box::new(on_change),
on_submit: None,
@@ -100,8 +102,6 @@ impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {
}
/// Converts the [`TextInput`] into a secure password input.
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn password(mut self) -> Self {
self.is_secure = true;
self
@@ -109,39 +109,31 @@ impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {
/// Sets the [`Font`] of the [`Text`].
///
- /// [`Text`]: struct.Text.html
- /// [`Font`]: ../../struct.Font.html
+ /// [`Font`]: crate::widget::text::Renderer::Font
+ /// [`Text`]: crate::widget::Text
pub fn font(mut self, font: Renderer::Font) -> Self {
self.font = font;
self
}
/// Sets the width of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the maximum width of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
self
}
- /// Sets the padding of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
- pub fn padding(mut self, units: u16) -> Self {
- self.padding = units;
+ /// Sets the [`Padding`] of the [`TextInput`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
/// Sets the text size of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn size(mut self, size: u16) -> Self {
self.size = Some(size);
self
@@ -149,34 +141,75 @@ impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {
/// Sets the message that should be produced when the [`TextInput`] is
/// focused and the enter key is pressed.
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn on_submit(mut self, message: Message) -> Self {
self.on_submit = Some(message);
self
}
/// Sets the style of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
}
/// Returns the current [`State`] of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn state(&self) -> &State {
self.state
}
}
+impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>
+where
+ Renderer: self::Renderer,
+{
+ /// Draws the [`TextInput`] with the given [`Renderer`], overriding its
+ /// [`Value`] if provided.
+ pub fn draw(
+ &self,
+ renderer: &mut Renderer,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ value: Option<&Value>,
+ ) -> Renderer::Output {
+ let value = value.unwrap_or(&self.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,
+ )
+ } else {
+ self::Renderer::draw(
+ renderer,
+ bounds,
+ text_bounds,
+ cursor_position,
+ self.font,
+ self.size.unwrap_or(renderer.default_size()),
+ &self.placeholder,
+ value,
+ &self.state,
+ &self.style,
+ )
+ }
+ }
+}
+
impl<'a, Message, Renderer> Widget<Message, Renderer>
for TextInput<'a, Message, Renderer>
where
- Renderer: self::Renderer,
Message: Clone,
+ Renderer: self::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -191,19 +224,21 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let padding = self.padding as f32;
let text_size = self.size.unwrap_or(renderer.default_size());
let limits = limits
- .pad(padding)
+ .pad(self.padding)
.width(self.width)
.max_width(self.max_width)
.height(Length::Units(text_size));
let mut text = layout::Node::new(limits.resolve(Size::ZERO));
- text.move_to(Point::new(padding, padding));
+ text.move_to(Point::new(
+ self.padding.left.into(),
+ self.padding.top.into(),
+ ));
- layout::Node::with_children(text.size().pad(padding), vec![text])
+ layout::Node::with_children(text.size().pad(self.padding), vec![text])
}
fn on_event(
@@ -211,14 +246,17 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
renderer: &Renderer,
- clipboard: Option<&dyn Clipboard>,
- ) {
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
let is_clicked = layout.bounds().contains(cursor_position);
+ self.state.is_focused = is_clicked;
+
if is_clicked {
let text_layout = layout.children().next().unwrap();
let target = cursor_position.x - text_layout.bounds().x;
@@ -250,6 +288,8 @@ where
} else {
self.state.cursor.move_to(0);
}
+
+ self.state.is_dragging = true;
}
click::Kind::Double => {
if self.is_secure {
@@ -269,25 +309,30 @@ where
self.value.next_end_of_word(position),
);
}
+
+ self.state.is_dragging = false;
}
click::Kind::Triple => {
self.state.cursor.select_all(&self.value);
+ self.state.is_dragging = false;
}
}
self.state.last_click = Some(click);
- }
- self.state.is_dragging = is_clicked;
- self.state.is_focused = is_clicked;
+ return event::Status::Captured;
+ }
}
- Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerLifted { .. })
+ | Event::Touch(touch::Event::FingerLost { .. }) => {
self.state.is_dragging = false;
}
- Event::Mouse(mouse::Event::CursorMoved { x, .. }) => {
+ Event::Mouse(mouse::Event::CursorMoved { position })
+ | Event::Touch(touch::Event::FingerMoved { position, .. }) => {
if self.state.is_dragging {
let text_layout = layout.children().next().unwrap();
- let target = x - text_layout.bounds().x;
+ let target = position.x - text_layout.bounds().x;
if target > 0.0 {
let value = if self.is_secure {
@@ -310,11 +355,14 @@ where
position,
);
}
+
+ return event::Status::Captured;
}
}
Event::Keyboard(keyboard::Event::CharacterReceived(c))
if self.state.is_focused
&& self.state.is_pasting.is_none()
+ && !self.state.keyboard_modifiers.command()
&& !c.is_control() =>
{
let mut editor =
@@ -324,120 +372,179 @@ where
let message = (self.on_change)(editor.contents());
messages.push(message);
+
+ return event::Status::Captured;
}
Event::Keyboard(keyboard::Event::KeyPressed {
- key_code,
- modifiers,
- }) if self.state.is_focused => match key_code {
- keyboard::KeyCode::Enter => {
- if let Some(on_submit) = self.on_submit.clone() {
- messages.push(on_submit);
- }
- }
- keyboard::KeyCode::Backspace => {
- if platform::is_jump_modifier_pressed(modifiers)
- && self.state.cursor.selection(&self.value).is_none()
- {
- if self.is_secure {
- let cursor_pos = self.state.cursor.end(&self.value);
- self.state.cursor.select_range(0, cursor_pos);
- } else {
- self.state.cursor.select_left_by_words(&self.value);
+ key_code, ..
+ }) if self.state.is_focused => {
+ let modifiers = self.state.keyboard_modifiers;
+
+ match key_code {
+ keyboard::KeyCode::Enter => {
+ if let Some(on_submit) = self.on_submit.clone() {
+ messages.push(on_submit);
}
}
+ keyboard::KeyCode::Backspace => {
+ if platform::is_jump_modifier_pressed(modifiers)
+ && self
+ .state
+ .cursor
+ .selection(&self.value)
+ .is_none()
+ {
+ if self.is_secure {
+ let cursor_pos =
+ self.state.cursor.end(&self.value);
+ self.state.cursor.select_range(0, cursor_pos);
+ } else {
+ self.state
+ .cursor
+ .select_left_by_words(&self.value);
+ }
+ }
- let mut editor =
- Editor::new(&mut self.value, &mut self.state.cursor);
+ let mut editor = Editor::new(
+ &mut self.value,
+ &mut self.state.cursor,
+ );
- editor.backspace();
+ editor.backspace();
- let message = (self.on_change)(editor.contents());
- messages.push(message);
- }
- keyboard::KeyCode::Delete => {
- if platform::is_jump_modifier_pressed(modifiers)
- && self.state.cursor.selection(&self.value).is_none()
- {
- if self.is_secure {
- let cursor_pos = self.state.cursor.end(&self.value);
- self.state
- .cursor
- .select_range(cursor_pos, self.value.len());
- } else {
- self.state
+ let message = (self.on_change)(editor.contents());
+ messages.push(message);
+ }
+ keyboard::KeyCode::Delete => {
+ if platform::is_jump_modifier_pressed(modifiers)
+ && self
+ .state
.cursor
- .select_right_by_words(&self.value);
+ .selection(&self.value)
+ .is_none()
+ {
+ if self.is_secure {
+ let cursor_pos =
+ self.state.cursor.end(&self.value);
+ self.state
+ .cursor
+ .select_range(cursor_pos, self.value.len());
+ } else {
+ self.state
+ .cursor
+ .select_right_by_words(&self.value);
+ }
}
- }
- let mut editor =
- Editor::new(&mut self.value, &mut self.state.cursor);
+ let mut editor = Editor::new(
+ &mut self.value,
+ &mut self.state.cursor,
+ );
- editor.delete();
+ editor.delete();
- let message = (self.on_change)(editor.contents());
- messages.push(message);
- }
- keyboard::KeyCode::Left => {
- if platform::is_jump_modifier_pressed(modifiers)
- && !self.is_secure
- {
- if modifiers.shift {
- self.state.cursor.select_left_by_words(&self.value);
+ let message = (self.on_change)(editor.contents());
+ messages.push(message);
+ }
+ keyboard::KeyCode::Left => {
+ if platform::is_jump_modifier_pressed(modifiers)
+ && !self.is_secure
+ {
+ if modifiers.shift() {
+ self.state
+ .cursor
+ .select_left_by_words(&self.value);
+ } else {
+ self.state
+ .cursor
+ .move_left_by_words(&self.value);
+ }
+ } else if modifiers.shift() {
+ self.state.cursor.select_left(&self.value)
} else {
- self.state.cursor.move_left_by_words(&self.value);
+ self.state.cursor.move_left(&self.value);
}
- } else if modifiers.shift {
- self.state.cursor.select_left(&self.value)
- } else {
- self.state.cursor.move_left(&self.value);
}
- }
- keyboard::KeyCode::Right => {
- if platform::is_jump_modifier_pressed(modifiers)
- && !self.is_secure
- {
- if modifiers.shift {
- self.state
- .cursor
- .select_right_by_words(&self.value);
+ keyboard::KeyCode::Right => {
+ if platform::is_jump_modifier_pressed(modifiers)
+ && !self.is_secure
+ {
+ if modifiers.shift() {
+ self.state
+ .cursor
+ .select_right_by_words(&self.value);
+ } else {
+ self.state
+ .cursor
+ .move_right_by_words(&self.value);
+ }
+ } else if modifiers.shift() {
+ self.state.cursor.select_right(&self.value)
} else {
- self.state.cursor.move_right_by_words(&self.value);
+ self.state.cursor.move_right(&self.value);
}
- } else if modifiers.shift {
- self.state.cursor.select_right(&self.value)
- } else {
- self.state.cursor.move_right(&self.value);
}
- }
- keyboard::KeyCode::Home => {
- if modifiers.shift {
- self.state.cursor.select_range(
- self.state.cursor.start(&self.value),
- 0,
- );
- } else {
- self.state.cursor.move_to(0);
+ keyboard::KeyCode::Home => {
+ if modifiers.shift() {
+ self.state.cursor.select_range(
+ self.state.cursor.start(&self.value),
+ 0,
+ );
+ } else {
+ self.state.cursor.move_to(0);
+ }
}
- }
- keyboard::KeyCode::End => {
- if modifiers.shift {
- self.state.cursor.select_range(
- self.state.cursor.start(&self.value),
- self.value.len(),
+ keyboard::KeyCode::End => {
+ if modifiers.shift() {
+ self.state.cursor.select_range(
+ self.state.cursor.start(&self.value),
+ self.value.len(),
+ );
+ } else {
+ self.state.cursor.move_to(self.value.len());
+ }
+ }
+ keyboard::KeyCode::C
+ if self.state.keyboard_modifiers.command() =>
+ {
+ match self.state.cursor.selection(&self.value) {
+ Some((start, end)) => {
+ clipboard.write(
+ self.value.select(start, end).to_string(),
+ );
+ }
+ None => {}
+ }
+ }
+ keyboard::KeyCode::X
+ if self.state.keyboard_modifiers.command() =>
+ {
+ match self.state.cursor.selection(&self.value) {
+ Some((start, end)) => {
+ clipboard.write(
+ self.value.select(start, end).to_string(),
+ );
+ }
+ None => {}
+ }
+
+ let mut editor = Editor::new(
+ &mut self.value,
+ &mut self.state.cursor,
);
- } else {
- self.state.cursor.move_to(self.value.len());
+
+ editor.delete();
+
+ let message = (self.on_change)(editor.contents());
+ messages.push(message);
}
- }
- keyboard::KeyCode::V => {
- if platform::is_copy_paste_modifier_pressed(modifiers) {
- if let Some(clipboard) = clipboard {
+ keyboard::KeyCode::V => {
+ if self.state.keyboard_modifiers.command() {
let content = match self.state.is_pasting.take() {
Some(content) => content,
None => {
let content: String = clipboard
- .content()
+ .read()
.unwrap_or(String::new())
.chars()
.filter(|c| !c.is_control())
@@ -458,33 +565,49 @@ where
messages.push(message);
self.state.is_pasting = Some(content);
+ } else {
+ self.state.is_pasting = None;
}
- } else {
- self.state.is_pasting = None;
}
- }
- keyboard::KeyCode::A => {
- if platform::is_copy_paste_modifier_pressed(modifiers) {
+ keyboard::KeyCode::A
+ if self.state.keyboard_modifiers.command() =>
+ {
self.state.cursor.select_all(&self.value);
}
+ keyboard::KeyCode::Escape => {
+ self.state.is_focused = false;
+ self.state.is_dragging = false;
+ self.state.is_pasting = None;
+
+ self.state.keyboard_modifiers =
+ keyboard::Modifiers::default();
+ }
+ _ => {}
}
- keyboard::KeyCode::Escape => {
- self.state.is_focused = false;
- self.state.is_dragging = false;
- self.state.is_pasting = None;
- }
- _ => {}
- },
+
+ return event::Status::Captured;
+ }
Event::Keyboard(keyboard::Event::KeyReleased {
key_code, ..
- }) => match key_code {
- keyboard::KeyCode::V => {
- self.state.is_pasting = None;
+ }) if self.state.is_focused => {
+ match key_code {
+ keyboard::KeyCode::V => {
+ self.state.is_pasting = None;
+ }
+ _ => {}
}
- _ => {}
- },
+
+ return event::Status::Captured;
+ }
+ Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers))
+ if self.state.is_focused =>
+ {
+ self.state.keyboard_modifiers = modifiers;
+ }
_ => {}
}
+
+ event::Status::Ignored
}
fn draw(
@@ -493,37 +616,9 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
- 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,
- &self.value.secure(),
- &self.state,
- &self.style,
- )
- } else {
- self::Renderer::draw(
- renderer,
- bounds,
- text_bounds,
- cursor_position,
- self.font,
- self.size.unwrap_or(renderer.default_size()),
- &self.placeholder,
- &self.value,
- &self.state,
- &self.style,
- )
- }
+ self.draw(renderer, layout, cursor_position, None)
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -543,15 +638,12 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`TextInput`] in your user interface.
///
-/// [`TextInput`]: struct.TextInput.html
-/// [renderer]: ../../renderer/index.html
+/// [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`].
- ///
- /// [`TextInput`]: struct.TextInput.html
fn measure_value(&self, value: &str, size: u16, font: Self::Font) -> f32;
/// Returns the current horizontal offset of the value of the
@@ -559,9 +651,6 @@ pub trait Renderer: text::Renderer + Sized {
///
/// This is the amount of horizontal scrolling applied when the [`Value`]
/// does not fit the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
- /// [`Value`]: struct.Value.html
fn offset(
&self,
text_bounds: Rectangle,
@@ -580,10 +669,6 @@ pub trait Renderer: text::Renderer + Sized {
/// - the placeholder to show when the value is empty
/// - the current [`Value`]
/// - the current [`State`]
- ///
- /// [`TextInput`]: struct.TextInput.html
- /// [`Value`]: struct.Value.html
- /// [`State`]: struct.State.html
fn draw(
&mut self,
bounds: Rectangle,
@@ -599,8 +684,6 @@ pub trait Renderer: text::Renderer + Sized {
/// Computes the position of the text cursor at the given X coordinate of
/// a [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
fn find_cursor_position(
&self,
text_bounds: Rectangle,
@@ -629,8 +712,8 @@ pub trait Renderer: text::Renderer + Sized {
impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer,
Message: 'a + Clone,
+ Renderer: 'a + self::Renderer,
{
fn from(
text_input: TextInput<'a, Message, Renderer>,
@@ -640,8 +723,6 @@ where
}
/// The state of a [`TextInput`].
-///
-/// [`TextInput`]: struct.TextInput.html
#[derive(Debug, Default, Clone)]
pub struct State {
is_focused: bool,
@@ -649,20 +730,17 @@ pub struct State {
is_pasting: Option<Value>,
last_click: Option<mouse::Click>,
cursor: Cursor,
+ keyboard_modifiers: keyboard::Modifiers,
// TODO: Add stateful horizontal scrolling offset
}
impl State {
/// Creates a new [`State`], representing an unfocused [`TextInput`].
- ///
- /// [`State`]: struct.State.html
pub fn new() -> Self {
Self::default()
}
/// Creates a new [`State`], representing a focused [`TextInput`].
- ///
- /// [`State`]: struct.State.html
pub fn focused() -> Self {
Self {
is_focused: true,
@@ -670,47 +748,49 @@ impl State {
is_pasting: None,
last_click: None,
cursor: Cursor::default(),
+ keyboard_modifiers: keyboard::Modifiers::default(),
}
}
/// Returns whether the [`TextInput`] is currently focused or not.
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn is_focused(&self) -> bool {
self.is_focused
}
/// Returns the [`Cursor`] of the [`TextInput`].
- ///
- /// [`Cursor`]: struct.Cursor.html
- /// [`TextInput`]: struct.TextInput.html
pub fn cursor(&self) -> Cursor {
self.cursor
}
+ /// Focuses the [`TextInput`].
+ pub fn focus(&mut self) {
+ self.is_focused = true;
+ }
+
+ /// Unfocuses the [`TextInput`].
+ pub fn unfocus(&mut self) {
+ self.is_focused = false;
+ }
+
/// Moves the [`Cursor`] of the [`TextInput`] to the front of the input text.
- ///
- /// [`Cursor`]: struct.Cursor.html
- /// [`TextInput`]: struct.TextInput.html
pub fn move_cursor_to_front(&mut self) {
self.cursor.move_to(0);
}
/// Moves the [`Cursor`] of the [`TextInput`] to the end of the input text.
- ///
- /// [`Cursor`]: struct.Cursor.html
- /// [`TextInput`]: struct.TextInput.html
pub fn move_cursor_to_end(&mut self) {
self.cursor.move_to(usize::MAX);
}
/// Moves the [`Cursor`] of the [`TextInput`] to an arbitrary location.
- ///
- /// [`Cursor`]: struct.Cursor.html
- /// [`TextInput`]: struct.TextInput.html
pub fn move_cursor_to(&mut self, position: usize) {
self.cursor.move_to(position);
}
+
+ /// Selects all the content of the [`TextInput`].
+ pub fn select_all(&mut self) {
+ self.cursor.select_range(0, usize::MAX);
+ }
}
// TODO: Reduce allocations
@@ -772,23 +852,11 @@ fn find_cursor_position<Renderer: self::Renderer>(
mod platform {
use crate::keyboard;
- pub fn is_jump_modifier_pressed(
- modifiers: keyboard::ModifiersState,
- ) -> bool {
- if cfg!(target_os = "macos") {
- modifiers.alt
- } else {
- modifiers.control
- }
- }
-
- pub fn is_copy_paste_modifier_pressed(
- modifiers: keyboard::ModifiersState,
- ) -> bool {
+ pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool {
if cfg!(target_os = "macos") {
- modifiers.logo
+ modifiers.alt()
} else {
- modifiers.control
+ modifiers.control()
}
}
}
diff --git a/native/src/widget/text_input/cursor.rs b/native/src/widget/text_input/cursor.rs
index aa03bb74..4f3b159b 100644
--- a/native/src/widget/text_input/cursor.rs
+++ b/native/src/widget/text_input/cursor.rs
@@ -8,8 +8,6 @@ pub struct Cursor {
}
/// The state of a [`Cursor`].
-///
-/// [`Cursor`]: struct.Cursor.html
#[derive(Debug, Copy, Clone)]
pub enum State {
/// Cursor without a selection
@@ -34,9 +32,6 @@ impl Default for Cursor {
impl Cursor {
/// Returns the [`State`] of the [`Cursor`].
- ///
- /// [`State`]: struct.State.html
- /// [`Cursor`]: struct.Cursor.html
pub fn state(&self, value: &Value) -> State {
match self.state {
State::Index(index) => State::Index(index.min(value.len())),
@@ -53,6 +48,18 @@ impl Cursor {
}
}
+ /// Returns the current selection of the [`Cursor`] for the given [`Value`].
+ ///
+ /// `start` is guaranteed to be <= than `end`.
+ pub fn selection(&self, value: &Value) -> Option<(usize, usize)> {
+ match self.state(value) {
+ State::Selection { start, end } => {
+ Some((start.min(end), start.max(end)))
+ }
+ _ => None,
+ }
+ }
+
pub(crate) fn move_to(&mut self, position: usize) {
self.state = State::Index(position);
}
@@ -106,7 +113,7 @@ impl Cursor {
State::Selection { start, end } if end > 0 => {
self.select_range(start, end - 1)
}
- _ => (),
+ _ => {}
}
}
@@ -118,7 +125,7 @@ impl Cursor {
State::Selection { start, end } if end < value.len() => {
self.select_range(start, end + 1)
}
- _ => (),
+ _ => {}
}
}
@@ -166,15 +173,6 @@ impl Cursor {
end.min(value.len())
}
- pub(crate) fn selection(&self, value: &Value) -> Option<(usize, usize)> {
- match self.state(value) {
- State::Selection { start, end } => {
- Some((start.min(end), start.max(end)))
- }
- _ => None,
- }
- }
-
fn left(&self, value: &Value) -> usize {
match self.state(value) {
State::Index(index) => index,
diff --git a/native/src/widget/text_input/editor.rs b/native/src/widget/text_input/editor.rs
index 20e42567..0b50a382 100644
--- a/native/src/widget/text_input/editor.rs
+++ b/native/src/widget/text_input/editor.rs
@@ -20,7 +20,7 @@ impl<'a> Editor<'a> {
self.cursor.move_left(self.value);
self.value.remove_many(left, right);
}
- _ => (),
+ _ => {}
}
self.value.insert(self.cursor.end(self.value), character);
@@ -35,7 +35,7 @@ impl<'a> Editor<'a> {
self.cursor.move_left(self.value);
self.value.remove_many(left, right);
}
- _ => (),
+ _ => {}
}
self.value.insert_many(self.cursor.end(self.value), content);
diff --git a/native/src/widget/text_input/value.rs b/native/src/widget/text_input/value.rs
index 1e9ba45b..2034cca4 100644
--- a/native/src/widget/text_input/value.rs
+++ b/native/src/widget/text_input/value.rs
@@ -2,7 +2,7 @@ use unicode_segmentation::UnicodeSegmentation;
/// The value of a [`TextInput`].
///
-/// [`TextInput`]: struct.TextInput.html
+/// [`TextInput`]: crate::widget::TextInput
// TODO: Reduce allocations, cache results (?)
#[derive(Debug, Clone)]
pub struct Value {
@@ -11,8 +11,6 @@ pub struct Value {
impl Value {
/// Creates a new [`Value`] from a string slice.
- ///
- /// [`Value`]: struct.Value.html
pub fn new(string: &str) -> Self {
let graphemes = UnicodeSegmentation::graphemes(string, true)
.map(String::from)
@@ -21,17 +19,20 @@ impl Value {
Self { graphemes }
}
- /// Returns the total amount of graphemes in the [`Value`].
+ /// Returns whether the [`Value`] is empty or not.
///
- /// [`Value`]: struct.Value.html
+ /// A [`Value`] is empty when it contains no graphemes.
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+
+ /// Returns the total amount of graphemes in the [`Value`].
pub fn len(&self) -> usize {
self.graphemes.len()
}
/// Returns the position of the previous start of a word from the given
/// grapheme `index`.
- ///
- /// [`Value`]: struct.Value.html
pub fn previous_start_of_word(&self, index: usize) -> usize {
let previous_string =
&self.graphemes[..index.min(self.graphemes.len())].concat();
@@ -54,8 +55,6 @@ impl Value {
/// Returns the position of the next end of a word from the given grapheme
/// `index`.
- ///
- /// [`Value`]: struct.Value.html
pub fn next_end_of_word(&self, index: usize) -> usize {
let next_string = &self.graphemes[index..].concat();
@@ -74,10 +73,17 @@ impl Value {
.unwrap_or(self.len())
}
+ /// Returns a new [`Value`] containing the graphemes from `start` until the
+ /// given `end`.
+ pub fn select(&self, start: usize, end: usize) -> Self {
+ let graphemes =
+ self.graphemes[start.min(self.len())..end.min(self.len())].to_vec();
+
+ Self { graphemes }
+ }
+
/// Returns a new [`Value`] containing the graphemes until the given
/// `index`.
- ///
- /// [`Value`]: struct.Value.html
pub fn until(&self, index: usize) -> Self {
let graphemes = self.graphemes[..index.min(self.len())].to_vec();
@@ -85,8 +91,6 @@ impl Value {
}
/// Converts the [`Value`] into a `String`.
- ///
- /// [`Value`]: struct.Value.html
pub fn to_string(&self) -> String {
self.graphemes.concat()
}
@@ -109,8 +113,6 @@ impl Value {
}
/// Removes the grapheme at the given `index`.
- ///
- /// [`Value`]: struct.Value.html
pub fn remove(&mut self, index: usize) {
let _ = self.graphemes.remove(index);
}
@@ -122,8 +124,6 @@ impl Value {
/// Returns a new [`Value`] with all its graphemes replaced with the
/// dot ('•') character.
- ///
- /// [`Value`]: struct.Value.html
pub fn secure(&self) -> Self {
Self {
graphemes: std::iter::repeat(String::from("•"))
diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs
new file mode 100644
index 00000000..4035276c
--- /dev/null
+++ b/native/src/widget/toggler.rs
@@ -0,0 +1,277 @@
+//! Show toggle controls using togglers.
+use std::hash::Hash;
+
+use crate::{
+ event, layout, mouse, row, text, Align, Clipboard, Element, Event, Hasher,
+ HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text,
+ VerticalAlignment, Widget,
+};
+
+/// A toggler widget
+///
+/// # Example
+///
+/// ```
+/// # type Toggler<Message> = iced_native::Toggler<Message, iced_native::renderer::Null>;
+/// #
+/// pub enum Message {
+/// TogglerToggled(bool),
+/// }
+///
+/// let is_active = true;
+///
+/// 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> {
+ is_active: bool,
+ on_toggle: Box<dyn Fn(bool) -> Message>,
+ label: Option<String>,
+ width: Length,
+ size: u16,
+ text_size: Option<u16>,
+ text_alignment: HorizontalAlignment,
+ spacing: u16,
+ font: Renderer::Font,
+ style: Renderer::Style,
+}
+
+impl<Message, Renderer: self::Renderer + text::Renderer>
+ Toggler<Message, Renderer>
+{
+ /// Creates a new [`Toggler`].
+ ///
+ /// It expects:
+ /// * a boolean describing whether the [`Toggler`] is checked or not
+ /// * An optional label for the [`Toggler`]
+ /// * a function that will be called when the [`Toggler`] is toggled. It
+ /// will receive the new state of the [`Toggler`] and must produce a
+ /// `Message`.
+ pub fn new<F>(
+ is_active: bool,
+ label: impl Into<Option<String>>,
+ f: F,
+ ) -> Self
+ where
+ F: 'static + Fn(bool) -> Message,
+ {
+ Toggler {
+ is_active,
+ on_toggle: Box::new(f),
+ label: label.into(),
+ width: Length::Fill,
+ size: <Renderer as self::Renderer>::DEFAULT_SIZE,
+ text_size: None,
+ text_alignment: HorizontalAlignment::Left,
+ spacing: 0,
+ font: Renderer::Font::default(),
+ style: Renderer::Style::default(),
+ }
+ }
+
+ /// Sets the size of the [`Toggler`].
+ pub fn size(mut self, size: u16) -> Self {
+ self.size = size;
+ self
+ }
+
+ /// Sets the width of the [`Toggler`].
+ pub fn width(mut self, width: Length) -> Self {
+ self.width = width;
+ self
+ }
+
+ /// Sets the text size o the [`Toggler`].
+ pub fn text_size(mut self, text_size: u16) -> Self {
+ self.text_size = Some(text_size);
+ self
+ }
+
+ /// Sets the horizontal alignment of the text of the [`Toggler`]
+ pub fn text_alignment(mut self, alignment: HorizontalAlignment) -> Self {
+ self.text_alignment = alignment;
+ self
+ }
+
+ /// Sets the spacing between the [`Toggler`] and the text.
+ pub fn spacing(mut self, spacing: u16) -> Self {
+ self.spacing = spacing;
+ self
+ }
+
+ /// Sets the [`Font`] of the text of the [`Toggler`]
+ pub fn font(mut self, font: Renderer::Font) -> Self {
+ self.font = font;
+ self
+ }
+
+ /// Sets the style of the [`Toggler`].
+ pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
+ self.style = style.into();
+ self
+ }
+}
+
+impl<Message, Renderer> Widget<Message, Renderer> for Toggler<Message, Renderer>
+where
+ Renderer: self::Renderer + text::Renderer + row::Renderer,
+{
+ fn width(&self) -> Length {
+ self.width
+ }
+
+ fn height(&self) -> Length {
+ Length::Shrink
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ let mut row = Row::<(), Renderer>::new()
+ .width(self.width)
+ .spacing(self.spacing)
+ .align_items(Align::Center);
+
+ if let Some(label) = &self.label {
+ row = row.push(
+ Text::new(label)
+ .horizontal_alignment(self.text_alignment)
+ .font(self.font)
+ .width(self.width)
+ .size(self.text_size.unwrap_or(renderer.default_size())),
+ );
+ }
+
+ row = row.push(
+ Row::new()
+ .width(Length::Units(2 * self.size))
+ .height(Length::Units(self.size)),
+ );
+
+ row.layout(renderer, limits)
+ }
+
+ fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _renderer: &Renderer,
+ _clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
+ match event {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+ let mouse_over = layout.bounds().contains(cursor_position);
+
+ if mouse_over {
+ messages.push((self.on_toggle)(!self.is_active));
+
+ event::Status::Captured
+ } else {
+ event::Status::Ignored
+ }
+ }
+ _ => event::Status::Ignored,
+ }
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ defaults: &Renderer::Defaults,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> Renderer::Output {
+ let bounds = layout.bounds();
+ let mut children = layout.children();
+
+ 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,
+ VerticalAlignment::Center,
+ ))
+ }
+
+ None => None,
+ };
+
+ let toggler_layout = children.next().unwrap();
+ let toggler_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,
+ )
+ }
+
+ fn hash_layout(&self, state: &mut Hasher) {
+ struct Marker;
+ std::any::TypeId::of::<Marker>().hash(state);
+
+ self.label.hash(state)
+ }
+}
+
+/// 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>>
+ for Element<'a, Message, Renderer>
+where
+ Renderer: 'a + self::Renderer + text::Renderer + row::Renderer,
+ Message: 'a,
+{
+ fn from(
+ toggler: Toggler<Message, Renderer>,
+ ) -> Element<'a, Message, Renderer> {
+ Element::new(toggler)
+ }
+}
diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs
new file mode 100644
index 00000000..276afd41
--- /dev/null
+++ b/native/src/widget/tooltip.rs
@@ -0,0 +1,210 @@
+//! Display a widget over another.
+use std::hash::Hash;
+
+use iced_core::Rectangle;
+
+use crate::widget::container;
+use crate::widget::text::{self, Text};
+use crate::{
+ event, layout, Clipboard, Element, Event, Hasher, Layout, Length, Point,
+ Widget,
+};
+
+/// An element to display a widget over another.
+#[allow(missing_debug_implementations)]
+pub struct Tooltip<'a, Message, Renderer: self::Renderer> {
+ content: Element<'a, Message, Renderer>,
+ tooltip: Text<Renderer>,
+ position: Position,
+ style: <Renderer as container::Renderer>::Style,
+ gap: u16,
+ padding: u16,
+}
+
+impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer>
+where
+ Renderer: self::Renderer,
+{
+ /// Creates an empty [`Tooltip`].
+ ///
+ /// [`Tooltip`]: struct.Tooltip.html
+ pub fn new(
+ content: impl Into<Element<'a, Message, Renderer>>,
+ tooltip: impl ToString,
+ position: Position,
+ ) -> Self {
+ Tooltip {
+ content: content.into(),
+ tooltip: Text::new(tooltip.to_string()),
+ position,
+ style: Default::default(),
+ gap: 0,
+ padding: Renderer::DEFAULT_PADDING,
+ }
+ }
+
+ /// Sets the size of the text of the [`Tooltip`].
+ pub fn size(mut self, size: u16) -> Self {
+ self.tooltip = self.tooltip.size(size);
+ self
+ }
+
+ /// Sets the font of the [`Tooltip`].
+ ///
+ /// [`Font`]: Renderer::Font
+ pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
+ self.tooltip = self.tooltip.font(font);
+ self
+ }
+
+ /// Sets the gap between the content and its [`Tooltip`].
+ pub fn gap(mut self, gap: u16) -> Self {
+ self.gap = gap;
+ self
+ }
+
+ /// Sets the padding of the [`Tooltip`].
+ pub fn padding(mut self, padding: u16) -> Self {
+ self.padding = padding;
+ self
+ }
+
+ /// Sets the style of the [`Tooltip`].
+ pub fn style(
+ mut self,
+ style: impl Into<<Renderer as container::Renderer>::Style>,
+ ) -> Self {
+ self.style = style.into();
+ self
+ }
+}
+
+/// The position of the tooltip. Defaults to following the cursor.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Position {
+ /// The tooltip will follow the cursor.
+ FollowCursor,
+ /// The tooltip will appear on the top of the widget.
+ Top,
+ /// The tooltip will appear on the bottom of the widget.
+ Bottom,
+ /// The tooltip will appear on the left of the widget.
+ Left,
+ /// The tooltip will appear on the right of the widget.
+ Right,
+}
+
+impl<'a, Message, Renderer> Widget<Message, Renderer>
+ for Tooltip<'a, Message, Renderer>
+where
+ Renderer: self::Renderer,
+{
+ fn width(&self) -> Length {
+ self.content.width()
+ }
+
+ fn height(&self) -> Length {
+ self.content.height()
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ self.content.layout(renderer, limits)
+ }
+
+ fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
+ self.content.widget.on_event(
+ event,
+ layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ messages,
+ )
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ defaults: &Renderer::Defaults,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> Renderer::Output {
+ self::Renderer::draw(
+ renderer,
+ defaults,
+ cursor_position,
+ layout,
+ viewport,
+ &self.content,
+ &self.tooltip,
+ self.position,
+ &self.style,
+ self.gap,
+ self.padding,
+ )
+ }
+
+ fn hash_layout(&self, state: &mut Hasher) {
+ struct Marker;
+ std::any::TypeId::of::<Marker>().hash(state);
+
+ self.content.hash_layout(state);
+ }
+}
+
+/// 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,
+ Message: 'a,
+{
+ fn from(
+ column: Tooltip<'a, Message, Renderer>,
+ ) -> Element<'a, Message, Renderer> {
+ Element::new(column)
+ }
+}