From e19a07d40049f40f36d879a498fab4ce63778b27 Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Mon, 11 Nov 2019 20:29:58 -0800 Subject: Added initial touch events to support iOS --- native/src/event.rs | 5 ++++- native/src/input.rs | 1 + native/src/input/touch.rs | 39 ++++++++++++++++++++++++++++++++++++ native/src/user_interface.rs | 14 ++++++++++--- native/src/widget/button.rs | 35 ++++++++++++++++---------------- native/src/widget/checkbox.rs | 5 +++-- native/src/widget/radio.rs | 5 +++-- native/src/widget/scrollable.rs | 44 +++++++++++++++++++++++++++++++++++++---- native/src/widget/slider.rs | 30 ++++++++++++++++------------ native/src/widget/text_input.rs | 5 +++-- 10 files changed, 139 insertions(+), 44 deletions(-) create mode 100644 native/src/input/touch.rs (limited to 'native') diff --git a/native/src/event.rs b/native/src/event.rs index b2550ead..fb5b9977 100644 --- a/native/src/event.rs +++ b/native/src/event.rs @@ -1,5 +1,5 @@ use crate::{ - input::{keyboard, mouse}, + input::{keyboard, mouse, touch}, window, }; @@ -19,4 +19,7 @@ pub enum Event { /// A window event Window(window::Event), + + /// A touch event + Touch(touch::Touch), } diff --git a/native/src/input.rs b/native/src/input.rs index 097fa730..c08beaf9 100644 --- a/native/src/input.rs +++ b/native/src/input.rs @@ -1,6 +1,7 @@ //! Map your system events into input events that the runtime can understand. pub mod keyboard; pub mod mouse; +pub mod touch; mod button_state; diff --git a/native/src/input/touch.rs b/native/src/input/touch.rs new file mode 100644 index 00000000..7c4a6210 --- /dev/null +++ b/native/src/input/touch.rs @@ -0,0 +1,39 @@ +//! Build touch events. +/// The touch of a mobile device. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Touch { + /// The touch cursor was started + Started { + /// The X coordinate of the touch position + x: f32, + + /// The Y coordinate of the touch position + y: f32, + }, + /// The touch cursor was ended + Ended { + /// The X coordinate of the touch position + x: f32, + + /// The Y coordinate of the touch position + y: f32, + }, + + /// The touch was moved. + Moved { + /// The X coordinate of the touch position + x: f32, + + /// The Y coordinate of the touch position + y: f32, + }, + + /// Some canceled button. + Cancelled { + /// The X coordinate of the touch position + x: f32, + + /// The Y coordinate of the touch position + y: f32, + }, +} diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 08914bed..751b2652 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -1,5 +1,6 @@ use crate::{ - input::mouse, layout, Clipboard, Element, Event, Layout, Point, Size, + input::{mouse, touch}, + layout, Clipboard, Element, Event, Layout, Point, Size, }; use std::hash::Hasher; @@ -181,8 +182,15 @@ where let mut messages = Vec::new(); for event in events { - if let Event::Mouse(mouse::Event::CursorMoved { x, y }) = event { - self.cursor_position = Point::new(x, y); + match event { + Event::Mouse(mouse::Event::CursorMoved { x, y }) + | Event::Touch(touch::Touch::Started { x, y }) + | Event::Touch(touch::Touch::Ended { x, y }) + | Event::Touch(touch::Touch::Moved { x, y }) + | Event::Touch(touch::Touch::Cancelled { x, y }) => { + self.cursor_position = Point::new(x, y); + } + _ => {} } self.root.widget.on_event( diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index f1d46936..8c397bc1 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -5,7 +5,7 @@ //! [`Button`]: struct.Button.html //! [`State`]: struct.State.html use crate::{ - input::{mouse, ButtonState}, + input::{mouse, touch::Touch, ButtonState}, layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, Widget, }; @@ -187,26 +187,27 @@ where match event { Event::Mouse(mouse::Event::Input { button: mouse::Button::Left, - state, - }) => { + state: ButtonState::Pressed, + }) + | Event::Touch(Touch::Started { .. }) => { + let bounds = layout.bounds(); + + self.state.is_pressed = bounds.contains(cursor_position); + } + Event::Mouse(mouse::Event::Input { + button: mouse::Button::Left, + state: ButtonState::Released, + }) + | Event::Touch(Touch::Ended { .. }) => { if let Some(on_press) = self.on_press.clone() { let bounds = layout.bounds(); + let is_clicked = self.state.is_pressed + && bounds.contains(cursor_position); - match state { - ButtonState::Pressed => { - self.state.is_pressed = - bounds.contains(cursor_position); - } - ButtonState::Released => { - let is_clicked = self.state.is_pressed - && bounds.contains(cursor_position); - - self.state.is_pressed = false; + self.state.is_pressed = false; - if is_clicked { - messages.push(on_press); - } - } + if is_clicked { + messages.push(on_press); } } } diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index b36d10a4..26665d8b 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -2,7 +2,7 @@ use std::hash::Hash; use crate::{ - input::{mouse, ButtonState}, + input::{mouse, touch::Touch, ButtonState}, layout, row, text, Align, Clipboard, Element, Event, Font, Hasher, HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, VerticalAlignment, Widget, @@ -155,7 +155,8 @@ where Event::Mouse(mouse::Event::Input { button: mouse::Button::Left, state: ButtonState::Pressed, - }) => { + }) + | Event::Touch(Touch::Started { .. }) => { let mouse_over = layout.bounds().contains(cursor_position); if mouse_over { diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index cdc4862c..8a9c02ce 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -1,6 +1,6 @@ //! Create choices using radio buttons. use crate::{ - input::{mouse, ButtonState}, + input::{mouse, touch, ButtonState}, layout, row, text, Align, Clipboard, Element, Event, Font, Hasher, HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, VerticalAlignment, Widget, @@ -121,7 +121,8 @@ where Event::Mouse(mouse::Event::Input { button: mouse::Button::Left, state: ButtonState::Pressed, - }) => { + }) + | Event::Touch(touch::Touch::Started { .. }) => { if layout.bounds().contains(cursor_position) { messages.push(self.on_click.clone()); } diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index ec9746d4..2a658bcc 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -1,7 +1,7 @@ //! Navigate an endless amount of content with a scrollbar. use crate::{ column, - input::{mouse, ButtonState}, + input::{mouse, touch, ButtonState}, layout, Align, Clipboard, Column, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size, Widget, }; @@ -175,6 +175,22 @@ where } } } + Event::Touch(touch::Touch::Started { .. }) => { + self.state.scroll_box_touched_at = Some(cursor_position); + } + Event::Touch(touch::Touch::Moved { .. }) => { + 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); + } + } + Event::Touch(touch::Touch::Ended { .. }) => { + self.state.scroll_box_touched_at = None; + } _ => {} } } @@ -191,10 +207,23 @@ where Event::Mouse(mouse::Event::Input { button: mouse::Button::Left, state: ButtonState::Released, - }) => { + }) + | Event::Touch(touch::Touch::Ended { .. }) => { self.state.scroller_grabbed_at = None; } - Event::Mouse(mouse::Event::CursorMoved { .. }) => { + Event::Mouse(mouse::Event::Input { + button: mouse::Button::Left, + state: ButtonState::Pressed, + }) + | Event::Touch(touch::Touch::Started { .. }) => { + self.state.scroll_to( + cursor_position.y / (bounds.y + bounds.height), + bounds, + content_bounds, + ); + } + Event::Mouse(mouse::Event::CursorMoved { .. }) + | Event::Touch(touch::Touch::Moved { .. }) => { if let (Some(scrollbar), Some(scroller_grabbed_at)) = (scrollbar, self.state.scroller_grabbed_at) { @@ -215,7 +244,8 @@ where Event::Mouse(mouse::Event::Input { button: mouse::Button::Left, state: ButtonState::Pressed, - }) => { + }) + | Event::Touch(touch::Touch::Started { .. }) => { if let Some(scrollbar) = scrollbar { if let Some(scroller_grabbed_at) = scrollbar.grab_scroller(cursor_position) @@ -326,6 +356,7 @@ where #[derive(Debug, Clone, Copy, Default)] pub struct State { scroller_grabbed_at: Option, + scroll_box_touched_at: Option, offset: f32, } @@ -391,6 +422,11 @@ impl State { 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`]. diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index 008203fe..95f63921 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -5,7 +5,7 @@ //! [`Slider`]: struct.Slider.html //! [`State`]: struct.State.html use crate::{ - input::{mouse, ButtonState}, + input::{mouse, touch::Touch, ButtonState}, layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size, Widget, }; @@ -166,19 +166,23 @@ where match event { Event::Mouse(mouse::Event::Input { button: mouse::Button::Left, - state, - }) => match state { - ButtonState::Pressed => { - if layout.bounds().contains(cursor_position) { - change(); - self.state.is_dragging = true; - } - } - ButtonState::Released => { - self.state.is_dragging = false; + state: ButtonState::Pressed, + }) + | Event::Touch(Touch::Started { .. }) => { + if layout.bounds().contains(cursor_position) { + change(); + self.state.is_dragging = true; } - }, - Event::Mouse(mouse::Event::CursorMoved { .. }) => { + } + Event::Mouse(mouse::Event::Input { + button: mouse::Button::Left, + state: ButtonState::Released, + }) + | Event::Touch(Touch::Ended { .. }) => { + self.state.is_dragging = false; + } + Event::Mouse(mouse::Event::CursorMoved { .. }) + | Event::Touch(Touch::Moved { .. }) => { if self.state.is_dragging { change(); } diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index c068b895..9cfc6bf0 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -5,7 +5,7 @@ //! [`TextInput`]: struct.TextInput.html //! [`State`]: struct.State.html use crate::{ - input::{keyboard, mouse, ButtonState}, + input::{keyboard, mouse, touch, ButtonState}, layout, Clipboard, Element, Event, Font, Hasher, Layout, Length, Point, Rectangle, Size, Widget, }; @@ -202,7 +202,8 @@ where Event::Mouse(mouse::Event::Input { button: mouse::Button::Left, state: ButtonState::Pressed, - }) => { + }) + | Event::Touch(touch::Touch::Started { .. }) => { let is_clicked = layout.bounds().contains(cursor_position); if is_clicked { -- cgit From d3572e1b819ff4d40de4f39f48eab71b9d0d4d5e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 19 Mar 2020 12:17:16 +0100 Subject: Turn `Touch` into a struct and add finger id --- native/src/event.rs | 6 ++-- native/src/input.rs | 1 + native/src/input/mouse/event.rs | 9 ++---- native/src/input/touch.rs | 69 ++++++++++++++++++++--------------------- native/src/user_interface.rs | 11 +++---- native/src/widget/button.rs | 24 +++++++++++--- native/src/widget/checkbox.rs | 7 +++-- native/src/widget/radio.rs | 7 +++-- native/src/widget/scrollable.rs | 56 ++++++++++++++++++++++----------- native/src/widget/slider.rs | 17 +++++++--- native/src/widget/text_input.rs | 7 +++-- 11 files changed, 129 insertions(+), 85 deletions(-) (limited to 'native') diff --git a/native/src/event.rs b/native/src/event.rs index fb5b9977..99a8e880 100644 --- a/native/src/event.rs +++ b/native/src/event.rs @@ -1,5 +1,5 @@ use crate::{ - input::{keyboard, mouse, touch}, + input::{keyboard, mouse, Touch}, window, }; @@ -9,7 +9,7 @@ use crate::{ /// additional events, feel free to [open an issue] and share your use case!_ /// /// [open an issue]: https://github.com/hecrj/iced/issues -#[derive(PartialEq, Clone, Debug)] +#[derive(Debug, Clone, PartialEq)] pub enum Event { /// A keyboard event Keyboard(keyboard::Event), @@ -21,5 +21,5 @@ pub enum Event { Window(window::Event), /// A touch event - Touch(touch::Touch), + Touch(Touch), } diff --git a/native/src/input.rs b/native/src/input.rs index c08beaf9..514a98ea 100644 --- a/native/src/input.rs +++ b/native/src/input.rs @@ -6,3 +6,4 @@ pub mod touch; mod button_state; pub use button_state::ButtonState; +pub use touch::Touch; diff --git a/native/src/input/mouse/event.rs b/native/src/input/mouse/event.rs index aafc4fe3..5068d634 100644 --- a/native/src/input/mouse/event.rs +++ b/native/src/input/mouse/event.rs @@ -1,5 +1,5 @@ use super::Button; -use crate::input::ButtonState; +use crate::{input::ButtonState, Point}; /// A mouse event. /// @@ -17,11 +17,8 @@ pub enum Event { /// The mouse cursor was moved CursorMoved { - /// The X coordinate of the mouse position - x: f32, - - /// The Y coordinate of the mouse position - y: f32, + /// The new position of the mouse cursor + position: Point, }, /// A mouse button was pressed or released. diff --git a/native/src/input/touch.rs b/native/src/input/touch.rs index 7c4a6210..ea879427 100644 --- a/native/src/input/touch.rs +++ b/native/src/input/touch.rs @@ -1,39 +1,36 @@ //! Build touch events. -/// The touch of a mobile device. + +use crate::Point; + +/// A touch interaction. #[derive(Debug, Clone, Copy, PartialEq)] -pub enum Touch { - /// The touch cursor was started - Started { - /// The X coordinate of the touch position - x: f32, - - /// The Y coordinate of the touch position - y: f32, - }, - /// The touch cursor was ended - Ended { - /// The X coordinate of the touch position - x: f32, - - /// The Y coordinate of the touch position - y: f32, - }, - - /// The touch was moved. - Moved { - /// The X coordinate of the touch position - x: f32, - - /// The Y coordinate of the touch position - y: f32, - }, - - /// Some canceled button. - Cancelled { - /// The X coordinate of the touch position - x: f32, - - /// The Y coordinate of the touch position - y: f32, - }, +pub struct Touch { + /// The finger of the touch. + pub finger: Finger, + + /// The position of the touch. + pub position: Point, + + /// The state of the touch. + pub phase: Phase, +} + +/// A unique identifier representing a finger on a touch interaction. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Finger(pub u64); + +/// The state of a touch interaction. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Phase { + /// A touch interaction was started. + Started, + + /// An on-going touch interaction was moved. + Moved, + + /// A touch interaction was ended. + Ended, + + /// A touch interaction was canceled. + Canceled, } diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 751b2652..b71b9003 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -1,5 +1,5 @@ use crate::{ - input::{mouse, touch}, + input::{mouse, Touch}, layout, Clipboard, Element, Event, Layout, Point, Size, }; @@ -183,12 +183,9 @@ where for event in events { match event { - Event::Mouse(mouse::Event::CursorMoved { x, y }) - | Event::Touch(touch::Touch::Started { x, y }) - | Event::Touch(touch::Touch::Ended { x, y }) - | Event::Touch(touch::Touch::Moved { x, y }) - | Event::Touch(touch::Touch::Cancelled { x, y }) => { - self.cursor_position = Point::new(x, y); + Event::Mouse(mouse::Event::CursorMoved { position }) + | Event::Touch(Touch { position, .. }) => { + self.cursor_position = position; } _ => {} } diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 8c397bc1..81dbe7c5 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -5,7 +5,7 @@ //! [`Button`]: struct.Button.html //! [`State`]: struct.State.html use crate::{ - input::{mouse, touch::Touch, ButtonState}, + input::{mouse, touch, ButtonState, Touch}, layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, Widget, }; @@ -189,16 +189,24 @@ where button: mouse::Button::Left, state: ButtonState::Pressed, }) - | Event::Touch(Touch::Started { .. }) => { - let bounds = layout.bounds(); + | Event::Touch(Touch { + phase: touch::Phase::Started, + .. + }) => { + if self.on_press.is_some() { + let bounds = layout.bounds(); - self.state.is_pressed = bounds.contains(cursor_position); + self.state.is_pressed = bounds.contains(cursor_position); + } } Event::Mouse(mouse::Event::Input { button: mouse::Button::Left, state: ButtonState::Released, }) - | Event::Touch(Touch::Ended { .. }) => { + | Event::Touch(Touch { + phase: touch::Phase::Ended, + .. + }) => { if let Some(on_press) = self.on_press.clone() { let bounds = layout.bounds(); let is_clicked = self.state.is_pressed @@ -211,6 +219,12 @@ where } } } + Event::Touch(Touch { + phase: touch::Phase::Canceled, + .. + }) => { + self.state.is_pressed = false; + } _ => {} } } diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 26665d8b..7b2345de 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -2,7 +2,7 @@ use std::hash::Hash; use crate::{ - input::{mouse, touch::Touch, ButtonState}, + input::{mouse, touch, ButtonState, Touch}, layout, row, text, Align, Clipboard, Element, Event, Font, Hasher, HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, VerticalAlignment, Widget, @@ -156,7 +156,10 @@ where button: mouse::Button::Left, state: ButtonState::Pressed, }) - | Event::Touch(Touch::Started { .. }) => { + | Event::Touch(Touch { + phase: touch::Phase::Started, + .. + }) => { let mouse_over = layout.bounds().contains(cursor_position); if mouse_over { diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 8a9c02ce..46983db3 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -1,6 +1,6 @@ //! Create choices using radio buttons. use crate::{ - input::{mouse, touch, ButtonState}, + input::{mouse, touch, ButtonState, Touch}, layout, row, text, Align, Clipboard, Element, Event, Font, Hasher, HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, VerticalAlignment, Widget, @@ -122,7 +122,10 @@ where button: mouse::Button::Left, state: ButtonState::Pressed, }) - | Event::Touch(touch::Touch::Started { .. }) => { + | Event::Touch(Touch { + phase: touch::Phase::Started, + .. + }) => { if layout.bounds().contains(cursor_position) { messages.push(self.on_click.clone()); } diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 2a658bcc..2f5a4820 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -1,7 +1,7 @@ //! Navigate an endless amount of content with a scrollbar. use crate::{ column, - input::{mouse, touch, ButtonState}, + input::{mouse, touch, ButtonState, Touch}, layout, Align, Clipboard, Column, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size, Widget, }; @@ -175,22 +175,26 @@ where } } } - Event::Touch(touch::Touch::Started { .. }) => { - self.state.scroll_box_touched_at = Some(cursor_position); - } - Event::Touch(touch::Touch::Moved { .. }) => { - 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); + Event::Touch(Touch { phase, .. }) => match phase { + touch::Phase::Started => { self.state.scroll_box_touched_at = Some(cursor_position); } - } - Event::Touch(touch::Touch::Ended { .. }) => { - self.state.scroll_box_touched_at = None; - } + touch::Phase::Moved => { + 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); + } + } + touch::Phase::Ended | touch::Phase::Canceled => { + self.state.scroll_box_touched_at = None; + } + }, _ => {} } } @@ -208,14 +212,24 @@ where button: mouse::Button::Left, state: ButtonState::Released, }) - | Event::Touch(touch::Touch::Ended { .. }) => { + | Event::Touch(Touch { + phase: touch::Phase::Ended, + .. + }) + | Event::Touch(Touch { + phase: touch::Phase::Canceled, + .. + }) => { self.state.scroller_grabbed_at = None; } Event::Mouse(mouse::Event::Input { button: mouse::Button::Left, state: ButtonState::Pressed, }) - | Event::Touch(touch::Touch::Started { .. }) => { + | Event::Touch(Touch { + phase: touch::Phase::Started, + .. + }) => { self.state.scroll_to( cursor_position.y / (bounds.y + bounds.height), bounds, @@ -223,7 +237,10 @@ where ); } Event::Mouse(mouse::Event::CursorMoved { .. }) - | Event::Touch(touch::Touch::Moved { .. }) => { + | Event::Touch(Touch { + phase: touch::Phase::Moved, + .. + }) => { if let (Some(scrollbar), Some(scroller_grabbed_at)) = (scrollbar, self.state.scroller_grabbed_at) { @@ -245,7 +262,10 @@ where button: mouse::Button::Left, state: ButtonState::Pressed, }) - | Event::Touch(touch::Touch::Started { .. }) => { + | Event::Touch(Touch { + phase: touch::Phase::Started, + .. + }) => { if let Some(scrollbar) = scrollbar { if let Some(scroller_grabbed_at) = scrollbar.grab_scroller(cursor_position) diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index 95f63921..c98cebb6 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -5,7 +5,7 @@ //! [`Slider`]: struct.Slider.html //! [`State`]: struct.State.html use crate::{ - input::{mouse, touch::Touch, ButtonState}, + input::{mouse, touch, ButtonState, Touch}, layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size, Widget, }; @@ -168,7 +168,10 @@ where button: mouse::Button::Left, state: ButtonState::Pressed, }) - | Event::Touch(Touch::Started { .. }) => { + | Event::Touch(Touch { + phase: touch::Phase::Started, + .. + }) => { if layout.bounds().contains(cursor_position) { change(); self.state.is_dragging = true; @@ -178,11 +181,17 @@ where button: mouse::Button::Left, state: ButtonState::Released, }) - | Event::Touch(Touch::Ended { .. }) => { + | Event::Touch(Touch { + phase: touch::Phase::Ended, + .. + }) => { self.state.is_dragging = false; } Event::Mouse(mouse::Event::CursorMoved { .. }) - | Event::Touch(Touch::Moved { .. }) => { + | Event::Touch(Touch { + phase: touch::Phase::Moved, + .. + }) => { if self.state.is_dragging { change(); } diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 9cfc6bf0..c06a8cce 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -5,7 +5,7 @@ //! [`TextInput`]: struct.TextInput.html //! [`State`]: struct.State.html use crate::{ - input::{keyboard, mouse, touch, ButtonState}, + input::{keyboard, mouse, touch, ButtonState, Touch}, layout, Clipboard, Element, Event, Font, Hasher, Layout, Length, Point, Rectangle, Size, Widget, }; @@ -203,7 +203,10 @@ where button: mouse::Button::Left, state: ButtonState::Pressed, }) - | Event::Touch(touch::Touch::Started { .. }) => { + | Event::Touch(Touch { + phase: touch::Phase::Started, + .. + }) => { let is_clicked = layout.bounds().contains(cursor_position); if is_clicked { -- cgit From 36bdc0be1a0f959c84c18286b85c1ab51be330e6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 19 Mar 2020 12:23:31 +0100 Subject: Remove redundant `scroll_to` in `Scrollable` --- native/src/widget/scrollable.rs | 14 -------------- 1 file changed, 14 deletions(-) (limited to 'native') diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 2f5a4820..eb1722ed 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -222,20 +222,6 @@ where }) => { self.state.scroller_grabbed_at = None; } - Event::Mouse(mouse::Event::Input { - button: mouse::Button::Left, - state: ButtonState::Pressed, - }) - | Event::Touch(Touch { - phase: touch::Phase::Started, - .. - }) => { - self.state.scroll_to( - cursor_position.y / (bounds.y + bounds.height), - bounds, - content_bounds, - ); - } Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(Touch { phase: touch::Phase::Moved, -- cgit From 0d8cefbf2d084053b92ded4785da8083486374ea Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Thu, 23 Apr 2020 15:34:55 -0700 Subject: Add `ImagePane` widget --- native/src/widget.rs | 3 + native/src/widget/image_pane.rs | 407 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 410 insertions(+) create mode 100644 native/src/widget/image_pane.rs (limited to 'native') diff --git a/native/src/widget.rs b/native/src/widget.rs index 4453145b..23194545 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -25,6 +25,7 @@ pub mod checkbox; pub mod column; pub mod container; pub mod image; +pub mod image_pane; pub mod pane_grid; pub mod progress_bar; pub mod radio; @@ -47,6 +48,8 @@ pub use container::Container; #[doc(no_inline)] pub use image::Image; #[doc(no_inline)] +pub use image_pane::ImagePane; +#[doc(no_inline)] pub use pane_grid::PaneGrid; #[doc(no_inline)] pub use progress_bar::ProgressBar; diff --git a/native/src/widget/image_pane.rs b/native/src/widget/image_pane.rs new file mode 100644 index 00000000..4d07f228 --- /dev/null +++ b/native/src/widget/image_pane.rs @@ -0,0 +1,407 @@ +//! Zoom and pan on an image. +use crate::{ + image, + input::{self, mouse}, + layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, + Rectangle, Size, Widget, +}; + +use std::{f32, hash::Hash, u32}; + +/// A widget that can display an image with the ability to zoom in/out and pan. +#[allow(missing_debug_implementations)] +pub struct ImagePane<'a> { + state: &'a mut State, + padding: u16, + width: Length, + height: Length, + max_width: u32, + max_height: u32, + handle: image::Handle, +} + +impl<'a> ImagePane<'a> { + /// Creates a new [`ImagePane`] with the given [`State`] and [`Handle`]. + /// + /// [`ImagePane`]: struct.ImagePane.html + /// [`State`]: struct.State.html + /// [`Handle`]: ../image/struct.Handle.html + pub fn new(state: &'a mut State, handle: image::Handle) -> Self { + ImagePane { + state, + padding: 0, + width: Length::Shrink, + height: Length::Shrink, + max_width: u32::MAX, + max_height: u32::MAX, + handle, + } + } + + /// Sets the padding of the [`ImagePane`]. + /// + /// [`ImagePane`]: struct.ImagePane.html + pub fn padding(mut self, units: u16) -> Self { + self.padding = units; + self + } + + /// Sets the width of the [`ImagePane`]. + /// + /// [`ImagePane`]: struct.ImagePane.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`ImagePane`]. + /// + /// [`ImagePane`]: struct.ImagePane.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the max width of the [`ImagePane`]. + /// + /// [`ImagePane`]: struct.ImagePane.html + pub fn max_width(mut self, max_width: u32) -> Self { + self.max_width = max_width; + self + } + + /// Sets the max height of the [`ImagePane`]. + /// + /// [`ImagePane`]: struct.ImagePane.html + pub fn max_height(mut self, max_height: u32) -> Self { + self.max_height = max_height; + self + } +} + +impl<'a, Message, Renderer> Widget for ImagePane<'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 padding = f32::from(self.padding); + + let limits = limits + .max_width(self.max_width) + .max_height(self.max_height) + .width(self.width) + .height(self.height) + .pad(padding); + + let size = limits.resolve(Size::INFINITY); + + layout::Node::new(size) + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + _messages: &mut Vec, + renderer: &Renderer, + _clipboard: Option<&dyn Clipboard>, + ) { + let bounds = layout.bounds(); + let is_mouse_over = bounds.contains(cursor_position); + + let image_bounds = { + let (width, height) = renderer.dimensions(&self.handle); + + let dimensions = if let Some(scale) = self.state.scale { + (width as f32 * scale, height as f32 * scale) + } else { + let dimensions = (width as f32, height as f32); + + let width_scale = bounds.width / dimensions.0; + let height_scale = bounds.height / dimensions.1; + + let scale = width_scale.min(height_scale); + + if scale < 1.0 { + (dimensions.0 * scale, dimensions.1 * scale) + } else { + (dimensions.0, dimensions.1) + } + }; + + Rectangle { + x: bounds.x, + y: bounds.y, + width: dimensions.0, + height: dimensions.1, + } + }; + + if is_mouse_over { + match event { + Event::Mouse(mouse::Event::WheelScrolled { delta }) => { + match delta { + mouse::ScrollDelta::Lines { y, .. } => { + // TODO: Configurable step and limits + if y > 0.0 { + self.state.scale = Some( + (self.state.scale.unwrap_or(1.0) + 0.25) + .min(10.0), + ); + } else { + self.state.scale = Some( + (self.state.scale.unwrap_or(1.0) - 0.25) + .max(0.25), + ); + } + } + mouse::ScrollDelta::Pixels { y, .. } => { + // TODO: Configurable step and limits + if y > 0.0 { + self.state.scale = Some( + (self.state.scale.unwrap_or(1.0) + 0.25) + .min(10.0), + ); + } else { + self.state.scale = Some( + (self.state.scale.unwrap_or(1.0) - 0.25) + .max(0.25), + ); + } + } + } + } + Event::Mouse(mouse::Event::Input { button, state }) => { + if button == mouse::Button::Left { + match state { + input::ButtonState::Pressed => { + self.state.starting_cursor_pos = Some(( + cursor_position.x, + cursor_position.y, + )); + + self.state.starting_offset = + self.state.current_offset; + } + input::ButtonState::Released => { + self.state.starting_cursor_pos = None + } + } + } + } + Event::Mouse(mouse::Event::CursorMoved { x, y }) => { + if self.state.is_cursor_clicked() { + self.state.pan(x, y, bounds, image_bounds); + } + } + _ => {} + } + } else if let Event::Mouse(mouse::Event::Input { button, state }) = + event + { + if button == mouse::Button::Left + && state == input::ButtonState::Released + { + self.state.starting_cursor_pos = None; + } + } + } + + fn draw( + &self, + renderer: &mut Renderer, + _defaults: &Renderer::Defaults, + layout: Layout<'_>, + cursor_position: Point, + ) -> Renderer::Output { + let bounds = layout.bounds(); + + let image_bounds = { + let (width, height) = renderer.dimensions(&self.handle); + + let dimensions = if let Some(scale) = self.state.scale { + (width as f32 * scale, height as f32 * scale) + } else { + let dimensions = (width as f32, height as f32); + + let width_scale = bounds.width / dimensions.0; + let height_scale = bounds.height / dimensions.1; + + let scale = width_scale.min(height_scale); + + if scale < 1.0 { + (dimensions.0 * scale, dimensions.1 * scale) + } else { + (dimensions.0, dimensions.1) + } + }; + + Rectangle { + x: bounds.x, + y: bounds.y, + width: dimensions.0, + height: dimensions.1, + } + }; + + let offset = self.state.offset(bounds, image_bounds); + + let is_mouse_over = bounds.contains(cursor_position); + + self::Renderer::draw( + renderer, + &self.state, + bounds, + image_bounds, + offset, + self.handle.clone(), + is_mouse_over, + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + + self.width.hash(state); + self.height.hash(state); + self.max_width.hash(state); + self.max_height.hash(state); + self.padding.hash(state); + + self.handle.hash(state); + } +} + +/// The local state of an [`ImagePane`]. +/// +/// [`ImagePane`]: struct.ImagePane.html +#[derive(Debug, Clone, Copy, Default)] +pub struct State { + scale: Option, + starting_offset: (f32, f32), + current_offset: (f32, f32), + starting_cursor_pos: Option<(f32, f32)>, +} + +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 panning offset to the current [`State`], given the bounds of + /// the [`ImagePane`] and its image. + /// + /// [`ImagePane`]: struct.ImagePane.html + /// [`State`]: struct.State.html + fn pan( + &mut self, + x: f32, + y: f32, + bounds: Rectangle, + image_bounds: Rectangle, + ) { + let delta_x = x - self.starting_cursor_pos.unwrap().0; + let delta_y = y - self.starting_cursor_pos.unwrap().1; + + if bounds.width < image_bounds.width { + self.current_offset.0 = (self.starting_offset.0 - delta_x) + .max(0.0) + .min((image_bounds.width - bounds.width) as f32); + } + + if bounds.height < image_bounds.height { + self.current_offset.1 = (self.starting_offset.1 - delta_y) + .max(0.0) + .min((image_bounds.height - bounds.height) as f32); + } + } + + /// Returns the current clipping offset of the [`State`], given the bounds + /// of the [`ImagePane`] and its contents. + /// + /// [`ImagePane`]: struct.ImagePane.html + /// [`State`]: struct.State.html + fn offset(&self, bounds: Rectangle, image_bounds: Rectangle) -> (u32, u32) { + let hidden_width = ((image_bounds.width - bounds.width) as f32) + .max(0.0) + .round() as u32; + + let hidden_height = ((image_bounds.height - bounds.height) as f32) + .max(0.0) + .round() as u32; + + ( + (self.current_offset.0).min(hidden_width as f32) as u32, + (self.current_offset.1).min(hidden_height as f32) as u32, + ) + } + + /// Returns if the left mouse button is still held down since clicking inside + /// the [`ImagePane`]. + /// + /// [`ImagePane`]: struct.ImagePane.html + /// [`State`]: struct.State.html + pub fn is_cursor_clicked(&self) -> bool { + self.starting_cursor_pos.is_some() + } +} + +/// The renderer of an [`ImagePane`]. +/// +/// Your [renderer] will need to implement this trait before being +/// able to use a [`ImagePane`] in your user interface. +/// +/// [`ImagePane`]: struct.ImagePane.html +/// [renderer]: ../../renderer/index.html +pub trait Renderer: crate::Renderer + Sized { + /// Draws the [`ImagePane`]. + /// + /// It receives: + /// - the [`State`] of the [`ImagePane`] + /// - the bounds of the [`ImagePane`] widget + /// - the bounds of the scaled [`ImagePane`] image + /// - the clipping x,y offset + /// - the [`Handle`] to the underlying image + /// - whether the mouse is over the [`ImagePane`] or not + /// + /// [`ImagePane`]: struct.ImagePane.html + /// [`State`]: struct.State.html + /// [`Handle`]: ../image/struct.Handle.html + fn draw( + &mut self, + state: &State, + bounds: Rectangle, + image_bounds: Rectangle, + offset: (u32, u32), + handle: image::Handle, + is_mouse_over: bool, + ) -> Self::Output; +} + +impl<'a, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + Renderer: 'a + self::Renderer + image::Renderer, + Message: 'a, +{ + fn from(image_pane: ImagePane<'a>) -> Element<'a, Message, Renderer> { + Element::new(image_pane) + } +} -- cgit From 6bf459e068043847a0ee1e1219056d3aced3f1cb Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Thu, 14 May 2020 11:54:05 -0700 Subject: Rebase to master and update for api changes --- native/src/widget/image_pane.rs | 55 ++++++++++++----------------------------- 1 file changed, 16 insertions(+), 39 deletions(-) (limited to 'native') diff --git a/native/src/widget/image_pane.rs b/native/src/widget/image_pane.rs index 4d07f228..4f3d4877 100644 --- a/native/src/widget/image_pane.rs +++ b/native/src/widget/image_pane.rs @@ -1,9 +1,7 @@ //! Zoom and pan on an image. use crate::{ - image, - input::{self, mouse}, - layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, - Rectangle, Size, Widget, + image, layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, + Point, Rectangle, Size, Widget, }; use std::{f32, hash::Hash, u32}; @@ -154,21 +152,8 @@ where match event { Event::Mouse(mouse::Event::WheelScrolled { delta }) => { match delta { - mouse::ScrollDelta::Lines { y, .. } => { - // TODO: Configurable step and limits - if y > 0.0 { - self.state.scale = Some( - (self.state.scale.unwrap_or(1.0) + 0.25) - .min(10.0), - ); - } else { - self.state.scale = Some( - (self.state.scale.unwrap_or(1.0) - 0.25) - .max(0.25), - ); - } - } - mouse::ScrollDelta::Pixels { y, .. } => { + mouse::ScrollDelta::Lines { y, .. } + | mouse::ScrollDelta::Pixels { y, .. } => { // TODO: Configurable step and limits if y > 0.0 { self.state.scale = Some( @@ -184,22 +169,17 @@ where } } } - Event::Mouse(mouse::Event::Input { button, state }) => { + Event::Mouse(mouse::Event::ButtonPressed(button)) => { if button == mouse::Button::Left { - match state { - input::ButtonState::Pressed => { - self.state.starting_cursor_pos = Some(( - cursor_position.x, - cursor_position.y, - )); - - self.state.starting_offset = - self.state.current_offset; - } - input::ButtonState::Released => { - self.state.starting_cursor_pos = None - } - } + self.state.starting_cursor_pos = + Some((cursor_position.x, cursor_position.y)); + + self.state.starting_offset = self.state.current_offset; + } + } + Event::Mouse(mouse::Event::ButtonReleased(button)) => { + if button == mouse::Button::Left { + self.state.starting_cursor_pos = None } } Event::Mouse(mouse::Event::CursorMoved { x, y }) => { @@ -209,12 +189,9 @@ where } _ => {} } - } else if let Event::Mouse(mouse::Event::Input { button, state }) = - event + } else if let Event::Mouse(mouse::Event::ButtonReleased(button)) = event { - if button == mouse::Button::Left - && state == input::ButtonState::Released - { + if button == mouse::Button::Left { self.state.starting_cursor_pos = None; } } -- cgit From 431171f975642fe96286f11fb75cd5b06827cc7f Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Fri, 15 May 2020 09:46:22 -0700 Subject: Rename and add to iced image module --- native/src/widget.rs | 4 +- native/src/widget/image_pane.rs | 384 -------------------------------------- native/src/widget/image_viewer.rs | 384 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 386 insertions(+), 386 deletions(-) delete mode 100644 native/src/widget/image_pane.rs create mode 100644 native/src/widget/image_viewer.rs (limited to 'native') diff --git a/native/src/widget.rs b/native/src/widget.rs index 23194545..46d41367 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -25,7 +25,7 @@ pub mod checkbox; pub mod column; pub mod container; pub mod image; -pub mod image_pane; +pub mod image_viewer; pub mod pane_grid; pub mod progress_bar; pub mod radio; @@ -48,7 +48,7 @@ pub use container::Container; #[doc(no_inline)] pub use image::Image; #[doc(no_inline)] -pub use image_pane::ImagePane; +pub use image_viewer::ImageViewer; #[doc(no_inline)] pub use pane_grid::PaneGrid; #[doc(no_inline)] diff --git a/native/src/widget/image_pane.rs b/native/src/widget/image_pane.rs deleted file mode 100644 index 4f3d4877..00000000 --- a/native/src/widget/image_pane.rs +++ /dev/null @@ -1,384 +0,0 @@ -//! Zoom and pan on an image. -use crate::{ - image, layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, - Point, Rectangle, Size, Widget, -}; - -use std::{f32, hash::Hash, u32}; - -/// A widget that can display an image with the ability to zoom in/out and pan. -#[allow(missing_debug_implementations)] -pub struct ImagePane<'a> { - state: &'a mut State, - padding: u16, - width: Length, - height: Length, - max_width: u32, - max_height: u32, - handle: image::Handle, -} - -impl<'a> ImagePane<'a> { - /// Creates a new [`ImagePane`] with the given [`State`] and [`Handle`]. - /// - /// [`ImagePane`]: struct.ImagePane.html - /// [`State`]: struct.State.html - /// [`Handle`]: ../image/struct.Handle.html - pub fn new(state: &'a mut State, handle: image::Handle) -> Self { - ImagePane { - state, - padding: 0, - width: Length::Shrink, - height: Length::Shrink, - max_width: u32::MAX, - max_height: u32::MAX, - handle, - } - } - - /// Sets the padding of the [`ImagePane`]. - /// - /// [`ImagePane`]: struct.ImagePane.html - pub fn padding(mut self, units: u16) -> Self { - self.padding = units; - self - } - - /// Sets the width of the [`ImagePane`]. - /// - /// [`ImagePane`]: struct.ImagePane.html - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`ImagePane`]. - /// - /// [`ImagePane`]: struct.ImagePane.html - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the max width of the [`ImagePane`]. - /// - /// [`ImagePane`]: struct.ImagePane.html - pub fn max_width(mut self, max_width: u32) -> Self { - self.max_width = max_width; - self - } - - /// Sets the max height of the [`ImagePane`]. - /// - /// [`ImagePane`]: struct.ImagePane.html - pub fn max_height(mut self, max_height: u32) -> Self { - self.max_height = max_height; - self - } -} - -impl<'a, Message, Renderer> Widget for ImagePane<'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 padding = f32::from(self.padding); - - let limits = limits - .max_width(self.max_width) - .max_height(self.max_height) - .width(self.width) - .height(self.height) - .pad(padding); - - let size = limits.resolve(Size::INFINITY); - - layout::Node::new(size) - } - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - _messages: &mut Vec, - renderer: &Renderer, - _clipboard: Option<&dyn Clipboard>, - ) { - let bounds = layout.bounds(); - let is_mouse_over = bounds.contains(cursor_position); - - let image_bounds = { - let (width, height) = renderer.dimensions(&self.handle); - - let dimensions = if let Some(scale) = self.state.scale { - (width as f32 * scale, height as f32 * scale) - } else { - let dimensions = (width as f32, height as f32); - - let width_scale = bounds.width / dimensions.0; - let height_scale = bounds.height / dimensions.1; - - let scale = width_scale.min(height_scale); - - if scale < 1.0 { - (dimensions.0 * scale, dimensions.1 * scale) - } else { - (dimensions.0, dimensions.1) - } - }; - - Rectangle { - x: bounds.x, - y: bounds.y, - width: dimensions.0, - height: dimensions.1, - } - }; - - if is_mouse_over { - match event { - Event::Mouse(mouse::Event::WheelScrolled { delta }) => { - match delta { - mouse::ScrollDelta::Lines { y, .. } - | mouse::ScrollDelta::Pixels { y, .. } => { - // TODO: Configurable step and limits - if y > 0.0 { - self.state.scale = Some( - (self.state.scale.unwrap_or(1.0) + 0.25) - .min(10.0), - ); - } else { - self.state.scale = Some( - (self.state.scale.unwrap_or(1.0) - 0.25) - .max(0.25), - ); - } - } - } - } - Event::Mouse(mouse::Event::ButtonPressed(button)) => { - if button == mouse::Button::Left { - self.state.starting_cursor_pos = - Some((cursor_position.x, cursor_position.y)); - - self.state.starting_offset = self.state.current_offset; - } - } - Event::Mouse(mouse::Event::ButtonReleased(button)) => { - if button == mouse::Button::Left { - self.state.starting_cursor_pos = None - } - } - Event::Mouse(mouse::Event::CursorMoved { x, y }) => { - if self.state.is_cursor_clicked() { - self.state.pan(x, y, bounds, image_bounds); - } - } - _ => {} - } - } else if let Event::Mouse(mouse::Event::ButtonReleased(button)) = event - { - if button == mouse::Button::Left { - self.state.starting_cursor_pos = None; - } - } - } - - fn draw( - &self, - renderer: &mut Renderer, - _defaults: &Renderer::Defaults, - layout: Layout<'_>, - cursor_position: Point, - ) -> Renderer::Output { - let bounds = layout.bounds(); - - let image_bounds = { - let (width, height) = renderer.dimensions(&self.handle); - - let dimensions = if let Some(scale) = self.state.scale { - (width as f32 * scale, height as f32 * scale) - } else { - let dimensions = (width as f32, height as f32); - - let width_scale = bounds.width / dimensions.0; - let height_scale = bounds.height / dimensions.1; - - let scale = width_scale.min(height_scale); - - if scale < 1.0 { - (dimensions.0 * scale, dimensions.1 * scale) - } else { - (dimensions.0, dimensions.1) - } - }; - - Rectangle { - x: bounds.x, - y: bounds.y, - width: dimensions.0, - height: dimensions.1, - } - }; - - let offset = self.state.offset(bounds, image_bounds); - - let is_mouse_over = bounds.contains(cursor_position); - - self::Renderer::draw( - renderer, - &self.state, - bounds, - image_bounds, - offset, - self.handle.clone(), - is_mouse_over, - ) - } - - fn hash_layout(&self, state: &mut Hasher) { - struct Marker; - std::any::TypeId::of::().hash(state); - - self.width.hash(state); - self.height.hash(state); - self.max_width.hash(state); - self.max_height.hash(state); - self.padding.hash(state); - - self.handle.hash(state); - } -} - -/// The local state of an [`ImagePane`]. -/// -/// [`ImagePane`]: struct.ImagePane.html -#[derive(Debug, Clone, Copy, Default)] -pub struct State { - scale: Option, - starting_offset: (f32, f32), - current_offset: (f32, f32), - starting_cursor_pos: Option<(f32, f32)>, -} - -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 panning offset to the current [`State`], given the bounds of - /// the [`ImagePane`] and its image. - /// - /// [`ImagePane`]: struct.ImagePane.html - /// [`State`]: struct.State.html - fn pan( - &mut self, - x: f32, - y: f32, - bounds: Rectangle, - image_bounds: Rectangle, - ) { - let delta_x = x - self.starting_cursor_pos.unwrap().0; - let delta_y = y - self.starting_cursor_pos.unwrap().1; - - if bounds.width < image_bounds.width { - self.current_offset.0 = (self.starting_offset.0 - delta_x) - .max(0.0) - .min((image_bounds.width - bounds.width) as f32); - } - - if bounds.height < image_bounds.height { - self.current_offset.1 = (self.starting_offset.1 - delta_y) - .max(0.0) - .min((image_bounds.height - bounds.height) as f32); - } - } - - /// Returns the current clipping offset of the [`State`], given the bounds - /// of the [`ImagePane`] and its contents. - /// - /// [`ImagePane`]: struct.ImagePane.html - /// [`State`]: struct.State.html - fn offset(&self, bounds: Rectangle, image_bounds: Rectangle) -> (u32, u32) { - let hidden_width = ((image_bounds.width - bounds.width) as f32) - .max(0.0) - .round() as u32; - - let hidden_height = ((image_bounds.height - bounds.height) as f32) - .max(0.0) - .round() as u32; - - ( - (self.current_offset.0).min(hidden_width as f32) as u32, - (self.current_offset.1).min(hidden_height as f32) as u32, - ) - } - - /// Returns if the left mouse button is still held down since clicking inside - /// the [`ImagePane`]. - /// - /// [`ImagePane`]: struct.ImagePane.html - /// [`State`]: struct.State.html - pub fn is_cursor_clicked(&self) -> bool { - self.starting_cursor_pos.is_some() - } -} - -/// The renderer of an [`ImagePane`]. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use a [`ImagePane`] in your user interface. -/// -/// [`ImagePane`]: struct.ImagePane.html -/// [renderer]: ../../renderer/index.html -pub trait Renderer: crate::Renderer + Sized { - /// Draws the [`ImagePane`]. - /// - /// It receives: - /// - the [`State`] of the [`ImagePane`] - /// - the bounds of the [`ImagePane`] widget - /// - the bounds of the scaled [`ImagePane`] image - /// - the clipping x,y offset - /// - the [`Handle`] to the underlying image - /// - whether the mouse is over the [`ImagePane`] or not - /// - /// [`ImagePane`]: struct.ImagePane.html - /// [`State`]: struct.State.html - /// [`Handle`]: ../image/struct.Handle.html - fn draw( - &mut self, - state: &State, - bounds: Rectangle, - image_bounds: Rectangle, - offset: (u32, u32), - handle: image::Handle, - is_mouse_over: bool, - ) -> Self::Output; -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - Renderer: 'a + self::Renderer + image::Renderer, - Message: 'a, -{ - fn from(image_pane: ImagePane<'a>) -> Element<'a, Message, Renderer> { - Element::new(image_pane) - } -} diff --git a/native/src/widget/image_viewer.rs b/native/src/widget/image_viewer.rs new file mode 100644 index 00000000..d0f31cb4 --- /dev/null +++ b/native/src/widget/image_viewer.rs @@ -0,0 +1,384 @@ +//! Zoom and pan on an image. +use crate::{ + image, layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, + Point, Rectangle, Size, Widget, +}; + +use std::{f32, hash::Hash, u32}; + +/// A widget that can display an image with the ability to zoom in/out and pan. +#[allow(missing_debug_implementations)] +pub struct ImageViewer<'a> { + state: &'a mut State, + padding: u16, + width: Length, + height: Length, + max_width: u32, + max_height: u32, + handle: image::Handle, +} + +impl<'a> ImageViewer<'a> { + /// Creates a new [`ImageViewer`] with the given [`State`] and [`Handle`]. + /// + /// [`ImageViewer`]: struct.ImageViewer.html + /// [`State`]: struct.State.html + /// [`Handle`]: ../image/struct.Handle.html + pub fn new(state: &'a mut State, handle: image::Handle) -> Self { + ImageViewer { + state, + padding: 0, + width: Length::Shrink, + height: Length::Shrink, + max_width: u32::MAX, + max_height: u32::MAX, + handle, + } + } + + /// Sets the padding of the [`ImageViewer`]. + /// + /// [`ImageViewer`]: struct.ImageViewer.html + pub fn padding(mut self, units: u16) -> Self { + self.padding = units; + self + } + + /// Sets the width of the [`ImageViewer`]. + /// + /// [`ImageViewer`]: struct.ImageViewer.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`ImageViewer`]. + /// + /// [`ImageViewer`]: struct.ImageViewer.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the max width of the [`ImageViewer`]. + /// + /// [`ImageViewer`]: struct.ImageViewer.html + pub fn max_width(mut self, max_width: u32) -> Self { + self.max_width = max_width; + self + } + + /// Sets the max height of the [`ImageViewer`]. + /// + /// [`ImageViewer`]: struct.ImageViewer.html + pub fn max_height(mut self, max_height: u32) -> Self { + self.max_height = max_height; + self + } +} + +impl<'a, Message, Renderer> Widget for ImageViewer<'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 padding = f32::from(self.padding); + + let limits = limits + .max_width(self.max_width) + .max_height(self.max_height) + .width(self.width) + .height(self.height) + .pad(padding); + + let size = limits.resolve(Size::INFINITY); + + layout::Node::new(size) + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + _messages: &mut Vec, + renderer: &Renderer, + _clipboard: Option<&dyn Clipboard>, + ) { + let bounds = layout.bounds(); + let is_mouse_over = bounds.contains(cursor_position); + + let image_bounds = { + let (width, height) = renderer.dimensions(&self.handle); + + let dimensions = if let Some(scale) = self.state.scale { + (width as f32 * scale, height as f32 * scale) + } else { + let dimensions = (width as f32, height as f32); + + let width_scale = bounds.width / dimensions.0; + let height_scale = bounds.height / dimensions.1; + + let scale = width_scale.min(height_scale); + + if scale < 1.0 { + (dimensions.0 * scale, dimensions.1 * scale) + } else { + (dimensions.0, dimensions.1) + } + }; + + Rectangle { + x: bounds.x, + y: bounds.y, + width: dimensions.0, + height: dimensions.1, + } + }; + + if is_mouse_over { + match event { + Event::Mouse(mouse::Event::WheelScrolled { delta }) => { + match delta { + mouse::ScrollDelta::Lines { y, .. } + | mouse::ScrollDelta::Pixels { y, .. } => { + // TODO: Configurable step and limits + if y > 0.0 { + self.state.scale = Some( + (self.state.scale.unwrap_or(1.0) + 0.25) + .min(10.0), + ); + } else { + self.state.scale = Some( + (self.state.scale.unwrap_or(1.0) - 0.25) + .max(0.25), + ); + } + } + } + } + Event::Mouse(mouse::Event::ButtonPressed(button)) => { + if button == mouse::Button::Left { + self.state.starting_cursor_pos = + Some((cursor_position.x, cursor_position.y)); + + self.state.starting_offset = self.state.current_offset; + } + } + Event::Mouse(mouse::Event::ButtonReleased(button)) => { + if button == mouse::Button::Left { + self.state.starting_cursor_pos = None + } + } + Event::Mouse(mouse::Event::CursorMoved { x, y }) => { + if self.state.is_cursor_clicked() { + self.state.pan(x, y, bounds, image_bounds); + } + } + _ => {} + } + } else if let Event::Mouse(mouse::Event::ButtonReleased(button)) = event + { + if button == mouse::Button::Left { + self.state.starting_cursor_pos = None; + } + } + } + + fn draw( + &self, + renderer: &mut Renderer, + _defaults: &Renderer::Defaults, + layout: Layout<'_>, + cursor_position: Point, + ) -> Renderer::Output { + let bounds = layout.bounds(); + + let image_bounds = { + let (width, height) = renderer.dimensions(&self.handle); + + let dimensions = if let Some(scale) = self.state.scale { + (width as f32 * scale, height as f32 * scale) + } else { + let dimensions = (width as f32, height as f32); + + let width_scale = bounds.width / dimensions.0; + let height_scale = bounds.height / dimensions.1; + + let scale = width_scale.min(height_scale); + + if scale < 1.0 { + (dimensions.0 * scale, dimensions.1 * scale) + } else { + (dimensions.0, dimensions.1) + } + }; + + Rectangle { + x: bounds.x, + y: bounds.y, + width: dimensions.0, + height: dimensions.1, + } + }; + + let offset = self.state.offset(bounds, image_bounds); + + let is_mouse_over = bounds.contains(cursor_position); + + self::Renderer::draw( + renderer, + &self.state, + bounds, + image_bounds, + offset, + self.handle.clone(), + is_mouse_over, + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + + self.width.hash(state); + self.height.hash(state); + self.max_width.hash(state); + self.max_height.hash(state); + self.padding.hash(state); + + self.handle.hash(state); + } +} + +/// The local state of an [`ImageViewer`]. +/// +/// [`ImageViewer`]: struct.ImageViewer.html +#[derive(Debug, Clone, Copy, Default)] +pub struct State { + scale: Option, + starting_offset: (f32, f32), + current_offset: (f32, f32), + starting_cursor_pos: Option<(f32, f32)>, +} + +impl State { + /// Creates a new [`State`]. + /// + /// [`State`]: struct.State.html + pub fn new() -> Self { + State::default() + } + + /// Apply a panning offset to the current [`State`], given the bounds of + /// the [`ImageViewer`] and its image. + /// + /// [`ImageViewer`]: struct.ImageViewer.html + /// [`State`]: struct.State.html + fn pan( + &mut self, + x: f32, + y: f32, + bounds: Rectangle, + image_bounds: Rectangle, + ) { + let delta_x = x - self.starting_cursor_pos.unwrap().0; + let delta_y = y - self.starting_cursor_pos.unwrap().1; + + if bounds.width < image_bounds.width { + self.current_offset.0 = (self.starting_offset.0 - delta_x) + .max(0.0) + .min((image_bounds.width - bounds.width) as f32); + } + + if bounds.height < image_bounds.height { + self.current_offset.1 = (self.starting_offset.1 - delta_y) + .max(0.0) + .min((image_bounds.height - bounds.height) as f32); + } + } + + /// Returns the current clipping offset of the [`State`], given the bounds + /// of the [`ImageViewer`] and its contents. + /// + /// [`ImageViewer`]: struct.ImageViewer.html + /// [`State`]: struct.State.html + fn offset(&self, bounds: Rectangle, image_bounds: Rectangle) -> (u32, u32) { + let hidden_width = ((image_bounds.width - bounds.width) as f32) + .max(0.0) + .round() as u32; + + let hidden_height = ((image_bounds.height - bounds.height) as f32) + .max(0.0) + .round() as u32; + + ( + (self.current_offset.0).min(hidden_width as f32) as u32, + (self.current_offset.1).min(hidden_height as f32) as u32, + ) + } + + /// Returns if the left mouse button is still held down since clicking inside + /// the [`ImageViewer`]. + /// + /// [`ImageViewer`]: struct.ImageViewer.html + /// [`State`]: struct.State.html + pub fn is_cursor_clicked(&self) -> bool { + self.starting_cursor_pos.is_some() + } +} + +/// The renderer of an [`ImageViewer`]. +/// +/// Your [renderer] will need to implement this trait before being +/// able to use a [`ImageViewer`] in your user interface. +/// +/// [`ImageViewer`]: struct.ImageViewer.html +/// [renderer]: ../../renderer/index.html +pub trait Renderer: crate::Renderer + Sized { + /// Draws the [`ImageViewer`]. + /// + /// It receives: + /// - the [`State`] of the [`ImageViewer`] + /// - the bounds of the [`ImageViewer`] widget + /// - the bounds of the scaled [`ImageViewer`] image + /// - the clipping x,y offset + /// - the [`Handle`] to the underlying image + /// - whether the mouse is over the [`ImageViewer`] or not + /// + /// [`ImageViewer`]: struct.ImageViewer.html + /// [`State`]: struct.State.html + /// [`Handle`]: ../image/struct.Handle.html + fn draw( + &mut self, + state: &State, + bounds: Rectangle, + image_bounds: Rectangle, + offset: (u32, u32), + handle: image::Handle, + is_mouse_over: bool, + ) -> Self::Output; +} + +impl<'a, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + Renderer: 'a + self::Renderer + image::Renderer, + Message: 'a, +{ + fn from(viewer: ImageViewer<'a>) -> Element<'a, Message, Renderer> { + Element::new(viewer) + } +} -- cgit From 5d045c2e9a639f8bbf43e68fde9091be702b3ab8 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Tue, 26 May 2020 17:15:55 -0700 Subject: rename to image::Viewer --- native/src/widget.rs | 3 - native/src/widget/image.rs | 3 + native/src/widget/image/viewer.rs | 383 +++++++++++++++++++++++++++++++++++++ native/src/widget/image_viewer.rs | 384 -------------------------------------- 4 files changed, 386 insertions(+), 387 deletions(-) create mode 100644 native/src/widget/image/viewer.rs delete mode 100644 native/src/widget/image_viewer.rs (limited to 'native') diff --git a/native/src/widget.rs b/native/src/widget.rs index 46d41367..4453145b 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -25,7 +25,6 @@ pub mod checkbox; pub mod column; pub mod container; pub mod image; -pub mod image_viewer; pub mod pane_grid; pub mod progress_bar; pub mod radio; @@ -48,8 +47,6 @@ pub use container::Container; #[doc(no_inline)] pub use image::Image; #[doc(no_inline)] -pub use image_viewer::ImageViewer; -#[doc(no_inline)] pub use pane_grid::PaneGrid; #[doc(no_inline)] pub use progress_bar::ProgressBar; diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 132f249d..685cb81a 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -1,4 +1,7 @@ //! Display images in your user interface. +pub mod viewer; +pub use viewer::{State, Viewer}; + use crate::{layout, Element, Hasher, Layout, Length, Point, Size, Widget}; use std::{ diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs new file mode 100644 index 00000000..c33f3a5e --- /dev/null +++ b/native/src/widget/image/viewer.rs @@ -0,0 +1,383 @@ +//! Zoom and pan on an image. +use crate::{ + image, layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, + Point, Rectangle, Size, Widget, +}; + +use std::{f32, hash::Hash, u32}; + +/// A widget that can display 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, + max_width: u32, + max_height: u32, + handle: image::Handle, +} + +impl<'a> Viewer<'a> { + /// Creates a new [`Viewer`] with the given [`State`] and [`Handle`]. + /// + /// [`Viewer`]: struct.Viewer.html + /// [`State`]: struct.State.html + /// [`Handle`]: ../../image/struct.Handle.html + pub fn new(state: &'a mut State, handle: image::Handle) -> Self { + Viewer { + state, + padding: 0, + width: Length::Shrink, + height: Length::Shrink, + max_width: u32::MAX, + max_height: u32::MAX, + handle, + } + } + + /// Sets the padding of the [`Viewer`]. + /// + /// [`Viewer`]: struct.Viewer.html + pub fn padding(mut self, units: u16) -> Self { + self.padding = units; + self + } + + /// Sets the width of the [`Viewer`]. + /// + /// [`Viewer`]: struct.Viewer.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Viewer`]. + /// + /// [`Viewer`]: struct.Viewer.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the max width of the [`Viewer`]. + /// + /// [`Viewer`]: struct.Viewer.html + pub fn max_width(mut self, max_width: u32) -> Self { + self.max_width = max_width; + self + } + + /// Sets the max height of the [`Viewer`]. + /// + /// [`Viewer`]: struct.Viewer.html + pub fn max_height(mut self, max_height: u32) -> Self { + self.max_height = max_height; + self + } +} + +impl<'a, Message, Renderer> Widget 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 padding = f32::from(self.padding); + + let limits = limits + .max_width(self.max_width) + .max_height(self.max_height) + .width(self.width) + .height(self.height) + .pad(padding); + + let size = limits.resolve(Size::INFINITY); + + layout::Node::new(size) + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + _messages: &mut Vec, + renderer: &Renderer, + _clipboard: Option<&dyn Clipboard>, + ) { + let bounds = layout.bounds(); + let is_mouse_over = bounds.contains(cursor_position); + + let image_bounds = { + let (width, height) = renderer.dimensions(&self.handle); + + let dimensions = if let Some(scale) = self.state.scale { + (width as f32 * scale, height as f32 * scale) + } else { + let dimensions = (width as f32, height as f32); + + let width_scale = bounds.width / dimensions.0; + let height_scale = bounds.height / dimensions.1; + + let scale = width_scale.min(height_scale); + + if scale < 1.0 { + (dimensions.0 * scale, dimensions.1 * scale) + } else { + (dimensions.0, dimensions.1) + } + }; + + Rectangle { + x: bounds.x, + y: bounds.y, + width: dimensions.0, + height: dimensions.1, + } + }; + + if is_mouse_over { + match event { + Event::Mouse(mouse::Event::WheelScrolled { delta }) => { + match delta { + mouse::ScrollDelta::Lines { y, .. } + | mouse::ScrollDelta::Pixels { y, .. } => { + // TODO: Configurable step and limits + if y > 0.0 { + self.state.scale = Some( + (self.state.scale.unwrap_or(1.0) + 0.25) + .min(10.0), + ); + } else { + self.state.scale = Some( + (self.state.scale.unwrap_or(1.0) - 0.25) + .max(0.25), + ); + } + } + } + } + Event::Mouse(mouse::Event::ButtonPressed(button)) => { + if button == mouse::Button::Left { + self.state.starting_cursor_pos = + Some((cursor_position.x, cursor_position.y)); + + self.state.starting_offset = self.state.current_offset; + } + } + Event::Mouse(mouse::Event::ButtonReleased(button)) => { + if button == mouse::Button::Left { + self.state.starting_cursor_pos = None + } + } + Event::Mouse(mouse::Event::CursorMoved { x, y }) => { + if self.state.is_cursor_clicked() { + self.state.pan(x, y, bounds, image_bounds); + } + } + _ => {} + } + } else if let Event::Mouse(mouse::Event::ButtonReleased(button)) = event + { + if button == mouse::Button::Left { + self.state.starting_cursor_pos = None; + } + } + } + + fn draw( + &self, + renderer: &mut Renderer, + _defaults: &Renderer::Defaults, + layout: Layout<'_>, + cursor_position: Point, + ) -> Renderer::Output { + let bounds = layout.bounds(); + + let image_bounds = { + let (width, height) = renderer.dimensions(&self.handle); + + let dimensions = if let Some(scale) = self.state.scale { + (width as f32 * scale, height as f32 * scale) + } else { + let dimensions = (width as f32, height as f32); + + let width_scale = bounds.width / dimensions.0; + let height_scale = bounds.height / dimensions.1; + + let scale = width_scale.min(height_scale); + + if scale < 1.0 { + (dimensions.0 * scale, dimensions.1 * scale) + } else { + (dimensions.0, dimensions.1) + } + }; + + Rectangle { + x: bounds.x, + y: bounds.y, + width: dimensions.0, + height: dimensions.1, + } + }; + + let offset = self.state.offset(bounds, image_bounds); + + let is_mouse_over = bounds.contains(cursor_position); + + self::Renderer::draw( + renderer, + &self.state, + bounds, + image_bounds, + offset, + self.handle.clone(), + is_mouse_over, + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + + self.width.hash(state); + self.height.hash(state); + self.max_width.hash(state); + self.max_height.hash(state); + self.padding.hash(state); + + self.handle.hash(state); + } +} + +/// The local state of a [`Viewer`]. +/// +/// [`Viewer`]: struct.Viewer.html +#[derive(Debug, Clone, Copy, Default)] +pub struct State { + scale: Option, + starting_offset: (f32, f32), + current_offset: (f32, f32), + starting_cursor_pos: Option<(f32, f32)>, +} + +impl State { + /// Creates a new [`State`]. + /// + /// [`State`]: struct.State.html + pub fn new() -> Self { + State::default() + } + + /// Apply a panning offset to the current [`State`], given the bounds of + /// the [`Viewer`] and its image. + /// + /// [`Viewer`]: struct.Viewer.html + /// [`State`]: struct.State.html + fn pan( + &mut self, + x: f32, + y: f32, + bounds: Rectangle, + image_bounds: Rectangle, + ) { + let delta_x = x - self.starting_cursor_pos.unwrap().0; + let delta_y = y - self.starting_cursor_pos.unwrap().1; + + if bounds.width < image_bounds.width { + self.current_offset.0 = (self.starting_offset.0 - delta_x) + .max(0.0) + .min((image_bounds.width - bounds.width) as f32); + } + + if bounds.height < image_bounds.height { + self.current_offset.1 = (self.starting_offset.1 - delta_y) + .max(0.0) + .min((image_bounds.height - bounds.height) as f32); + } + } + + /// Returns the current clipping offset of the [`State`], given the bounds + /// of the [`Viewer`] and its contents. + /// + /// [`Viewer`]: struct.Viewer.html + /// [`State`]: struct.State.html + fn offset(&self, bounds: Rectangle, image_bounds: Rectangle) -> (u32, u32) { + let hidden_width = ((image_bounds.width - bounds.width) as f32) + .max(0.0) + .round() as u32; + + let hidden_height = ((image_bounds.height - bounds.height) as f32) + .max(0.0) + .round() as u32; + + ( + (self.current_offset.0).min(hidden_width as f32) as u32, + (self.current_offset.1).min(hidden_height as f32) as u32, + ) + } + + /// Returns if the left mouse button is still held down since clicking inside + /// the [`Viewer`]. + /// + /// [`Viewer`]: struct.Viewer.html + /// [`State`]: struct.State.html + pub fn is_cursor_clicked(&self) -> bool { + self.starting_cursor_pos.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. +/// +/// [`Viewer`]: struct.Viewer.html +/// [renderer]: ../../../renderer/index.html +pub trait Renderer: crate::Renderer + Sized { + /// Draws the [`Viewer`]. + /// + /// It receives: + /// - the [`State`] of the [`Viewer`] + /// - the bounds of the [`Viewer`] widget + /// - the bounds of the scaled [`Viewer`] image + /// - the clipping x,y offset + /// - the [`Handle`] to the underlying image + /// - whether the mouse is over the [`Viewer`] or not + /// + /// [`Viewer`]: struct.Viewer.html + /// [`State`]: struct.State.html + /// [`Handle`]: ../../image/struct.Handle.html + fn draw( + &mut self, + state: &State, + bounds: Rectangle, + image_bounds: Rectangle, + offset: (u32, u32), + handle: image::Handle, + is_mouse_over: bool, + ) -> Self::Output; +} + +impl<'a, Message, Renderer> From> 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/image_viewer.rs b/native/src/widget/image_viewer.rs deleted file mode 100644 index d0f31cb4..00000000 --- a/native/src/widget/image_viewer.rs +++ /dev/null @@ -1,384 +0,0 @@ -//! Zoom and pan on an image. -use crate::{ - image, layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, - Point, Rectangle, Size, Widget, -}; - -use std::{f32, hash::Hash, u32}; - -/// A widget that can display an image with the ability to zoom in/out and pan. -#[allow(missing_debug_implementations)] -pub struct ImageViewer<'a> { - state: &'a mut State, - padding: u16, - width: Length, - height: Length, - max_width: u32, - max_height: u32, - handle: image::Handle, -} - -impl<'a> ImageViewer<'a> { - /// Creates a new [`ImageViewer`] with the given [`State`] and [`Handle`]. - /// - /// [`ImageViewer`]: struct.ImageViewer.html - /// [`State`]: struct.State.html - /// [`Handle`]: ../image/struct.Handle.html - pub fn new(state: &'a mut State, handle: image::Handle) -> Self { - ImageViewer { - state, - padding: 0, - width: Length::Shrink, - height: Length::Shrink, - max_width: u32::MAX, - max_height: u32::MAX, - handle, - } - } - - /// Sets the padding of the [`ImageViewer`]. - /// - /// [`ImageViewer`]: struct.ImageViewer.html - pub fn padding(mut self, units: u16) -> Self { - self.padding = units; - self - } - - /// Sets the width of the [`ImageViewer`]. - /// - /// [`ImageViewer`]: struct.ImageViewer.html - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`ImageViewer`]. - /// - /// [`ImageViewer`]: struct.ImageViewer.html - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the max width of the [`ImageViewer`]. - /// - /// [`ImageViewer`]: struct.ImageViewer.html - pub fn max_width(mut self, max_width: u32) -> Self { - self.max_width = max_width; - self - } - - /// Sets the max height of the [`ImageViewer`]. - /// - /// [`ImageViewer`]: struct.ImageViewer.html - pub fn max_height(mut self, max_height: u32) -> Self { - self.max_height = max_height; - self - } -} - -impl<'a, Message, Renderer> Widget for ImageViewer<'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 padding = f32::from(self.padding); - - let limits = limits - .max_width(self.max_width) - .max_height(self.max_height) - .width(self.width) - .height(self.height) - .pad(padding); - - let size = limits.resolve(Size::INFINITY); - - layout::Node::new(size) - } - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - _messages: &mut Vec, - renderer: &Renderer, - _clipboard: Option<&dyn Clipboard>, - ) { - let bounds = layout.bounds(); - let is_mouse_over = bounds.contains(cursor_position); - - let image_bounds = { - let (width, height) = renderer.dimensions(&self.handle); - - let dimensions = if let Some(scale) = self.state.scale { - (width as f32 * scale, height as f32 * scale) - } else { - let dimensions = (width as f32, height as f32); - - let width_scale = bounds.width / dimensions.0; - let height_scale = bounds.height / dimensions.1; - - let scale = width_scale.min(height_scale); - - if scale < 1.0 { - (dimensions.0 * scale, dimensions.1 * scale) - } else { - (dimensions.0, dimensions.1) - } - }; - - Rectangle { - x: bounds.x, - y: bounds.y, - width: dimensions.0, - height: dimensions.1, - } - }; - - if is_mouse_over { - match event { - Event::Mouse(mouse::Event::WheelScrolled { delta }) => { - match delta { - mouse::ScrollDelta::Lines { y, .. } - | mouse::ScrollDelta::Pixels { y, .. } => { - // TODO: Configurable step and limits - if y > 0.0 { - self.state.scale = Some( - (self.state.scale.unwrap_or(1.0) + 0.25) - .min(10.0), - ); - } else { - self.state.scale = Some( - (self.state.scale.unwrap_or(1.0) - 0.25) - .max(0.25), - ); - } - } - } - } - Event::Mouse(mouse::Event::ButtonPressed(button)) => { - if button == mouse::Button::Left { - self.state.starting_cursor_pos = - Some((cursor_position.x, cursor_position.y)); - - self.state.starting_offset = self.state.current_offset; - } - } - Event::Mouse(mouse::Event::ButtonReleased(button)) => { - if button == mouse::Button::Left { - self.state.starting_cursor_pos = None - } - } - Event::Mouse(mouse::Event::CursorMoved { x, y }) => { - if self.state.is_cursor_clicked() { - self.state.pan(x, y, bounds, image_bounds); - } - } - _ => {} - } - } else if let Event::Mouse(mouse::Event::ButtonReleased(button)) = event - { - if button == mouse::Button::Left { - self.state.starting_cursor_pos = None; - } - } - } - - fn draw( - &self, - renderer: &mut Renderer, - _defaults: &Renderer::Defaults, - layout: Layout<'_>, - cursor_position: Point, - ) -> Renderer::Output { - let bounds = layout.bounds(); - - let image_bounds = { - let (width, height) = renderer.dimensions(&self.handle); - - let dimensions = if let Some(scale) = self.state.scale { - (width as f32 * scale, height as f32 * scale) - } else { - let dimensions = (width as f32, height as f32); - - let width_scale = bounds.width / dimensions.0; - let height_scale = bounds.height / dimensions.1; - - let scale = width_scale.min(height_scale); - - if scale < 1.0 { - (dimensions.0 * scale, dimensions.1 * scale) - } else { - (dimensions.0, dimensions.1) - } - }; - - Rectangle { - x: bounds.x, - y: bounds.y, - width: dimensions.0, - height: dimensions.1, - } - }; - - let offset = self.state.offset(bounds, image_bounds); - - let is_mouse_over = bounds.contains(cursor_position); - - self::Renderer::draw( - renderer, - &self.state, - bounds, - image_bounds, - offset, - self.handle.clone(), - is_mouse_over, - ) - } - - fn hash_layout(&self, state: &mut Hasher) { - struct Marker; - std::any::TypeId::of::().hash(state); - - self.width.hash(state); - self.height.hash(state); - self.max_width.hash(state); - self.max_height.hash(state); - self.padding.hash(state); - - self.handle.hash(state); - } -} - -/// The local state of an [`ImageViewer`]. -/// -/// [`ImageViewer`]: struct.ImageViewer.html -#[derive(Debug, Clone, Copy, Default)] -pub struct State { - scale: Option, - starting_offset: (f32, f32), - current_offset: (f32, f32), - starting_cursor_pos: Option<(f32, f32)>, -} - -impl State { - /// Creates a new [`State`]. - /// - /// [`State`]: struct.State.html - pub fn new() -> Self { - State::default() - } - - /// Apply a panning offset to the current [`State`], given the bounds of - /// the [`ImageViewer`] and its image. - /// - /// [`ImageViewer`]: struct.ImageViewer.html - /// [`State`]: struct.State.html - fn pan( - &mut self, - x: f32, - y: f32, - bounds: Rectangle, - image_bounds: Rectangle, - ) { - let delta_x = x - self.starting_cursor_pos.unwrap().0; - let delta_y = y - self.starting_cursor_pos.unwrap().1; - - if bounds.width < image_bounds.width { - self.current_offset.0 = (self.starting_offset.0 - delta_x) - .max(0.0) - .min((image_bounds.width - bounds.width) as f32); - } - - if bounds.height < image_bounds.height { - self.current_offset.1 = (self.starting_offset.1 - delta_y) - .max(0.0) - .min((image_bounds.height - bounds.height) as f32); - } - } - - /// Returns the current clipping offset of the [`State`], given the bounds - /// of the [`ImageViewer`] and its contents. - /// - /// [`ImageViewer`]: struct.ImageViewer.html - /// [`State`]: struct.State.html - fn offset(&self, bounds: Rectangle, image_bounds: Rectangle) -> (u32, u32) { - let hidden_width = ((image_bounds.width - bounds.width) as f32) - .max(0.0) - .round() as u32; - - let hidden_height = ((image_bounds.height - bounds.height) as f32) - .max(0.0) - .round() as u32; - - ( - (self.current_offset.0).min(hidden_width as f32) as u32, - (self.current_offset.1).min(hidden_height as f32) as u32, - ) - } - - /// Returns if the left mouse button is still held down since clicking inside - /// the [`ImageViewer`]. - /// - /// [`ImageViewer`]: struct.ImageViewer.html - /// [`State`]: struct.State.html - pub fn is_cursor_clicked(&self) -> bool { - self.starting_cursor_pos.is_some() - } -} - -/// The renderer of an [`ImageViewer`]. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use a [`ImageViewer`] in your user interface. -/// -/// [`ImageViewer`]: struct.ImageViewer.html -/// [renderer]: ../../renderer/index.html -pub trait Renderer: crate::Renderer + Sized { - /// Draws the [`ImageViewer`]. - /// - /// It receives: - /// - the [`State`] of the [`ImageViewer`] - /// - the bounds of the [`ImageViewer`] widget - /// - the bounds of the scaled [`ImageViewer`] image - /// - the clipping x,y offset - /// - the [`Handle`] to the underlying image - /// - whether the mouse is over the [`ImageViewer`] or not - /// - /// [`ImageViewer`]: struct.ImageViewer.html - /// [`State`]: struct.State.html - /// [`Handle`]: ../image/struct.Handle.html - fn draw( - &mut self, - state: &State, - bounds: Rectangle, - image_bounds: Rectangle, - offset: (u32, u32), - handle: image::Handle, - is_mouse_over: bool, - ) -> Self::Output; -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - Renderer: 'a + self::Renderer + image::Renderer, - Message: 'a, -{ - fn from(viewer: ImageViewer<'a>) -> Element<'a, Message, Renderer> { - Element::new(viewer) - } -} -- cgit From de176beb282dcb2818c049957453772c6f530b69 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Wed, 27 May 2020 13:39:26 -0700 Subject: centered image and zoom to cursor --- native/src/widget/image/viewer.rs | 287 ++++++++++++++++++++++++++------------ 1 file changed, 196 insertions(+), 91 deletions(-) (limited to 'native') diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index c33f3a5e..af6d960b 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -1,7 +1,7 @@ //! Zoom and pan on an image. use crate::{ image, layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, - Point, Rectangle, Size, Widget, + Point, Rectangle, Size, Vector, Widget, }; use std::{f32, hash::Hash, u32}; @@ -15,6 +15,9 @@ pub struct Viewer<'a> { height: Length, max_width: u32, max_height: u32, + min_scale: f32, + max_scale: f32, + scale_pct: f32, handle: image::Handle, } @@ -32,6 +35,9 @@ impl<'a> Viewer<'a> { height: Length::Shrink, max_width: u32::MAX, max_height: u32::MAX, + min_scale: 0.25, + max_scale: 10.0, + scale_pct: 0.10, handle, } } @@ -75,6 +81,100 @@ impl<'a> Viewer<'a> { self.max_height = max_height; self } + + /// Sets the max scale applied to the image of the [`Viewer`]. + /// + /// Default is `10.0` + /// + /// [`Viewer`]: struct.Viewer.html + 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` + /// + /// [`Viewer`]: struct.Viewer.html + 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` + /// + /// [`Viewer`]: struct.Viewer.html + pub fn scale_pct(mut self, scale_pct: f32) -> Self { + self.scale_pct = scale_pct; + 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. + /// + /// [`Viewer`]: struct.Viewer.html + fn image_bounds( + &self, + renderer: &Renderer, + bounds: Rectangle, + ) -> Rectangle + where + Renderer: self::Renderer + image::Renderer, + { + let (width, height) = renderer.dimensions(&self.handle); + + let dimensions = { + 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.unwrap_or(1.0); + + if ratio < 1.0 { + (dimensions.0 * ratio * scale, dimensions.1 * ratio * scale) + } else { + (dimensions.0 * scale, dimensions.1 * scale) + } + }; + + Rectangle { + x: bounds.x, + y: bounds.y, + width: dimensions.0, + height: dimensions.1, + } + } + + /// Cursor position relative to the [`Viewer`] bounds. + /// + /// [`Viewer`]: struct.Viewer.html + fn relative_cursor_position( + &self, + mut absolute_position: Point, + bounds: Rectangle, + ) -> Point { + absolute_position.x -= bounds.x; + absolute_position.y -= bounds.y; + absolute_position + } + + /// Center point relative to the [`Viewer`] bounds. + /// + /// [`Viewer`]: struct.Viewer.html + fn relative_center(&self, bounds: Rectangle) -> Point { + let mut center = bounds.center(); + center.x -= bounds.x; + center.y -= bounds.y; + center + } } impl<'a, Message, Renderer> Widget for Viewer<'a> @@ -120,50 +220,59 @@ where let bounds = layout.bounds(); let is_mouse_over = bounds.contains(cursor_position); - let image_bounds = { - let (width, height) = renderer.dimensions(&self.handle); - - let dimensions = if let Some(scale) = self.state.scale { - (width as f32 * scale, height as f32 * scale) - } else { - let dimensions = (width as f32, height as f32); - - let width_scale = bounds.width / dimensions.0; - let height_scale = bounds.height / dimensions.1; - - let scale = width_scale.min(height_scale); - - if scale < 1.0 { - (dimensions.0 * scale, dimensions.1 * scale) - } else { - (dimensions.0, dimensions.1) - } - }; - - Rectangle { - x: bounds.x, - y: bounds.y, - width: dimensions.0, - height: dimensions.1, - } - }; - if is_mouse_over { match event { Event::Mouse(mouse::Event::WheelScrolled { delta }) => { match delta { mouse::ScrollDelta::Lines { y, .. } | mouse::ScrollDelta::Pixels { y, .. } => { - // TODO: Configurable step and limits - if y > 0.0 { + let previous_scale = + self.state.scale.unwrap_or(1.0); + + if y < 0.0 && previous_scale > self.min_scale + || y > 0.0 && previous_scale < self.max_scale + { self.state.scale = Some( - (self.state.scale.unwrap_or(1.0) + 0.25) - .min(10.0), + (if y > 0.0 { + self.state.scale.unwrap_or(1.0) + * (1.0 + self.scale_pct) + } else { + self.state.scale.unwrap_or(1.0) + / (1.0 + self.scale_pct) + }) + .max(self.min_scale) + .min(self.max_scale), ); - } else { - self.state.scale = Some( - (self.state.scale.unwrap_or(1.0) - 0.25) - .max(0.25), + + let image_bounds = + self.image_bounds(renderer, bounds); + + let factor = self.state.scale.unwrap() + / previous_scale + - 1.0; + + let cursor_to_center = + self.relative_cursor_position( + cursor_position, + bounds, + ) - self.relative_center(bounds); + + let adjustment = cursor_to_center * factor + + self.state.current_offset * factor; + + self.state.current_offset = Vector::new( + if image_bounds.width > bounds.width { + self.state.current_offset.x + + adjustment.x + } else { + 0.0 + }, + if image_bounds.height > bounds.height { + self.state.current_offset.y + + adjustment.y + } else { + 0.0 + }, ); } } @@ -171,8 +280,7 @@ where } Event::Mouse(mouse::Event::ButtonPressed(button)) => { if button == mouse::Button::Left { - self.state.starting_cursor_pos = - Some((cursor_position.x, cursor_position.y)); + self.state.starting_cursor_pos = Some(cursor_position); self.state.starting_offset = self.state.current_offset; } @@ -184,6 +292,8 @@ where } Event::Mouse(mouse::Event::CursorMoved { x, y }) => { if self.state.is_cursor_clicked() { + let image_bounds = self.image_bounds(renderer, bounds); + self.state.pan(x, y, bounds, image_bounds); } } @@ -206,36 +316,17 @@ where ) -> Renderer::Output { let bounds = layout.bounds(); - let image_bounds = { - let (width, height) = renderer.dimensions(&self.handle); + let image_bounds = self.image_bounds(renderer, bounds); - let dimensions = if let Some(scale) = self.state.scale { - (width as f32 * scale, height as f32 * scale) - } else { - let dimensions = (width as f32, height as f32); - - let width_scale = bounds.width / dimensions.0; - let height_scale = bounds.height / dimensions.1; + let translation = { + let image_top_left = Vector::new( + bounds.width / 2.0 - image_bounds.width / 2.0, + bounds.height / 2.0 - image_bounds.height / 2.0, + ); - let scale = width_scale.min(height_scale); - - if scale < 1.0 { - (dimensions.0 * scale, dimensions.1 * scale) - } else { - (dimensions.0, dimensions.1) - } - }; - - Rectangle { - x: bounds.x, - y: bounds.y, - width: dimensions.0, - height: dimensions.1, - } + image_top_left - self.state.offset(bounds, image_bounds) }; - let offset = self.state.offset(bounds, image_bounds); - let is_mouse_over = bounds.contains(cursor_position); self::Renderer::draw( @@ -243,7 +334,7 @@ where &self.state, bounds, image_bounds, - offset, + translation, self.handle.clone(), is_mouse_over, ) @@ -269,9 +360,9 @@ where #[derive(Debug, Clone, Copy, Default)] pub struct State { scale: Option, - starting_offset: (f32, f32), - current_offset: (f32, f32), - starting_cursor_pos: Option<(f32, f32)>, + starting_offset: Vector, + current_offset: Vector, + starting_cursor_pos: Option, } impl State { @@ -294,39 +385,53 @@ impl State { bounds: Rectangle, image_bounds: Rectangle, ) { - let delta_x = x - self.starting_cursor_pos.unwrap().0; - let delta_y = y - self.starting_cursor_pos.unwrap().1; + let hidden_width = ((image_bounds.width - bounds.width) as f32 / 2.0) + .max(0.0) + .round(); + let hidden_height = ((image_bounds.height - bounds.height) as f32 + / 2.0) + .max(0.0) + .round(); + + let delta_x = x - self.starting_cursor_pos.unwrap().x; + let delta_y = y - self.starting_cursor_pos.unwrap().y; if bounds.width < image_bounds.width { - self.current_offset.0 = (self.starting_offset.0 - delta_x) - .max(0.0) - .min((image_bounds.width - bounds.width) as f32); + self.current_offset.x = (self.starting_offset.x - delta_x) + .min(hidden_width) + .max(-1.0 * hidden_width); } if bounds.height < image_bounds.height { - self.current_offset.1 = (self.starting_offset.1 - delta_y) - .max(0.0) - .min((image_bounds.height - bounds.height) as f32); + self.current_offset.y = (self.starting_offset.y - delta_y) + .min(hidden_height) + .max(-1.0 * hidden_height); } } - /// Returns the current clipping offset of the [`State`], given the bounds - /// of the [`Viewer`] and its contents. + /// Returns the current offset of the [`State`], given the bounds + /// of the [`Viewer`] and its image. /// /// [`Viewer`]: struct.Viewer.html /// [`State`]: struct.State.html - fn offset(&self, bounds: Rectangle, image_bounds: Rectangle) -> (u32, u32) { - let hidden_width = ((image_bounds.width - bounds.width) as f32) + fn offset(&self, bounds: Rectangle, image_bounds: Rectangle) -> Vector { + let hidden_width = ((image_bounds.width - bounds.width) as f32 / 2.0) .max(0.0) - .round() as u32; - - let hidden_height = ((image_bounds.height - bounds.height) as f32) + .round(); + let hidden_height = ((image_bounds.height - bounds.height) as f32 + / 2.0) .max(0.0) - .round() as u32; - - ( - (self.current_offset.0).min(hidden_width as f32) as u32, - (self.current_offset.1).min(hidden_height as f32) as u32, + .round(); + + Vector::new( + self.current_offset + .x + .min(hidden_width) + .max(-1.0 * hidden_width), + self.current_offset + .y + .min(hidden_height) + .max(-1.0 * hidden_height), ) } @@ -354,7 +459,7 @@ pub trait Renderer: crate::Renderer + Sized { /// - the [`State`] of the [`Viewer`] /// - the bounds of the [`Viewer`] widget /// - the bounds of the scaled [`Viewer`] image - /// - the clipping x,y offset + /// - the translation of the clipped image /// - the [`Handle`] to the underlying image /// - whether the mouse is over the [`Viewer`] or not /// @@ -366,7 +471,7 @@ pub trait Renderer: crate::Renderer + Sized { state: &State, bounds: Rectangle, image_bounds: Rectangle, - offset: (u32, u32), + translation: Vector, handle: image::Handle, is_mouse_over: bool, ) -> Self::Output; -- cgit From 5dd62bacd5b21d460b2e0ff22197a65cace3934b Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Wed, 27 May 2020 14:16:38 -0700 Subject: update docs --- native/src/widget/image/viewer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'native') diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index af6d960b..b129924b 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -6,7 +6,7 @@ use crate::{ use std::{f32, hash::Hash, u32}; -/// A widget that can display an image with the ability to zoom in/out and pan. +/// 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, -- cgit From c7bb43411381a1bffe70ea8e684cd9e4a27739e0 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Wed, 27 May 2020 14:20:07 -0700 Subject: remove re-export on viewer::State --- native/src/widget/image.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'native') diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 685cb81a..49905830 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -1,6 +1,6 @@ //! Display images in your user interface. pub mod viewer; -pub use viewer::{State, Viewer}; +pub use viewer::Viewer; use crate::{layout, Element, Hasher, Layout, Length, Point, Size, Widget}; -- cgit From f54590d7adac611db84b88cbcbf4f56c7542039c Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Tue, 8 Dec 2020 18:47:01 -0600 Subject: Replace TitleBar string title with generic Content --- native/src/renderer/null.rs | 5 +- native/src/widget/pane_grid.rs | 9 +-- native/src/widget/pane_grid/title_bar.rs | 111 +++++++++++-------------------- 3 files changed, 42 insertions(+), 83 deletions(-) (limited to 'native') diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs index 91ee9a28..bea8041c 100644 --- a/native/src/renderer/null.rs +++ b/native/src/renderer/null.rs @@ -276,10 +276,7 @@ impl pane_grid::Renderer for Null { _defaults: &Self::Defaults, _bounds: Rectangle, _style: &Self::Style, - _title: &str, - _title_size: u16, - _title_font: Self::Font, - _title_bounds: Rectangle, + _content: (&Element<'_, Message, Self>, Layout<'_>), _controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>, _cursor_position: Point, ) { diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index ff19cbc2..85ef021f 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -586,18 +586,15 @@ 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 fn draw_title_bar( &mut self, defaults: &Self::Defaults, bounds: Rectangle, style: &Self::Style, - title: &str, - title_size: u16, - title_font: Self::Font, - title_bounds: Rectangle, + content: (&Element<'_, Message, Self>, Layout<'_>), controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>, cursor_position: Point, ) -> Self::Output; diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index 475cb9ae..a73acff7 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -1,30 +1,31 @@ use crate::event::{self, Event}; use crate::layout; use crate::pane_grid; -use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size}; +use crate::{Clipboard, Element, Hasher, Layout, Point, Size}; /// The title bar of a [`Pane`]. /// /// [`Pane`]: crate::widget::pane_grid::Pane #[allow(missing_debug_implementations)] pub struct TitleBar<'a, Message, Renderer: pane_grid::Renderer> { - title: String, - title_size: Option, + content: Element<'a, Message, Renderer>, controls: Option>, padding: u16, always_show_controls: bool, style: Renderer::Style, } -impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer> +impl<'a, Message, Renderer: 'a> TitleBar<'a, Message, Renderer> where Renderer: pane_grid::Renderer, { - /// Creates a new [`TitleBar`] with the given title. - pub fn new(title: impl Into) -> Self { + /// Creates a new [`TitleBar`] with the given content. + pub fn new(content: E) -> Self + where + E: Into>, + { Self { - title: title.into(), - title_size: None, + content: content.into(), controls: None, padding: 0, always_show_controls: false, @@ -32,12 +33,6 @@ where } } - /// Sets the size of the title of the [`TitleBar`]. - pub fn title_size(mut self, size: u16) -> Self { - self.title_size = Some(size); - self - } - /// Sets the controls of the [`TitleBar`]. pub fn controls( mut self, @@ -91,48 +86,29 @@ where 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, + ) } /// Returns whether the mouse cursor is over the pick area of the @@ -165,8 +141,7 @@ 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); } @@ -179,15 +154,10 @@ where let limits = limits.pad(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 @@ -196,16 +166,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( @@ -213,7 +175,10 @@ 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)); -- cgit From 3bdf931925067acbaabf040f6c437a54640ed1a0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 15 Dec 2020 06:38:46 +0100 Subject: Turn `Touch` into a `touch::Event` enum --- native/src/event.rs | 2 +- native/src/touch.rs | 32 +++++++------------- native/src/widget/button.rs | 17 +++-------- native/src/widget/checkbox.rs | 7 ++--- native/src/widget/radio.rs | 7 ++--- native/src/widget/scrollable.rs | 65 ++++++++++++++++++++--------------------- native/src/widget/slider.rs | 18 ++++-------- native/src/widget/text_input.rs | 7 ++--- 8 files changed, 58 insertions(+), 97 deletions(-) (limited to 'native') diff --git a/native/src/event.rs b/native/src/event.rs index f3c260c0..205bb797 100644 --- a/native/src/event.rs +++ b/native/src/event.rs @@ -22,7 +22,7 @@ pub enum Event { Window(window::Event), /// A touch event - Touch(touch::Touch), + Touch(touch::Event), } /// The status of an [`Event`] after being processed. diff --git a/native/src/touch.rs b/native/src/touch.rs index 88bd83bb..18120644 100644 --- a/native/src/touch.rs +++ b/native/src/touch.rs @@ -3,33 +3,21 @@ use crate::Point; /// A touch interaction. #[derive(Debug, Clone, Copy, PartialEq)] -pub struct Touch { - /// The finger of the touch. - pub finger: Finger, - - /// The position of the touch. - pub position: Point, - - /// The state of the touch. - pub phase: Phase, -} - -/// A unique identifier representing a finger on a touch interaction. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct Finger(pub u64); - -/// The state of a touch interaction. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Phase { +#[allow(missing_docs)] +pub enum Event { /// A touch interaction was started. - Started, + FingerPressed { id: Finger, position: Point }, /// An on-going touch interaction was moved. - Moved, + FingerMoved { id: Finger, position: Point }, /// A touch interaction was ended. - Ended, + FingerLifted { id: Finger, position: Point }, /// A touch interaction was canceled. - Canceled, + FingerLost { id: Finger, position: Point }, } + +/// A unique identifier representing a finger on a touch interaction. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Finger(pub u64); diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 7d5eb30c..8e2450de 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -4,7 +4,7 @@ use crate::event::{self, Event}; use crate::layout; use crate::mouse; -use crate::touch::{self, Touch}; +use crate::touch; use crate::{ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget, }; @@ -166,10 +166,7 @@ where ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(Touch { - phase: touch::Phase::Started, - .. - }) => { + | Event::Touch(touch::Event::FingerPressed { .. }) => { if self.on_press.is_some() { let bounds = layout.bounds(); @@ -181,10 +178,7 @@ where } } Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(Touch { - phase: touch::Phase::Ended, - .. - }) => { + | Event::Touch(touch::Event::FingerLifted { .. }) => { if let Some(on_press) = self.on_press.clone() { let bounds = layout.bounds(); @@ -199,10 +193,7 @@ where } } } - Event::Touch(Touch { - phase: touch::Phase::Canceled, - .. - }) => { + Event::Touch(touch::Event::FingerLost { .. }) => { self.state.is_pressed = false; } _ => {} diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 92175b25..77a82fad 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -6,7 +6,7 @@ use crate::layout; use crate::mouse; use crate::row; use crate::text; -use crate::touch::{self, Touch}; +use crate::touch; use crate::{ Align, Clipboard, Element, Hasher, HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, VerticalAlignment, Widget, @@ -156,10 +156,7 @@ where ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(Touch { - phase: touch::Phase::Started, - .. - }) => { + | Event::Touch(touch::Event::FingerPressed { .. }) => { let mouse_over = layout.bounds().contains(cursor_position); if mouse_over { diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 3a1dd386..69952345 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -4,7 +4,7 @@ use crate::layout; use crate::mouse; use crate::row; use crate::text; -use crate::touch::{self, Touch}; +use crate::touch; use crate::{ Align, Clipboard, Element, Hasher, HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, VerticalAlignment, Widget, @@ -162,10 +162,7 @@ where ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(Touch { - phase: touch::Phase::Started, - .. - }) => { + | Event::Touch(touch::Event::FingerPressed { .. }) => { if layout.bounds().contains(cursor_position) { messages.push(self.on_click.clone()); diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 8c321ee5..18cdf169 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -4,7 +4,7 @@ use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::overlay; -use crate::touch::{self, Touch}; +use crate::touch; use crate::{ Align, Clipboard, Column, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector, Widget, @@ -230,26 +230,37 @@ where return event::Status::Captured; } - Event::Touch(Touch { phase, .. }) => match phase { - touch::Phase::Started => { - self.state.scroll_box_touched_at = - Some(cursor_position); - } - touch::Phase::Moved => { - 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); + 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); + } + } + touch::Event::FingerLifted { .. } + | touch::Event::FingerLost { .. } => { + self.state.scroll_box_touched_at = None; + } } - touch::Phase::Ended | touch::Phase::Canceled => { - self.state.scroll_box_touched_at = None; - } - }, + + return event::Status::Captured; + } _ => {} } } @@ -259,23 +270,14 @@ where Event::Mouse(mouse::Event::ButtonReleased( mouse::Button::Left, )) - | Event::Touch(Touch { - phase: touch::Phase::Ended, - .. - }) - | Event::Touch(Touch { - phase: touch::Phase::Canceled, - .. - }) => { + | 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::Touch(Touch { - phase: touch::Phase::Moved, - .. - }) => { + | Event::Touch(touch::Event::FingerMoved { .. }) => { if let (Some(scrollbar), Some(scroller_grabbed_at)) = (scrollbar, self.state.scroller_grabbed_at) { @@ -298,10 +300,7 @@ where Event::Mouse(mouse::Event::ButtonPressed( mouse::Button::Left, )) - | Event::Touch(Touch { - phase: touch::Phase::Started, - .. - }) => { + | Event::Touch(touch::Event::FingerPressed { .. }) => { if let Some(scrollbar) = scrollbar { if let Some(scroller_grabbed_at) = scrollbar.grab_scroller(cursor_position) diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index 755e6b2b..010c6e53 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -4,7 +4,7 @@ use crate::event::{self, Event}; use crate::layout; use crate::mouse; -use crate::touch::{self, Touch}; +use crate::touch; use crate::{ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, }; @@ -209,10 +209,7 @@ where match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(Touch { - phase: touch::Phase::Started, - .. - }) => { + | Event::Touch(touch::Event::FingerPressed { .. }) => { if layout.bounds().contains(cursor_position) { change(); self.state.is_dragging = true; @@ -221,10 +218,8 @@ where } } Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(Touch { - phase: touch::Phase::Ended, - .. - }) => { + | 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); @@ -235,10 +230,7 @@ where } } Event::Mouse(mouse::Event::CursorMoved { .. }) - | Event::Touch(Touch { - phase: touch::Phase::Moved, - .. - }) => { + | Event::Touch(touch::Event::FingerMoved { .. }) => { if self.state.is_dragging { change(); diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index ca71c20c..1e84e22a 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -16,7 +16,7 @@ use crate::keyboard; use crate::layout; use crate::mouse::{self, click}; use crate::text; -use crate::touch::{self, Touch}; +use crate::touch; use crate::{ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, }; @@ -249,10 +249,7 @@ where ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(Touch { - phase: touch::Phase::Started, - .. - }) => { + | Event::Touch(touch::Event::FingerPressed { .. }) => { let is_clicked = layout.bounds().contains(cursor_position); self.state.is_focused = is_clicked; -- cgit From 70164f68a6cfca6f7c86cb5cb46e7b2b1f08cf2c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 15 Dec 2020 06:48:12 +0100 Subject: Fix text selection with touch events in `TextInput` --- native/src/widget/text_input.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'native') diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 1e84e22a..2fd9cec1 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -320,10 +320,13 @@ where 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 { position }) => { + 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 = position.x - text_layout.bounds().x; -- cgit From 8fb0ede72ef9ae4e40ae5bb32ae3e2415d4749c2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 18 Dec 2020 09:40:10 +0100 Subject: Propagate `Button` events to contents --- native/src/widget/button.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'native') diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 8e2450de..b8c14634 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -161,9 +161,20 @@ where layout: Layout<'_>, cursor_position: Point, messages: &mut Vec, - _renderer: &Renderer, - _clipboard: Option<&dyn Clipboard>, + 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, + messages, + renderer, + clipboard, + ) { + return event::Status::Captured; + } + match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { -- cgit From 71de341684e27568c620c65e6b9e8e9ec837183c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 18 Dec 2020 10:24:04 +0100 Subject: Turn methods into helper functions in `image::viewer` --- native/src/widget/image/viewer.rs | 46 +++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 26 deletions(-) (limited to 'native') diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index 9544beab..3ffdf2c0 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -132,7 +132,7 @@ impl<'a> Viewer<'a> { { let (width, height) = renderer.dimensions(&self.handle); - let dimensions = { + let (width, height) = { let dimensions = (width as f32, height as f32); let width_ratio = bounds.width / dimensions.0; @@ -152,33 +152,27 @@ impl<'a> Viewer<'a> { Rectangle { x: bounds.x, y: bounds.y, - width: dimensions.0, - height: dimensions.1, + width, + height, } } +} - /// Cursor position relative to the [`Viewer`] bounds. - /// - /// [`Viewer`]: struct.Viewer.html - fn relative_cursor_position( - &self, - mut absolute_position: Point, - bounds: Rectangle, - ) -> Point { - absolute_position.x -= bounds.x; - absolute_position.y -= bounds.y; - absolute_position - } +/// Cursor position relative to the [`Viewer`] bounds. +/// +/// [`Viewer`]: struct.Viewer.html +fn relative_cursor_position( + absolute_position: Point, + bounds: Rectangle, +) -> Point { + absolute_position - Vector::new(bounds.x, bounds.y) +} - /// Center point relative to the [`Viewer`] bounds. - /// - /// [`Viewer`]: struct.Viewer.html - fn relative_center(&self, bounds: Rectangle) -> Point { - let mut center = bounds.center(); - center.x -= bounds.x; - center.y -= bounds.y; - center - } +/// Center point relative to the [`Viewer`] bounds. +/// +/// [`Viewer`]: struct.Viewer.html +fn relative_center(bounds: Rectangle) -> Point { + bounds.center() - Vector::new(bounds.x, bounds.y) } impl<'a, Message, Renderer> Widget for Viewer<'a> @@ -256,10 +250,10 @@ where - 1.0; let cursor_to_center = - self.relative_cursor_position( + relative_cursor_position( cursor_position, bounds, - ) - self.relative_center(bounds); + ) - relative_center(bounds); let adjustment = cursor_to_center * factor + self.state.current_offset * factor; -- cgit From 21b10dc103638ead2a567b3426c937f39e9addbd Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 18 Dec 2020 10:44:24 +0100 Subject: Fix `layout` of `image::Viewer` --- native/src/widget/image/viewer.rs | 90 +++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 52 deletions(-) (limited to 'native') diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index 3ffdf2c0..8b8e9824 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -122,11 +122,7 @@ impl<'a> Viewer<'a> { /// will be respected. /// /// [`Viewer`]: struct.Viewer.html - fn image_bounds( - &self, - renderer: &Renderer, - bounds: Rectangle, - ) -> Rectangle + fn image_size(&self, renderer: &Renderer, bounds: Size) -> Size where Renderer: self::Renderer + image::Renderer, { @@ -149,12 +145,7 @@ impl<'a> Viewer<'a> { } }; - Rectangle { - x: bounds.x, - y: bounds.y, - width, - height, - } + Size::new(width, height) } } @@ -189,19 +180,25 @@ where fn layout( &self, - _renderer: &Renderer, + renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let padding = f32::from(self.padding); + let (width, height) = renderer.dimensions(&self.handle); - let limits = limits - .max_width(self.max_width) - .max_height(self.max_height) + let aspect_ratio = width as f32 / height as f32; + + let mut size = limits .width(self.width) .height(self.height) - .pad(padding); + .resolve(Size::new(width as f32, height as f32)); + + let viewport_aspect_ratio = size.width / size.height; - let size = limits.resolve(Size::INFINITY); + 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; + } layout::Node::new(size) } @@ -242,8 +239,8 @@ where .min(self.max_scale), ); - let image_bounds = - self.image_bounds(renderer, bounds); + let image_size = + self.image_size(renderer, bounds.size()); let factor = self.state.scale.unwrap() / previous_scale @@ -259,13 +256,13 @@ where + self.state.current_offset * factor; self.state.current_offset = Vector::new( - if image_bounds.width > bounds.width { + if image_size.width > bounds.width { self.state.current_offset.x + adjustment.x } else { 0.0 }, - if image_bounds.height > bounds.height { + if image_size.height > bounds.height { self.state.current_offset.y + adjustment.y } else { @@ -290,14 +287,11 @@ where } Event::Mouse(mouse::Event::CursorMoved { position }) => { if self.state.is_cursor_clicked() { - let image_bounds = self.image_bounds(renderer, bounds); - - self.state.pan( - position.x, - position.y, - bounds, - image_bounds, - ); + let image_size = + self.image_size(renderer, bounds.size()); + + self.state + .pan(position.x, position.y, bounds, image_size); } } _ => {} @@ -322,15 +316,15 @@ where ) -> Renderer::Output { let bounds = layout.bounds(); - let image_bounds = self.image_bounds(renderer, bounds); + let image_size = self.image_size(renderer, bounds.size()); let translation = { let image_top_left = Vector::new( - bounds.width / 2.0 - image_bounds.width / 2.0, - bounds.height / 2.0 - image_bounds.height / 2.0, + 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_bounds) + image_top_left - self.state.offset(bounds, image_size) }; let is_mouse_over = bounds.contains(cursor_position); @@ -339,7 +333,7 @@ where renderer, &self.state, bounds, - image_bounds, + image_size, translation, self.handle.clone(), is_mouse_over, @@ -384,31 +378,24 @@ impl State { /// /// [`Viewer`]: struct.Viewer.html /// [`State`]: struct.State.html - fn pan( - &mut self, - x: f32, - y: f32, - bounds: Rectangle, - image_bounds: Rectangle, - ) { - let hidden_width = ((image_bounds.width - bounds.width) as f32 / 2.0) + fn pan(&mut self, x: f32, y: f32, bounds: Rectangle, image_size: Size) { + let hidden_width = ((image_size.width - bounds.width) as f32 / 2.0) .max(0.0) .round(); - let hidden_height = ((image_bounds.height - bounds.height) as f32 - / 2.0) + let hidden_height = ((image_size.height - bounds.height) as f32 / 2.0) .max(0.0) .round(); let delta_x = x - self.starting_cursor_pos.unwrap().x; let delta_y = y - self.starting_cursor_pos.unwrap().y; - if bounds.width < image_bounds.width { + if bounds.width < image_size.width { self.current_offset.x = (self.starting_offset.x - delta_x) .min(hidden_width) .max(-1.0 * hidden_width); } - if bounds.height < image_bounds.height { + if bounds.height < image_size.height { self.current_offset.y = (self.starting_offset.y - delta_y) .min(hidden_height) .max(-1.0 * hidden_height); @@ -420,12 +407,11 @@ impl State { /// /// [`Viewer`]: struct.Viewer.html /// [`State`]: struct.State.html - fn offset(&self, bounds: Rectangle, image_bounds: Rectangle) -> Vector { - let hidden_width = ((image_bounds.width - bounds.width) as f32 / 2.0) + fn offset(&self, bounds: Rectangle, image_size: Size) -> Vector { + let hidden_width = ((image_size.width - bounds.width) as f32 / 2.0) .max(0.0) .round(); - let hidden_height = ((image_bounds.height - bounds.height) as f32 - / 2.0) + let hidden_height = ((image_size.height - bounds.height) as f32 / 2.0) .max(0.0) .round(); @@ -476,7 +462,7 @@ pub trait Renderer: crate::Renderer + Sized { &mut self, state: &State, bounds: Rectangle, - image_bounds: Rectangle, + image_size: Size, translation: Vector, handle: image::Handle, is_mouse_over: bool, -- cgit From add167d6a00843fb1229ff542d411f733f916035 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 18 Dec 2020 10:47:29 +0100 Subject: Pan `image::Viewer` even if the cursor is out of bounds --- native/src/widget/image/viewer.rs | 145 ++++++++++++++++++-------------------- 1 file changed, 68 insertions(+), 77 deletions(-) (limited to 'native') diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index 8b8e9824..6c170341 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -215,92 +215,83 @@ where let bounds = layout.bounds(); let is_mouse_over = bounds.contains(cursor_position); - if is_mouse_over { - match event { - Event::Mouse(mouse::Event::WheelScrolled { delta }) => { - match delta { - mouse::ScrollDelta::Lines { y, .. } - | mouse::ScrollDelta::Pixels { y, .. } => { - let previous_scale = - self.state.scale.unwrap_or(1.0); - - if y < 0.0 && previous_scale > self.min_scale - || y > 0.0 && previous_scale < self.max_scale - { - self.state.scale = Some( - (if y > 0.0 { - self.state.scale.unwrap_or(1.0) - * (1.0 + self.scale_pct) - } else { - self.state.scale.unwrap_or(1.0) - / (1.0 + self.scale_pct) - }) - .max(self.min_scale) - .min(self.max_scale), - ); - - let image_size = - self.image_size(renderer, bounds.size()); - - let factor = self.state.scale.unwrap() - / previous_scale - - 1.0; - - let cursor_to_center = - relative_cursor_position( - cursor_position, - bounds, - ) - relative_center(bounds); - - 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 - }, - ); - } + 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.unwrap_or(1.0); + + if y < 0.0 && previous_scale > self.min_scale + || y > 0.0 && previous_scale < self.max_scale + { + self.state.scale = Some( + (if y > 0.0 { + self.state.scale.unwrap_or(1.0) + * (1.0 + self.scale_pct) + } else { + self.state.scale.unwrap_or(1.0) + / (1.0 + self.scale_pct) + }) + .max(self.min_scale) + .min(self.max_scale), + ); + + let image_size = + self.image_size(renderer, bounds.size()); + + let factor = self.state.scale.unwrap() + / previous_scale + - 1.0; + + let cursor_to_center = relative_cursor_position( + cursor_position, + bounds, + ) - relative_center(bounds); + + 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::Mouse(mouse::Event::ButtonPressed(button)) => { - if button == mouse::Button::Left { - self.state.starting_cursor_pos = Some(cursor_position); + } + Event::Mouse(mouse::Event::ButtonPressed(button)) + if is_mouse_over => + { + if button == mouse::Button::Left { + self.state.starting_cursor_pos = Some(cursor_position); - self.state.starting_offset = self.state.current_offset; - } + self.state.starting_offset = self.state.current_offset; } - Event::Mouse(mouse::Event::ButtonReleased(button)) => { - if button == mouse::Button::Left { - self.state.starting_cursor_pos = None - } + } + Event::Mouse(mouse::Event::ButtonReleased(button)) => { + if button == mouse::Button::Left { + self.state.starting_cursor_pos = None } - Event::Mouse(mouse::Event::CursorMoved { position }) => { - if self.state.is_cursor_clicked() { - let image_size = - self.image_size(renderer, bounds.size()); + } + Event::Mouse(mouse::Event::CursorMoved { position }) => { + if self.state.is_cursor_clicked() { + let image_size = self.image_size(renderer, bounds.size()); - self.state - .pan(position.x, position.y, bounds, image_size); - } + self.state.pan(position.x, position.y, bounds, image_size); } - _ => {} - } - } else if let Event::Mouse(mouse::Event::ButtonReleased(button)) = event - { - if button == mouse::Button::Left { - self.state.starting_cursor_pos = None; } + _ => {} } event::Status::Ignored -- cgit From ca3e4e9f1bef9ffbe011e08c91ccb012312b71e9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 18 Dec 2020 10:49:10 +0100 Subject: Simplify pattern match in `image::Viewer::on_event` --- native/src/widget/image/viewer.rs | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) (limited to 'native') diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index 6c170341..3402cf18 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -270,26 +270,21 @@ where } } } - Event::Mouse(mouse::Event::ButtonPressed(button)) + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) if is_mouse_over => { - if button == mouse::Button::Left { - self.state.starting_cursor_pos = Some(cursor_position); - - self.state.starting_offset = self.state.current_offset; - } + self.state.starting_cursor_pos = Some(cursor_position); + self.state.starting_offset = self.state.current_offset; } - Event::Mouse(mouse::Event::ButtonReleased(button)) => { - if button == mouse::Button::Left { - self.state.starting_cursor_pos = None - } + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => { + self.state.starting_cursor_pos = None } - Event::Mouse(mouse::Event::CursorMoved { position }) => { - if self.state.is_cursor_clicked() { - let image_size = self.image_size(renderer, bounds.size()); + Event::Mouse(mouse::Event::CursorMoved { position }) + if self.state.is_cursor_clicked() => + { + let image_size = self.image_size(renderer, bounds.size()); - self.state.pan(position.x, position.y, bounds, image_size); - } + self.state.pan(position.x, position.y, bounds, image_size); } _ => {} } -- cgit From e6f23e37719ccc52e3a3802e204a9b33aeba9d2a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 18 Dec 2020 10:53:38 +0100 Subject: Rename `scale_pct` to `scale_step` in `image::Viewer` --- native/src/widget/image/viewer.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'native') diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index 3402cf18..c8656101 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -21,7 +21,7 @@ pub struct Viewer<'a> { max_height: u32, min_scale: f32, max_scale: f32, - scale_pct: f32, + scale_step: f32, handle: image::Handle, } @@ -41,7 +41,7 @@ impl<'a> Viewer<'a> { max_height: u32::MAX, min_scale: 0.25, max_scale: 10.0, - scale_pct: 0.10, + scale_step: 0.10, handle, } } @@ -112,8 +112,8 @@ impl<'a> Viewer<'a> { /// Default is `0.10` /// /// [`Viewer`]: struct.Viewer.html - pub fn scale_pct(mut self, scale_pct: f32) -> Self { - self.scale_pct = scale_pct; + pub fn scale_step(mut self, scale_step: f32) -> Self { + self.scale_step = scale_step; self } @@ -230,10 +230,10 @@ where self.state.scale = Some( (if y > 0.0 { self.state.scale.unwrap_or(1.0) - * (1.0 + self.scale_pct) + * (1.0 + self.scale_step) } else { self.state.scale.unwrap_or(1.0) - / (1.0 + self.scale_pct) + / (1.0 + self.scale_step) }) .max(self.min_scale) .min(self.max_scale), -- cgit From 64af860ad2317d9f2e72cbda659102d96ec0f931 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 18 Dec 2020 10:55:18 +0100 Subject: Remove unnecessary `Option` in `image::viewer::State` --- native/src/widget/image/viewer.rs | 42 ++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 18 deletions(-) (limited to 'native') diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index c8656101..0f081a1a 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -136,7 +136,7 @@ impl<'a> Viewer<'a> { let ratio = width_ratio.min(height_ratio); - let scale = self.state.scale.unwrap_or(1.0); + let scale = self.state.scale; if ratio < 1.0 { (dimensions.0 * ratio * scale, dimensions.1 * ratio * scale) @@ -222,29 +222,24 @@ where match delta { mouse::ScrollDelta::Lines { y, .. } | mouse::ScrollDelta::Pixels { y, .. } => { - let previous_scale = self.state.scale.unwrap_or(1.0); + 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 = Some( - (if y > 0.0 { - self.state.scale.unwrap_or(1.0) - * (1.0 + self.scale_step) - } else { - self.state.scale.unwrap_or(1.0) - / (1.0 + self.scale_step) - }) - .max(self.min_scale) - .min(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.unwrap() - / previous_scale - - 1.0; + let factor = + self.state.scale / previous_scale - 1.0; let cursor_to_center = relative_cursor_position( cursor_position, @@ -343,14 +338,25 @@ where /// The local state of a [`Viewer`]. /// /// [`Viewer`]: struct.Viewer.html -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Copy)] pub struct State { - scale: Option, + scale: f32, starting_offset: Vector, current_offset: Vector, starting_cursor_pos: Option, } +impl Default for State { + fn default() -> Self { + Self { + scale: 1.0, + starting_offset: Vector::default(), + current_offset: Vector::default(), + starting_cursor_pos: None, + } + } +} + impl State { /// Creates a new [`State`]. /// -- cgit From 4eb5779542e3d53d2a41f30f259640bb2f8069fd Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 18 Dec 2020 11:00:13 +0100 Subject: Remove redundant `f32` conversions in `image::Viewer` --- native/src/widget/image/viewer.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) (limited to 'native') diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index 0f081a1a..605298f1 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -371,12 +371,11 @@ impl State { /// [`Viewer`]: struct.Viewer.html /// [`State`]: struct.State.html fn pan(&mut self, x: f32, y: f32, bounds: Rectangle, image_size: Size) { - let hidden_width = ((image_size.width - bounds.width) as f32 / 2.0) - .max(0.0) - .round(); - let hidden_height = ((image_size.height - bounds.height) as f32 / 2.0) - .max(0.0) - .round(); + 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_x = x - self.starting_cursor_pos.unwrap().x; let delta_y = y - self.starting_cursor_pos.unwrap().y; @@ -400,12 +399,11 @@ impl State { /// [`Viewer`]: struct.Viewer.html /// [`State`]: struct.State.html fn offset(&self, bounds: Rectangle, image_size: Size) -> Vector { - let hidden_width = ((image_size.width - bounds.width) as f32 / 2.0) - .max(0.0) - .round(); - let hidden_height = ((image_size.height - bounds.height) as f32 / 2.0) - .max(0.0) - .round(); + 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 -- cgit From 8245a1176648f8df803f9820bd5ceac71d6a031a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 18 Dec 2020 11:01:20 +0100 Subject: Negate values instead of multipling by `-1.0` in `image::Viewer` --- native/src/widget/image/viewer.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) (limited to 'native') diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index 605298f1..62745fba 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -383,13 +383,13 @@ impl State { if bounds.width < image_size.width { self.current_offset.x = (self.starting_offset.x - delta_x) .min(hidden_width) - .max(-1.0 * hidden_width); + .max(-hidden_width); } if bounds.height < image_size.height { self.current_offset.y = (self.starting_offset.y - delta_y) .min(hidden_height) - .max(-1.0 * hidden_height); + .max(-hidden_height); } } @@ -406,14 +406,8 @@ impl State { (image_size.height - bounds.height / 2.0).max(0.0).round(); Vector::new( - self.current_offset - .x - .min(hidden_width) - .max(-1.0 * hidden_width), - self.current_offset - .y - .min(hidden_height) - .max(-1.0 * hidden_height), + self.current_offset.x.min(hidden_width).max(-hidden_width), + self.current_offset.y.min(hidden_height).max(-hidden_height), ) } -- cgit From 43ef85ae5c5b8901642e0832456ed907635600ff Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 18 Dec 2020 11:04:07 +0100 Subject: Rename `starting_cursor_pos` to `cursor_grabbed_at` in `image::Viewer` --- native/src/widget/image/viewer.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) (limited to 'native') diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index 62745fba..af687af0 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -268,14 +268,14 @@ where Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) if is_mouse_over => { - self.state.starting_cursor_pos = Some(cursor_position); + self.state.cursor_grabbed_at = Some(cursor_position); self.state.starting_offset = self.state.current_offset; } Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => { - self.state.starting_cursor_pos = None + self.state.cursor_grabbed_at = None } Event::Mouse(mouse::Event::CursorMoved { position }) - if self.state.is_cursor_clicked() => + if self.state.is_cursor_grabbed() => { let image_size = self.image_size(renderer, bounds.size()); @@ -343,7 +343,7 @@ pub struct State { scale: f32, starting_offset: Vector, current_offset: Vector, - starting_cursor_pos: Option, + cursor_grabbed_at: Option, } impl Default for State { @@ -352,7 +352,7 @@ impl Default for State { scale: 1.0, starting_offset: Vector::default(), current_offset: Vector::default(), - starting_cursor_pos: None, + cursor_grabbed_at: None, } } } @@ -377,8 +377,8 @@ impl State { let hidden_height = (image_size.height - bounds.height / 2.0).max(0.0).round(); - let delta_x = x - self.starting_cursor_pos.unwrap().x; - let delta_y = y - self.starting_cursor_pos.unwrap().y; + let delta_x = x - self.cursor_grabbed_at.unwrap().x; + let delta_y = y - self.cursor_grabbed_at.unwrap().y; if bounds.width < image_size.width { self.current_offset.x = (self.starting_offset.x - delta_x) @@ -411,13 +411,12 @@ impl State { ) } - /// Returns if the left mouse button is still held down since clicking inside - /// the [`Viewer`]. + /// Returns if the cursor is currently grabbed by the [`Viewer`]. /// /// [`Viewer`]: struct.Viewer.html /// [`State`]: struct.State.html - pub fn is_cursor_clicked(&self) -> bool { - self.starting_cursor_pos.is_some() + pub fn is_cursor_grabbed(&self) -> bool { + self.cursor_grabbed_at.is_some() } } -- cgit From 149098cb686dcaad21f88adc2c646372c85a7d52 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 18 Dec 2020 11:20:25 +0100 Subject: Remove use of `unwrap` in `image::Viewer` --- native/src/widget/image/viewer.rs | 67 +++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 34 deletions(-) (limited to 'native') diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index af687af0..ab9f3802 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -274,12 +274,39 @@ where Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => { self.state.cursor_grabbed_at = None } - Event::Mouse(mouse::Event::CursorMoved { position }) - if self.state.is_cursor_grabbed() => - { - let image_size = self.image_size(renderer, bounds.size()); - - self.state.pan(position.x, position.y, bounds, image_size); + 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); + } } _ => {} } @@ -365,34 +392,6 @@ impl State { State::default() } - /// Apply a panning offset to the current [`State`], given the bounds of - /// the [`Viewer`] and its image. - /// - /// [`Viewer`]: struct.Viewer.html - /// [`State`]: struct.State.html - fn pan(&mut self, x: f32, y: f32, bounds: Rectangle, image_size: 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_x = x - self.cursor_grabbed_at.unwrap().x; - let delta_y = y - self.cursor_grabbed_at.unwrap().y; - - if bounds.width < image_size.width { - self.current_offset.x = (self.starting_offset.x - delta_x) - .min(hidden_width) - .max(-hidden_width); - } - - if bounds.height < image_size.height { - self.current_offset.y = (self.starting_offset.y - delta_y) - .min(hidden_height) - .max(-hidden_height); - } - } - /// Returns the current offset of the [`State`], given the bounds /// of the [`Viewer`] and its image. /// -- cgit From 6a51282933ca90283c2fb9ae2088129157394d02 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 18 Dec 2020 11:23:22 +0100 Subject: Simplify `cursor_to_center` in `image::Viewer` --- native/src/widget/image/viewer.rs | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) (limited to 'native') diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index ab9f3802..d376e475 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -149,23 +149,6 @@ impl<'a> Viewer<'a> { } } -/// Cursor position relative to the [`Viewer`] bounds. -/// -/// [`Viewer`]: struct.Viewer.html -fn relative_cursor_position( - absolute_position: Point, - bounds: Rectangle, -) -> Point { - absolute_position - Vector::new(bounds.x, bounds.y) -} - -/// Center point relative to the [`Viewer`] bounds. -/// -/// [`Viewer`]: struct.Viewer.html -fn relative_center(bounds: Rectangle) -> Point { - bounds.center() - Vector::new(bounds.x, bounds.y) -} - impl<'a, Message, Renderer> Widget for Viewer<'a> where Renderer: self::Renderer + image::Renderer, @@ -241,10 +224,8 @@ where let factor = self.state.scale / previous_scale - 1.0; - let cursor_to_center = relative_cursor_position( - cursor_position, - bounds, - ) - relative_center(bounds); + let cursor_to_center = + cursor_position - bounds.center(); let adjustment = cursor_to_center * factor + self.state.current_offset * factor; -- cgit From c54a6446a3a22077af651cddb37eaeb5f659e316 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 18 Dec 2020 11:27:43 +0100 Subject: Use intra-doc links in `image::viewer` --- native/src/widget/image/viewer.rs | 41 ++++----------------------------------- 1 file changed, 4 insertions(+), 37 deletions(-) (limited to 'native') diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index d376e475..ba4ee087 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -28,9 +28,7 @@ pub struct Viewer<'a> { impl<'a> Viewer<'a> { /// Creates a new [`Viewer`] with the given [`State`] and [`Handle`]. /// - /// [`Viewer`]: struct.Viewer.html - /// [`State`]: struct.State.html - /// [`Handle`]: ../../image/struct.Handle.html + /// [`Handle`]: image::Handle pub fn new(state: &'a mut State, handle: image::Handle) -> Self { Viewer { state, @@ -47,40 +45,30 @@ impl<'a> Viewer<'a> { } /// Sets the padding of the [`Viewer`]. - /// - /// [`Viewer`]: struct.Viewer.html pub fn padding(mut self, units: u16) -> Self { self.padding = units; self } /// Sets the width of the [`Viewer`]. - /// - /// [`Viewer`]: struct.Viewer.html pub fn width(mut self, width: Length) -> Self { self.width = width; self } /// Sets the height of the [`Viewer`]. - /// - /// [`Viewer`]: struct.Viewer.html pub fn height(mut self, height: Length) -> Self { self.height = height; self } /// Sets the max width of the [`Viewer`]. - /// - /// [`Viewer`]: struct.Viewer.html pub fn max_width(mut self, max_width: u32) -> Self { self.max_width = max_width; self } /// Sets the max height of the [`Viewer`]. - /// - /// [`Viewer`]: struct.Viewer.html pub fn max_height(mut self, max_height: u32) -> Self { self.max_height = max_height; self @@ -89,8 +77,6 @@ impl<'a> Viewer<'a> { /// Sets the max scale applied to the image of the [`Viewer`]. /// /// Default is `10.0` - /// - /// [`Viewer`]: struct.Viewer.html pub fn max_scale(mut self, max_scale: f32) -> Self { self.max_scale = max_scale; self @@ -99,8 +85,6 @@ impl<'a> Viewer<'a> { /// Sets the min scale applied to the image of the [`Viewer`]. /// /// Default is `0.25` - /// - /// [`Viewer`]: struct.Viewer.html pub fn min_scale(mut self, min_scale: f32) -> Self { self.min_scale = min_scale; self @@ -110,8 +94,6 @@ impl<'a> Viewer<'a> { /// when zoomed in / out. /// /// Default is `0.10` - /// - /// [`Viewer`]: struct.Viewer.html pub fn scale_step(mut self, scale_step: f32) -> Self { self.scale_step = scale_step; self @@ -120,8 +102,6 @@ impl<'a> Viewer<'a> { /// Returns the bounds of the underlying image, given the bounds of /// the [`Viewer`]. Scaling will be applied and original aspect ratio /// will be respected. - /// - /// [`Viewer`]: struct.Viewer.html fn image_size(&self, renderer: &Renderer, bounds: Size) -> Size where Renderer: self::Renderer + image::Renderer, @@ -344,8 +324,6 @@ where } /// The local state of a [`Viewer`]. -/// -/// [`Viewer`]: struct.Viewer.html #[derive(Debug, Clone, Copy)] pub struct State { scale: f32, @@ -367,17 +345,12 @@ impl Default for State { impl State { /// Creates a new [`State`]. - /// - /// [`State`]: struct.State.html pub fn new() -> Self { State::default() } /// Returns the current offset of the [`State`], given the bounds /// of the [`Viewer`] and its image. - /// - /// [`Viewer`]: struct.Viewer.html - /// [`State`]: struct.State.html fn offset(&self, bounds: Rectangle, image_size: Size) -> Vector { let hidden_width = (image_size.width - bounds.width / 2.0).max(0.0).round(); @@ -392,9 +365,6 @@ impl State { } /// Returns if the cursor is currently grabbed by the [`Viewer`]. - /// - /// [`Viewer`]: struct.Viewer.html - /// [`State`]: struct.State.html pub fn is_cursor_grabbed(&self) -> bool { self.cursor_grabbed_at.is_some() } @@ -405,22 +375,19 @@ impl State { /// Your [renderer] will need to implement this trait before being /// able to use a [`Viewer`] in your user interface. /// -/// [`Viewer`]: struct.Viewer.html -/// [renderer]: ../../../renderer/index.html +/// [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 bounds of the scaled [`Viewer`] image + /// - 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 /// - /// [`Viewer`]: struct.Viewer.html - /// [`State`]: struct.State.html - /// [`Handle`]: ../../image/struct.Handle.html + /// [`Handle`]: image::Handle fn draw( &mut self, state: &State, -- cgit From 10d6df73e34e421cbf96d62b26c0c0701d9096ef Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 18 Dec 2020 11:28:55 +0100 Subject: Remove `max_width` and `max_height` from `image::Viewer` The support for these layout constraints is currently not ideal. We should reintroduce these methods once our layout engine improves. --- native/src/widget/image/viewer.rs | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) (limited to 'native') diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index ba4ee087..4ec3faf6 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -8,7 +8,7 @@ use crate::{ Widget, }; -use std::{f32, hash::Hash, u32}; +use std::hash::Hash; /// A frame that displays an image with the ability to zoom in/out and pan. #[allow(missing_debug_implementations)] @@ -17,8 +17,6 @@ pub struct Viewer<'a> { padding: u16, width: Length, height: Length, - max_width: u32, - max_height: u32, min_scale: f32, max_scale: f32, scale_step: f32, @@ -35,8 +33,6 @@ impl<'a> Viewer<'a> { padding: 0, width: Length::Shrink, height: Length::Shrink, - max_width: u32::MAX, - max_height: u32::MAX, min_scale: 0.25, max_scale: 10.0, scale_step: 0.10, @@ -62,18 +58,6 @@ impl<'a> Viewer<'a> { self } - /// Sets the max width of the [`Viewer`]. - pub fn max_width(mut self, max_width: u32) -> Self { - self.max_width = max_width; - self - } - - /// Sets the max height of the [`Viewer`]. - pub fn max_height(mut self, max_height: u32) -> Self { - self.max_height = max_height; - self - } - /// Sets the max scale applied to the image of the [`Viewer`]. /// /// Default is `10.0` @@ -315,8 +299,6 @@ where self.width.hash(state); self.height.hash(state); - self.max_width.hash(state); - self.max_height.hash(state); self.padding.hash(state); self.handle.hash(state); -- cgit From f2c2f3fc7588054417a0c44d3890defa976c5f61 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 22 Dec 2020 14:44:44 +0100 Subject: Remove unnecessary `text::Renderer` bound for `PaneGrid` This is no longer necessary, as we do not render text directly anymore. --- native/src/widget/pane_grid.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'native') diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 85ef021f..9cf8bc34 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -33,7 +33,6 @@ use crate::layout; use crate::mouse; use crate::overlay; use crate::row; -use crate::text; use crate::{ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector, Widget, @@ -543,9 +542,7 @@ where /// able to use a [`PaneGrid`] in your user interface. /// /// [renderer]: crate::renderer -pub trait Renderer: - crate::Renderer + container::Renderer + text::Renderer + Sized -{ +pub trait Renderer: crate::Renderer + container::Renderer + Sized { /// Draws a [`PaneGrid`]. /// /// It receives: -- cgit From e815c5bbd72f618ad890e1d3c5a67c811cd07108 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 22 Dec 2020 14:47:18 +0100 Subject: Remove unnecessary lifetime bound in `TitleBar` --- native/src/widget/pane_grid/title_bar.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'native') diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index a73acff7..30e88e6f 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -15,7 +15,7 @@ pub struct TitleBar<'a, Message, Renderer: pane_grid::Renderer> { style: Renderer::Style, } -impl<'a, Message, Renderer: 'a> TitleBar<'a, Message, Renderer> +impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer> where Renderer: pane_grid::Renderer, { -- cgit From a7bb7bb2eaaae5a016721788fcaf03c4c7413acd Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 1 Jan 2021 15:28:38 +0100 Subject: Implement split highlight on hover for `PaneGrid` --- native/src/renderer/null.rs | 9 +++-- native/src/widget/pane_grid.rs | 58 ++++++++++++++++++++++++++------ native/src/widget/pane_grid/content.rs | 9 +++-- native/src/widget/pane_grid/title_bar.rs | 10 ++++-- 4 files changed, 67 insertions(+), 19 deletions(-) (limited to 'native') diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs index bea8041c..f505b012 100644 --- a/native/src/renderer/null.rs +++ b/native/src/renderer/null.rs @@ -246,13 +246,16 @@ impl container::Renderer for Null { } impl pane_grid::Renderer for Null { + type Style = (); + fn draw( &mut self, _defaults: &Self::Defaults, _content: &[(pane_grid::Pane, pane_grid::Content<'_, Message, Self>)], _dragging: Option<(pane_grid::Pane, Point)>, - _resizing: Option, + _resizing: Option<(pane_grid::Axis, Rectangle, bool)>, _layout: Layout<'_>, + _style: &::Style, _cursor_position: Point, ) { } @@ -261,7 +264,7 @@ impl pane_grid::Renderer for Null { &mut self, _defaults: &Self::Defaults, _bounds: Rectangle, - _style: &Self::Style, + _style: &::Style, _title_bar: Option<( &pane_grid::TitleBar<'_, Message, Self>, Layout<'_>, @@ -275,7 +278,7 @@ impl pane_grid::Renderer for Null { &mut self, _defaults: &Self::Defaults, _bounds: Rectangle, - _style: &Self::Style, + _style: &::Style, _content: (&Element<'_, Message, Self>, Layout<'_>), _controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>, _cursor_position: Point, diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 9cf8bc34..da3e25fd 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -97,6 +97,7 @@ pub struct PaneGrid<'a, Message, Renderer: self::Renderer> { on_click: Option Message + 'a>>, on_drag: Option Message + 'a>>, on_resize: Option<(u16, Box Message + 'a>)>, + style: ::Style, } impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> @@ -128,6 +129,7 @@ where on_click: None, on_drag: None, on_resize: None, + style: Default::default(), } } @@ -185,6 +187,15 @@ where self.on_resize = Some((leeway, Box::new(f))); self } + + /// Sets the style of the [`PaneGrid`]. + pub fn style( + mut self, + style: impl Into<::Style>, + ) -> Self { + self.style = style.into(); + self + } } impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> @@ -382,7 +393,7 @@ where relative_cursor, ); - if let Some((split, axis)) = clicked_split { + if let Some((split, axis, _)) = clicked_split { self.state.pick_split(&split, axis); } else { self.click_pane( @@ -475,6 +486,23 @@ where 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(); @@ -488,15 +516,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, @@ -505,6 +538,7 @@ where self.state.picked_pane(), picked_split, layout, + &self.style, cursor_position, ) } @@ -543,6 +577,9 @@ where /// /// [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: @@ -556,8 +593,9 @@ pub trait Renderer: crate::Renderer + container::Renderer + Sized { defaults: &Self::Defaults, content: &[(Pane, Content<'_, Message, Self>)], dragging: Option<(Pane, Point)>, - resizing: Option, + resizing: Option<(Axis, Rectangle, bool)>, layout: Layout<'_>, + style: &::Style, cursor_position: Point, ) -> Self::Output; @@ -572,7 +610,7 @@ pub trait Renderer: crate::Renderer + container::Renderer + Sized { &mut self, defaults: &Self::Defaults, bounds: Rectangle, - style: &Self::Style, + style: &::Style, title_bar: Option<(&TitleBar<'_, Message, Self>, Layout<'_>)>, body: (&Element<'_, Message, Self>, Layout<'_>), cursor_position: Point, @@ -590,7 +628,7 @@ pub trait Renderer: crate::Renderer + container::Renderer + Sized { &mut self, defaults: &Self::Defaults, bounds: Rectangle, - style: &Self::Style, + style: &::Style, content: (&Element<'_, Message, Self>, Layout<'_>), controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>, cursor_position: Point, @@ -617,14 +655,14 @@ fn hovered_split<'a>( splits: impl Iterator, 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/content.rs b/native/src/widget/pane_grid/content.rs index c9981903..913cfe96 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -12,7 +12,7 @@ use crate::{Clipboard, Element, Hasher, Layout, Point, Size}; pub struct Content<'a, Message, Renderer: pane_grid::Renderer> { title_bar: Option>, body: Element<'a, Message, Renderer>, - style: Renderer::Style, + style: ::Style, } impl<'a, Message, Renderer> Content<'a, Message, Renderer> @@ -24,7 +24,7 @@ where Self { title_bar: None, body: body.into(), - style: Renderer::Style::default(), + style: Default::default(), } } @@ -38,7 +38,10 @@ where } /// Sets the style of the [`Content`]. - pub fn style(mut self, style: impl Into) -> Self { + pub fn style( + mut self, + style: impl Into<::Style>, + ) -> Self { self.style = style.into(); self } diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index 30e88e6f..efaecf9e 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -1,3 +1,4 @@ +use crate::container; use crate::event::{self, Event}; use crate::layout; use crate::pane_grid; @@ -12,7 +13,7 @@ pub struct TitleBar<'a, Message, Renderer: pane_grid::Renderer> { controls: Option>, padding: u16, always_show_controls: bool, - style: Renderer::Style, + style: ::Style, } impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer> @@ -29,7 +30,7 @@ where controls: None, padding: 0, always_show_controls: false, - style: Renderer::Style::default(), + style: Default::default(), } } @@ -49,7 +50,10 @@ where } /// Sets the style of the [`TitleBar`]. - pub fn style(mut self, style: impl Into) -> Self { + pub fn style( + mut self, + style: impl Into<::Style>, + ) -> Self { self.style = style.into(); self } -- cgit From e7344d03b467e87b3e50fa8d7ecd74994b46a4e6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Jan 2021 21:07:44 +0100 Subject: Use `BTreeMap` for splits and regions in `pane_grid` This preserves ordering between calls to update and draw logic. --- native/src/widget/pane_grid/node.rs | 14 +++++++------- native/src/widget/pane_grid/pane.rs | 2 +- native/src/widget/pane_grid/split.rs | 2 +- native/src/widget/pane_grid/state.rs | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) (limited to 'native') diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs index 319936fc..84714e00 100644 --- a/native/src/widget/pane_grid/node.rs +++ b/native/src/widget/pane_grid/node.rs @@ -3,7 +3,7 @@ use crate::{ Rectangle, Size, }; -use std::collections::HashMap; +use std::collections::BTreeMap; /// A layout node of a [`PaneGrid`]. /// @@ -59,8 +59,8 @@ impl Node { &self, spacing: f32, size: Size, - ) -> HashMap { - let mut regions = HashMap::new(); + ) -> BTreeMap { + let mut regions = BTreeMap::new(); self.compute_regions( spacing, @@ -83,8 +83,8 @@ impl Node { &self, spacing: f32, size: Size, - ) -> HashMap { - let mut splits = HashMap::new(); + ) -> BTreeMap { + let mut splits = BTreeMap::new(); self.compute_splits( spacing, @@ -191,7 +191,7 @@ impl Node { &self, spacing: f32, current: &Rectangle, - regions: &mut HashMap, + regions: &mut BTreeMap, ) { match self { Node::Split { @@ -212,7 +212,7 @@ impl Node { &self, spacing: f32, current: &Rectangle, - splits: &mut HashMap, + splits: &mut BTreeMap, ) { match self { Node::Split { diff --git a/native/src/widget/pane_grid/pane.rs b/native/src/widget/pane_grid/pane.rs index 39d9f3ef..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`]: crate::widget::PaneGrid -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[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 16975abc..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`]: crate::widget::PaneGrid -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[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 666e1ca0..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`]. /// @@ -257,7 +257,7 @@ impl Internal { &self, spacing: f32, size: Size, - ) -> HashMap { + ) -> BTreeMap { self.layout.pane_regions(spacing, size) } @@ -265,7 +265,7 @@ impl Internal { &self, spacing: f32, size: Size, - ) -> HashMap { + ) -> BTreeMap { self.layout.split_regions(spacing, size) } -- cgit From c7f6b2a5c7778cdbd39e327bfac196014140902c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 11 Jan 2021 19:31:34 +0100 Subject: Capture relevant events in `image::Viewer` --- native/src/widget/image/viewer.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) (limited to 'native') diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index 4ec3faf6..5f0dda4e 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -209,15 +209,23 @@ where } } } + + 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)) => { - self.state.cursor_grabbed_at = None + 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 { @@ -251,12 +259,14 @@ where }; self.state.current_offset = Vector::new(x, y); + + event::Status::Captured + } else { + event::Status::Ignored } } - _ => {} + _ => event::Status::Ignored, } - - event::Status::Ignored } fn draw( -- cgit From 0646280d674e02fb11fcba0dc1c9b2d3d4d8a0fe Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 15 Jan 2021 14:26:23 +0100 Subject: Fix `Widget::width` implementation for `PickList` --- native/src/widget/pick_list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'native') diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 58c0dfe1..4110b9bb 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -132,7 +132,7 @@ where Renderer: self::Renderer + scrollable::Renderer + 'a, { fn width(&self) -> Length { - Length::Shrink + self.width } fn height(&self) -> Length { -- cgit From 0b140488b425a7d1fd45ca41592de25b28d3ac17 Mon Sep 17 00:00:00 2001 From: cossonleo Date: Fri, 15 Jan 2021 22:40:16 +0800 Subject: add focus event --- native/src/window/event.rs | 3 +++ 1 file changed, 3 insertions(+) (limited to 'native') diff --git a/native/src/window/event.rs b/native/src/window/event.rs index b177141a..d649760b 100644 --- a/native/src/window/event.rs +++ b/native/src/window/event.rs @@ -29,4 +29,7 @@ pub enum Event { /// There will be a single `FilesHoveredLeft` event triggered even if /// multiple files were hovered. FilesHoveredLeft, + + /// A window was focused or not + Focused(bool), } -- cgit From 45dc02e9bd0b4f2c6cc65781b850f460cddf6171 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 15 Jan 2021 18:21:44 +0100 Subject: Split `window::Event::Focused` into two variants --- native/src/window/event.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'native') diff --git a/native/src/window/event.rs b/native/src/window/event.rs index d649760b..fc746781 100644 --- a/native/src/window/event.rs +++ b/native/src/window/event.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; /// A window-related event. #[derive(PartialEq, Clone, Debug)] pub enum Event { - /// A window was resized + /// A window was resized. Resized { /// The new width of the window (in units) width: u32, @@ -12,6 +12,12 @@ pub enum Event { height: u32, }, + /// A window was focused. + Focused, + + /// A window was unfocused. + Unfocused, + /// A file is being hovered over the window. /// /// When the user hovers multiple files at once, this event will be emitted @@ -29,7 +35,4 @@ pub enum Event { /// There will be a single `FilesHoveredLeft` event triggered even if /// multiple files were hovered. FilesHoveredLeft, - - /// A window was focused or not - Focused(bool), } -- cgit From 9e453843b26f2f73228316334298a4c608b2f050 Mon Sep 17 00:00:00 2001 From: anunge Date: Fri, 12 Feb 2021 21:52:20 +0200 Subject: Touch support for `PaneGrid` and `PickList` (#650) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * touch events properly parsed and converted to logical size, button working * scrolling with a nice touch * fixed application state level touch cursor. panel_grid is touchable now. * format glicthes fixes * format glitches * tight format * fixed pane grid * fixing with upstream * Remove unused `touch` module from `iced_core` * Remove unused `crate::text` import in `iced_native` * Remove redundant match branch in `iced_winit` * Keep removed line break in `UserInterface::update` * Compute `text_size` only when bounds contains cursor in `overlay::menu` Co-authored-by: Héctor Ramón Jiménez --- native/src/overlay/menu.rs | 26 +++++++- native/src/widget/pane_grid.rs | 131 ++++++++++++++++++++--------------------- native/src/widget/pick_list.rs | 4 +- 3 files changed, 91 insertions(+), 70 deletions(-) (limited to 'native') diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index abac849f..5ad1391f 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -6,6 +6,7 @@ use crate::mouse; use crate::overlay; use crate::scrollable; use crate::text; +use crate::touch; use crate::{ Clipboard, Container, Element, Hasher, Layout, Length, Point, Rectangle, Scrollable, Size, Vector, Widget, @@ -337,15 +338,36 @@ where } Event::Mouse(mouse::Event::CursorMoved { .. }) => { let bounds = layout.bounds(); - let text_size = - self.text_size.unwrap_or(renderer.default_size()); if bounds.contains(cursor_position) { + let text_size = + self.text_size.unwrap_or(renderer.default_size()); + + *self.hovered_option = Some( + ((cursor_position.y - bounds.y) + / f32::from(text_size + self.padding * 2)) + as usize, + ); + } + } + Event::Touch(touch::Event::FingerPressed { .. }) => { + let bounds = layout.bounds(); + + if bounds.contains(cursor_position) { + let text_size = + self.text_size.unwrap_or(renderer.default_size()); + *self.hovered_option = Some( ((cursor_position.y - bounds.y) / f32::from(text_size + self.padding * 2)) as usize, ); + + if let Some(index) = *self.hovered_option { + if let Some(option) = self.options.get(index) { + *self.last_selection = Some(option.clone()); + } + } } } _ => {} diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index da3e25fd..c6fe4b60 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -33,6 +33,7 @@ use crate::layout; use crate::mouse; use crate::overlay; use crate::row; +use crate::touch; use crate::{ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector, Widget, @@ -368,42 +369,34 @@ where let mut event_status = event::Status::Ignored; match event { - Event::Mouse(mouse_event) => match mouse_event { - mouse::Event::ButtonPressed(mouse::Button::Left) => { - let bounds = layout.bounds(); - - 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, - ); + 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, @@ -411,47 +404,51 @@ where ); } } + None => { + self.click_pane(layout, cursor_position, messages); + } } } - mouse::Event::ButtonReleased(mouse::Button::Left) => { - 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)| { + } + 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)); - } + messages.push(on_drag(event)); + } - self.state.idle(); + self.state.idle(); - event_status = event::Status::Captured; - } else if self.state.picked_split().is_some() { - 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_status = - self.trigger_resize(layout, cursor_position, messages); + event_status = event::Status::Captured; } - _ => {} - }, + } + Event::Mouse(mouse::Event::CursorMoved { .. }) + | Event::Touch(touch::Event::FingerMoved { .. }) => { + event_status = + self.trigger_resize(layout, cursor_position, messages); + } _ => {} } diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 4110b9bb..74f4508e 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -6,6 +6,7 @@ use crate::overlay; use crate::overlay::menu::{self, Menu}; use crate::scrollable; use crate::text; +use crate::touch; use crate::{ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, }; @@ -214,7 +215,8 @@ where _clipboard: Option<&dyn Clipboard>, ) -> 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 event_status = if *self.is_open { // TODO: Encode cursor availability in the type system *self.is_open = -- cgit From a19f89d3a6af2804f2ac4e30f6d639b56a9bebfd Mon Sep 17 00:00:00 2001 From: Yusuf Bera Ertan Date: Tue, 28 Jul 2020 18:07:46 +0300 Subject: feat(native): add Tooltip widget --- native/src/element.rs | 14 +- native/src/overlay.rs | 2 + native/src/overlay/element.rs | 8 +- native/src/overlay/menu.rs | 1 + native/src/user_interface.rs | 16 +- native/src/widget.rs | 5 + native/src/widget/column.rs | 10 +- native/src/widget/container.rs | 8 +- native/src/widget/pane_grid.rs | 6 +- native/src/widget/pane_grid/content.rs | 7 +- native/src/widget/pick_list.rs | 2 + native/src/widget/row.rs | 10 +- native/src/widget/scrollable.rs | 8 +- native/src/widget/tooltip.rs | 300 +++++++++++++++++++++++++++++++++ 14 files changed, 381 insertions(+), 16 deletions(-) create mode 100644 native/src/widget/tooltip.rs (limited to 'native') diff --git a/native/src/element.rs b/native/src/element.rs index d6e9639a..5e906524 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -259,8 +259,11 @@ where pub fn overlay<'b>( &'b mut self, layout: Layout<'_>, + overlay_content_bounds: Option, + cursor_position: Point, ) -> Option> { - self.widget.overlay(layout) + self.widget + .overlay(layout, overlay_content_bounds, cursor_position) } } @@ -352,11 +355,13 @@ where fn overlay( &mut self, layout: Layout<'_>, + overlay_content_bounds: Option, + cursor_position: Point, ) -> Option> { let mapper = &self.mapper; self.widget - .overlay(layout) + .overlay(layout, overlay_content_bounds, cursor_position) .map(move |overlay| overlay.map(mapper)) } } @@ -440,7 +445,10 @@ where fn overlay( &mut self, layout: Layout<'_>, + overlay_content_bounds: Option, + cursor_position: Point, ) -> Option> { - self.element.overlay(layout) + self.element + .overlay(layout, overlay_content_bounds, cursor_position) } } diff --git a/native/src/overlay.rs b/native/src/overlay.rs index ea8bb384..20e3ee92 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -4,6 +4,7 @@ mod element; pub mod menu; pub use element::Element; +use iced_core::Rectangle; pub use menu::Menu; use crate::event::{self, Event}; @@ -35,6 +36,7 @@ where defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, + viewport: &Rectangle, ) -> Renderer::Output; /// Computes the _layout_ hash of the [`Overlay`]. diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index 0f44a781..fbe05d31 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -1,3 +1,5 @@ +use iced_core::Rectangle; + pub use crate::Overlay; use crate::event::{self, Event}; @@ -74,9 +76,10 @@ where defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, + viewport: &Rectangle, ) -> Renderer::Output { self.overlay - .draw(renderer, defaults, layout, cursor_position) + .draw(renderer, defaults, layout, cursor_position, viewport) } /// Computes the _layout_ hash of the [`Element`]. @@ -145,9 +148,10 @@ where defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, + viewport: &Rectangle, ) -> Renderer::Output { self.content - .draw(renderer, defaults, layout, cursor_position) + .draw(renderer, defaults, layout, cursor_position, viewport) } fn hash_layout(&self, state: &mut Hasher, position: Point) { diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 5ad1391f..c920e86e 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -239,6 +239,7 @@ where defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, + _viewport: &Rectangle, ) -> Renderer::Output { let primitives = self.container.draw( renderer, diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 7a64ac59..996bdd30 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -198,8 +198,11 @@ where messages: &mut Vec, ) -> Vec { let (base_cursor, overlay_statuses) = if let Some(mut overlay) = - self.root.overlay(Layout::new(&self.base.layout)) - { + self.root.overlay( + Layout::new(&self.base.layout), + self.overlay.as_ref().map(|l| l.layout.bounds()), + cursor_position, + ) { let layer = Self::overlay_layer( self.overlay.take(), self.bounds, @@ -334,9 +337,11 @@ where ) -> Renderer::Output { let viewport = Rectangle::with_size(self.bounds); - let overlay = if let Some(mut overlay) = - self.root.overlay(Layout::new(&self.base.layout)) - { + let overlay = if let Some(mut overlay) = self.root.overlay( + Layout::new(&self.base.layout), + self.overlay.as_ref().map(|l| l.layout.bounds()), + cursor_position, + ) { let layer = Self::overlay_layer( self.overlay.take(), self.bounds, @@ -351,6 +356,7 @@ where &Renderer::Defaults::default(), Layout::new(&layer.layout), cursor_position, + &viewport, ); self.overlay = Some(layer); diff --git a/native/src/widget.rs b/native/src/widget.rs index 3677713a..1309d6af 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -36,6 +36,7 @@ pub mod space; pub mod svg; pub mod text; pub mod text_input; +pub mod tooltip; #[doc(no_inline)] pub use button::Button; @@ -71,6 +72,8 @@ pub use svg::Svg; pub use text::Text; #[doc(no_inline)] pub use text_input::TextInput; +#[doc(no_inline)] +pub use tooltip::Tooltip; use crate::event::{self, Event}; use crate::layout; @@ -172,6 +175,8 @@ where fn overlay( &mut self, _layout: Layout<'_>, + _overlay_content_bounds: Option, + _cursor_position: Point, ) -> Option> { None } diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index e0e88d31..9ee60627 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -198,11 +198,19 @@ where fn overlay( &mut self, layout: Layout<'_>, + overlay_content_bounds: Option, + cursor_position: Point, ) -> Option> { self.children .iter_mut() .zip(layout.children()) - .filter_map(|(child, layout)| child.widget.overlay(layout)) + .filter_map(|(child, layout)| { + child.widget.overlay( + layout, + overlay_content_bounds, + cursor_position, + ) + }) .next() } } diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 65764148..2fc6707e 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -200,8 +200,14 @@ where fn overlay( &mut self, layout: Layout<'_>, + overlay_content_bounds: Option, + cursor_position: Point, ) -> Option> { - self.content.overlay(layout.children().next().unwrap()) + self.content.overlay( + layout.children().next().unwrap(), + overlay_content_bounds, + cursor_position, + ) } } diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index c6fe4b60..0a7d818d 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -558,11 +558,15 @@ where fn overlay( &mut self, layout: Layout<'_>, + overlay_content_bounds: Option, + cursor_position: Point, ) -> Option> { self.elements .iter_mut() .zip(layout.children()) - .filter_map(|((_, pane), layout)| pane.overlay(layout)) + .filter_map(|((_, pane), layout)| { + pane.overlay(layout, overlay_content_bounds, cursor_position) + }) .next() } } diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index 913cfe96..28515624 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -1,3 +1,5 @@ +use iced_core::Rectangle; + use crate::container; use crate::event::{self, Event}; use crate::layout; @@ -189,6 +191,8 @@ where pub(crate) fn overlay( &mut self, layout: Layout<'_>, + overlay_content_bounds: Option, + cursor_position: Point, ) -> Option> { let body_layout = if self.title_bar.is_some() { let mut children = layout.children(); @@ -201,7 +205,8 @@ where layout }; - self.body.overlay(body_layout) + self.body + .overlay(body_layout, overlay_content_bounds, cursor_position) } } diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 74f4508e..6c424d28 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -274,6 +274,8 @@ where fn overlay( &mut self, layout: Layout<'_>, + _overlay_content_bounds: Option, + _cursor_position: Point, ) -> Option> { if *self.is_open { let bounds = layout.bounds(); diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index b71663bd..c542aedc 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -197,11 +197,19 @@ where fn overlay( &mut self, layout: Layout<'_>, + overlay_content_bounds: Option, + cursor_position: Point, ) -> Option> { self.children .iter_mut() .zip(layout.children()) - .filter_map(|(child, layout)| child.widget.overlay(layout)) + .filter_map(|(child, layout)| { + child.widget.overlay( + layout, + overlay_content_bounds, + cursor_position, + ) + }) .next() } } diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 18cdf169..86a68f22 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -401,11 +401,17 @@ where fn overlay( &mut self, layout: Layout<'_>, + overlay_content_bounds: Option, + cursor_position: Point, ) -> Option> { let Self { content, state, .. } = self; content - .overlay(layout.children().next().unwrap()) + .overlay( + layout.children().next().unwrap(), + overlay_content_bounds, + cursor_position, + ) .map(|overlay| { let bounds = layout.bounds(); let content_layout = layout.children().next().unwrap(); diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs new file mode 100644 index 00000000..cae38d46 --- /dev/null +++ b/native/src/widget/tooltip.rs @@ -0,0 +1,300 @@ +//! Display a widget over another. +use std::hash::Hash; + +use iced_core::Rectangle; + +use crate::{ + event, layout, overlay, Clipboard, Element, Event, Hasher, Layout, Length, + Point, Size, Vector, 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: Element<'a, Message, Renderer>, + tooltip_position: TooltipPosition, +} + +impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer> +where + Renderer: self::Renderer, +{ + /// Creates an empty [`Tooltip`]. + /// + /// [`Tooltip`]: struct.Tooltip.html + pub fn new( + content: T, + tooltip: H, + tooltip_position: TooltipPosition, + ) -> Self + where + T: Into>, + H: Into>, + { + Tooltip { + content: content.into(), + tooltip: tooltip.into(), + tooltip_position, + } + } +} + +/// The position of the tooltip. Defaults to following the cursor. +#[derive(Debug, PartialEq)] +pub enum TooltipPosition { + /// 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 Default for TooltipPosition { + fn default() -> Self { + TooltipPosition::FollowCursor + } +} + +impl<'a, Message, Renderer> Widget + 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, + messages: &mut Vec, + renderer: &Renderer, + clipboard: Option<&dyn Clipboard>, + ) -> event::Status { + self.content.widget.on_event( + event, + layout, + cursor_position, + messages, + renderer, + clipboard, + ) + } + + fn draw( + &self, + renderer: &mut Renderer, + defaults: &Renderer::Defaults, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) -> Renderer::Output { + renderer.draw( + defaults, + cursor_position, + &self.content, + layout, + viewport, + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + + self.content.hash_layout(state); + } + + fn overlay( + &mut self, + layout: Layout<'_>, + overlay_content_bounds: Option, + cursor_position: Point, + ) -> Option> { + let bounds = layout.bounds(); + + if bounds.contains(cursor_position) { + let mut position = cursor_position; + + if let Some(content_bounds) = overlay_content_bounds { + if TooltipPosition::FollowCursor != self.tooltip_position { + match self.tooltip_position { + TooltipPosition::Top | TooltipPosition::Bottom => { + let x = bounds.x + bounds.width * 0.5 + - content_bounds.width * 0.5; + + position = match self.tooltip_position { + TooltipPosition::Top => Point::new( + x, + bounds.y - content_bounds.height, + ), + TooltipPosition::Bottom => Point::new( + x, + bounds.y + + bounds.height + + content_bounds.height, + ), + _ => unreachable!(), + }; + } + TooltipPosition::Left | TooltipPosition::Right => { + let y = + bounds.center_y() + content_bounds.height * 0.5; + + position = match self.tooltip_position { + TooltipPosition::Left => Point::new( + bounds.x - content_bounds.width, + y, + ), + TooltipPosition::Right => { + Point::new(bounds.x + bounds.width, y) + } + _ => unreachable!(), + }; + } + _ => {} + } + } + } + + Some(overlay::Element::new( + position, + Box::new(Overlay::new(&self.tooltip)), + )) + } else { + None + } + } +} + +struct Overlay<'a, Message, Renderer: self::Renderer> { + content: &'a Element<'a, Message, Renderer>, +} + +impl<'a, Message, Renderer: self::Renderer> Overlay<'a, Message, Renderer> +where + Message: 'a, + Renderer: 'a, +{ + pub fn new(content: &'a Element<'a, Message, Renderer>) -> Self { + Self { content } + } +} + +impl<'a, Message, Renderer> crate::Overlay + for Overlay<'a, Message, Renderer> +where + Renderer: self::Renderer, +{ + fn layout( + &self, + renderer: &Renderer, + bounds: Size, + position: Point, + ) -> layout::Node { + let space_below = bounds.height - position.y; + let space_above = position.y; + + let limits = layout::Limits::new( + Size::ZERO, + Size::new( + bounds.width - position.x, + if space_below > space_above { + space_below + } else { + space_above + }, + ), + ) + .width(self.content.width()); + + let mut node = self.content.layout(renderer, &limits); + + node.move_to(position - Vector::new(0.0, node.size().height)); + + node + } + + fn hash_layout(&self, state: &mut Hasher, position: Point) { + struct Marker; + std::any::TypeId::of::().hash(state); + + (position.x as u32).hash(state); + (position.y as u32).hash(state); + self.content.hash_layout(state); + } + + fn draw( + &self, + renderer: &mut Renderer, + defaults: &Renderer::Defaults, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) -> Renderer::Output { + renderer.draw( + defaults, + cursor_position, + &self.content, + layout, + viewport, + ) + } +} + +/// 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 { + /// The style supported by this renderer. + type Style: Default; + + /// Draws a [`Tooltip`]. + /// + /// [`Tooltip`]: struct.Tooltip.html + fn draw( + &mut self, + defaults: &Self::Defaults, + cursor_position: Point, + content: &Element<'_, Message, Self>, + content_layout: Layout<'_>, + viewport: &Rectangle, + ) -> Self::Output; +} + +impl<'a, Message, Renderer> From> + 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) + } +} -- cgit From 81c75c15249b608dd8a6d47e25f96feb10ca68da Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 23 Feb 2021 03:09:16 +0100 Subject: Change `Tooltip` to support `Text` only for now --- native/src/element.rs | 14 +-- native/src/layout.rs | 7 +- native/src/user_interface.rs | 15 +-- native/src/widget.rs | 2 - native/src/widget/column.rs | 10 +- native/src/widget/container.rs | 8 +- native/src/widget/pane_grid.rs | 6 +- native/src/widget/pane_grid/content.rs | 7 +- native/src/widget/pick_list.rs | 2 - native/src/widget/row.rs | 10 +- native/src/widget/scrollable.rs | 8 +- native/src/widget/tooltip.rs | 194 +++++---------------------------- 12 files changed, 46 insertions(+), 237 deletions(-) (limited to 'native') diff --git a/native/src/element.rs b/native/src/element.rs index 5e906524..d6e9639a 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -259,11 +259,8 @@ where pub fn overlay<'b>( &'b mut self, layout: Layout<'_>, - overlay_content_bounds: Option, - cursor_position: Point, ) -> Option> { - self.widget - .overlay(layout, overlay_content_bounds, cursor_position) + self.widget.overlay(layout) } } @@ -355,13 +352,11 @@ where fn overlay( &mut self, layout: Layout<'_>, - overlay_content_bounds: Option, - cursor_position: Point, ) -> Option> { let mapper = &self.mapper; self.widget - .overlay(layout, overlay_content_bounds, cursor_position) + .overlay(layout) .map(move |overlay| overlay.map(mapper)) } } @@ -445,10 +440,7 @@ where fn overlay( &mut self, layout: Layout<'_>, - overlay_content_bounds: Option, - cursor_position: Point, ) -> Option> { - self.element - .overlay(layout, overlay_content_bounds, cursor_position) + self.element.overlay(layout) } } diff --git a/native/src/layout.rs b/native/src/layout.rs index 6d144902..b4b4a021 100644 --- a/native/src/layout.rs +++ b/native/src/layout.rs @@ -19,11 +19,14 @@ pub struct Layout<'a> { } impl<'a> Layout<'a> { - pub(crate) fn new(node: &'a Node) -> Self { + /// Creates a new [`Layout`] for the given [`Node`] at the origin. + pub fn new(node: &'a Node) -> Self { Self::with_offset(Vector::new(0.0, 0.0), node) } - pub(crate) fn with_offset(offset: Vector, node: &'a Node) -> Self { + /// Creates a new [`Layout`] for the given [`Node`] with the provided offset + /// from the origin. + pub fn with_offset(offset: Vector, node: &'a Node) -> Self { let bounds = node.bounds(); Self { diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 996bdd30..d1835b65 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -198,11 +198,8 @@ where messages: &mut Vec, ) -> Vec { let (base_cursor, overlay_statuses) = if let Some(mut overlay) = - self.root.overlay( - Layout::new(&self.base.layout), - self.overlay.as_ref().map(|l| l.layout.bounds()), - cursor_position, - ) { + self.root.overlay(Layout::new(&self.base.layout)) + { let layer = Self::overlay_layer( self.overlay.take(), self.bounds, @@ -337,11 +334,9 @@ where ) -> Renderer::Output { let viewport = Rectangle::with_size(self.bounds); - let overlay = if let Some(mut overlay) = self.root.overlay( - Layout::new(&self.base.layout), - self.overlay.as_ref().map(|l| l.layout.bounds()), - cursor_position, - ) { + let overlay = if let Some(mut overlay) = + self.root.overlay(Layout::new(&self.base.layout)) + { let layer = Self::overlay_layer( self.overlay.take(), self.bounds, diff --git a/native/src/widget.rs b/native/src/widget.rs index 1309d6af..d5c353df 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -175,8 +175,6 @@ where fn overlay( &mut self, _layout: Layout<'_>, - _overlay_content_bounds: Option, - _cursor_position: Point, ) -> Option> { None } diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 9ee60627..e0e88d31 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -198,19 +198,11 @@ where fn overlay( &mut self, layout: Layout<'_>, - overlay_content_bounds: Option, - cursor_position: Point, ) -> Option> { self.children .iter_mut() .zip(layout.children()) - .filter_map(|(child, layout)| { - child.widget.overlay( - layout, - overlay_content_bounds, - cursor_position, - ) - }) + .filter_map(|(child, layout)| child.widget.overlay(layout)) .next() } } diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 2fc6707e..65764148 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -200,14 +200,8 @@ where fn overlay( &mut self, layout: Layout<'_>, - overlay_content_bounds: Option, - cursor_position: Point, ) -> Option> { - self.content.overlay( - layout.children().next().unwrap(), - overlay_content_bounds, - cursor_position, - ) + self.content.overlay(layout.children().next().unwrap()) } } diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 0a7d818d..c6fe4b60 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -558,15 +558,11 @@ where fn overlay( &mut self, layout: Layout<'_>, - overlay_content_bounds: Option, - cursor_position: Point, ) -> Option> { self.elements .iter_mut() .zip(layout.children()) - .filter_map(|((_, pane), layout)| { - pane.overlay(layout, overlay_content_bounds, cursor_position) - }) + .filter_map(|((_, pane), layout)| pane.overlay(layout)) .next() } } diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index 28515624..913cfe96 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -1,5 +1,3 @@ -use iced_core::Rectangle; - use crate::container; use crate::event::{self, Event}; use crate::layout; @@ -191,8 +189,6 @@ where pub(crate) fn overlay( &mut self, layout: Layout<'_>, - overlay_content_bounds: Option, - cursor_position: Point, ) -> Option> { let body_layout = if self.title_bar.is_some() { let mut children = layout.children(); @@ -205,8 +201,7 @@ where layout }; - self.body - .overlay(body_layout, overlay_content_bounds, cursor_position) + self.body.overlay(body_layout) } } diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 6c424d28..74f4508e 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -274,8 +274,6 @@ where fn overlay( &mut self, layout: Layout<'_>, - _overlay_content_bounds: Option, - _cursor_position: Point, ) -> Option> { if *self.is_open { let bounds = layout.bounds(); diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index c542aedc..b71663bd 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -197,19 +197,11 @@ where fn overlay( &mut self, layout: Layout<'_>, - overlay_content_bounds: Option, - cursor_position: Point, ) -> Option> { self.children .iter_mut() .zip(layout.children()) - .filter_map(|(child, layout)| { - child.widget.overlay( - layout, - overlay_content_bounds, - cursor_position, - ) - }) + .filter_map(|(child, layout)| child.widget.overlay(layout)) .next() } } diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 86a68f22..18cdf169 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -401,17 +401,11 @@ where fn overlay( &mut self, layout: Layout<'_>, - overlay_content_bounds: Option, - cursor_position: Point, ) -> Option> { let Self { content, state, .. } = self; content - .overlay( - layout.children().next().unwrap(), - overlay_content_bounds, - cursor_position, - ) + .overlay(layout.children().next().unwrap()) .map(|overlay| { let bounds = layout.bounds(); let content_layout = layout.children().next().unwrap(); diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs index cae38d46..72d03c1a 100644 --- a/native/src/widget/tooltip.rs +++ b/native/src/widget/tooltip.rs @@ -3,46 +3,43 @@ use std::hash::Hash; use iced_core::Rectangle; +use crate::widget::text::{self, Text}; use crate::{ - event, layout, overlay, Clipboard, Element, Event, Hasher, Layout, Length, - Point, Size, Vector, Widget, + 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> { +pub struct Tooltip<'a, Message, Renderer: self::Renderer + text::Renderer> { content: Element<'a, Message, Renderer>, - tooltip: Element<'a, Message, Renderer>, - tooltip_position: TooltipPosition, + tooltip: Text, + position: Position, } impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer> where - Renderer: self::Renderer, + Renderer: self::Renderer + text::Renderer, { /// Creates an empty [`Tooltip`]. /// /// [`Tooltip`]: struct.Tooltip.html - pub fn new( - content: T, - tooltip: H, - tooltip_position: TooltipPosition, - ) -> Self - where - T: Into>, - H: Into>, - { + pub fn new( + content: impl Into>, + tooltip: Text, + position: Position, + ) -> Self { Tooltip { content: content.into(), - tooltip: tooltip.into(), - tooltip_position, + tooltip, + position, } } } /// The position of the tooltip. Defaults to following the cursor. -#[derive(Debug, PartialEq)] -pub enum TooltipPosition { +#[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. @@ -55,16 +52,10 @@ pub enum TooltipPosition { Right, } -impl Default for TooltipPosition { - fn default() -> Self { - TooltipPosition::FollowCursor - } -} - impl<'a, Message, Renderer> Widget for Tooltip<'a, Message, Renderer> where - Renderer: self::Renderer, + Renderer: self::Renderer + text::Renderer, { fn width(&self) -> Length { self.content.width() @@ -109,12 +100,15 @@ where cursor_position: Point, viewport: &Rectangle, ) -> Renderer::Output { - renderer.draw( + self::Renderer::draw( + renderer, defaults, cursor_position, - &self.content, layout, viewport, + &self.content, + &self.tooltip, + self.position, ) } @@ -124,142 +118,6 @@ where self.content.hash_layout(state); } - - fn overlay( - &mut self, - layout: Layout<'_>, - overlay_content_bounds: Option, - cursor_position: Point, - ) -> Option> { - let bounds = layout.bounds(); - - if bounds.contains(cursor_position) { - let mut position = cursor_position; - - if let Some(content_bounds) = overlay_content_bounds { - if TooltipPosition::FollowCursor != self.tooltip_position { - match self.tooltip_position { - TooltipPosition::Top | TooltipPosition::Bottom => { - let x = bounds.x + bounds.width * 0.5 - - content_bounds.width * 0.5; - - position = match self.tooltip_position { - TooltipPosition::Top => Point::new( - x, - bounds.y - content_bounds.height, - ), - TooltipPosition::Bottom => Point::new( - x, - bounds.y - + bounds.height - + content_bounds.height, - ), - _ => unreachable!(), - }; - } - TooltipPosition::Left | TooltipPosition::Right => { - let y = - bounds.center_y() + content_bounds.height * 0.5; - - position = match self.tooltip_position { - TooltipPosition::Left => Point::new( - bounds.x - content_bounds.width, - y, - ), - TooltipPosition::Right => { - Point::new(bounds.x + bounds.width, y) - } - _ => unreachable!(), - }; - } - _ => {} - } - } - } - - Some(overlay::Element::new( - position, - Box::new(Overlay::new(&self.tooltip)), - )) - } else { - None - } - } -} - -struct Overlay<'a, Message, Renderer: self::Renderer> { - content: &'a Element<'a, Message, Renderer>, -} - -impl<'a, Message, Renderer: self::Renderer> Overlay<'a, Message, Renderer> -where - Message: 'a, - Renderer: 'a, -{ - pub fn new(content: &'a Element<'a, Message, Renderer>) -> Self { - Self { content } - } -} - -impl<'a, Message, Renderer> crate::Overlay - for Overlay<'a, Message, Renderer> -where - Renderer: self::Renderer, -{ - fn layout( - &self, - renderer: &Renderer, - bounds: Size, - position: Point, - ) -> layout::Node { - let space_below = bounds.height - position.y; - let space_above = position.y; - - let limits = layout::Limits::new( - Size::ZERO, - Size::new( - bounds.width - position.x, - if space_below > space_above { - space_below - } else { - space_above - }, - ), - ) - .width(self.content.width()); - - let mut node = self.content.layout(renderer, &limits); - - node.move_to(position - Vector::new(0.0, node.size().height)); - - node - } - - fn hash_layout(&self, state: &mut Hasher, position: Point) { - struct Marker; - std::any::TypeId::of::().hash(state); - - (position.x as u32).hash(state); - (position.y as u32).hash(state); - self.content.hash_layout(state); - } - - fn draw( - &self, - renderer: &mut Renderer, - defaults: &Renderer::Defaults, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ) -> Renderer::Output { - renderer.draw( - defaults, - cursor_position, - &self.content, - layout, - viewport, - ) - } } /// The renderer of a [`Tooltip`]. @@ -269,7 +127,7 @@ where /// /// [`Tooltip`]: struct.Tooltip.html /// [renderer]: ../../renderer/index.html -pub trait Renderer: crate::Renderer { +pub trait Renderer: crate::Renderer + text::Renderer { /// The style supported by this renderer. type Style: Default; @@ -280,16 +138,18 @@ pub trait Renderer: crate::Renderer { &mut self, defaults: &Self::Defaults, cursor_position: Point, - content: &Element<'_, Message, Self>, content_layout: Layout<'_>, viewport: &Rectangle, + content: &Element<'_, Message, Self>, + tooltip: &Text, + position: Position, ) -> Self::Output; } impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where - Renderer: 'a + self::Renderer, + Renderer: 'a + self::Renderer + text::Renderer, Message: 'a, { fn from( -- cgit From 9f60a256fc909dda0c30e301020d03d7ec28d722 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 23 Feb 2021 03:12:00 +0100 Subject: Remove `viewport` from `Overlay::draw` for now --- native/src/overlay.rs | 2 -- native/src/overlay/element.rs | 8 ++------ native/src/overlay/menu.rs | 1 - native/src/user_interface.rs | 1 - 4 files changed, 2 insertions(+), 10 deletions(-) (limited to 'native') diff --git a/native/src/overlay.rs b/native/src/overlay.rs index 20e3ee92..ea8bb384 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -4,7 +4,6 @@ mod element; pub mod menu; pub use element::Element; -use iced_core::Rectangle; pub use menu::Menu; use crate::event::{self, Event}; @@ -36,7 +35,6 @@ where defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, - viewport: &Rectangle, ) -> Renderer::Output; /// Computes the _layout_ hash of the [`Overlay`]. diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index fbe05d31..0f44a781 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -1,5 +1,3 @@ -use iced_core::Rectangle; - pub use crate::Overlay; use crate::event::{self, Event}; @@ -76,10 +74,9 @@ where defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, - viewport: &Rectangle, ) -> Renderer::Output { self.overlay - .draw(renderer, defaults, layout, cursor_position, viewport) + .draw(renderer, defaults, layout, cursor_position) } /// Computes the _layout_ hash of the [`Element`]. @@ -148,10 +145,9 @@ where defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, - viewport: &Rectangle, ) -> Renderer::Output { self.content - .draw(renderer, defaults, layout, cursor_position, viewport) + .draw(renderer, defaults, layout, cursor_position) } fn hash_layout(&self, state: &mut Hasher, position: Point) { diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index c920e86e..5ad1391f 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -239,7 +239,6 @@ where defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, - _viewport: &Rectangle, ) -> Renderer::Output { let primitives = self.container.draw( renderer, diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index d1835b65..7a64ac59 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -351,7 +351,6 @@ where &Renderer::Defaults::default(), Layout::new(&layer.layout), cursor_position, - &viewport, ); self.overlay = Some(layer); -- cgit From 2f766b73413fe60cd881e139fa0e84a0f0134d91 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 23 Feb 2021 03:16:37 +0100 Subject: Introduce `Tooltip::gap` to control spacing --- native/src/widget/tooltip.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'native') diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs index 72d03c1a..6da7b0ca 100644 --- a/native/src/widget/tooltip.rs +++ b/native/src/widget/tooltip.rs @@ -15,6 +15,7 @@ pub struct Tooltip<'a, Message, Renderer: self::Renderer + text::Renderer> { content: Element<'a, Message, Renderer>, tooltip: Text, position: Position, + gap: u16, } impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer> @@ -33,8 +34,15 @@ where content: content.into(), tooltip, position, + gap: 0, } } + + /// Sets the gap between the content and its [`Tooltip`]. + pub fn gap(mut self, gap: u16) -> Self { + self.gap = gap; + self + } } /// The position of the tooltip. Defaults to following the cursor. @@ -109,6 +117,7 @@ where &self.content, &self.tooltip, self.position, + self.gap, ) } @@ -143,6 +152,7 @@ pub trait Renderer: crate::Renderer + text::Renderer { content: &Element<'_, Message, Self>, tooltip: &Text, position: Position, + gap: u16, ) -> Self::Output; } -- cgit From 4e923290ccb38dc9cee05592554f98f1f0f12966 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 23 Feb 2021 04:00:35 +0100 Subject: Add `style` and `padding` to `Tooltip` --- native/src/widget/tooltip.rs | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) (limited to 'native') diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs index 6da7b0ca..56559827 100644 --- a/native/src/widget/tooltip.rs +++ b/native/src/widget/tooltip.rs @@ -3,6 +3,7 @@ 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, @@ -11,16 +12,18 @@ use crate::{ /// An element to display a widget over another. #[allow(missing_debug_implementations)] -pub struct Tooltip<'a, Message, Renderer: self::Renderer + text::Renderer> { +pub struct Tooltip<'a, Message, Renderer: self::Renderer> { content: Element<'a, Message, Renderer>, tooltip: Text, position: Position, + style: ::Style, gap: u16, + padding: u16, } impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer> where - Renderer: self::Renderer + text::Renderer, + Renderer: self::Renderer, { /// Creates an empty [`Tooltip`]. /// @@ -34,15 +37,32 @@ where content: content.into(), tooltip, position, + style: Default::default(), gap: 0, + padding: Renderer::DEFAULT_PADDING, } } + /// Sets the style of the [`Tooltip`]. + pub fn style( + mut self, + style: impl Into<::Style>, + ) -> Self { + self.style = style.into(); + 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 + } } /// The position of the tooltip. Defaults to following the cursor. @@ -63,7 +83,7 @@ pub enum Position { impl<'a, Message, Renderer> Widget for Tooltip<'a, Message, Renderer> where - Renderer: self::Renderer + text::Renderer, + Renderer: self::Renderer, { fn width(&self) -> Length { self.content.width() @@ -117,7 +137,9 @@ where &self.content, &self.tooltip, self.position, + &self.style, self.gap, + self.padding, ) } @@ -136,9 +158,11 @@ where /// /// [`Tooltip`]: struct.Tooltip.html /// [renderer]: ../../renderer/index.html -pub trait Renderer: crate::Renderer + text::Renderer { - /// The style supported by this renderer. - type Style: Default; +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`]. /// @@ -152,14 +176,16 @@ pub trait Renderer: crate::Renderer + text::Renderer { content: &Element<'_, Message, Self>, tooltip: &Text, position: Position, + style: &::Style, gap: u16, + padding: u16, ) -> Self::Output; } impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where - Renderer: 'a + self::Renderer + text::Renderer, + Renderer: 'a + self::Renderer, Message: 'a, { fn from( -- cgit From 2736e4ca35f17a92768f0be682acf6da3b574cb6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 24 Feb 2021 00:59:29 +0100 Subject: Hide `Text` as an implementation detail of `Tooltip` --- native/src/widget/tooltip.rs | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) (limited to 'native') diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs index 56559827..ab07868c 100644 --- a/native/src/widget/tooltip.rs +++ b/native/src/widget/tooltip.rs @@ -30,12 +30,12 @@ where /// [`Tooltip`]: struct.Tooltip.html pub fn new( content: impl Into>, - tooltip: Text, + tooltip: impl ToString, position: Position, ) -> Self { Tooltip { content: content.into(), - tooltip, + tooltip: Text::new(tooltip.to_string()), position, style: Default::default(), gap: 0, @@ -43,12 +43,17 @@ where } } - /// Sets the style of the [`Tooltip`]. - pub fn style( - mut self, - style: impl Into<::Style>, - ) -> Self { - self.style = style.into(); + /// 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) -> Self { + self.tooltip = self.tooltip.font(font); self } @@ -63,6 +68,15 @@ where self.padding = padding; self } + + /// Sets the style of the [`Tooltip`]. + pub fn style( + mut self, + style: impl Into<::Style>, + ) -> Self { + self.style = style.into(); + self + } } /// The position of the tooltip. Defaults to following the cursor. -- cgit From f52f8c1337f42cf9483abb40784129f4effbe48e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 27 Feb 2021 03:36:46 +0100 Subject: Fix `viewport` argument in `PaneGrid` draw calls --- native/src/renderer/null.rs | 3 +++ native/src/widget/pane_grid.rs | 6 +++++- native/src/widget/pane_grid/content.rs | 5 ++++- native/src/widget/pane_grid/title_bar.rs | 4 +++- 4 files changed, 15 insertions(+), 3 deletions(-) (limited to 'native') diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs index f505b012..9e91d29f 100644 --- a/native/src/renderer/null.rs +++ b/native/src/renderer/null.rs @@ -257,6 +257,7 @@ impl pane_grid::Renderer for Null { _layout: Layout<'_>, _style: &::Style, _cursor_position: Point, + _viewport: &Rectangle, ) { } @@ -271,6 +272,7 @@ impl pane_grid::Renderer for Null { )>, _body: (&Element<'_, Message, Self>, Layout<'_>), _cursor_position: Point, + _viewport: &Rectangle, ) { } @@ -282,6 +284,7 @@ impl pane_grid::Renderer for Null { _content: (&Element<'_, Message, Self>, Layout<'_>), _controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>, _cursor_position: Point, + _viewport: &Rectangle, ) { } } diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index c6fe4b60..e6274a28 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -478,7 +478,7 @@ where defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, - _viewport: &Rectangle, + viewport: &Rectangle, ) -> Renderer::Output { let picked_split = self .state @@ -537,6 +537,7 @@ where layout, &self.style, cursor_position, + viewport, ) } @@ -594,6 +595,7 @@ pub trait Renderer: crate::Renderer + container::Renderer + Sized { layout: Layout<'_>, style: &::Style, cursor_position: Point, + viewport: &Rectangle, ) -> Self::Output; /// Draws a [`Pane`]. @@ -611,6 +613,7 @@ pub trait Renderer: crate::Renderer + container::Renderer + Sized { title_bar: Option<(&TitleBar<'_, Message, Self>, Layout<'_>)>, body: (&Element<'_, Message, Self>, Layout<'_>), cursor_position: Point, + viewport: &Rectangle, ) -> Self::Output; /// Draws a [`TitleBar`]. @@ -629,6 +632,7 @@ pub trait Renderer: crate::Renderer + container::Renderer + Sized { content: (&Element<'_, Message, Self>, Layout<'_>), controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>, cursor_position: Point, + viewport: &Rectangle, ) -> Self::Output; } diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index 913cfe96..421da47b 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -3,7 +3,7 @@ use crate::event::{self, Event}; use crate::layout; use crate::overlay; use crate::pane_grid::{self, TitleBar}; -use crate::{Clipboard, Element, Hasher, Layout, Point, Size}; +use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size}; /// The content of a [`Pane`]. /// @@ -60,6 +60,7 @@ where 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(); @@ -73,6 +74,7 @@ where Some((title_bar, title_bar_layout)), (&self.body, body_layout), cursor_position, + viewport, ) } else { renderer.draw_pane( @@ -82,6 +84,7 @@ where None, (&self.body, layout), cursor_position, + viewport, ) } } diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index efaecf9e..9fbd2797 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -2,7 +2,7 @@ use crate::container; use crate::event::{self, Event}; use crate::layout; use crate::pane_grid; -use crate::{Clipboard, Element, Hasher, Layout, Point, Size}; +use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size}; /// The title bar of a [`Pane`]. /// @@ -85,6 +85,7 @@ where defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, + viewport: &Rectangle, show_controls: bool, ) -> Renderer::Output { let mut children = layout.children(); @@ -112,6 +113,7 @@ where (&self.content, title_layout), controls, cursor_position, + viewport, ) } -- cgit From bbca5c4bde6f9e6e54cca6cb216d38dfd3864e74 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 9 Mar 2021 04:49:17 +0100 Subject: Call `hash_layout` for `controls` in `pane_grid::TitleBar` --- native/src/widget/pane_grid/title_bar.rs | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'native') diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index efaecf9e..2f9659e8 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -147,6 +147,10 @@ where self.content.hash_layout(hasher); self.padding.hash(hasher); + + if let Some(controls) = &self.controls { + controls.hash_layout(hasher); + } } pub(crate) fn layout( -- cgit From b22b0dd7ff56d433c459e0d14e14eb5472a6224d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 10 Mar 2021 01:16:26 +0100 Subject: Update `window_clipboard` to `0.2` --- native/src/clipboard.rs | 4 ++-- native/src/widget/text_input.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'native') diff --git a/native/src/clipboard.rs b/native/src/clipboard.rs index ecdccabf..3eeb184d 100644 --- a/native/src/clipboard.rs +++ b/native/src/clipboard.rs @@ -1,6 +1,6 @@ /// A buffer for short-term storage and transfer within and between /// applications. pub trait Clipboard { - /// Returns the current content of the [`Clipboard`] as text. - fn content(&self) -> Option; + /// Reads the current content of the [`Clipboard`] as text. + fn read(&self) -> Option; } diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 2fd9cec1..4c38b1a3 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -509,7 +509,7 @@ where Some(content) => content, None => { let content: String = clipboard - .content() + .read() .unwrap_or(String::new()) .chars() .filter(|c| !c.is_control()) -- cgit From 35425001edcb54d861a42ec6d23f9e57b37745fd Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 10 Mar 2021 01:18:22 +0100 Subject: Introduce `write` method to `Clipboard` trait --- native/src/clipboard.rs | 3 +++ 1 file changed, 3 insertions(+) (limited to 'native') diff --git a/native/src/clipboard.rs b/native/src/clipboard.rs index 3eeb184d..fdb769da 100644 --- a/native/src/clipboard.rs +++ b/native/src/clipboard.rs @@ -3,4 +3,7 @@ pub trait Clipboard { /// Reads the current content of the [`Clipboard`] as text. fn read(&self) -> Option; + + /// Writes the given text contents to the [`Clipboard`]. + fn write(&mut self, contents: String); } -- cgit From 21971e0037c2ddcb96fd48ea96332445de4137bb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 10 Mar 2021 01:59:02 +0100 Subject: Make `Clipboard` argument in `Widget` trait mutable --- native/src/clipboard.rs | 14 +++++++ native/src/element.rs | 18 ++++----- native/src/lib.rs | 2 +- native/src/overlay.rs | 4 +- native/src/overlay/element.rs | 12 +++--- native/src/overlay/menu.rs | 10 ++--- native/src/program/state.rs | 4 +- native/src/user_interface.rs | 16 ++++---- native/src/widget.rs | 4 +- native/src/widget/button.rs | 6 +-- native/src/widget/checkbox.rs | 4 +- native/src/widget/column.rs | 6 +-- native/src/widget/container.rs | 6 +-- native/src/widget/image/viewer.rs | 4 +- native/src/widget/pane_grid.rs | 6 +-- native/src/widget/pane_grid/content.rs | 8 ++-- native/src/widget/pane_grid/title_bar.rs | 6 +-- native/src/widget/pick_list.rs | 4 +- native/src/widget/radio.rs | 4 +- native/src/widget/row.rs | 6 +-- native/src/widget/scrollable.rs | 6 +-- native/src/widget/slider.rs | 4 +- native/src/widget/text_input.rs | 63 ++++++++++++++++---------------- native/src/widget/text_input/cursor.rs | 21 ++++++----- native/src/widget/text_input/value.rs | 9 +++++ native/src/widget/tooltip.rs | 6 +-- 26 files changed, 140 insertions(+), 113 deletions(-) (limited to 'native') diff --git a/native/src/clipboard.rs b/native/src/clipboard.rs index fdb769da..081b4004 100644 --- a/native/src/clipboard.rs +++ b/native/src/clipboard.rs @@ -1,3 +1,5 @@ +//! Access the clipboard. + /// A buffer for short-term storage and transfer within and between /// applications. pub trait Clipboard { @@ -7,3 +9,15 @@ pub trait Clipboard { /// Writes the given text contents to the [`Clipboard`]. fn write(&mut self, contents: String); } + +/// A null implementation of the [`Clipboard`] trait. +#[derive(Debug, Clone, Copy)] +pub struct Null; + +impl Clipboard for Null { + fn read(&self) -> Option { + None + } + + fn write(&mut self, _contents: String) {} +} diff --git a/native/src/element.rs b/native/src/element.rs index d6e9639a..5c84a388 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -223,17 +223,17 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec, renderer: &Renderer, - clipboard: Option<&dyn Clipboard>, + clipboard: &mut dyn Clipboard, + messages: &mut Vec, ) -> event::Status { self.widget.on_event( event, layout, cursor_position, - messages, renderer, clipboard, + messages, ) } @@ -311,9 +311,9 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec, renderer: &Renderer, - clipboard: Option<&dyn Clipboard>, + clipboard: &mut dyn Clipboard, + messages: &mut Vec, ) -> event::Status { let mut original_messages = Vec::new(); @@ -321,9 +321,9 @@ where event, layout, cursor_position, - &mut original_messages, renderer, clipboard, + &mut original_messages, ); original_messages @@ -401,17 +401,17 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec, renderer: &Renderer, - clipboard: Option<&dyn Clipboard>, + clipboard: &mut dyn Clipboard, + messages: &mut Vec, ) -> event::Status { self.element.widget.on_event( event, layout, cursor_position, - messages, renderer, clipboard, + messages, ) } diff --git a/native/src/lib.rs b/native/src/lib.rs index 0890785b..20bbb1d0 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -33,6 +33,7 @@ #![deny(unused_results)] #![forbid(unsafe_code)] #![forbid(rust_2018_idioms)] +pub mod clipboard; pub mod event; pub mod keyboard; pub mod layout; @@ -45,7 +46,6 @@ pub mod touch; pub mod widget; pub mod window; -mod clipboard; mod element; mod hasher; mod runtime; diff --git a/native/src/overlay.rs b/native/src/overlay.rs index ea8bb384..84145e7f 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -67,9 +67,9 @@ where _event: Event, _layout: Layout<'_>, _cursor_position: Point, - _messages: &mut Vec, _renderer: &Renderer, - _clipboard: Option<&dyn Clipboard>, + _clipboard: &mut dyn Clipboard, + _messages: &mut Vec, ) -> event::Status { event::Status::Ignored } diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index 0f44a781..e4819037 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -53,17 +53,17 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec, renderer: &Renderer, - clipboard: Option<&dyn Clipboard>, + clipboard: &mut dyn Clipboard, + messages: &mut Vec, ) -> event::Status { self.overlay.on_event( event, layout, cursor_position, - messages, renderer, clipboard, + messages, ) } @@ -117,9 +117,9 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec, renderer: &Renderer, - clipboard: Option<&dyn Clipboard>, + clipboard: &mut dyn Clipboard, + messages: &mut Vec, ) -> event::Status { let mut original_messages = Vec::new(); @@ -127,9 +127,9 @@ where event, layout, cursor_position, - &mut original_messages, renderer, clipboard, + &mut original_messages, ); original_messages diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 5ad1391f..afb17bd3 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -219,17 +219,17 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec, renderer: &Renderer, - clipboard: Option<&dyn Clipboard>, + clipboard: &mut dyn Clipboard, + messages: &mut Vec, ) -> event::Status { self.container.on_event( event.clone(), layout, cursor_position, - messages, renderer, clipboard, + messages, ) } @@ -320,9 +320,9 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - _messages: &mut Vec, renderer: &Renderer, - _clipboard: Option<&dyn Clipboard>, + _clipboard: &mut dyn Clipboard, + _messages: &mut Vec, ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { diff --git a/native/src/program/state.rs b/native/src/program/state.rs index e630890a..3f5f6069 100644 --- a/native/src/program/state.rs +++ b/native/src/program/state.rs @@ -91,8 +91,8 @@ where &mut self, bounds: Size, cursor_position: Point, - clipboard: Option<&dyn Clipboard>, renderer: &mut P::Renderer, + clipboard: &mut dyn Clipboard, debug: &mut Debug, ) -> Option> { let mut user_interface = build_user_interface( @@ -109,8 +109,8 @@ where let _ = user_interface.update( &self.queued_events, cursor_position, - clipboard, renderer, + clipboard, &mut messages, ); diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 7a64ac59..475faf8d 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -134,7 +134,7 @@ where /// completing [the previous example](#example): /// /// ```no_run - /// use iced_native::{UserInterface, Cache, Size, Point}; + /// use iced_native::{clipboard, UserInterface, Cache, Size, Point}; /// use iced_wgpu::Renderer; /// /// # mod iced_wgpu { @@ -157,6 +157,7 @@ where /// let mut renderer = Renderer::new(); /// let mut window_size = Size::new(1024.0, 768.0); /// let mut cursor_position = Point::default(); + /// let mut clipboard = clipboard::Null; /// /// // Initialize our event storage /// let mut events = Vec::new(); @@ -176,8 +177,8 @@ where /// let event_statuses = user_interface.update( /// &events, /// cursor_position, - /// None, /// &renderer, + /// &mut clipboard, /// &mut messages /// ); /// @@ -193,8 +194,8 @@ where &mut self, events: &[Event], cursor_position: Point, - clipboard: Option<&dyn Clipboard>, renderer: &Renderer, + clipboard: &mut dyn Clipboard, messages: &mut Vec, ) -> Vec { let (base_cursor, overlay_statuses) = if let Some(mut overlay) = @@ -215,9 +216,9 @@ where event, Layout::new(&layer.layout), cursor_position, - messages, renderer, clipboard, + messages, ) }) .collect(); @@ -246,9 +247,9 @@ where event, Layout::new(&self.base.layout), base_cursor, - messages, renderer, clipboard, + messages, ); event_status.merge(overlay_status) @@ -269,7 +270,7 @@ where /// [completing the last example](#example-1): /// /// ```no_run - /// use iced_native::{UserInterface, Cache, Size, Point}; + /// use iced_native::{clipboard, UserInterface, Cache, Size, Point}; /// use iced_wgpu::Renderer; /// /// # mod iced_wgpu { @@ -292,6 +293,7 @@ where /// let mut renderer = Renderer::new(); /// let mut window_size = Size::new(1024.0, 768.0); /// let mut cursor_position = Point::default(); + /// let mut clipboard = clipboard::Null; /// let mut events = Vec::new(); /// let mut messages = Vec::new(); /// @@ -309,8 +311,8 @@ where /// let event_statuses = user_interface.update( /// &events, /// cursor_position, - /// None, /// &renderer, + /// &mut clipboard, /// &mut messages /// ); /// diff --git a/native/src/widget.rs b/native/src/widget.rs index d5c353df..791c53a3 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -164,9 +164,9 @@ where _event: Event, _layout: Layout<'_>, _cursor_position: Point, - _messages: &mut Vec, _renderer: &Renderer, - _clipboard: Option<&dyn Clipboard>, + _clipboard: &mut dyn Clipboard, + _messages: &mut Vec, ) -> event::Status { event::Status::Ignored } diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index b8c14634..59d6e219 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -160,17 +160,17 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec, renderer: &Renderer, - clipboard: Option<&dyn Clipboard>, + clipboard: &mut dyn Clipboard, + messages: &mut Vec, ) -> event::Status { if let event::Status::Captured = self.content.on_event( event.clone(), layout.children().next().unwrap(), cursor_position, - messages, renderer, clipboard, + messages, ) { return event::Status::Captured; } diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 77a82fad..6ce2e973 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -150,9 +150,9 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec, _renderer: &Renderer, - _clipboard: Option<&dyn Clipboard>, + _clipboard: &mut dyn Clipboard, + messages: &mut Vec, ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index e0e88d31..d7f0365a 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -141,9 +141,9 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec, renderer: &Renderer, - clipboard: Option<&dyn Clipboard>, + clipboard: &mut dyn Clipboard, + messages: &mut Vec, ) -> event::Status { self.children .iter_mut() @@ -153,9 +153,9 @@ where event.clone(), layout, cursor_position, - messages, renderer, clipboard, + messages, ) }) .fold(event::Status::Ignored, event::Status::merge) diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 65764148..69fe699b 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -151,17 +151,17 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec, renderer: &Renderer, - clipboard: Option<&dyn Clipboard>, + clipboard: &mut dyn Clipboard, + messages: &mut Vec, ) -> event::Status { self.content.widget.on_event( event, layout.children().next().unwrap(), cursor_position, - messages, renderer, clipboard, + messages, ) } diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index 5f0dda4e..a006c0af 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -155,9 +155,9 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - _messages: &mut Vec, renderer: &Renderer, - _clipboard: Option<&dyn Clipboard>, + _clipboard: &mut dyn Clipboard, + _messages: &mut Vec, ) -> event::Status { let bounds = layout.bounds(); let is_mouse_over = bounds.contains(cursor_position); diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index c6fe4b60..9a3580e0 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -362,9 +362,9 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec, renderer: &Renderer, - clipboard: Option<&dyn Clipboard>, + clipboard: &mut dyn Clipboard, + messages: &mut Vec, ) -> event::Status { let mut event_status = event::Status::Ignored; @@ -461,9 +461,9 @@ where event.clone(), layout, cursor_position, - messages, renderer, clipboard, + messages, ) }) .fold(event_status, event::Status::merge) diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index 913cfe96..a5493fe2 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -143,9 +143,9 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec, renderer: &Renderer, - clipboard: Option<&dyn Clipboard>, + clipboard: &mut dyn Clipboard, + messages: &mut Vec, ) -> event::Status { let mut event_status = event::Status::Ignored; @@ -156,9 +156,9 @@ where event.clone(), children.next().unwrap(), cursor_position, - messages, renderer, clipboard, + messages, ); children.next().unwrap() @@ -170,9 +170,9 @@ where event, body_layout, cursor_position, - messages, renderer, clipboard, + messages, ); event_status.merge(body_status) diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index 2f9659e8..ff689ddb 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -199,9 +199,9 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec, renderer: &Renderer, - clipboard: Option<&dyn Clipboard>, + clipboard: &mut dyn Clipboard, + messages: &mut Vec, ) -> event::Status { if let Some(controls) = &mut self.controls { let mut children = layout.children(); @@ -215,9 +215,9 @@ where event, controls_layout, cursor_position, - messages, renderer, clipboard, + messages, ) } else { event::Status::Ignored diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 74f4508e..046d5779 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -210,9 +210,9 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec, _renderer: &Renderer, - _clipboard: Option<&dyn Clipboard>, + _clipboard: &mut dyn Clipboard, + messages: &mut Vec, ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 69952345..9482a9b1 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -156,9 +156,9 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec, _renderer: &Renderer, - _clipboard: Option<&dyn Clipboard>, + _clipboard: &mut dyn Clipboard, + messages: &mut Vec, ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index b71663bd..5634ab12 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -140,9 +140,9 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec, renderer: &Renderer, - clipboard: Option<&dyn Clipboard>, + clipboard: &mut dyn Clipboard, + messages: &mut Vec, ) -> event::Status { self.children .iter_mut() @@ -152,9 +152,9 @@ where event.clone(), layout, cursor_position, - messages, renderer, clipboard, + messages, ) }) .fold(event::Status::Ignored, event::Status::merge) diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 18cdf169..70ebebe2 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -162,9 +162,9 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec, renderer: &Renderer, - clipboard: Option<&dyn Clipboard>, + clipboard: &mut dyn Clipboard, + messages: &mut Vec, ) -> event::Status { let bounds = layout.bounds(); let is_mouse_over = bounds.contains(cursor_position); @@ -205,9 +205,9 @@ where event.clone(), content, cursor_position, - messages, renderer, clipboard, + messages, ) }; diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index 010c6e53..2a74d5a3 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -180,9 +180,9 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec, _renderer: &Renderer, - _clipboard: Option<&dyn Clipboard>, + _clipboard: &mut dyn Clipboard, + messages: &mut Vec, ) -> event::Status { let mut change = || { let bounds = layout.bounds(); diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 4c38b1a3..400cae2a 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -243,9 +243,9 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec, renderer: &Renderer, - clipboard: Option<&dyn Clipboard>, + clipboard: &mut dyn Clipboard, + messages: &mut Vec, ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) @@ -503,43 +503,42 @@ where } keyboard::KeyCode::V => { if self.state.keyboard_modifiers.is_command_pressed() { - if let Some(clipboard) = clipboard { - let content = match self.state.is_pasting.take() - { - Some(content) => content, - None => { - let content: String = clipboard - .read() - .unwrap_or(String::new()) - .chars() - .filter(|c| !c.is_control()) - .collect(); - - Value::new(&content) - } - }; - - let mut editor = Editor::new( - &mut self.value, - &mut self.state.cursor, - ); + let content = match self.state.is_pasting.take() { + Some(content) => content, + None => { + let content: String = clipboard + .read() + .unwrap_or(String::new()) + .chars() + .filter(|c| !c.is_control()) + .collect(); + + Value::new(&content) + } + }; + + let mut editor = Editor::new( + &mut self.value, + &mut self.state.cursor, + ); - editor.paste(content.clone()); + editor.paste(content.clone()); - let message = - (self.on_change)(editor.contents()); - messages.push(message); + let message = (self.on_change)(editor.contents()); + messages.push(message); - self.state.is_pasting = Some(content); - } + self.state.is_pasting = Some(content); } else { self.state.is_pasting = None; } } - keyboard::KeyCode::A => { - if self.state.keyboard_modifiers.is_command_pressed() { - self.state.cursor.select_all(&self.value); - } + keyboard::KeyCode::A + if self + .state + .keyboard_modifiers + .is_command_pressed() => + { + self.state.cursor.select_all(&self.value); } keyboard::KeyCode::Escape => { self.state.is_focused = false; diff --git a/native/src/widget/text_input/cursor.rs b/native/src/widget/text_input/cursor.rs index e630e293..1e7aee83 100644 --- a/native/src/widget/text_input/cursor.rs +++ b/native/src/widget/text_input/cursor.rs @@ -48,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); } @@ -161,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/value.rs b/native/src/widget/text_input/value.rs index 86be2790..2034cca4 100644 --- a/native/src/widget/text_input/value.rs +++ b/native/src/widget/text_input/value.rs @@ -73,6 +73,15 @@ 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`. pub fn until(&self, index: usize) -> Self { diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs index ab07868c..276afd41 100644 --- a/native/src/widget/tooltip.rs +++ b/native/src/widget/tooltip.rs @@ -120,17 +120,17 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec, renderer: &Renderer, - clipboard: Option<&dyn Clipboard>, + clipboard: &mut dyn Clipboard, + messages: &mut Vec, ) -> event::Status { self.content.widget.on_event( event, layout, cursor_position, - messages, renderer, clipboard, + messages, ) } -- cgit From 17dcfa8faf68afe3cbad1151f41eb35230ef83e1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 10 Mar 2021 01:59:20 +0100 Subject: Implement copy and cut behavior for `TextInput` --- native/src/widget/text_input.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'native') diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 400cae2a..de6032b7 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -501,6 +501,46 @@ where self.state.cursor.move_to(self.value.len()); } } + keyboard::KeyCode::C + if self + .state + .keyboard_modifiers + .is_command_pressed() => + { + 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 + .is_command_pressed() => + { + 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, + ); + + editor.delete(); + + let message = (self.on_change)(editor.contents()); + messages.push(message); + } keyboard::KeyCode::V => { if self.state.keyboard_modifiers.is_command_pressed() { let content = match self.state.is_pasting.take() { -- cgit From ae517b9fa033ba75df5fc6ce766698fab22504fa Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 11 Mar 2021 03:38:20 +0100 Subject: Add `clipboard` argument to `Application::update` --- native/src/program.rs | 11 +++++++++-- native/src/program/state.rs | 7 +++---- 2 files changed, 12 insertions(+), 6 deletions(-) (limited to 'native') diff --git a/native/src/program.rs b/native/src/program.rs index 9ee72703..066c29d8 100644 --- a/native/src/program.rs +++ b/native/src/program.rs @@ -1,5 +1,5 @@ //! Build interactive programs using The Elm Architecture. -use crate::{Command, Element, Renderer}; +use crate::{Clipboard, Command, Element, Renderer}; mod state; @@ -13,6 +13,9 @@ pub trait Program: Sized { /// The type of __messages__ your [`Program`] will produce. type Message: std::fmt::Debug + Send; + /// The type of [`Clipboard`] your [`Program`] will use. + type Clipboard: Clipboard; + /// Handles a __message__ and updates the state of the [`Program`]. /// /// This is where you define your __update logic__. All the __messages__, @@ -21,7 +24,11 @@ pub trait Program: Sized { /// /// Any [`Command`] returned will be executed immediately in the /// background by shells. - fn update(&mut self, message: Self::Message) -> Command; + fn update( + &mut self, + message: Self::Message, + clipboard: &mut Self::Clipboard, + ) -> Command; /// Returns the widgets to display in the [`Program`]. /// diff --git a/native/src/program/state.rs b/native/src/program/state.rs index 3f5f6069..fd1f2b52 100644 --- a/native/src/program/state.rs +++ b/native/src/program/state.rs @@ -1,6 +1,5 @@ use crate::{ - Cache, Clipboard, Command, Debug, Event, Point, Program, Renderer, Size, - UserInterface, + Cache, Command, Debug, Event, Point, Program, Renderer, Size, UserInterface, }; /// The execution state of a [`Program`]. It leverages caching, event @@ -92,7 +91,7 @@ where bounds: Size, cursor_position: Point, renderer: &mut P::Renderer, - clipboard: &mut dyn Clipboard, + clipboard: &mut P::Clipboard, debug: &mut Debug, ) -> Option> { let mut user_interface = build_user_interface( @@ -136,7 +135,7 @@ where debug.log_message(&message); debug.update_started(); - let command = self.program.update(message); + let command = self.program.update(message, clipboard); debug.update_finished(); command -- cgit From 57247106b95acb120381305cbe1ab4077b17651f Mon Sep 17 00:00:00 2001 From: Aldo Fregoso Date: Sat, 13 Mar 2021 14:50:59 -0600 Subject: Added `select_all` method to `TextInput`. --- native/src/widget/text_input.rs | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'native') diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index de6032b7..2a0913f3 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -792,6 +792,11 @@ impl State { 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 -- cgit From 0333a8daff6db989adc6035a4c09df171a86f6fe Mon Sep 17 00:00:00 2001 From: Nicolas Levy Date: Sun, 14 Mar 2021 23:39:01 +0100 Subject: Overwrite `overlay` method in Widget implementation for Button (#764) * Overwrite `overlay` method in Widget implementation for Button * Overwrite `overlay` method in Widget implementation for Button (cargo fmt) * Fix button overlay --- native/src/widget/button.rs | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'native') diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 59d6e219..99e98fd1 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -4,6 +4,7 @@ use crate::event::{self, Event}; use crate::layout; use crate::mouse; +use crate::overlay; use crate::touch; use crate::{ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget, @@ -240,6 +241,13 @@ where self.width.hash(state); self.content.hash_layout(state); } + + fn overlay( + &mut self, + layout: Layout<'_>, + ) -> Option> { + self.content.overlay(layout.children().next().unwrap()) + } } /// The renderer of a [`Button`]. -- cgit From 00de9d0c9ba20b313ffb459ed291ea2b85e53d32 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Mar 2021 21:33:57 +0200 Subject: Add `CloseRequested` variant to `window::Event` --- native/src/window/event.rs | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'native') diff --git a/native/src/window/event.rs b/native/src/window/event.rs index fc746781..3aa1ab0b 100644 --- a/native/src/window/event.rs +++ b/native/src/window/event.rs @@ -12,6 +12,12 @@ pub enum Event { height: u32, }, + /// The user has requested for the window to close. + /// + /// Usually, you will want to terminate the execution whenever this event + /// occurs. + CloseRequested, + /// A window was focused. Focused, -- cgit From 0864e63bde129b95261590b960efdc46c6d2d4d0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 31 Mar 2021 20:06:03 +0200 Subject: Bump versions :tada: --- native/Cargo.toml | 6 +++--- native/README.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'native') diff --git a/native/Cargo.toml b/native/Cargo.toml index 2c99638a..a3134ef4 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_native" -version = "0.3.0" +version = "0.4.0" authors = ["Héctor Ramón Jiménez "] edition = "2018" description = "A renderer-agnostic library for native GUIs" @@ -16,10 +16,10 @@ unicode-segmentation = "1.6" num-traits = "0.2" [dependencies.iced_core] -version = "0.3" +version = "0.4" path = "../core" [dependencies.iced_futures] -version = "0.2" +version = "0.3" path = "../futures" features = ["thread-pool"] diff --git a/native/README.md b/native/README.md index 6323dd4f..0d79690a 100644 --- a/native/README.md +++ b/native/README.md @@ -28,7 +28,7 @@ To achieve this, it introduces a bunch of reusable interfaces: Add `iced_native` as a dependency in your `Cargo.toml`: ```toml -iced_native = "0.3" +iced_native = "0.4" ``` __Iced moves fast and the `master` branch can contain breaking changes!__ If -- cgit From 983aa1b3665c6b546700767d21d73de72372ddad Mon Sep 17 00:00:00 2001 From: Dispersia Date: Mon, 12 Apr 2021 23:23:47 -0700 Subject: Run cargo fmt --- native/src/widget/pane_grid.rs | 9 +++++---- native/src/widget/text_input/cursor.rs | 4 ++-- native/src/widget/text_input/editor.rs | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) (limited to 'native') diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 44028f5e..3df7b156 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -209,10 +209,11 @@ where cursor_position: Point, messages: &mut Vec, ) { - let mut clicked_region = - self.elements.iter().zip(layout.children()).filter( - |(_, layout)| layout.bounds().contains(cursor_position), - ); + let mut clicked_region = self + .elements + .iter() + .zip(layout.children()) + .filter(|(_, layout)| layout.bounds().contains(cursor_position)); if let Some(((pane, content), layout)) = clicked_region.next() { if let Some(on_click) = &self.on_click { diff --git a/native/src/widget/text_input/cursor.rs b/native/src/widget/text_input/cursor.rs index 1e7aee83..4f3b159b 100644 --- a/native/src/widget/text_input/cursor.rs +++ b/native/src/widget/text_input/cursor.rs @@ -113,7 +113,7 @@ impl Cursor { State::Selection { start, end } if end > 0 => { self.select_range(start, end - 1) } - _ => (), + _ => {} } } @@ -125,7 +125,7 @@ impl Cursor { State::Selection { start, end } if end < value.len() => { self.select_range(start, end + 1) } - _ => (), + _ => {} } } 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); -- cgit From 77e6e111e0f655487076ce0e4605fd3b143e0320 Mon Sep 17 00:00:00 2001 From: 13r0ck Date: Mon, 10 May 2021 14:29:35 -0600 Subject: add scrolling to pick_lists --- native/src/widget/pick_list.rs | 68 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) (limited to 'native') diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 046d5779..eba15253 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -25,6 +25,7 @@ where on_selected: Box Message>, options: Cow<'a, [T]>, selected: Option, + on_change: Option, width: Length, padding: u16, text_size: Option, @@ -82,6 +83,7 @@ where on_selected: Box::new(on_selected), options: options.into(), selected, + on_change: None, width: Length::Shrink, text_size: None, padding: Renderer::DEFAULT_PADDING, @@ -114,6 +116,12 @@ where self } + /// Sets the message sent when [`PickList`] selection changes + pub fn on_change(mut self, msg: Message) -> Self { + self.on_change = Some(msg); + self + } + /// Sets the style of the [`PickList`]. pub fn style( mut self, @@ -247,6 +255,66 @@ where event_status } } + Event::Mouse(mouse::Event::WheelScrolled { delta }) + if layout.bounds().contains(cursor_position) + && !*self.is_open => + { + let y = match delta { + mouse::ScrollDelta::Lines { y, .. } + | mouse::ScrollDelta::Pixels { y, .. } => y, + }; + + if y.is_sign_negative() { + if let Some(selected) = self.selected.as_ref() { + let i = self + .options + .iter() + .position(|option| option == selected) + .unwrap_or(0) + + 1; + if i < self.options.len() { + messages.push((self.on_selected)( + self.options[i].clone(), + )); + if let Some(msg) = self.on_change.take() { + messages.push(msg) + } + } + } else { + messages + .push((self.on_selected)(self.options[0].clone())); + if let Some(msg) = self.on_change.take() { + messages.push(msg) + } + } + } else { + if let Some(selected) = self.selected.as_ref() { + let i = self + .options + .iter() + .position(|option| option == selected) + .unwrap_or(0); + if i != 0 { + messages.push((self.on_selected)( + self.options[i - 1].clone(), + )); + if let Some(msg) = self.on_change.take() { + messages.push(msg) + } + } + } else { + messages.push((self.on_selected)( + self.options[self.options.len() - 1].clone(), + )); + if let Some(msg) = self.on_change.take() { + messages.push(msg) + } + } + } + + return event::Status::Captured; + } + _ => event::Status::Ignored, } } -- cgit From 8f319d7c6fde1efb1e88a15ed19ce93aef3d26c4 Mon Sep 17 00:00:00 2001 From: 13r0ck Date: Mon, 10 May 2021 16:05:50 -0600 Subject: Improve performance using iters --- native/src/widget/pick_list.rs | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) (limited to 'native') diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index eba15253..f2f6ba2d 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -265,19 +265,11 @@ where }; if y.is_sign_negative() { + let mut options_iter = self.options.iter(); if let Some(selected) = self.selected.as_ref() { - let i = self - .options - .iter() - .position(|option| option == selected) - .unwrap_or(0) - + 1; - if i < self.options.len() { - messages.push((self.on_selected)( - self.options[i].clone(), - )); - if let Some(msg) = self.on_change.take() { - messages.push(msg) + if let Some(_) = options_iter.position(|o| o == selected) { + if let Some(prev_val) = options_iter.next() { + messages.push((self.on_selected)(prev_val.clone())); } } } else { @@ -288,18 +280,11 @@ where } } } else { + let mut options_iter = self.options.iter().rev(); if let Some(selected) = self.selected.as_ref() { - let i = self - .options - .iter() - .position(|option| option == selected) - .unwrap_or(0); - if i != 0 { - messages.push((self.on_selected)( - self.options[i - 1].clone(), - )); - if let Some(msg) = self.on_change.take() { - messages.push(msg) + if let Some(_) = options_iter.position(|o| o == selected) { + if let Some(next_val) = options_iter.next() { + messages.push((self.on_selected)(next_val.clone())); } } } else { -- cgit From 3a9ad8997039631ff633abe7333faad9a2f1c042 Mon Sep 17 00:00:00 2001 From: 13r0ck Date: Mon, 10 May 2021 16:06:39 -0600 Subject: cargo fmt --- native/src/widget/pick_list.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'native') diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index f2f6ba2d..fcb15503 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -267,24 +267,30 @@ where if y.is_sign_negative() { let mut options_iter = self.options.iter(); if let Some(selected) = self.selected.as_ref() { - if let Some(_) = options_iter.position(|o| o == selected) { + if let Some(_) = + options_iter.position(|o| o == selected) + { if let Some(prev_val) = options_iter.next() { - messages.push((self.on_selected)(prev_val.clone())); + messages + .push((self.on_selected)(prev_val.clone())); } } } else { messages .push((self.on_selected)(self.options[0].clone())); - if let Some(msg) = self.on_change.take() { - messages.push(msg) - } + if let Some(msg) = self.on_change.take() { + messages.push(msg) + } } } else { let mut options_iter = self.options.iter().rev(); if let Some(selected) = self.selected.as_ref() { - if let Some(_) = options_iter.position(|o| o == selected) { + if let Some(_) = + options_iter.position(|o| o == selected) + { if let Some(next_val) = options_iter.next() { - messages.push((self.on_selected)(next_val.clone())); + messages + .push((self.on_selected)(next_val.clone())); } } } else { -- cgit From 40d21d23659bdb9fc6a6166208adb351e188846b Mon Sep 17 00:00:00 2001 From: zdevwu Date: Mon, 17 May 2021 14:22:55 +0100 Subject: Added text color and font options for native radio and checkbox (#831) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * text color and font options to radio * code formatting * code formatting * code formatting * Added text_color for native checkbox * Removed clone as color has Copy * Fix code formatting with `cargo fmt` Co-authored-by: Héctor Ramón --- native/src/widget/checkbox.rs | 14 +++++++++++--- native/src/widget/radio.rs | 26 +++++++++++++++++++++----- 2 files changed, 32 insertions(+), 8 deletions(-) (limited to 'native') diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 6ce2e973..0f21c873 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -8,8 +8,8 @@ use crate::row; use crate::text; use crate::touch; use crate::{ - Align, Clipboard, Element, 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. @@ -39,6 +39,7 @@ pub struct Checkbox { spacing: u16, text_size: Option, font: Renderer::Font, + text_color: Option, style: Renderer::Style, } @@ -66,6 +67,7 @@ impl spacing: Renderer::DEFAULT_SPACING, text_size: None, font: Renderer::Font::default(), + text_color: None, style: Renderer::Style::default(), } } @@ -102,6 +104,12 @@ impl 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`]. pub fn style(mut self, style: impl Into) -> Self { self.style = style.into(); @@ -193,7 +201,7 @@ where &self.label, self.text_size.unwrap_or(renderer.default_size()), self.font, - None, + self.text_color, HorizontalAlignment::Left, VerticalAlignment::Center, ); diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 9482a9b1..dee82d1f 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -1,17 +1,17 @@ //! Create choices using radio buttons. +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, Color}; use crate::{ Align, Clipboard, Element, Hasher, HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, VerticalAlignment, Widget, }; -use std::hash::Hash; - /// A circular button representing a choice. /// /// # Example @@ -47,6 +47,8 @@ pub struct Radio { size: u16, spacing: u16, text_size: Option, + text_color: Option, + font: Renderer::Font, style: Renderer::Style, } @@ -81,6 +83,8 @@ where size: ::DEFAULT_SIZE, spacing: Renderer::DEFAULT_SPACING, //15 text_size: None, + text_color: None, + font: Default::default(), style: Renderer::Style::default(), } } @@ -109,6 +113,18 @@ where 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. pub fn style(mut self, style: impl Into) -> Self { self.style = style.into(); @@ -196,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, ); -- cgit From fe2392e9736477a047f605454c6892ed72378032 Mon Sep 17 00:00:00 2001 From: chiheisen Date: Mon, 17 May 2021 22:01:43 +0200 Subject: viewer: Don't calculate viewport on Fill|Portion Currently image::viewer will not expand it's image into empty space even when Length::Fill or Length::FillPortion are used. Only calculate viewport when viewer cannot expand. --- native/src/widget/image/viewer.rs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) (limited to 'native') diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index a006c0af..405daf00 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -132,19 +132,30 @@ where ) -> layout::Node { let (width, height) = renderer.dimensions(&self.handle); - let aspect_ratio = width as f32 / height as f32; - let mut size = limits .width(self.width) .height(self.height) .resolve(Size::new(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; + let expansion_size = if height > width { + self.width } else { - size.height = height as f32 * size.width / width as f32; + 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) -- cgit From 59f3896392f2c02844f0ed35046035ce7430872e Mon Sep 17 00:00:00 2001 From: chiheisen Date: Tue, 18 May 2021 12:37:23 +0200 Subject: fix pick_list layouting not respecting fonts --- native/src/widget/pick_list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'native') diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 046d5779..b17d93a3 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -163,7 +163,7 @@ where let (width, _) = renderer.measure( &label, text_size, - Renderer::Font::default(), + self.font, Size::new(f32::INFINITY, f32::INFINITY), ); -- cgit From 8b7452a55def8620f2c91df40d3882c449f85420 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Wed, 19 May 2021 16:26:04 +0700 Subject: Fix formatting with `cargo fmt` --- native/src/widget/pane_grid.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'native') diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 3df7b156..44028f5e 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -209,11 +209,10 @@ where cursor_position: Point, messages: &mut Vec, ) { - let mut clicked_region = self - .elements - .iter() - .zip(layout.children()) - .filter(|(_, layout)| layout.bounds().contains(cursor_position)); + let mut clicked_region = + self.elements.iter().zip(layout.children()).filter( + |(_, layout)| layout.bounds().contains(cursor_position), + ); if let Some(((pane, content), layout)) = clicked_region.next() { if let Some(on_click) = &self.on_click { -- cgit From d4c5f3ee950262c578c7b9b2a4aab60d3c5edaed Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Mon, 24 May 2021 16:37:47 -0500 Subject: Enable event handling within the title elements Shrink the pick area to avoid both the controls and the title elements. Handle events and merge title area event status with control events. --- native/src/widget/pane_grid/title_bar.rs | 33 ++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) (limited to 'native') diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index a1e5107e..8e42ce38 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -129,15 +129,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 @@ -205,16 +206,17 @@ where clipboard: &mut dyn Clipboard, messages: &mut Vec, ) -> event::Status { - if let Some(controls) = &mut self.controls { - let mut children = layout.children(); - let padded = children.next().unwrap(); + 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, renderer, @@ -223,6 +225,17 @@ where ) } else { event::Status::Ignored - } + }; + + let title_status = self.content.on_event( + event, + title_layout, + cursor_position, + renderer, + clipboard, + messages, + ); + + control_status.merge(title_status) } } -- cgit From fbfb28b8d46a56de206cfd84c978f1902effc4f3 Mon Sep 17 00:00:00 2001 From: 13r0ck Date: Tue, 25 May 2021 08:23:04 -0600 Subject: Remove redundant 'on_change' --- native/src/widget/pick_list.rs | 14 -------------- 1 file changed, 14 deletions(-) (limited to 'native') diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index fcb15503..71c167a6 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -25,7 +25,6 @@ where on_selected: Box Message>, options: Cow<'a, [T]>, selected: Option, - on_change: Option, width: Length, padding: u16, text_size: Option, @@ -83,7 +82,6 @@ where on_selected: Box::new(on_selected), options: options.into(), selected, - on_change: None, width: Length::Shrink, text_size: None, padding: Renderer::DEFAULT_PADDING, @@ -116,12 +114,6 @@ where self } - /// Sets the message sent when [`PickList`] selection changes - pub fn on_change(mut self, msg: Message) -> Self { - self.on_change = Some(msg); - self - } - /// Sets the style of the [`PickList`]. pub fn style( mut self, @@ -278,9 +270,6 @@ where } else { messages .push((self.on_selected)(self.options[0].clone())); - if let Some(msg) = self.on_change.take() { - messages.push(msg) - } } } else { let mut options_iter = self.options.iter().rev(); @@ -297,9 +286,6 @@ where messages.push((self.on_selected)( self.options[self.options.len() - 1].clone(), )); - if let Some(msg) = self.on_change.take() { - messages.push(msg) - } } } -- cgit From fe0a27c56d9d75fb521e69352259f1d737402a20 Mon Sep 17 00:00:00 2001 From: Ben LeFevre Date: Mon, 23 Nov 2020 17:19:21 +0000 Subject: Add support for asymmetrical padding --- native/src/layout/flex.rs | 12 +++++++----- native/src/layout/limits.rs | 9 ++++++--- native/src/lib.rs | 4 ++-- native/src/overlay/menu.rs | 32 ++++++++++++++++++++------------ native/src/renderer/null.rs | 4 ++-- native/src/widget/button.rs | 28 ++++++++++++++++++---------- native/src/widget/column.rs | 20 +++++++++++++------- native/src/widget/container.rs | 29 ++++++++++++++++++----------- native/src/widget/pane_grid/title_bar.rs | 29 +++++++++++++++++++---------- native/src/widget/pick_list.rs | 26 ++++++++++++++++---------- native/src/widget/row.rs | 20 +++++++++++++------- native/src/widget/scrollable.rs | 13 +++++++++---- native/src/widget/text_input.rs | 28 ++++++++++++++++++---------- 13 files changed, 161 insertions(+), 93 deletions(-) (limited to 'native') diff --git a/native/src/layout/flex.rs b/native/src/layout/flex.rs index 4f6523fb..3d3ff82c 100644 --- a/native/src/layout/flex.rs +++ b/native/src/layout/flex.rs @@ -16,9 +16,10 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + use crate::{ layout::{Limits, Node}, - Align, Element, Point, Size, + Align, Element, Padding, Point, Size, }; /// The main axis of a flex layout. @@ -62,7 +63,7 @@ pub fn resolve( axis: Axis, renderer: &Renderer, limits: &Limits, - padding: f32, + padding: Padding, spacing: f32, align_items: Align, items: &[Element<'_, Message, Renderer>], @@ -141,14 +142,15 @@ where } } - let mut main = padding; + let pad = axis.pack(padding.left as f32, padding.top as f32); + let mut main = pad.0; for (i, node) in nodes.iter_mut().enumerate() { if i > 0 { main += spacing; } - let (x, y) = axis.pack(main, padding); + let (x, y) = axis.pack(main, pad.1); node.move_to(Point::new(x, y)); @@ -166,7 +168,7 @@ where main += axis.main(size); } - let (width, height) = axis.pack(main - padding, cross); + let (width, height) = axis.pack(main - pad.0, cross); let size = limits.resolve(Size::new(width, height)); Node::with_children(size.pad(padding), nodes) diff --git a/native/src/layout/limits.rs b/native/src/layout/limits.rs index a7bb5c9c..0057e3ba 100644 --- a/native/src/layout/limits.rs +++ b/native/src/layout/limits.rs @@ -1,4 +1,4 @@ -use crate::{Length, Size}; +use crate::{Length, Padding, Size}; /// A set of size constraints for layouting. #[derive(Debug, Clone, Copy)] @@ -117,8 +117,11 @@ impl Limits { } /// Shrinks the current [`Limits`] to account for the given padding. - pub fn pad(&self, padding: f32) -> Limits { - self.shrink(Size::new(padding * 2.0, padding * 2.0)) + pub fn pad(&self, padding: Padding) -> Limits { + self.shrink(Size::new( + (padding.left + padding.right) as f32, + (padding.top + padding.bottom) as f32, + )) } /// Shrinks the current [`Limits`] by the given [`Size`]. diff --git a/native/src/lib.rs b/native/src/lib.rs index 20bbb1d0..cd214e36 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -61,8 +61,8 @@ mod debug; mod debug; pub use iced_core::{ - Align, Background, Color, Font, HorizontalAlignment, Length, Point, - Rectangle, Size, Vector, VerticalAlignment, + Align, Background, Color, Font, HorizontalAlignment, Length, Padding, + Point, Rectangle, Size, Vector, VerticalAlignment, }; pub use iced_futures::{executor, futures, Command}; diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index afb17bd3..3c4062fd 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -8,8 +8,8 @@ use crate::scrollable; use crate::text; use crate::touch; use crate::{ - Clipboard, Container, Element, Hasher, Layout, Length, Point, Rectangle, - Scrollable, Size, Vector, Widget, + Clipboard, Container, Element, Hasher, Layout, Length, Padding, Point, + Rectangle, Scrollable, Size, Vector, Widget, }; /// A list of selectable options. @@ -20,7 +20,7 @@ pub struct Menu<'a, T, Renderer: self::Renderer> { hovered_option: &'a mut Option, last_selection: &'a mut Option, width: u16, - padding: u16, + padding: Padding, text_size: Option, font: Renderer::Font, style: ::Style, @@ -45,7 +45,7 @@ where hovered_option, last_selection, width: 0, - padding: 0, + padding: Padding::ZERO, text_size: None, font: Default::default(), style: Default::default(), @@ -58,9 +58,14 @@ where self } - /// Sets the padding of the [`Menu`]. - pub fn padding(mut self, padding: u16) -> Self { - self.padding = padding; + /// Sets the [`Padding`] of the [`Menu`]. + ///```ignore + /// Menu::new(/*...*/).padding(20); // 20px on all sides + /// Menu::new(/*...*/).padding([10, 20]); // top/bottom, left/right + /// Menu::new(/*...*/).padding([5, 10, 15, 20]); // top, right, bottom, left + /// ``` + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); self } @@ -261,7 +266,7 @@ struct List<'a, T, Renderer: self::Renderer> { options: &'a [T], hovered_option: &'a mut Option, last_selection: &'a mut Option, - padding: u16, + padding: Padding, text_size: Option, font: Renderer::Font, style: ::Style, @@ -294,7 +299,7 @@ where let size = { let intrinsic = Size::new( 0.0, - f32::from(text_size + self.padding * 2) + f32::from(text_size + self.padding.top + self.padding.bottom) * self.options.len() as f32, ); @@ -359,8 +364,11 @@ where *self.hovered_option = Some( ((cursor_position.y - bounds.y) - / f32::from(text_size + self.padding * 2)) - as usize, + / f32::from( + text_size + + self.padding.top + + self.padding.bottom, + )) as usize, ); if let Some(index) = *self.hovered_option { @@ -430,7 +438,7 @@ pub trait Renderer: viewport: &Rectangle, options: &[T], hovered_option: Option, - padding: u16, + padding: Padding, text_size: u16, font: Self::Font, style: &::Style, diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs index 9e91d29f..28746585 100644 --- a/native/src/renderer/null.rs +++ b/native/src/renderer/null.rs @@ -1,7 +1,7 @@ use crate::{ button, checkbox, column, container, pane_grid, progress_bar, radio, row, scrollable, slider, text, text_input, Color, Element, Font, - HorizontalAlignment, Layout, Point, Rectangle, Renderer, Size, + HorizontalAlignment, Layout, Padding, Point, Rectangle, Renderer, Size, VerticalAlignment, }; @@ -145,7 +145,7 @@ impl text_input::Renderer for Null { } impl button::Renderer for Null { - const DEFAULT_PADDING: u16 = 0; + const DEFAULT_PADDING: Padding = Padding::ZERO; type Style = (); diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 99e98fd1..3ad5fa62 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -7,7 +7,8 @@ use crate::mouse; use crate::overlay; use crate::touch; use crate::{ - Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget, + Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle, + Widget, }; use std::hash::Hash; @@ -37,7 +38,7 @@ pub struct Button<'a, Message, Renderer: self::Renderer> { height: Length, min_width: u32, min_height: u32, - padding: u16, + padding: Padding, style: Renderer::Style, } @@ -89,9 +90,14 @@ where self } - /// Sets the padding of the [`Button`]. - pub fn padding(mut self, padding: u16) -> Self { - self.padding = padding; + /// Sets the [`Padding`] of the [`Button`]. + ///```ignore + /// Button::new(/*...*/).padding(20); // 20px on all sides + /// Button::new(/*...*/).padding([10, 20]); // top/bottom, left/right + /// Button::new(/*...*/).padding([5, 10, 15, 20]); // top, right, bottom, left + /// ``` + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); self } @@ -140,18 +146,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]) } @@ -258,7 +266,7 @@ where /// [renderer]: crate::renderer pub trait Renderer: crate::Renderer + Sized { /// The default padding of a [`Button`]. - const DEFAULT_PADDING: u16; + const DEFAULT_PADDING: Padding; /// The style supported by this renderer. type Style: Default; diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index d7f0365a..9f25f918 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -5,7 +5,8 @@ use crate::event::{self, Event}; use crate::layout; use crate::overlay; use crate::{ - Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget, + Align, Clipboard, Element, Hasher, Layout, Length, Padding, Point, + Rectangle, Widget, }; use std::u32; @@ -14,7 +15,7 @@ use std::u32; #[allow(missing_debug_implementations)] pub struct Column<'a, Message, Renderer> { spacing: u16, - padding: u16, + padding: Padding, width: Length, height: Length, max_width: u32, @@ -35,7 +36,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { ) -> Self { Column { spacing: 0, - padding: 0, + padding: Padding::ZERO, width: Length::Shrink, height: Length::Shrink, max_width: u32::MAX, @@ -55,9 +56,14 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { self } - /// Sets the padding of the [`Column`]. - pub fn padding(mut self, units: u16) -> Self { - self.padding = units; + /// Sets the [`Padding`] of the [`Column`]. + ///```ignore + /// Column::new(/*...*/).padding(20); // 20px on all sides + /// Column::new(/*...*/).padding([10, 20]); // top/bottom, left/right + /// Column::new(/*...*/).padding([5, 10, 15, 20]); // top, right, bottom, left + /// ``` + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); self } @@ -129,7 +135,7 @@ where layout::flex::Axis::Vertical, renderer, &limits, - self.padding as f32, + self.padding, self.spacing as f32, self.align_items, &self.children, diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 69fe699b..cf5cb3dc 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -5,7 +5,8 @@ use crate::event::{self, Event}; use crate::layout; use crate::overlay; use crate::{ - Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget, + Align, Clipboard, Element, Hasher, Layout, Length, Padding, Point, + Rectangle, Widget, }; use std::u32; @@ -15,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, @@ -36,7 +37,7 @@ where T: Into>, { Container { - padding: 0, + padding: Padding::ZERO, width: Length::Shrink, height: Length::Shrink, max_width: u32::MAX, @@ -48,9 +49,14 @@ where } } - /// Sets the padding of the [`Container`]. - pub fn padding(mut self, units: u16) -> Self { - self.padding = units; + /// Sets the [`Padding`] of the [`Container`]. + ///```ignore + /// Container::new(/*...*/).padding(20); // 20px on all sides + /// Container::new(/*...*/).padding([10, 20]); // top/bottom, left/right + /// Container::new(/*...*/).padding([5, 10, 15, 20]); // top, right, bottom, left + /// ``` + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); self } @@ -127,23 +133,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( diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index 8e42ce38..8f32992a 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -2,7 +2,9 @@ use crate::container; use crate::event::{self, Event}; use crate::layout; use crate::pane_grid; -use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size}; +use crate::{ + Clipboard, Element, Hasher, Layout, Padding, Point, Rectangle, Size, +}; /// The title bar of a [`Pane`]. /// @@ -11,7 +13,7 @@ use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size}; pub struct TitleBar<'a, Message, Renderer: pane_grid::Renderer> { content: Element<'a, Message, Renderer>, controls: Option>, - padding: u16, + padding: Padding, always_show_controls: bool, style: ::Style, } @@ -28,7 +30,7 @@ where Self { content: content.into(), controls: None, - padding: 0, + padding: Padding::ZERO, always_show_controls: false, style: Default::default(), } @@ -43,9 +45,14 @@ where self } - /// Sets the padding of the [`TitleBar`]. - pub fn padding(mut self, units: u16) -> Self { - self.padding = units; + /// Sets the [`Padding`] of the [`TitleBar`]. + ///```ignore + /// TitleBar::new(/*...*/).padding(20); // 20px on all sides + /// TitleBar::new(/*...*/).padding([10, 20]); // top/bottom, left/right + /// TitleBar::new(/*...*/).padding([5, 10, 15, 20]); // top, right, bottom, left + /// ``` + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); self } @@ -161,8 +168,7 @@ 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_layout = self @@ -192,9 +198,12 @@ where ) }; - 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( diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index b17d93a3..15fe66b2 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -8,7 +8,8 @@ use crate::scrollable; use crate::text; use crate::touch; use crate::{ - Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, + Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle, + Size, Widget, }; use std::borrow::Cow; @@ -26,7 +27,7 @@ where options: Cow<'a, [T]>, selected: Option, width: Length, - padding: u16, + padding: Padding, text_size: Option, font: Renderer::Font, style: ::Style, @@ -96,9 +97,14 @@ where self } - /// Sets the padding of the [`PickList`]. - pub fn padding(mut self, padding: u16) -> Self { - self.padding = padding; + /// Sets the [`Padding`] of the [`PickList`]. + ///```ignore + /// PickList::new(/*...*/).padding(20); // 20px on all sides + /// PickList::new(/*...*/).padding([10, 20]); // top/bottom, left/right + /// PickList::new(/*...*/).padding([5, 10, 15, 20]); // top, right, bottom, left + /// ``` + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); self } @@ -150,7 +156,7 @@ 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()); @@ -179,11 +185,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) @@ -308,7 +314,7 @@ where /// [renderer]: crate::renderer pub trait Renderer: text::Renderer + menu::Renderer { /// The default padding of a [`PickList`]. - const DEFAULT_PADDING: u16; + const DEFAULT_PADDING: Padding; /// The [`PickList`] style supported by this renderer. type Style: Default; @@ -324,7 +330,7 @@ pub trait Renderer: text::Renderer + menu::Renderer { bounds: Rectangle, cursor_position: Point, selected: Option, - padding: u16, + padding: Padding, text_size: u16, font: Self::Font, style: &::Style, diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index 5634ab12..9aa059c3 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -3,7 +3,8 @@ use crate::event::{self, Event}; use crate::layout; use crate::overlay; use crate::{ - Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget, + Align, Clipboard, Element, Hasher, Layout, Length, Padding, Point, + Rectangle, Widget, }; use std::hash::Hash; @@ -13,7 +14,7 @@ use std::u32; #[allow(missing_debug_implementations)] pub struct Row<'a, Message, Renderer> { spacing: u16, - padding: u16, + padding: Padding, width: Length, height: Length, max_width: u32, @@ -34,7 +35,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { ) -> Self { Row { spacing: 0, - padding: 0, + padding: Padding::ZERO, width: Length::Shrink, height: Length::Shrink, max_width: u32::MAX, @@ -54,9 +55,14 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { self } - /// Sets the padding of the [`Row`]. - pub fn padding(mut self, units: u16) -> Self { - self.padding = units; + /// Sets the [`Padding`] of the [`Row`]. + ///```ignore + /// Row::new(/*...*/).padding(20); // 20px on all sides + /// Row::new(/*...*/).padding([10, 20]); // top/bottom, left/right + /// Row::new(/*...*/).padding([5, 10, 15, 20]); // top, right, bottom, left + /// ``` + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); self } @@ -128,7 +134,7 @@ where layout::flex::Axis::Horizontal, renderer, &limits, - self.padding as f32, + self.padding, self.spacing as f32, self.align_items, &self.children, diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 70ebebe2..2b699e3b 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -6,7 +6,7 @@ use crate::mouse; use crate::overlay; use crate::touch; use crate::{ - Align, Clipboard, Column, Element, Hasher, Layout, Length, Point, + Align, Clipboard, Column, Element, Hasher, Layout, Length, Padding, Point, Rectangle, Size, Vector, Widget, }; @@ -51,9 +51,14 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> { self } - /// Sets the padding of the [`Scrollable`]. - pub fn padding(mut self, units: u16) -> Self { - self.content = self.content.padding(units); + /// Sets the [`Padding`] of the [`Scrollable`]. + ///```ignore + /// Scrollable::new(/*...*/).padding(20); // 20px on all sides + /// Scrollable::new(/*...*/).padding([10, 20]); // top/bottom, left/right + /// Scrollable::new(/*...*/).padding([5, 10, 15, 20]); // top, right, bottom, left + /// ``` + pub fn padding>(mut self, padding: P) -> Self { + self.content = self.content.padding(padding); self } diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index de6032b7..197f1599 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -18,7 +18,8 @@ use crate::mouse::{self, click}; use crate::text; use crate::touch; use crate::{ - Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, + Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle, + Size, Widget, }; use std::u32; @@ -56,7 +57,7 @@ pub struct TextInput<'a, Message, Renderer: self::Renderer> { font: Renderer::Font, width: Length, max_width: u32, - padding: u16, + padding: Padding, size: Option, on_change: Box Message>, on_submit: Option, @@ -92,7 +93,7 @@ where 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, @@ -126,9 +127,14 @@ where self } - /// Sets the padding of the [`TextInput`]. - pub fn padding(mut self, units: u16) -> Self { - self.padding = units; + /// Sets the [`Padding`] of the [`TextInput`]. + ///```ignore + /// TextInput::new(/*...*/).padding(20); // 20px on all sides + /// TextInput::new(/*...*/).padding([10, 20]); // top/bottom, left/right + /// TextInput::new(/*...*/).padding([5, 10, 15, 20]); // top, right, bottom, left + /// ``` + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); self } @@ -223,19 +229,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( -- cgit From 92361ef07d69cf9418289be131af53952915c423 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Tue, 1 Jun 2021 19:13:34 +0700 Subject: Fix `overlay::Menu` implementation --- native/src/overlay/menu.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'native') diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 3c4062fd..d4375a1d 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -350,7 +350,7 @@ where *self.hovered_option = Some( ((cursor_position.y - bounds.y) - / f32::from(text_size + self.padding * 2)) + / f32::from(text_size + self.padding.vertical())) as usize, ); } -- cgit From b94cd7a2a83d81769d31f6379539089ce68cbdcd Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Tue, 1 Jun 2021 19:21:43 +0700 Subject: Use `Padding::horizontal` and `Padding::vertical` helpers --- native/src/layout/limits.rs | 4 ++-- native/src/overlay/menu.rs | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) (limited to 'native') diff --git a/native/src/layout/limits.rs b/native/src/layout/limits.rs index 0057e3ba..6d5f6563 100644 --- a/native/src/layout/limits.rs +++ b/native/src/layout/limits.rs @@ -119,8 +119,8 @@ impl Limits { /// Shrinks the current [`Limits`] to account for the given padding. pub fn pad(&self, padding: Padding) -> Limits { self.shrink(Size::new( - (padding.left + padding.right) as f32, - (padding.top + padding.bottom) as f32, + padding.horizontal() as f32, + padding.vertical() as f32, )) } diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index d4375a1d..b5ed07c7 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -299,7 +299,7 @@ where let size = { let intrinsic = Size::new( 0.0, - f32::from(text_size + self.padding.top + self.padding.bottom) + f32::from(text_size + self.padding.vertical()) * self.options.len() as f32, ); @@ -364,11 +364,8 @@ where *self.hovered_option = Some( ((cursor_position.y - bounds.y) - / f32::from( - text_size - + self.padding.top - + self.padding.bottom, - )) as usize, + / f32::from(text_size + self.padding.vertical())) + as usize, ); if let Some(index) = *self.hovered_option { -- cgit From 8a3b71df8b619571ce0a972826cb5a3987b66b3d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Tue, 1 Jun 2021 19:45:47 +0700 Subject: Replace ignored doc-tests with additional documentation for `Padding` --- native/src/overlay/menu.rs | 5 ----- native/src/widget/button.rs | 5 ----- native/src/widget/column.rs | 5 ----- native/src/widget/container.rs | 5 ----- native/src/widget/pane_grid/title_bar.rs | 5 ----- native/src/widget/pick_list.rs | 5 ----- native/src/widget/row.rs | 5 ----- native/src/widget/scrollable.rs | 5 ----- native/src/widget/text_input.rs | 5 ----- 9 files changed, 45 deletions(-) (limited to 'native') diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index b5ed07c7..f62dcb46 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -59,11 +59,6 @@ where } /// Sets the [`Padding`] of the [`Menu`]. - ///```ignore - /// Menu::new(/*...*/).padding(20); // 20px on all sides - /// Menu::new(/*...*/).padding([10, 20]); // top/bottom, left/right - /// Menu::new(/*...*/).padding([5, 10, 15, 20]); // top, right, bottom, left - /// ``` pub fn padding>(mut self, padding: P) -> Self { self.padding = padding.into(); self diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 3ad5fa62..f61c22d0 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -91,11 +91,6 @@ where } /// Sets the [`Padding`] of the [`Button`]. - ///```ignore - /// Button::new(/*...*/).padding(20); // 20px on all sides - /// Button::new(/*...*/).padding([10, 20]); // top/bottom, left/right - /// Button::new(/*...*/).padding([5, 10, 15, 20]); // top, right, bottom, left - /// ``` pub fn padding>(mut self, padding: P) -> Self { self.padding = padding.into(); self diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 9f25f918..52a2e80c 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -57,11 +57,6 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { } /// Sets the [`Padding`] of the [`Column`]. - ///```ignore - /// Column::new(/*...*/).padding(20); // 20px on all sides - /// Column::new(/*...*/).padding([10, 20]); // top/bottom, left/right - /// Column::new(/*...*/).padding([5, 10, 15, 20]); // top, right, bottom, left - /// ``` pub fn padding>(mut self, padding: P) -> Self { self.padding = padding.into(); self diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index cf5cb3dc..69aee64d 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -50,11 +50,6 @@ where } /// Sets the [`Padding`] of the [`Container`]. - ///```ignore - /// Container::new(/*...*/).padding(20); // 20px on all sides - /// Container::new(/*...*/).padding([10, 20]); // top/bottom, left/right - /// Container::new(/*...*/).padding([5, 10, 15, 20]); // top, right, bottom, left - /// ``` pub fn padding>(mut self, padding: P) -> Self { self.padding = padding.into(); self diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index 8f32992a..d9d85dbb 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -46,11 +46,6 @@ where } /// Sets the [`Padding`] of the [`TitleBar`]. - ///```ignore - /// TitleBar::new(/*...*/).padding(20); // 20px on all sides - /// TitleBar::new(/*...*/).padding([10, 20]); // top/bottom, left/right - /// TitleBar::new(/*...*/).padding([5, 10, 15, 20]); // top, right, bottom, left - /// ``` pub fn padding>(mut self, padding: P) -> Self { self.padding = padding.into(); self diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 15fe66b2..92c183f3 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -98,11 +98,6 @@ where } /// Sets the [`Padding`] of the [`PickList`]. - ///```ignore - /// PickList::new(/*...*/).padding(20); // 20px on all sides - /// PickList::new(/*...*/).padding([10, 20]); // top/bottom, left/right - /// PickList::new(/*...*/).padding([5, 10, 15, 20]); // top, right, bottom, left - /// ``` pub fn padding>(mut self, padding: P) -> Self { self.padding = padding.into(); self diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index 9aa059c3..9ebc9145 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -56,11 +56,6 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { } /// Sets the [`Padding`] of the [`Row`]. - ///```ignore - /// Row::new(/*...*/).padding(20); // 20px on all sides - /// Row::new(/*...*/).padding([10, 20]); // top/bottom, left/right - /// Row::new(/*...*/).padding([5, 10, 15, 20]); // top, right, bottom, left - /// ``` pub fn padding>(mut self, padding: P) -> Self { self.padding = padding.into(); self diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 2b699e3b..7c4ea16c 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -52,11 +52,6 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> { } /// Sets the [`Padding`] of the [`Scrollable`]. - ///```ignore - /// Scrollable::new(/*...*/).padding(20); // 20px on all sides - /// Scrollable::new(/*...*/).padding([10, 20]); // top/bottom, left/right - /// Scrollable::new(/*...*/).padding([5, 10, 15, 20]); // top, right, bottom, left - /// ``` pub fn padding>(mut self, padding: P) -> Self { self.content = self.content.padding(padding); self diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 197f1599..20117fa0 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -128,11 +128,6 @@ where } /// Sets the [`Padding`] of the [`TextInput`]. - ///```ignore - /// TextInput::new(/*...*/).padding(20); // 20px on all sides - /// TextInput::new(/*...*/).padding([10, 20]); // top/bottom, left/right - /// TextInput::new(/*...*/).padding([5, 10, 15, 20]); // top, right, bottom, left - /// ``` pub fn padding>(mut self, padding: P) -> Self { self.padding = padding.into(); self -- cgit From 52a185fbab728b85cf414d4997567f52ebc66205 Mon Sep 17 00:00:00 2001 From: Kaiden42 Date: Sat, 19 Sep 2020 18:44:27 +0200 Subject: Implement `Toggler` widget for iced_native --- native/src/renderer/null.rs | 18 ++- native/src/widget.rs | 3 + native/src/widget/toggler.rs | 262 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 native/src/widget/toggler.rs (limited to 'native') diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs index 28746585..89bb9433 100644 --- a/native/src/renderer/null.rs +++ b/native/src/renderer/null.rs @@ -1,6 +1,6 @@ use crate::{ button, checkbox, column, container, pane_grid, progress_bar, radio, row, - scrollable, slider, text, text_input, Color, Element, Font, + scrollable, slider, text, text_input, toggler, Color, Element, Font, HorizontalAlignment, Layout, Padding, Point, Rectangle, Renderer, Size, VerticalAlignment, }; @@ -288,3 +288,19 @@ impl pane_grid::Renderer for Null { ) { } } + +impl toggler::Renderer for Null { + type Style = (); + + const DEFAULT_SIZE: u16 = 20; + + fn draw( + &mut self, + _bounds: Rectangle, + _is_checked: bool, + _is_mouse_over: bool, + _label: Self::Output, + _style: &Self::Style, + ) { + } +} diff --git a/native/src/widget.rs b/native/src/widget.rs index 791c53a3..759fe71a 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -36,6 +36,7 @@ pub mod space; pub mod svg; pub mod text; pub mod text_input; +pub mod toggler; pub mod tooltip; #[doc(no_inline)] @@ -73,6 +74,8 @@ pub use text::Text; #[doc(no_inline)] pub use text_input::TextInput; #[doc(no_inline)] +pub use toggler::Toggler; +#[doc(no_inline)] pub use tooltip::Tooltip; use crate::event::{self, Event}; diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs new file mode 100644 index 00000000..250abac4 --- /dev/null +++ b/native/src/widget/toggler.rs @@ -0,0 +1,262 @@ +//! 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 = iced_native::Toggler; +/// # +/// pub enum Message { +/// TogglerToggled(bool), +/// } +/// +/// let is_active = true; +/// +/// Toggler::new(is_active, "Toggle me!", |b| Message::TogglerToggled(b)) +/// ``` +/// +#[allow(missing_debug_implementations)] +pub struct Toggler { + is_active: bool, + on_toggle: Box Message>, + label: String, + width: Length, + size: u16, + text_size: Option, + font: Renderer::Font, + style: Renderer::Style, +} + +impl + Toggler +{ + /// Creates a new [`Toggler`]. + /// + /// It expects: + /// * a boolean describing whether the [`Toggler`] is checked or not + /// * the label of 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`. + /// + /// [`Toggler`]: struct.Toggler.html + pub fn new(is_active: bool, label: impl Into, f: F) -> Self + where + F: 'static + Fn(bool) -> Message, + { + Toggler { + is_active, + on_toggle: Box::new(f), + label: label.into(), + width: Length::Fill, + size: ::DEFAULT_SIZE, + text_size: None, + font: Renderer::Font::default(), + style: Renderer::Style::default(), + } + } + + /// Sets the size of the [`Toggler`]. + /// + /// [`Toggler`]: struct.Toggler.html + pub fn size(mut self, size: u16) -> Self { + self.size = size; + self + } + + /// Sets the width of the [`Toggler`]. + /// + /// [`Toggler`]: struct.Toggler.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the text size o the [`Toggler`]. + /// + /// [`Toggler`]: struct.Toggler.html + pub fn text_size(mut self, text_size: u16) -> Self { + self.text_size = Some(text_size); + self + } + + /// Sets the [`Font`] of the text of the [`Toggler`] + /// + /// [`Toggler`]: struct.Toggler.html + /// [`Font`]: ../../struct.Font.html + pub fn font(mut self, font: Renderer::Font) -> Self { + self.font = font; + self + } + + /// Sets the style of the [`Toggler`]. + /// + /// [`Toggler`]: struct.Toggler.html + pub fn style(mut self, style: impl Into) -> Self { + self.style = style.into(); + self + } +} + +impl Widget for Toggler +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 { + Row::<(), Renderer>::new() + .width(self.width) + .align_items(Align::Center) + .push( + Text::new(&self.label) + .font(self.font) + .width(self.width) + .size(self.text_size.unwrap_or(renderer.default_size())), + ) + .push( + Row::new() + .width(Length::Units(2 * self.size)) + .height(Length::Units(self.size)), + ) + .layout(renderer, limits) + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + _renderer: &Renderer, + _clipboard: &mut dyn Clipboard, + messages: &mut Vec, + ) -> 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_layout = children.next().unwrap(); + let toggler_layout = children.next().unwrap(); + let toggler_bounds = toggler_layout.bounds(); + + let label = text::Renderer::draw( + renderer, + defaults, + label_layout.bounds(), + &self.label, + self.text_size.unwrap_or(renderer.default_size()), + self.font, + None, + HorizontalAlignment::Left, + VerticalAlignment::Center, + ); + + 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::().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. +/// +/// [`Toggler`]: struct.Toggler.html +/// [renderer]: ../../renderer/index.html +pub trait Renderer: crate::Renderer { + /// The style supported by this renderer. + type Style: Default; + + /// The default size of a [`Toggler`]. + /// + /// [`Toggler`]: struct.Toggler.html + 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`] + /// + /// [`Toggler`]: struct.Toggler.html + fn draw( + &mut self, + bounds: Rectangle, + is_active: bool, + is_mouse_over: bool, + label: Self::Output, + style: &Self::Style, + ) -> Self::Output; +} + +impl<'a, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + Renderer: 'a + self::Renderer + text::Renderer + row::Renderer, + Message: 'a, +{ + fn from( + toggler: Toggler, + ) -> Element<'a, Message, Renderer> { + Element::new(toggler) + } +} -- cgit From 7370dfac6e798667771d768dbc5c3ed04930364c Mon Sep 17 00:00:00 2001 From: Kaiden42 Date: Sat, 19 Sep 2020 19:57:56 +0200 Subject: fix missing semicolon in doc test --- native/src/widget/toggler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'native') diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs index 250abac4..1acdf6ec 100644 --- a/native/src/widget/toggler.rs +++ b/native/src/widget/toggler.rs @@ -20,7 +20,7 @@ use crate::{ /// /// let is_active = true; /// -/// Toggler::new(is_active, "Toggle me!", |b| Message::TogglerToggled(b)) +/// Toggler::new(is_active, "Toggle me!", |b| Message::TogglerToggled(b)); /// ``` /// #[allow(missing_debug_implementations)] -- cgit From aa18a6e0d5550a83510aaf38a2b01d4a5fa56ccd Mon Sep 17 00:00:00 2001 From: Kaiden42 Date: Thu, 24 Sep 2020 15:49:48 +0200 Subject: Add alignment of `Toggler` label. --- native/src/widget/toggler.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) (limited to 'native') diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs index 1acdf6ec..63058d06 100644 --- a/native/src/widget/toggler.rs +++ b/native/src/widget/toggler.rs @@ -31,6 +31,8 @@ pub struct Toggler { width: Length, size: u16, text_size: Option, + text_align: Option, + spacing: u16, font: Renderer::Font, style: Renderer::Style, } @@ -59,6 +61,8 @@ impl width: Length::Fill, size: ::DEFAULT_SIZE, text_size: None, + text_align: None, + spacing: 0, font: Renderer::Font::default(), style: Renderer::Style::default(), } @@ -88,6 +92,22 @@ impl self } + /// Sets the alignment of the text of the [`Toggler`] + /// + /// [`Toggler`]: struct.Toggler.html + pub fn text_align(mut self, align: HorizontalAlignment) -> Self { + self.text_align = Some(align); + self + } + + /// Sets the spacing between the [`Toggler`] and the text. + /// + /// [`Toggler`]: struct.Toggler.html + pub fn spacing(mut self, spacing: u16) -> Self { + self.spacing = spacing; + self + } + /// Sets the [`Font`] of the text of the [`Toggler`] /// /// [`Toggler`]: struct.Toggler.html @@ -125,9 +145,13 @@ where ) -> layout::Node { Row::<(), Renderer>::new() .width(self.width) + .spacing(self.spacing) .align_items(Align::Center) .push( Text::new(&self.label) + .horizontal_alignment( + self.text_align.unwrap_or(HorizontalAlignment::Left), + ) .font(self.font) .width(self.width) .size(self.text_size.unwrap_or(renderer.default_size())), @@ -188,7 +212,7 @@ where self.text_size.unwrap_or(renderer.default_size()), self.font, None, - HorizontalAlignment::Left, + self.text_align.unwrap_or(HorizontalAlignment::Left), VerticalAlignment::Center, ); -- cgit From 7a626f3b7b6871052e5fade697e120cfb7d726d7 Mon Sep 17 00:00:00 2001 From: Kaiden42 Date: Thu, 24 Sep 2020 16:31:39 +0200 Subject: Change label of `Toggler` to optional --- native/src/renderer/null.rs | 2 +- native/src/widget/toggler.rs | 74 +++++++++++++++++++++++++++----------------- 2 files changed, 46 insertions(+), 30 deletions(-) (limited to 'native') diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs index 89bb9433..bb57c163 100644 --- a/native/src/renderer/null.rs +++ b/native/src/renderer/null.rs @@ -299,7 +299,7 @@ impl toggler::Renderer for Null { _bounds: Rectangle, _is_checked: bool, _is_mouse_over: bool, - _label: Self::Output, + _label: Option, _style: &Self::Style, ) { } diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs index 63058d06..36e7d110 100644 --- a/native/src/widget/toggler.rs +++ b/native/src/widget/toggler.rs @@ -20,14 +20,14 @@ use crate::{ /// /// let is_active = true; /// -/// Toggler::new(is_active, "Toggle me!", |b| Message::TogglerToggled(b)); +/// Toggler::new(is_active, String::from("Toggle me!"), |b| Message::TogglerToggled(b)); /// ``` /// #[allow(missing_debug_implementations)] pub struct Toggler { is_active: bool, on_toggle: Box Message>, - label: String, + label: Option, width: Length, size: u16, text_size: Option, @@ -44,13 +44,17 @@ impl /// /// It expects: /// * a boolean describing whether the [`Toggler`] is checked or not - /// * the label of the [`Toggler`] + /// * 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`. /// /// [`Toggler`]: struct.Toggler.html - pub fn new(is_active: bool, label: impl Into, f: F) -> Self + pub fn new( + is_active: bool, + label: impl Into>, + f: F, + ) -> Self where F: 'static + Fn(bool) -> Message, { @@ -143,25 +147,30 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - Row::<(), Renderer>::new() + let mut row = Row::<(), Renderer>::new() .width(self.width) .spacing(self.spacing) - .align_items(Align::Center) - .push( - Text::new(&self.label) + .align_items(Align::Center); + + if let Some(label) = &self.label { + row = row.push( + Text::new(label) .horizontal_alignment( self.text_align.unwrap_or(HorizontalAlignment::Left), ) .font(self.font) .width(self.width) .size(self.text_size.unwrap_or(renderer.default_size())), - ) - .push( - Row::new() - .width(Length::Units(2 * self.size)) - .height(Length::Units(self.size)), - ) - .layout(renderer, limits) + ); + } + + row = row.push( + Row::new() + .width(Length::Units(2 * self.size)) + .height(Length::Units(self.size)), + ); + + row.layout(renderer, limits) } fn on_event( @@ -200,22 +209,29 @@ where let bounds = layout.bounds(); let mut children = layout.children(); - let label_layout = children.next().unwrap(); + 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_align.unwrap_or(HorizontalAlignment::Left), + VerticalAlignment::Center, + )) + } + + None => None, + }; + let toggler_layout = children.next().unwrap(); let toggler_bounds = toggler_layout.bounds(); - let label = text::Renderer::draw( - renderer, - defaults, - label_layout.bounds(), - &self.label, - self.text_size.unwrap_or(renderer.default_size()), - self.font, - None, - self.text_align.unwrap_or(HorizontalAlignment::Left), - VerticalAlignment::Center, - ); - let is_mouse_over = bounds.contains(cursor_position); self::Renderer::draw( @@ -267,7 +283,7 @@ pub trait Renderer: crate::Renderer { bounds: Rectangle, is_active: bool, is_mouse_over: bool, - label: Self::Output, + label: Option, style: &Self::Style, ) -> Self::Output; } -- cgit From c0cfd9d5cf373d4d7f89cf14c15f36e9995c1dbf Mon Sep 17 00:00:00 2001 From: Kaiden42 Date: Fri, 25 Sep 2020 16:38:30 +0200 Subject: Update documentation of `Toggler` --- native/src/widget/toggler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'native') diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs index 36e7d110..8dbd94a1 100644 --- a/native/src/widget/toggler.rs +++ b/native/src/widget/toggler.rs @@ -96,7 +96,7 @@ impl self } - /// Sets the alignment of the text of the [`Toggler`] + /// Sets the horizontal alignment of the text of the [`Toggler`] /// /// [`Toggler`]: struct.Toggler.html pub fn text_align(mut self, align: HorizontalAlignment) -> Self { -- cgit From a32ce271bd38a6d405859210fa3fbd5a14146ce8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Thu, 3 Jun 2021 20:27:32 +0700 Subject: Rename `text_align` to `text_alignment` in `Toggler` --- native/src/widget/toggler.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'native') diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs index 8dbd94a1..d565bda1 100644 --- a/native/src/widget/toggler.rs +++ b/native/src/widget/toggler.rs @@ -31,7 +31,7 @@ pub struct Toggler { width: Length, size: u16, text_size: Option, - text_align: Option, + text_alignment: HorizontalAlignment, spacing: u16, font: Renderer::Font, style: Renderer::Style, @@ -65,7 +65,7 @@ impl width: Length::Fill, size: ::DEFAULT_SIZE, text_size: None, - text_align: None, + text_alignment: HorizontalAlignment::Left, spacing: 0, font: Renderer::Font::default(), style: Renderer::Style::default(), @@ -99,8 +99,8 @@ impl /// Sets the horizontal alignment of the text of the [`Toggler`] /// /// [`Toggler`]: struct.Toggler.html - pub fn text_align(mut self, align: HorizontalAlignment) -> Self { - self.text_align = Some(align); + pub fn text_alignment(mut self, alignment: HorizontalAlignment) -> Self { + self.text_alignment = alignment; self } @@ -155,9 +155,7 @@ where if let Some(label) = &self.label { row = row.push( Text::new(label) - .horizontal_alignment( - self.text_align.unwrap_or(HorizontalAlignment::Left), - ) + .horizontal_alignment(self.text_alignment) .font(self.font) .width(self.width) .size(self.text_size.unwrap_or(renderer.default_size())), @@ -221,7 +219,7 @@ where self.text_size.unwrap_or(renderer.default_size()), self.font, None, - self.text_align.unwrap_or(HorizontalAlignment::Left), + self.text_alignment, VerticalAlignment::Center, )) } -- cgit From ef5f46bcddffb191bde5dd2df131bdd1197a1e69 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Thu, 3 Jun 2021 20:28:36 +0700 Subject: Use intra-doc links in `Toggler` docs --- native/src/widget/toggler.rs | 23 ----------------------- 1 file changed, 23 deletions(-) (limited to 'native') diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs index d565bda1..4035276c 100644 --- a/native/src/widget/toggler.rs +++ b/native/src/widget/toggler.rs @@ -22,7 +22,6 @@ use crate::{ /// /// Toggler::new(is_active, String::from("Toggle me!"), |b| Message::TogglerToggled(b)); /// ``` -/// #[allow(missing_debug_implementations)] pub struct Toggler { is_active: bool, @@ -48,8 +47,6 @@ impl /// * 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`. - /// - /// [`Toggler`]: struct.Toggler.html pub fn new( is_active: bool, label: impl Into>, @@ -73,57 +70,42 @@ impl } /// Sets the size of the [`Toggler`]. - /// - /// [`Toggler`]: struct.Toggler.html pub fn size(mut self, size: u16) -> Self { self.size = size; self } /// Sets the width of the [`Toggler`]. - /// - /// [`Toggler`]: struct.Toggler.html pub fn width(mut self, width: Length) -> Self { self.width = width; self } /// Sets the text size o the [`Toggler`]. - /// - /// [`Toggler`]: struct.Toggler.html 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`] - /// - /// [`Toggler`]: struct.Toggler.html pub fn text_alignment(mut self, alignment: HorizontalAlignment) -> Self { self.text_alignment = alignment; self } /// Sets the spacing between the [`Toggler`] and the text. - /// - /// [`Toggler`]: struct.Toggler.html pub fn spacing(mut self, spacing: u16) -> Self { self.spacing = spacing; self } /// Sets the [`Font`] of the text of the [`Toggler`] - /// - /// [`Toggler`]: struct.Toggler.html - /// [`Font`]: ../../struct.Font.html pub fn font(mut self, font: Renderer::Font) -> Self { self.font = font; self } /// Sets the style of the [`Toggler`]. - /// - /// [`Toggler`]: struct.Toggler.html pub fn style(mut self, style: impl Into) -> Self { self.style = style.into(); self @@ -255,15 +237,12 @@ where /// Your [renderer] will need to implement this trait before being /// able to use a [`Toggler`] in your user interface. /// -/// [`Toggler`]: struct.Toggler.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer { /// The style supported by this renderer. type Style: Default; /// The default size of a [`Toggler`]. - /// - /// [`Toggler`]: struct.Toggler.html const DEFAULT_SIZE: u16; /// Draws a [`Toggler`]. @@ -274,8 +253,6 @@ pub trait Renderer: crate::Renderer { /// * whether the mouse is over the [`Toggler`] or not /// * the drawn label of the [`Toggler`] /// * the style of the [`Toggler`] - /// - /// [`Toggler`]: struct.Toggler.html fn draw( &mut self, bounds: Rectangle, -- cgit From f7d6e40bf0a0a61dc86d8a53303fab7cf93514a5 Mon Sep 17 00:00:00 2001 From: Yusuf Bera Ertan Date: Tue, 10 Nov 2020 14:23:49 +0300 Subject: feat(native): Make scrollable programmatically scrollable for some use cases, add snap_to_bottom by default --- native/src/widget/scrollable.rs | 271 +++++++++++++++++++++++++++------------- 1 file changed, 181 insertions(+), 90 deletions(-) (limited to 'native') diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 7c4ea16c..374dcf76 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -10,7 +10,7 @@ use crate::{ Rectangle, Size, Vector, Widget, }; -use std::{f32, hash::Hash, u32}; +use std::{cell::RefCell, f32, hash::Hash, u32}; /// A widget that can vertically display an infinite amount of content with a /// scrollbar. @@ -24,6 +24,8 @@ pub struct Scrollable<'a, Message, Renderer: self::Renderer> { scroller_width: u16, content: Column<'a, Message, Renderer>, style: Renderer::Style, + on_scroll: Option Message>>, + snap_to_bottom: bool, } impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> { @@ -38,9 +40,36 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> { scroller_width: 10, content: Column::new(), style: Renderer::Style::default(), + on_scroll: None, + snap_to_bottom: false, } } + /// Whether to set the [`Scrollable`] to snap to bottom when the user + /// scrolls to bottom or not. This will keep the scrollable at the bottom + /// even if new content is added to the scrollable. + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn snap_to_bottom(mut self, snap: bool) -> Self { + self.snap_to_bottom = snap; + self + } + + /// Sets a function to call when the [`Scrollable`] is scrolled. + /// + /// The function takes two `f32` as arguments. First is the percentage of + /// where the scrollable is at right now. Second is the percentage of where + /// the scrollable was *before*. `0.0` means top and `1.0` means bottom. + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn on_scroll(mut self, message_constructor: F) -> Self + where + F: 'static + Fn(f32, f32) -> Message, + { + self.on_scroll = Some(Box::new(message_constructor)); + self + } + /// Sets the vertical spacing _between_ elements. /// /// Custom margins per element do not exist in Iced. You should use this @@ -186,7 +215,7 @@ where .map(|scrollbar| scrollbar.is_mouse_over(cursor_position)) .unwrap_or(false); - let event_status = { + let mut event_status = { let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar { Point::new( cursor_position.x, @@ -211,99 +240,78 @@ where ) }; - if let event::Status::Captured = event_status { - return event::Status::Captured; - } - - if is_mouse_over { - match event { - Event::Mouse(mouse::Event::WheelScrolled { delta }) => { - match delta { - mouse::ScrollDelta::Lines { y, .. } => { - // TODO: Configurable speed (?) - self.state.scroll(y * 60.0, bounds, content_bounds); - } - mouse::ScrollDelta::Pixels { y, .. } => { - self.state.scroll(y, bounds, content_bounds); - } - } - - 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; + if let event::Status::Ignored = event_status { + self.state.prev_offset = self.state.offset(bounds, content_bounds); + if is_mouse_over { + match event { + Event::Mouse(mouse::Event::WheelScrolled { delta }) => { + match delta { + mouse::ScrollDelta::Lines { y, .. } => { + // TODO: Configurable speed (?) self.state.scroll( - delta, + y * 60.0, bounds, content_bounds, ); + } + mouse::ScrollDelta::Pixels { y, .. } => { + self.state.scroll(y, bounds, content_bounds); + } + } + event_status = 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); + } + } + touch::Event::FingerLifted { .. } + | touch::Event::FingerLost { .. } => { + self.state.scroll_box_touched_at = None; + } } - touch::Event::FingerLifted { .. } - | touch::Event::FingerLost { .. } => { - self.state.scroll_box_touched_at = None; - } - } - return event::Status::Captured; + event_status = event::Status::Captured; + } + _ => {} } - _ => {} } - } - 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; + 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::Touch(touch::Event::FingerMoved { .. }) => { - if let (Some(scrollbar), Some(scroller_grabbed_at)) = - (scrollbar, self.state.scroller_grabbed_at) - { - self.state.scroll_to( - scrollbar.scroll_percentage( - scroller_grabbed_at, - cursor_position, - ), - bounds, - content_bounds, - ); - - return event::Status::Captured; + event_status = event::Status::Captured; } - } - _ => {} - } - } else if is_mouse_over_scrollbar { - 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) + Event::Mouse(mouse::Event::CursorMoved { .. }) + | Event::Touch(touch::Event::FingerMoved { .. }) => { + if let (Some(scrollbar), Some(scroller_grabbed_at)) = + (scrollbar, self.state.scroller_grabbed_at) { self.state.scroll_to( scrollbar.scroll_percentage( @@ -314,18 +322,71 @@ where content_bounds, ); - self.state.scroller_grabbed_at = - Some(scroller_grabbed_at); + event_status = event::Status::Captured; + } + } + _ => {} + } + } else if is_mouse_over_scrollbar { + 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) + { + self.state.scroll_to( + scrollbar.scroll_percentage( + scroller_grabbed_at, + cursor_position, + ), + bounds, + content_bounds, + ); - return event::Status::Captured; + self.state.scroller_grabbed_at = + Some(scroller_grabbed_at); + + event_status = event::Status::Captured; + } } } + _ => {} } - _ => {} } } - event::Status::Ignored + if let event::Status::Captured = event_status { + if self.snap_to_bottom { + let new_offset = self.state.offset(bounds, content_bounds); + + if new_offset < self.state.prev_offset { + self.state.snap_to_bottom = false; + } else { + let scroll_perc = new_offset as f32 + / (content_bounds.height - bounds.height); + + if scroll_perc >= 1.0 - f32::EPSILON { + self.state.snap_to_bottom = true; + } + } + } + + if let Some(on_scroll) = &self.on_scroll { + messages.push(on_scroll( + self.state.offset(bounds, content_bounds) as f32 + / (content_bounds.height - bounds.height), + self.state.prev_offset as f32 + / (content_bounds.height - bounds.height), + )); + } + + event::Status::Captured + } else { + event::Status::Ignored + } } fn draw( @@ -339,6 +400,15 @@ where let bounds = layout.bounds(); let content_layout = layout.children().next().unwrap(); let content_bounds = content_layout.bounds(); + + if self.state.snap_to_bottom { + self.state.scroll_to(1.0, bounds, content_bounds); + } + + if let Some(scroll_to) = self.state.scroll_to.borrow_mut().take() { + self.state.scroll_to(scroll_to, bounds, content_bounds); + } + let offset = self.state.offset(bounds, content_bounds); let scrollbar = renderer.scrollbar( bounds, @@ -418,11 +488,14 @@ where } /// The local state of a [`Scrollable`]. -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Default)] pub struct State { scroller_grabbed_at: Option, scroll_box_touched_at: Option, - offset: f32, + prev_offset: u32, + snap_to_bottom: bool, + offset: RefCell, + scroll_to: RefCell>, } impl State { @@ -443,7 +516,8 @@ impl State { return; } - self.offset = (self.offset - delta_y) + let offset_val = *self.offset.borrow(); + *self.offset.borrow_mut() = (offset_val - delta_y) .max(0.0) .min((content_bounds.height - bounds.height) as f32); } @@ -454,22 +528,39 @@ impl State { /// `0` represents scrollbar at the top, while `1` represents scrollbar at /// the bottom. pub fn scroll_to( - &mut self, + &self, percentage: f32, bounds: Rectangle, content_bounds: Rectangle, ) { - self.offset = + *self.offset.borrow_mut() = ((content_bounds.height - bounds.height) * percentage).max(0.0); } + /// Marks the scrollable to scroll to `perc` percentage (between 0.0 and 1.0) + /// in the next `draw` call. + /// + /// [`Scrollable`]: struct.Scrollable.html + /// [`State`]: struct.State.html + pub fn scroll_to_percentage(&mut self, perc: f32) { + *self.scroll_to.borrow_mut() = Some(perc.max(0.0).min(1.0)); + } + + /// Marks the scrollable to scroll to bottom in the next `draw` call. + /// + /// [`Scrollable`]: struct.Scrollable.html + /// [`State`]: struct.State.html + pub fn scroll_to_bottom(&mut self) { + self.scroll_to_percentage(1.0); + } + /// Returns the current scrolling offset of the [`State`], given the bounds /// of the [`Scrollable`] and its contents. 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.borrow().min(hidden_content as f32) as u32 } /// Returns whether the scroller is currently grabbed or not. -- cgit From 827577c179f78c9fb82a46142d2cdb9e61f4662b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Fri, 4 Jun 2021 19:39:08 +0700 Subject: Introduce `snap_to` and `unsnap` to `scrollable::State` --- native/src/widget/scrollable.rs | 323 +++++++++++++++++----------------------- 1 file changed, 139 insertions(+), 184 deletions(-) (limited to 'native') diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 374dcf76..28d695ba 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -10,7 +10,7 @@ use crate::{ Rectangle, Size, Vector, Widget, }; -use std::{cell::RefCell, f32, hash::Hash, u32}; +use std::{f32, hash::Hash, u32}; /// A widget that can vertically display an infinite amount of content with a /// scrollbar. @@ -24,8 +24,6 @@ pub struct Scrollable<'a, Message, Renderer: self::Renderer> { scroller_width: u16, content: Column<'a, Message, Renderer>, style: Renderer::Style, - on_scroll: Option Message>>, - snap_to_bottom: bool, } impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> { @@ -40,36 +38,9 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> { scroller_width: 10, content: Column::new(), style: Renderer::Style::default(), - on_scroll: None, - snap_to_bottom: false, } } - /// Whether to set the [`Scrollable`] to snap to bottom when the user - /// scrolls to bottom or not. This will keep the scrollable at the bottom - /// even if new content is added to the scrollable. - /// - /// [`Scrollable`]: struct.Scrollable.html - pub fn snap_to_bottom(mut self, snap: bool) -> Self { - self.snap_to_bottom = snap; - self - } - - /// Sets a function to call when the [`Scrollable`] is scrolled. - /// - /// The function takes two `f32` as arguments. First is the percentage of - /// where the scrollable is at right now. Second is the percentage of where - /// the scrollable was *before*. `0.0` means top and `1.0` means bottom. - /// - /// [`Scrollable`]: struct.Scrollable.html - pub fn on_scroll(mut self, message_constructor: F) -> Self - where - F: 'static + Fn(f32, f32) -> Message, - { - self.on_scroll = Some(Box::new(message_constructor)); - self - } - /// Sets the vertical spacing _between_ elements. /// /// Custom margins per element do not exist in Iced. You should use this @@ -215,7 +186,7 @@ where .map(|scrollbar| scrollbar.is_mouse_over(cursor_position)) .unwrap_or(false); - let mut event_status = { + let event_status = { let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar { Point::new( cursor_position.x, @@ -240,78 +211,99 @@ where ) }; - if let event::Status::Ignored = event_status { - self.state.prev_offset = self.state.offset(bounds, content_bounds); + if let event::Status::Captured = event_status { + return event::Status::Captured; + } + + if is_mouse_over { + match event { + Event::Mouse(mouse::Event::WheelScrolled { delta }) => { + match delta { + mouse::ScrollDelta::Lines { y, .. } => { + // TODO: Configurable speed (?) + self.state.scroll(y * 60.0, bounds, content_bounds); + } + mouse::ScrollDelta::Pixels { y, .. } => { + self.state.scroll(y, bounds, content_bounds); + } + } + + 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; - if is_mouse_over { - match event { - Event::Mouse(mouse::Event::WheelScrolled { delta }) => { - match delta { - mouse::ScrollDelta::Lines { y, .. } => { - // TODO: Configurable speed (?) self.state.scroll( - y * 60.0, + delta, bounds, content_bounds, ); - } - mouse::ScrollDelta::Pixels { y, .. } => { - self.state.scroll(y, bounds, content_bounds); - } - } - event_status = 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); - } - } - touch::Event::FingerLifted { .. } - | touch::Event::FingerLost { .. } => { - self.state.scroll_box_touched_at = None; - } } - - event_status = event::Status::Captured; + touch::Event::FingerLifted { .. } + | touch::Event::FingerLost { .. } => { + self.state.scroll_box_touched_at = None; + } } - _ => {} + + return event::Status::Captured; } + _ => {} } + } - 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; + 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; - event_status = event::Status::Captured; + return event::Status::Captured; + } + Event::Mouse(mouse::Event::CursorMoved { .. }) + | Event::Touch(touch::Event::FingerMoved { .. }) => { + if let (Some(scrollbar), Some(scroller_grabbed_at)) = + (scrollbar, self.state.scroller_grabbed_at) + { + self.state.scroll_to( + scrollbar.scroll_percentage( + scroller_grabbed_at, + cursor_position, + ), + bounds, + content_bounds, + ); + + return event::Status::Captured; } - Event::Mouse(mouse::Event::CursorMoved { .. }) - | Event::Touch(touch::Event::FingerMoved { .. }) => { - if let (Some(scrollbar), Some(scroller_grabbed_at)) = - (scrollbar, self.state.scroller_grabbed_at) + } + _ => {} + } + } else if is_mouse_over_scrollbar { + 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) { self.state.scroll_to( scrollbar.scroll_percentage( @@ -322,71 +314,18 @@ where content_bounds, ); - event_status = event::Status::Captured; - } - } - _ => {} - } - } else if is_mouse_over_scrollbar { - 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) - { - self.state.scroll_to( - scrollbar.scroll_percentage( - scroller_grabbed_at, - cursor_position, - ), - bounds, - content_bounds, - ); - - self.state.scroller_grabbed_at = - Some(scroller_grabbed_at); + self.state.scroller_grabbed_at = + Some(scroller_grabbed_at); - event_status = event::Status::Captured; - } + return event::Status::Captured; } } - _ => {} } + _ => {} } } - if let event::Status::Captured = event_status { - if self.snap_to_bottom { - let new_offset = self.state.offset(bounds, content_bounds); - - if new_offset < self.state.prev_offset { - self.state.snap_to_bottom = false; - } else { - let scroll_perc = new_offset as f32 - / (content_bounds.height - bounds.height); - - if scroll_perc >= 1.0 - f32::EPSILON { - self.state.snap_to_bottom = true; - } - } - } - - if let Some(on_scroll) = &self.on_scroll { - messages.push(on_scroll( - self.state.offset(bounds, content_bounds) as f32 - / (content_bounds.height - bounds.height), - self.state.prev_offset as f32 - / (content_bounds.height - bounds.height), - )); - } - - event::Status::Captured - } else { - event::Status::Ignored - } + event::Status::Ignored } fn draw( @@ -400,15 +339,6 @@ where let bounds = layout.bounds(); let content_layout = layout.children().next().unwrap(); let content_bounds = content_layout.bounds(); - - if self.state.snap_to_bottom { - self.state.scroll_to(1.0, bounds, content_bounds); - } - - if let Some(scroll_to) = self.state.scroll_to.borrow_mut().take() { - self.state.scroll_to(scroll_to, bounds, content_bounds); - } - let offset = self.state.offset(bounds, content_bounds); let scrollbar = renderer.scrollbar( bounds, @@ -488,14 +418,44 @@ where } /// The local state of a [`Scrollable`]. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Copy)] pub struct State { scroller_grabbed_at: Option, scroll_box_touched_at: Option, - prev_offset: u32, - snap_to_bottom: bool, - offset: RefCell, - scroll_to: RefCell>, + 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 { @@ -516,51 +476,46 @@ impl State { return; } - let offset_val = *self.offset.borrow(); - *self.offset.borrow_mut() = (offset_val - 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. pub fn scroll_to( - &self, + &mut self, percentage: f32, bounds: Rectangle, content_bounds: Rectangle, ) { - *self.offset.borrow_mut() = - ((content_bounds.height - bounds.height) * percentage).max(0.0); + self.snap_to(percentage); + self.unsnap(bounds, content_bounds); } - /// Marks the scrollable to scroll to `perc` percentage (between 0.0 and 1.0) - /// in the next `draw` call. + /// Snaps the scroll position to a relative amount. /// - /// [`Scrollable`]: struct.Scrollable.html - /// [`State`]: struct.State.html - pub fn scroll_to_percentage(&mut self, perc: f32) { - *self.scroll_to.borrow_mut() = Some(perc.max(0.0).min(1.0)); + /// `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)); } - /// Marks the scrollable to scroll to bottom in the next `draw` call. - /// - /// [`Scrollable`]: struct.Scrollable.html - /// [`State`]: struct.State.html - pub fn scroll_to_bottom(&mut self) { - self.scroll_to_percentage(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 = + 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. 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.borrow().min(hidden_content as f32) as u32 + self.offset.absolute(bounds, content_bounds) as u32 } /// Returns whether the scroller is currently grabbed or not. -- cgit From 3051d4ec763f0e073dd94526fde04f953967bd86 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Fri, 4 Jun 2021 20:46:27 +0700 Subject: Introduce `on_scroll` event in `Scrollable` --- native/src/widget/scrollable.rs | 48 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) (limited to 'native') diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 28d695ba..68da2e67 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -23,6 +23,7 @@ pub struct Scrollable<'a, Message, Renderer: self::Renderer> { scrollbar_margin: u16, scroller_width: u16, content: Column<'a, Message, Renderer>, + on_scroll: Option Message>>, style: Renderer::Style, } @@ -37,6 +38,7 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> { scrollbar_margin: 0, scroller_width: 10, content: Column::new(), + on_scroll: None, style: Renderer::Style::default(), } } @@ -101,12 +103,22 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> { } /// Sets the scroller width of the [`Scrollable`] . - /// Silently enforces a minimum value of 1. + /// + /// 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) -> Self { self.style = style.into(); @@ -121,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, + ) { + 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 @@ -228,6 +258,8 @@ where } } + self.notify_on_scroll(bounds, content_bounds, messages); + return event::Status::Captured; } Event::Touch(event) => { @@ -251,6 +283,12 @@ where self.state.scroll_box_touched_at = Some(cursor_position); + + self.notify_on_scroll( + bounds, + content_bounds, + messages, + ); } } touch::Event::FingerLifted { .. } @@ -290,6 +328,8 @@ where content_bounds, ); + self.notify_on_scroll(bounds, content_bounds, messages); + return event::Status::Captured; } } @@ -317,6 +357,12 @@ where self.state.scroller_grabbed_at = Some(scroller_grabbed_at); + self.notify_on_scroll( + bounds, + content_bounds, + messages, + ); + return event::Status::Captured; } } -- cgit From dbc1181011d579ac1da2546fba08e11094633f4b Mon Sep 17 00:00:00 2001 From: Jonas Matser Date: Tue, 1 Dec 2020 11:52:52 +0100 Subject: Adds doc comment for disabled button Makes disabled button behavior consistent in web --- native/src/widget/button.rs | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'native') diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index f61c22d0..c9b9df58 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -29,6 +29,13 @@ use std::hash::Hash; /// let button = Button::new(&mut state, Text::new("Press me!")) /// .on_press(Message::ButtonPressed); /// ``` +/// +/// Buttons can be disabled by not having an on_press. +/// +/// ``` +/// let mut state = button::State::new(); +/// let disabled_button = Button::new(&mut state, Text::new("I'm disabled!")); +/// ``` #[allow(missing_debug_implementations)] pub struct Button<'a, Message, Renderer: self::Renderer> { state: &'a mut State, @@ -97,6 +104,7 @@ where } /// Sets the message that will be produced when the [`Button`] is pressed. + /// 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 -- cgit From e66120b9c1d3998085de7422edaac778e4ebf3e3 Mon Sep 17 00:00:00 2001 From: Jonas Matser Date: Tue, 1 Dec 2020 14:28:40 +0100 Subject: Fix failing doctests --- native/src/widget/button.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'native') diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index c9b9df58..5d0940ec 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -33,8 +33,18 @@ use std::hash::Hash; /// Buttons can be disabled by not having an on_press. /// /// ``` +/// # use iced_native::{button, Text}; +/// # +/// # type Button<'a, Message> = +/// # iced_native::Button<'a, Message, iced_native::renderer::Null>; +/// # +/// # #[derive(Clone)] +/// # enum Message { +/// # ButtonPressed, +/// # } +/// # /// let mut state = button::State::new(); -/// let disabled_button = Button::new(&mut state, Text::new("I'm disabled!")); +/// let disabled_button = Button::::new(&mut state, Text::new("I'm disabled!")); /// ``` #[allow(missing_debug_implementations)] pub struct Button<'a, Message, Renderer: self::Renderer> { -- cgit From d46dd67a91731177406570aae5e921a728b8c2b4 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Thu, 10 Jun 2021 18:40:32 +0700 Subject: Update disabled example of `Button` in docs --- native/src/widget/button.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) (limited to 'native') diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 5d0940ec..c469a0e5 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -30,7 +30,8 @@ use std::hash::Hash; /// .on_press(Message::ButtonPressed); /// ``` /// -/// Buttons can be disabled by not having an on_press. +/// If a [`Button::on_press`] handler is not set, the resulting [`Button`] will +/// be disabled: /// /// ``` /// # use iced_native::{button, Text}; @@ -38,13 +39,18 @@ use std::hash::Hash; /// # type Button<'a, Message> = /// # iced_native::Button<'a, Message, iced_native::renderer::Null>; /// # -/// # #[derive(Clone)] -/// # enum Message { -/// # ButtonPressed, -/// # } -/// # -/// let mut state = button::State::new(); -/// let disabled_button = Button::::new(&mut state, Text::new("I'm disabled!")); +/// #[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> { -- cgit From 83d19689c80266874e0a26085f17a94fd3507e1e Mon Sep 17 00:00:00 2001 From: Yusuf Bera Ertan Date: Mon, 14 Jun 2021 21:01:37 +0300 Subject: docs: update all 0.2 github links to 0.3 --- native/src/user_interface.rs | 2 +- native/src/widget.rs | 10 +++++----- native/src/widget/pane_grid.rs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) (limited to 'native') diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 475faf8d..8e0d7d1c 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -16,7 +16,7 @@ use std::hash::Hasher; /// The [`integration` example] uses a [`UserInterface`] to integrate Iced in /// an existing graphical application. /// -/// [`integration` example]: https://github.com/hecrj/iced/tree/0.2/examples/integration +/// [`integration` example]: https://github.com/hecrj/iced/tree/0.3/examples/integration #[allow(missing_debug_implementations)] pub struct UserInterface<'a, Message, Renderer> { root: Element<'a, Message, Renderer>, diff --git a/native/src/widget.rs b/native/src/widget.rs index 759fe71a..43c1b023 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -99,12 +99,12 @@ use crate::{Clipboard, Hasher, Layout, Length, Point, Rectangle}; /// - [`geometry`], a custom widget showcasing how to draw geometry with the /// `Mesh2D` primitive in [`iced_wgpu`]. /// -/// [examples]: https://github.com/hecrj/iced/tree/0.2/examples -/// [`bezier_tool`]: https://github.com/hecrj/iced/tree/0.2/examples/bezier_tool -/// [`custom_widget`]: https://github.com/hecrj/iced/tree/0.2/examples/custom_widget -/// [`geometry`]: https://github.com/hecrj/iced/tree/0.2/examples/geometry +/// [examples]: https://github.com/hecrj/iced/tree/0.3/examples +/// [`bezier_tool`]: https://github.com/hecrj/iced/tree/0.3/examples/bezier_tool +/// [`custom_widget`]: https://github.com/hecrj/iced/tree/0.3/examples/custom_widget +/// [`geometry`]: https://github.com/hecrj/iced/tree/0.3/examples/geometry /// [`lyon`]: https://github.com/nical/lyon -/// [`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.2/wgpu +/// [`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.3/wgpu pub trait Widget where Renderer: crate::Renderer, diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 44028f5e..b72172cc 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -6,7 +6,7 @@ //! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing, //! drag and drop, and hotkey support. //! -//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.2/examples/pane_grid +//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid mod axis; mod configuration; mod content; -- cgit From 27b42ca6b6585477fda0a5d07ec09bd74e501a1a Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Thu, 17 Jun 2021 14:50:28 -0500 Subject: Allow overlay from pane grid title bar --- native/src/widget/pane_grid/content.rs | 16 ++++++++-------- native/src/widget/pane_grid/title_bar.rs | 8 ++++++++ 2 files changed, 16 insertions(+), 8 deletions(-) (limited to 'native') diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index f028ec25..188c47e3 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -193,18 +193,18 @@ where &mut self, layout: Layout<'_>, ) -> Option> { - 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().unwrap(); - // Overlays only allowed in the pane body, for now at least. - let _title_bar_layout = children.next(); + if let Some(overlay) = title_bar.overlay(title_bar_layout) { + return Some(overlay); + } - children.next()? + self.body.overlay(children.next()?) } else { - layout - }; - - self.body.overlay(body_layout) + self.body.overlay(layout) + } } } diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index d9d85dbb..efdc1e54 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -1,6 +1,7 @@ use crate::container; use crate::event::{self, Event}; use crate::layout; +use crate::overlay; use crate::pane_grid; use crate::{ Clipboard, Element, Hasher, Layout, Padding, Point, Rectangle, Size, @@ -242,4 +243,11 @@ where control_status.merge(title_status) } + + pub(crate) fn overlay( + &mut self, + layout: Layout<'_>, + ) -> Option> { + self.content.overlay(layout) + } } -- cgit From 15c17a72502e15393f6430424eaad576581f2e38 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 22 Jun 2021 11:29:06 +0200 Subject: Use `match` statement in `Content::overlay` ... to improve readability a bit. --- native/src/widget/pane_grid/content.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'native') diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index 188c47e3..b0110393 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -195,13 +195,12 @@ where ) -> Option> { if let Some(title_bar) = self.title_bar.as_mut() { let mut children = layout.children(); - let title_bar_layout = children.next().unwrap(); + let title_bar_layout = children.next()?; - if let Some(overlay) = title_bar.overlay(title_bar_layout) { - return Some(overlay); + match title_bar.overlay(title_bar_layout) { + Some(overlay) => Some(overlay), + None => self.body.overlay(children.next()?), } - - self.body.overlay(children.next()?) } else { self.body.overlay(layout) } -- cgit From 9ae22b58d843d9a39212028478598c19a49bc2e6 Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 21 Apr 2021 17:52:31 -0300 Subject: Added events for url handling and create example --- native/src/event.rs | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'native') diff --git a/native/src/event.rs b/native/src/event.rs index 205bb797..59c5c0cb 100644 --- a/native/src/event.rs +++ b/native/src/event.rs @@ -23,6 +23,10 @@ pub enum Event { /// A touch event Touch(touch::Event), + + // TODO: System(system::Event)? + /// A url was received. + UrlReceived(String), } /// The status of an [`Event`] after being processed. -- cgit From 96a462d2f2cb608ad14c93cc55896108a2dccb2b Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 9 Jun 2021 15:00:01 -0300 Subject: Use new enum variant and new winit repo --- native/src/event.rs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) (limited to 'native') diff --git a/native/src/event.rs b/native/src/event.rs index 59c5c0cb..1c26b5f2 100644 --- a/native/src/event.rs +++ b/native/src/event.rs @@ -23,10 +23,27 @@ pub enum Event { /// A touch event Touch(touch::Event), - - // TODO: System(system::Event)? - /// A url was received. - UrlReceived(String), + + /// A platform specific event + PlatformSpecific(PlatformSpecific), +} + +/// A platform specific event +#[derive(Debug, Clone, PartialEq)] +pub enum PlatformSpecific { + /// A MacOS specific event + MacOS(MacOS), +} + +/// Describes an event specific to MacOS +#[derive(Debug, Clone, PartialEq)] +pub enum MacOS { + /// Triggered when the app receives an URL from the system + /// + /// _**Note:** For this event to be triggered, the executable needs to be properly [bundled]!_ + /// + /// [bundled]: https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW19 + ReceivedUrl(String), } /// The status of an [`Event`] after being processed. -- cgit From 9fc5ad23edca93553137100d167de7b69e88f785 Mon Sep 17 00:00:00 2001 From: Richard Date: Mon, 5 Jul 2021 16:23:44 -0300 Subject: Initial menu implementation --- native/src/lib.rs | 2 ++ native/src/menu.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 native/src/menu.rs (limited to 'native') diff --git a/native/src/lib.rs b/native/src/lib.rs index cd214e36..56a933f0 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -37,6 +37,7 @@ pub mod clipboard; pub mod event; pub mod keyboard; pub mod layout; +pub mod menu; pub mod mouse; pub mod overlay; pub mod program; @@ -75,6 +76,7 @@ pub use element::Element; pub use event::Event; pub use hasher::Hasher; pub use layout::Layout; +pub use menu::{Menu, MenuEntry}; pub use overlay::Overlay; pub use program::Program; pub use renderer::Renderer; diff --git a/native/src/menu.rs b/native/src/menu.rs new file mode 100644 index 00000000..6c73cb32 --- /dev/null +++ b/native/src/menu.rs @@ -0,0 +1,81 @@ +//! Build menus for your application. +use crate::keyboard::Hotkey; + +/// Menu representation. +/// +/// This can be used by `shell` implementations to create a menu. +#[derive(Debug, Clone, PartialEq)] +pub struct Menu { + items: Vec>, +} + +impl Menu { + /// Creates an empty [`Menu`]. + pub fn new() -> Self { + Menu { items: Vec::new() } + } + + /// Adds an item to the [`Menu`]. + pub fn item>( + mut self, + content: S, + hotkey: impl Into>, + on_activation: Message, + ) -> Self { + let content = content.into(); + let hotkey = hotkey.into(); + + self.items.push(MenuEntry::Item { + on_activation, + content, + hotkey, + }); + self + } + + /// Adds a separator to the [`Menu`]. + pub fn separator(mut self) -> Self { + self.items.push(MenuEntry::Separator); + self + } + + /// Adds a dropdown to the [`Menu`]. + pub fn dropdown>( + mut self, + content: S, + submenu: Menu, + ) -> Self { + let content = content.into(); + + self.items.push(MenuEntry::Dropdown { content, submenu }); + self + } + + /// Returns a [`MenuEntry`] iterator. + pub fn iter(self) -> std::vec::IntoIter> { + self.items.into_iter() + } +} + +/// Represents one of the possible entries used to build a [`Menu`]. +#[derive(Debug, Clone, PartialEq)] +pub enum MenuEntry { + /// Item for a [`Menu`] + Item { + /// The title of the item + content: String, + /// The [`Hotkey`] to activate the item, if any + hotkey: Option, + /// The message generated when the item is activated + on_activation: Message, + }, + /// Dropdown for a [`Menu`] + Dropdown { + /// Title of the dropdown + content: String, + /// The submenu of the dropdown + submenu: Menu, + }, + /// Separator for a [`Menu`] + Separator, +} -- cgit From 1428e9180ae9f4edbf22514bb74c5c7e9df9c712 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 12 Jul 2021 21:38:54 +0200 Subject: Make `Menu` API a bit more functional --- native/src/lib.rs | 2 +- native/src/menu.rs | 75 ++++++++++++++++++++++++++++-------------------------- 2 files changed, 40 insertions(+), 37 deletions(-) (limited to 'native') diff --git a/native/src/lib.rs b/native/src/lib.rs index 56a933f0..564f2514 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -76,7 +76,7 @@ pub use element::Element; pub use event::Event; pub use hasher::Hasher; pub use layout::Layout; -pub use menu::{Menu, MenuEntry}; +pub use menu::Menu; pub use overlay::Overlay; pub use program::Program; pub use renderer::Renderer; diff --git a/native/src/menu.rs b/native/src/menu.rs index 6c73cb32..fe770216 100644 --- a/native/src/menu.rs +++ b/native/src/menu.rs @@ -6,60 +6,35 @@ use crate::keyboard::Hotkey; /// This can be used by `shell` implementations to create a menu. #[derive(Debug, Clone, PartialEq)] pub struct Menu { - items: Vec>, + entries: Vec>, } impl Menu { /// Creates an empty [`Menu`]. pub fn new() -> Self { - Menu { items: Vec::new() } + Self::with_entries(Vec::new()) } - /// Adds an item to the [`Menu`]. - pub fn item>( - mut self, - content: S, - hotkey: impl Into>, - on_activation: Message, - ) -> Self { - let content = content.into(); - let hotkey = hotkey.into(); - - self.items.push(MenuEntry::Item { - on_activation, - content, - hotkey, - }); - self + /// Creates a new [`Menu`] with the given entries. + pub fn with_entries(entries: Vec>) -> Self { + Self { entries } } - /// Adds a separator to the [`Menu`]. - pub fn separator(mut self) -> Self { - self.items.push(MenuEntry::Separator); - self - } - - /// Adds a dropdown to the [`Menu`]. - pub fn dropdown>( - mut self, - content: S, - submenu: Menu, - ) -> Self { - let content = content.into(); - - self.items.push(MenuEntry::Dropdown { content, submenu }); + /// Adds an [`Entry`] to the [`Menu`]. + pub fn push(mut self, entry: Entry) -> Self { + self.entries.push(entry); self } /// Returns a [`MenuEntry`] iterator. - pub fn iter(self) -> std::vec::IntoIter> { - self.items.into_iter() + pub fn iter(self) -> impl Iterator> { + self.entries.into_iter() } } /// Represents one of the possible entries used to build a [`Menu`]. #[derive(Debug, Clone, PartialEq)] -pub enum MenuEntry { +pub enum Entry { /// Item for a [`Menu`] Item { /// The title of the item @@ -79,3 +54,31 @@ pub enum MenuEntry { /// Separator for a [`Menu`] Separator, } + +impl Entry { + /// Creates an [`Entry::Item`]. + pub fn item>( + content: S, + hotkey: impl Into>, + on_activation: Message, + ) -> Self { + let content = content.into(); + let hotkey = hotkey.into(); + + Entry::Item { + content, + hotkey, + on_activation, + } + } + + /// Creates an [`Entry::Dropdown`]. + pub fn dropdown>( + content: S, + submenu: Menu, + ) -> Self { + let content = content.into(); + + Entry::Dropdown { content, submenu } + } +} -- cgit From 735cfb790813c44852612400e31c0190b9c641a6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 12 Jul 2021 21:44:01 +0200 Subject: Move `menu` module from `iced_native` to `iced_core` --- native/src/lib.rs | 6 ++-- native/src/menu.rs | 84 ------------------------------------------------------ 2 files changed, 2 insertions(+), 88 deletions(-) delete mode 100644 native/src/menu.rs (limited to 'native') diff --git a/native/src/lib.rs b/native/src/lib.rs index 564f2514..cbb02506 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -37,7 +37,6 @@ pub mod clipboard; pub mod event; pub mod keyboard; pub mod layout; -pub mod menu; pub mod mouse; pub mod overlay; pub mod program; @@ -62,8 +61,8 @@ mod debug; mod debug; pub use iced_core::{ - Align, Background, Color, Font, HorizontalAlignment, Length, Padding, - Point, Rectangle, Size, Vector, VerticalAlignment, + menu, Align, Background, Color, Font, HorizontalAlignment, Length, Menu, + Padding, Point, Rectangle, Size, Vector, VerticalAlignment, }; pub use iced_futures::{executor, futures, Command}; @@ -76,7 +75,6 @@ pub use element::Element; pub use event::Event; pub use hasher::Hasher; pub use layout::Layout; -pub use menu::Menu; pub use overlay::Overlay; pub use program::Program; pub use renderer::Renderer; diff --git a/native/src/menu.rs b/native/src/menu.rs deleted file mode 100644 index fe770216..00000000 --- a/native/src/menu.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! Build menus for your application. -use crate::keyboard::Hotkey; - -/// Menu representation. -/// -/// This can be used by `shell` implementations to create a menu. -#[derive(Debug, Clone, PartialEq)] -pub struct Menu { - entries: Vec>, -} - -impl Menu { - /// Creates an empty [`Menu`]. - pub fn new() -> Self { - Self::with_entries(Vec::new()) - } - - /// Creates a new [`Menu`] with the given entries. - pub fn with_entries(entries: Vec>) -> Self { - Self { entries } - } - - /// Adds an [`Entry`] to the [`Menu`]. - pub fn push(mut self, entry: Entry) -> Self { - self.entries.push(entry); - self - } - - /// Returns a [`MenuEntry`] iterator. - pub fn iter(self) -> impl Iterator> { - self.entries.into_iter() - } -} - -/// Represents one of the possible entries used to build a [`Menu`]. -#[derive(Debug, Clone, PartialEq)] -pub enum Entry { - /// Item for a [`Menu`] - Item { - /// The title of the item - content: String, - /// The [`Hotkey`] to activate the item, if any - hotkey: Option, - /// The message generated when the item is activated - on_activation: Message, - }, - /// Dropdown for a [`Menu`] - Dropdown { - /// Title of the dropdown - content: String, - /// The submenu of the dropdown - submenu: Menu, - }, - /// Separator for a [`Menu`] - Separator, -} - -impl Entry { - /// Creates an [`Entry::Item`]. - pub fn item>( - content: S, - hotkey: impl Into>, - on_activation: Message, - ) -> Self { - let content = content.into(); - let hotkey = hotkey.into(); - - Entry::Item { - content, - hotkey, - on_activation, - } - } - - /// Creates an [`Entry::Dropdown`]. - pub fn dropdown>( - content: S, - submenu: Menu, - ) -> Self { - let content = content.into(); - - Entry::Dropdown { content, submenu } - } -} -- cgit From b57d567981bb7ef5f9ff397c210778f211d2de5b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 12 Jul 2021 22:01:57 +0200 Subject: Use `bitflags` for `keyboard::Modifiers` --- native/src/widget/text_input.rs | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) (limited to 'native') diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 20117fa0..f06f057b 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -362,7 +362,7 @@ where Event::Keyboard(keyboard::Event::CharacterReceived(c)) if self.state.is_focused && self.state.is_pasting.is_none() - && !self.state.keyboard_modifiers.is_command_pressed() + && !self.state.keyboard_modifiers.command() && !c.is_control() => { let mut editor = @@ -450,7 +450,7 @@ where if platform::is_jump_modifier_pressed(modifiers) && !self.is_secure { - if modifiers.shift { + if modifiers.shift() { self.state .cursor .select_left_by_words(&self.value); @@ -459,7 +459,7 @@ where .cursor .move_left_by_words(&self.value); } - } else if modifiers.shift { + } else if modifiers.shift() { self.state.cursor.select_left(&self.value) } else { self.state.cursor.move_left(&self.value); @@ -469,7 +469,7 @@ where if platform::is_jump_modifier_pressed(modifiers) && !self.is_secure { - if modifiers.shift { + if modifiers.shift() { self.state .cursor .select_right_by_words(&self.value); @@ -478,14 +478,14 @@ where .cursor .move_right_by_words(&self.value); } - } else if modifiers.shift { + } 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 { + if modifiers.shift() { self.state.cursor.select_range( self.state.cursor.start(&self.value), 0, @@ -495,7 +495,7 @@ where } } keyboard::KeyCode::End => { - if modifiers.shift { + if modifiers.shift() { self.state.cursor.select_range( self.state.cursor.start(&self.value), self.value.len(), @@ -505,10 +505,7 @@ where } } keyboard::KeyCode::C - if self - .state - .keyboard_modifiers - .is_command_pressed() => + if self.state.keyboard_modifiers.command() => { match self.state.cursor.selection(&self.value) { Some((start, end)) => { @@ -520,10 +517,7 @@ where } } keyboard::KeyCode::X - if self - .state - .keyboard_modifiers - .is_command_pressed() => + if self.state.keyboard_modifiers.command() => { match self.state.cursor.selection(&self.value) { Some((start, end)) => { @@ -545,7 +539,7 @@ where messages.push(message); } keyboard::KeyCode::V => { - if self.state.keyboard_modifiers.is_command_pressed() { + if self.state.keyboard_modifiers.command() { let content = match self.state.is_pasting.take() { Some(content) => content, None => { @@ -576,10 +570,7 @@ where } } keyboard::KeyCode::A - if self - .state - .keyboard_modifiers - .is_command_pressed() => + if self.state.keyboard_modifiers.command() => { self.state.cursor.select_all(&self.value); } @@ -858,9 +849,9 @@ mod platform { pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool { if cfg!(target_os = "macos") { - modifiers.alt + modifiers.alt() } else { - modifiers.control + modifiers.control() } } } -- cgit From 5df2a92f28be1d53d32d5b42a6645459b7d78efe Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 13 Jul 2021 21:15:07 +0200 Subject: Force `Application::Message` to implement `Clone` A `Message` should represent an application event (e.g. user interactions, command results, subscription results...). Therefore, it should always consist of pure, cloneable data. --- native/src/program.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'native') diff --git a/native/src/program.rs b/native/src/program.rs index 066c29d8..75fab094 100644 --- a/native/src/program.rs +++ b/native/src/program.rs @@ -11,7 +11,7 @@ pub trait Program: Sized { type Renderer: Renderer; /// The type of __messages__ your [`Program`] will produce. - type Message: std::fmt::Debug + Send; + type Message: std::fmt::Debug + Clone + Send; /// The type of [`Clipboard`] your [`Program`] will use. type Clipboard: Clipboard; -- cgit From 2993e9b46674d976443abe1380c0e2b54f934a8e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 15 Jul 2021 16:43:52 +0200 Subject: Fix implementation of `Widget::overlay` for `pane_grid::TitleBar` --- native/src/widget/pane_grid/title_bar.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'native') diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index efdc1e54..48d24c41 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -248,6 +248,22 @@ where &mut self, layout: Layout<'_>, ) -> Option> { - self.content.overlay(layout) + let mut children = layout.children(); + let padded = children.next().unwrap(); + + let mut children = padded.children(); + let title_layout = children.next().unwrap(); + + let Self { + content, controls, .. + } = self; + + content.overlay(title_layout).or_else(move || { + controls.as_mut().and_then(|controls| { + let controls_layout = children.next().unwrap(); + + controls.overlay(controls_layout) + }) + }) } } -- cgit From 4dc1bba5cdc68a7ff3fd3547bd06ff2aa6a7985d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 15 Jul 2021 16:49:19 +0200 Subject: Remove unnecesary use of `Option::unwrap` ... in `overlay` implementation for `pane_grid::TitleBar` --- native/src/widget/pane_grid/title_bar.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'native') diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index 48d24c41..070010f8 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -249,10 +249,10 @@ where layout: Layout<'_>, ) -> Option> { let mut children = layout.children(); - let padded = children.next().unwrap(); + let padded = children.next()?; let mut children = padded.children(); - let title_layout = children.next().unwrap(); + let title_layout = children.next()?; let Self { content, controls, .. @@ -260,7 +260,7 @@ where content.overlay(title_layout).or_else(move || { controls.as_mut().and_then(|controls| { - let controls_layout = children.next().unwrap(); + let controls_layout = children.next()?; controls.overlay(controls_layout) }) -- cgit From fa433743b352f9a27e0669d4da41f645db8b04cb Mon Sep 17 00:00:00 2001 From: Jon Pacheco Date: Sat, 22 May 2021 19:28:17 +0100 Subject: feat: add placeholders to pick_list see issue #726 --- native/src/widget/pick_list.rs | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) (limited to 'native') diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 92c183f3..f83a2e8d 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -25,6 +25,7 @@ where last_selection: &'a mut Option, on_selected: Box Message>, options: Cow<'a, [T]>, + placeholder: Option, selected: Option, width: Length, padding: Padding, @@ -82,6 +83,7 @@ where last_selection, on_selected: Box::new(on_selected), options: options.into(), + placeholder: None, selected, width: Length::Shrink, text_size: None, @@ -91,6 +93,12 @@ where } } + /// Sets the placeholder of the [`PickList`]. + pub fn placeholder(mut self, placeholder: impl Into) -> Self { + self.placeholder = Some(placeholder.into()); + self + } + /// Sets the width of the [`PickList`]. pub fn width(mut self, width: Length) -> Self { self.width = width; @@ -158,8 +166,7 @@ where let max_width = match self.width { Length::Shrink => { let labels = self.options.iter().map(ToString::to_string); - - labels + let labels_width = labels .map(|label| { let (width, _) = renderer.measure( &label, @@ -171,7 +178,24 @@ where width.round() as u32 }) .max() - .unwrap_or(100) + .unwrap_or(100); + + let placeholder_width = self + .placeholder + .as_ref() + .map(|placeholder| { + let (width, _) = renderer.measure( + placeholder, + text_size, + self.font, + Size::new(f32::INFINITY, f32::INFINITY), + ); + + width.round() as u32 + }) + .unwrap_or(100); + + labels_width.max(placeholder_width) } _ => 0, }; @@ -265,6 +289,7 @@ where layout.bounds(), cursor_position, self.selected.as_ref().map(ToString::to_string), + self.placeholder.clone(), self.padding, self.text_size.unwrap_or(renderer.default_size()), self.font, @@ -325,6 +350,7 @@ pub trait Renderer: text::Renderer + menu::Renderer { bounds: Rectangle, cursor_position: Point, selected: Option, + placeholder: Option, padding: Padding, text_size: u16, font: Self::Font, -- cgit From 45bd685f7c12ccb4c03a756b3d694672948c1a03 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 22 Jul 2021 18:55:55 +0700 Subject: Hash `placeholder` in `hash_layout` implementation for `PickList` --- native/src/widget/pick_list.rs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'native') diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index f83a2e8d..fbc091ad 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -219,6 +219,8 @@ where match self.width { Length::Shrink => { + self.placeholder.hash(state); + self.options .iter() .map(ToString::to_string) -- cgit From 26b2a824a930b8f98f4510aa2d2f3aaef7b669b9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 22 Jul 2021 20:09:13 +0700 Subject: Remove duplication of measuring logic in `PickList` --- native/src/widget/pick_list.rs | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) (limited to 'native') diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index fbc091ad..21c0c153 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -162,37 +162,31 @@ where .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); - let labels_width = labels - .map(|label| { - let (width, _) = renderer.measure( - &label, - text_size, - self.font, - 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(|placeholder| { - let (width, _) = renderer.measure( - placeholder, - text_size, - self.font, - Size::new(f32::INFINITY, f32::INFINITY), - ); - - width.round() as u32 - }) + .map(String::as_str) + .map(measure) .unwrap_or(100); labels_width.max(placeholder_width) -- cgit From a866f8742e4ddf5714455519790fed0f961fad66 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 22 Jul 2021 20:16:53 +0700 Subject: Avoid cloning `placeholder` for `PickList` unnecessarily during `draw` --- native/src/widget/pick_list.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'native') diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 21c0c153..4f4e751e 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -285,7 +285,7 @@ where layout.bounds(), cursor_position, self.selected.as_ref().map(ToString::to_string), - self.placeholder.clone(), + self.placeholder.as_ref().map(String::as_str), self.padding, self.text_size.unwrap_or(renderer.default_size()), self.font, @@ -346,7 +346,7 @@ pub trait Renderer: text::Renderer + menu::Renderer { bounds: Rectangle, cursor_position: Point, selected: Option, - placeholder: Option, + placeholder: Option<&str>, padding: Padding, text_size: u16, font: Self::Font, -- cgit From 6069e90c9b1b44c405706e08b608ae39dae0b1f4 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 22 Jul 2021 21:15:35 +0700 Subject: Abstract and improve scroll logic in `PickList` --- native/src/widget/pick_list.rs | 46 +++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 25 deletions(-) (limited to 'native') diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 71c167a6..667c9f18 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -256,37 +256,33 @@ where | mouse::ScrollDelta::Pixels { y, .. } => y, }; - if y.is_sign_negative() { - let mut options_iter = self.options.iter(); + fn find_next<'a, T: PartialEq>( + selected: &'a T, + mut options: impl Iterator, + ) -> 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() { - if let Some(_) = - options_iter.position(|o| o == selected) - { - if let Some(prev_val) = options_iter.next() { - messages - .push((self.on_selected)(prev_val.clone())); - } - } + find_next(selected, self.options.iter()) } else { - messages - .push((self.on_selected)(self.options[0].clone())); + self.options.first() } - } else { - let mut options_iter = self.options.iter().rev(); + } else if y > 0.0 { if let Some(selected) = self.selected.as_ref() { - if let Some(_) = - options_iter.position(|o| o == selected) - { - if let Some(next_val) = options_iter.next() { - messages - .push((self.on_selected)(next_val.clone())); - } - } + find_next(selected, self.options.iter().rev()) } else { - messages.push((self.on_selected)( - self.options[self.options.len() - 1].clone(), - )); + self.options.last() } + } else { + None + }; + + if let Some(next_option) = next_option { + messages.push((self.on_selected)(next_option.clone())); } return event::Status::Captured; -- cgit From 46aab24d91eb083a2f030094e33f98ba8e0dad7f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 22 Jul 2021 21:28:21 +0700 Subject: Enable scroll selection for `PickList` only when scroll is discrete --- native/src/widget/pick_list.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) (limited to 'native') diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 667c9f18..ddd0d0a2 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -247,15 +247,11 @@ where event_status } } - Event::Mouse(mouse::Event::WheelScrolled { delta }) - if layout.bounds().contains(cursor_position) - && !*self.is_open => + Event::Mouse(mouse::Event::WheelScrolled { + delta: mouse::ScrollDelta::Lines { y, .. }, + }) if layout.bounds().contains(cursor_position) + && !*self.is_open => { - let y = match delta { - mouse::ScrollDelta::Lines { y, .. } - | mouse::ScrollDelta::Pixels { y, .. } => y, - }; - fn find_next<'a, T: PartialEq>( selected: &'a T, mut options: impl Iterator, -- cgit