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 +++-- wgpu/Cargo.toml | 2 +- winit/src/application.rs | 1 - winit/src/conversion.rs | 26 +++++++++++++++++++++++- 13 files changed, 165 insertions(+), 47 deletions(-) create mode 100644 native/src/input/touch.rs 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 { diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 6c75af7b..b03fa43f 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -19,7 +19,7 @@ wgpu_glyph = "0.7" glyph_brush = "0.6" raw-window-handle = "0.3" glam = "0.8" -font-kit = "0.4" +font-kit = "0.5.0" log = "0.4" guillotiere = "0.4" diff --git a/winit/src/application.rs b/winit/src/application.rs index 891b8f12..65bffd5b 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -310,7 +310,6 @@ pub trait Application: Sized { physical_size.width, physical_size.height, ); - resized = false; } diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index b6a0b64b..2fc76c9d 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -5,7 +5,7 @@ use crate::{ input::{ keyboard::{self, KeyCode, ModifiersState}, - mouse, ButtonState, + mouse, touch, ButtonState, }, window, Event, Mode, MouseCursor, }; @@ -84,6 +84,9 @@ pub fn window_event( WindowEvent::HoveredFileCancelled => { Some(Event::Window(window::Event::FilesHoveredLeft)) } + WindowEvent::Touch(touch) => { + Some(Event::Touch(touch_event(touch))) + } _ => None, } } @@ -342,3 +345,24 @@ pub(crate) fn is_private_use_character(c: char) -> bool { _ => false, } } +pub(crate) fn touch_event(touch: winit::event::Touch) -> touch::Touch { + let location = touch.location; + match touch.phase { + winit::event::TouchPhase::Started => touch::Touch::Started { + x: location.x as f32, + y: location.y as f32, + }, + winit::event::TouchPhase::Ended => touch::Touch::Ended { + x: location.x as f32, + y: location.y as f32, + }, + winit::event::TouchPhase::Moved => touch::Touch::Moved { + x: location.x as f32, + y: location.y as f32, + }, + winit::event::TouchPhase::Cancelled => touch::Touch::Cancelled { + x: location.x as f32, + y: location.y as f32, + }, + } +} -- 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 +++-- wgpu/Cargo.toml | 2 +- winit/src/conversion.rs | 51 ++++++++++++++---------------- 13 files changed, 153 insertions(+), 114 deletions(-) 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 { diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index b03fa43f..17dfb4a3 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -19,7 +19,7 @@ wgpu_glyph = "0.7" glyph_brush = "0.6" raw-window-handle = "0.3" glam = "0.8" -font-kit = "0.5.0" +font-kit = "0.5" log = "0.4" guillotiere = "0.4" diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 2fc76c9d..1d008d05 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -5,9 +5,9 @@ use crate::{ input::{ keyboard::{self, KeyCode, ModifiersState}, - mouse, touch, ButtonState, + mouse, touch, ButtonState, Touch, }, - window, Event, Mode, MouseCursor, + window, Event, Mode, MouseCursor, Point, }; /// Converts a winit window event into an iced event. @@ -31,8 +31,7 @@ pub fn window_event( let position = position.to_logical::(scale_factor); Some(Event::Mouse(mouse::Event::CursorMoved { - x: position.x as f32, - y: position.y as f32, + position: Point::new(position.x as f32, position.y as f32), })) } WindowEvent::MouseInput { button, state, .. } => { @@ -84,9 +83,7 @@ pub fn window_event( WindowEvent::HoveredFileCancelled => { Some(Event::Window(window::Event::FilesHoveredLeft)) } - WindowEvent::Touch(touch) => { - Some(Event::Touch(touch_event(touch))) - } + WindowEvent::Touch(touch) => Some(Event::Touch(touch_event(touch))), _ => None, } } @@ -162,6 +159,25 @@ pub fn modifiers_state( } } +/// Converts a `Touch` from [`winit`] to an [`iced_native`] touch event. +/// +/// [`winit`]: https://github.com/rust-windowing/winit +/// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native +pub fn touch_event(touch: winit::event::Touch) -> Touch { + let phase = match touch.phase { + winit::event::TouchPhase::Started => touch::Phase::Started, + winit::event::TouchPhase::Moved => touch::Phase::Moved, + winit::event::TouchPhase::Ended => touch::Phase::Ended, + winit::event::TouchPhase::Cancelled => touch::Phase::Canceled, + }; + + Touch { + finger: touch::Finger(touch.id), + position: Point::new(touch.location.x as f32, touch.location.y as f32), + phase, + } +} + /// Converts a `VirtualKeyCode` from [`winit`] to an [`iced_native`] key code. /// /// [`winit`]: https://github.com/rust-windowing/winit @@ -345,24 +361,3 @@ pub(crate) fn is_private_use_character(c: char) -> bool { _ => false, } } -pub(crate) fn touch_event(touch: winit::event::Touch) -> touch::Touch { - let location = touch.location; - match touch.phase { - winit::event::TouchPhase::Started => touch::Touch::Started { - x: location.x as f32, - y: location.y as f32, - }, - winit::event::TouchPhase::Ended => touch::Touch::Ended { - x: location.x as f32, - y: location.y as f32, - }, - winit::event::TouchPhase::Moved => touch::Touch::Moved { - x: location.x as f32, - y: location.y as f32, - }, - winit::event::TouchPhase::Cancelled => touch::Touch::Cancelled { - x: location.x as f32, - y: location.y as f32, - }, - } -} -- 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(-) 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 +++++++++++++++++++++++++++++++++ src/widget.rs | 10 +- wgpu/src/renderer/widget.rs | 2 + wgpu/src/renderer/widget/image_pane.rs | 36 +++ wgpu/src/widget.rs | 8 + wgpu/src/widget/image_pane.rs | 6 + 7 files changed, 470 insertions(+), 2 deletions(-) create mode 100644 native/src/widget/image_pane.rs create mode 100644 wgpu/src/renderer/widget/image_pane.rs create mode 100644 wgpu/src/widget/image_pane.rs 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) + } +} diff --git a/src/widget.rs b/src/widget.rs index e33a6b2c..a1bc8f5b 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -33,6 +33,12 @@ mod platform { pub use iced_winit::image::{Handle, Image}; } + #[cfg_attr(docsrs, doc(cfg(feature = "image")))] + pub mod image_pane { + //! Zoom and pan on an image. + pub use iced_wgpu::image_pane::{ImagePane, State}; + } + #[cfg_attr(docsrs, doc(cfg(feature = "svg")))] pub mod svg { //! Display vector graphics in your user interface. @@ -44,8 +50,8 @@ mod platform { #[doc(no_inline)] pub use { button::Button, checkbox::Checkbox, container::Container, image::Image, - pane_grid::PaneGrid, progress_bar::ProgressBar, radio::Radio, - scrollable::Scrollable, slider::Slider, svg::Svg, + image_pane::ImagePane, pane_grid::PaneGrid, progress_bar::ProgressBar, + radio::Radio, scrollable::Scrollable, slider::Slider, svg::Svg, text_input::TextInput, }; diff --git a/wgpu/src/renderer/widget.rs b/wgpu/src/renderer/widget.rs index 37421fbe..6e1b7fe9 100644 --- a/wgpu/src/renderer/widget.rs +++ b/wgpu/src/renderer/widget.rs @@ -17,3 +17,5 @@ mod svg; #[cfg(feature = "image")] mod image; +#[cfg(feature = "image")] +mod image_pane; diff --git a/wgpu/src/renderer/widget/image_pane.rs b/wgpu/src/renderer/widget/image_pane.rs new file mode 100644 index 00000000..8b032250 --- /dev/null +++ b/wgpu/src/renderer/widget/image_pane.rs @@ -0,0 +1,36 @@ +use crate::{Primitive, Renderer}; +use iced_native::{image, image_pane, MouseCursor, Rectangle, Vector}; + +impl image_pane::Renderer for Renderer { + fn draw( + &mut self, + state: &image_pane::State, + bounds: Rectangle, + image_bounds: Rectangle, + offset: (u32, u32), + handle: image::Handle, + is_mouse_over: bool, + ) -> Self::Output { + ( + { + Primitive::Clip { + bounds, + offset: Vector::new(offset.0, offset.1), + content: Box::new(Primitive::Image { + handle, + bounds: image_bounds, + }), + } + }, + { + if state.is_cursor_clicked() { + MouseCursor::Grabbing + } else if is_mouse_over { + MouseCursor::Grab + } else { + MouseCursor::OutOfBounds + } + }, + ) + } +} diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs index 32ccad17..a62a610d 100644 --- a/wgpu/src/widget.rs +++ b/wgpu/src/widget.rs @@ -47,3 +47,11 @@ pub mod canvas; #[cfg(feature = "canvas")] #[doc(no_inline)] pub use canvas::Canvas; + +#[cfg(feature = "image")] +#[doc(no_inline)] +pub mod image_pane; + +#[cfg(feature = "image")] +#[doc(no_inline)] +pub use image_pane::ImagePane; diff --git a/wgpu/src/widget/image_pane.rs b/wgpu/src/widget/image_pane.rs new file mode 100644 index 00000000..aa30a8cc --- /dev/null +++ b/wgpu/src/widget/image_pane.rs @@ -0,0 +1,6 @@ +//! Zoom and pan on an image. + +pub use iced_native::image_pane::State; + +/// A widget that can display an image with the ability to zoom in/out and pan. +pub type ImagePane<'a> = iced_native::ImagePane<'a>; -- cgit From 7f7e803448e9706d0eec901b32eb4cf35b3ec0b0 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Thu, 23 Apr 2020 16:22:53 -0700 Subject: Show idle cursor if image can't be panned --- wgpu/src/renderer/widget/image_pane.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/wgpu/src/renderer/widget/image_pane.rs b/wgpu/src/renderer/widget/image_pane.rs index 8b032250..a7cee6ac 100644 --- a/wgpu/src/renderer/widget/image_pane.rs +++ b/wgpu/src/renderer/widget/image_pane.rs @@ -25,10 +25,13 @@ impl image_pane::Renderer for Renderer { { if state.is_cursor_clicked() { MouseCursor::Grabbing - } else if is_mouse_over { + } else if is_mouse_over + && (image_bounds.width > bounds.width + || image_bounds.height > bounds.height) + { MouseCursor::Grab } else { - MouseCursor::OutOfBounds + MouseCursor::Idle } }, ) -- 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 ++++++++++------------------------ wgpu/src/renderer/widget/image_pane.rs | 8 ++--- 2 files changed, 20 insertions(+), 43 deletions(-) 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; } } diff --git a/wgpu/src/renderer/widget/image_pane.rs b/wgpu/src/renderer/widget/image_pane.rs index a7cee6ac..b5e86913 100644 --- a/wgpu/src/renderer/widget/image_pane.rs +++ b/wgpu/src/renderer/widget/image_pane.rs @@ -1,5 +1,5 @@ use crate::{Primitive, Renderer}; -use iced_native::{image, image_pane, MouseCursor, Rectangle, Vector}; +use iced_native::{image, image_pane, mouse, Rectangle, Vector}; impl image_pane::Renderer for Renderer { fn draw( @@ -24,14 +24,14 @@ impl image_pane::Renderer for Renderer { }, { if state.is_cursor_clicked() { - MouseCursor::Grabbing + mouse::Interaction::Grabbing } else if is_mouse_over && (image_bounds.width > bounds.width || image_bounds.height > bounds.height) { - MouseCursor::Grab + mouse::Interaction::Grab } else { - MouseCursor::Idle + mouse::Interaction::Idle } }, ) -- 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 +++++++++++++++++++++++++++++++ src/widget.rs | 20 +- wgpu/src/renderer/widget.rs | 2 +- wgpu/src/renderer/widget/image_pane.rs | 39 ---- wgpu/src/renderer/widget/image_viewer.rs | 39 ++++ wgpu/src/widget.rs | 4 +- wgpu/src/widget/image_pane.rs | 6 - wgpu/src/widget/image_viewer.rs | 6 + 10 files changed, 445 insertions(+), 443 deletions(-) delete mode 100644 native/src/widget/image_pane.rs create mode 100644 native/src/widget/image_viewer.rs delete mode 100644 wgpu/src/renderer/widget/image_pane.rs create mode 100644 wgpu/src/renderer/widget/image_viewer.rs delete mode 100644 wgpu/src/widget/image_pane.rs create mode 100644 wgpu/src/widget/image_viewer.rs 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) + } +} diff --git a/src/widget.rs b/src/widget.rs index a1bc8f5b..92303277 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -31,12 +31,7 @@ mod platform { pub mod image { //! Display images in your user interface. pub use iced_winit::image::{Handle, Image}; - } - - #[cfg_attr(docsrs, doc(cfg(feature = "image")))] - pub mod image_pane { - //! Zoom and pan on an image. - pub use iced_wgpu::image_pane::{ImagePane, State}; + pub use iced_winit::image_viewer::{ImageViewer, State}; } #[cfg_attr(docsrs, doc(cfg(feature = "svg")))] @@ -49,9 +44,16 @@ mod platform { #[doc(no_inline)] pub use { - button::Button, checkbox::Checkbox, container::Container, image::Image, - image_pane::ImagePane, pane_grid::PaneGrid, progress_bar::ProgressBar, - radio::Radio, scrollable::Scrollable, slider::Slider, svg::Svg, + button::Button, + checkbox::Checkbox, + container::Container, + image::{Image, ImageViewer}, + pane_grid::PaneGrid, + progress_bar::ProgressBar, + radio::Radio, + scrollable::Scrollable, + slider::Slider, + svg::Svg, text_input::TextInput, }; diff --git a/wgpu/src/renderer/widget.rs b/wgpu/src/renderer/widget.rs index 6e1b7fe9..c9f288c7 100644 --- a/wgpu/src/renderer/widget.rs +++ b/wgpu/src/renderer/widget.rs @@ -18,4 +18,4 @@ mod svg; #[cfg(feature = "image")] mod image; #[cfg(feature = "image")] -mod image_pane; +mod image_viewer; diff --git a/wgpu/src/renderer/widget/image_pane.rs b/wgpu/src/renderer/widget/image_pane.rs deleted file mode 100644 index b5e86913..00000000 --- a/wgpu/src/renderer/widget/image_pane.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::{Primitive, Renderer}; -use iced_native::{image, image_pane, mouse, Rectangle, Vector}; - -impl image_pane::Renderer for Renderer { - fn draw( - &mut self, - state: &image_pane::State, - bounds: Rectangle, - image_bounds: Rectangle, - offset: (u32, u32), - handle: image::Handle, - is_mouse_over: bool, - ) -> Self::Output { - ( - { - Primitive::Clip { - bounds, - offset: Vector::new(offset.0, offset.1), - content: Box::new(Primitive::Image { - handle, - bounds: image_bounds, - }), - } - }, - { - if state.is_cursor_clicked() { - mouse::Interaction::Grabbing - } else if is_mouse_over - && (image_bounds.width > bounds.width - || image_bounds.height > bounds.height) - { - mouse::Interaction::Grab - } else { - mouse::Interaction::Idle - } - }, - ) - } -} diff --git a/wgpu/src/renderer/widget/image_viewer.rs b/wgpu/src/renderer/widget/image_viewer.rs new file mode 100644 index 00000000..b8546d43 --- /dev/null +++ b/wgpu/src/renderer/widget/image_viewer.rs @@ -0,0 +1,39 @@ +use crate::{Primitive, Renderer}; +use iced_native::{image, image_viewer, mouse, Rectangle, Vector}; + +impl image_viewer::Renderer for Renderer { + fn draw( + &mut self, + state: &image_viewer::State, + bounds: Rectangle, + image_bounds: Rectangle, + offset: (u32, u32), + handle: image::Handle, + is_mouse_over: bool, + ) -> Self::Output { + ( + { + Primitive::Clip { + bounds, + offset: Vector::new(offset.0, offset.1), + content: Box::new(Primitive::Image { + handle, + bounds: image_bounds, + }), + } + }, + { + if state.is_cursor_clicked() { + mouse::Interaction::Grabbing + } else if is_mouse_over + && (image_bounds.width > bounds.width + || image_bounds.height > bounds.height) + { + mouse::Interaction::Grab + } else { + mouse::Interaction::Idle + } + }, + ) + } +} diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs index a62a610d..e968c366 100644 --- a/wgpu/src/widget.rs +++ b/wgpu/src/widget.rs @@ -50,8 +50,8 @@ pub use canvas::Canvas; #[cfg(feature = "image")] #[doc(no_inline)] -pub mod image_pane; +pub mod image_viewer; #[cfg(feature = "image")] #[doc(no_inline)] -pub use image_pane::ImagePane; +pub use image_viewer::ImageViewer; diff --git a/wgpu/src/widget/image_pane.rs b/wgpu/src/widget/image_pane.rs deleted file mode 100644 index aa30a8cc..00000000 --- a/wgpu/src/widget/image_pane.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Zoom and pan on an image. - -pub use iced_native::image_pane::State; - -/// A widget that can display an image with the ability to zoom in/out and pan. -pub type ImagePane<'a> = iced_native::ImagePane<'a>; diff --git a/wgpu/src/widget/image_viewer.rs b/wgpu/src/widget/image_viewer.rs new file mode 100644 index 00000000..ec44e30a --- /dev/null +++ b/wgpu/src/widget/image_viewer.rs @@ -0,0 +1,6 @@ +//! Zoom and pan on an image. + +pub use iced_native::image_viewer::State; + +/// A widget that can display an image with the ability to zoom in/out and pan. +pub type ImageViewer<'a> = iced_native::ImageViewer<'a>; -- 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 ------------------------------- src/widget.rs | 16 +- wgpu/src/renderer/widget.rs | 2 - wgpu/src/renderer/widget/image.rs | 2 + wgpu/src/renderer/widget/image/viewer.rs | 39 ++++ wgpu/src/renderer/widget/image_viewer.rs | 39 ---- wgpu/src/widget.rs | 8 - wgpu/src/widget/image_viewer.rs | 6 - 11 files changed, 431 insertions(+), 454 deletions(-) create mode 100644 native/src/widget/image/viewer.rs delete mode 100644 native/src/widget/image_viewer.rs create mode 100644 wgpu/src/renderer/widget/image/viewer.rs delete mode 100644 wgpu/src/renderer/widget/image_viewer.rs delete mode 100644 wgpu/src/widget/image_viewer.rs 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) - } -} diff --git a/src/widget.rs b/src/widget.rs index 92303277..0b0b25db 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -30,8 +30,7 @@ mod platform { #[cfg_attr(docsrs, doc(cfg(feature = "image")))] pub mod image { //! Display images in your user interface. - pub use iced_winit::image::{Handle, Image}; - pub use iced_winit::image_viewer::{ImageViewer, State}; + pub use iced_winit::image::{Handle, Image, State, Viewer}; } #[cfg_attr(docsrs, doc(cfg(feature = "svg")))] @@ -44,16 +43,9 @@ mod platform { #[doc(no_inline)] pub use { - button::Button, - checkbox::Checkbox, - container::Container, - image::{Image, ImageViewer}, - pane_grid::PaneGrid, - progress_bar::ProgressBar, - radio::Radio, - scrollable::Scrollable, - slider::Slider, - svg::Svg, + button::Button, checkbox::Checkbox, container::Container, image::Image, + pane_grid::PaneGrid, progress_bar::ProgressBar, radio::Radio, + scrollable::Scrollable, slider::Slider, svg::Svg, text_input::TextInput, }; diff --git a/wgpu/src/renderer/widget.rs b/wgpu/src/renderer/widget.rs index c9f288c7..37421fbe 100644 --- a/wgpu/src/renderer/widget.rs +++ b/wgpu/src/renderer/widget.rs @@ -17,5 +17,3 @@ mod svg; #[cfg(feature = "image")] mod image; -#[cfg(feature = "image")] -mod image_viewer; diff --git a/wgpu/src/renderer/widget/image.rs b/wgpu/src/renderer/widget/image.rs index c4c04984..d32c078a 100644 --- a/wgpu/src/renderer/widget/image.rs +++ b/wgpu/src/renderer/widget/image.rs @@ -1,3 +1,5 @@ +mod viewer; + use crate::{Primitive, Renderer}; use iced_native::{image, mouse, Layout}; diff --git a/wgpu/src/renderer/widget/image/viewer.rs b/wgpu/src/renderer/widget/image/viewer.rs new file mode 100644 index 00000000..72e5d93b --- /dev/null +++ b/wgpu/src/renderer/widget/image/viewer.rs @@ -0,0 +1,39 @@ +use crate::{Primitive, Renderer}; +use iced_native::{image, mouse, Rectangle, Vector}; + +impl image::viewer::Renderer for Renderer { + fn draw( + &mut self, + state: &image::State, + bounds: Rectangle, + image_bounds: Rectangle, + offset: (u32, u32), + handle: image::Handle, + is_mouse_over: bool, + ) -> Self::Output { + ( + { + Primitive::Clip { + bounds, + offset: Vector::new(offset.0, offset.1), + content: Box::new(Primitive::Image { + handle, + bounds: image_bounds, + }), + } + }, + { + if state.is_cursor_clicked() { + mouse::Interaction::Grabbing + } else if is_mouse_over + && (image_bounds.width > bounds.width + || image_bounds.height > bounds.height) + { + mouse::Interaction::Grab + } else { + mouse::Interaction::Idle + } + }, + ) + } +} diff --git a/wgpu/src/renderer/widget/image_viewer.rs b/wgpu/src/renderer/widget/image_viewer.rs deleted file mode 100644 index b8546d43..00000000 --- a/wgpu/src/renderer/widget/image_viewer.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::{Primitive, Renderer}; -use iced_native::{image, image_viewer, mouse, Rectangle, Vector}; - -impl image_viewer::Renderer for Renderer { - fn draw( - &mut self, - state: &image_viewer::State, - bounds: Rectangle, - image_bounds: Rectangle, - offset: (u32, u32), - handle: image::Handle, - is_mouse_over: bool, - ) -> Self::Output { - ( - { - Primitive::Clip { - bounds, - offset: Vector::new(offset.0, offset.1), - content: Box::new(Primitive::Image { - handle, - bounds: image_bounds, - }), - } - }, - { - if state.is_cursor_clicked() { - mouse::Interaction::Grabbing - } else if is_mouse_over - && (image_bounds.width > bounds.width - || image_bounds.height > bounds.height) - { - mouse::Interaction::Grab - } else { - mouse::Interaction::Idle - } - }, - ) - } -} diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs index e968c366..32ccad17 100644 --- a/wgpu/src/widget.rs +++ b/wgpu/src/widget.rs @@ -47,11 +47,3 @@ pub mod canvas; #[cfg(feature = "canvas")] #[doc(no_inline)] pub use canvas::Canvas; - -#[cfg(feature = "image")] -#[doc(no_inline)] -pub mod image_viewer; - -#[cfg(feature = "image")] -#[doc(no_inline)] -pub use image_viewer::ImageViewer; diff --git a/wgpu/src/widget/image_viewer.rs b/wgpu/src/widget/image_viewer.rs deleted file mode 100644 index ec44e30a..00000000 --- a/wgpu/src/widget/image_viewer.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Zoom and pan on an image. - -pub use iced_native::image_viewer::State; - -/// A widget that can display an image with the ability to zoom in/out and pan. -pub type ImageViewer<'a> = iced_native::ImageViewer<'a>; -- 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 +++++++++++++++++++++---------- wgpu/src/renderer/widget/image/viewer.rs | 13 +- 2 files changed, 204 insertions(+), 96 deletions(-) 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; diff --git a/wgpu/src/renderer/widget/image/viewer.rs b/wgpu/src/renderer/widget/image/viewer.rs index 72e5d93b..f71ca6fb 100644 --- a/wgpu/src/renderer/widget/image/viewer.rs +++ b/wgpu/src/renderer/widget/image/viewer.rs @@ -7,7 +7,7 @@ impl image::viewer::Renderer for Renderer { state: &image::State, bounds: Rectangle, image_bounds: Rectangle, - offset: (u32, u32), + translation: Vector, handle: image::Handle, is_mouse_over: bool, ) -> Self::Output { @@ -15,11 +15,14 @@ impl image::viewer::Renderer for Renderer { { Primitive::Clip { bounds, - offset: Vector::new(offset.0, offset.1), - content: Box::new(Primitive::Image { - handle, - bounds: image_bounds, + content: Box::new(Primitive::Translate { + translation, + content: Box::new(Primitive::Image { + handle, + bounds: image_bounds, + }), }), + offset: Vector::new(0, 0), } }, { -- 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(-) 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 +- src/widget.rs | 4 +++- wgpu/src/renderer/widget/image/viewer.rs | 9 ++++++--- 3 files changed, 10 insertions(+), 5 deletions(-) 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}; diff --git a/src/widget.rs b/src/widget.rs index 0b0b25db..932a8cf6 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -30,7 +30,9 @@ mod platform { #[cfg_attr(docsrs, doc(cfg(feature = "image")))] pub mod image { //! Display images in your user interface. - pub use iced_winit::image::{Handle, Image, State, Viewer}; + pub use iced_winit::image::{Handle, Image, Viewer}; + + pub use iced_winit::image::viewer; } #[cfg_attr(docsrs, doc(cfg(feature = "svg")))] diff --git a/wgpu/src/renderer/widget/image/viewer.rs b/wgpu/src/renderer/widget/image/viewer.rs index f71ca6fb..2599bfa5 100644 --- a/wgpu/src/renderer/widget/image/viewer.rs +++ b/wgpu/src/renderer/widget/image/viewer.rs @@ -1,10 +1,13 @@ use crate::{Primitive, Renderer}; -use iced_native::{image, mouse, Rectangle, Vector}; +use iced_native::{ + image::{self, viewer}, + mouse, Rectangle, Vector, +}; -impl image::viewer::Renderer for Renderer { +impl viewer::Renderer for Renderer { fn draw( &mut self, - state: &image::State, + state: &viewer::State, bounds: Rectangle, image_bounds: Rectangle, translation: Vector, -- 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 --- graphics/src/widget/pane_grid.rs | 28 +++----- native/src/renderer/null.rs | 5 +- native/src/widget/pane_grid.rs | 9 +-- native/src/widget/pane_grid/title_bar.rs | 111 +++++++++++-------------------- 4 files changed, 51 insertions(+), 102 deletions(-) diff --git a/graphics/src/widget/pane_grid.rs b/graphics/src/widget/pane_grid.rs index f09984fc..b4380232 100644 --- a/graphics/src/widget/pane_grid.rs +++ b/graphics/src/widget/pane_grid.rs @@ -12,11 +12,7 @@ use crate::defaults; use crate::{Primitive, Renderer}; use iced_native::mouse; use iced_native::pane_grid; -use iced_native::text; -use iced_native::{ - Element, HorizontalAlignment, Layout, Point, Rectangle, Vector, - VerticalAlignment, -}; +use iced_native::{Element, Layout, Point, Rectangle, Vector}; pub use iced_native::pane_grid::{ Axis, Configuration, Content, Direction, DragEvent, Pane, ResizeEvent, @@ -188,14 +184,12 @@ where defaults: &Self::Defaults, bounds: Rectangle, style_sheet: &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 { let style = style_sheet.style(); + let (title_content, title_layout) = content; let defaults = Self::Defaults { text: defaults::Text { @@ -205,16 +199,12 @@ where let background = crate::widget::container::background(bounds, &style); - let (title_primitive, _) = text::Renderer::draw( + let (title_primitive, title_interaction) = title_content.draw( self, &defaults, - title_bounds, - title, - title_size, - title_font, - None, - HorizontalAlignment::Left, - VerticalAlignment::Top, + title_layout, + cursor_position, + &bounds, ); if let Some((controls, controls_layout)) = controls { @@ -234,7 +224,7 @@ where controls_primitive, ], }, - controls_interaction, + controls_interaction.max(title_interaction), ) } else { ( @@ -245,7 +235,7 @@ where } else { title_primitive }, - mouse::Interaction::default(), + title_interaction, ) } } 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 fb478a4014021d200a76c93d7f93f57371a843d8 Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Tue, 8 Dec 2020 18:47:47 -0600 Subject: Update PaneGrid example with more complex TitleBar --- examples/pane_grid/src/main.rs | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 3c3256cf..c44d24fd 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -1,7 +1,7 @@ use iced::{ button, executor, keyboard, pane_grid, scrollable, Align, Application, - Button, Column, Command, Container, Element, HorizontalAlignment, Length, - PaneGrid, Scrollable, Settings, Subscription, Text, + Button, Color, Column, Command, Container, Element, HorizontalAlignment, + Length, PaneGrid, Row, Scrollable, Settings, Subscription, Text, }; use iced_native::{event, subscription, Event}; @@ -141,10 +141,21 @@ impl Application for Example { let pane_grid = PaneGrid::new(&mut self.panes, |pane, content| { let is_focused = focus == Some(pane); - let title_bar = - pane_grid::TitleBar::new(format!("Pane {}", content.id)) - .padding(10) - .style(style::TitleBar { is_focused }); + let title = Row::with_children(vec![ + Text::new("Pane").into(), + Text::new(content.id.to_string()) + .color(if is_focused { + PANE_ID_COLOR_FOCUSED + } else { + PANE_ID_COLOR_UNFOCUSED + }) + .into(), + ]) + .spacing(5); + + let title_bar = pane_grid::TitleBar::new(title) + .padding(10) + .style(style::TitleBar { is_focused }); pane_grid::Content::new(content.view(pane, total_panes)) .title_bar(title_bar) @@ -165,6 +176,17 @@ impl Application for Example { } } +const PANE_ID_COLOR_UNFOCUSED: Color = Color::from_rgb( + 0xFF as f32 / 255.0, + 0xC7 as f32 / 255.0, + 0xC7 as f32 / 255.0, +); +const PANE_ID_COLOR_FOCUSED: Color = Color::from_rgb( + 0xFF as f32 / 255.0, + 0x47 as f32 / 255.0, + 0x47 as f32 / 255.0, +); + fn handle_hotkey(key_code: keyboard::KeyCode) -> Option { use keyboard::KeyCode; use pane_grid::{Axis, Direction}; -- 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 ++--- winit/src/conversion.rs | 50 ++++++++++++++++++------------- 9 files changed, 88 insertions(+), 117 deletions(-) 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; diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index e6fc4b96..7a6ab2de 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -2,12 +2,11 @@ //! //! [`winit`]: https://github.com/rust-windowing/winit //! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native -use crate::{ - keyboard::{self, KeyCode, Modifiers}, - mouse, - touch::{self, Touch}, - window, Event, Mode, Point, -}; +use crate::keyboard; +use crate::mouse; +use crate::touch; +use crate::window; +use crate::{Event, Mode, Point}; /// Converts a winit window event into an iced event. pub fn window_event( @@ -183,8 +182,10 @@ pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button { /// /// [`winit`]: https://github.com/rust-windowing/winit /// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native -pub fn modifiers(modifiers: winit::event::ModifiersState) -> Modifiers { - Modifiers { +pub fn modifiers( + modifiers: winit::event::ModifiersState, +) -> keyboard::Modifiers { + keyboard::Modifiers { shift: modifiers.shift(), control: modifiers.ctrl(), alt: modifiers.alt(), @@ -206,18 +207,23 @@ pub fn cursor_position( /// /// [`winit`]: https://github.com/rust-windowing/winit /// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native -pub fn touch_event(touch: winit::event::Touch) -> Touch { - let phase = match touch.phase { - winit::event::TouchPhase::Started => touch::Phase::Started, - winit::event::TouchPhase::Moved => touch::Phase::Moved, - winit::event::TouchPhase::Ended => touch::Phase::Ended, - winit::event::TouchPhase::Cancelled => touch::Phase::Canceled, - }; +pub fn touch_event(touch: winit::event::Touch) -> touch::Event { + let id = touch::Finger(touch.id); + let position = Point::new(touch.location.x as f32, touch.location.y as f32); - Touch { - finger: touch::Finger(touch.id), - position: Point::new(touch.location.x as f32, touch.location.y as f32), - phase, + match touch.phase { + winit::event::TouchPhase::Started => { + touch::Event::FingerPressed { id, position } + } + winit::event::TouchPhase::Moved => { + touch::Event::FingerMoved { id, position } + } + winit::event::TouchPhase::Ended => { + touch::Event::FingerLifted { id, position } + } + winit::event::TouchPhase::Cancelled => { + touch::Event::FingerLost { id, position } + } } } @@ -225,7 +231,11 @@ pub fn touch_event(touch: winit::event::Touch) -> Touch { /// /// [`winit`]: https://github.com/rust-windowing/winit /// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native -pub fn key_code(virtual_keycode: winit::event::VirtualKeyCode) -> KeyCode { +pub fn key_code( + virtual_keycode: winit::event::VirtualKeyCode, +) -> keyboard::KeyCode { + use keyboard::KeyCode; + match virtual_keycode { winit::event::VirtualKeyCode::Key1 => KeyCode::Key1, winit::event::VirtualKeyCode::Key2 => KeyCode::Key2, -- cgit From 056f7e6951f6e96da82f35626b8920f6853bea43 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 15 Dec 2020 06:44:00 +0100 Subject: Change cursor position on touch event --- winit/src/application/state.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs index 58bc7ed6..46297370 100644 --- a/winit/src/application/state.rs +++ b/winit/src/application/state.rs @@ -2,7 +2,7 @@ use crate::conversion; use crate::{Application, Color, Debug, Mode, Point, Size, Viewport}; use std::marker::PhantomData; -use winit::event::WindowEvent; +use winit::event::{Touch, WindowEvent}; use winit::window::Window; /// The state of a windowed [`Application`]. @@ -128,7 +128,10 @@ impl State { self.viewport_version = self.viewport_version.wrapping_add(1); } - WindowEvent::CursorMoved { position, .. } => { + WindowEvent::CursorMoved { position, .. } + | WindowEvent::Touch(Touch { + location: position, .. + }) => { self.cursor_position = *position; } WindowEvent::CursorLeft { .. } => { -- 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(-) 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 277ae74d6817ee6192b5f7a44de19dcbdf8d58f7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 15 Dec 2020 06:58:15 +0100 Subject: Produce logical coordinates for `touch::Event` --- winit/src/conversion.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 7a6ab2de..f073c474 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -118,7 +118,9 @@ pub fn window_event( WindowEvent::HoveredFileCancelled => { Some(Event::Window(window::Event::FilesHoveredLeft)) } - WindowEvent::Touch(touch) => Some(Event::Touch(touch_event(*touch))), + WindowEvent::Touch(touch) => { + Some(Event::Touch(touch_event(*touch, scale_factor))) + } _ => None, } } @@ -207,9 +209,16 @@ pub fn cursor_position( /// /// [`winit`]: https://github.com/rust-windowing/winit /// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native -pub fn touch_event(touch: winit::event::Touch) -> touch::Event { +pub fn touch_event( + touch: winit::event::Touch, + scale_factor: f64, +) -> touch::Event { let id = touch::Finger(touch.id); - let position = Point::new(touch.location.x as f32, touch.location.y as f32); + let position = { + let location = touch.location.to_logical::(scale_factor); + + Point::new(location.x as f32, location.y as f32) + }; match touch.phase { winit::event::TouchPhase::Started => { -- cgit From 07b570036aad3f42578876e1a299a0577e6761ae Mon Sep 17 00:00:00 2001 From: Nils Martel Date: Tue, 15 Dec 2020 11:25:15 +0100 Subject: Mention birth year in `README`s --- README.md | 2 +- examples/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 09647a1e..da754686 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ Browse the [documentation] and the [examples] to learn more! Iced was originally born as an attempt at bringing the simplicity of [Elm] and [The Elm Architecture] into [Coffee], a 2D game engine I am working on. -The core of the library was implemented during May in [this pull request]. +The core of the library was implemented during May 2019 in [this pull request]. [The first alpha version] was eventually released as [a renderer-agnostic GUI library]. The library did not provide a renderer and implemented the current [tour example] on top of [`ggez`], a game library. diff --git a/examples/README.md b/examples/README.md index 32ccf724..10c28cf5 100644 --- a/examples/README.md +++ b/examples/README.md @@ -118,7 +118,7 @@ cargo run --package [Ghostscript Tiger]: https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg ## [Coffee] -Since [Iced was born in May], it has been powering the user interfaces in +Since [Iced was born in May 2019], it has been powering the user interfaces in [Coffee], an experimental 2D game engine. @@ -128,6 +128,6 @@ Since [Iced was born in May], it has been powering the user interfaces in -[Iced was born in May]: https://github.com/hecrj/coffee/pull/35 +[Iced was born in May 2019]: https://github.com/hecrj/coffee/pull/35 [`ui` module]: https://docs.rs/coffee/0.3.2/coffee/ui/index.html [Coffee]: https://github.com/hecrj/coffee -- 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(-) 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 74ee7cca811d6f462ca253bced4e3f07e52d9300 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 18 Dec 2020 10:18:39 +0100 Subject: Format use declarations in `image::viewer` --- graphics/src/widget/image/viewer.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graphics/src/widget/image/viewer.rs b/graphics/src/widget/image/viewer.rs index b6217ff7..0bcd09fc 100644 --- a/graphics/src/widget/image/viewer.rs +++ b/graphics/src/widget/image/viewer.rs @@ -2,10 +2,10 @@ use crate::backend::{self, Backend}; use crate::{Primitive, Renderer}; -use iced_native::{ - image::{self, viewer}, - mouse, Rectangle, Vector, -}; +use iced_native::image; +use iced_native::image::viewer; +use iced_native::mouse; +use iced_native::{Rectangle, Vector}; impl viewer::Renderer for Renderer where -- 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(-) 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 0cdf8d56ee515fb56b08ee149683a2216a535950 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 18 Dec 2020 10:44:06 +0100 Subject: Use `image::Viewer` in `pokedex` example --- examples/pokedex/src/main.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs index 187e5dee..f432f0fc 100644 --- a/examples/pokedex/src/main.rs +++ b/examples/pokedex/src/main.rs @@ -1,6 +1,6 @@ use iced::{ button, futures, image, Align, Application, Button, Column, Command, - Container, Element, Image, Length, Row, Settings, Text, + Container, Element, Length, Row, Settings, Text, }; pub fn main() -> iced::Result { @@ -112,16 +112,20 @@ struct Pokemon { name: String, description: String, image: image::Handle, + image_viewer: image::viewer::State, } impl Pokemon { const TOTAL: u16 = 807; - fn view(&self) -> Element { + fn view(&mut self) -> Element { Row::new() .spacing(20) .align_items(Align::Center) - .push(Image::new(self.image.clone())) + .push(image::Viewer::new( + &mut self.image_viewer, + self.image.clone(), + )) .push( Column::new() .spacing(20) @@ -200,6 +204,7 @@ impl Pokemon { .map(|c| if c.is_control() { ' ' } else { c }) .collect(), image, + image_viewer: image::viewer::State::new(), }) } -- 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` --- graphics/src/widget/image/viewer.rs | 14 +++--- native/src/widget/image/viewer.rs | 90 ++++++++++++++++--------------------- 2 files changed, 47 insertions(+), 57 deletions(-) diff --git a/graphics/src/widget/image/viewer.rs b/graphics/src/widget/image/viewer.rs index 0bcd09fc..804f2be6 100644 --- a/graphics/src/widget/image/viewer.rs +++ b/graphics/src/widget/image/viewer.rs @@ -5,7 +5,7 @@ use crate::{Primitive, Renderer}; use iced_native::image; use iced_native::image::viewer; use iced_native::mouse; -use iced_native::{Rectangle, Vector}; +use iced_native::{Rectangle, Size, Vector}; impl viewer::Renderer for Renderer where @@ -15,7 +15,7 @@ where &mut self, state: &viewer::State, bounds: Rectangle, - image_bounds: Rectangle, + image_size: Size, translation: Vector, handle: image::Handle, is_mouse_over: bool, @@ -28,7 +28,11 @@ where translation, content: Box::new(Primitive::Image { handle, - bounds: image_bounds, + bounds: Rectangle { + x: bounds.x, + y: bounds.y, + ..Rectangle::with_size(image_size) + }, }), }), offset: Vector::new(0, 0), @@ -38,8 +42,8 @@ where if state.is_cursor_clicked() { mouse::Interaction::Grabbing } else if is_mouse_over - && (image_bounds.width > bounds.width - || image_bounds.height > bounds.height) + && (image_size.width > bounds.width + || image_size.height > bounds.height) { mouse::Interaction::Grab } else { 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(-) 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(-) 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(-) 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(-) 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 cf97982929c2c5e4487e4ae0236b24749fca3013 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 18 Dec 2020 10:57:51 +0100 Subject: Build `todos` examples instead of `pokedex` in CI --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9e73d3d3..bc531abf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,5 +29,5 @@ jobs: run: cargo check --package iced --target wasm32-unknown-unknown - name: Check compilation of `tour` example run: cargo build --package tour --target wasm32-unknown-unknown - - name: Check compilation of `pokedex` example - run: cargo build --package pokedex --target wasm32-unknown-unknown + - name: Check compilation of `todos` example + run: cargo build --package todos --target wasm32-unknown-unknown -- 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(-) 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(-) 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` --- graphics/src/widget/image/viewer.rs | 2 +- native/src/widget/image/viewer.rs | 21 ++++++++++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/graphics/src/widget/image/viewer.rs b/graphics/src/widget/image/viewer.rs index 804f2be6..28dffc4f 100644 --- a/graphics/src/widget/image/viewer.rs +++ b/graphics/src/widget/image/viewer.rs @@ -39,7 +39,7 @@ where } }, { - if state.is_cursor_clicked() { + if state.is_cursor_grabbed() { mouse::Interaction::Grabbing } else if is_mouse_over && (image_size.width > bounds.width 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(-) 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(-) 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(-) 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(-) 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 81f37123ad23ec5c0812604f1bb8e93fcdf4f6a0 Mon Sep 17 00:00:00 2001 From: Greg V Date: Wed, 25 Nov 2020 22:25:15 +0300 Subject: Update resvg to `0.12` --- wgpu/Cargo.toml | 9 ++++++--- wgpu/src/image/vector.rs | 45 +++++++++++++++++++++++++++++---------------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 5f4699a8..d469da0a 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT AND OFL-1.1" repository = "https://github.com/hecrj/iced" [features] -svg = ["resvg"] +svg = ["resvg", "usvg"] canvas = ["iced_graphics/canvas"] qr_code = ["iced_graphics/qr_code"] default_system_font = ["iced_graphics/font-source"] @@ -40,8 +40,11 @@ version = "0.23" optional = true [dependencies.resvg] -version = "0.9" -features = ["raqote-backend"] +version = "0.12" +optional = true + +[dependencies.usvg] +version = "0.12" optional = true [package.metadata.docs.rs] diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 95df2e99..ab0f67d0 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -3,7 +3,7 @@ use iced_native::svg; use std::collections::{HashMap, HashSet}; pub enum Svg { - Loaded(resvg::usvg::Tree), + Loaded(usvg::Tree), NotFound, } @@ -43,17 +43,15 @@ impl Cache { return self.svgs.get(&handle.id()).unwrap(); } - let opt = resvg::Options::default(); - let svg = match handle.data() { svg::Data::Path(path) => { - match resvg::usvg::Tree::from_file(path, &opt.usvg) { + match usvg::Tree::from_file(path, &Default::default()) { Ok(tree) => Svg::Loaded(tree), Err(_) => Svg::NotFound, } } svg::Data::Bytes(bytes) => { - match resvg::usvg::Tree::from_data(&bytes, &opt.usvg) { + match usvg::Tree::from_data(&bytes, &Default::default()) { Ok(tree) => Svg::Loaded(tree), Err(_) => Svg::NotFound, } @@ -101,23 +99,38 @@ impl Cache { // We currently rerasterize the SVG when its size changes. This is slow // as heck. A GPU rasterizer like `pathfinder` may perform better. // It would be cool to be able to smooth resize the `svg` example. - let screen_size = - resvg::ScreenSize::new(width, height).unwrap(); + let img = resvg::render( + tree, + if width > height { + usvg::FitTo::Width(width) + } else { + usvg::FitTo::Height(height) + }, + None, + )?; + let width = img.width(); + let height = img.height(); - let mut canvas = - resvg::raqote::DrawTarget::new(width as i32, height as i32); + let mut rgba = img.take().into_iter(); - resvg::backend_raqote::render_to_canvas( - tree, - &resvg::Options::default(), - screen_size, - &mut canvas, - ); + // TODO: Perform conversion in the GPU + let bgra: Vec = std::iter::from_fn(move || { + use std::iter::once; + + let r = rgba.next()?; + let g = rgba.next()?; + let b = rgba.next()?; + let a = rgba.next()?; + + Some(once(b).chain(once(g)).chain(once(r)).chain(once(a))) + }) + .flatten() + .collect(); let allocation = texture_atlas.upload( width, height, - bytemuck::cast_slice(canvas.get_data()), + bytemuck::cast_slice(bgra.as_slice()), device, encoder, )?; -- cgit From 4bbfdef14b6983c05d91137f930c15a52da37d86 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 19 Dec 2020 01:24:46 +0100 Subject: Update `font-kit` to `0.10` --- graphics/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 73dc47bf..aac9ccf2 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -44,7 +44,7 @@ version = "0.12" optional = true [dependencies.font-kit] -version = "0.8" +version = "0.10" optional = true [package.metadata.docs.rs] -- 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. --- graphics/src/widget/pane_grid.rs | 5 ++--- native/src/widget/pane_grid.rs | 5 +---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/graphics/src/widget/pane_grid.rs b/graphics/src/widget/pane_grid.rs index b4380232..29478447 100644 --- a/graphics/src/widget/pane_grid.rs +++ b/graphics/src/widget/pane_grid.rs @@ -7,9 +7,8 @@ //! drag and drop, and hotkey support. //! //! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.2/examples/pane_grid -use crate::backend::{self, Backend}; use crate::defaults; -use crate::{Primitive, Renderer}; +use crate::{Backend, Primitive, Renderer}; use iced_native::mouse; use iced_native::pane_grid; use iced_native::{Element, Layout, Point, Rectangle, Vector}; @@ -30,7 +29,7 @@ pub type PaneGrid<'a, Message, Backend> = impl pane_grid::Renderer for Renderer where - B: Backend + backend::Text, + B: Backend, { fn draw( &mut self, 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(-) 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` --- glow/src/widget/pane_grid.rs | 6 +-- graphics/src/widget/pane_grid.rs | 78 +++++++++++++++++++++++++++----- 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 ++-- style/src/lib.rs | 1 + style/src/pane_grid.rs | 51 +++++++++++++++++++++ wgpu/src/widget/pane_grid.rs | 6 +-- 9 files changed, 191 insertions(+), 37 deletions(-) create mode 100644 style/src/pane_grid.rs diff --git a/glow/src/widget/pane_grid.rs b/glow/src/widget/pane_grid.rs index c26dde48..44f9201c 100644 --- a/glow/src/widget/pane_grid.rs +++ b/glow/src/widget/pane_grid.rs @@ -9,9 +9,9 @@ //! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.2/examples/pane_grid use crate::Renderer; -pub use iced_native::pane_grid::{ - Axis, Configuration, Direction, DragEvent, Node, Pane, ResizeEvent, Split, - State, +pub use iced_graphics::pane_grid::{ + Axis, Configuration, Direction, DragEvent, Line, Node, Pane, ResizeEvent, + Split, State, StyleSheet, }; /// A collection of panes distributed using either vertical or horizontal splits diff --git a/graphics/src/widget/pane_grid.rs b/graphics/src/widget/pane_grid.rs index 29478447..3cd4fd34 100644 --- a/graphics/src/widget/pane_grid.rs +++ b/graphics/src/widget/pane_grid.rs @@ -8,16 +8,19 @@ //! //! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.2/examples/pane_grid use crate::defaults; -use crate::{Backend, Primitive, Renderer}; +use crate::{Backend, Color, Primitive, Renderer}; +use iced_native::container; use iced_native::mouse; use iced_native::pane_grid; use iced_native::{Element, Layout, Point, Rectangle, Vector}; pub use iced_native::pane_grid::{ - Axis, Configuration, Content, Direction, DragEvent, Pane, ResizeEvent, - Split, State, TitleBar, + Axis, Configuration, Content, Direction, DragEvent, Node, Pane, + ResizeEvent, Split, State, TitleBar, }; +pub use iced_style::pane_grid::{Line, StyleSheet}; + /// A collection of panes distributed using either vertical or horizontal splits /// to completely fill the space available. /// @@ -31,13 +34,16 @@ impl pane_grid::Renderer for Renderer where B: Backend, { + type Style = Box; + fn draw( &mut self, defaults: &Self::Defaults, content: &[(Pane, Content<'_, Message, Self>)], dragging: Option<(Pane, Point)>, - resizing: Option, + resizing: Option<(Axis, Rectangle, bool)>, layout: Layout<'_>, + style_sheet: &::Style, cursor_position: Point, ) -> Self::Output { let pane_cursor_position = if dragging.is_some() { @@ -73,7 +79,8 @@ where }) .collect(); - let primitives = if let Some((index, layout, origin)) = dragged_pane { + let mut primitives = if let Some((index, layout, origin)) = dragged_pane + { let pane = panes.remove(index); let bounds = layout.bounds(); @@ -103,15 +110,62 @@ where panes }; + let (primitives, mouse_interaction) = + if let Some((axis, split_region, is_picked)) = resizing { + let highlight = if is_picked { + style_sheet.picked_split() + } else { + style_sheet.hovered_split() + }; + + if let Some(highlight) = highlight { + primitives.push(Primitive::Quad { + bounds: match axis { + Axis::Horizontal => Rectangle { + x: split_region.x, + y: (split_region.y + + (split_region.height - highlight.width) + / 2.0) + .round(), + width: split_region.width, + height: highlight.width, + }, + Axis::Vertical => Rectangle { + x: (split_region.x + + (split_region.width - highlight.width) + / 2.0) + .round(), + y: split_region.y, + width: highlight.width, + height: split_region.height, + }, + }, + background: highlight.color.into(), + border_radius: 0.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + }); + } + + ( + primitives, + match axis { + Axis::Horizontal => { + mouse::Interaction::ResizingVertically + } + Axis::Vertical => { + mouse::Interaction::ResizingHorizontally + } + }, + ) + } else { + (primitives, mouse_interaction) + }; + ( Primitive::Group { primitives }, if dragging.is_some() { mouse::Interaction::Grabbing - } else if let Some(axis) = resizing { - match axis { - Axis::Horizontal => mouse::Interaction::ResizingVertically, - Axis::Vertical => mouse::Interaction::ResizingHorizontally, - } } else { mouse_interaction }, @@ -122,7 +176,7 @@ where &mut self, defaults: &Self::Defaults, bounds: Rectangle, - style_sheet: &Self::Style, + style_sheet: &::Style, title_bar: Option<(&TitleBar<'_, Message, Self>, Layout<'_>)>, body: (&Element<'_, Message, Self>, Layout<'_>), cursor_position: Point, @@ -182,7 +236,7 @@ where &mut self, defaults: &Self::Defaults, bounds: Rectangle, - style_sheet: &Self::Style, + style_sheet: &::Style, content: (&Element<'_, Message, Self>, Layout<'_>), controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>, cursor_position: Point, 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 } diff --git a/style/src/lib.rs b/style/src/lib.rs index 7e0a9f49..f09b5f9d 100644 --- a/style/src/lib.rs +++ b/style/src/lib.rs @@ -10,6 +10,7 @@ pub mod button; pub mod checkbox; pub mod container; pub mod menu; +pub mod pane_grid; pub mod pick_list; pub mod progress_bar; pub mod radio; diff --git a/style/src/pane_grid.rs b/style/src/pane_grid.rs new file mode 100644 index 00000000..e39ee797 --- /dev/null +++ b/style/src/pane_grid.rs @@ -0,0 +1,51 @@ +//! Let your users split regions of your application and organize layout +//! dynamically. +use iced_core::Color; + +/// A set of rules that dictate the style of a container. +pub trait StyleSheet { + /// The [`Line`] to draw when a split is picked. + fn picked_split(&self) -> Option; + + /// The [`Line`] to draw when a split is hovered. + fn hovered_split(&self) -> Option; +} + +/// A line. +/// +/// It is normally used to define the highlight of something, like a split. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Line { + /// The [`Color`] of the [`Line`]. + pub color: Color, + + /// The width of the [`Line`]. + pub width: f32, +} + +struct Default; + +impl StyleSheet for Default { + fn picked_split(&self) -> Option { + None + } + + fn hovered_split(&self) -> Option { + None + } +} + +impl std::default::Default for Box { + fn default() -> Self { + Box::new(Default) + } +} + +impl From for Box +where + T: 'static + StyleSheet, +{ + fn from(style: T) -> Self { + Box::new(style) + } +} diff --git a/wgpu/src/widget/pane_grid.rs b/wgpu/src/widget/pane_grid.rs index c26dde48..44f9201c 100644 --- a/wgpu/src/widget/pane_grid.rs +++ b/wgpu/src/widget/pane_grid.rs @@ -9,9 +9,9 @@ //! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.2/examples/pane_grid use crate::Renderer; -pub use iced_native::pane_grid::{ - Axis, Configuration, Direction, DragEvent, Node, Pane, ResizeEvent, Split, - State, +pub use iced_graphics::pane_grid::{ + Axis, Configuration, Direction, DragEvent, Line, Node, Pane, ResizeEvent, + Split, State, StyleSheet, }; /// A collection of panes distributed using either vertical or horizontal splits -- cgit From 50452e62b4df458d676fc95361b04ef2dd83aebf Mon Sep 17 00:00:00 2001 From: Yusuf Bera Ertan Date: Mon, 4 Jan 2021 22:58:39 +0300 Subject: Update `tokio` to `1.0` --- examples/game_of_life/Cargo.toml | 2 +- futures/Cargo.toml | 5 +++-- futures/src/time.rs | 18 +++++++++++++++--- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/examples/game_of_life/Cargo.toml b/examples/game_of_life/Cargo.toml index 9c4172c4..ffd2f19e 100644 --- a/examples/game_of_life/Cargo.toml +++ b/examples/game_of_life/Cargo.toml @@ -7,6 +7,6 @@ publish = false [dependencies] iced = { path = "../..", features = ["canvas", "tokio", "debug"] } -tokio = { version = "0.3", features = ["sync"] } +tokio = { version = "1.0", features = ["sync"] } itertools = "0.9" rustc-hash = "1.1" diff --git a/futures/Cargo.toml b/futures/Cargo.toml index e8e47c08..92b504a6 100644 --- a/futures/Cargo.toml +++ b/futures/Cargo.toml @@ -26,9 +26,10 @@ optional = true features = ["rt-core", "rt-threaded", "time", "stream"] [target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio] -version = "0.3" +package = "tokio" +version = "1.0" optional = true -features = ["rt-multi-thread", "time", "stream"] +features = ["rt", "rt-multi-thread", "time"] [target.'cfg(not(target_arch = "wasm32"))'.dependencies.async-std] version = "1.0" diff --git a/futures/src/time.rs b/futures/src/time.rs index 5e9ea436..7458af06 100644 --- a/futures/src/time.rs +++ b/futures/src/time.rs @@ -67,8 +67,20 @@ where let start = tokio::time::Instant::now() + self.0; - tokio::time::interval_at(start, self.0) - .map(|_| std::time::Instant::now()) - .boxed() + let stream = { + #[cfg(feature = "tokio")] + { + futures::stream::unfold( + tokio::time::interval_at(start, self.0), + |mut interval| async move { + Some((interval.tick().await, interval)) + }, + ) + } + #[cfg(feature = "tokio_old")] + tokio::time::interval_at(start, self.0) + }; + + stream.map(|_| std::time::Instant::now()).boxed() } } -- cgit From 09ea73bd2a43ecbcae56cb281aa8d0cce2d5f55d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 4 Jan 2021 23:19:15 +0100 Subject: Use `Instant::into_std` in `futures::time` --- futures/src/time.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/futures/src/time.rs b/futures/src/time.rs index 7458af06..d015d2f0 100644 --- a/futures/src/time.rs +++ b/futures/src/time.rs @@ -81,6 +81,6 @@ where tokio::time::interval_at(start, self.0) }; - stream.map(|_| std::time::Instant::now()).boxed() + stream.map(tokio::time::Instant::into_std).boxed() } } -- cgit From 31522e30aa1436f634a2675ba69d12495ff672bc Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 4 Jan 2021 23:38:07 +0100 Subject: Remove unnecessary `SaveError` variant in `todos` example --- examples/todos/src/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index ccee2703..b47df5df 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -489,7 +489,6 @@ enum LoadError { #[derive(Debug, Clone)] enum SaveError { - DirectoryError, FileError, WriteError, FormatError, @@ -538,7 +537,7 @@ impl SavedState { if let Some(dir) = path.parent() { async_std::fs::create_dir_all(dir) .await - .map_err(|_| SaveError::DirectoryError)?; + .map_err(|_| SaveError::FileError)?; } { -- 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(-) 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(-) 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 b2415eee61063d5c2b220d7b7a513d1952ce2be1 Mon Sep 17 00:00:00 2001 From: Jayce Fayne Date: Wed, 13 Jan 2021 01:48:35 +0100 Subject: Add `smol` async runtime --- Cargo.toml | 2 ++ futures/Cargo.toml | 4 ++++ futures/src/executor.rs | 6 ++++++ futures/src/executor/smol.rs | 18 ++++++++++++++++++ futures/src/lib.rs | 16 ++++++++++++++-- futures/src/time.rs | 29 ++++++++++++++++++++++++++++- src/executor.rs | 30 ++++++++++++++++++++++++------ src/lib.rs | 8 +++++++- 8 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 futures/src/executor/smol.rs diff --git a/Cargo.toml b/Cargo.toml index 6221ae4b..75499df9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,8 @@ tokio = ["iced_futures/tokio"] tokio_old = ["iced_futures/tokio_old"] # Enables `async-std` as the `executor::Default` on native platforms async-std = ["iced_futures/async-std"] +# Enables `smol` as the `executor::Default` on native platforms +smol = ["iced_futures/smol"] # Enables advanced color conversion via `palette` palette = ["iced_core/palette"] diff --git a/futures/Cargo.toml b/futures/Cargo.toml index 92b504a6..c42cc603 100644 --- a/futures/Cargo.toml +++ b/futures/Cargo.toml @@ -36,6 +36,10 @@ version = "1.0" optional = true features = ["unstable"] +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.smol] +version = "1.0" +optional = true + [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen-futures = "0.4" diff --git a/futures/src/executor.rs b/futures/src/executor.rs index fa87216a..b35b5bc1 100644 --- a/futures/src/executor.rs +++ b/futures/src/executor.rs @@ -13,6 +13,9 @@ mod tokio_old; #[cfg(all(not(target_arch = "wasm32"), feature = "async-std"))] mod async_std; +#[cfg(all(not(target_arch = "wasm32"), feature = "smol"))] +mod smol; + #[cfg(target_arch = "wasm32")] mod wasm_bindgen; @@ -30,6 +33,9 @@ pub use self::tokio_old::TokioOld; #[cfg(all(not(target_arch = "wasm32"), feature = "async-std"))] pub use self::async_std::AsyncStd; +#[cfg(all(not(target_arch = "wasm32"), feature = "smol"))] +pub use self::smol::Smol; + #[cfg(target_arch = "wasm32")] pub use wasm_bindgen::WasmBindgen; diff --git a/futures/src/executor/smol.rs b/futures/src/executor/smol.rs new file mode 100644 index 00000000..deafd43a --- /dev/null +++ b/futures/src/executor/smol.rs @@ -0,0 +1,18 @@ +use crate::Executor; + +use futures::Future; + +/// A `smol` runtime. +#[cfg_attr(docsrs, doc(cfg(feature = "smol")))] +#[derive(Debug)] +pub struct Smol; + +impl Executor for Smol { + fn new() -> Result { + Ok(Self) + } + + fn spawn(&self, future: impl Future + Send + 'static) { + smol::spawn(future).detach(); + } +} diff --git a/futures/src/lib.rs b/futures/src/lib.rs index c7c6fd3a..01cf5c89 100644 --- a/futures/src/lib.rs +++ b/futures/src/lib.rs @@ -17,10 +17,22 @@ pub mod executor; pub mod subscription; #[cfg(all( - any(feature = "tokio", feature = "tokio_old", feature = "async-std"), + any( + feature = "tokio", + feature = "tokio_old", + feature = "async-std", + feature = "smol" + ), not(target_arch = "wasm32") ))] -#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio", feature = "async-std"))))] +#[cfg_attr( + docsrs, + doc(cfg(any( + feature = "tokio", + feature = "async-std", + feature = "smol" + ))) +)] pub mod time; pub use command::Command; diff --git a/futures/src/time.rs b/futures/src/time.rs index d015d2f0..86b4a4e7 100644 --- a/futures/src/time.rs +++ b/futures/src/time.rs @@ -13,6 +13,33 @@ pub fn every( struct Every(std::time::Duration); +#[cfg(all( + not(any(feature = "tokio_old", feature = "tokio", feature = "async-std")), + feature = "smol" +))] +impl subscription::Recipe for Every +where + H: std::hash::Hasher, +{ + type Output = std::time::Instant; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::().hash(state); + self.0.hash(state); + } + + fn stream( + self: Box, + _input: futures::stream::BoxStream<'static, E>, + ) -> futures::stream::BoxStream<'static, Self::Output> { + use futures::stream::StreamExt; + + smol::Timer::interval(self.0).boxed() + } +} + #[cfg(feature = "async-std")] impl subscription::Recipe for Every where @@ -41,7 +68,7 @@ where #[cfg(all( any(feature = "tokio", feature = "tokio_old"), - not(feature = "async-std") + not(any(feature = "async-std", feature = "smol")) ))] impl subscription::Recipe for Every where diff --git a/src/executor.rs b/src/executor.rs index 0333bc1d..2c6d9ec0 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -7,22 +7,40 @@ pub use platform::Default; mod platform { use iced_futures::{executor, futures}; - #[cfg(feature = "tokio_old")] + #[cfg(all( + not(any(feature = "tokio", feature = "smol", feature = "async-std")), + feature = "tokio_old" + ))] type Executor = executor::TokioOld; - #[cfg(all(not(feature = "tokio_old"), feature = "tokio"))] + #[cfg(all( + not(any( + feature = "tokio_old", + feature = "smol", + feature = "async-std" + )), + feature = "tokio" + ))] type Executor = executor::Tokio; + #[cfg(feature = "async-std")] + type Executor = executor::AsyncStd; + #[cfg(all( - not(any(feature = "tokio_old", feature = "tokio")), - feature = "async-std" + not(any( + feature = "tokio_old", + feature = "tokio", + feature = "async-std" + )), + feature = "smol" ))] - type Executor = executor::AsyncStd; + type Executor = executor::Smol; #[cfg(not(any( feature = "tokio_old", feature = "tokio", - feature = "async-std" + feature = "async-std", + feature = "smol", )))] type Executor = executor::ThreadPool; diff --git a/src/lib.rs b/src/lib.rs index 3578ea82..dedcac7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -191,7 +191,12 @@ pub mod widget; pub mod window; #[cfg(all( - any(feature = "tokio", feature = "tokio_old", feature = "async-std"), + any( + feature = "tokio", + feature = "tokio_old", + feature = "async-std", + feature = "smol" + ), not(target_arch = "wasm32") ))] #[cfg_attr( @@ -200,6 +205,7 @@ pub mod window; feature = "tokio", feature = "tokio_old", feature = "async-std" + feature = "smol" ))) )] pub mod time; -- cgit From c542224f4b466f9de103d8f67e20ce48583a5461 Mon Sep 17 00:00:00 2001 From: Jayce Fayne Date: Wed, 13 Jan 2021 17:15:47 +0100 Subject: Update `CHANGELOG.md` --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee650337..e4d1a4fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added - `"system_font"` feature gates reading system fonts. [#370] +- Support for the [`smol`] async runtime. [#699] [#370]: https://github.com/hecrj/iced/pull/370 +[#699]: https://github.com/hecrj/iced/pull/699 +[`smol`]: https://github.com/smol-rs/smol ## [0.1.1] - 2020-04-15 ### Added -- cgit From 766bb7f5ccf4ac9294fab92a5126c60c0fa8d818 Mon Sep 17 00:00:00 2001 From: Tanner Rogalsky Date: Thu, 14 Jan 2021 10:38:12 -0500 Subject: Fix `physical_width` getter incorrectly returning the height --- graphics/src/viewport.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphics/src/viewport.rs b/graphics/src/viewport.rs index 78d539af..2c0b541a 100644 --- a/graphics/src/viewport.rs +++ b/graphics/src/viewport.rs @@ -31,7 +31,7 @@ impl Viewport { /// Returns the physical width of the [`Viewport`]. pub fn physical_width(&self) -> u32 { - self.physical_size.height + self.physical_size.width } /// Returns the physical height of the [`Viewport`]. -- 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(-) 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 +++ winit/src/conversion.rs | 3 +++ 2 files changed, 6 insertions(+) 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), } diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index f073c474..5265b844 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -121,6 +121,9 @@ pub fn window_event( WindowEvent::Touch(touch) => { Some(Event::Touch(touch_event(*touch, scale_factor))) } + WindowEvent::Focused(focused) => { + Some(Event::Window(window::Event::Focused(*focused))) + } _ => None, } } -- 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 +++++++---- winit/src/conversion.rs | 8 +++++--- 2 files changed, 12 insertions(+), 7 deletions(-) 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), } diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 5265b844..0e04b35d 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -109,6 +109,11 @@ pub fn window_event( WindowEvent::ModifiersChanged(new_modifiers) => Some(Event::Keyboard( keyboard::Event::ModifiersChanged(self::modifiers(*new_modifiers)), )), + WindowEvent::Focused(focused) => Some(Event::Window(if *focused { + window::Event::Focused + } else { + window::Event::Unfocused + })), WindowEvent::HoveredFile(path) => { Some(Event::Window(window::Event::FileHovered(path.clone()))) } @@ -121,9 +126,6 @@ pub fn window_event( WindowEvent::Touch(touch) => { Some(Event::Touch(touch_event(*touch, scale_factor))) } - WindowEvent::Focused(focused) => { - Some(Event::Window(window::Event::Focused(*focused))) - } _ => None, } } -- cgit From a42a0844c2ffc51e117304a5d08e4a0fb52adac7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 15 Jan 2021 18:31:30 +0100 Subject: Keep old behavior for `Executor` feature flags --- src/executor.rs | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/executor.rs b/src/executor.rs index 2c6d9ec0..9f3656b1 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -7,32 +7,25 @@ pub use platform::Default; mod platform { use iced_futures::{executor, futures}; - #[cfg(all( - not(any(feature = "tokio", feature = "smol", feature = "async-std")), - feature = "tokio_old" - ))] + #[cfg(feature = "tokio_old")] type Executor = executor::TokioOld; - #[cfg(all( - not(any( - feature = "tokio_old", - feature = "smol", - feature = "async-std" - )), - feature = "tokio" - ))] + #[cfg(all(feature = "tokio", not(feature = "tokio_old")))] type Executor = executor::Tokio; - #[cfg(feature = "async-std")] + #[cfg(all( + feature = "async-std", + not(any(feature = "tokio_old", feature = "tokio")), + ))] type Executor = executor::AsyncStd; #[cfg(all( + feature = "smol", not(any( feature = "tokio_old", feature = "tokio", feature = "async-std" )), - feature = "smol" ))] type Executor = executor::Smol; -- cgit From bcc54b0831d3794e2105f591db4c325615d35041 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 15 Jan 2021 18:37:20 +0100 Subject: Add latest release (`0.2.0`) to `CHANGELOG` --- CHANGELOG.md | 49 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4d1a4fd..70673f79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,13 +6,55 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- `"system_font"` feature gates reading system fonts. [#370] - Support for the [`smol`] async runtime. [#699] -[#370]: https://github.com/hecrj/iced/pull/370 [#699]: https://github.com/hecrj/iced/pull/699 [`smol`]: https://github.com/smol-rs/smol +## [0.2.0] - 2020-11-26 +- __[`Canvas` interactivity][canvas]__ (#325) + A trait-based approach to react to mouse and keyboard interactions in [the `Canvas` widget][#193]. + +- __[`iced_graphics` subcrate][opengl]__ (#354) + A backend-agnostic graphics subcrate that can be leveraged to build new renderers. + +- __[OpenGL renderer][opengl]__ (#354) + An OpenGL renderer powered by [`iced_graphics`], [`glow`], and [`glutin`]. It is an alternative to the default [`wgpu`] renderer. + +- __[Overlay support][pick_list]__ (#444) + Basic support for superpositioning interactive widgets on top of other widgets. + +- __[Faster event loop][view]__ (#597) + The event loop now takes advantage of the data dependencies in [The Elm Architecture] and leverages the borrow checker to keep the widget tree alive between iterations, avoiding unnecessary rebuilds. + +- __[Event capturing][event]__ (#614) + The runtime now can tell whether a widget has handled an event or not, easing [integration with existing applications]. + +- __[`PickList` widget][pick_list]__ (#444) + A drop-down selector widget built on top of the new overlay support. + +- __[`QRCode` widget][qr_code]__ (#622) + A widget that displays a QR code, powered by [the `qrcode` crate]. + +[canvas]: https://github.com/hecrj/iced/pull/325 +[opengl]: https://github.com/hecrj/iced/pull/354 +[`iced_graphics`]: https://github.com/hecrj/iced/pull/354 +[pane_grid]: https://github.com/hecrj/iced/pull/397 +[pick_list]: https://github.com/hecrj/iced/pull/444 +[error]: https://github.com/hecrj/iced/pull/514 +[view]: https://github.com/hecrj/iced/pull/597 +[event]: https://github.com/hecrj/iced/pull/614 +[color]: https://github.com/hecrj/iced/pull/200 +[qr_code]: https://github.com/hecrj/iced/pull/622 +[#193]: https://github.com/hecrj/iced/pull/193 +[`glutin`]: https://github.com/rust-windowing/glutin +[`wgpu`]: https://github.com/gfx-rs/wgpu-rs +[`glow`]: https://github.com/grovesNL/glow +[the `qrcode` crate]: https://docs.rs/qrcode/0.12.0/qrcode/ +[integration with existing applications]: https://github.com/hecrj/iced/pull/183 +[The Elm Architecture]: https://guide.elm-lang.org/architecture/ + + ## [0.1.1] - 2020-04-15 ### Added - `Settings::with_flags` to easily initialize some default settings with flags. [#266] @@ -117,7 +159,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - First release! :tada: -[Unreleased]: https://github.com/hecrj/iced/compare/0.1.1...HEAD +[Unreleased]: https://github.com/hecrj/iced/compare/0.2.0...HEAD +[0.2.0]: https://github.com/hecrj/iced/compare/0.1.1...0.2.0 [0.1.1]: https://github.com/hecrj/iced/compare/0.1.0...0.1.1 [0.1.0]: https://github.com/hecrj/iced/compare/0.1.0-beta...0.1.0 [0.1.0-beta]: https://github.com/hecrj/iced/compare/0.1.0-alpha...0.1.0-beta -- cgit From fd2c96c8e36eb37ea4a53aafe0986b569a4e3753 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 15 Jan 2021 18:52:12 +0100 Subject: Fix `time::Every` implementation for `smol` runtime --- examples/stopwatch/Cargo.toml | 2 +- futures/Cargo.toml | 2 +- futures/src/time.rs | 10 +++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/examples/stopwatch/Cargo.toml b/examples/stopwatch/Cargo.toml index 075aa111..9f935951 100644 --- a/examples/stopwatch/Cargo.toml +++ b/examples/stopwatch/Cargo.toml @@ -6,4 +6,4 @@ edition = "2018" publish = false [dependencies] -iced = { path = "../..", features = ["tokio"] } +iced = { path = "../..", features = ["smol"] } diff --git a/futures/Cargo.toml b/futures/Cargo.toml index c42cc603..c266f705 100644 --- a/futures/Cargo.toml +++ b/futures/Cargo.toml @@ -37,7 +37,7 @@ optional = true features = ["unstable"] [target.'cfg(not(target_arch = "wasm32"))'.dependencies.smol] -version = "1.0" +version = "1.2" optional = true [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/futures/src/time.rs b/futures/src/time.rs index 86b4a4e7..c11942d2 100644 --- a/futures/src/time.rs +++ b/futures/src/time.rs @@ -35,8 +35,16 @@ where _input: futures::stream::BoxStream<'static, E>, ) -> futures::stream::BoxStream<'static, Self::Output> { use futures::stream::StreamExt; + use std::time::Instant; - smol::Timer::interval(self.0).boxed() + let duration = self.0; + + futures::stream::unfold(Instant::now(), move |last_tick| async move { + let last_tick = smol::Timer::at(last_tick + duration).await; + + Some((last_tick, last_tick)) + }) + .boxed() } } -- cgit From 803cc88483c310797911636562a98a6ebba33af7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 15 Jan 2021 19:05:20 +0100 Subject: Fix consistency in `CHANGELOG` --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70673f79..b0cb739d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [`smol`]: https://github.com/smol-rs/smol ## [0.2.0] - 2020-11-26 +### Added - __[`Canvas` interactivity][canvas]__ (#325) A trait-based approach to react to mouse and keyboard interactions in [the `Canvas` widget][#193]. -- cgit From 9da0a3de54e8e7161d4a3405633bc574ce43eb2d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 15 Jan 2021 21:07:37 +0100 Subject: Use `smol::Timer::interval` for `time::Every` --- futures/src/time.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/futures/src/time.rs b/futures/src/time.rs index c11942d2..86b4a4e7 100644 --- a/futures/src/time.rs +++ b/futures/src/time.rs @@ -35,16 +35,8 @@ where _input: futures::stream::BoxStream<'static, E>, ) -> futures::stream::BoxStream<'static, Self::Output> { use futures::stream::StreamExt; - use std::time::Instant; - let duration = self.0; - - futures::stream::unfold(Instant::now(), move |last_tick| async move { - let last_tick = smol::Timer::at(last_tick + duration).await; - - Some((last_tick, last_tick)) - }) - .boxed() + smol::Timer::interval(self.0).boxed() } } -- cgit From d1c4239ac7ffdf299e4f9fae36406361cfef9267 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 21 Jan 2021 05:07:41 +0100 Subject: Disable default features of `qrcode` for `iced_graphics` --- graphics/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index aac9ccf2..ab41ac44 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -42,6 +42,7 @@ optional = true [dependencies.qrcode] version = "0.12" optional = true +default-features = false [dependencies.font-kit] version = "0.10" -- cgit From 8d882d787e6b7fd7c2435f42f82933e2ed904edf Mon Sep 17 00:00:00 2001 From: GunpowderGuy Date: Thu, 21 Jan 2021 19:36:05 -0500 Subject: update example description at sandbox's documentation (#710) * Update sandbox.rs * Update sandbox.rs * format Co-authored-by: Diego Rosario --- src/sandbox.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sandbox.rs b/src/sandbox.rs index dbaa02f1..b53fa1f2 100644 --- a/src/sandbox.rs +++ b/src/sandbox.rs @@ -17,8 +17,8 @@ use crate::{ /// # Examples /// [The repository has a bunch of examples] that use the [`Sandbox`] trait: /// -/// - [`bezier_tool`], a Paint-like tool for drawing Bézier curves using -/// [`lyon`]. +/// - [`bezier_tool`], a Paint-like tool for drawing Bézier curves using the +/// [`Canvas widget`]. /// - [`counter`], the classic counter example explained in [the overview]. /// - [`custom_widget`], a demonstration of how to build a custom widget that /// draws a circle. @@ -45,7 +45,7 @@ use crate::{ /// [`styling`]: https://github.com/hecrj/iced/tree/0.2/examples/styling /// [`svg`]: https://github.com/hecrj/iced/tree/0.2/examples/svg /// [`tour`]: https://github.com/hecrj/iced/tree/0.2/examples/tour -/// [`lyon`]: https://github.com/nical/lyon +/// [`Canvas widget`]: crate::widget::Canvas /// [the overview]: index.html#overview /// [`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.2/wgpu /// [`Svg` widget]: crate::widget::Svg -- cgit From 14d900d8359a2b011e1f90fd54f7ad76b103b459 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Fri, 29 Jan 2021 14:03:27 +0900 Subject: Make iced::Error Send + Sync --- src/error.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index 31b87d17..c8fa6636 100644 --- a/src/error.rs +++ b/src/error.rs @@ -9,7 +9,7 @@ pub enum Error { /// The application window could not be created. #[error("the application window could not be created")] - WindowCreationFailed(Box), + WindowCreationFailed(Box), /// A suitable graphics adapter or device could not be found. #[error("a suitable graphics adapter or device could not be found")] @@ -32,3 +32,14 @@ impl From for Error { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn assert_send_sync() { + fn _assert() {} + _assert::(); + } +} -- cgit From 2969558afd7da2443641e81ee84b4faa99eceba4 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Fri, 29 Jan 2021 14:08:14 +0900 Subject: impl Clone and Copy for all Style types in iced_style --- style/src/button.rs | 2 +- style/src/checkbox.rs | 2 +- style/src/progress_bar.rs | 2 +- style/src/radio.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/style/src/button.rs b/style/src/button.rs index 43d27216..2281e32f 100644 --- a/style/src/button.rs +++ b/style/src/button.rs @@ -2,7 +2,7 @@ use iced_core::{Background, Color, Vector}; /// The appearance of a button. -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct Style { pub shadow_offset: Vector, pub background: Option, diff --git a/style/src/checkbox.rs b/style/src/checkbox.rs index 1c5f2460..566136bb 100644 --- a/style/src/checkbox.rs +++ b/style/src/checkbox.rs @@ -2,7 +2,7 @@ use iced_core::{Background, Color}; /// The appearance of a checkbox. -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct Style { pub background: Background, pub checkmark_color: Color, diff --git a/style/src/progress_bar.rs b/style/src/progress_bar.rs index 36be63f9..d0878c84 100644 --- a/style/src/progress_bar.rs +++ b/style/src/progress_bar.rs @@ -2,7 +2,7 @@ use iced_core::{Background, Color}; /// The appearance of a progress bar. -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct Style { pub background: Background, pub bar: Background, diff --git a/style/src/radio.rs b/style/src/radio.rs index 83310e05..c41b70c0 100644 --- a/style/src/radio.rs +++ b/style/src/radio.rs @@ -2,7 +2,7 @@ use iced_core::{Background, Color}; /// The appearance of a radio button. -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct Style { pub background: Background, pub dot_color: Color, -- cgit From 4a727dfb8b5b418d88e225a8b1164c865345124f Mon Sep 17 00:00:00 2001 From: Poly Date: Wed, 3 Feb 2021 19:20:23 +0100 Subject: The beginning of wgpu 0.7 update hecrj/iced#723 --- wgpu/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index d469da0a..b4b2a42b 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -14,8 +14,8 @@ qr_code = ["iced_graphics/qr_code"] default_system_font = ["iced_graphics/font-source"] [dependencies] -wgpu = "0.6" -wgpu_glyph = "0.10" +wgpu = "0.7.0" +wgpu_glyph = {git="https://github.com/hecrj/wgpu_glyph.git"} glyph_brush = "0.7" raw-window-handle = "0.3" log = "0.4" -- cgit From 2d76c7165c4a82f27090cbf69f811f8e0f2f28a4 Mon Sep 17 00:00:00 2001 From: Poly Date: Wed, 3 Feb 2021 19:21:02 +0100 Subject: [wgpu 0.7] Update quad.rs --- wgpu/src/quad.rs | 116 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 62 insertions(+), 54 deletions(-) diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 24d20cfa..c1399d53 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -24,8 +24,9 @@ impl Pipeline { entries: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStage::VERTEX, - ty: wgpu::BindingType::UniformBuffer { - dynamic: false, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, min_binding_size: wgpu::BufferSize::new( mem::size_of::() as u64, ), @@ -46,9 +47,11 @@ impl Pipeline { layout: &constant_layout, entries: &[wgpu::BindGroupEntry { binding: 0, - resource: wgpu::BindingResource::Buffer( - constants_buffer.slice(..), - ), + resource: wgpu::BindingResource::Buffer { + buffer: &constants_buffer, + offset: 0, + size: None, + }, }], }); @@ -59,87 +62,61 @@ impl Pipeline { bind_group_layouts: &[&constant_layout], }); - let vs_module = device - .create_shader_module(wgpu::include_spirv!("shader/quad.vert.spv")); + let vs_module = device.create_shader_module(&wgpu::include_spirv!( + "shader/quad.vert.spv" + )); - let fs_module = device - .create_shader_module(wgpu::include_spirv!("shader/quad.frag.spv")); + let fs_module = device.create_shader_module(&wgpu::include_spirv!( + "shader/quad.frag.spv" + )); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("iced_wgpu::quad pipeline"), layout: Some(&layout), - vertex_stage: wgpu::ProgrammableStageDescriptor { + vertex: wgpu::VertexState { module: &vs_module, entry_point: "main", - }, - fragment_stage: Some(wgpu::ProgrammableStageDescriptor { - module: &fs_module, - entry_point: "main", - }), - rasterization_state: Some(wgpu::RasterizationStateDescriptor { - front_face: wgpu::FrontFace::Cw, - cull_mode: wgpu::CullMode::None, - ..Default::default() - }), - primitive_topology: wgpu::PrimitiveTopology::TriangleList, - color_states: &[wgpu::ColorStateDescriptor { - format, - color_blend: wgpu::BlendDescriptor { - src_factor: wgpu::BlendFactor::SrcAlpha, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - alpha_blend: wgpu::BlendDescriptor { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - write_mask: wgpu::ColorWrite::ALL, - }], - depth_stencil_state: None, - vertex_state: wgpu::VertexStateDescriptor { - index_format: wgpu::IndexFormat::Uint16, - vertex_buffers: &[ - wgpu::VertexBufferDescriptor { - stride: mem::size_of::() as u64, + buffers: &[ + wgpu::VertexBufferLayout { + array_stride: mem::size_of::() as u64, step_mode: wgpu::InputStepMode::Vertex, - attributes: &[wgpu::VertexAttributeDescriptor { + attributes: &[wgpu::VertexAttribute { shader_location: 0, format: wgpu::VertexFormat::Float2, offset: 0, }], }, - wgpu::VertexBufferDescriptor { - stride: mem::size_of::() as u64, + wgpu::VertexBufferLayout { + array_stride: mem::size_of::() as u64, step_mode: wgpu::InputStepMode::Instance, attributes: &[ - wgpu::VertexAttributeDescriptor { + wgpu::VertexAttribute { shader_location: 1, format: wgpu::VertexFormat::Float2, offset: 0, }, - wgpu::VertexAttributeDescriptor { + wgpu::VertexAttribute { shader_location: 2, format: wgpu::VertexFormat::Float2, offset: 4 * 2, }, - wgpu::VertexAttributeDescriptor { + wgpu::VertexAttribute { shader_location: 3, format: wgpu::VertexFormat::Float4, offset: 4 * (2 + 2), }, - wgpu::VertexAttributeDescriptor { + wgpu::VertexAttribute { shader_location: 4, format: wgpu::VertexFormat::Float4, offset: 4 * (2 + 2 + 4), }, - wgpu::VertexAttributeDescriptor { + wgpu::VertexAttribute { shader_location: 5, format: wgpu::VertexFormat::Float, offset: 4 * (2 + 2 + 4 + 4), }, - wgpu::VertexAttributeDescriptor { + wgpu::VertexAttribute { shader_location: 6, format: wgpu::VertexFormat::Float, offset: 4 * (2 + 2 + 4 + 4 + 1), @@ -148,9 +125,36 @@ impl Pipeline { }, ], }, - sample_count: 1, - sample_mask: !0, - alpha_to_coverage_enabled: false, + fragment: Some(wgpu::FragmentState { + module: &fs_module, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + format, + alpha_blend: wgpu::BlendState { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + color_blend: wgpu::BlendState { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + write_mask: wgpu::ColorWrite::ALL, + }], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + front_face: wgpu::FrontFace::Cw, + cull_mode: wgpu::CullMode::None, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, }); let vertices = @@ -232,6 +236,7 @@ impl Pipeline { { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("iced_wgpu::quad render pass"), color_attachments: &[ wgpu::RenderPassColorAttachmentDescriptor { attachment: target, @@ -247,7 +252,10 @@ impl Pipeline { render_pass.set_pipeline(&self.pipeline); render_pass.set_bind_group(0, &self.constants, &[]); - render_pass.set_index_buffer(self.indices.slice(..)); + render_pass.set_index_buffer( + self.indices.slice(..), + wgpu::IndexFormat::Uint16, + ); render_pass.set_vertex_buffer(0, self.vertices.slice(..)); render_pass.set_vertex_buffer(1, self.instances.slice(..)); render_pass.set_scissor_rect( -- cgit From 98d108d2b73b994a63140361110dec6401a80ae4 Mon Sep 17 00:00:00 2001 From: Poly Date: Wed, 3 Feb 2021 19:32:30 +0100 Subject: [wgpu 0.7] Update window/compositor.rs Notes: - wgpu::PowerPreference::Default no longer exists, maybe there is another way to replicate its behavior. --- wgpu/src/window/compositor.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 492efb42..492cb955 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -27,7 +27,7 @@ impl Compositor { let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { power_preference: if settings.antialiasing.is_none() { - wgpu::PowerPreference::Default + wgpu::PowerPreference::default() } else { wgpu::PowerPreference::HighPerformance }, @@ -38,12 +38,14 @@ impl Compositor { let (device, queue) = adapter .request_device( &wgpu::DeviceDescriptor { + label: Some( + "iced_wgpu::window::Compositor Device Descriptor", + ), features: wgpu::Features::empty(), limits: wgpu::Limits { max_bind_groups: 2, ..wgpu::Limits::default() }, - shader_validation: false, }, None, ) @@ -103,7 +105,7 @@ impl iced_graphics::window::Compositor for Compositor { self.device.create_swap_chain( surface, &wgpu::SwapChainDescriptor { - usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + usage: wgpu::TextureUsage::RENDER_ATTACHMENT, format: self.settings.format, present_mode: self.settings.present_mode, width, @@ -130,6 +132,7 @@ impl iced_graphics::window::Compositor for Compositor { ); let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("iced_wgpu::window::Compositor Render Pass"), color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { attachment: &frame.output.view, resolve_target: None, -- cgit From 5f935c34fdd23a99e39ea80fef3845d476fc78f4 Mon Sep 17 00:00:00 2001 From: Poly Date: Wed, 3 Feb 2021 20:05:37 +0100 Subject: [wgpu 0.7] Update triangle/msaa.rs Notes: - not sure if `filtering sampler` should be used, so for now it is not used . --- wgpu/src/triangle/msaa.rs | 74 +++++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index db86f748..13c2a3c4 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -32,7 +32,10 @@ impl Blit { entries: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStage::FRAGMENT, - ty: wgpu::BindingType::Sampler { comparison: false }, + ty: wgpu::BindingType::Sampler { + comparison: false, + filtering: true, + }, count: None, }], }); @@ -53,9 +56,11 @@ impl Blit { entries: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStage::FRAGMENT, - ty: wgpu::BindingType::SampledTexture { - dimension: wgpu::TextureViewDimension::D2, - component_type: wgpu::TextureComponentType::Float, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { + filterable: true, + }, + view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: None, @@ -69,11 +74,11 @@ impl Blit { bind_group_layouts: &[&constant_layout, &texture_layout], }); - let vs_module = device.create_shader_module(wgpu::include_spirv!( + let vs_module = device.create_shader_module(&wgpu::include_spirv!( "../shader/blit.vert.spv" )); - let fs_module = device.create_shader_module(wgpu::include_spirv!( + let fs_module = device.create_shader_module(&wgpu::include_spirv!( "../shader/blit.frag.spv" )); @@ -81,42 +86,42 @@ impl Blit { device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("iced_wgpu::triangle::msaa pipeline"), layout: Some(&layout), - vertex_stage: wgpu::ProgrammableStageDescriptor { + vertex: wgpu::VertexState { module: &vs_module, entry_point: "main", + buffers: &[], }, - fragment_stage: Some(wgpu::ProgrammableStageDescriptor { + fragment: Some(wgpu::FragmentState { module: &fs_module, entry_point: "main", + targets: &[wgpu::ColorTargetState { + format, + color_blend: wgpu::BlendState { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha_blend: wgpu::BlendState { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + write_mask: wgpu::ColorWrite::ALL, + }], }), - rasterization_state: Some(wgpu::RasterizationStateDescriptor { + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, front_face: wgpu::FrontFace::Cw, cull_mode: wgpu::CullMode::None, ..Default::default() - }), - primitive_topology: wgpu::PrimitiveTopology::TriangleList, - color_states: &[wgpu::ColorStateDescriptor { - format, - color_blend: wgpu::BlendDescriptor { - src_factor: wgpu::BlendFactor::SrcAlpha, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - alpha_blend: wgpu::BlendDescriptor { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - write_mask: wgpu::ColorWrite::ALL, - }], - depth_stencil_state: None, - vertex_state: wgpu::VertexStateDescriptor { - index_format: wgpu::IndexFormat::Uint16, - vertex_buffers: &[], }, - sample_count: 1, - sample_mask: !0, - alpha_to_coverage_enabled: false, + depth_stencil: None, + + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, }); Blit { @@ -172,6 +177,7 @@ impl Blit { ) { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("iced_wgpu::triangle::msaa Render Pass"), color_attachments: &[ wgpu::RenderPassColorAttachmentDescriptor { attachment: target, @@ -227,7 +233,7 @@ impl Targets { sample_count, dimension: wgpu::TextureDimension::D2, format, - usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + usage: wgpu::TextureUsage::RENDER_ATTACHMENT, }); let resolve = device.create_texture(&wgpu::TextureDescriptor { @@ -237,7 +243,7 @@ impl Targets { sample_count: 1, dimension: wgpu::TextureDimension::D2, format, - usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT + usage: wgpu::TextureUsage::RENDER_ATTACHMENT | wgpu::TextureUsage::SAMPLED, }); -- cgit From e2595ac0aa53448feb421c1591178b748814055a Mon Sep 17 00:00:00 2001 From: Poly Date: Wed, 3 Feb 2021 20:06:07 +0100 Subject: [wgpu 0.7] Update triangle.rs --- wgpu/src/triangle.rs | 112 +++++++++++++++++++++++++++------------------------ 1 file changed, 60 insertions(+), 52 deletions(-) diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 61a771d8..d23ce29e 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -86,8 +86,9 @@ impl Pipeline { entries: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStage::VERTEX, - ty: wgpu::BindingType::UniformBuffer { - dynamic: true, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: true, min_binding_size: wgpu::BufferSize::new( mem::size_of::() as u64, ), @@ -109,11 +110,13 @@ impl Pipeline { layout: &constants_layout, entries: &[wgpu::BindGroupEntry { binding: 0, - resource: wgpu::BindingResource::Buffer( - constants_buffer - .raw - .slice(0..std::mem::size_of::() as u64), - ), + resource: wgpu::BindingResource::Buffer { + buffer: &constants_buffer.raw, + offset: 0, + size: wgpu::BufferSize::new( + std::mem::size_of::() as u64, + ), + }, }], }); @@ -124,11 +127,11 @@ impl Pipeline { bind_group_layouts: &[&constants_layout], }); - let vs_module = device.create_shader_module(wgpu::include_spirv!( + let vs_module = device.create_shader_module(&wgpu::include_spirv!( "shader/triangle.vert.spv" )); - let fs_module = device.create_shader_module(wgpu::include_spirv!( + let fs_module = device.create_shader_module(&wgpu::include_spirv!( "shader/triangle.frag.spv" )); @@ -136,49 +139,21 @@ impl Pipeline { device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("iced_wgpu::triangle pipeline"), layout: Some(&layout), - vertex_stage: wgpu::ProgrammableStageDescriptor { + vertex: wgpu::VertexState { module: &vs_module, entry_point: "main", - }, - fragment_stage: Some(wgpu::ProgrammableStageDescriptor { - module: &fs_module, - entry_point: "main", - }), - rasterization_state: Some(wgpu::RasterizationStateDescriptor { - front_face: wgpu::FrontFace::Cw, - cull_mode: wgpu::CullMode::None, - ..Default::default() - }), - primitive_topology: wgpu::PrimitiveTopology::TriangleList, - color_states: &[wgpu::ColorStateDescriptor { - format, - color_blend: wgpu::BlendDescriptor { - src_factor: wgpu::BlendFactor::SrcAlpha, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - alpha_blend: wgpu::BlendDescriptor { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - write_mask: wgpu::ColorWrite::ALL, - }], - depth_stencil_state: None, - vertex_state: wgpu::VertexStateDescriptor { - index_format: wgpu::IndexFormat::Uint32, - vertex_buffers: &[wgpu::VertexBufferDescriptor { - stride: mem::size_of::() as u64, + buffers: &[wgpu::VertexBufferLayout { + array_stride: mem::size_of::() as u64, step_mode: wgpu::InputStepMode::Vertex, attributes: &[ // Position - wgpu::VertexAttributeDescriptor { + wgpu::VertexAttribute { shader_location: 0, format: wgpu::VertexFormat::Float2, offset: 0, }, // Color - wgpu::VertexAttributeDescriptor { + wgpu::VertexAttribute { shader_location: 1, format: wgpu::VertexFormat::Float4, offset: 4 * 2, @@ -186,11 +161,38 @@ impl Pipeline { ], }], }, - sample_count: u32::from( - antialiasing.map(|a| a.sample_count()).unwrap_or(1), - ), - sample_mask: !0, - alpha_to_coverage_enabled: false, + fragment: Some(wgpu::FragmentState { + module: &fs_module, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + format, + color_blend: wgpu::BlendState { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha_blend: wgpu::BlendState { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + write_mask: wgpu::ColorWrite::ALL, + }], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + front_face: wgpu::FrontFace::Cw, + cull_mode: wgpu::CullMode::None, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: u32::from( + antialiasing.map(|a| a.sample_count()).unwrap_or(1), + ), + mask: !0, + alpha_to_coverage_enabled: false, + }, }); Pipeline { @@ -252,11 +254,15 @@ impl Pipeline { layout: &self.constants_layout, entries: &[wgpu::BindGroupEntry { binding: 0, - resource: wgpu::BindingResource::Buffer( - self.uniforms_buffer.raw.slice( - 0..std::mem::size_of::() as u64, - ), - ), + resource: wgpu::BindingResource::Buffer { + buffer: &self.uniforms_buffer.raw, + offset: 0, + size: wgpu::BufferSize::new(std::mem::size_of::< + Uniforms, + >( + ) + as u64), + }, }], }); } @@ -356,6 +362,7 @@ impl Pipeline { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("iced_wgpu::triangle Render Pass"), color_attachments: &[ wgpu::RenderPassColorAttachmentDescriptor { attachment, @@ -390,6 +397,7 @@ impl Pipeline { self.index_buffer .raw .slice(index_offset * mem::size_of::() as u64..), + wgpu::IndexFormat::Uint32, ); render_pass.set_vertex_buffer( -- cgit From 4a6db30d47683110663bc610c9ef9c7a9c9ac184 Mon Sep 17 00:00:00 2001 From: Poly Date: Wed, 3 Feb 2021 20:24:48 +0100 Subject: [wgpu 0.7] Update image.rs --- wgpu/src/image.rs | 121 +++++++++++++++++++++++++++++------------------------- 1 file changed, 66 insertions(+), 55 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index c256ca7e..b44d621b 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -62,8 +62,9 @@ impl Pipeline { wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStage::VERTEX, - ty: wgpu::BindingType::UniformBuffer { - dynamic: false, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, min_binding_size: wgpu::BufferSize::new( mem::size_of::() as u64, ), @@ -73,7 +74,10 @@ impl Pipeline { wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStage::FRAGMENT, - ty: wgpu::BindingType::Sampler { comparison: false }, + ty: wgpu::BindingType::Sampler { + comparison: false, + filtering: false, + }, count: None, }, ], @@ -93,9 +97,11 @@ impl Pipeline { entries: &[ wgpu::BindGroupEntry { binding: 0, - resource: wgpu::BindingResource::Buffer( - uniforms_buffer.slice(..), - ), + resource: wgpu::BindingResource::Buffer { + buffer: &uniforms_buffer, + offset: 0, + size: None, + }, }, wgpu::BindGroupEntry { binding: 1, @@ -110,9 +116,11 @@ impl Pipeline { entries: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStage::FRAGMENT, - ty: wgpu::BindingType::SampledTexture { - dimension: wgpu::TextureViewDimension::D2, - component_type: wgpu::TextureComponentType::Float, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { + filterable: false, + }, + view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: None, @@ -126,11 +134,11 @@ impl Pipeline { bind_group_layouts: &[&constant_layout, &texture_layout], }); - let vs_module = device.create_shader_module(wgpu::include_spirv!( + let vs_module = device.create_shader_module(&wgpu::include_spirv!( "shader/image.vert.spv" )); - let fs_module = device.create_shader_module(wgpu::include_spirv!( + let fs_module = device.create_shader_module(&wgpu::include_spirv!( "shader/image.frag.spv" )); @@ -138,72 +146,44 @@ impl Pipeline { device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("iced_wgpu::image pipeline"), layout: Some(&layout), - vertex_stage: wgpu::ProgrammableStageDescriptor { + vertex: wgpu::VertexState { module: &vs_module, entry_point: "main", - }, - fragment_stage: Some(wgpu::ProgrammableStageDescriptor { - module: &fs_module, - entry_point: "main", - }), - rasterization_state: Some(wgpu::RasterizationStateDescriptor { - front_face: wgpu::FrontFace::Cw, - cull_mode: wgpu::CullMode::None, - ..Default::default() - }), - primitive_topology: wgpu::PrimitiveTopology::TriangleList, - color_states: &[wgpu::ColorStateDescriptor { - format, - color_blend: wgpu::BlendDescriptor { - src_factor: wgpu::BlendFactor::SrcAlpha, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - alpha_blend: wgpu::BlendDescriptor { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - write_mask: wgpu::ColorWrite::ALL, - }], - depth_stencil_state: None, - vertex_state: wgpu::VertexStateDescriptor { - index_format: wgpu::IndexFormat::Uint16, - vertex_buffers: &[ - wgpu::VertexBufferDescriptor { - stride: mem::size_of::() as u64, + buffers: &[ + wgpu::VertexBufferLayout { + array_stride: mem::size_of::() as u64, step_mode: wgpu::InputStepMode::Vertex, - attributes: &[wgpu::VertexAttributeDescriptor { + attributes: &[wgpu::VertexAttribute { shader_location: 0, format: wgpu::VertexFormat::Float2, offset: 0, }], }, - wgpu::VertexBufferDescriptor { - stride: mem::size_of::() as u64, + wgpu::VertexBufferLayout { + array_stride: mem::size_of::() as u64, step_mode: wgpu::InputStepMode::Instance, attributes: &[ - wgpu::VertexAttributeDescriptor { + wgpu::VertexAttribute { shader_location: 1, format: wgpu::VertexFormat::Float2, offset: 0, }, - wgpu::VertexAttributeDescriptor { + wgpu::VertexAttribute { shader_location: 2, format: wgpu::VertexFormat::Float2, offset: 4 * 2, }, - wgpu::VertexAttributeDescriptor { + wgpu::VertexAttribute { shader_location: 3, format: wgpu::VertexFormat::Float2, offset: 4 * 4, }, - wgpu::VertexAttributeDescriptor { + wgpu::VertexAttribute { shader_location: 4, format: wgpu::VertexFormat::Float2, offset: 4 * 6, }, - wgpu::VertexAttributeDescriptor { + wgpu::VertexAttribute { shader_location: 5, format: wgpu::VertexFormat::Uint, offset: 4 * 8, @@ -212,9 +192,36 @@ impl Pipeline { }, ], }, - sample_count: 1, - sample_mask: !0, - alpha_to_coverage_enabled: false, + fragment: Some(wgpu::FragmentState { + module: &fs_module, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + format, + color_blend: wgpu::BlendState { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha_blend: wgpu::BlendState { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + write_mask: wgpu::ColorWrite::ALL, + }], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + front_face: wgpu::FrontFace::Cw, + cull_mode: wgpu::CullMode::None, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, }); let vertices = @@ -415,6 +422,7 @@ impl Pipeline { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("iced_wgpu::image Render Pass"), color_attachments: &[ wgpu::RenderPassColorAttachmentDescriptor { attachment: target, @@ -431,7 +439,10 @@ impl Pipeline { render_pass.set_pipeline(&self.pipeline); render_pass.set_bind_group(0, &self.constants, &[]); render_pass.set_bind_group(1, &self.texture, &[]); - render_pass.set_index_buffer(self.indices.slice(..)); + render_pass.set_index_buffer( + self.indices.slice(..), + wgpu::IndexFormat::Uint16, + ); render_pass.set_vertex_buffer(0, self.vertices.slice(..)); render_pass.set_vertex_buffer(1, self.instances.slice(..)); -- cgit From c5d6ddc1264a294cab8e41e05828ece3e7df5945 Mon Sep 17 00:00:00 2001 From: Poly Date: Wed, 3 Feb 2021 21:13:51 +0100 Subject: [wgpu 0.7] triangle/msaa disable filtering --- wgpu/src/triangle/msaa.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index 13c2a3c4..0a5a703e 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -34,7 +34,7 @@ impl Blit { visibility: wgpu::ShaderStage::FRAGMENT, ty: wgpu::BindingType::Sampler { comparison: false, - filtering: true, + filtering: false, }, count: None, }], @@ -58,7 +58,7 @@ impl Blit { visibility: wgpu::ShaderStage::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { - filterable: true, + filterable: false, }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, -- cgit From bd6b8304bd940c5519fdf705698974994d5d1ca1 Mon Sep 17 00:00:00 2001 From: Poly Date: Wed, 3 Feb 2021 21:51:11 +0100 Subject: Fix ScissorRect - Breaks `TODO: Address anti-aliasing adjustments properly` --- wgpu/src/quad.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index c1399d53..f8531992 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -258,12 +258,13 @@ impl Pipeline { ); render_pass.set_vertex_buffer(0, self.vertices.slice(..)); render_pass.set_vertex_buffer(1, self.instances.slice(..)); + render_pass.set_scissor_rect( bounds.x, bounds.y, bounds.width, // TODO: Address anti-aliasing adjustments properly - bounds.height + 1, + bounds.height, ); render_pass.draw_indexed( -- cgit From 1fb60c5dcbea77bf36934f243bb0832c3e5a116e Mon Sep 17 00:00:00 2001 From: Poly Date: Wed, 3 Feb 2021 22:26:53 +0100 Subject: Fix TextureViewDimension for image wgpu validation helped to find this long standing type error --- wgpu/src/image.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index b44d621b..32843828 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -120,7 +120,7 @@ impl Pipeline { sample_type: wgpu::TextureSampleType::Float { filterable: false, }, - view_dimension: wgpu::TextureViewDimension::D2, + view_dimension: wgpu::TextureViewDimension::D2Array, multisampled: false, }, count: None, -- cgit From 09a5348740803329c98f6422700569595c2be8c5 Mon Sep 17 00:00:00 2001 From: Poly Date: Wed, 3 Feb 2021 22:38:45 +0100 Subject: [wgpu 0.7] Update integration example --- examples/integration/src/main.rs | 8 ++++---- examples/integration/src/scene.rs | 40 +++++++++++++++++++-------------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 9b52f3a5..ec01c4e6 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -36,7 +36,7 @@ pub fn main() { let (mut device, queue) = futures::executor::block_on(async { let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::Default, + power_preference: wgpu::PowerPreference::HighPerformance, compatible_surface: Some(&surface), }) .await @@ -45,9 +45,9 @@ pub fn main() { adapter .request_device( &wgpu::DeviceDescriptor { + label: None, features: wgpu::Features::empty(), limits: wgpu::Limits::default(), - shader_validation: false, }, None, ) @@ -63,7 +63,7 @@ pub fn main() { device.create_swap_chain( &surface, &wgpu::SwapChainDescriptor { - usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + usage: wgpu::TextureUsage::RENDER_ATTACHMENT, format: format, width: size.width, height: size.height, @@ -157,7 +157,7 @@ pub fn main() { swap_chain = device.create_swap_chain( &surface, &wgpu::SwapChainDescriptor { - usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + usage: wgpu::TextureUsage::RENDER_ATTACHMENT, format: format, width: size.width, height: size.height, diff --git a/examples/integration/src/scene.rs b/examples/integration/src/scene.rs index 03a338c6..36c0a41d 100644 --- a/examples/integration/src/scene.rs +++ b/examples/integration/src/scene.rs @@ -19,6 +19,7 @@ impl Scene { background_color: Color, ) -> wgpu::RenderPass<'a> { encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { attachment: target, resolve_target: None, @@ -48,10 +49,10 @@ impl Scene { fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline { let vs_module = - device.create_shader_module(wgpu::include_spirv!("shader/vert.spv")); + device.create_shader_module(&wgpu::include_spirv!("shader/vert.spv")); let fs_module = - device.create_shader_module(wgpu::include_spirv!("shader/frag.spv")); + device.create_shader_module(&wgpu::include_spirv!("shader/frag.spv")); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { @@ -64,34 +65,33 @@ fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline { device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: Some(&pipeline_layout), - vertex_stage: wgpu::ProgrammableStageDescriptor { + vertex: wgpu::VertexState { module: &vs_module, entry_point: "main", + buffers: &[], }, - fragment_stage: Some(wgpu::ProgrammableStageDescriptor { + fragment: Some(wgpu::FragmentState { module: &fs_module, entry_point: "main", + targets: &[wgpu::ColorTargetState { + format: wgpu::TextureFormat::Bgra8UnormSrgb, + color_blend: wgpu::BlendState::REPLACE, + alpha_blend: wgpu::BlendState::REPLACE, + write_mask: wgpu::ColorWrite::ALL, + }], }), - rasterization_state: Some(wgpu::RasterizationStateDescriptor { + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, front_face: wgpu::FrontFace::Ccw, cull_mode: wgpu::CullMode::None, ..Default::default() - }), - primitive_topology: wgpu::PrimitiveTopology::TriangleList, - color_states: &[wgpu::ColorStateDescriptor { - format: wgpu::TextureFormat::Bgra8UnormSrgb, - color_blend: wgpu::BlendDescriptor::REPLACE, - alpha_blend: wgpu::BlendDescriptor::REPLACE, - write_mask: wgpu::ColorWrite::ALL, - }], - depth_stencil_state: None, - vertex_state: wgpu::VertexStateDescriptor { - index_format: wgpu::IndexFormat::Uint16, - vertex_buffers: &[], }, - sample_count: 1, - sample_mask: !0, - alpha_to_coverage_enabled: false, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, }); pipeline -- cgit From b0d1be69d679444e7d0957e1b619720705e03107 Mon Sep 17 00:00:00 2001 From: Poly Date: Wed, 3 Feb 2021 23:50:03 +0100 Subject: Change `PowerPreference` from `default()` to `LowPower` There is no reason to hide the fact that this is always in `LowPower` mode --- wgpu/src/window/compositor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 492cb955..361b9ecf 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -27,7 +27,7 @@ impl Compositor { let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { power_preference: if settings.antialiasing.is_none() { - wgpu::PowerPreference::default() + wgpu::PowerPreference::LowPower } else { wgpu::PowerPreference::HighPerformance }, -- cgit From 5fc4210270852d8d633a63168d8a166a235236c6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 6 Feb 2021 15:27:27 +0100 Subject: Use the latest release of `wgpu_glyph` --- wgpu/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index b4b2a42b..bdd9e65a 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -14,8 +14,8 @@ qr_code = ["iced_graphics/qr_code"] default_system_font = ["iced_graphics/font-source"] [dependencies] -wgpu = "0.7.0" -wgpu_glyph = {git="https://github.com/hecrj/wgpu_glyph.git"} +wgpu = "0.7" +wgpu_glyph = "0.11" glyph_brush = "0.7" raw-window-handle = "0.3" log = "0.4" -- cgit From ffdf87fbe2cf132b63d0a5af46c8ec1aead9af12 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 6 Feb 2021 15:37:05 +0100 Subject: Use lowercase in `wgpu` labels for consistency --- wgpu/src/image.rs | 2 +- wgpu/src/triangle.rs | 2 +- wgpu/src/triangle/msaa.rs | 2 +- wgpu/src/window/compositor.rs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 32843828..e34cbb0a 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -422,7 +422,7 @@ impl Pipeline { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::image Render Pass"), + label: Some("iced_wgpu::image render pass"), color_attachments: &[ wgpu::RenderPassColorAttachmentDescriptor { attachment: target, diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index d23ce29e..2f255940 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -362,7 +362,7 @@ impl Pipeline { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::triangle Render Pass"), + label: Some("iced_wgpu::triangle render pass"), color_attachments: &[ wgpu::RenderPassColorAttachmentDescriptor { attachment, diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index 0a5a703e..4c8fe20c 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -177,7 +177,7 @@ impl Blit { ) { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::triangle::msaa Render Pass"), + label: Some("iced_wgpu::triangle::msaa render pass"), color_attachments: &[ wgpu::RenderPassColorAttachmentDescriptor { attachment: target, diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 361b9ecf..36d6ab1a 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -39,7 +39,7 @@ impl Compositor { .request_device( &wgpu::DeviceDescriptor { label: Some( - "iced_wgpu::window::Compositor Device Descriptor", + "iced_wgpu::window::compositor device descriptor", ), features: wgpu::Features::empty(), limits: wgpu::Limits { @@ -132,7 +132,7 @@ impl iced_graphics::window::Compositor for Compositor { ); let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::window::Compositor Render Pass"), + label: Some("iced_wgpu::window::Compositor render pass"), color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { attachment: &frame.output.view, resolve_target: None, -- cgit From 15c4901aba7fbcc5373b68e0d1a52ad446b64b48 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 6 Feb 2021 15:38:02 +0100 Subject: Remove unnecessary line break in `triangle::msaa` --- wgpu/src/triangle/msaa.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index 4c8fe20c..d964f815 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -116,7 +116,6 @@ impl Blit { ..Default::default() }, depth_stencil: None, - multisample: wgpu::MultisampleState { count: 1, mask: !0, -- cgit From 7eefad34fc93838a5a3fe976ed0ee47818004b83 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 6 Feb 2021 15:55:03 +0100 Subject: List `color_blend` first in `wgpu::quad` --- wgpu/src/quad.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index f8531992..e0a6e043 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -130,13 +130,13 @@ impl Pipeline { entry_point: "main", targets: &[wgpu::ColorTargetState { format, - alpha_blend: wgpu::BlendState { - src_factor: wgpu::BlendFactor::One, + color_blend: wgpu::BlendState { + src_factor: wgpu::BlendFactor::SrcAlpha, dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, operation: wgpu::BlendOperation::Add, }, - color_blend: wgpu::BlendState { - src_factor: wgpu::BlendFactor::SrcAlpha, + alpha_blend: wgpu::BlendState { + src_factor: wgpu::BlendFactor::One, dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, operation: wgpu::BlendOperation::Add, }, -- cgit From 74b9ea520f0f861bda82bbf79a6d4327e42a62fc Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 6 Feb 2021 16:04:43 +0100 Subject: Enable filtering in `wgpu::image` --- wgpu/src/image.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index e34cbb0a..70bfe586 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -76,7 +76,7 @@ impl Pipeline { visibility: wgpu::ShaderStage::FRAGMENT, ty: wgpu::BindingType::Sampler { comparison: false, - filtering: false, + filtering: true, }, count: None, }, @@ -118,7 +118,7 @@ impl Pipeline { visibility: wgpu::ShaderStage::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { - filterable: false, + filterable: true, }, view_dimension: wgpu::TextureViewDimension::D2Array, multisampled: false, -- 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 +- wgpu/src/text.rs | 1 + 4 files changed, 92 insertions(+), 70 deletions(-) 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 = diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 78999cf8..4d92d9e9 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -19,6 +19,7 @@ impl Pipeline { let default_font = default_font.map(|slice| slice.to_vec()); // TODO: Font customization + #[cfg(not(target_os = "ios"))] #[cfg(feature = "default_system_font")] let default_font = { default_font.or_else(|| { -- cgit From 9f5c2eb0c43daa61b19769322cf3692b29e0ac0f Mon Sep 17 00:00:00 2001 From: Folyd Date: Sat, 13 Feb 2021 05:00:52 +0800 Subject: Improve download_progress example (#283) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add advanced download example * Rename to task fields and variables * Cargo fmt advanced_download/src/download.rs * Add progress bar for advanced download example * Merge two download examples to single one * Apply great review suggestions * Change to url::Url instead of plain String * Simplify `download_progress` example * Update `README` of `download_progress` example Co-authored-by: Héctor Ramón Jiménez --- examples/download_progress/Cargo.toml | 6 +- examples/download_progress/README.md | 2 +- examples/download_progress/src/download.rs | 46 ++++--- examples/download_progress/src/main.rs | 195 ++++++++++++++++++++--------- 4 files changed, 168 insertions(+), 81 deletions(-) diff --git a/examples/download_progress/Cargo.toml b/examples/download_progress/Cargo.toml index 4b05e7dc..d3c578b1 100644 --- a/examples/download_progress/Cargo.toml +++ b/examples/download_progress/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "download_progress" version = "0.1.0" -authors = ["Songtronix "] +authors = ["Songtronix ", "Folyd "] edition = "2018" publish = false [dependencies] -iced = { path = "../..", features = ["tokio_old"] } +iced = { path = "../..", features = ["tokio"] } iced_native = { path = "../../native" } iced_futures = { path = "../../futures" } -reqwest = "0.10" +reqwest = "0.11" diff --git a/examples/download_progress/README.md b/examples/download_progress/README.md index c606c5f9..7999ce94 100644 --- a/examples/download_progress/README.md +++ b/examples/download_progress/README.md @@ -1,6 +1,6 @@ ## Download progress -A basic application that asynchronously downloads a dummy file of 100 MB and tracks the download progress. +A basic application that asynchronously downloads multiple dummy files of 100 MB and tracks the download progress. The example implements a custom `Subscription` in the __[`download`](src/download.rs)__ module. This subscription downloads and produces messages that can be used to keep track of its progress. diff --git a/examples/download_progress/src/download.rs b/examples/download_progress/src/download.rs index f46a01f7..08805f13 100644 --- a/examples/download_progress/src/download.rs +++ b/examples/download_progress/src/download.rs @@ -1,37 +1,46 @@ use iced_futures::futures; +use std::hash::{Hash, Hasher}; // Just a little utility function -pub fn file(url: T) -> iced::Subscription { +pub fn file( + id: I, + url: T, +) -> iced::Subscription<(I, Progress)> { iced::Subscription::from_recipe(Download { + id, url: url.to_string(), }) } -pub struct Download { +pub struct Download { + id: I, url: String, } // Make sure iced can use our download stream -impl iced_native::subscription::Recipe for Download +impl iced_native::subscription::Recipe for Download where - H: std::hash::Hasher, + T: 'static + Hash + Copy + Send, + H: Hasher, { - type Output = Progress; + type Output = (T, Progress); fn hash(&self, state: &mut H) { - use std::hash::Hash; + struct Marker; + std::any::TypeId::of::().hash(state); - std::any::TypeId::of::().hash(state); - self.url.hash(state); + self.id.hash(state); } fn stream( self: Box, _input: futures::stream::BoxStream<'static, I>, ) -> futures::stream::BoxStream<'static, Self::Output> { + let id = self.id; + Box::pin(futures::stream::unfold( State::Ready(self.url), - |state| async move { + move |state| async move { match state { State::Ready(url) => { let response = reqwest::get(&url).await; @@ -40,7 +49,7 @@ where Ok(response) => { if let Some(total) = response.content_length() { Some(( - Progress::Started, + (id, Progress::Started), State::Downloading { response, total, @@ -48,11 +57,14 @@ where }, )) } else { - Some((Progress::Errored, State::Finished)) + Some(( + (id, Progress::Errored), + State::Finished, + )) } } Err(_) => { - Some((Progress::Errored, State::Finished)) + Some(((id, Progress::Errored), State::Finished)) } } } @@ -68,7 +80,7 @@ where (downloaded as f32 / total as f32) * 100.0; Some(( - Progress::Advanced(percentage), + (id, Progress::Advanced(percentage)), State::Downloading { response, total, @@ -76,8 +88,12 @@ where }, )) } - Ok(None) => Some((Progress::Finished, State::Finished)), - Err(_) => Some((Progress::Errored, State::Finished)), + Ok(None) => { + Some(((id, Progress::Finished), State::Finished)) + } + Err(_) => { + Some(((id, Progress::Errored), State::Finished)) + } }, State::Finished => { // We do not let the stream die, as it would start a diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index 77b01354..cd024926 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -10,17 +10,17 @@ pub fn main() -> iced::Result { } #[derive(Debug)] -enum Example { - Idle { button: button::State }, - Downloading { progress: f32 }, - Finished { button: button::State }, - Errored { button: button::State }, +struct Example { + downloads: Vec, + last_id: usize, + add: button::State, } #[derive(Debug, Clone)] pub enum Message { - Download, - DownloadProgressed(download::Progress), + Add, + Download(usize), + DownloadProgressed((usize, download::Progress)), } impl Application for Example { @@ -30,8 +30,10 @@ impl Application for Example { fn new(_flags: ()) -> (Example, Command) { ( - Example::Idle { - button: button::State::new(), + Example { + downloads: vec![Download::new(0)], + last_id: 0, + add: button::State::new(), }, Command::none(), ) @@ -43,102 +45,171 @@ impl Application for Example { fn update(&mut self, message: Message) -> Command { match message { - Message::Download => match self { - Example::Idle { .. } - | Example::Finished { .. } - | Example::Errored { .. } => { - *self = Example::Downloading { progress: 0.0 }; + Message::Add => { + self.last_id = self.last_id + 1; + + self.downloads.push(Download::new(self.last_id)); + } + Message::Download(index) => { + if let Some(download) = self.downloads.get_mut(index) { + download.start(); } - _ => {} - }, - Message::DownloadProgressed(message) => match self { - Example::Downloading { progress } => match message { - download::Progress::Started => { - *progress = 0.0; - } - download::Progress::Advanced(percentage) => { - *progress = percentage; - } - download::Progress::Finished => { - *self = Example::Finished { - button: button::State::new(), - } - } - download::Progress::Errored => { - *self = Example::Errored { - button: button::State::new(), - }; - } - }, - _ => {} - }, + } + Message::DownloadProgressed((id, progress)) => { + if let Some(download) = + self.downloads.iter_mut().find(|download| download.id == id) + { + download.progress(progress); + } + } }; Command::none() } fn subscription(&self) -> Subscription { - match self { - Example::Downloading { .. } => { - download::file("https://speed.hetzner.de/100MB.bin") + Subscription::batch(self.downloads.iter().map(Download::subscription)) + } + + fn view(&mut self) -> Element { + let downloads = self + .downloads + .iter_mut() + .fold(Column::new().spacing(20), |column, download| { + column.push(download.view()) + }) + .push( + Button::new(&mut self.add, Text::new("Add another download")) + .on_press(Message::Add) + .padding(10), + ) + .align_items(Align::End); + + Container::new(downloads) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .padding(20) + .into() + } +} + +#[derive(Debug)] +struct Download { + id: usize, + state: State, +} + +#[derive(Debug)] +enum State { + Idle { button: button::State }, + Downloading { progress: f32 }, + Finished { button: button::State }, + Errored { button: button::State }, +} + +impl Download { + pub fn new(id: usize) -> Self { + Download { + id, + state: State::Idle { + button: button::State::new(), + }, + } + } + + pub fn start(&mut self) { + match self.state { + State::Idle { .. } + | State::Finished { .. } + | State::Errored { .. } => { + self.state = State::Downloading { progress: 0.0 }; + } + _ => {} + } + } + + pub fn progress(&mut self, new_progress: download::Progress) { + match &mut self.state { + State::Downloading { progress } => match new_progress { + download::Progress::Started => { + *progress = 0.0; + } + download::Progress::Advanced(percentage) => { + *progress = percentage; + } + download::Progress::Finished => { + self.state = State::Finished { + button: button::State::new(), + } + } + download::Progress::Errored => { + self.state = State::Errored { + button: button::State::new(), + }; + } + }, + _ => {} + } + } + + pub fn subscription(&self) -> Subscription { + match self.state { + State::Downloading { .. } => { + download::file(self.id, "https://speed.hetzner.de/100MB.bin?") .map(Message::DownloadProgressed) } _ => Subscription::none(), } } - fn view(&mut self) -> Element { - let current_progress = match self { - Example::Idle { .. } => 0.0, - Example::Downloading { progress } => *progress, - Example::Finished { .. } => 100.0, - Example::Errored { .. } => 0.0, + pub fn view(&mut self) -> Element { + let current_progress = match &self.state { + State::Idle { .. } => 0.0, + State::Downloading { progress } => *progress, + State::Finished { .. } => 100.0, + State::Errored { .. } => 0.0, }; let progress_bar = ProgressBar::new(0.0..=100.0, current_progress); - let control: Element<_> = match self { - Example::Idle { button } => { + let control: Element<_> = match &mut self.state { + State::Idle { button } => { Button::new(button, Text::new("Start the download!")) - .on_press(Message::Download) + .on_press(Message::Download(self.id)) .into() } - Example::Finished { button } => Column::new() + State::Finished { button } => Column::new() .spacing(10) .align_items(Align::Center) .push(Text::new("Download finished!")) .push( Button::new(button, Text::new("Start again")) - .on_press(Message::Download), + .on_press(Message::Download(self.id)), ) .into(), - Example::Downloading { .. } => { + State::Downloading { .. } => { Text::new(format!("Downloading... {:.2}%", current_progress)) .into() } - Example::Errored { button } => Column::new() + State::Errored { button } => Column::new() .spacing(10) .align_items(Align::Center) .push(Text::new("Something went wrong :(")) .push( Button::new(button, Text::new("Try again")) - .on_press(Message::Download), + .on_press(Message::Download(self.id)), ) .into(), }; - let content = Column::new() + Column::new() .spacing(10) .padding(10) .align_items(Align::Center) .push(progress_bar) - .push(control); - - Container::new(content) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() + .push(control) .into() } } -- cgit From 8f126c212b887b2621cd8220bc4a52ba4febb1eb Mon Sep 17 00:00:00 2001 From: Greg V Date: Fri, 5 Jun 2020 21:18:22 +0300 Subject: Add image format options to reduce code bloat, fixes #376 --- glow/src/backend.rs | 2 +- src/widget.rs | 2 +- wgpu/Cargo.toml | 16 +++++++++++++++- wgpu/src/backend.rs | 14 +++++++------- wgpu/src/image.rs | 18 +++++++++--------- wgpu/src/image/atlas/entry.rs | 2 +- wgpu/src/image/raster.rs | 8 ++++---- wgpu/src/lib.rs | 2 +- 8 files changed, 39 insertions(+), 25 deletions(-) diff --git a/glow/src/backend.rs b/glow/src/backend.rs index 92bb993e..390ffbe5 100644 --- a/glow/src/backend.rs +++ b/glow/src/backend.rs @@ -208,7 +208,7 @@ impl backend::Text for Backend { } } -#[cfg(feature = "image")] +#[cfg(feature = "image_rs")] impl backend::Image for Backend { fn dimensions(&self, _handle: &iced_native::image::Handle) -> (u32, u32) { (50, 50) diff --git a/src/widget.rs b/src/widget.rs index edd35d2d..e220a15e 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -34,7 +34,7 @@ mod platform { )] pub use crate::renderer::widget::qr_code; - #[cfg_attr(docsrs, doc(cfg(feature = "image")))] + #[cfg_attr(docsrs, doc(cfg(feature = "image_rs")))] pub mod image { //! Display images in your user interface. pub use crate::runtime::image::viewer; diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index bdd9e65a..aa39f068 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -9,6 +9,18 @@ repository = "https://github.com/hecrj/iced" [features] svg = ["resvg", "usvg"] +image = ["png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"] +png = ["image_rs/png"] +jpeg = ["image_rs/jpeg"] +jpeg_rayon = ["image_rs/jpeg_rayon"] +gif = ["image_rs/gif"] +webp = ["image_rs/webp"] +pnm = ["image_rs/pnm"] +ico = ["image_rs/ico"] +bmp = ["image_rs/bmp"] +hdr = ["image_rs/hdr"] +dds = ["image_rs/dds"] +farbfeld = ["image_rs/farbfeld"] canvas = ["iced_graphics/canvas"] qr_code = ["iced_graphics/qr_code"] default_system_font = ["iced_graphics/font-source"] @@ -35,8 +47,10 @@ version = "0.1" path = "../graphics" features = ["font-fallback", "font-icons"] -[dependencies.image] +[dependencies.image_rs] version = "0.23" +package = "image" +default-features = false optional = true [dependencies.resvg] diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index fccb5ac7..534c6cb7 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -9,7 +9,7 @@ use iced_graphics::{Primitive, Viewport}; use iced_native::mouse; use iced_native::{Font, HorizontalAlignment, Size, VerticalAlignment}; -#[cfg(any(feature = "image", feature = "svg"))] +#[cfg(any(feature = "image_rs", feature = "svg"))] use crate::image; /// A [`wgpu`] graphics backend for [`iced`]. @@ -22,7 +22,7 @@ pub struct Backend { text_pipeline: text::Pipeline, triangle_pipeline: triangle::Pipeline, - #[cfg(any(feature = "image", feature = "svg"))] + #[cfg(any(feature = "image_rs", feature = "svg"))] image_pipeline: image::Pipeline, default_text_size: u16, @@ -40,7 +40,7 @@ impl Backend { settings.antialiasing, ); - #[cfg(any(feature = "image", feature = "svg"))] + #[cfg(any(feature = "image_rs", feature = "svg"))] let image_pipeline = image::Pipeline::new(device, settings.format); Self { @@ -48,7 +48,7 @@ impl Backend { text_pipeline, triangle_pipeline, - #[cfg(any(feature = "image", feature = "svg"))] + #[cfg(any(feature = "image_rs", feature = "svg"))] image_pipeline, default_text_size: settings.default_text_size, @@ -92,7 +92,7 @@ impl Backend { ); } - #[cfg(any(feature = "image", feature = "svg"))] + #[cfg(any(feature = "image_rs", feature = "svg"))] self.image_pipeline.trim_cache(); *mouse_interaction @@ -142,7 +142,7 @@ impl Backend { ); } - #[cfg(any(feature = "image", feature = "svg"))] + #[cfg(any(feature = "image_rs", feature = "svg"))] { if !layer.images.is_empty() { let scaled = transformation @@ -270,7 +270,7 @@ impl backend::Text for Backend { } } -#[cfg(feature = "image")] +#[cfg(feature = "image_rs")] impl backend::Image for Backend { fn dimensions(&self, handle: &iced_native::image::Handle) -> (u32, u32) { self.image_pipeline.dimensions(handle) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 70bfe586..5511565e 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -1,6 +1,6 @@ mod atlas; -#[cfg(feature = "image")] +#[cfg(feature = "image_rs")] mod raster; #[cfg(feature = "svg")] @@ -16,7 +16,7 @@ use std::mem; use bytemuck::{Pod, Zeroable}; -#[cfg(feature = "image")] +#[cfg(feature = "image_rs")] use iced_native::image; #[cfg(feature = "svg")] @@ -24,7 +24,7 @@ use iced_native::svg; #[derive(Debug)] pub struct Pipeline { - #[cfg(feature = "image")] + #[cfg(feature = "image_rs")] raster_cache: RefCell, #[cfg(feature = "svg")] vector_cache: RefCell, @@ -259,7 +259,7 @@ impl Pipeline { }); Pipeline { - #[cfg(feature = "image")] + #[cfg(feature = "image_rs")] raster_cache: RefCell::new(raster::Cache::new()), #[cfg(feature = "svg")] @@ -278,7 +278,7 @@ impl Pipeline { } } - #[cfg(feature = "image")] + #[cfg(feature = "image_rs")] pub fn dimensions(&self, handle: &image::Handle) -> (u32, u32) { let mut cache = self.raster_cache.borrow_mut(); let memory = cache.load(&handle); @@ -307,7 +307,7 @@ impl Pipeline { ) { let instances: &mut Vec = &mut Vec::new(); - #[cfg(feature = "image")] + #[cfg(feature = "image_rs")] let mut raster_cache = self.raster_cache.borrow_mut(); #[cfg(feature = "svg")] @@ -315,7 +315,7 @@ impl Pipeline { for image in images { match &image { - #[cfg(feature = "image")] + #[cfg(feature = "image_rs")] layer::Image::Raster { handle, bounds } => { if let Some(atlas_entry) = raster_cache.upload( handle, @@ -331,7 +331,7 @@ impl Pipeline { ); } } - #[cfg(not(feature = "image"))] + #[cfg(not(feature = "image_rs"))] layer::Image::Raster { .. } => {} #[cfg(feature = "svg")] @@ -464,7 +464,7 @@ impl Pipeline { } pub fn trim_cache(&mut self) { - #[cfg(feature = "image")] + #[cfg(feature = "image_rs")] self.raster_cache.borrow_mut().trim(&mut self.texture_atlas); #[cfg(feature = "svg")] diff --git a/wgpu/src/image/atlas/entry.rs b/wgpu/src/image/atlas/entry.rs index 0310fc54..9b3f16df 100644 --- a/wgpu/src/image/atlas/entry.rs +++ b/wgpu/src/image/atlas/entry.rs @@ -10,7 +10,7 @@ pub enum Entry { } impl Entry { - #[cfg(feature = "image")] + #[cfg(feature = "image_rs")] pub fn size(&self) -> (u32, u32) { match self { Entry::Contiguous(allocation) => allocation.size(), diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 25607dab..d5c62545 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -4,7 +4,7 @@ use std::collections::{HashMap, HashSet}; #[derive(Debug)] pub enum Memory { - Host(::image::ImageBuffer<::image::Bgra, Vec>), + Host(::image_rs::ImageBuffer<::image_rs::Bgra, Vec>), Device(atlas::Entry), NotFound, Invalid, @@ -42,14 +42,14 @@ impl Cache { let memory = match handle.data() { image::Data::Path(path) => { - if let Ok(image) = ::image::open(path) { + if let Ok(image) = ::image_rs::open(path) { Memory::Host(image.to_bgra8()) } else { Memory::NotFound } } image::Data::Bytes(bytes) => { - if let Ok(image) = ::image::load_from_memory(&bytes) { + if let Ok(image) = ::image_rs::load_from_memory(&bytes) { Memory::Host(image.to_bgra8()) } else { Memory::Invalid @@ -60,7 +60,7 @@ impl Cache { height, pixels, } => { - if let Some(image) = ::image::ImageBuffer::from_vec( + if let Some(image) = ::image_rs::ImageBuffer::from_vec( *width, *height, pixels.to_vec(), diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index a4c2ac0e..e868a655 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -49,7 +49,7 @@ pub use widget::*; pub(crate) use iced_graphics::Transformation; -#[cfg(any(feature = "image", feature = "svg"))] +#[cfg(any(feature = "image_rs", feature = "svg"))] mod image; /// A [`wgpu`] graphics renderer for [`iced`]. -- cgit From e1b1227f0c2214b0ade4004b7985a137e7d56890 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 13 Feb 2021 16:05:51 +0100 Subject: Fix `image` feature name in `glow` and `iced` --- glow/src/backend.rs | 2 +- src/widget.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/glow/src/backend.rs b/glow/src/backend.rs index 390ffbe5..92bb993e 100644 --- a/glow/src/backend.rs +++ b/glow/src/backend.rs @@ -208,7 +208,7 @@ impl backend::Text for Backend { } } -#[cfg(feature = "image_rs")] +#[cfg(feature = "image")] impl backend::Image for Backend { fn dimensions(&self, _handle: &iced_native::image::Handle) -> (u32, u32) { (50, 50) diff --git a/src/widget.rs b/src/widget.rs index e220a15e..edd35d2d 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -34,7 +34,7 @@ mod platform { )] pub use crate::renderer::widget::qr_code; - #[cfg_attr(docsrs, doc(cfg(feature = "image_rs")))] + #[cfg_attr(docsrs, doc(cfg(feature = "image")))] pub mod image { //! Display images in your user interface. pub use crate::runtime::image::viewer; -- 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 --- Cargo.toml | 1 + examples/tooltip/Cargo.toml | 9 + examples/tooltip/README.md | 14 ++ examples/tooltip/src/main.rs | 123 ++++++++++++++ graphics/src/widget.rs | 3 + graphics/src/widget/tooltip.rs | 37 ++++ 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 +++++++++++++++++++++++++++++++++ src/widget.rs | 5 +- wgpu/src/widget.rs | 3 + wgpu/src/widget/tooltip.rs | 6 + 23 files changed, 580 insertions(+), 18 deletions(-) create mode 100644 examples/tooltip/Cargo.toml create mode 100644 examples/tooltip/README.md create mode 100644 examples/tooltip/src/main.rs create mode 100644 graphics/src/widget/tooltip.rs create mode 100644 native/src/widget/tooltip.rs create mode 100644 wgpu/src/widget/tooltip.rs diff --git a/Cargo.toml b/Cargo.toml index 75499df9..f3a9676f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,6 +83,7 @@ members = [ "examples/svg", "examples/todos", "examples/tour", + "examples/tooltip", ] [dependencies] diff --git a/examples/tooltip/Cargo.toml b/examples/tooltip/Cargo.toml new file mode 100644 index 00000000..1171de00 --- /dev/null +++ b/examples/tooltip/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "tooltip" +version = "0.1.0" +authors = ["Yusuf Bera Ertan "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../..", features = ["debug"] } diff --git a/examples/tooltip/README.md b/examples/tooltip/README.md new file mode 100644 index 00000000..4ccf6578 --- /dev/null +++ b/examples/tooltip/README.md @@ -0,0 +1,14 @@ +## Tooltip + +A tooltip. + +It displays and positions a widget on another based on cursor position. + +The __[`main`]__ file contains all the code of the example. + +You can run it with `cargo run`: +``` +cargo run --package tooltip +``` + +[`main`]: src/main.rs diff --git a/examples/tooltip/src/main.rs b/examples/tooltip/src/main.rs new file mode 100644 index 00000000..6e2c4dd4 --- /dev/null +++ b/examples/tooltip/src/main.rs @@ -0,0 +1,123 @@ +use iced::{ + button, tooltip::TooltipPosition, Button, Column, Container, Element, + Length, Row, Sandbox, Settings, Text, Tooltip, +}; + +pub fn main() { + Example::run(Settings::default()).unwrap() +} + +#[derive(Default)] +struct Example { + tooltip_top_button_state: button::State, + tooltip_bottom_button_state: button::State, + tooltip_right_button_state: button::State, + tooltip_left_button_state: button::State, + tooltip_cursor_button_state: button::State, +} + +#[derive(Debug, Clone, Copy)] +struct Message; + +impl Sandbox for Example { + type Message = Message; + + fn new() -> Self { + Self::default() + } + + fn title(&self) -> String { + String::from("Tooltip - Iced") + } + + fn update(&mut self, _message: Message) {} + + fn view(&mut self) -> Element { + let tooltip_top = tooltip_builder( + "Tooltip at top", + &mut self.tooltip_top_button_state, + TooltipPosition::Top, + ); + let tooltip_bottom = tooltip_builder( + "Tooltip at bottom", + &mut self.tooltip_bottom_button_state, + TooltipPosition::Bottom, + ); + let tooltip_right = tooltip_builder( + "Tooltip at right", + &mut self.tooltip_right_button_state, + TooltipPosition::Right, + ); + let tooltip_left = tooltip_builder( + "Tooltip at left", + &mut self.tooltip_left_button_state, + TooltipPosition::Left, + ); + + let fixed_tooltips = Row::with_children(vec![ + tooltip_top.into(), + tooltip_bottom.into(), + tooltip_left.into(), + tooltip_right.into(), + ]) + .width(Length::Fill) + .height(Length::Fill) + .align_items(iced::Align::Center) + .spacing(120); + + let cursor_tooltip_area = Tooltip::new( + Button::new( + &mut self.tooltip_cursor_button_state, + Container::new(Text::new("Tooltip follows cursor").size(40)) + .center_y() + .center_x() + .width(Length::Fill) + .height(Length::Fill), + ) + .on_press(Message) + .width(Length::Fill) + .height(Length::Fill), + tooltip(), + TooltipPosition::FollowCursor, + ); + + let content = Column::with_children(vec![ + Container::new(fixed_tooltips) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into(), + cursor_tooltip_area.into(), + ]) + .width(Length::Fill) + .height(Length::Fill); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} + +fn tooltip_builder<'a>( + label: &str, + button_state: &'a mut button::State, + position: TooltipPosition, +) -> Container<'a, Message> { + Container::new(Tooltip::new( + Button::new(button_state, Text::new(label).size(40)).on_press(Message), + tooltip(), + position, + )) + .center_x() + .center_y() + .width(Length::Fill) + .height(Length::Fill) +} + +fn tooltip() -> Text { + Text::new("Tooltip").size(20) +} diff --git a/graphics/src/widget.rs b/graphics/src/widget.rs index 159ca91b..190ea9c0 100644 --- a/graphics/src/widget.rs +++ b/graphics/src/widget.rs @@ -20,6 +20,7 @@ pub mod scrollable; pub mod slider; pub mod svg; pub mod text_input; +pub mod tooltip; mod column; mod row; @@ -48,6 +49,8 @@ pub use scrollable::Scrollable; pub use slider::Slider; #[doc(no_inline)] pub use text_input::TextInput; +#[doc(no_inline)] +pub use tooltip::Tooltip; pub use column::Column; pub use image::Image; diff --git a/graphics/src/widget/tooltip.rs b/graphics/src/widget/tooltip.rs new file mode 100644 index 00000000..b5b0c558 --- /dev/null +++ b/graphics/src/widget/tooltip.rs @@ -0,0 +1,37 @@ +//! Decorate content and apply alignment. +use crate::defaults::Defaults; +use crate::{Backend, Renderer}; +use iced_native::{Element, Layout, Point, Rectangle}; + +/// An element decorating some content. +/// +/// This is an alias of an `iced_native` tooltip with a default +/// `Renderer`. +pub type Tooltip<'a, Message, Backend> = + iced_native::Tooltip<'a, Message, Renderer>; + +impl iced_native::tooltip::Renderer for Renderer +where + B: Backend, +{ + type Style = (); + + fn draw( + &mut self, + defaults: &Defaults, + cursor_position: Point, + content: &Element<'_, Message, Self>, + content_layout: Layout<'_>, + viewport: &Rectangle, + ) -> Self::Output { + let (content, mouse_interaction) = content.draw( + self, + &defaults, + content_layout, + cursor_position, + viewport, + ); + + (content, mouse_interaction) + } +} 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) + } +} diff --git a/src/widget.rs b/src/widget.rs index edd35d2d..eac50d57 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -17,7 +17,8 @@ mod platform { pub use crate::renderer::widget::{ button, checkbox, container, pane_grid, pick_list, progress_bar, radio, - rule, scrollable, slider, text_input, Column, Row, Space, Text, + rule, scrollable, slider, text_input, tooltip, Column, Row, Space, + Text, }; #[cfg(any(feature = "canvas", feature = "glow_canvas"))] @@ -52,7 +53,7 @@ mod platform { button::Button, checkbox::Checkbox, container::Container, image::Image, pane_grid::PaneGrid, pick_list::PickList, progress_bar::ProgressBar, radio::Radio, rule::Rule, scrollable::Scrollable, slider::Slider, - svg::Svg, text_input::TextInput, + svg::Svg, text_input::TextInput, tooltip::Tooltip, }; #[cfg(any(feature = "canvas", feature = "glow_canvas"))] diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs index 177ae1b6..304bb726 100644 --- a/wgpu/src/widget.rs +++ b/wgpu/src/widget.rs @@ -20,6 +20,7 @@ pub mod rule; pub mod scrollable; pub mod slider; pub mod text_input; +pub mod tooltip; #[doc(no_inline)] pub use button::Button; @@ -43,6 +44,8 @@ pub use scrollable::Scrollable; pub use slider::Slider; #[doc(no_inline)] pub use text_input::TextInput; +#[doc(no_inline)] +pub use tooltip::Tooltip; #[cfg(feature = "canvas")] #[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] diff --git a/wgpu/src/widget/tooltip.rs b/wgpu/src/widget/tooltip.rs new file mode 100644 index 00000000..b7d4f11e --- /dev/null +++ b/wgpu/src/widget/tooltip.rs @@ -0,0 +1,6 @@ +//! Display a widget over another. +/// A widget allowing the selection of a single value from a list of options. +pub type Tooltip<'a, Message> = + iced_native::Tooltip<'a, Message, crate::Renderer>; + +pub use iced_native::tooltip::TooltipPosition; -- cgit From 9ba955842913c9e1060bdb98deef9d645917b5cb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 15 Feb 2021 23:59:31 +0100 Subject: Allow dead code explicitly in `iced_web` --- web/src/widget/button.rs | 2 ++ web/src/widget/checkbox.rs | 1 + web/src/widget/container.rs | 1 + web/src/widget/radio.rs | 1 + web/src/widget/scrollable.rs | 1 + web/src/widget/slider.rs | 2 ++ 6 files changed, 8 insertions(+) diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs index e7cff6a0..7c389a9f 100644 --- a/web/src/widget/button.rs +++ b/web/src/widget/button.rs @@ -25,8 +25,10 @@ pub struct Button<'a, Message> { content: Element<'a, Message>, on_press: Option, width: Length, + #[allow(dead_code)] height: Length, min_width: u32, + #[allow(dead_code)] min_height: u32, padding: u16, style: Box, diff --git a/web/src/widget/checkbox.rs b/web/src/widget/checkbox.rs index 543af99a..43110aa7 100644 --- a/web/src/widget/checkbox.rs +++ b/web/src/widget/checkbox.rs @@ -30,6 +30,7 @@ pub struct Checkbox { label: String, id: Option, width: Length, + #[allow(dead_code)] style: Box, } diff --git a/web/src/widget/container.rs b/web/src/widget/container.rs index 7187a4f0..8de3cc41 100644 --- a/web/src/widget/container.rs +++ b/web/src/widget/container.rs @@ -12,6 +12,7 @@ pub struct Container<'a, Message> { width: Length, height: Length, max_width: u32, + #[allow(dead_code)] max_height: u32, horizontal_alignment: Align, vertical_alignment: Align, diff --git a/web/src/widget/radio.rs b/web/src/widget/radio.rs index 5a9bc379..fbc88d29 100644 --- a/web/src/widget/radio.rs +++ b/web/src/widget/radio.rs @@ -37,6 +37,7 @@ pub struct Radio { label: String, id: Option, name: Option, + #[allow(dead_code)] style: Box, } diff --git a/web/src/widget/scrollable.rs b/web/src/widget/scrollable.rs index f9135dd6..bd9260ff 100644 --- a/web/src/widget/scrollable.rs +++ b/web/src/widget/scrollable.rs @@ -11,6 +11,7 @@ pub struct Scrollable<'a, Message> { height: Length, max_height: u32, content: Column<'a, Message>, + #[allow(dead_code)] style: Box, } diff --git a/web/src/widget/slider.rs b/web/src/widget/slider.rs index 91a4d2ec..f457aa4c 100644 --- a/web/src/widget/slider.rs +++ b/web/src/widget/slider.rs @@ -38,7 +38,9 @@ pub struct Slider<'a, T, Message> { step: T, value: T, on_change: Rc Message>>, + #[allow(dead_code)] width: Length, + #[allow(dead_code)] style: Box, } -- cgit From ec20763aef242bfa52f1d1afb910d36c903343dc Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 16 Feb 2021 00:04:36 +0100 Subject: Use string literal as panic message in `iced_glow` --- glow/src/program.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/glow/src/program.rs b/glow/src/program.rs index 489a194f..601f9ce6 100644 --- a/glow/src/program.rs +++ b/glow/src/program.rs @@ -17,7 +17,7 @@ pub unsafe fn create( gl.compile_shader(shader); if !gl.get_shader_compile_status(shader) { - panic!(gl.get_shader_info_log(shader)); + panic!("{}", gl.get_shader_info_log(shader)); } gl.attach_shader(program, shader); @@ -27,7 +27,7 @@ pub unsafe fn create( gl.link_program(program); if !gl.get_program_link_status(program) { - panic!(gl.get_program_info_log(program)); + panic!("{}", gl.get_program_info_log(program)); } for shader in shaders { -- cgit From 6759a5c56fc53286d77698ac9a86812b6d7b03ff Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 23 Feb 2021 02:16:12 +0100 Subject: Log event subscription error as a warning --- futures/src/subscription/tracker.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/futures/src/subscription/tracker.rs b/futures/src/subscription/tracker.rs index 43222b5b..3a8d4a87 100644 --- a/futures/src/subscription/tracker.rs +++ b/futures/src/subscription/tracker.rs @@ -135,7 +135,7 @@ where .filter_map(|connection| connection.listener.as_mut()) .for_each(|listener| { if let Err(error) = listener.try_send(event.clone()) { - log::error!( + log::warn!( "Error sending event to subscription: {:?}", error ); -- 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 --- examples/tooltip/src/main.rs | 114 +++++++++---------- graphics/src/widget/tooltip.rs | 76 ++++++++++++- 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 +++++---------------------------- wgpu/src/widget/tooltip.rs | 2 +- 15 files changed, 172 insertions(+), 303 deletions(-) diff --git a/examples/tooltip/src/main.rs b/examples/tooltip/src/main.rs index 6e2c4dd4..a49caa70 100644 --- a/examples/tooltip/src/main.rs +++ b/examples/tooltip/src/main.rs @@ -1,6 +1,7 @@ +use iced::tooltip::{self, Tooltip}; use iced::{ - button, tooltip::TooltipPosition, Button, Column, Container, Element, - Length, Row, Sandbox, Settings, Text, Tooltip, + button, Button, Column, Container, Element, HorizontalAlignment, Length, + Row, Sandbox, Settings, Text, VerticalAlignment, }; pub fn main() { @@ -9,11 +10,11 @@ pub fn main() { #[derive(Default)] struct Example { - tooltip_top_button_state: button::State, - tooltip_bottom_button_state: button::State, - tooltip_right_button_state: button::State, - tooltip_left_button_state: button::State, - tooltip_cursor_button_state: button::State, + top: button::State, + bottom: button::State, + right: button::State, + left: button::State, + follow_cursor: button::State, } #[derive(Debug, Clone, Copy)] @@ -33,52 +34,39 @@ impl Sandbox for Example { fn update(&mut self, _message: Message) {} fn view(&mut self) -> Element { - let tooltip_top = tooltip_builder( - "Tooltip at top", - &mut self.tooltip_top_button_state, - TooltipPosition::Top, - ); - let tooltip_bottom = tooltip_builder( + let top = + tooltip("Tooltip at top", &mut self.top, tooltip::Position::Top); + + let bottom = tooltip( "Tooltip at bottom", - &mut self.tooltip_bottom_button_state, - TooltipPosition::Bottom, + &mut self.bottom, + tooltip::Position::Bottom, ); - let tooltip_right = tooltip_builder( + + let left = + tooltip("Tooltip at left", &mut self.left, tooltip::Position::Left); + + let right = tooltip( "Tooltip at right", - &mut self.tooltip_right_button_state, - TooltipPosition::Right, - ); - let tooltip_left = tooltip_builder( - "Tooltip at left", - &mut self.tooltip_left_button_state, - TooltipPosition::Left, + &mut self.right, + tooltip::Position::Right, ); let fixed_tooltips = Row::with_children(vec![ - tooltip_top.into(), - tooltip_bottom.into(), - tooltip_left.into(), - tooltip_right.into(), + top.into(), + bottom.into(), + left.into(), + right.into(), ]) .width(Length::Fill) .height(Length::Fill) .align_items(iced::Align::Center) - .spacing(120); - - let cursor_tooltip_area = Tooltip::new( - Button::new( - &mut self.tooltip_cursor_button_state, - Container::new(Text::new("Tooltip follows cursor").size(40)) - .center_y() - .center_x() - .width(Length::Fill) - .height(Length::Fill), - ) - .on_press(Message) - .width(Length::Fill) - .height(Length::Fill), - tooltip(), - TooltipPosition::FollowCursor, + .spacing(50); + + let follow_cursor = tooltip( + "Tooltip follows cursor", + &mut self.follow_cursor, + tooltip::Position::FollowCursor, ); let content = Column::with_children(vec![ @@ -88,36 +76,42 @@ impl Sandbox for Example { .center_x() .center_y() .into(), - cursor_tooltip_area.into(), + follow_cursor.into(), ]) .width(Length::Fill) - .height(Length::Fill); + .height(Length::Fill) + .spacing(50); Container::new(content) .width(Length::Fill) .height(Length::Fill) .center_x() .center_y() + .padding(50) .into() } } -fn tooltip_builder<'a>( +fn tooltip<'a>( label: &str, button_state: &'a mut button::State, - position: TooltipPosition, -) -> Container<'a, Message> { - Container::new(Tooltip::new( - Button::new(button_state, Text::new(label).size(40)).on_press(Message), - tooltip(), + position: tooltip::Position, +) -> Element<'a, Message> { + Tooltip::new( + Button::new( + button_state, + Text::new(label) + .size(40) + .width(Length::Fill) + .height(Length::Fill) + .horizontal_alignment(HorizontalAlignment::Center) + .vertical_alignment(VerticalAlignment::Center), + ) + .on_press(Message) + .width(Length::Fill) + .height(Length::Fill), + Text::new("Tooltip"), position, - )) - .center_x() - .center_y() - .width(Length::Fill) - .height(Length::Fill) -} - -fn tooltip() -> Text { - Text::new("Tooltip").size(20) + ) + .into() } diff --git a/graphics/src/widget/tooltip.rs b/graphics/src/widget/tooltip.rs index b5b0c558..0b4d1d2f 100644 --- a/graphics/src/widget/tooltip.rs +++ b/graphics/src/widget/tooltip.rs @@ -1,7 +1,10 @@ //! Decorate content and apply alignment. +use crate::backend::{self, Backend}; use crate::defaults::Defaults; -use crate::{Backend, Renderer}; -use iced_native::{Element, Layout, Point, Rectangle}; +use crate::{Primitive, Renderer, Vector}; + +use iced_native::layout::{self, Layout}; +use iced_native::{Element, Point, Rectangle, Size, Text}; /// An element decorating some content. /// @@ -10,9 +13,11 @@ use iced_native::{Element, Layout, Point, Rectangle}; pub type Tooltip<'a, Message, Backend> = iced_native::Tooltip<'a, Message, Renderer>; +pub use iced_native::tooltip::Position; + impl iced_native::tooltip::Renderer for Renderer where - B: Backend, + B: Backend + backend::Text, { type Style = (); @@ -20,10 +25,14 @@ where &mut self, defaults: &Defaults, cursor_position: Point, - content: &Element<'_, Message, Self>, content_layout: Layout<'_>, viewport: &Rectangle, + content: &Element<'_, Message, Self>, + tooltip: &Text, + position: Position, ) -> Self::Output { + let bounds = content_layout.bounds(); + let (content, mouse_interaction) = content.draw( self, &defaults, @@ -32,6 +41,63 @@ where viewport, ); - (content, mouse_interaction) + if bounds.contains(cursor_position) { + use iced_native::Widget; + + let tooltip_layout = Widget::<(), Self>::layout( + tooltip, + self, + &layout::Limits::new(Size::ZERO, viewport.size()), + ); + + let tooltip_bounds = tooltip_layout.bounds(); + + let x_center = + bounds.x + (bounds.width - tooltip_bounds.width) / 2.0; + + let y_center = + bounds.y + (bounds.height - tooltip_bounds.height) / 2.0; + + let offset = match position { + Position::Top => { + Vector::new(x_center, bounds.y - tooltip_bounds.height) + } + Position::Bottom => { + Vector::new(x_center, bounds.y + bounds.height) + } + Position::Left => { + Vector::new(bounds.x - tooltip_bounds.width, y_center) + } + Position::Right => { + Vector::new(bounds.x + bounds.width, y_center) + } + Position::FollowCursor => Vector::new( + cursor_position.x, + cursor_position.y - tooltip_bounds.height, + ), + }; + + let (tooltip, _) = Widget::<(), Self>::draw( + tooltip, + self, + defaults, + Layout::with_offset(offset, &tooltip_layout), + cursor_position, + viewport, + ); + + ( + Primitive::Clip { + bounds: *viewport, + offset: Vector::new(0, 0), + content: Box::new(Primitive::Group { + primitives: vec![content, tooltip], + }), + }, + mouse_interaction, + ) + } else { + (content, mouse_interaction) + } } } 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( diff --git a/wgpu/src/widget/tooltip.rs b/wgpu/src/widget/tooltip.rs index b7d4f11e..89ab3a15 100644 --- a/wgpu/src/widget/tooltip.rs +++ b/wgpu/src/widget/tooltip.rs @@ -3,4 +3,4 @@ pub type Tooltip<'a, Message> = iced_native::Tooltip<'a, Message, crate::Renderer>; -pub use iced_native::tooltip::TooltipPosition; +pub use iced_native::tooltip::Position; -- 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(-) 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 --- examples/tooltip/src/main.rs | 1 + graphics/src/widget/tooltip.rs | 16 ++++++++++------ native/src/widget/tooltip.rs | 10 ++++++++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/examples/tooltip/src/main.rs b/examples/tooltip/src/main.rs index a49caa70..3677dc75 100644 --- a/examples/tooltip/src/main.rs +++ b/examples/tooltip/src/main.rs @@ -113,5 +113,6 @@ fn tooltip<'a>( Text::new("Tooltip"), position, ) + .gap(10) .into() } diff --git a/graphics/src/widget/tooltip.rs b/graphics/src/widget/tooltip.rs index 0b4d1d2f..26b18507 100644 --- a/graphics/src/widget/tooltip.rs +++ b/graphics/src/widget/tooltip.rs @@ -30,6 +30,7 @@ where content: &Element<'_, Message, Self>, tooltip: &Text, position: Position, + gap: u16, ) -> Self::Output { let bounds = content_layout.bounds(); @@ -58,18 +59,21 @@ where let y_center = bounds.y + (bounds.height - tooltip_bounds.height) / 2.0; + let gap = f32::from(gap); + let offset = match position { - Position::Top => { - Vector::new(x_center, bounds.y - tooltip_bounds.height) - } + Position::Top => Vector::new( + x_center, + bounds.y - tooltip_bounds.height - gap, + ), Position::Bottom => { - Vector::new(x_center, bounds.y + bounds.height) + Vector::new(x_center, bounds.y + bounds.height + gap) } Position::Left => { - Vector::new(bounds.x - tooltip_bounds.width, y_center) + Vector::new(bounds.x - tooltip_bounds.width - gap, y_center) } Position::Right => { - Vector::new(bounds.x + bounds.width, y_center) + Vector::new(bounds.x + bounds.width + gap, y_center) } Position::FollowCursor => Vector::new( cursor_position.x, 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 9d4996cbab012559c73852145b9968f6b101663a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 23 Feb 2021 03:18:47 +0100 Subject: Export `Tooltip` in `iced_glow` --- glow/src/widget.rs | 3 +++ glow/src/widget/tooltip.rs | 6 ++++++ 2 files changed, 9 insertions(+) create mode 100644 glow/src/widget/tooltip.rs diff --git a/glow/src/widget.rs b/glow/src/widget.rs index b5c84c56..5481216a 100644 --- a/glow/src/widget.rs +++ b/glow/src/widget.rs @@ -20,6 +20,7 @@ pub mod rule; pub mod scrollable; pub mod slider; pub mod text_input; +pub mod tooltip; #[doc(no_inline)] pub use button::Button; @@ -43,6 +44,8 @@ pub use scrollable::Scrollable; pub use slider::Slider; #[doc(no_inline)] pub use text_input::TextInput; +#[doc(no_inline)] +pub use tooltip::Tooltip; #[cfg(feature = "canvas")] #[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] diff --git a/glow/src/widget/tooltip.rs b/glow/src/widget/tooltip.rs new file mode 100644 index 00000000..89ab3a15 --- /dev/null +++ b/glow/src/widget/tooltip.rs @@ -0,0 +1,6 @@ +//! Display a widget over another. +/// A widget allowing the selection of a single value from a list of options. +pub type Tooltip<'a, Message> = + iced_native::Tooltip<'a, Message, crate::Renderer>; + +pub use iced_native::tooltip::Position; -- 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` --- examples/tooltip/src/main.rs | 20 ++++++++++ graphics/src/widget/tooltip.rs | 86 ++++++++++++++++++++++++++++++------------ native/src/widget/tooltip.rs | 40 ++++++++++++++++---- 3 files changed, 115 insertions(+), 31 deletions(-) diff --git a/examples/tooltip/src/main.rs b/examples/tooltip/src/main.rs index 3677dc75..b10b8b50 100644 --- a/examples/tooltip/src/main.rs +++ b/examples/tooltip/src/main.rs @@ -114,5 +114,25 @@ fn tooltip<'a>( position, ) .gap(10) + .padding(20) + .style(style::Tooltip) .into() } + +mod style { + use iced::container; + use iced::Color; + + pub struct Tooltip; + + impl container::StyleSheet for Tooltip { + fn style(&self) -> container::Style { + container::Style { + text_color: Some(Color::from_rgb8(0xEE, 0xEE, 0xEE)), + background: Some(Color::from_rgb(0.11, 0.42, 0.87).into()), + border_radius: 12.0, + ..container::Style::default() + } + } + } +} diff --git a/graphics/src/widget/tooltip.rs b/graphics/src/widget/tooltip.rs index 26b18507..51b465a5 100644 --- a/graphics/src/widget/tooltip.rs +++ b/graphics/src/widget/tooltip.rs @@ -1,8 +1,9 @@ //! Decorate content and apply alignment. use crate::backend::{self, Backend}; -use crate::defaults::Defaults; +use crate::defaults::{self, Defaults}; use crate::{Primitive, Renderer, Vector}; +use iced_native::container; use iced_native::layout::{self, Layout}; use iced_native::{Element, Point, Rectangle, Size, Text}; @@ -19,7 +20,7 @@ impl iced_native::tooltip::Renderer for Renderer where B: Backend + backend::Text, { - type Style = (); + const DEFAULT_PADDING: u16 = 5; fn draw( &mut self, @@ -30,10 +31,10 @@ where content: &Element<'_, Message, Self>, tooltip: &Text, position: Position, + style_sheet: &::Style, gap: u16, + padding: u16, ) -> Self::Output { - let bounds = content_layout.bounds(); - let (content, mouse_interaction) = content.draw( self, &defaults, @@ -42,13 +43,26 @@ where viewport, ); + let bounds = content_layout.bounds(); + if bounds.contains(cursor_position) { use iced_native::Widget; + let gap = f32::from(gap); + let padding = f32::from(padding); + let style = style_sheet.style(); + + let defaults = Defaults { + text: defaults::Text { + color: style.text_color.unwrap_or(defaults.text.color), + }, + }; + let tooltip_layout = Widget::<(), Self>::layout( tooltip, self, - &layout::Limits::new(Size::ZERO, viewport.size()), + &layout::Limits::new(Size::ZERO, viewport.size()) + .pad(f32::from(padding)), ); let tooltip_bounds = tooltip_layout.bounds(); @@ -59,22 +73,23 @@ where let y_center = bounds.y + (bounds.height - tooltip_bounds.height) / 2.0; - let gap = f32::from(gap); - let offset = match position { Position::Top => Vector::new( x_center, - bounds.y - tooltip_bounds.height - gap, + bounds.y - tooltip_bounds.height - gap - padding, + ), + Position::Bottom => Vector::new( + x_center, + bounds.y + bounds.height + gap + padding, + ), + Position::Left => Vector::new( + bounds.x - tooltip_bounds.width - gap - padding, + y_center, + ), + Position::Right => Vector::new( + bounds.x + bounds.width + gap + padding, + y_center, ), - Position::Bottom => { - Vector::new(x_center, bounds.y + bounds.height + gap) - } - Position::Left => { - Vector::new(bounds.x - tooltip_bounds.width - gap, y_center) - } - Position::Right => { - Vector::new(bounds.x + bounds.width + gap, y_center) - } Position::FollowCursor => Vector::new( cursor_position.x, cursor_position.y - tooltip_bounds.height, @@ -84,19 +99,42 @@ where let (tooltip, _) = Widget::<(), Self>::draw( tooltip, self, - defaults, + &defaults, Layout::with_offset(offset, &tooltip_layout), cursor_position, viewport, ); + let tooltip_bounds = Rectangle { + x: offset.x - padding, + y: offset.y - padding, + width: tooltip_bounds.width + padding * 2.0, + height: tooltip_bounds.height + padding * 2.0, + }; + ( - Primitive::Clip { - bounds: *viewport, - offset: Vector::new(0, 0), - content: Box::new(Primitive::Group { - primitives: vec![content, tooltip], - }), + Primitive::Group { + primitives: vec![ + content, + Primitive::Clip { + bounds: *viewport, + offset: Vector::new(0, 0), + content: Box::new( + if let Some(background) = + crate::container::background( + tooltip_bounds, + &style, + ) + { + Primitive::Group { + primitives: vec![background, tooltip], + } + } else { + tooltip + }, + ), + }, + ], }, mouse_interaction, ) 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 5e2743361bf0a2e10c0e14638a9b2818c9e5e364 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 23 Feb 2021 04:02:55 +0100 Subject: Generate new layers only for clip primitives in `Layer::generate` --- graphics/src/layer.rs | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs index ab40b114..7dce1d4c 100644 --- a/graphics/src/layer.rs +++ b/graphics/src/layer.rs @@ -82,7 +82,12 @@ impl<'a> Layer<'a> { let mut layers = vec![first_layer]; - Self::process_primitive(&mut layers, Vector::new(0.0, 0.0), primitive); + Self::process_primitive( + &mut layers, + Vector::new(0.0, 0.0), + primitive, + 0, + ); layers } @@ -91,13 +96,19 @@ impl<'a> Layer<'a> { layers: &mut Vec, translation: Vector, primitive: &'a Primitive, + current_layer: usize, ) { match primitive { Primitive::None => {} Primitive::Group { primitives } => { // TODO: Inspect a bit and regroup (?) for primitive in primitives { - Self::process_primitive(layers, translation, primitive) + Self::process_primitive( + layers, + translation, + primitive, + current_layer, + ) } } Primitive::Text { @@ -109,7 +120,7 @@ impl<'a> Layer<'a> { horizontal_alignment, vertical_alignment, } => { - let layer = layers.last_mut().unwrap(); + let layer = &mut layers[current_layer]; layer.text.push(Text { content, @@ -128,7 +139,7 @@ impl<'a> Layer<'a> { border_width, border_color, } => { - let layer = layers.last_mut().unwrap(); + let layer = &mut layers[current_layer]; // TODO: Move some of these computations to the GPU (?) layer.quads.push(Quad { @@ -146,7 +157,7 @@ impl<'a> Layer<'a> { }); } Primitive::Mesh2D { buffers, size } => { - let layer = layers.last_mut().unwrap(); + let layer = &mut layers[current_layer]; let bounds = Rectangle::new( Point::new(translation.x, translation.y), @@ -167,7 +178,7 @@ impl<'a> Layer<'a> { offset, content, } => { - let layer = layers.last_mut().unwrap(); + let layer = &mut layers[current_layer]; let translated_bounds = *bounds + translation; // Only draw visible content @@ -175,16 +186,15 @@ impl<'a> Layer<'a> { layer.bounds.intersection(&translated_bounds) { let clip_layer = Layer::new(clip_bounds); - let new_layer = Layer::new(layer.bounds); - layers.push(clip_layer); + Self::process_primitive( layers, translation - Vector::new(offset.x as f32, offset.y as f32), content, + layers.len() - 1, ); - layers.push(new_layer); } } Primitive::Translate { @@ -195,13 +205,19 @@ impl<'a> Layer<'a> { layers, translation + *new_translation, &content, + current_layer, ); } Primitive::Cached { cache } => { - Self::process_primitive(layers, translation, &cache); + Self::process_primitive( + layers, + translation, + &cache, + current_layer, + ); } Primitive::Image { handle, bounds } => { - let layer = layers.last_mut().unwrap(); + let layer = &mut layers[current_layer]; layer.images.push(Image::Raster { handle: handle.clone(), @@ -209,7 +225,7 @@ impl<'a> Layer<'a> { }); } Primitive::Svg { handle, bounds } => { - let layer = layers.last_mut().unwrap(); + let layer = &mut layers[current_layer]; layer.images.push(Image::Vector { handle: handle.clone(), -- 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` --- examples/tooltip/src/main.rs | 6 +++--- native/src/widget/tooltip.rs | 30 ++++++++++++++++++++++-------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/examples/tooltip/src/main.rs b/examples/tooltip/src/main.rs index b10b8b50..d6c8b8e1 100644 --- a/examples/tooltip/src/main.rs +++ b/examples/tooltip/src/main.rs @@ -110,11 +110,11 @@ fn tooltip<'a>( .on_press(Message) .width(Length::Fill) .height(Length::Fill), - Text::new("Tooltip"), + "Tooltip", position, ) - .gap(10) - .padding(20) + .gap(5) + .padding(10) .style(style::Tooltip) .into() } 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 --- graphics/src/widget/pane_grid.rs | 19 ++++++++++++++----- 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 +++- 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/graphics/src/widget/pane_grid.rs b/graphics/src/widget/pane_grid.rs index 3cd4fd34..d06f8c6c 100644 --- a/graphics/src/widget/pane_grid.rs +++ b/graphics/src/widget/pane_grid.rs @@ -45,6 +45,7 @@ where layout: Layout<'_>, style_sheet: &::Style, cursor_position: Point, + viewport: &Rectangle, ) -> Self::Output { let pane_cursor_position = if dragging.is_some() { // TODO: Remove once cursor availability is encoded in the type @@ -62,8 +63,13 @@ where .zip(layout.children()) .enumerate() .map(|(i, ((id, pane), layout))| { - let (primitive, new_mouse_interaction) = - pane.draw(self, defaults, layout, pane_cursor_position); + let (primitive, new_mouse_interaction) = pane.draw( + self, + defaults, + layout, + pane_cursor_position, + viewport, + ); if new_mouse_interaction > mouse_interaction { mouse_interaction = new_mouse_interaction; @@ -180,12 +186,13 @@ where title_bar: Option<(&TitleBar<'_, Message, Self>, Layout<'_>)>, body: (&Element<'_, Message, Self>, Layout<'_>), cursor_position: Point, + viewport: &Rectangle, ) -> Self::Output { let style = style_sheet.style(); let (body, body_layout) = body; let (body_primitive, body_interaction) = - body.draw(self, defaults, body_layout, cursor_position, &bounds); + body.draw(self, defaults, body_layout, cursor_position, viewport); let background = crate::widget::container::background(bounds, &style); @@ -199,6 +206,7 @@ where defaults, title_bar_layout, cursor_position, + viewport, show_controls, ); @@ -240,6 +248,7 @@ where content: (&Element<'_, Message, Self>, Layout<'_>), controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>, cursor_position: Point, + viewport: &Rectangle, ) -> Self::Output { let style = style_sheet.style(); let (title_content, title_layout) = content; @@ -257,7 +266,7 @@ where &defaults, title_layout, cursor_position, - &bounds, + viewport, ); if let Some((controls, controls_layout)) = controls { @@ -266,7 +275,7 @@ where &defaults, controls_layout, cursor_position, - &bounds, + viewport, ); ( 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 c51b771519c5da5a4d5cd39eaadfe763c1e60978 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 27 Feb 2021 03:47:13 +0100 Subject: Reposition `Tooltip` inside `viewport` bounds ... only when out of bounds. --- graphics/src/widget/tooltip.rs | 97 ++++++++++++++++++++++++++---------------- 1 file changed, 60 insertions(+), 37 deletions(-) diff --git a/graphics/src/widget/tooltip.rs b/graphics/src/widget/tooltip.rs index 51b465a5..1a1b5352 100644 --- a/graphics/src/widget/tooltip.rs +++ b/graphics/src/widget/tooltip.rs @@ -58,60 +58,83 @@ where }, }; - let tooltip_layout = Widget::<(), Self>::layout( + let text_layout = Widget::<(), Self>::layout( tooltip, self, &layout::Limits::new(Size::ZERO, viewport.size()) .pad(f32::from(padding)), ); - let tooltip_bounds = tooltip_layout.bounds(); - - let x_center = - bounds.x + (bounds.width - tooltip_bounds.width) / 2.0; - + let text_bounds = text_layout.bounds(); + let x_center = bounds.x + (bounds.width - text_bounds.width) / 2.0; let y_center = - bounds.y + (bounds.height - tooltip_bounds.height) / 2.0; - - let offset = match position { - Position::Top => Vector::new( - x_center, - bounds.y - tooltip_bounds.height - gap - padding, - ), - Position::Bottom => Vector::new( - x_center, - bounds.y + bounds.height + gap + padding, - ), - Position::Left => Vector::new( - bounds.x - tooltip_bounds.width - gap - padding, - y_center, - ), - Position::Right => Vector::new( - bounds.x + bounds.width + gap + padding, - y_center, - ), - Position::FollowCursor => Vector::new( - cursor_position.x, - cursor_position.y - tooltip_bounds.height, - ), + bounds.y + (bounds.height - text_bounds.height) / 2.0; + + let mut tooltip_bounds = { + let offset = match position { + Position::Top => Vector::new( + x_center, + bounds.y - text_bounds.height - gap - padding, + ), + Position::Bottom => Vector::new( + x_center, + bounds.y + bounds.height + gap + padding, + ), + Position::Left => Vector::new( + bounds.x - text_bounds.width - gap - padding, + y_center, + ), + Position::Right => Vector::new( + bounds.x + bounds.width + gap + padding, + y_center, + ), + Position::FollowCursor => Vector::new( + cursor_position.x, + cursor_position.y - text_bounds.height, + ), + }; + + Rectangle { + x: offset.x - padding, + y: offset.y - padding, + width: text_bounds.width + padding * 2.0, + height: text_bounds.height + padding * 2.0, + } }; + if tooltip_bounds.x < viewport.x { + tooltip_bounds.x = viewport.x; + } else if viewport.x + viewport.width + < tooltip_bounds.x + tooltip_bounds.width + { + tooltip_bounds.x = + viewport.x + viewport.width - tooltip_bounds.width; + } + + if tooltip_bounds.y < viewport.y { + tooltip_bounds.y = viewport.y; + } else if viewport.y + viewport.height + < tooltip_bounds.y + tooltip_bounds.height + { + tooltip_bounds.y = + viewport.y + viewport.height - tooltip_bounds.height; + } + let (tooltip, _) = Widget::<(), Self>::draw( tooltip, self, &defaults, - Layout::with_offset(offset, &tooltip_layout), + Layout::with_offset( + Vector::new( + tooltip_bounds.x + padding, + tooltip_bounds.y + padding, + ), + &text_layout, + ), cursor_position, viewport, ); - let tooltip_bounds = Rectangle { - x: offset.x - padding, - y: offset.y - padding, - width: tooltip_bounds.width + padding * 2.0, - height: tooltip_bounds.height + padding * 2.0, - }; - ( Primitive::Group { primitives: vec![ -- cgit From 5f27ed47207645bbb9fe2837675531584d88d394 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 3 Mar 2021 01:23:18 +0100 Subject: Update: rand --- examples/solar_system/Cargo.toml | 2 +- examples/solar_system/src/main.rs | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/examples/solar_system/Cargo.toml b/examples/solar_system/Cargo.toml index 44ced729..327fe0aa 100644 --- a/examples/solar_system/Cargo.toml +++ b/examples/solar_system/Cargo.toml @@ -7,4 +7,4 @@ publish = false [dependencies] iced = { path = "../..", features = ["canvas", "tokio", "debug"] } -rand = "0.7" +rand = "0.8.3" diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 6a2de736..c8f74978 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -117,15 +117,13 @@ impl State { ( Point::new( rng.gen_range( - -(width as f32) / 2.0, - width as f32 / 2.0, + (-(width as f32) / 2.0)..(width as f32 / 2.0), ), rng.gen_range( - -(height as f32) / 2.0, - height as f32 / 2.0, + (-(height as f32) / 2.0)..(height as f32 / 2.0), ), ), - rng.gen_range(0.5, 1.0), + rng.gen_range(0.5..1.0), ) }) .collect() -- 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(+) 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` --- glutin/src/application.rs | 2 +- native/src/clipboard.rs | 4 ++-- native/src/widget/text_input.rs | 2 +- winit/Cargo.toml | 2 +- winit/src/application.rs | 2 +- winit/src/clipboard.rs | 8 +++++--- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 42513feb..e6bb28e4 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -136,7 +136,7 @@ async fn run_instance( use glutin::event; use iced_winit::futures::stream::StreamExt; - let clipboard = Clipboard::new(context.window()); + let clipboard = Clipboard::connect(context.window()); let mut state = application::State::new(&application, context.window()); let mut viewport_version = state.viewport_version(); 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()) diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 39a6a5fa..ecee0e2e 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -15,7 +15,7 @@ debug = ["iced_native/debug"] [dependencies] winit = "0.24" -window_clipboard = "0.1" +window_clipboard = "0.2" log = "0.4" thiserror = "1.0" diff --git a/winit/src/application.rs b/winit/src/application.rs index d1a94864..6f8cfc22 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -194,7 +194,7 @@ async fn run_instance( use winit::event; let surface = compositor.create_surface(&window); - let clipboard = Clipboard::new(&window); + let clipboard = Clipboard::connect(&window); let mut state = State::new(&application, &window); let mut viewport_version = state.viewport_version(); diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index 93d53b11..cce2b371 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -5,13 +5,15 @@ pub struct Clipboard(window_clipboard::Clipboard); impl Clipboard { /// Creates a new [`Clipboard`] for the given window. - pub fn new(window: &winit::window::Window) -> Option { - window_clipboard::Clipboard::new(window).map(Clipboard).ok() + pub fn connect(window: &winit::window::Window) -> Option { + window_clipboard::Clipboard::connect(window) + .map(Clipboard) + .ok() } } impl iced_native::Clipboard for Clipboard { - fn content(&self) -> Option { + fn read(&self) -> Option { self.0.read().ok() } } -- 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 +++ winit/src/clipboard.rs | 7 +++++++ 2 files changed, 10 insertions(+) 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); } diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index cce2b371..06e7c1a8 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -16,4 +16,11 @@ impl iced_native::Clipboard for Clipboard { fn read(&self) -> Option { self.0.read().ok() } + + fn write(&mut self, contents: String) { + match self.0.write(contents) { + Ok(()) => {} + Err(error) => log::warn!("error writing to clipboard: {}", error), + } + } } -- 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 --- examples/integration/src/main.rs | 5 ++- glutin/src/application.rs | 4 +- graphics/src/widget/canvas.rs | 4 +- 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 +-- winit/src/application.rs | 4 +- winit/src/clipboard.rs | 34 +++++++++++++---- 31 files changed, 175 insertions(+), 129 deletions(-) diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index ec01c4e6..b57c73d8 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -5,7 +5,7 @@ use controls::Controls; use scene::Scene; use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport}; -use iced_winit::{conversion, futures, program, winit, Debug, Size}; +use iced_winit::{conversion, futures, program, winit, Clipboard, Debug, Size}; use futures::task::SpawnExt; use winit::{ @@ -28,6 +28,7 @@ pub fn main() { ); let mut cursor_position = PhysicalPosition::new(-1.0, -1.0); let mut modifiers = ModifiersState::default(); + let mut clipboard = Clipboard::connect(&window); // Initialize wgpu let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY); @@ -141,8 +142,8 @@ pub fn main() { cursor_position, viewport.scale_factor(), ), - None, &mut renderer, + &mut clipboard, &mut debug, ); diff --git a/glutin/src/application.rs b/glutin/src/application.rs index e6bb28e4..19eee219 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -136,7 +136,7 @@ async fn run_instance( use glutin::event; use iced_winit::futures::stream::StreamExt; - let clipboard = Clipboard::connect(context.window()); + let mut clipboard = Clipboard::connect(context.window()); let mut state = application::State::new(&application, context.window()); let mut viewport_version = state.viewport_version(); @@ -170,8 +170,8 @@ async fn run_instance( let statuses = user_interface.update( &events, state.cursor_position(), - clipboard.as_ref().map(|c| c as _), &mut renderer, + &mut clipboard, &mut messages, ); diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index 95ede50f..7897c8ec 100644 --- a/graphics/src/widget/canvas.rs +++ b/graphics/src/widget/canvas.rs @@ -154,9 +154,9 @@ where event: iced_native::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(); 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, ) } diff --git a/winit/src/application.rs b/winit/src/application.rs index 6f8cfc22..2cb2c016 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -194,7 +194,7 @@ async fn run_instance( use winit::event; let surface = compositor.create_surface(&window); - let clipboard = Clipboard::connect(&window); + let mut clipboard = Clipboard::connect(&window); let mut state = State::new(&application, &window); let mut viewport_version = state.viewport_version(); @@ -237,8 +237,8 @@ async fn run_instance( let statuses = user_interface.update( &events, state.cursor_position(), - clipboard.as_ref().map(|c| c as _), &mut renderer, + &mut clipboard, &mut messages, ); diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index 06e7c1a8..15dc989f 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -1,26 +1,44 @@ /// A buffer for short-term storage and transfer within and between /// applications. #[allow(missing_debug_implementations)] -pub struct Clipboard(window_clipboard::Clipboard); +pub struct Clipboard { + state: State, +} + +enum State { + Connected(window_clipboard::Clipboard), + Unavailable, +} impl Clipboard { /// Creates a new [`Clipboard`] for the given window. - pub fn connect(window: &winit::window::Window) -> Option { - window_clipboard::Clipboard::connect(window) - .map(Clipboard) + pub fn connect(window: &winit::window::Window) -> Clipboard { + let state = window_clipboard::Clipboard::connect(window) .ok() + .map(State::Connected) + .unwrap_or(State::Unavailable); + + Clipboard { state } } } impl iced_native::Clipboard for Clipboard { fn read(&self) -> Option { - self.0.read().ok() + match &self.state { + State::Connected(clipboard) => clipboard.read().ok(), + State::Unavailable => None, + } } fn write(&mut self, contents: String) { - match self.0.write(contents) { - Ok(()) => {} - Err(error) => log::warn!("error writing to clipboard: {}", error), + match &mut self.state { + State::Connected(clipboard) => match clipboard.write(contents) { + Ok(()) => {} + Err(error) => { + log::warn!("error writing to clipboard: {}", error) + } + }, + State::Unavailable => {} } } } -- 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(+) 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` --- examples/clock/src/main.rs | 10 +++++++--- examples/download_progress/src/main.rs | 10 +++++++--- examples/events/src/main.rs | 10 +++++++--- examples/game_of_life/src/main.rs | 10 +++++++--- examples/integration/src/controls.rs | 11 ++++++++--- examples/pane_grid/src/main.rs | 11 ++++++++--- examples/pokedex/src/main.rs | 10 +++++++--- examples/solar_system/src/main.rs | 10 +++++++--- examples/stopwatch/src/main.rs | 10 +++++++--- examples/todos/src/main.rs | 10 +++++++--- glutin/src/application.rs | 1 + glutin/src/lib.rs | 2 +- native/src/program.rs | 11 +++++++++-- native/src/program/state.rs | 7 +++---- src/application.rs | 25 ++++++++++++++++++------- src/lib.rs | 5 +++-- src/sandbox.rs | 9 +++++++-- winit/src/application.rs | 6 ++++-- 18 files changed, 118 insertions(+), 50 deletions(-) diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index b317ac00..9bcc827b 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,7 +1,7 @@ use iced::{ canvas::{self, Cache, Canvas, Cursor, Geometry, LineCap, Path, Stroke}, - executor, time, Application, Color, Command, Container, Element, Length, - Point, Rectangle, Settings, Subscription, Vector, + executor, time, Application, Clipboard, Color, Command, Container, Element, + Length, Point, Rectangle, Settings, Subscription, Vector, }; pub fn main() -> iced::Result { @@ -40,7 +40,11 @@ impl Application for Clock { String::from("Clock - Iced") } - fn update(&mut self, message: Message) -> Command { + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command { match message { Message::Tick(local_time) => { let now = local_time; diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index cd024926..6f844e66 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -1,6 +1,6 @@ use iced::{ - button, executor, Align, Application, Button, Column, Command, Container, - Element, Length, ProgressBar, Settings, Subscription, Text, + button, executor, Align, Application, Button, Clipboard, Column, Command, + Container, Element, Length, ProgressBar, Settings, Subscription, Text, }; mod download; @@ -43,7 +43,11 @@ impl Application for Example { String::from("Download progress - Iced") } - fn update(&mut self, message: Message) -> Command { + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command { match message { Message::Add => { self.last_id = self.last_id + 1; diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index 6eba6aad..18e6364b 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -1,6 +1,6 @@ use iced::{ - executor, Align, Application, Checkbox, Column, Command, Container, - Element, Length, Settings, Subscription, Text, + executor, Align, Application, Checkbox, Clipboard, Column, Command, + Container, Element, Length, Settings, Subscription, Text, }; pub fn main() -> iced::Result { @@ -32,7 +32,11 @@ impl Application for Events { String::from("Events - Iced") } - fn update(&mut self, message: Message) -> Command { + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command { match message { Message::EventOccurred(event) => { self.last.push(event); diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index e18bd6e0..64599163 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -10,8 +10,8 @@ use iced::pick_list::{self, PickList}; use iced::slider::{self, Slider}; use iced::time; use iced::{ - Align, Application, Checkbox, Column, Command, Container, Element, Length, - Row, Settings, Subscription, Text, + Align, Application, Checkbox, Clipboard, Column, Command, Container, + Element, Length, Row, Settings, Subscription, Text, }; use preset::Preset; use std::time::{Duration, Instant}; @@ -65,7 +65,11 @@ impl Application for GameOfLife { String::from("Game of Life - Iced") } - fn update(&mut self, message: Message) -> Command { + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command { match message { Message::Grid(message, version) => { if version == self.version { diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs index 824f9f53..36ee9b7e 100644 --- a/examples/integration/src/controls.rs +++ b/examples/integration/src/controls.rs @@ -1,7 +1,7 @@ use iced_wgpu::Renderer; use iced_winit::{ - slider, Align, Color, Column, Command, Element, Length, Program, Row, - Slider, Text, + slider, Align, Clipboard, Color, Column, Command, Element, Length, Program, + Row, Slider, Text, }; pub struct Controls { @@ -30,8 +30,13 @@ impl Controls { impl Program for Controls { type Renderer = Renderer; type Message = Message; + type Clipboard = Clipboard; - fn update(&mut self, message: Message) -> Command { + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command { match message { Message::BackgroundColorChanged(color) => { self.background_color = color; diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index c44d24fd..4b87a568 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -1,7 +1,8 @@ use iced::{ button, executor, keyboard, pane_grid, scrollable, Align, Application, - Button, Color, Column, Command, Container, Element, HorizontalAlignment, - Length, PaneGrid, Row, Scrollable, Settings, Subscription, Text, + Button, Clipboard, Color, Column, Command, Container, Element, + HorizontalAlignment, Length, PaneGrid, Row, Scrollable, Settings, + Subscription, Text, }; use iced_native::{event, subscription, Event}; @@ -49,7 +50,11 @@ impl Application for Example { String::from("Pane grid - Iced") } - fn update(&mut self, message: Message) -> Command { + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command { match message { Message::Split(axis, pane) => { let result = self.panes.split( diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs index f432f0fc..d2f1bb62 100644 --- a/examples/pokedex/src/main.rs +++ b/examples/pokedex/src/main.rs @@ -1,6 +1,6 @@ use iced::{ - button, futures, image, Align, Application, Button, Column, Command, - Container, Element, Length, Row, Settings, Text, + button, futures, image, Align, Application, Button, Clipboard, Column, + Command, Container, Element, Length, Row, Settings, Text, }; pub fn main() -> iced::Result { @@ -48,7 +48,11 @@ impl Application for Pokedex { format!("{} - Pokédex", subtitle) } - fn update(&mut self, message: Message) -> Command { + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command { match message { Message::PokemonFound(Ok(pokemon)) => { *self = Pokedex::Loaded { diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index c8f74978..8f844828 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -8,8 +8,8 @@ //! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system use iced::{ canvas::{self, Cursor, Path, Stroke}, - executor, time, window, Application, Canvas, Color, Command, Element, - Length, Point, Rectangle, Settings, Size, Subscription, Vector, + executor, time, window, Application, Canvas, Clipboard, Color, Command, + Element, Length, Point, Rectangle, Settings, Size, Subscription, Vector, }; use std::time::Instant; @@ -48,7 +48,11 @@ impl Application for SolarSystem { String::from("Solar system - Iced") } - fn update(&mut self, message: Message) -> Command { + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command { match message { Message::Tick(instant) => { self.state.update(instant); diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs index 983cf3e6..51972e01 100644 --- a/examples/stopwatch/src/main.rs +++ b/examples/stopwatch/src/main.rs @@ -1,6 +1,6 @@ use iced::{ - button, executor, time, Align, Application, Button, Column, Command, - Container, Element, HorizontalAlignment, Length, Row, Settings, + button, executor, time, Align, Application, Button, Clipboard, Column, + Command, Container, Element, HorizontalAlignment, Length, Row, Settings, Subscription, Text, }; use std::time::{Duration, Instant}; @@ -49,7 +49,11 @@ impl Application for Stopwatch { String::from("Stopwatch - Iced") } - fn update(&mut self, message: Message) -> Command { + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command { match message { Message::Toggle => match self.state { State::Idle => { diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index b47df5df..7186b950 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -1,7 +1,7 @@ use iced::{ button, scrollable, text_input, Align, Application, Button, Checkbox, - Column, Command, Container, Element, Font, HorizontalAlignment, Length, - Row, Scrollable, Settings, Text, TextInput, + Clipboard, Column, Command, Container, Element, Font, HorizontalAlignment, + Length, Row, Scrollable, Settings, Text, TextInput, }; use serde::{Deserialize, Serialize}; @@ -58,7 +58,11 @@ impl Application for Todos { format!("Todos{} - Iced", if dirty { "*" } else { "" }) } - fn update(&mut self, message: Message) -> Command { + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command { match self { Todos::Loading => { match message { diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 19eee219..3a08104e 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -190,6 +190,7 @@ async fn run_instance( &mut application, &mut runtime, &mut debug, + &mut clipboard, &mut messages, ); diff --git a/glutin/src/lib.rs b/glutin/src/lib.rs index f2c0102a..107d9556 100644 --- a/glutin/src/lib.rs +++ b/glutin/src/lib.rs @@ -17,7 +17,7 @@ pub use iced_native::*; pub mod application; pub use iced_winit::settings; -pub use iced_winit::{Error, Mode}; +pub use iced_winit::{Clipboard, Error, Mode}; #[doc(no_inline)] pub use application::Application; 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 diff --git a/src/application.rs b/src/application.rs index 3b690a7c..162fde84 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,5 +1,7 @@ use crate::window; -use crate::{Color, Command, Element, Executor, Settings, Subscription}; +use crate::{ + Clipboard, Color, Command, Element, Executor, Settings, Subscription, +}; /// An interactive cross-platform application. /// @@ -57,7 +59,7 @@ use crate::{Color, Command, Element, Executor, Settings, Subscription}; /// says "Hello, world!": /// /// ```no_run -/// use iced::{executor, Application, Command, Element, Settings, Text}; +/// use iced::{executor, Application, Clipboard, Command, Element, Settings, Text}; /// /// pub fn main() -> iced::Result { /// Hello::run(Settings::default()) @@ -78,7 +80,7 @@ use crate::{Color, Command, Element, Executor, Settings, Subscription}; /// String::from("A cool application") /// } /// -/// fn update(&mut self, _message: Self::Message) -> Command { +/// fn update(&mut self, _message: Self::Message, _clipboard: &mut Clipboard) -> Command { /// Command::none() /// } /// @@ -127,7 +129,11 @@ pub trait Application: Sized { /// this method. /// /// Any [`Command`] returned will be executed immediately in the background. - fn update(&mut self, message: Self::Message) -> Command; + fn update( + &mut self, + message: Self::Message, + clipboard: &mut Clipboard, + ) -> Command; /// Returns the event [`Subscription`] for the current state of the /// application. @@ -228,9 +234,14 @@ where { type Renderer = crate::renderer::Renderer; type Message = A::Message; - - fn update(&mut self, message: Self::Message) -> Command { - self.0.update(message) + type Clipboard = iced_winit::Clipboard; + + fn update( + &mut self, + message: Self::Message, + clipboard: &mut iced_winit::Clipboard, + ) -> Command { + self.0.update(message, clipboard) } fn view(&mut self) -> Element<'_, Self::Message> { diff --git a/src/lib.rs b/src/lib.rs index dedcac7a..812a50c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -245,6 +245,7 @@ pub use sandbox::Sandbox; pub use settings::Settings; pub use runtime::{ - futures, Align, Background, Color, Command, Font, HorizontalAlignment, - Length, Point, Rectangle, Size, Subscription, Vector, VerticalAlignment, + futures, Align, Background, Clipboard, Color, Command, Font, + HorizontalAlignment, Length, Point, Rectangle, Size, Subscription, Vector, + VerticalAlignment, }; diff --git a/src/sandbox.rs b/src/sandbox.rs index b53fa1f2..9dd17b7e 100644 --- a/src/sandbox.rs +++ b/src/sandbox.rs @@ -1,5 +1,6 @@ use crate::{ - Application, Color, Command, Element, Error, Settings, Subscription, + Application, Clipboard, Color, Command, Element, Error, Settings, + Subscription, }; /// A sandboxed [`Application`]. @@ -161,7 +162,11 @@ where T::title(self) } - fn update(&mut self, message: T::Message) -> Command { + fn update( + &mut self, + message: T::Message, + _clipboard: &mut Clipboard, + ) -> Command { T::update(self, message); Command::none() diff --git a/winit/src/application.rs b/winit/src/application.rs index 2cb2c016..d7d7660e 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -29,7 +29,7 @@ use std::mem::ManuallyDrop; /// /// When using an [`Application`] with the `debug` feature enabled, a debug view /// can be toggled by pressing `F12`. -pub trait Application: Program { +pub trait Application: Program { /// The data needed to initialize your [`Application`]. type Flags; @@ -257,6 +257,7 @@ async fn run_instance( &mut application, &mut runtime, &mut debug, + &mut clipboard, &mut messages, ); @@ -409,13 +410,14 @@ pub fn update( application: &mut A, runtime: &mut Runtime, A::Message>, debug: &mut Debug, + clipboard: &mut A::Clipboard, messages: &mut Vec, ) { for message in messages.drain(..) { debug.log_message(&message); debug.update_started(); - let command = runtime.enter(|| application.update(message)); + let command = runtime.enter(|| application.update(message, clipboard)); debug.update_finished(); runtime.spawn(command); -- cgit From a365998264420b1dac26a0de8e50ad4a33900885 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 11 Mar 2021 03:40:15 +0100 Subject: Expose `read` and `write` methods in `iced_winit::Clipboard` directly --- winit/src/clipboard.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index 15dc989f..ca25c065 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -20,17 +20,17 @@ impl Clipboard { Clipboard { state } } -} -impl iced_native::Clipboard for Clipboard { - fn read(&self) -> Option { + /// Reads the current content of the [`Clipboard`] as text. + pub fn read(&self) -> Option { match &self.state { State::Connected(clipboard) => clipboard.read().ok(), State::Unavailable => None, } } - fn write(&mut self, contents: String) { + /// Writes the given text contents to the [`Clipboard`]. + pub fn write(&mut self, contents: String) { match &mut self.state { State::Connected(clipboard) => match clipboard.write(contents) { Ok(()) => {} @@ -42,3 +42,13 @@ impl iced_native::Clipboard for Clipboard { } } } + +impl iced_native::Clipboard for Clipboard { + fn read(&self) -> Option { + self.read() + } + + fn write(&mut self, contents: String) { + self.write(contents) + } +} -- cgit From 7da3fb1b2225732c87aebb13a067fbdb30b0cf2d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 11 Mar 2021 03:49:45 +0100 Subject: Implement stub `Clipboard` in `iced_web` We need to figure out browser permissions and use of unstable `web-sys` APIs --- src/application.rs | 8 ++++++-- web/src/clipboard.rs | 21 +++++++++++++++++++++ web/src/lib.rs | 13 +++++++++++-- 3 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 web/src/clipboard.rs diff --git a/src/application.rs b/src/application.rs index 162fde84..4c6d5b7f 100644 --- a/src/application.rs +++ b/src/application.rs @@ -305,8 +305,12 @@ where self.0.title() } - fn update(&mut self, message: Self::Message) -> Command { - self.0.update(message) + fn update( + &mut self, + message: Self::Message, + clipboard: &mut Clipboard, + ) -> Command { + self.0.update(message, clipboard) } fn subscription(&self) -> Subscription { diff --git a/web/src/clipboard.rs b/web/src/clipboard.rs new file mode 100644 index 00000000..167a1e53 --- /dev/null +++ b/web/src/clipboard.rs @@ -0,0 +1,21 @@ +/// A buffer for short-term storage and transfer within and between +/// applications. +#[derive(Debug, Clone, Copy)] +pub struct Clipboard; + +impl Clipboard { + /// Creates a new [`Clipboard`]. + pub fn new() -> Self { + Self + } + + /// Reads the current content of the [`Clipboard`] as text. + pub fn read(&self) -> Option { + unimplemented! {} + } + + /// Writes the given text contents to the [`Clipboard`]. + pub fn write(&mut self, _contents: String) { + unimplemented! {} + } +} diff --git a/web/src/lib.rs b/web/src/lib.rs index 58f6591d..4c65dfa3 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -59,6 +59,7 @@ use dodrio::bumpalo; use std::{cell::RefCell, rc::Rc}; mod bus; +mod clipboard; mod element; mod hasher; @@ -67,6 +68,7 @@ pub mod subscription; pub mod widget; pub use bus::Bus; +pub use clipboard::Clipboard; pub use css::Css; pub use dodrio; pub use element::Element; @@ -126,7 +128,11 @@ pub trait Application { /// this method. /// /// Any [`Command`] returned will be executed immediately in the background. - fn update(&mut self, message: Self::Message) -> Command; + fn update( + &mut self, + message: Self::Message, + clipboard: &mut Clipboard, + ) -> Command; /// Returns the widgets to display in the [`Application`]. /// @@ -156,6 +162,8 @@ pub trait Application { let document = window.document().unwrap(); let body = document.body().unwrap(); + let mut clipboard = Clipboard::new(); + let (sender, receiver) = iced_futures::futures::channel::mpsc::unbounded(); @@ -182,7 +190,8 @@ pub trait Application { let event_loop = receiver.for_each(move |message| { let (command, subscription) = runtime.enter(|| { - let command = application.borrow_mut().update(message); + let command = + application.borrow_mut().update(message, &mut clipboard); let subscription = application.borrow().subscription(); (command, subscription) -- 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`. --- examples/todos/src/main.rs | 4 +++- native/src/widget/text_input.rs | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 7186b950..ee0022d5 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -265,8 +265,10 @@ impl Task { self.completed = completed; } TaskMessage::Edit => { + let mut text_input = text_input::State::focused(); + text_input.select_all(); self.state = TaskState::Editing { - text_input: text_input::State::focused(), + text_input, delete_button: button::State::new(), }; } 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(+) 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 d66a34b27272ed9d6c143483b0a700468b00e311 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 24 Mar 2021 04:59:13 +0100 Subject: Convert `ScaleFactorChanged` into `Resized` events in `iced_winit` ... instead of just dropping them when calling `to_static`. --- winit/src/application.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/winit/src/application.rs b/winit/src/application.rs index d7d7660e..5aefb378 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -164,7 +164,22 @@ where return; } - if let Some(event) = event.to_static() { + let event = match event { + winit::event::Event::WindowEvent { + event: + winit::event::WindowEvent::ScaleFactorChanged { + new_inner_size, + .. + }, + window_id, + } => Some(winit::event::Event::WindowEvent { + event: winit::event::WindowEvent::Resized(*new_inner_size), + window_id, + }), + _ => event.to_static(), + }; + + if let Some(event) = event { sender.start_send(event).expect("Send event"); let poll = instance.as_mut().poll(&mut context); -- cgit From 2b520ca0984486d3ad930873837df8c819bab30c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 24 Mar 2021 05:29:19 +0100 Subject: Convert `ScaleFactorChanged` into `Resized` events in `iced_glutin` ... instead of just dropping them when calling `to_static`. --- glutin/src/application.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 3a08104e..163bc9f9 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -107,7 +107,22 @@ where return; } - if let Some(event) = event.to_static() { + let event = match event { + glutin::event::Event::WindowEvent { + event: + glutin::event::WindowEvent::ScaleFactorChanged { + new_inner_size, + .. + }, + window_id, + } => Some(glutin::event::Event::WindowEvent { + event: glutin::event::WindowEvent::Resized(*new_inner_size), + window_id, + }), + _ => event.to_static(), + }; + + if let Some(event) = event { sender.start_send(event).expect("Send event"); let poll = instance.as_mut().poll(&mut context); -- cgit From ab8dcf91bdcad6c8d0a39802f2823e65aafa8f0a Mon Sep 17 00:00:00 2001 From: Downtime Date: Sun, 21 Mar 2021 11:42:25 +0800 Subject: Support choosing `wgpu` backend using env var --- wgpu/src/window/compositor.rs | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 36d6ab1a..3d785f71 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -15,6 +15,35 @@ pub struct Compositor { local_pool: futures::executor::LocalPool, } +#[derive(Clone)] +pub enum WgpuBackend { + Auto, + Vulkan, + Metal, + Dx12, + Dx11, + Gl, + BrowserWgpu, +} + +impl WgpuBackend { + fn from_env() -> Self { + if let Ok(backend) = std::env::var("WGPU_BACKEND") { + match backend.to_lowercase().as_str() { + "vulkan" => WgpuBackend::Vulkan, + "metal" => WgpuBackend::Metal, + "dx12" => WgpuBackend::Dx12, + "dx11" => WgpuBackend::Dx11, + "gl" => WgpuBackend::Gl, + "webgpu" => WgpuBackend::BrowserWgpu, + other => panic!("Unknown backend: {}", other), + } + } else { + WgpuBackend::Auto + } + } +} + impl Compositor { const CHUNK_SIZE: u64 = 10 * 1024; @@ -22,7 +51,17 @@ impl Compositor { /// /// Returns `None` if no compatible graphics adapter could be found. pub async fn request(settings: Settings) -> Option { - let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY); + let backend = match WgpuBackend::from_env() { + WgpuBackend::Auto => wgpu::BackendBit::PRIMARY, + WgpuBackend::Vulkan => wgpu::BackendBit::VULKAN, + WgpuBackend::Metal => wgpu::BackendBit::METAL, + WgpuBackend::Dx12 => wgpu::BackendBit::DX12, + WgpuBackend::Dx11 => wgpu::BackendBit::DX11, + WgpuBackend::Gl => wgpu::BackendBit::GL, + WgpuBackend::BrowserWgpu => wgpu::BackendBit::BROWSER_WEBGPU, + }; + + let instance = wgpu::Instance::new(backend); let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { -- cgit From 883c7e71ae699a29c23b8f95b7335d015e5a985d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 25 Mar 2021 11:27:31 +0100 Subject: Introduce `internal_backend` to `iced_wgpu::Settings` --- glow/src/settings.rs | 9 +++++++++ src/application.rs | 2 +- wgpu/src/settings.rs | 41 +++++++++++++++++++++++++++++++++++++++++ wgpu/src/window/compositor.rs | 41 +---------------------------------------- 4 files changed, 52 insertions(+), 41 deletions(-) diff --git a/glow/src/settings.rs b/glow/src/settings.rs index 524d91a9..8477eb57 100644 --- a/glow/src/settings.rs +++ b/glow/src/settings.rs @@ -29,3 +29,12 @@ impl Default for Settings { } } } + +impl Settings { + /// Creates new [`Settings`] using environment configuration. + /// + /// Currently, this is equivalent to calling [`Settings::default`]. + pub fn from_env() -> Self { + Self::default() + } +} diff --git a/src/application.rs b/src/application.rs index 4c6d5b7f..83ce900a 100644 --- a/src/application.rs +++ b/src/application.rs @@ -206,7 +206,7 @@ pub trait Application: Sized { } else { None }, - ..crate::renderer::Settings::default() + ..crate::renderer::Settings::from_env() }; Ok(crate::runtime::application::run::< diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index 26763e22..abc404dc 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -16,6 +16,9 @@ pub struct Settings { /// [`Backend`]: crate::Backend pub present_mode: wgpu::PresentMode, + /// The internal graphics backend to use. + pub internal_backend: wgpu::BackendBit, + /// The bytes of the font that will be used by default. /// /// If `None` is provided, a default system font will be chosen. @@ -30,14 +33,52 @@ pub struct Settings { pub antialiasing: Option, } +impl Settings { + /// Creates new [`Settings`] using environment configuration. + /// + /// Specifically: + /// + /// - The `internal_backend` can be configured using the `WGPU_BACKEND` + /// environment variable. If the variable is not set, the primary backend + /// will be used. The following values are allowed: + /// - `vulkan` + /// - `metal` + /// - `dx12` + /// - `dx11` + /// - `gl` + /// - `webgpu` + pub fn from_env() -> Self { + Settings { + internal_backend: backend_from_env() + .unwrap_or(wgpu::BackendBit::PRIMARY), + ..Self::default() + } + } +} + impl Default for Settings { fn default() -> Settings { Settings { format: wgpu::TextureFormat::Bgra8UnormSrgb, present_mode: wgpu::PresentMode::Mailbox, + internal_backend: wgpu::BackendBit::PRIMARY, default_font: None, default_text_size: 20, antialiasing: None, } } } + +fn backend_from_env() -> Option { + std::env::var("WGPU_BACKEND").ok().map(|backend| { + match backend.to_lowercase().as_str() { + "vulkan" => wgpu::BackendBit::VULKAN, + "metal" => wgpu::BackendBit::METAL, + "dx12" => wgpu::BackendBit::DX12, + "dx11" => wgpu::BackendBit::DX11, + "gl" => wgpu::BackendBit::GL, + "webgpu" => wgpu::BackendBit::BROWSER_WEBGPU, + other => panic!("Unknown backend: {}", other), + } + }) +} diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 3d785f71..fdd648e8 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -15,35 +15,6 @@ pub struct Compositor { local_pool: futures::executor::LocalPool, } -#[derive(Clone)] -pub enum WgpuBackend { - Auto, - Vulkan, - Metal, - Dx12, - Dx11, - Gl, - BrowserWgpu, -} - -impl WgpuBackend { - fn from_env() -> Self { - if let Ok(backend) = std::env::var("WGPU_BACKEND") { - match backend.to_lowercase().as_str() { - "vulkan" => WgpuBackend::Vulkan, - "metal" => WgpuBackend::Metal, - "dx12" => WgpuBackend::Dx12, - "dx11" => WgpuBackend::Dx11, - "gl" => WgpuBackend::Gl, - "webgpu" => WgpuBackend::BrowserWgpu, - other => panic!("Unknown backend: {}", other), - } - } else { - WgpuBackend::Auto - } - } -} - impl Compositor { const CHUNK_SIZE: u64 = 10 * 1024; @@ -51,17 +22,7 @@ impl Compositor { /// /// Returns `None` if no compatible graphics adapter could be found. pub async fn request(settings: Settings) -> Option { - let backend = match WgpuBackend::from_env() { - WgpuBackend::Auto => wgpu::BackendBit::PRIMARY, - WgpuBackend::Vulkan => wgpu::BackendBit::VULKAN, - WgpuBackend::Metal => wgpu::BackendBit::METAL, - WgpuBackend::Dx12 => wgpu::BackendBit::DX12, - WgpuBackend::Dx11 => wgpu::BackendBit::DX11, - WgpuBackend::Gl => wgpu::BackendBit::GL, - WgpuBackend::BrowserWgpu => wgpu::BackendBit::BROWSER_WEBGPU, - }; - - let instance = wgpu::Instance::new(backend); + let instance = wgpu::Instance::new(settings.internal_backend); let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { -- cgit From 1207afa7d0862b0fa06b8dd9dd01acb03e49acec Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 26 Mar 2021 14:46:19 +0100 Subject: Skip redrawing if window has no surface --- winit/src/application.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/winit/src/application.rs b/winit/src/application.rs index 5aefb378..ef6c8463 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -299,11 +299,16 @@ async fn run_instance( messages.push(message); } event::Event::RedrawRequested(_) => { + let physical_size = state.physical_size(); + + if physical_size.width == 0 || physical_size.height == 0 { + continue; + } + debug.render_started(); let current_viewport_version = state.viewport_version(); if viewport_version != current_viewport_version { - let physical_size = state.physical_size(); let logical_size = state.logical_size(); debug.layout_started(); -- 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 ++++++ winit/src/conversion.rs | 3 +++ 2 files changed, 9 insertions(+) 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, diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 0e04b35d..0fa27413 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -33,6 +33,9 @@ pub fn window_event( height: logical_size.height, })) } + WindowEvent::CloseRequested => { + Some(Event::Window(window::Event::CloseRequested)) + } WindowEvent::CursorMoved { position, .. } => { let position = position.to_logical::(scale_factor); -- cgit From 67db13ff7c727254182a8c4474bd962b205e2e99 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Mar 2021 21:44:19 +0200 Subject: Add support for graceful exits in `Application` - `Settings` now contains an `exit_on_close_request` field - `Application` has a new `should_exit` method --- glutin/src/application.rs | 13 +++++++++++-- src/application.rs | 11 +++++++++++ src/settings.rs | 15 +++++++++++---- winit/src/application.rs | 23 ++++++++++++++++++++--- winit/src/settings.rs | 4 ++++ 5 files changed, 57 insertions(+), 9 deletions(-) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 163bc9f9..79fcf745 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -92,10 +92,11 @@ where application, compositor, renderer, - context, runtime, debug, receiver, + context, + settings.exit_on_close_request, )); let mut context = task::Context::from_waker(task::noop_waker_ref()); @@ -139,10 +140,11 @@ async fn run_instance( mut application: A, mut compositor: C, mut renderer: A::Renderer, - context: glutin::ContextWrapper, mut runtime: Runtime, A::Message>, mut debug: Debug, mut receiver: mpsc::UnboundedReceiver>, + context: glutin::ContextWrapper, + exit_on_close_request: bool, ) where A: Application + 'static, E: Executor + 'static, @@ -212,6 +214,8 @@ async fn run_instance( // Update window state.synchronize(&application, context.window()); + let should_exit = application.should_exit(); + user_interface = ManuallyDrop::new(application::build_user_interface( &mut application, @@ -220,6 +224,10 @@ async fn run_instance( state.logical_size(), &mut debug, )); + + if should_exit { + break; + } } debug.draw_started(); @@ -290,6 +298,7 @@ async fn run_instance( .. } => { if application::requests_exit(&window_event, state.modifiers()) + && exit_on_close_request { break; } diff --git a/src/application.rs b/src/application.rs index 83ce900a..7b7de6d4 100644 --- a/src/application.rs +++ b/src/application.rs @@ -184,6 +184,13 @@ pub trait Application: Sized { 1.0 } + /// Returns whether the [`Application`] should be terminated. + /// + /// By default, it returns `false`. + fn should_exit(&self) -> bool { + false + } + /// Runs the [`Application`]. /// /// On native platforms, this method will take control of the current thread @@ -284,6 +291,10 @@ where fn scale_factor(&self) -> f64 { self.0.scale_factor() } + + fn should_exit(&self) -> bool { + self.0.should_exit() + } } #[cfg(target_arch = "wasm32")] diff --git a/src/settings.rs b/src/settings.rs index c82a1354..2b32258d 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -25,6 +25,10 @@ pub struct Settings { /// The default value is 20. pub default_text_size: u16, + /// Whether the [`Application`] should exit when the user requests the + /// window to close (e.g. the user presses the close button). + pub exit_on_close_request: bool, + /// If set to true, the renderer will try to perform antialiasing for some /// primitives. /// @@ -46,10 +50,11 @@ impl Settings { Self { flags, - antialiasing: default_settings.antialiasing, + window: default_settings.window, default_font: default_settings.default_font, default_text_size: default_settings.default_text_size, - window: default_settings.window, + exit_on_close_request: default_settings.exit_on_close_request, + antialiasing: default_settings.antialiasing, } } } @@ -61,10 +66,11 @@ where fn default() -> Self { Self { flags: Default::default(), - antialiasing: Default::default(), + window: Default::default(), default_font: Default::default(), default_text_size: 20, - window: Default::default(), + exit_on_close_request: true, + antialiasing: false, } } } @@ -75,6 +81,7 @@ impl From> for iced_winit::Settings { iced_winit::Settings { window: settings.window.into(), flags: settings.flags, + exit_on_close_request: settings.exit_on_close_request, } } } diff --git a/winit/src/application.rs b/winit/src/application.rs index ef6c8463..106d5218 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -91,6 +91,13 @@ pub trait Application: Program { fn scale_factor(&self) -> f64 { 1.0 } + + /// Returns whether the [`Application`] should be terminated. + /// + /// By default, it returns `false`. + fn should_exit(&self) -> bool { + false + } } /// Runs an [`Application`] with an executor, compositor, and the provided @@ -149,10 +156,11 @@ where application, compositor, renderer, - window, runtime, debug, receiver, + window, + settings.exit_on_close_request, )); let mut context = task::Context::from_waker(task::noop_waker_ref()); @@ -196,10 +204,11 @@ async fn run_instance( mut application: A, mut compositor: C, mut renderer: A::Renderer, - window: winit::window::Window, mut runtime: Runtime, A::Message>, mut debug: Debug, mut receiver: mpsc::UnboundedReceiver>, + window: winit::window::Window, + exit_on_close_request: bool, ) where A: Application + 'static, E: Executor + 'static, @@ -279,6 +288,8 @@ async fn run_instance( // Update window state.synchronize(&application, &window); + let should_exit = application.should_exit(); + user_interface = ManuallyDrop::new(build_user_interface( &mut application, cache, @@ -286,6 +297,10 @@ async fn run_instance( state.logical_size(), &mut debug, )); + + if should_exit { + break; + } } debug.draw_started(); @@ -358,7 +373,9 @@ async fn run_instance( event: window_event, .. } => { - if requests_exit(&window_event, state.modifiers()) { + if requests_exit(&window_event, state.modifiers()) + && exit_on_close_request + { break; } diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 2e8715cd..9ce5cfc5 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -23,6 +23,10 @@ pub struct Settings { /// /// [`Application`]: crate::Application pub flags: Flags, + + /// Whether the [`Application`] should exit when the user requests the + /// window to close (e.g. the user presses the close button). + pub exit_on_close_request: bool, } /// The window settings of an application. -- cgit From 8f952452ce3d61203856bcebae7016372556be31 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Mar 2021 21:45:49 +0200 Subject: Showcase graceful exiting in `events` example --- examples/events/src/main.rs | 47 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index 18e6364b..446c190b 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -1,22 +1,30 @@ use iced::{ - executor, Align, Application, Checkbox, Clipboard, Column, Command, - Container, Element, Length, Settings, Subscription, Text, + button, executor, Align, Application, Button, Checkbox, Clipboard, Column, + Command, Container, Element, HorizontalAlignment, Length, Settings, + Subscription, Text, }; +use iced_native::{window, Event}; pub fn main() -> iced::Result { - Events::run(Settings::default()) + Events::run(Settings { + exit_on_close_request: false, + ..Settings::default() + }) } #[derive(Debug, Default)] struct Events { last: Vec, enabled: bool, + exit: button::State, + should_exit: bool, } #[derive(Debug, Clone)] enum Message { EventOccurred(iced_native::Event), Toggled(bool), + Exit, } impl Application for Events { @@ -38,27 +46,35 @@ impl Application for Events { _clipboard: &mut Clipboard, ) -> Command { match message { - Message::EventOccurred(event) => { + Message::EventOccurred(event) if self.enabled => { self.last.push(event); if self.last.len() > 5 { let _ = self.last.remove(0); } } + Message::EventOccurred(event) => { + if let Event::Window(window::Event::CloseRequested) = event { + self.should_exit = true; + } + } Message::Toggled(enabled) => { self.enabled = enabled; } + Message::Exit => { + self.should_exit = true; + } }; Command::none() } fn subscription(&self) -> Subscription { - if self.enabled { - iced_native::subscription::events().map(Message::EventOccurred) - } else { - Subscription::none() - } + iced_native::subscription::events().map(Message::EventOccurred) + } + + fn should_exit(&self) -> bool { + self.should_exit } fn view(&mut self) -> Element { @@ -75,11 +91,22 @@ impl Application for Events { Message::Toggled, ); + let exit = Button::new( + &mut self.exit, + Text::new("Exit") + .width(Length::Fill) + .horizontal_alignment(HorizontalAlignment::Center), + ) + .width(Length::Units(100)) + .padding(10) + .on_press(Message::Exit); + let content = Column::new() .align_items(Align::Center) .spacing(20) .push(events) - .push(toggle); + .push(toggle) + .push(exit); Container::new(content) .width(Length::Fill) -- cgit From 2b20512a3da1d9c14e13a7a1ea208b76bea5e12d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 31 Mar 2021 19:59:23 +0200 Subject: Update `CHANGELOG` --- CHANGELOG.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0cb739d..6fedd4e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,67 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- Support for the [`smol`] async runtime. [#699] +- Touch support. [#57] [#650] (thanks to @simlay and @discordance!) +- Clipboard write access for + - `TextInput` widget. [#770] + - `Application::update`. [#773] +- `image::Viewer` widget. It allows panning and scaling of an image. [#319] (thanks to @tarkah!) +- `Tooltip` widget. It annotates content with some text on mouse hover. [#465] (thanks to @yusdacra!) +- Support for the [`smol`] async runtime. [#699] (thanks to @JayceFayne!) +- Support for graceful exiting when using the `Application` trait. [#804] +- Image format features in [`iced_wgpu`] to reduce code bloat. [#392] (thanks to @unrelentingtech!) +- `Focused` and `Unfocused` variant to `window::Event`. [#701] (thanks to @cossonleo!) +- `WGPU_BACKEND` environment variable to configure the internal graphics backend of `iced_wgpu`. [#789] (thanks to @Cupnfish!) +### Changed +- The `TitleBar` of a `PaneGrid` now supports generic elements. [#657] (thanks to @clarkmoody!) +- The `Error` type now implements `Send` and `Sync`. [#719] (thanks to @taiki-e!) +- The `Style` types in `iced_style` now implement `Clone` and `Copy`. [#720] (thanks to @taiki-e!) +- The following dependencies have been updated: + - [`font-kit`] → `0.10` [#669] + - [`glutin`] → `0.26` [#658] + - [`resvg`] → `0.12` [#669] + - [`tokio`] → `1.0` [#672] (thanks to @yusdacra!) + - [`winit`] → `0.24` [#658] + - [`wgpu`] → `0.7` [#725] (thanks to @PolyMeilex) +- The following examples were improved: + - `download_progress` now showcases multiple file downloads at once. [#283] (thanks to @Folyd!) + - `solar_system` uses the new `rand` API. [#760] (thanks to @TriedAngle!) + +### Fixed +- Button events not being propagated to contents. [#668] +- Incorrect overlay implementation for the `Button` widget. [#764] +- `Viewport::physical_width` returning the wrong value. [#700] +- Outdated documentation for the `Sandbox` trait. [#710] + +[#57]: https://github.com/hecrj/iced/pull/57 +[#283]: https://github.com/hecrj/iced/pull/283 +[#319]: https://github.com/hecrj/iced/pull/319 +[#392]: https://github.com/hecrj/iced/pull/392 +[#465]: https://github.com/hecrj/iced/pull/465 +[#650]: https://github.com/hecrj/iced/pull/650 +[#657]: https://github.com/hecrj/iced/pull/657 +[#658]: https://github.com/hecrj/iced/pull/658 +[#668]: https://github.com/hecrj/iced/pull/668 +[#669]: https://github.com/hecrj/iced/pull/669 +[#672]: https://github.com/hecrj/iced/pull/672 [#699]: https://github.com/hecrj/iced/pull/699 +[#700]: https://github.com/hecrj/iced/pull/700 +[#701]: https://github.com/hecrj/iced/pull/701 +[#710]: https://github.com/hecrj/iced/pull/710 +[#719]: https://github.com/hecrj/iced/pull/719 +[#720]: https://github.com/hecrj/iced/pull/720 +[#725]: https://github.com/hecrj/iced/pull/725 +[#760]: https://github.com/hecrj/iced/pull/760 +[#764]: https://github.com/hecrj/iced/pull/764 +[#770]: https://github.com/hecrj/iced/pull/770 +[#773]: https://github.com/hecrj/iced/pull/773 +[#789]: https://github.com/hecrj/iced/pull/789 +[#804]: https://github.com/hecrj/iced/pull/804 [`smol`]: https://github.com/smol-rs/smol +[`winit`]: https://github.com/rust-windowing/winit +[`glutin`]: https://github.com/rust-windowing/glutin +[`font-kit`]: https://github.com/servo/font-kit ## [0.2.0] - 2020-11-26 ### Added -- 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: --- CHANGELOG.md | 5 ++++- Cargo.toml | 16 ++++++++-------- README.md | 2 +- core/Cargo.toml | 2 +- core/README.md | 2 +- futures/Cargo.toml | 2 +- glow/Cargo.toml | 6 +++--- glutin/Cargo.toml | 8 ++++---- glutin/README.md | 2 +- graphics/Cargo.toml | 6 +++--- native/Cargo.toml | 6 +++--- native/README.md | 2 +- style/Cargo.toml | 7 ++++--- web/Cargo.toml | 8 ++++---- web/README.md | 2 +- wgpu/Cargo.toml | 6 +++--- wgpu/README.md | 2 +- winit/Cargo.toml | 8 ++++---- winit/README.md | 2 +- 19 files changed, 49 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fedd4e6..ab41f509 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [0.3.0] - 2021-03-31 ### Added - Touch support. [#57] [#650] (thanks to @simlay and @discordance!) - Clipboard write access for @@ -217,7 +219,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - First release! :tada: -[Unreleased]: https://github.com/hecrj/iced/compare/0.2.0...HEAD +[Unreleased]: https://github.com/hecrj/iced/compare/0.3.0...HEAD +[0.3.0]: https://github.com/hecrj/iced/compare/0.2.0...0.3.0 [0.2.0]: https://github.com/hecrj/iced/compare/0.1.1...0.2.0 [0.1.1]: https://github.com/hecrj/iced/compare/0.1.0...0.1.1 [0.1.0]: https://github.com/hecrj/iced/compare/0.1.0-beta...0.1.0 diff --git a/Cargo.toml b/Cargo.toml index f3a9676f..2b0113b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced" -version = "0.2.0" +version = "0.3.0" authors = ["Héctor Ramón Jiménez "] edition = "2018" description = "A cross-platform GUI library inspired by Elm" @@ -87,18 +87,18 @@ members = [ ] [dependencies] -iced_core = { version = "0.3", path = "core" } -iced_futures = { version = "0.2", path = "futures" } +iced_core = { version = "0.4", path = "core" } +iced_futures = { version = "0.3", path = "futures" } thiserror = "1.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -iced_winit = { version = "0.2", path = "winit" } -iced_glutin = { version = "0.1", path = "glutin", optional = true } -iced_wgpu = { version = "0.3", path = "wgpu", optional = true } -iced_glow = { version = "0.1", path = "glow", optional = true} +iced_winit = { version = "0.3", path = "winit" } +iced_glutin = { version = "0.2", path = "glutin", optional = true } +iced_wgpu = { version = "0.4", path = "wgpu", optional = true } +iced_glow = { version = "0.2", path = "glow", optional = true} [target.'cfg(target_arch = "wasm32")'.dependencies] -iced_web = { version = "0.3", path = "web" } +iced_web = { version = "0.4", path = "web" } [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] diff --git a/README.md b/README.md index da754686..4ad2fb70 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ __Iced is currently experimental software.__ [Take a look at the roadmap], Add `iced` as a dependency in your `Cargo.toml`: ```toml -iced = "0.2" +iced = "0.3" ``` __Iced moves fast and the `master` branch can contain breaking changes!__ If diff --git a/core/Cargo.toml b/core/Cargo.toml index a859c868..88f257b7 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_core" -version = "0.3.0" +version = "0.4.0" authors = ["Héctor Ramón Jiménez "] edition = "2018" description = "The essential concepts of Iced" diff --git a/core/README.md b/core/README.md index 3ec053ac..86d631d2 100644 --- a/core/README.md +++ b/core/README.md @@ -18,7 +18,7 @@ This crate is meant to be a starting point for an Iced runtime. Add `iced_core` as a dependency in your `Cargo.toml`: ```toml -iced_core = "0.3" +iced_core = "0.4" ``` __Iced moves fast and the `master` branch can contain breaking changes!__ If diff --git a/futures/Cargo.toml b/futures/Cargo.toml index c266f705..3cea6e1a 100644 --- a/futures/Cargo.toml +++ b/futures/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_futures" -version = "0.2.0" +version = "0.3.0" authors = ["Héctor Ramón Jiménez "] edition = "2018" description = "Commands, subscriptions, and runtimes for Iced" diff --git a/glow/Cargo.toml b/glow/Cargo.toml index 3f85e52d..e40b8ba8 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_glow" -version = "0.1.0" +version = "0.2.0" authors = ["Héctor Ramón Jiménez "] edition = "2018" description = "A glow renderer for iced" @@ -24,11 +24,11 @@ bytemuck = "1.4" log = "0.4" [dependencies.iced_native] -version = "0.3" +version = "0.4" path = "../native" [dependencies.iced_graphics] -version = "0.1" +version = "0.2" path = "../graphics" features = ["font-fallback", "font-icons", "opengl"] diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml index 505ee7e5..5d2c5be7 100644 --- a/glutin/Cargo.toml +++ b/glutin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_glutin" -version = "0.1.0" +version = "0.2.0" authors = ["Héctor Ramón Jiménez "] edition = "2018" description = "A glutin runtime for Iced" @@ -17,14 +17,14 @@ debug = ["iced_winit/debug"] glutin = "0.26" [dependencies.iced_native] -version = "0.3" +version = "0.4" path = "../native" [dependencies.iced_winit] -version = "0.2" +version = "0.3" path = "../winit" [dependencies.iced_graphics] -version = "0.1" +version = "0.2" path = "../graphics" features = ["opengl"] diff --git a/glutin/README.md b/glutin/README.md index addb9228..fcae157e 100644 --- a/glutin/README.md +++ b/glutin/README.md @@ -20,7 +20,7 @@ It exposes a renderer-agnostic `Application` trait that can be implemented and t Add `iced_glutin` as a dependency in your `Cargo.toml`: ```toml -iced_glutin = "0.1" +iced_glutin = "0.2" ``` __Iced moves fast and the `master` branch can contain breaking changes!__ If diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index ab41ac44..ea9471c6 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_graphics" -version = "0.1.0" +version = "0.2.0" authors = ["Héctor Ramón Jiménez "] edition = "2018" description = "A bunch of backend-agnostic types that can be leveraged to build a renderer for Iced" @@ -28,11 +28,11 @@ version = "1.4" features = ["derive"] [dependencies.iced_native] -version = "0.3" +version = "0.4" path = "../native" [dependencies.iced_style] -version = "0.2" +version = "0.3" path = "../style" [dependencies.lyon] 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 diff --git a/style/Cargo.toml b/style/Cargo.toml index ac16f8ee..a3086477 100644 --- a/style/Cargo.toml +++ b/style/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_style" -version = "0.2.0" +version = "0.3.0" authors = ["Héctor Ramón Jiménez "] edition = "2018" description = "The default set of styles of Iced" @@ -10,5 +10,6 @@ documentation = "https://docs.rs/iced_style" keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] -[dependencies] -iced_core = { version = "0.3", path = "../core" } +[dependencies.iced_core] +version = "0.4" +path = "../core" diff --git a/web/Cargo.toml b/web/Cargo.toml index e063a021..06573b95 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_web" -version = "0.3.0" +version = "0.4.0" authors = ["Héctor Ramón Jiménez "] edition = "2018" description = "A web backend for Iced" @@ -22,15 +22,15 @@ url = "2.0" 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" [dependencies.iced_style] -version = "0.2" +version = "0.3" path = "../style" [dependencies.web-sys] diff --git a/web/README.md b/web/README.md index 0e770589..58ad8235 100644 --- a/web/README.md +++ b/web/README.md @@ -16,7 +16,7 @@ The crate is currently a __very experimental__, simple abstraction layer over [` Add `iced_web` as a dependency in your `Cargo.toml`: ```toml -iced_web = "0.3" +iced_web = "0.4" ``` __Iced moves fast and the `master` branch can contain breaking changes!__ If diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index aa39f068..fe41d9c0 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_wgpu" -version = "0.3.0" +version = "0.4.0" authors = ["Héctor Ramón Jiménez "] edition = "2018" description = "A wgpu renderer for Iced" @@ -39,11 +39,11 @@ version = "1.4" features = ["derive"] [dependencies.iced_native] -version = "0.3" +version = "0.4" path = "../native" [dependencies.iced_graphics] -version = "0.1" +version = "0.2" path = "../graphics" features = ["font-fallback", "font-icons"] diff --git a/wgpu/README.md b/wgpu/README.md index e8cb0a43..a9d95ea7 100644 --- a/wgpu/README.md +++ b/wgpu/README.md @@ -29,7 +29,7 @@ Currently, `iced_wgpu` supports the following primitives: Add `iced_wgpu` as a dependency in your `Cargo.toml`: ```toml -iced_wgpu = "0.3" +iced_wgpu = "0.4" ``` __Iced moves fast and the `master` branch can contain breaking changes!__ If diff --git a/winit/Cargo.toml b/winit/Cargo.toml index ecee0e2e..7f117fa1 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_winit" -version = "0.2.0" +version = "0.3.0" authors = ["Héctor Ramón Jiménez "] edition = "2018" description = "A winit runtime for Iced" @@ -20,15 +20,15 @@ log = "0.4" thiserror = "1.0" [dependencies.iced_native] -version = "0.3" +version = "0.4" path = "../native" [dependencies.iced_graphics] -version = "0.1" +version = "0.2" path = "../graphics" [dependencies.iced_futures] -version = "0.2" +version = "0.3" path = "../futures" [target.'cfg(target_os = "windows")'.dependencies.winapi] diff --git a/winit/README.md b/winit/README.md index 721baa14..58c782c6 100644 --- a/winit/README.md +++ b/winit/README.md @@ -20,7 +20,7 @@ It exposes a renderer-agnostic `Application` trait that can be implemented and t Add `iced_winit` as a dependency in your `Cargo.toml`: ```toml -iced_winit = "0.2" +iced_winit = "0.3" ``` __Iced moves fast and the `master` branch can contain breaking changes!__ If -- cgit From 90fee3a9237e71967eb0a63d8cef4f8ce1e5d2b5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 31 Mar 2021 20:46:59 +0200 Subject: Fix `iced_wgpu` link in `CHANGELOG` --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab41f509..3feaeba3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -207,7 +207,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [`wasm-bindgen-futures`]: https://github.com/rustwasm/wasm-bindgen/tree/master/crates/futures [`resvg`]: https://github.com/RazrFalcon/resvg [`raqote`]: https://github.com/jrmuizel/raqote -[`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.1/wgpu +[`iced_wgpu`]: https://github.com/hecrj/iced/tree/master/wgpu ## [0.1.0-beta] - 2019-11-25 -- cgit From ca4257ff5c252e84e4bfe8a56b43801f26b56ef6 Mon Sep 17 00:00:00 2001 From: Downtime Date: Thu, 1 Apr 2021 21:58:25 +0800 Subject: Update frame.rs --- graphics/src/widget/canvas/frame.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphics/src/widget/canvas/frame.rs b/graphics/src/widget/canvas/frame.rs index b86f9e04..5af9d11f 100644 --- a/graphics/src/widget/canvas/frame.rs +++ b/graphics/src/widget/canvas/frame.rs @@ -54,7 +54,7 @@ impl Frame { self.size.width } - /// Returns the width of the [`Frame`]. + /// Returns the height of the [`Frame`]. #[inline] pub fn height(&self) -> f32 { self.size.height -- cgit From 8473d8e984f1c74e2f87d655ed048e2c07128f8b Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Tue, 6 Apr 2021 02:46:44 +0900 Subject: Enable qr_code feature on docs.rs --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2b0113b2..6d894eba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,4 +102,4 @@ iced_web = { version = "0.4", path = "web" } [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] -features = ["image", "svg", "canvas"] +features = ["image", "svg", "canvas", "qr_code"] -- cgit From cdab8f90fb525c509e0a15bb1d0b3d7213e176f3 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Thu, 8 Apr 2021 12:58:08 -0700 Subject: add window visibility --- src/application.rs | 11 +++++++++++ winit/src/application.rs | 8 ++++++++ winit/src/application/state.rs | 12 ++++++++++++ winit/src/settings.rs | 4 +++- 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/application.rs b/src/application.rs index 7b7de6d4..317f9801 100644 --- a/src/application.rs +++ b/src/application.rs @@ -191,6 +191,13 @@ pub trait Application: Sized { false } + /// Returns whether the [`Application`] should be visible or not + /// + /// By default, it returns `true`. + fn visible(&self) -> bool { + true + } + /// Runs the [`Application`]. /// /// On native platforms, this method will take control of the current thread @@ -295,6 +302,10 @@ where fn should_exit(&self) -> bool { self.0.should_exit() } + + fn visible(&self) -> bool { + self.0.visible() + } } #[cfg(target_arch = "wasm32")] diff --git a/winit/src/application.rs b/winit/src/application.rs index 106d5218..e73f3df1 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -98,6 +98,13 @@ pub trait Application: Program { fn should_exit(&self) -> bool { false } + + /// Returns whether the [`Application`] should be visible or not + /// + /// By default, it returns `true`. + fn visible(&self) -> bool { + true + } } /// Runs an [`Application`] with an executor, compositor, and the provided @@ -145,6 +152,7 @@ where .into_builder( &application.title(), application.mode(), + application.visible(), event_loop.primary_monitor(), ) .build(&event_loop) diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs index 46297370..d157211a 100644 --- a/winit/src/application/state.rs +++ b/winit/src/application/state.rs @@ -12,6 +12,7 @@ pub struct State { mode: Mode, background_color: Color, scale_factor: f64, + visible: bool, viewport: Viewport, viewport_version: usize, cursor_position: winit::dpi::PhysicalPosition, @@ -26,6 +27,7 @@ impl State { let mode = application.mode(); let background_color = application.background_color(); let scale_factor = application.scale_factor(); + let visible = application.visible(); let viewport = { let physical_size = window.inner_size(); @@ -41,6 +43,7 @@ impl State { mode, background_color, scale_factor, + visible, viewport, viewport_version: 0, // TODO: Encode cursor availability in the type-system @@ -201,5 +204,14 @@ impl State { self.scale_factor = new_scale_factor; } + + // Update window visibility + let new_visible = application.visible(); + + if self.visible != new_visible { + window.set_visible(new_visible); + + self.visible = new_visible; + } } } diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 9ce5cfc5..56be1e07 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -66,6 +66,7 @@ impl Window { self, title: &str, mode: Mode, + visible: bool, primary_monitor: Option, ) -> WindowBuilder { let mut window_builder = WindowBuilder::new(); @@ -80,7 +81,8 @@ impl Window { .with_transparent(self.transparent) .with_window_icon(self.icon) .with_always_on_top(self.always_on_top) - .with_fullscreen(conversion::fullscreen(primary_monitor, mode)); + .with_fullscreen(conversion::fullscreen(primary_monitor, mode)) + .with_visible(visible); if let Some((width, height)) = self.min_size { window_builder = window_builder -- cgit From 6f6f1d82e822983941f3b3ba1c6d93df3e98b8c3 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Thu, 8 Apr 2021 13:09:58 -0700 Subject: add to glutin --- glutin/src/application.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 79fcf745..5d72ee41 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -55,6 +55,7 @@ where let builder = settings.window.into_builder( &application.title(), application.mode(), + application.visible(), event_loop.primary_monitor(), ); -- cgit From 84c0c9bc7ab858793183560739c8fd6087e22f6e Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Fri, 9 Apr 2021 09:00:29 -0700 Subject: use Mode::Hidden instead --- glutin/src/application.rs | 1 - src/application.rs | 12 +----------- src/window/mode.rs | 3 +++ winit/src/application.rs | 8 -------- winit/src/application/state.rs | 14 ++------------ winit/src/conversion.rs | 10 +++++++++- winit/src/mode.rs | 3 +++ winit/src/settings.rs | 3 +-- 8 files changed, 19 insertions(+), 35 deletions(-) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 5d72ee41..79fcf745 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -55,7 +55,6 @@ where let builder = settings.window.into_builder( &application.title(), application.mode(), - application.visible(), event_loop.primary_monitor(), ); diff --git a/src/application.rs b/src/application.rs index 317f9801..ee532e0b 100644 --- a/src/application.rs +++ b/src/application.rs @@ -191,13 +191,6 @@ pub trait Application: Sized { false } - /// Returns whether the [`Application`] should be visible or not - /// - /// By default, it returns `true`. - fn visible(&self) -> bool { - true - } - /// Runs the [`Application`]. /// /// On native platforms, this method will take control of the current thread @@ -284,6 +277,7 @@ where match self.0.mode() { window::Mode::Windowed => iced_winit::Mode::Windowed, window::Mode::Fullscreen => iced_winit::Mode::Fullscreen, + window::Mode::Hidden => iced_winit::Mode::Hidden, } } @@ -302,10 +296,6 @@ where fn should_exit(&self) -> bool { self.0.should_exit() } - - fn visible(&self) -> bool { - self.0.visible() - } } #[cfg(target_arch = "wasm32")] diff --git a/src/window/mode.rs b/src/window/mode.rs index 37464711..fdce8e23 100644 --- a/src/window/mode.rs +++ b/src/window/mode.rs @@ -6,4 +6,7 @@ pub enum Mode { /// The application takes the whole screen of its current monitor. Fullscreen, + + /// The application is hidden + Hidden, } diff --git a/winit/src/application.rs b/winit/src/application.rs index e73f3df1..106d5218 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -98,13 +98,6 @@ pub trait Application: Program { fn should_exit(&self) -> bool { false } - - /// Returns whether the [`Application`] should be visible or not - /// - /// By default, it returns `true`. - fn visible(&self) -> bool { - true - } } /// Runs an [`Application`] with an executor, compositor, and the provided @@ -152,7 +145,6 @@ where .into_builder( &application.title(), application.mode(), - application.visible(), event_loop.primary_monitor(), ) .build(&event_loop) diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs index d157211a..b54d3aed 100644 --- a/winit/src/application/state.rs +++ b/winit/src/application/state.rs @@ -12,7 +12,6 @@ pub struct State { mode: Mode, background_color: Color, scale_factor: f64, - visible: bool, viewport: Viewport, viewport_version: usize, cursor_position: winit::dpi::PhysicalPosition, @@ -27,7 +26,6 @@ impl State { let mode = application.mode(); let background_color = application.background_color(); let scale_factor = application.scale_factor(); - let visible = application.visible(); let viewport = { let physical_size = window.inner_size(); @@ -43,7 +41,6 @@ impl State { mode, background_color, scale_factor, - visible, viewport, viewport_version: 0, // TODO: Encode cursor availability in the type-system @@ -185,6 +182,8 @@ impl State { new_mode, )); + window.set_visible(conversion::visible(new_mode)); + self.mode = new_mode; } @@ -204,14 +203,5 @@ impl State { self.scale_factor = new_scale_factor; } - - // Update window visibility - let new_visible = application.visible(); - - if self.visible != new_visible { - window.set_visible(new_visible); - - self.visible = new_visible; - } } } diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 0fa27413..b850a805 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -141,13 +141,21 @@ pub fn fullscreen( mode: Mode, ) -> Option { match mode { - Mode::Windowed => None, + Mode::Windowed | Mode::Hidden => None, Mode::Fullscreen => { Some(winit::window::Fullscreen::Borderless(monitor)) } } } +/// Converts a [`Mode`] to a visibility flag. +pub fn visible(mode: Mode) -> bool { + match mode { + Mode::Windowed | Mode::Fullscreen => true, + Mode::Hidden => false, + } +} + /// Converts a `MouseCursor` from [`iced_native`] to a [`winit`] cursor icon. /// /// [`winit`]: https://github.com/rust-windowing/winit diff --git a/winit/src/mode.rs b/winit/src/mode.rs index 37464711..fdce8e23 100644 --- a/winit/src/mode.rs +++ b/winit/src/mode.rs @@ -6,4 +6,7 @@ pub enum Mode { /// The application takes the whole screen of its current monitor. Fullscreen, + + /// The application is hidden + Hidden, } diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 56be1e07..663fa07a 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -66,7 +66,6 @@ impl Window { self, title: &str, mode: Mode, - visible: bool, primary_monitor: Option, ) -> WindowBuilder { let mut window_builder = WindowBuilder::new(); @@ -82,7 +81,7 @@ impl Window { .with_window_icon(self.icon) .with_always_on_top(self.always_on_top) .with_fullscreen(conversion::fullscreen(primary_monitor, mode)) - .with_visible(visible); + .with_visible(conversion::visible(mode)); if let Some((width, height)) = self.min_size { window_builder = window_builder -- cgit From 9a2c78c4059d2be37d10adda397fb6e64f38ac02 Mon Sep 17 00:00:00 2001 From: Dispersia Date: Sun, 11 Apr 2021 18:55:57 -0700 Subject: Upgrade wgpu --- examples/integration/src/scene.rs | 11 ++-- wgpu/Cargo.toml | 4 +- wgpu/src/image.rs | 59 +++++++++--------- wgpu/src/image/atlas.rs | 24 ++++---- wgpu/src/quad.rs | 76 +++++++++++------------ wgpu/src/shader/blit.frag | 12 ---- wgpu/src/shader/blit.frag.spv | Bin 684 -> 0 bytes wgpu/src/shader/blit.vert | 26 -------- wgpu/src/shader/blit.vert.spv | Bin 1384 -> 0 bytes wgpu/src/shader/blit.wgsl | 43 +++++++++++++ wgpu/src/shader/image.frag | 12 ---- wgpu/src/shader/image.frag.spv | Bin 684 -> 0 bytes wgpu/src/shader/image.vert | 27 --------- wgpu/src/shader/image.vert.spv | Bin 2504 -> 0 bytes wgpu/src/shader/image.wgsl | 45 ++++++++++++++ wgpu/src/shader/quad.frag | 66 -------------------- wgpu/src/shader/quad.frag.spv | Bin 4212 -> 0 bytes wgpu/src/shader/quad.vert | 47 --------------- wgpu/src/shader/quad.vert.spv | Bin 3604 -> 0 bytes wgpu/src/shader/quad.wgsl | 123 ++++++++++++++++++++++++++++++++++++++ wgpu/src/shader/triangle.frag | 8 --- wgpu/src/shader/triangle.frag.spv | Bin 372 -> 0 bytes wgpu/src/shader/triangle.vert | 15 ----- wgpu/src/shader/triangle.vert.spv | Bin 1256 -> 0 bytes wgpu/src/shader/triangle.wgsl | 31 ++++++++++ wgpu/src/triangle.rs | 57 +++++++++--------- wgpu/src/triangle/msaa.rs | 49 ++++++++------- wgpu/src/window/compositor.rs | 4 +- 28 files changed, 386 insertions(+), 353 deletions(-) delete mode 100644 wgpu/src/shader/blit.frag delete mode 100644 wgpu/src/shader/blit.frag.spv delete mode 100644 wgpu/src/shader/blit.vert delete mode 100644 wgpu/src/shader/blit.vert.spv create mode 100644 wgpu/src/shader/blit.wgsl delete mode 100644 wgpu/src/shader/image.frag delete mode 100644 wgpu/src/shader/image.frag.spv delete mode 100644 wgpu/src/shader/image.vert delete mode 100644 wgpu/src/shader/image.vert.spv create mode 100644 wgpu/src/shader/image.wgsl delete mode 100644 wgpu/src/shader/quad.frag delete mode 100644 wgpu/src/shader/quad.frag.spv delete mode 100644 wgpu/src/shader/quad.vert delete mode 100644 wgpu/src/shader/quad.vert.spv create mode 100644 wgpu/src/shader/quad.wgsl delete mode 100644 wgpu/src/shader/triangle.frag delete mode 100644 wgpu/src/shader/triangle.frag.spv delete mode 100644 wgpu/src/shader/triangle.vert delete mode 100644 wgpu/src/shader/triangle.vert.spv create mode 100644 wgpu/src/shader/triangle.wgsl diff --git a/examples/integration/src/scene.rs b/examples/integration/src/scene.rs index 36c0a41d..3e8277c8 100644 --- a/examples/integration/src/scene.rs +++ b/examples/integration/src/scene.rs @@ -20,8 +20,8 @@ impl Scene { ) -> wgpu::RenderPass<'a> { encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, - color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { - attachment: target, + color_attachments: &[wgpu::RenderPassColorAttachment { + view: target, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear({ @@ -75,15 +75,16 @@ fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline { entry_point: "main", targets: &[wgpu::ColorTargetState { format: wgpu::TextureFormat::Bgra8UnormSrgb, - color_blend: wgpu::BlendState::REPLACE, - alpha_blend: wgpu::BlendState::REPLACE, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent::REPLACE, + alpha: wgpu::BlendComponent::REPLACE, + }), write_mask: wgpu::ColorWrite::ALL, }], }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, front_face: wgpu::FrontFace::Ccw, - cull_mode: wgpu::CullMode::None, ..Default::default() }, depth_stencil: None, diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index fe41d9c0..28927638 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -26,8 +26,8 @@ qr_code = ["iced_graphics/qr_code"] default_system_font = ["iced_graphics/font-source"] [dependencies] -wgpu = "0.7" -wgpu_glyph = "0.11" +wgpu = {git = "https://github.com/gfx-rs/wgpu-rs", rev = "53600ecd834893ef3e90458c48b84f2582d6c343"} +wgpu_glyph = {git = "https://github.com/Dispersia/wgpu_glyph", branch = "update-wgpu"} glyph_brush = "0.7" raw-window-handle = "0.3" log = "0.4" diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 5511565e..713af209 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -134,28 +134,26 @@ impl Pipeline { bind_group_layouts: &[&constant_layout, &texture_layout], }); - let vs_module = device.create_shader_module(&wgpu::include_spirv!( - "shader/image.vert.spv" - )); - - let fs_module = device.create_shader_module(&wgpu::include_spirv!( - "shader/image.frag.spv" - )); + let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor { + label: Some("iced_wgpu::image::shader"), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!("shader/image.wgsl"))), + flags: wgpu::ShaderFlags::all() + }); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("iced_wgpu::image pipeline"), layout: Some(&layout), vertex: wgpu::VertexState { - module: &vs_module, - entry_point: "main", + module: &shader, + entry_point: "vs_main", buffers: &[ wgpu::VertexBufferLayout { array_stride: mem::size_of::() as u64, step_mode: wgpu::InputStepMode::Vertex, attributes: &[wgpu::VertexAttribute { shader_location: 0, - format: wgpu::VertexFormat::Float2, + format: wgpu::VertexFormat::Float32x2, offset: 0, }], }, @@ -165,27 +163,27 @@ impl Pipeline { attributes: &[ wgpu::VertexAttribute { shader_location: 1, - format: wgpu::VertexFormat::Float2, + format: wgpu::VertexFormat::Float32x2, offset: 0, }, wgpu::VertexAttribute { shader_location: 2, - format: wgpu::VertexFormat::Float2, + format: wgpu::VertexFormat::Float32x2, offset: 4 * 2, }, wgpu::VertexAttribute { shader_location: 3, - format: wgpu::VertexFormat::Float2, + format: wgpu::VertexFormat::Float32x2, offset: 4 * 4, }, wgpu::VertexAttribute { shader_location: 4, - format: wgpu::VertexFormat::Float2, + format: wgpu::VertexFormat::Float32x2, offset: 4 * 6, }, wgpu::VertexAttribute { shader_location: 5, - format: wgpu::VertexFormat::Uint, + format: wgpu::VertexFormat::Uint32, offset: 4 * 8, }, ], @@ -193,27 +191,28 @@ impl Pipeline { ], }, fragment: Some(wgpu::FragmentState { - module: &fs_module, - entry_point: "main", + module: &shader, + entry_point: "fs_main", targets: &[wgpu::ColorTargetState { format, - color_blend: wgpu::BlendState { - src_factor: wgpu::BlendFactor::SrcAlpha, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - alpha_blend: wgpu::BlendState { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + }), write_mask: wgpu::ColorWrite::ALL, }], }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, front_face: wgpu::FrontFace::Cw, - cull_mode: wgpu::CullMode::None, ..Default::default() }, depth_stencil: None, @@ -424,8 +423,8 @@ impl Pipeline { encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("iced_wgpu::image render pass"), color_attachments: &[ - wgpu::RenderPassColorAttachmentDescriptor { - attachment: target, + wgpu::RenderPassColorAttachment { + view: target, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index 660ebe44..4855fa4a 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -4,6 +4,8 @@ mod allocation; mod allocator; mod layer; +use std::num::NonZeroU32; + pub use allocation::Allocation; pub use entry::Entry; pub use layer::Layer; @@ -24,7 +26,7 @@ impl Atlas { let extent = wgpu::Extent3d { width: SIZE, height: SIZE, - depth: 1, + depth_or_array_layers: 1, }; let texture = device.create_texture(&wgpu::TextureDescriptor { @@ -294,19 +296,19 @@ impl Atlas { let extent = wgpu::Extent3d { width, height, - depth: 1, + depth_or_array_layers: 1, }; encoder.copy_buffer_to_texture( - wgpu::BufferCopyView { + wgpu::ImageCopyBuffer { buffer, - layout: wgpu::TextureDataLayout { + layout: wgpu::ImageDataLayout { offset: offset as u64, - bytes_per_row: 4 * image_width + padding, - rows_per_image: image_height, + bytes_per_row: NonZeroU32::new(4 * image_width + padding), + rows_per_image: NonZeroU32::new(image_height), }, }, - wgpu::TextureCopyView { + wgpu::ImageCopyTexture { texture: &self.texture, mip_level: 0, origin: wgpu::Origin3d { @@ -334,7 +336,7 @@ impl Atlas { size: wgpu::Extent3d { width: SIZE, height: SIZE, - depth: self.layers.len() as u32, + depth_or_array_layers: self.layers.len() as u32, }, mip_level_count: 1, sample_count: 1, @@ -355,7 +357,7 @@ impl Atlas { } encoder.copy_texture_to_texture( - wgpu::TextureCopyView { + wgpu::ImageCopyTexture { texture: &self.texture, mip_level: 0, origin: wgpu::Origin3d { @@ -364,7 +366,7 @@ impl Atlas { z: i as u32, }, }, - wgpu::TextureCopyView { + wgpu::ImageCopyTexture { texture: &new_texture, mip_level: 0, origin: wgpu::Origin3d { @@ -376,7 +378,7 @@ impl Atlas { wgpu::Extent3d { width: SIZE, height: SIZE, - depth: 1, + depth_or_array_layers: 1, }, ); } diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index e0a6e043..458679e4 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -62,28 +62,29 @@ impl Pipeline { bind_group_layouts: &[&constant_layout], }); - let vs_module = device.create_shader_module(&wgpu::include_spirv!( - "shader/quad.vert.spv" - )); - - let fs_module = device.create_shader_module(&wgpu::include_spirv!( - "shader/quad.frag.spv" - )); + let shader = + device.create_shader_module(&wgpu::ShaderModuleDescriptor { + label: Some("iced_wgpu::quad::shader"), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( + include_str!("shader/quad.wgsl"), + )), + flags: wgpu::ShaderFlags::all(), + }); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("iced_wgpu::quad pipeline"), layout: Some(&layout), vertex: wgpu::VertexState { - module: &vs_module, - entry_point: "main", + module: &shader, + entry_point: "vs_main", buffers: &[ wgpu::VertexBufferLayout { array_stride: mem::size_of::() as u64, step_mode: wgpu::InputStepMode::Vertex, attributes: &[wgpu::VertexAttribute { shader_location: 0, - format: wgpu::VertexFormat::Float2, + format: wgpu::VertexFormat::Float32x2, offset: 0, }], }, @@ -93,32 +94,32 @@ impl Pipeline { attributes: &[ wgpu::VertexAttribute { shader_location: 1, - format: wgpu::VertexFormat::Float2, + format: wgpu::VertexFormat::Float32x2, offset: 0, }, wgpu::VertexAttribute { shader_location: 2, - format: wgpu::VertexFormat::Float2, + format: wgpu::VertexFormat::Float32x2, offset: 4 * 2, }, wgpu::VertexAttribute { shader_location: 3, - format: wgpu::VertexFormat::Float4, + format: wgpu::VertexFormat::Float32x4, offset: 4 * (2 + 2), }, wgpu::VertexAttribute { shader_location: 4, - format: wgpu::VertexFormat::Float4, + format: wgpu::VertexFormat::Float32x4, offset: 4 * (2 + 2 + 4), }, wgpu::VertexAttribute { shader_location: 5, - format: wgpu::VertexFormat::Float, + format: wgpu::VertexFormat::Float32, offset: 4 * (2 + 2 + 4 + 4), }, wgpu::VertexAttribute { shader_location: 6, - format: wgpu::VertexFormat::Float, + format: wgpu::VertexFormat::Float32, offset: 4 * (2 + 2 + 4 + 4 + 1), }, ], @@ -126,27 +127,28 @@ impl Pipeline { ], }, fragment: Some(wgpu::FragmentState { - module: &fs_module, - entry_point: "main", + module: &shader, + entry_point: "fs_main", targets: &[wgpu::ColorTargetState { format, - color_blend: wgpu::BlendState { - src_factor: wgpu::BlendFactor::SrcAlpha, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - alpha_blend: wgpu::BlendState { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + }), write_mask: wgpu::ColorWrite::ALL, }], }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, front_face: wgpu::FrontFace::Cw, - cull_mode: wgpu::CullMode::None, ..Default::default() }, depth_stencil: None, @@ -237,16 +239,14 @@ impl Pipeline { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("iced_wgpu::quad render pass"), - color_attachments: &[ - wgpu::RenderPassColorAttachmentDescriptor { - attachment: target, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: true, - }, + color_attachments: &[wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, }, - ], + }], depth_stencil_attachment: None, }); diff --git a/wgpu/src/shader/blit.frag b/wgpu/src/shader/blit.frag deleted file mode 100644 index dfed960f..00000000 --- a/wgpu/src/shader/blit.frag +++ /dev/null @@ -1,12 +0,0 @@ -#version 450 - -layout(location = 0) in vec2 v_Uv; - -layout(set = 0, binding = 0) uniform sampler u_Sampler; -layout(set = 1, binding = 0) uniform texture2D u_Texture; - -layout(location = 0) out vec4 o_Color; - -void main() { - o_Color = texture(sampler2D(u_Texture, u_Sampler), v_Uv); -} diff --git a/wgpu/src/shader/blit.frag.spv b/wgpu/src/shader/blit.frag.spv deleted file mode 100644 index 2c5638b5..00000000 Binary files a/wgpu/src/shader/blit.frag.spv and /dev/null differ diff --git a/wgpu/src/shader/blit.vert b/wgpu/src/shader/blit.vert deleted file mode 100644 index 899cd39d..00000000 --- a/wgpu/src/shader/blit.vert +++ /dev/null @@ -1,26 +0,0 @@ -#version 450 - -layout(location = 0) out vec2 o_Uv; - -const vec2 positions[6] = vec2[6]( - vec2(-1.0, 1.0), - vec2(-1.0, -1.0), - vec2(1.0, -1.0), - vec2(-1.0, 1.0), - vec2(1.0, 1.0), - vec2(1.0, -1.0) -); - -const vec2 uvs[6] = vec2[6]( - vec2(0.0, 0.0), - vec2(0.0, 1.0), - vec2(1.0, 1.0), - vec2(0.0, 0.0), - vec2(1.0, 0.0), - vec2(1.0, 1.0) -); - -void main() { - o_Uv = uvs[gl_VertexIndex]; - gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); -} diff --git a/wgpu/src/shader/blit.vert.spv b/wgpu/src/shader/blit.vert.spv deleted file mode 100644 index e0b436ce..00000000 Binary files a/wgpu/src/shader/blit.vert.spv and /dev/null differ diff --git a/wgpu/src/shader/blit.wgsl b/wgpu/src/shader/blit.wgsl new file mode 100644 index 00000000..7a987c2c --- /dev/null +++ b/wgpu/src/shader/blit.wgsl @@ -0,0 +1,43 @@ +var positions: array, 6> = array, 6>( + vec2(-1.0, 1.0), + vec2(-1.0, -1.0), + vec2(1.0, -1.0), + vec2(-1.0, 1.0), + vec2(1.0, 1.0), + vec2(1.0, -1.0) +); + +var uvs: array, 6> = array, 6>( + vec2(0.0, 0.0), + vec2(0.0, 1.0), + vec2(1.0, 1.0), + vec2(0.0, 0.0), + vec2(1.0, 0.0), + vec2(1.0, 1.0) +); + +[[group(0), binding(0)]] var u_sampler: sampler; +[[group(1), binding(0)]] var u_texture: texture_2d; + +struct VertexInput { + [[builtin(vertex_index)]] vertex_index: u32; +}; + +struct VertexOutput { + [[builtin(position)]] position: vec4; + [[location(0)]] uv: vec2; +}; + +[[stage(vertex)]] +fn vs_main(input: VertexInput) -> VertexOutput { + var out: VertexOutput; + out.uv = uvs[input.vertex_index]; + out.position = vec4(positions[input.vertex_index], 0.0, 1.0); + + return out; +} + +[[stage(fragment)]] +fn fs_main(input: VertexOutput) -> [[location(0)]] vec4 { + return textureSample(u_texture, u_sampler, input.uv); +} \ No newline at end of file diff --git a/wgpu/src/shader/image.frag b/wgpu/src/shader/image.frag deleted file mode 100644 index 2809e9e6..00000000 --- a/wgpu/src/shader/image.frag +++ /dev/null @@ -1,12 +0,0 @@ -#version 450 - -layout(location = 0) in vec3 v_Uv; - -layout(set = 0, binding = 1) uniform sampler u_Sampler; -layout(set = 1, binding = 0) uniform texture2DArray u_Texture; - -layout(location = 0) out vec4 o_Color; - -void main() { - o_Color = texture(sampler2DArray(u_Texture, u_Sampler), v_Uv); -} diff --git a/wgpu/src/shader/image.frag.spv b/wgpu/src/shader/image.frag.spv deleted file mode 100644 index 65b08aa3..00000000 Binary files a/wgpu/src/shader/image.frag.spv and /dev/null differ diff --git a/wgpu/src/shader/image.vert b/wgpu/src/shader/image.vert deleted file mode 100644 index dab53cfe..00000000 --- a/wgpu/src/shader/image.vert +++ /dev/null @@ -1,27 +0,0 @@ -#version 450 - -layout(location = 0) in vec2 v_Pos; -layout(location = 1) in vec2 i_Pos; -layout(location = 2) in vec2 i_Scale; -layout(location = 3) in vec2 i_Atlas_Pos; -layout(location = 4) in vec2 i_Atlas_Scale; -layout(location = 5) in uint i_Layer; - -layout (set = 0, binding = 0) uniform Globals { - mat4 u_Transform; -}; - -layout(location = 0) out vec3 o_Uv; - -void main() { - o_Uv = vec3(v_Pos * i_Atlas_Scale + i_Atlas_Pos, i_Layer); - - mat4 i_Transform = mat4( - vec4(i_Scale.x, 0.0, 0.0, 0.0), - vec4(0.0, i_Scale.y, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(i_Pos, 0.0, 1.0) - ); - - gl_Position = u_Transform * i_Transform * vec4(v_Pos, 0.0, 1.0); -} diff --git a/wgpu/src/shader/image.vert.spv b/wgpu/src/shader/image.vert.spv deleted file mode 100644 index 21f5db2d..00000000 Binary files a/wgpu/src/shader/image.vert.spv and /dev/null differ diff --git a/wgpu/src/shader/image.wgsl b/wgpu/src/shader/image.wgsl new file mode 100644 index 00000000..c623247e --- /dev/null +++ b/wgpu/src/shader/image.wgsl @@ -0,0 +1,45 @@ +[[block]] +struct Globals { + transform: mat4x4; +}; + +[[group(0), binding(0)]] var globals: Globals; +[[group(0), binding(1)]] var u_sampler: sampler; +[[group(0), binding(2)]] var u_texture: texture2d; + +struct VertexInput { + [[location(0)]] v_pos: vec2; + [[location(1)]] pos: vec2; + [[location(2)]] scale: vec2; + [[location(3)]] atlas_pos: vec2; + [[location(4)]] atlas_scale: vec2; + [[location(5)]] layer: u32; +}; + +struct VertexOutput { + [[builtin(position)]] position: vec4; + [[location(0)]] uv: vec3; +}; + +[[stage(vertex)]] +fn vs_main(input: VertexInput) -> VertexOutput { + var out: VertexOutput; + + out.uv = vec3(input.v_pos * input.atlas_scale + input.atlas_pos, input.layer); + + var transform: mat4x4 = mat4x4( + vec4(input.scale.x, 0.0, 0.0, 0.0), + vec4(0.0, scale.y, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(input.pos, 0.0, 1.0) + ); + + out.position = globals.transform * transform * vec4(input.v_pos, 0.0, 1.0); + + return out; +} + +[[stage(fragment)]] +fn fs_main(input: VertexOutput) -> [[location(0)]] vec4 { + return textureSample(u_texture, u_sampler, input.uv); +} diff --git a/wgpu/src/shader/quad.frag b/wgpu/src/shader/quad.frag deleted file mode 100644 index ad1af1ad..00000000 --- a/wgpu/src/shader/quad.frag +++ /dev/null @@ -1,66 +0,0 @@ -#version 450 - -layout(location = 0) in vec4 v_Color; -layout(location = 1) in vec4 v_BorderColor; -layout(location = 2) in vec2 v_Pos; -layout(location = 3) in vec2 v_Scale; -layout(location = 4) in float v_BorderRadius; -layout(location = 5) in float v_BorderWidth; - -layout(location = 0) out vec4 o_Color; - -float distance(in vec2 frag_coord, in vec2 position, in vec2 size, float radius) -{ - // TODO: Try SDF approach: https://www.shadertoy.com/view/wd3XRN - vec2 inner_size = size - vec2(radius, radius) * 2.0; - vec2 top_left = position + vec2(radius, radius); - vec2 bottom_right = top_left + inner_size; - - vec2 top_left_distance = top_left - frag_coord; - vec2 bottom_right_distance = frag_coord - bottom_right; - - vec2 distance = vec2( - max(max(top_left_distance.x, bottom_right_distance.x), 0), - max(max(top_left_distance.y, bottom_right_distance.y), 0) - ); - - return sqrt(distance.x * distance.x + distance.y * distance.y); -} - -void main() { - vec4 mixed_color; - - // TODO: Remove branching (?) - if(v_BorderWidth > 0) { - float internal_border = max(v_BorderRadius - v_BorderWidth, 0); - - float internal_distance = distance( - gl_FragCoord.xy, - v_Pos + vec2(v_BorderWidth), - v_Scale - vec2(v_BorderWidth * 2.0), - internal_border - ); - - float border_mix = smoothstep( - max(internal_border - 0.5, 0.0), - internal_border + 0.5, - internal_distance - ); - - mixed_color = mix(v_Color, v_BorderColor, border_mix); - } else { - mixed_color = v_Color; - } - - float d = distance( - gl_FragCoord.xy, - v_Pos, - v_Scale, - v_BorderRadius - ); - - float radius_alpha = - 1.0 - smoothstep(max(v_BorderRadius - 0.5, 0), v_BorderRadius + 0.5, d); - - o_Color = vec4(mixed_color.xyz, mixed_color.w * radius_alpha); -} diff --git a/wgpu/src/shader/quad.frag.spv b/wgpu/src/shader/quad.frag.spv deleted file mode 100644 index 519f5f01..00000000 Binary files a/wgpu/src/shader/quad.frag.spv and /dev/null differ diff --git a/wgpu/src/shader/quad.vert b/wgpu/src/shader/quad.vert deleted file mode 100644 index 09a278b1..00000000 --- a/wgpu/src/shader/quad.vert +++ /dev/null @@ -1,47 +0,0 @@ -#version 450 - -layout(location = 0) in vec2 v_Pos; -layout(location = 1) in vec2 i_Pos; -layout(location = 2) in vec2 i_Scale; -layout(location = 3) in vec4 i_Color; -layout(location = 4) in vec4 i_BorderColor; -layout(location = 5) in float i_BorderRadius; -layout(location = 6) in float i_BorderWidth; - -layout (set = 0, binding = 0) uniform Globals { - mat4 u_Transform; - float u_Scale; -}; - -layout(location = 0) out vec4 o_Color; -layout(location = 1) out vec4 o_BorderColor; -layout(location = 2) out vec2 o_Pos; -layout(location = 3) out vec2 o_Scale; -layout(location = 4) out float o_BorderRadius; -layout(location = 5) out float o_BorderWidth; - -void main() { - vec2 p_Pos = i_Pos * u_Scale; - vec2 p_Scale = i_Scale * u_Scale; - - float i_BorderRadius = min( - i_BorderRadius, - min(i_Scale.x, i_Scale.y) / 2.0 - ); - - mat4 i_Transform = mat4( - vec4(p_Scale.x + 1.0, 0.0, 0.0, 0.0), - vec4(0.0, p_Scale.y + 1.0, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(p_Pos - vec2(0.5, 0.5), 0.0, 1.0) - ); - - o_Color = i_Color; - o_BorderColor = i_BorderColor; - o_Pos = p_Pos; - o_Scale = p_Scale; - o_BorderRadius = i_BorderRadius * u_Scale; - o_BorderWidth = i_BorderWidth * u_Scale; - - gl_Position = u_Transform * i_Transform * vec4(v_Pos, 0.0, 1.0); -} diff --git a/wgpu/src/shader/quad.vert.spv b/wgpu/src/shader/quad.vert.spv deleted file mode 100644 index fa71ba1e..00000000 Binary files a/wgpu/src/shader/quad.vert.spv and /dev/null differ diff --git a/wgpu/src/shader/quad.wgsl b/wgpu/src/shader/quad.wgsl new file mode 100644 index 00000000..c67b45e3 --- /dev/null +++ b/wgpu/src/shader/quad.wgsl @@ -0,0 +1,123 @@ +[[block]] +struct Globals { + transform: mat4x4; + scale: f32; +}; + +[[group(0), binding(0)]] var globals: Globals; + +struct VertexInput { + [[location(0)]] v_pos: vec2; + [[location(1)]] pos: vec2; + [[location(2)]] scale: vec2; + [[location(3)]] color: vec4; + [[location(4)]] border_color: vec4; + [[location(5)]] border_radius: f32; + [[location(6)]] border_width: f32; +}; + +struct VertexOutput { + [[builtin(position)]] position: vec4; + [[location(0)]] color: vec4; + [[location(1)]] border_color: vec4; + [[location(2)]] pos: vec2; + [[location(3)]] scale: vec2; + [[location(4)]] border_radius: f32; + [[location(5)]] border_width: f32; +}; + +[[stage(vertex)]] +fn vs_main(input: VertexInput) -> VertexOutput { + var out: VertexOutput; + + var pos: vec2 = input.pos * globals.scale; + var scale: vec2 = input.scale * globals.scale; + + var border_radius: f32 = min( + input.border_radius, + min(input.scale.x, input.scale.y) / 2.0 + ); + + var transform: mat4x4 = mat4x4( + vec4(scale.x + 1.0, 0.0, 0.0, 0.0), + vec4(0.0, scale.y + 1.0, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(pos - vec2(0.5, 0.5), 0.0, 1.0) + ); + + out.color = input.color; + out.border_color = input.border_color; + out.pos = pos; + out.scale = scale; + out.border_radius = border_radius * globals.scale; + out.border_width = input.border_width * globals.scale; + out.position = globals.transform * transform * vec4(input.v_pos, 0.0, 1.0); + + return out; +} + +fn distance_alg( + frag_coord: vec2, + position: vec2, + size: vec2, + radius: f32 +) -> f32 { + var inner_size: vec2 = size - vec2(radius, radius) * 2.0; + var top_left: vec2 = position + vec2(radius, radius); + var bottom_right: vec2 = top_left + inner_size; + + var top_left_distance: vec2 = top_left - frag_coord; + var bottom_right_distance: vec2 = frag_coord - bottom_right; + + var dist: vec2 = vec2( + max(max(top_left_distance.x, bottom_right_distance.x), 0.0), + max(max(top_left_distance.y, bottom_right_distance.y), 0.0) + ); + + return sqrt(dist.x * dist.x + dist.y * dist.y); +} + + +[[stage(fragment)]] +fn fs_main( + input: VertexOutput, + [[builtin(position)]] coord: vec4 +) -> [[location(0)]] vec4 { + var mixed_color: vec4 = input.color; + + if (input.border_width > 0.0) { + var internal_border: f32 = max( + input.border_radius - input.border_width, + 0.0 + ); + + var internal_distance: f32 = distance_alg( + vec2(coord.x, coord.y), + input.pos + vec2(input.border_width, input.border_width), + input.scale - vec2(input.border_width * 2.0, input.border_width * 2.0), + internal_border + ); + + var border_mix: f32 = smoothStep( + max(internal_border - 0.5, 0.0), + internal_border + 0.5, + internal_distance + ); + + mixed_color = mix(input.color, input.border_color, vec4(border_mix, border_mix, border_mix, border_mix)); + } + + var dist: f32 = distance_alg( + vec2(coord.x, coord.y), + input.pos, + input.scale, + input.border_radius + ); + + var radius_alpha: f32 = 1.0 - smoothStep( + max(input.border_radius - 0.5, 0.0), + input.border_radius + 0.5, + dist); + + return vec4(radius_alpha, radius_alpha, radius_alpha, radius_alpha); +} \ No newline at end of file diff --git a/wgpu/src/shader/triangle.frag b/wgpu/src/shader/triangle.frag deleted file mode 100644 index e39c45e7..00000000 --- a/wgpu/src/shader/triangle.frag +++ /dev/null @@ -1,8 +0,0 @@ -#version 450 - -layout(location = 0) in vec4 i_Color; -layout(location = 0) out vec4 o_Color; - -void main() { - o_Color = i_Color; -} diff --git a/wgpu/src/shader/triangle.frag.spv b/wgpu/src/shader/triangle.frag.spv deleted file mode 100644 index 11201872..00000000 Binary files a/wgpu/src/shader/triangle.frag.spv and /dev/null differ diff --git a/wgpu/src/shader/triangle.vert b/wgpu/src/shader/triangle.vert deleted file mode 100644 index 1f2c009b..00000000 --- a/wgpu/src/shader/triangle.vert +++ /dev/null @@ -1,15 +0,0 @@ -#version 450 - -layout(location = 0) in vec2 i_Position; -layout(location = 1) in vec4 i_Color; - -layout(location = 0) out vec4 o_Color; - -layout (set = 0, binding = 0) uniform Globals { - mat4 u_Transform; -}; - -void main() { - gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0); - o_Color = i_Color; -} diff --git a/wgpu/src/shader/triangle.vert.spv b/wgpu/src/shader/triangle.vert.spv deleted file mode 100644 index 871f4f55..00000000 Binary files a/wgpu/src/shader/triangle.vert.spv and /dev/null differ diff --git a/wgpu/src/shader/triangle.wgsl b/wgpu/src/shader/triangle.wgsl new file mode 100644 index 00000000..96eaabcc --- /dev/null +++ b/wgpu/src/shader/triangle.wgsl @@ -0,0 +1,31 @@ +[[block]] +struct Globals { + transform: mat4x4; +}; + +[[group(0), binding(0)]] var globals: Globals; + +struct VertexInput { + [[location(0)]] position: vec2; + [[location(1)]] color: vec4; +}; + +struct VertexOutput { + [[builtin(position)]] position: vec4; + [[location(0)]] color: vec4; +}; + +[[stage(vertex)]] +fn vs_main(input: VertexInput) -> VertexOutput { + var out: VertexOutput; + + out.color = input.color; + out.position = globals.transform * vec4(input.position, 0.0, 1.0); + + return out; +} + +[[stage(fragment)]] +fn fs_main(input: VertexOutput) -> [[location(0)]] vec4 { + return input.color; +} diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 2f255940..168ff953 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -127,21 +127,19 @@ impl Pipeline { bind_group_layouts: &[&constants_layout], }); - let vs_module = device.create_shader_module(&wgpu::include_spirv!( - "shader/triangle.vert.spv" - )); - - let fs_module = device.create_shader_module(&wgpu::include_spirv!( - "shader/triangle.frag.spv" - )); + let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor { + label: Some("iced_wgpu::triangle::shader"), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!("shader/triangle.wgsl"))), + flags: wgpu::ShaderFlags::all() + }); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("iced_wgpu::triangle pipeline"), layout: Some(&layout), vertex: wgpu::VertexState { - module: &vs_module, - entry_point: "main", + module: &shader, + entry_point: "vs_main", buffers: &[wgpu::VertexBufferLayout { array_stride: mem::size_of::() as u64, step_mode: wgpu::InputStepMode::Vertex, @@ -149,40 +147,41 @@ impl Pipeline { // Position wgpu::VertexAttribute { shader_location: 0, - format: wgpu::VertexFormat::Float2, + format: wgpu::VertexFormat::Float32x2, offset: 0, }, // Color wgpu::VertexAttribute { shader_location: 1, - format: wgpu::VertexFormat::Float4, + format: wgpu::VertexFormat::Float32x4, offset: 4 * 2, }, ], }], }, fragment: Some(wgpu::FragmentState { - module: &fs_module, - entry_point: "main", + module: &shader, + entry_point: "fs_main", targets: &[wgpu::ColorTargetState { format, - color_blend: wgpu::BlendState { - src_factor: wgpu::BlendFactor::SrcAlpha, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - alpha_blend: wgpu::BlendState { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + }), write_mask: wgpu::ColorWrite::ALL, }], }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, front_face: wgpu::FrontFace::Cw, - cull_mode: wgpu::CullMode::None, ..Default::default() }, depth_stencil: None, @@ -261,7 +260,8 @@ impl Pipeline { Uniforms, >( ) - as u64), + as + u64), }, }], }); @@ -303,6 +303,8 @@ impl Pipeline { vertex_buffer.copy_from_slice(vertices); } + println!("Indices: {} - Index Size: {}", indices_size, self.index_buffer.size); + { let mut index_buffer = staging_belt.write_buffer( encoder, @@ -331,6 +333,7 @@ impl Pipeline { let uniforms = bytemuck::cast_slice(&uniforms); + if let Some(uniforms_size) = wgpu::BufferSize::new(uniforms.len() as u64) { @@ -364,8 +367,8 @@ impl Pipeline { encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("iced_wgpu::triangle render pass"), color_attachments: &[ - wgpu::RenderPassColorAttachmentDescriptor { - attachment, + wgpu::RenderPassColorAttachment { + view: attachment, resolve_target, ops: wgpu::Operations { load, store: true }, }, diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index d964f815..d155f3ec 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -74,45 +74,44 @@ impl Blit { bind_group_layouts: &[&constant_layout, &texture_layout], }); - let vs_module = device.create_shader_module(&wgpu::include_spirv!( - "../shader/blit.vert.spv" - )); - - let fs_module = device.create_shader_module(&wgpu::include_spirv!( - "../shader/blit.frag.spv" - )); + let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor { + label: Some("iced_wgpu::triangle::blit_shader"), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!("../shader/blit.wgsl"))), + flags: wgpu::ShaderFlags::all() + }); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("iced_wgpu::triangle::msaa pipeline"), layout: Some(&layout), vertex: wgpu::VertexState { - module: &vs_module, - entry_point: "main", + module: &shader, + entry_point: "vs_main", buffers: &[], }, fragment: Some(wgpu::FragmentState { - module: &fs_module, - entry_point: "main", + module: &shader, + entry_point: "fs_main", targets: &[wgpu::ColorTargetState { format, - color_blend: wgpu::BlendState { - src_factor: wgpu::BlendFactor::SrcAlpha, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - alpha_blend: wgpu::BlendState { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + }), write_mask: wgpu::ColorWrite::ALL, }], }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, front_face: wgpu::FrontFace::Cw, - cull_mode: wgpu::CullMode::None, ..Default::default() }, depth_stencil: None, @@ -178,8 +177,8 @@ impl Blit { encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("iced_wgpu::triangle::msaa render pass"), color_attachments: &[ - wgpu::RenderPassColorAttachmentDescriptor { - attachment: target, + wgpu::RenderPassColorAttachment { + view: target, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, @@ -222,7 +221,7 @@ impl Targets { let extent = wgpu::Extent3d { width, height, - depth: 1, + depth_or_array_layers: 1, }; let attachment = device.create_texture(&wgpu::TextureDescriptor { diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index fdd648e8..6ff499af 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -133,8 +133,8 @@ impl iced_graphics::window::Compositor for Compositor { let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("iced_wgpu::window::Compositor render pass"), - color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { - attachment: &frame.output.view, + color_attachments: &[wgpu::RenderPassColorAttachment { + view: &frame.output.view, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear({ -- cgit From c719091c3d7b5f71899530437fde9b512bc2b0f3 Mon Sep 17 00:00:00 2001 From: Dispersia Date: Mon, 12 Apr 2021 22:06:16 -0700 Subject: Add staging belt fix --- Cargo.toml | 12 ++++++++++++ wgpu/Cargo.toml | 4 ++-- wgpu/src/triangle.rs | 27 +++++++++++++-------------- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6d894eba..19f25d9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,18 @@ readme = "README.md" keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] +[patch."https://github.com/gfx-rs/wgpu"] +wgpu-types = { git = "https://github.com/Dispersia/wgpu" } +wgpu-core = { git = "https://github.com/Dispersia/wgpu" } + +[patch."https://github.com/gfx-rs/wgpu-rs"] +wgpu = { git = "https://github.com/Dispersia/wgpu-rs" } + +[patch.crates-io] +wgpu-types = { git = "https://github.com/Dispersia/wgpu" } +wgpu-core = { git = "https://github.com/Dispersia/wgpu" } +wgpu = { git = "https://github.com/Dispersia/wgpu-rs" } + [features] default = ["wgpu", "default_system_font"] # Enables the `iced_wgpu` renderer diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 28927638..f44b3874 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -26,8 +26,8 @@ qr_code = ["iced_graphics/qr_code"] default_system_font = ["iced_graphics/font-source"] [dependencies] -wgpu = {git = "https://github.com/gfx-rs/wgpu-rs", rev = "53600ecd834893ef3e90458c48b84f2582d6c343"} -wgpu_glyph = {git = "https://github.com/Dispersia/wgpu_glyph", branch = "update-wgpu"} +wgpu = {git="https://github.com/Dispersia/wgpu-rs"} +wgpu_glyph = {git="https://github.com/Dispersia/wgpu_glyph", branch = "update-wgpu"} glyph_brush = "0.7" raw-window-handle = "0.3" log = "0.4" diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 168ff953..67f14e4d 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -127,11 +127,14 @@ impl Pipeline { bind_group_layouts: &[&constants_layout], }); - let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor { - label: Some("iced_wgpu::triangle::shader"), - source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!("shader/triangle.wgsl"))), - flags: wgpu::ShaderFlags::all() - }); + let shader = + device.create_shader_module(&wgpu::ShaderModuleDescriptor { + label: Some("iced_wgpu::triangle::shader"), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( + include_str!("shader/triangle.wgsl"), + )), + flags: wgpu::ShaderFlags::all(), + }); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { @@ -303,8 +306,6 @@ impl Pipeline { vertex_buffer.copy_from_slice(vertices); } - println!("Indices: {} - Index Size: {}", indices_size, self.index_buffer.size); - { let mut index_buffer = staging_belt.write_buffer( encoder, @@ -366,13 +367,11 @@ impl Pipeline { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("iced_wgpu::triangle render pass"), - color_attachments: &[ - wgpu::RenderPassColorAttachment { - view: attachment, - resolve_target, - ops: wgpu::Operations { load, store: true }, - }, - ], + color_attachments: &[wgpu::RenderPassColorAttachment { + view: attachment, + resolve_target, + ops: wgpu::Operations { load, store: true }, + }], depth_stencil_attachment: None, }); -- cgit From 0722d5e3ec307fd82a1cc76593d17d83cf828943 Mon Sep 17 00:00:00 2001 From: Dispersia Date: Mon, 12 Apr 2021 23:07:58 -0700 Subject: add temporary fix for image wgsl --- wgpu/src/image.rs | 2 +- wgpu/src/shader/blit.wgsl | 2 +- wgpu/src/shader/image.wgsl | 14 ++++++++------ wgpu/src/shader/quad.wgsl | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 713af209..45b63701 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -183,7 +183,7 @@ impl Pipeline { }, wgpu::VertexAttribute { shader_location: 5, - format: wgpu::VertexFormat::Uint32, + format: wgpu::VertexFormat::Sint32, offset: 4 * 8, }, ], diff --git a/wgpu/src/shader/blit.wgsl b/wgpu/src/shader/blit.wgsl index 7a987c2c..694f192e 100644 --- a/wgpu/src/shader/blit.wgsl +++ b/wgpu/src/shader/blit.wgsl @@ -40,4 +40,4 @@ fn vs_main(input: VertexInput) -> VertexOutput { [[stage(fragment)]] fn fs_main(input: VertexOutput) -> [[location(0)]] vec4 { return textureSample(u_texture, u_sampler, input.uv); -} \ No newline at end of file +} diff --git a/wgpu/src/shader/image.wgsl b/wgpu/src/shader/image.wgsl index c623247e..a63ee8f6 100644 --- a/wgpu/src/shader/image.wgsl +++ b/wgpu/src/shader/image.wgsl @@ -5,7 +5,7 @@ struct Globals { [[group(0), binding(0)]] var globals: Globals; [[group(0), binding(1)]] var u_sampler: sampler; -[[group(0), binding(2)]] var u_texture: texture2d; +[[group(1), binding(0)]] var u_texture: texture_2d_array; struct VertexInput { [[location(0)]] v_pos: vec2; @@ -13,23 +13,25 @@ struct VertexInput { [[location(2)]] scale: vec2; [[location(3)]] atlas_pos: vec2; [[location(4)]] atlas_scale: vec2; - [[location(5)]] layer: u32; + [[location(5)]] layer: i32; }; struct VertexOutput { [[builtin(position)]] position: vec4; - [[location(0)]] uv: vec3; + [[location(0)]] uv: vec2; + [[location(1)]] layer: f32; // this should be an i32, but naga currently reads that as requiring interpolation. }; [[stage(vertex)]] fn vs_main(input: VertexInput) -> VertexOutput { var out: VertexOutput; - out.uv = vec3(input.v_pos * input.atlas_scale + input.atlas_pos, input.layer); + out.uv = vec2(input.v_pos * input.atlas_scale + input.atlas_pos); + out.layer = f32(input.layer); var transform: mat4x4 = mat4x4( vec4(input.scale.x, 0.0, 0.0, 0.0), - vec4(0.0, scale.y, 0.0, 0.0), + vec4(0.0, input.scale.y, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(input.pos, 0.0, 1.0) ); @@ -41,5 +43,5 @@ fn vs_main(input: VertexInput) -> VertexOutput { [[stage(fragment)]] fn fs_main(input: VertexOutput) -> [[location(0)]] vec4 { - return textureSample(u_texture, u_sampler, input.uv); + return textureSample(u_texture, u_sampler, input.uv, i32(input.layer)); } diff --git a/wgpu/src/shader/quad.wgsl b/wgpu/src/shader/quad.wgsl index c67b45e3..3c0aa9f2 100644 --- a/wgpu/src/shader/quad.wgsl +++ b/wgpu/src/shader/quad.wgsl @@ -120,4 +120,4 @@ fn fs_main( dist); return vec4(radius_alpha, radius_alpha, radius_alpha, radius_alpha); -} \ No newline at end of file +} -- 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 --- examples/pokedex/src/main.rs | 5 ++++- examples/styling/src/main.rs | 2 +- futures/src/subscription.rs | 3 ++- native/src/widget/pane_grid.rs | 9 +++++---- native/src/widget/text_input/cursor.rs | 4 ++-- native/src/widget/text_input/editor.rs | 4 ++-- src/window/icon.rs | 18 ++++++++++-------- wgpu/src/image.rs | 29 +++++++++++++++-------------- wgpu/src/shader/quad.wgsl | 2 +- wgpu/src/triangle/msaa.rs | 29 +++++++++++++++-------------- 10 files changed, 57 insertions(+), 48 deletions(-) diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs index d2f1bb62..da1d5d5d 100644 --- a/examples/pokedex/src/main.rs +++ b/examples/pokedex/src/main.rs @@ -213,7 +213,10 @@ impl Pokemon { } async fn fetch_image(id: u16) -> Result { - let url = format!("https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{}.png", id); + let url = format!( + "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{}.png", + id + ); #[cfg(not(target_arch = "wasm32"))] { diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs index 8975fd9a..4d7dfc48 100644 --- a/examples/styling/src/main.rs +++ b/examples/styling/src/main.rs @@ -44,7 +44,7 @@ impl Sandbox for Styling { match message { Message::ThemeChanged(theme) => self.theme = theme, Message::InputChanged(value) => self.input_value = value, - Message::ButtonPressed => (), + Message::ButtonPressed => {} Message::SliderChanged(value) => self.slider_value = value, Message::CheckboxToggled(value) => self.toggle_value = value, } diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs index 27d2d295..aa81f844 100644 --- a/futures/src/subscription.rs +++ b/futures/src/subscription.rs @@ -78,7 +78,8 @@ where .drain(..) .map(|recipe| { Box::new(With::new(recipe, value.clone())) - as Box> + as + Box> }) .collect(), } 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); diff --git a/src/window/icon.rs b/src/window/icon.rs index 0d27b00e..287538b1 100644 --- a/src/window/icon.rs +++ b/src/window/icon.rs @@ -97,23 +97,25 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::InvalidData { byte_count } => { - write!(f, - "The provided RGBA data (with length {:?}) isn't divisble by \ + write!( + f, + "The provided RGBA data (with length {:?}) isn't divisble by \ 4. Therefore, it cannot be safely interpreted as 32bpp RGBA \ pixels.", - byte_count, - ) + byte_count, + ) } Error::DimensionsMismatch { width, height, pixel_count, } => { - write!(f, - "The number of RGBA pixels ({:?}) does not match the provided \ + write!( + f, + "The number of RGBA pixels ({:?}) does not match the provided \ dimensions ({:?}x{:?}).", - pixel_count, width, height, - ) + pixel_count, width, height, + ) } Error::OsError(e) => write!( f, diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 45b63701..78f70dd8 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -134,11 +134,14 @@ impl Pipeline { bind_group_layouts: &[&constant_layout, &texture_layout], }); - let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor { - label: Some("iced_wgpu::image::shader"), - source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!("shader/image.wgsl"))), - flags: wgpu::ShaderFlags::all() - }); + let shader = + device.create_shader_module(&wgpu::ShaderModuleDescriptor { + label: Some("iced_wgpu::image::shader"), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( + include_str!("shader/image.wgsl"), + )), + flags: wgpu::ShaderFlags::all(), + }); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { @@ -422,16 +425,14 @@ impl Pipeline { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("iced_wgpu::image render pass"), - color_attachments: &[ - wgpu::RenderPassColorAttachment { - view: target, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: true, - }, + color_attachments: &[wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, }, - ], + }], depth_stencil_attachment: None, }); diff --git a/wgpu/src/shader/quad.wgsl b/wgpu/src/shader/quad.wgsl index 3c0aa9f2..bae05a0c 100644 --- a/wgpu/src/shader/quad.wgsl +++ b/wgpu/src/shader/quad.wgsl @@ -119,5 +119,5 @@ fn fs_main( input.border_radius + 0.5, dist); - return vec4(radius_alpha, radius_alpha, radius_alpha, radius_alpha); + return vec4(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha); } diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index d155f3ec..cc19539a 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -74,11 +74,14 @@ impl Blit { bind_group_layouts: &[&constant_layout, &texture_layout], }); - let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor { - label: Some("iced_wgpu::triangle::blit_shader"), - source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!("../shader/blit.wgsl"))), - flags: wgpu::ShaderFlags::all() - }); + let shader = + device.create_shader_module(&wgpu::ShaderModuleDescriptor { + label: Some("iced_wgpu::triangle::blit_shader"), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( + include_str!("../shader/blit.wgsl"), + )), + flags: wgpu::ShaderFlags::all(), + }); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { @@ -176,16 +179,14 @@ impl Blit { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("iced_wgpu::triangle::msaa render pass"), - color_attachments: &[ - wgpu::RenderPassColorAttachment { - view: target, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: true, - }, + color_attachments: &[wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, }, - ], + }], depth_stencil_attachment: None, }); -- cgit From 59c2500c55430ac715b846017f79ad93a58fc3a5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Mon, 3 May 2021 15:42:02 +0700 Subject: Fix `Scrollable` scrollbar being rendered behind contents ... by issuing a new clip layer just for the scrollbar itself. --- examples/scrollable/Cargo.toml | 2 +- examples/scrollable/src/main.rs | 41 ++++++++++++++++++++++++++------------- graphics/src/widget/scrollable.rs | 10 +++++++++- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/examples/scrollable/Cargo.toml b/examples/scrollable/Cargo.toml index 12753fb6..08502458 100644 --- a/examples/scrollable/Cargo.toml +++ b/examples/scrollable/Cargo.toml @@ -6,4 +6,4 @@ edition = "2018" publish = false [dependencies] -iced = { path = "../.." } +iced = { path = "../..", features = ["debug"] } diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index 8dd2e20c..a570f0f6 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -1,8 +1,8 @@ mod style; use iced::{ - scrollable, Column, Container, Element, Length, Radio, Row, Rule, Sandbox, - Scrollable, Settings, Space, Text, + button, scrollable, Button, Column, Container, Element, Length, Radio, Row, + Rule, Sandbox, Scrollable, Settings, Space, Text, }; pub fn main() -> iced::Result { @@ -63,12 +63,14 @@ impl Sandbox for ScrollableDemo { variants .iter_mut() .map(|variant| { - let mut scrollable = Scrollable::new(&mut variant.state) - .padding(10) - .width(Length::Fill) - .height(Length::Fill) - .style(*theme) - .push(Text::new(variant.title)); + let mut scrollable = + Scrollable::new(&mut variant.scrollable) + .padding(10) + .spacing(10) + .width(Length::Fill) + .height(Length::Fill) + .style(*theme) + .push(Text::new(variant.title)); if let Some(scrollbar_width) = variant.scrollbar_width { scrollable = scrollable @@ -108,6 +110,14 @@ impl Sandbox for ScrollableDemo { .push(Space::with_height(Length::Units(1200))) .push(Text::new("Middle")) .push(Space::with_height(Length::Units(1200))) + .push( + Button::new( + &mut variant.button, + Text::new("I am a button"), + ) + .width(Length::Fill) + .padding(10), + ) .push(Text::new("The End.")); Container::new(scrollable) @@ -142,7 +152,8 @@ impl Sandbox for ScrollableDemo { /// A version of a scrollable struct Variant { title: &'static str, - state: scrollable::State, + scrollable: scrollable::State, + button: button::State, scrollbar_width: Option, scrollbar_margin: Option, scroller_width: Option, @@ -153,28 +164,32 @@ impl Variant { vec![ Self { title: "Default Scrollbar", - state: scrollable::State::new(), + scrollable: scrollable::State::new(), + button: button::State::new(), scrollbar_width: None, scrollbar_margin: None, scroller_width: None, }, Self { title: "Slimmed & Margin", - state: scrollable::State::new(), + scrollable: scrollable::State::new(), + button: button::State::new(), scrollbar_width: Some(4), scrollbar_margin: Some(3), scroller_width: Some(4), }, Self { title: "Wide Scroller", - state: scrollable::State::new(), + scrollable: scrollable::State::new(), + button: button::State::new(), scrollbar_width: Some(4), scrollbar_margin: None, scroller_width: Some(10), }, Self { title: "Narrow Scroller", - state: scrollable::State::new(), + scrollable: scrollable::State::new(), + button: button::State::new(), scrollbar_width: Some(10), scrollbar_margin: None, scroller_width: Some(4), diff --git a/graphics/src/widget/scrollable.rs b/graphics/src/widget/scrollable.rs index 57065ba2..2220e4b8 100644 --- a/graphics/src/widget/scrollable.rs +++ b/graphics/src/widget/scrollable.rs @@ -134,8 +134,16 @@ where Primitive::None }; + let scroll = Primitive::Clip { + bounds, + offset: Vector::new(0, 0), + content: Box::new(Primitive::Group { + primitives: vec![scrollbar, scroller], + }), + }; + Primitive::Group { - primitives: vec![clip, scrollbar, scroller], + primitives: vec![clip, scroll], } } else { content -- cgit From 3840b75beaa3925f3438a7b40f01aaac221b8206 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Wed, 5 May 2021 14:33:03 +0700 Subject: Provide `compatible_surface` in `iced_wgpu::Compositor` --- graphics/src/window/compositor.rs | 5 ++++- rustfmt.toml | 1 + wgpu/src/window/compositor.rs | 23 ++++++++++++++++++----- winit/src/application.rs | 4 ++-- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/graphics/src/window/compositor.rs b/graphics/src/window/compositor.rs index 0bc8cbc8..7d5d789b 100644 --- a/graphics/src/window/compositor.rs +++ b/graphics/src/window/compositor.rs @@ -17,7 +17,10 @@ pub trait Compositor: Sized { type SwapChain; /// Creates a new [`Compositor`]. - fn new(settings: Self::Settings) -> Result<(Self, Self::Renderer), Error>; + fn new( + settings: Self::Settings, + compatible_window: Option<&W>, + ) -> Result<(Self, Self::Renderer), Error>; /// Crates a new [`Surface`] for the given window. /// diff --git a/rustfmt.toml b/rustfmt.toml index d979d317..fa50ab92 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1,2 @@ max_width=80 +edition="2018" diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index fdd648e8..aa873df8 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -21,9 +21,16 @@ impl Compositor { /// Requests a new [`Compositor`] with the given [`Settings`]. /// /// Returns `None` if no compatible graphics adapter could be found. - pub async fn request(settings: Settings) -> Option { + pub async fn request( + settings: Settings, + compatible_window: Option<&W>, + ) -> Option { let instance = wgpu::Instance::new(settings.internal_backend); + #[allow(unsafe_code)] + let compatible_surface = compatible_window + .map(|window| unsafe { instance.create_surface(window) }); + let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { power_preference: if settings.antialiasing.is_none() { @@ -31,7 +38,7 @@ impl Compositor { } else { wgpu::PowerPreference::HighPerformance }, - compatible_surface: None, + compatible_surface: compatible_surface.as_ref(), }) .await?; @@ -77,9 +84,15 @@ impl iced_graphics::window::Compositor for Compositor { type Surface = wgpu::Surface; type SwapChain = wgpu::SwapChain; - fn new(settings: Self::Settings) -> Result<(Self, Renderer), Error> { - let compositor = futures::executor::block_on(Self::request(settings)) - .ok_or(Error::AdapterNotFound)?; + fn new( + settings: Self::Settings, + compatible_window: Option<&W>, + ) -> Result<(Self, Renderer), Error> { + let compositor = futures::executor::block_on(Self::request( + settings, + compatible_window, + )) + .ok_or(Error::AdapterNotFound)?; let backend = compositor.create_backend(); diff --git a/winit/src/application.rs b/winit/src/application.rs index 106d5218..b1d5f418 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -118,8 +118,6 @@ where let mut debug = Debug::new(); debug.startup_started(); - let (compositor, renderer) = C::new(compositor_settings)?; - let event_loop = EventLoop::with_user_event(); let mut runtime = { @@ -150,6 +148,8 @@ where .build(&event_loop) .map_err(Error::WindowCreationFailed)?; + let (compositor, renderer) = C::new(compositor_settings, Some(&window))?; + let (mut sender, receiver) = mpsc::unbounded(); let mut instance = Box::pin(run_instance::( -- 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(+) 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(-) 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(-) 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 77a17cde83c9bdb8a61b871c7a15303f13e8d781 Mon Sep 17 00:00:00 2001 From: Zak Date: Tue, 11 May 2021 22:41:55 +0100 Subject: This commit optimizes the function used to converg rgba pixels into bgra pixels. --- wgpu/src/image/vector.rs | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index ab0f67d0..8c7de617 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -1,7 +1,8 @@ -use crate::image::atlas::{self, Atlas}; use iced_native::svg; use std::collections::{HashMap, HashSet}; +use crate::image::atlas::{self, Atlas}; + pub enum Svg { Loaded(usvg::Tree), NotFound, @@ -111,26 +112,13 @@ impl Cache { let width = img.width(); let height = img.height(); - let mut rgba = img.take().into_iter(); - - // TODO: Perform conversion in the GPU - let bgra: Vec = std::iter::from_fn(move || { - use std::iter::once; - - let r = rgba.next()?; - let g = rgba.next()?; - let b = rgba.next()?; - let a = rgba.next()?; - - Some(once(b).chain(once(g)).chain(once(r)).chain(once(a))) - }) - .flatten() - .collect(); + let mut rgba = img.take(); + rgba.chunks_exact_mut(4).for_each(|rgba| rgba.swap(0, 2)); let allocation = texture_atlas.upload( width, height, - bytemuck::cast_slice(bgra.as_slice()), + bytemuck::cast_slice(rgba.as_slice()), device, encoder, )?; -- cgit From e6db43987072e62ae146e9b6fe4bc2ef0e519073 Mon Sep 17 00:00:00 2001 From: Downtime Date: Thu, 13 May 2021 16:46:20 +0800 Subject: Add a primary backend that can be set Add a primary backend that can be set --- wgpu/src/settings.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index abc404dc..4a1bb322 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -78,6 +78,7 @@ fn backend_from_env() -> Option { "dx11" => wgpu::BackendBit::DX11, "gl" => wgpu::BackendBit::GL, "webgpu" => wgpu::BackendBit::BROWSER_WEBGPU, + "primary" => wgpu::BackendBit::PRIMARY, other => panic!("Unknown backend: {}", other), } }) -- cgit From 5f1b880521efb125f2f85137cc2e5ff1e66a5f42 Mon Sep 17 00:00:00 2001 From: Riccardo D'Ambrosio Date: Thu, 13 May 2021 11:04:09 -0400 Subject: Change examples to point to 0.3 examples, not 0.2 --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 812a50c1..6a029afd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,7 +30,7 @@ //! [windowing shell]: https://github.com/hecrj/iced/tree/master/winit //! [`dodrio`]: https://github.com/fitzgen/dodrio //! [web runtime]: https://github.com/hecrj/iced/tree/master/web -//! [examples]: https://github.com/hecrj/iced/tree/0.2/examples +//! [examples]: https://github.com/hecrj/iced/tree/0.3/examples //! [repository]: https://github.com/hecrj/iced //! //! # Overview -- cgit From 88defb65caa70688c47a9d4d2d4f51dd97814e01 Mon Sep 17 00:00:00 2001 From: Downtime Date: Fri, 14 May 2021 21:21:25 +0800 Subject: update doc --- wgpu/src/settings.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index 4a1bb322..6c97d895 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -47,6 +47,7 @@ impl Settings { /// - `dx11` /// - `gl` /// - `webgpu` + /// - `primary` pub fn from_env() -> Self { Settings { internal_backend: backend_from_env() -- 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(-) 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(-) 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(-) 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` --- futures/src/subscription.rs | 3 +-- native/src/widget/pane_grid.rs | 9 ++++----- wgpu/src/triangle.rs | 4 +--- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs index aa81f844..27d2d295 100644 --- a/futures/src/subscription.rs +++ b/futures/src/subscription.rs @@ -78,8 +78,7 @@ where .drain(..) .map(|recipe| { Box::new(With::new(recipe, value.clone())) - as - Box> + as Box> }) .collect(), } 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 { diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 67f14e4d..21d0e0ab 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -263,8 +263,7 @@ impl Pipeline { Uniforms, >( ) - as - u64), + as u64), }, }], }); @@ -334,7 +333,6 @@ impl Pipeline { let uniforms = bytemuck::cast_slice(&uniforms); - if let Some(uniforms_size) = wgpu::BufferSize::new(uniforms.len() as u64) { -- cgit From cf6af4c2560f5996bc533402ac3e4289c0c94702 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Wed, 19 May 2021 17:11:51 +0700 Subject: Use latest `wgpu` releases instead of patched sources --- Cargo.toml | 12 ------------ wgpu/Cargo.toml | 4 ++-- wgpu/src/image.rs | 12 +++++++----- wgpu/src/quad.rs | 4 ++-- wgpu/src/triangle.rs | 36 ++++++++++++++++++++---------------- 5 files changed, 31 insertions(+), 37 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 19f25d9d..6d894eba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,18 +11,6 @@ readme = "README.md" keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] -[patch."https://github.com/gfx-rs/wgpu"] -wgpu-types = { git = "https://github.com/Dispersia/wgpu" } -wgpu-core = { git = "https://github.com/Dispersia/wgpu" } - -[patch."https://github.com/gfx-rs/wgpu-rs"] -wgpu = { git = "https://github.com/Dispersia/wgpu-rs" } - -[patch.crates-io] -wgpu-types = { git = "https://github.com/Dispersia/wgpu" } -wgpu-core = { git = "https://github.com/Dispersia/wgpu" } -wgpu = { git = "https://github.com/Dispersia/wgpu-rs" } - [features] default = ["wgpu", "default_system_font"] # Enables the `iced_wgpu` renderer diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index f44b3874..3eacdd0f 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -26,8 +26,8 @@ qr_code = ["iced_graphics/qr_code"] default_system_font = ["iced_graphics/font-source"] [dependencies] -wgpu = {git="https://github.com/Dispersia/wgpu-rs"} -wgpu_glyph = {git="https://github.com/Dispersia/wgpu_glyph", branch = "update-wgpu"} +wgpu = "0.8" +wgpu_glyph = "0.12" glyph_brush = "0.7" raw-window-handle = "0.3" log = "0.4" diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 78f70dd8..cd7b3d23 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -97,11 +97,13 @@ impl Pipeline { entries: &[ wgpu::BindGroupEntry { binding: 0, - resource: wgpu::BindingResource::Buffer { - buffer: &uniforms_buffer, - offset: 0, - size: None, - }, + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer: &uniforms_buffer, + offset: 0, + size: None, + }, + ), }, wgpu::BindGroupEntry { binding: 1, diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 458679e4..0f9d7b99 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -47,11 +47,11 @@ impl Pipeline { layout: &constant_layout, entries: &[wgpu::BindGroupEntry { binding: 0, - resource: wgpu::BindingResource::Buffer { + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: &constants_buffer, offset: 0, size: None, - }, + }), }], }); diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 21d0e0ab..8636b331 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -110,13 +110,17 @@ impl Pipeline { layout: &constants_layout, entries: &[wgpu::BindGroupEntry { binding: 0, - resource: wgpu::BindingResource::Buffer { - buffer: &constants_buffer.raw, - offset: 0, - size: wgpu::BufferSize::new( - std::mem::size_of::() as u64, - ), - }, + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer: &constants_buffer.raw, + offset: 0, + size: wgpu::BufferSize::new(std::mem::size_of::< + Uniforms, + >( + ) + as u64), + }, + ), }], }); @@ -256,15 +260,15 @@ impl Pipeline { layout: &self.constants_layout, entries: &[wgpu::BindGroupEntry { binding: 0, - resource: wgpu::BindingResource::Buffer { - buffer: &self.uniforms_buffer.raw, - offset: 0, - size: wgpu::BufferSize::new(std::mem::size_of::< - Uniforms, - >( - ) - as u64), - }, + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer: &self.uniforms_buffer.raw, + offset: 0, + size: wgpu::BufferSize::new( + std::mem::size_of::() as u64, + ), + }, + ), }], }); } -- cgit From d91422e345d30f0d33d737a3be8ad1c90f5d14b9 Mon Sep 17 00:00:00 2001 From: Dispersia Date: Wed, 19 May 2021 08:09:03 -0700 Subject: temporary up --- wgpu/src/quad.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 0f9d7b99..9b87ef81 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -28,7 +28,7 @@ impl Pipeline { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: wgpu::BufferSize::new( - mem::size_of::() as u64, + mem::size_of::() as wgpu::BufferAddress, ), }, count: None, @@ -37,7 +37,7 @@ impl Pipeline { let constants_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("iced_wgpu::quad uniforms buffer"), - size: mem::size_of::() as u64, + size: mem::size_of::() as wgpu::BufferAddress, usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, mapped_at_creation: false, }); @@ -47,11 +47,7 @@ impl Pipeline { layout: &constant_layout, entries: &[wgpu::BindGroupEntry { binding: 0, - resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { - buffer: &constants_buffer, - offset: 0, - size: None, - }), + resource: constants_buffer.as_entire_binding(), }], }); -- cgit From b40c44164646e853239d1b76fd8e4768eb21d9ac Mon Sep 17 00:00:00 2001 From: Dispersia Date: Wed, 19 May 2021 21:04:47 -0700 Subject: Add padding to quad to fix alignment issue --- wgpu/src/quad.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 9b87ef81..b91e3e89 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -305,6 +305,9 @@ const MAX_INSTANCES: usize = 100_000; struct Uniforms { transform: [f32; 16], scale: f32, + // Uniforms must be aligned to their largest member, + // this uses a mat4x4 which aligns to 16, so align to that + _padding: [f32; 3], } impl Uniforms { @@ -312,6 +315,7 @@ impl Uniforms { Self { transform: *transformation.as_ref(), scale, + _padding: [0.0; 3] } } } @@ -321,6 +325,7 @@ impl Default for Uniforms { Self { transform: *Transformation::identity().as_ref(), scale: 1.0, + _padding: [0.0; 3] } } } -- cgit From 2d549d806cd9ff1d7b7b237d818cd24c84957c83 Mon Sep 17 00:00:00 2001 From: Dispersia Date: Wed, 19 May 2021 21:09:19 -0700 Subject: Remove padding from triangle --- wgpu/src/triangle.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 8636b331..141fc9af 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -425,18 +425,12 @@ impl Pipeline { #[derive(Debug, Clone, Copy, Zeroable, Pod)] struct Uniforms { transform: [f32; 16], - // We need to align this to 256 bytes to please `wgpu`... - // TODO: Be smarter and stop wasting memory! - _padding_a: [f32; 32], - _padding_b: [f32; 16], } impl Default for Uniforms { fn default() -> Self { Self { transform: *Transformation::identity().as_ref(), - _padding_a: [0.0; 32], - _padding_b: [0.0; 16], } } } @@ -445,8 +439,6 @@ impl From for Uniforms { fn from(transformation: Transformation) -> Uniforms { Self { transform: transformation.into(), - _padding_a: [0.0; 32], - _padding_b: [0.0; 16], } } } -- cgit From a70715ad9e41bf133e8e37d43633ffa84ae211b9 Mon Sep 17 00:00:00 2001 From: Dispersia Date: Wed, 19 May 2021 22:07:27 -0700 Subject: run format --- wgpu/src/quad.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index b91e3e89..6f221ca3 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -315,7 +315,7 @@ impl Uniforms { Self { transform: *transformation.as_ref(), scale, - _padding: [0.0; 3] + _padding: [0.0; 3], } } } @@ -325,7 +325,7 @@ impl Default for Uniforms { Self { transform: *Transformation::identity().as_ref(), scale: 1.0, - _padding: [0.0; 3] + _padding: [0.0; 3], } } } -- cgit From 0772310c4f6ab1ff4b9771011c04b6ece0b84df3 Mon Sep 17 00:00:00 2001 From: Dispersia Date: Thu, 20 May 2021 23:10:22 -0700 Subject: Fix duplicating fragment position --- wgpu/src/shader/quad.wgsl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/wgpu/src/shader/quad.wgsl b/wgpu/src/shader/quad.wgsl index bae05a0c..80d733ab 100644 --- a/wgpu/src/shader/quad.wgsl +++ b/wgpu/src/shader/quad.wgsl @@ -80,8 +80,7 @@ fn distance_alg( [[stage(fragment)]] fn fs_main( - input: VertexOutput, - [[builtin(position)]] coord: vec4 + input: VertexOutput ) -> [[location(0)]] vec4 { var mixed_color: vec4 = input.color; @@ -92,7 +91,7 @@ fn fs_main( ); var internal_distance: f32 = distance_alg( - vec2(coord.x, coord.y), + vec2(input.position.x, input.position.y), input.pos + vec2(input.border_width, input.border_width), input.scale - vec2(input.border_width * 2.0, input.border_width * 2.0), internal_border @@ -108,7 +107,7 @@ fn fs_main( } var dist: f32 = distance_alg( - vec2(coord.x, coord.y), + vec2(input.position.x, input.position.y), input.pos, input.scale, input.border_radius -- cgit From ebec84ea7c54fffb5bc9645326782d84a30a8197 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Fri, 21 May 2021 20:29:17 +0700 Subject: Revert "Remove padding from triangle" This reverts commit 2d549d806cd9ff1d7b7b237d818cd24c84957c83. --- wgpu/src/triangle.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 141fc9af..8636b331 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -425,12 +425,18 @@ impl Pipeline { #[derive(Debug, Clone, Copy, Zeroable, Pod)] struct Uniforms { transform: [f32; 16], + // We need to align this to 256 bytes to please `wgpu`... + // TODO: Be smarter and stop wasting memory! + _padding_a: [f32; 32], + _padding_b: [f32; 16], } impl Default for Uniforms { fn default() -> Self { Self { transform: *Transformation::identity().as_ref(), + _padding_a: [0.0; 32], + _padding_b: [0.0; 16], } } } @@ -439,6 +445,8 @@ impl From for Uniforms { fn from(transformation: Transformation) -> Uniforms { Self { transform: transformation.into(), + _padding_a: [0.0; 32], + _padding_b: [0.0; 16], } } } -- cgit From 4cbc34524598756ce18cb25a664bc6f256d42851 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Fri, 21 May 2021 20:34:08 +0700 Subject: Use `FilterMode::Nearest` in `triangle::msaa` --- wgpu/src/triangle/msaa.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index cc19539a..c099d518 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -20,9 +20,9 @@ impl Blit { address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - mipmap_filter: wgpu::FilterMode::Linear, + mag_filter: wgpu::FilterMode::Nearest, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, ..Default::default() }); -- cgit From e6f8b32583ae00eb77d6e10e3a43c899ecadc88f Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Mon, 24 May 2021 14:28:52 -0500 Subject: Example: Add close button to pane grid controls Refactors the state data structure to hold content and controls in separate fields. Adds a new button style for the control button. --- examples/pane_grid/src/main.rs | 62 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 4b87a568..7c0d6582 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -11,7 +11,7 @@ pub fn main() -> iced::Result { } struct Example { - panes: pane_grid::State, + panes: pane_grid::State, panes_created: usize, focus: Option, } @@ -34,7 +34,7 @@ impl Application for Example { type Flags = (); fn new(_flags: ()) -> (Self, Command) { - let (panes, _) = pane_grid::State::new(Content::new(0)); + let (panes, _) = pane_grid::State::new(Pane::new(0)); ( Example { @@ -60,7 +60,7 @@ impl Application for Example { let result = self.panes.split( axis, &pane, - Content::new(self.panes_created), + Pane::new(self.panes_created), ); if let Some((pane, _)) = result { @@ -74,7 +74,7 @@ impl Application for Example { let result = self.panes.split( axis, &pane, - Content::new(self.panes_created), + Pane::new(self.panes_created), ); if let Some((pane, _)) = result { @@ -143,12 +143,12 @@ impl Application for Example { let focus = self.focus; let total_panes = self.panes.len(); - let pane_grid = PaneGrid::new(&mut self.panes, |pane, content| { - let is_focused = focus == Some(pane); + let pane_grid = PaneGrid::new(&mut self.panes, |id, pane| { + let is_focused = focus == Some(id); let title = Row::with_children(vec![ Text::new("Pane").into(), - Text::new(content.id.to_string()) + Text::new(pane.content.id.to_string()) .color(if is_focused { PANE_ID_COLOR_FOCUSED } else { @@ -159,10 +159,11 @@ impl Application for Example { .spacing(5); let title_bar = pane_grid::TitleBar::new(title) + .controls(pane.controls.view(id, total_panes)) .padding(10) .style(style::TitleBar { is_focused }); - pane_grid::Content::new(content.view(pane, total_panes)) + pane_grid::Content::new(pane.content.view(id, total_panes)) .title_bar(title_bar) .style(style::Pane { is_focused }) }) @@ -212,6 +213,11 @@ fn handle_hotkey(key_code: keyboard::KeyCode) -> Option { } } +struct Pane { + pub content: Content, + pub controls: Controls, +} + struct Content { id: usize, scroll: scrollable::State, @@ -220,6 +226,19 @@ struct Content { close: button::State, } +struct Controls { + close: button::State, +} + +impl Pane { + fn new(id: usize) -> Self { + Self { + content: Content::new(id), + controls: Controls::new(), + } + } +} + impl Content { fn new(id: usize) -> Self { Content { @@ -297,7 +316,31 @@ impl Content { } } +impl Controls { + fn new() -> Self { + Self { + close: button::State::new(), + } + } + + pub fn view( + &mut self, + pane: pane_grid::Pane, + total_panes: usize, + ) -> Element { + let mut button = + Button::new(&mut self.close, Text::new("Close").size(14)) + .style(style::Button::Control) + .padding(3); + if total_panes > 1 { + button = button.on_press(Message::Close(pane)); + } + button.into() + } +} + mod style { + use crate::PANE_ID_COLOR_FOCUSED; use iced::{button, container, Background, Color, Vector}; const SURFACE: Color = Color::from_rgb( @@ -359,6 +402,7 @@ mod style { pub enum Button { Primary, Destructive, + Control, } impl button::StyleSheet for Button { @@ -368,6 +412,7 @@ mod style { Button::Destructive => { (None, Color::from_rgb8(0xFF, 0x47, 0x47)) } + Button::Control => (Some(PANE_ID_COLOR_FOCUSED), Color::WHITE), }; button::Style { @@ -388,6 +433,7 @@ mod style { a: 0.2, ..active.text_color }), + Button::Control => Some(PANE_ID_COLOR_FOCUSED), }; button::Style { -- cgit From 1a2fd4e743fe2b5237a4b51fa0ab2ddc660dfd5a Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Mon, 24 May 2021 15:53:20 -0500 Subject: Example: Add Pin button to prevent closing a pane Functionality will not work until PaneGrid implementation is updated to support events within the title area. --- examples/pane_grid/src/main.rs | 50 +++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 7c0d6582..81cf1770 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -24,6 +24,7 @@ enum Message { Clicked(pane_grid::Pane), Dragged(pane_grid::DragEvent), Resized(pane_grid::ResizeEvent), + TogglePin(pane_grid::Pane), Close(pane_grid::Pane), CloseFocused, } @@ -106,6 +107,12 @@ impl Application for Example { self.panes.swap(&pane, &target); } Message::Dragged(_) => {} + Message::TogglePin(pane) => { + if let Some(Pane { is_pinned, .. }) = self.panes.get_mut(&pane) + { + *is_pinned = !*is_pinned; + } + } Message::Close(pane) => { if let Some((_, sibling)) = self.panes.close(&pane) { self.focus = Some(sibling); @@ -113,8 +120,14 @@ impl Application for Example { } Message::CloseFocused => { if let Some(pane) = self.focus { - if let Some((_, sibling)) = self.panes.close(&pane) { - self.focus = Some(sibling); + if let Some(Pane { is_pinned, .. }) = self.panes.get(&pane) + { + if !is_pinned { + if let Some((_, sibling)) = self.panes.close(&pane) + { + self.focus = Some(sibling); + } + } } } } @@ -146,7 +159,15 @@ impl Application for Example { let pane_grid = PaneGrid::new(&mut self.panes, |id, pane| { let is_focused = focus == Some(id); + let text = if pane.is_pinned { "Unpin" } else { "Pin" }; + let pin_button = + Button::new(&mut pane.pin_button, Text::new(text).size(14)) + .on_press(Message::TogglePin(id)) + .style(style::Button::Pin) + .padding(3); + let title = Row::with_children(vec![ + pin_button.into(), Text::new("Pane").into(), Text::new(pane.content.id.to_string()) .color(if is_focused { @@ -159,13 +180,17 @@ impl Application for Example { .spacing(5); let title_bar = pane_grid::TitleBar::new(title) - .controls(pane.controls.view(id, total_panes)) + .controls(pane.controls.view(id, total_panes, pane.is_pinned)) .padding(10) .style(style::TitleBar { is_focused }); - pane_grid::Content::new(pane.content.view(id, total_panes)) - .title_bar(title_bar) - .style(style::Pane { is_focused }) + pane_grid::Content::new(pane.content.view( + id, + total_panes, + pane.is_pinned, + )) + .title_bar(title_bar) + .style(style::Pane { is_focused }) }) .width(Length::Fill) .height(Length::Fill) @@ -214,6 +239,8 @@ fn handle_hotkey(key_code: keyboard::KeyCode) -> Option { } struct Pane { + pub is_pinned: bool, + pub pin_button: button::State, pub content: Content, pub controls: Controls, } @@ -233,6 +260,8 @@ struct Controls { impl Pane { fn new(id: usize) -> Self { Self { + is_pinned: false, + pin_button: button::State::new(), content: Content::new(id), controls: Controls::new(), } @@ -253,6 +282,7 @@ impl Content { &mut self, pane: pane_grid::Pane, total_panes: usize, + is_pinned: bool, ) -> Element { let Content { scroll, @@ -292,7 +322,7 @@ impl Content { style::Button::Primary, )); - if total_panes > 1 { + if total_panes > 1 && !is_pinned { controls = controls.push(button( close, "Close", @@ -327,12 +357,13 @@ impl Controls { &mut self, pane: pane_grid::Pane, total_panes: usize, + is_pinned: bool, ) -> Element { let mut button = Button::new(&mut self.close, Text::new("Close").size(14)) .style(style::Button::Control) .padding(3); - if total_panes > 1 { + if total_panes > 1 && !is_pinned { button = button.on_press(Message::Close(pane)); } button.into() @@ -403,6 +434,7 @@ mod style { Primary, Destructive, Control, + Pin, } impl button::StyleSheet for Button { @@ -413,6 +445,7 @@ mod style { (None, Color::from_rgb8(0xFF, 0x47, 0x47)) } Button::Control => (Some(PANE_ID_COLOR_FOCUSED), Color::WHITE), + Button::Pin => (Some(ACTIVE), Color::WHITE), }; button::Style { @@ -434,6 +467,7 @@ mod style { ..active.text_color }), Button::Control => Some(PANE_ID_COLOR_FOCUSED), + Button::Pin => Some(HOVERED), }; button::Style { -- 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. --- graphics/src/widget/pane_grid.rs | 6 +++--- native/src/widget/pane_grid/title_bar.rs | 33 ++++++++++++++++++++++---------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/graphics/src/widget/pane_grid.rs b/graphics/src/widget/pane_grid.rs index d06f8c6c..93b4b672 100644 --- a/graphics/src/widget/pane_grid.rs +++ b/graphics/src/widget/pane_grid.rs @@ -218,10 +218,10 @@ where body_primitive, ], }, - if is_over_pick_area { - mouse::Interaction::Grab - } else if title_bar_interaction > body_interaction { + if title_bar_interaction > body_interaction { title_bar_interaction + } else if is_over_pick_area { + mouse::Interaction::Grab } else { body_interaction }, 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(-) 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 f04bc94b80942857f8cc1e0d39658bd1eb633a06 Mon Sep 17 00:00:00 2001 From: Quentin Boyer Date: Thu, 27 May 2021 14:22:11 +0200 Subject: allow disabling drag and drop on windows --- winit/src/settings.rs | 2 ++ winit/src/settings/windows.rs | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 663fa07a..941d88ce 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -100,6 +100,8 @@ impl Window { if let Some(parent) = self.platform_specific.parent { window_builder = window_builder.with_parent_window(parent); } + window_builder = window_builder + .with_drag_and_drop(self.platform_specific.drag_and_drop); } window_builder diff --git a/winit/src/settings/windows.rs b/winit/src/settings/windows.rs index 76b8d067..68dadefd 100644 --- a/winit/src/settings/windows.rs +++ b/winit/src/settings/windows.rs @@ -2,8 +2,19 @@ //! Platform specific settings for Windows. /// The platform specific window settings of an application. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct PlatformSpecific { /// Parent Window pub parent: Option, + /// Drap and Drop support + pub drag_and_drop: bool, +} + +impl Default for PlatformSpecific { + fn default() -> Self { + Self { + parent: None, + drag_and_drop: true, + } + } } -- cgit From 662889bb8355d9e388d5eda6c69bf065f156884a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Tue, 1 Jun 2021 18:32:56 +0700 Subject: Implement `Default` for `Style` in `rule` --- style/src/rule.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/style/src/rule.rs b/style/src/rule.rs index 5021340b..be4c86d1 100644 --- a/style/src/rule.rs +++ b/style/src/rule.rs @@ -79,6 +79,17 @@ pub struct Style { pub fill_mode: FillMode, } +impl std::default::Default for Style { + fn default() -> Self { + Style { + color: [0.6, 0.6, 0.6, 0.51].into(), + width: 1, + radius: 0.0, + fill_mode: FillMode::Percent(90.0), + } + } +} + /// A set of rules that dictate the style of a rule. pub trait StyleSheet { /// Produces the style of a rule. @@ -89,12 +100,7 @@ struct Default; impl StyleSheet for Default { fn style(&self) -> Style { - Style { - color: [0.6, 0.6, 0.6, 0.51].into(), - width: 1, - radius: 0.0, - fill_mode: FillMode::Percent(90.0), - } + Style::default() } } -- cgit From 903570846e51df260925c81851d733dce5955b41 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Tue, 1 Jun 2021 18:57:36 +0700 Subject: Fix documentation of `PlatformSpecific` settings --- winit/src/settings/windows.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/winit/src/settings/windows.rs b/winit/src/settings/windows.rs index 68dadefd..fc26acd7 100644 --- a/winit/src/settings/windows.rs +++ b/winit/src/settings/windows.rs @@ -4,9 +4,10 @@ /// The platform specific window settings of an application. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct PlatformSpecific { - /// Parent Window + /// Parent window pub parent: Option, - /// Drap and Drop support + + /// Drag and drop support pub drag_and_drop: bool, } -- 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 --- core/src/lib.rs | 2 + core/src/padding.rs | 65 ++++++++++++++++++++++++++++++++ core/src/size.rs | 8 ++-- graphics/src/overlay/menu.rs | 12 +++--- graphics/src/widget/button.rs | 4 +- graphics/src/widget/pick_list.rs | 12 +++--- 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 +++++++++----- web/src/css.rs | 23 ++++++----- web/src/lib.rs | 2 +- web/src/widget/button.rs | 32 +++++++--------- web/src/widget/column.rs | 25 ++++++------ web/src/widget/container.rs | 25 ++++++------ web/src/widget/row.rs | 25 ++++++------ web/src/widget/scrollable.rs | 15 ++++++-- web/src/widget/text_input.rs | 30 +++++++-------- 27 files changed, 339 insertions(+), 195 deletions(-) create mode 100644 core/src/padding.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index f2d21a5f..6453d599 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -22,6 +22,7 @@ mod background; mod color; mod font; mod length; +mod padding; mod point; mod rectangle; mod size; @@ -32,6 +33,7 @@ pub use background::Background; pub use color::Color; pub use font::Font; pub use length::Length; +pub use padding::Padding; pub use point::Point; pub use rectangle::Rectangle; pub use size::Size; diff --git a/core/src/padding.rs b/core/src/padding.rs new file mode 100644 index 00000000..083f7eab --- /dev/null +++ b/core/src/padding.rs @@ -0,0 +1,65 @@ +/// An amount of space to pad for each side of a box +#[derive(Debug, Hash, Copy, Clone)] +pub struct Padding { + /// Top padding + pub top: u16, + /// Right padding + pub right: u16, + /// Bottom padding + pub bottom: u16, + /// Left padding + pub left: u16, +} + +impl Padding { + /// Padding of zero + pub const ZERO: Padding = Padding { + top: 0, + right: 0, + bottom: 0, + left: 0, + }; + + /// Create a Padding that is equal on all sides + pub const fn new(padding: u16) -> Padding { + Padding { + top: padding, + right: padding, + bottom: padding, + left: padding, + } + } +} + +impl std::convert::From for Padding { + fn from(p: u16) -> Self { + Padding { + top: p, + right: p, + bottom: p, + left: p, + } + } +} + +impl std::convert::From<[u16; 2]> for Padding { + fn from(p: [u16; 2]) -> Self { + Padding { + top: p[0], + right: p[1], + bottom: p[0], + left: p[1], + } + } +} + +impl std::convert::From<[u16; 4]> for Padding { + fn from(p: [u16; 4]) -> Self { + Padding { + top: p[0], + right: p[1], + bottom: p[2], + left: p[3], + } + } +} diff --git a/core/src/size.rs b/core/src/size.rs index 9ea9e686..712caee2 100644 --- a/core/src/size.rs +++ b/core/src/size.rs @@ -1,4 +1,4 @@ -use crate::Vector; +use crate::{Padding, Vector}; use std::f32; /// An amount of space in 2 dimensions. @@ -28,10 +28,10 @@ impl Size { pub const INFINITY: Size = Size::new(f32::INFINITY, f32::INFINITY); /// Increments the [`Size`] to account for the given padding. - pub fn pad(&self, padding: f32) -> Self { + pub fn pad(&self, padding: Padding) -> Self { Size { - width: self.width + padding * 2.0, - height: self.height + padding * 2.0, + width: self.width + (padding.left + padding.right) as f32, + height: self.height + (padding.top + padding.bottom) as f32, } } } diff --git a/graphics/src/overlay/menu.rs b/graphics/src/overlay/menu.rs index ffe998c5..443f1746 100644 --- a/graphics/src/overlay/menu.rs +++ b/graphics/src/overlay/menu.rs @@ -2,8 +2,8 @@ use crate::backend::{self, Backend}; use crate::{Primitive, Renderer}; use iced_native::{ - mouse, overlay, Color, Font, HorizontalAlignment, Point, Rectangle, - VerticalAlignment, + mouse, overlay, Color, Font, HorizontalAlignment, Padding, Point, + Rectangle, VerticalAlignment, }; pub use iced_style::menu::Style; @@ -45,7 +45,7 @@ where viewport: &Rectangle, options: &[T], hovered_option: Option, - padding: u16, + padding: Padding, text_size: u16, font: Font, style: &Style, @@ -53,7 +53,7 @@ where use std::f32; let is_mouse_over = bounds.contains(cursor_position); - let option_height = text_size as usize + padding as usize * 2; + let option_height = (text_size + padding.top + padding.bottom) as usize; let mut primitives = Vec::new(); @@ -72,7 +72,7 @@ where x: bounds.x, y: bounds.y + (option_height * i) as f32, width: bounds.width, - height: f32::from(text_size + padding * 2), + height: f32::from(text_size + padding.top + padding.bottom), }; if is_selected { @@ -88,7 +88,7 @@ where primitives.push(Primitive::Text { content: option.to_string(), bounds: Rectangle { - x: bounds.x + f32::from(padding), + x: bounds.x + padding.left as f32, y: bounds.center_y(), width: f32::INFINITY, ..bounds diff --git a/graphics/src/widget/button.rs b/graphics/src/widget/button.rs index 2e3f78ca..60400ed8 100644 --- a/graphics/src/widget/button.rs +++ b/graphics/src/widget/button.rs @@ -5,7 +5,7 @@ use crate::defaults::{self, Defaults}; use crate::{Backend, Primitive, Renderer}; use iced_native::mouse; use iced_native::{ - Background, Color, Element, Layout, Point, Rectangle, Vector, + Background, Color, Element, Layout, Padding, Point, Rectangle, Vector, }; pub use iced_native::button::State; @@ -21,7 +21,7 @@ impl iced_native::button::Renderer for Renderer where B: Backend, { - const DEFAULT_PADDING: u16 = 5; + const DEFAULT_PADDING: Padding = Padding::new(5); type Style = Box; diff --git a/graphics/src/widget/pick_list.rs b/graphics/src/widget/pick_list.rs index f42a8707..c6fbcf76 100644 --- a/graphics/src/widget/pick_list.rs +++ b/graphics/src/widget/pick_list.rs @@ -2,7 +2,8 @@ use crate::backend::{self, Backend}; use crate::{Primitive, Renderer}; use iced_native::{ - mouse, Font, HorizontalAlignment, Point, Rectangle, VerticalAlignment, + mouse, Font, HorizontalAlignment, Padding, Point, Rectangle, + VerticalAlignment, }; use iced_style::menu; @@ -19,7 +20,7 @@ where { type Style = Box; - const DEFAULT_PADDING: u16 = 5; + const DEFAULT_PADDING: Padding = Padding::new(5); fn menu_style(style: &Box) -> menu::Style { style.menu() @@ -30,7 +31,7 @@ where bounds: Rectangle, cursor_position: Point, selected: Option, - padding: u16, + padding: Padding, text_size: u16, font: Font, style: &Box, @@ -56,7 +57,8 @@ where font: B::ICON_FONT, size: bounds.height * style.icon_size, bounds: Rectangle { - x: bounds.x + bounds.width - f32::from(padding) * 2.0, + x: bounds.x + bounds.width + - f32::from(padding.left + padding.right), y: bounds.center_y(), ..bounds }, @@ -74,7 +76,7 @@ where font, color: style.text_color, bounds: Rectangle { - x: bounds.x + f32::from(padding), + x: bounds.x + f32::from(padding.left), y: bounds.center_y(), ..bounds }, 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( diff --git a/web/src/css.rs b/web/src/css.rs index bdde23f3..66c363f2 100644 --- a/web/src/css.rs +++ b/web/src/css.rs @@ -1,5 +1,5 @@ //! Style your widgets. -use crate::{bumpalo, Align, Background, Color, Length}; +use crate::{bumpalo, Align, Background, Color, Length, Padding}; use std::collections::BTreeMap; @@ -12,9 +12,6 @@ pub enum Rule { /// Container with horizonal distribution Row, - /// Padding of the container - Padding(u16), - /// Spacing between elements Spacing(u16), } @@ -25,7 +22,6 @@ impl Rule { match self { Rule::Column => String::from("c"), Rule::Row => String::from("r"), - Rule::Padding(padding) => format!("p-{}", padding), Rule::Spacing(spacing) => format!("s-{}", spacing), } } @@ -45,13 +41,6 @@ impl Rule { bumpalo::format!(in bump, ".{} {}", class, body).into_bump_str() } - Rule::Padding(padding) => bumpalo::format!( - in bump, - ".{} {{ box-sizing: border-box; padding: {}px }}", - class, - padding - ) - .into_bump_str(), Rule::Spacing(spacing) => bumpalo::format!( in bump, ".c.{} > * {{ margin-bottom: {}px }} \ @@ -170,3 +159,13 @@ pub fn align(align: Align) -> &'static str { Align::End => "flex-end", } } + +/// Returns the style value for the given [`Padding`]. +/// +/// [`Padding`]: struct.Padding.html +pub fn padding(padding: Padding) -> String { + format!( + "{}px {}px {}px {}px", + padding.top, padding.right, padding.bottom, padding.left + ) +} diff --git a/web/src/lib.rs b/web/src/lib.rs index 4c65dfa3..e4271d58 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -75,7 +75,7 @@ pub use element::Element; pub use hasher::Hasher; pub use iced_core::{ keyboard, mouse, Align, Background, Color, Font, HorizontalAlignment, - Length, Point, Rectangle, Size, Vector, VerticalAlignment, + Length, Padding, Point, Rectangle, Size, Vector, VerticalAlignment, }; pub use iced_futures::{executor, futures, Command}; pub use subscription::Subscription; diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs index 7c389a9f..0e75fa3f 100644 --- a/web/src/widget/button.rs +++ b/web/src/widget/button.rs @@ -1,7 +1,7 @@ //! Allow your users to perform actions by pressing a button. //! //! A [`Button`] has some local [`State`]. -use crate::{css, Background, Bus, Css, Element, Length, Widget}; +use crate::{css, Background, Bus, Css, Element, Length, Padding, Widget}; pub use iced_style::button::{Style, StyleSheet}; @@ -30,7 +30,7 @@ pub struct Button<'a, Message> { min_width: u32, #[allow(dead_code)] min_height: u32, - padding: u16, + padding: Padding, style: Box, } @@ -48,7 +48,7 @@ impl<'a, Message> Button<'a, Message> { height: Length::Shrink, min_width: 0, min_height: 0, - padding: 5, + padding: Padding::new(5), style: Default::default(), } } @@ -77,9 +77,14 @@ impl<'a, Message> Button<'a, Message> { 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 } @@ -122,9 +127,6 @@ where // TODO: State-based styling let style = self.style.active(); - let padding_class = - style_sheet.insert(bump, css::Rule::Padding(self.padding)); - let background = match style.background { None => String::from("none"), Some(background) => match background { @@ -132,25 +134,19 @@ where }, }; - let class = { - use dodrio::bumpalo::collections::String; - - String::from_str_in(&padding_class, bump).into_bump_str() - }; - let mut node = button(bump) - .attr("class", class) .attr( "style", bumpalo::format!( in bump, "background: {}; border-radius: {}px; width:{}; \ - min-width: {}; color: {}", + min-width: {}; color: {}; padding: {}", background, style.border_radius, css::length(self.width), css::min_length(self.min_width), - css::color(style.text_color) + css::color(style.text_color), + css::padding(self.padding) ) .into_bump_str(), ) diff --git a/web/src/widget/column.rs b/web/src/widget/column.rs index d832fdcb..a4c99046 100644 --- a/web/src/widget/column.rs +++ b/web/src/widget/column.rs @@ -1,4 +1,4 @@ -use crate::{css, Align, Bus, Css, Element, Length, Widget}; +use crate::{css, Align, Bus, Css, Element, Length, Padding, Widget}; use dodrio::bumpalo; use std::u32; @@ -9,7 +9,7 @@ use std::u32; #[allow(missing_debug_implementations)] pub struct Column<'a, Message> { spacing: u16, - padding: u16, + padding: Padding, width: Length, height: Length, max_width: u32, @@ -28,7 +28,7 @@ impl<'a, Message> Column<'a, Message> { pub fn with_children(children: Vec>) -> Self { Column { spacing: 0, - padding: 0, + padding: Padding::ZERO, width: Length::Fill, height: Length::Shrink, max_width: u32::MAX, @@ -48,9 +48,14 @@ impl<'a, Message> Column<'a, Message> { 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 } @@ -114,23 +119,21 @@ impl<'a, Message> Widget for Column<'a, Message> { let spacing_class = style_sheet.insert(bump, css::Rule::Spacing(self.spacing)); - let padding_class = - style_sheet.insert(bump, css::Rule::Padding(self.padding)); - // TODO: Complete styling div(bump) .attr( "class", - bumpalo::format!(in bump, "{} {} {}", column_class, spacing_class, padding_class) + bumpalo::format!(in bump, "{} {}", column_class, spacing_class) .into_bump_str(), ) .attr("style", bumpalo::format!( in bump, - "width: {}; height: {}; max-width: {}; max-height: {}; align-items: {}", + "width: {}; height: {}; max-width: {}; max-height: {}; padding: {}; align-items: {}", css::length(self.width), css::length(self.height), css::max_length(self.max_width), css::max_length(self.max_height), + css::padding(self.padding), css::align(self.align_items) ).into_bump_str() ) diff --git a/web/src/widget/container.rs b/web/src/widget/container.rs index 8de3cc41..9040f2ae 100644 --- a/web/src/widget/container.rs +++ b/web/src/widget/container.rs @@ -1,5 +1,5 @@ //! Decorate content and apply alignment. -use crate::{bumpalo, css, Align, Bus, Css, Element, Length, Widget}; +use crate::{bumpalo, css, Align, Bus, Css, Element, Length, Padding, Widget}; pub use iced_style::container::{Style, StyleSheet}; @@ -8,7 +8,7 @@ pub use iced_style::container::{Style, StyleSheet}; /// It is normally used for alignment purposes. #[allow(missing_debug_implementations)] pub struct Container<'a, Message> { - padding: u16, + padding: Padding, width: Length, height: Length, max_width: u32, @@ -29,7 +29,7 @@ impl<'a, Message> Container<'a, Message> { use std::u32; Container { - padding: 0, + padding: Padding::ZERO, width: Length::Shrink, height: Length::Shrink, max_width: u32::MAX, @@ -41,9 +41,14 @@ impl<'a, Message> Container<'a, Message> { } } - /// 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 } @@ -106,24 +111,22 @@ where let column_class = style_sheet.insert(bump, css::Rule::Column); - let padding_class = - style_sheet.insert(bump, css::Rule::Padding(self.padding)); - let style = self.style_sheet.style(); let node = div(bump) .attr( "class", - bumpalo::format!(in bump, "{} {}", column_class, padding_class).into_bump_str(), + bumpalo::format!(in bump, "{}", column_class).into_bump_str(), ) .attr( "style", bumpalo::format!( in bump, - "width: {}; height: {}; max-width: {}; align-items: {}; justify-content: {}; background: {}; color: {}; border-width: {}px; border-color: {}; border-radius: {}px", + "width: {}; height: {}; max-width: {}; padding: {}; align-items: {}; justify-content: {}; background: {}; color: {}; border-width: {}px; border-color: {}; border-radius: {}px", css::length(self.width), css::length(self.height), css::max_length(self.max_width), + css::padding(self.padding), css::align(self.horizontal_alignment), css::align(self.vertical_alignment), style.background.map(css::background).unwrap_or(String::from("initial")), diff --git a/web/src/widget/row.rs b/web/src/widget/row.rs index f00a544a..e0df294e 100644 --- a/web/src/widget/row.rs +++ b/web/src/widget/row.rs @@ -1,4 +1,4 @@ -use crate::{css, Align, Bus, Css, Element, Length, Widget}; +use crate::{css, Align, Bus, Css, Element, Length, Padding, Widget}; use dodrio::bumpalo; use std::u32; @@ -9,7 +9,7 @@ use std::u32; #[allow(missing_debug_implementations)] pub struct Row<'a, Message> { spacing: u16, - padding: u16, + padding: Padding, width: Length, height: Length, max_width: u32, @@ -28,7 +28,7 @@ impl<'a, Message> Row<'a, Message> { pub fn with_children(children: Vec>) -> Self { Row { spacing: 0, - padding: 0, + padding: Padding::ZERO, width: Length::Fill, height: Length::Shrink, max_width: u32::MAX, @@ -48,9 +48,14 @@ impl<'a, Message> Row<'a, Message> { 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 } @@ -114,23 +119,21 @@ impl<'a, Message> Widget for Row<'a, Message> { let spacing_class = style_sheet.insert(bump, css::Rule::Spacing(self.spacing)); - let padding_class = - style_sheet.insert(bump, css::Rule::Padding(self.padding)); - // TODO: Complete styling div(bump) .attr( "class", - bumpalo::format!(in bump, "{} {} {}", row_class, spacing_class, padding_class) + bumpalo::format!(in bump, "{} {}", row_class, spacing_class) .into_bump_str(), ) .attr("style", bumpalo::format!( in bump, - "width: {}; height: {}; max-width: {}; max-height: {}; align-items: {}", + "width: {}; height: {}; max-width: {}; max-height: {}; padding: {}; align-items: {}", css::length(self.width), css::length(self.height), css::max_length(self.max_width), css::max_length(self.max_height), + css::padding(self.padding), css::align(self.align_items) ).into_bump_str() ) diff --git a/web/src/widget/scrollable.rs b/web/src/widget/scrollable.rs index bd9260ff..10f633de 100644 --- a/web/src/widget/scrollable.rs +++ b/web/src/widget/scrollable.rs @@ -1,5 +1,7 @@ //! Navigate an endless amount of content with a scrollbar. -use crate::{bumpalo, css, Align, Bus, Column, Css, Element, Length, Widget}; +use crate::{ + bumpalo, css, Align, Bus, Column, Css, Element, Length, Padding, Widget, +}; pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet}; @@ -39,9 +41,14 @@ impl<'a, Message> Scrollable<'a, Message> { 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/web/src/widget/text_input.rs b/web/src/widget/text_input.rs index bc2048a8..c671f80e 100644 --- a/web/src/widget/text_input.rs +++ b/web/src/widget/text_input.rs @@ -1,7 +1,7 @@ //! Display fields that can be filled with text. //! //! A [`TextInput`] has some local [`State`]. -use crate::{bumpalo, css, Bus, Css, Element, Length, Widget}; +use crate::{bumpalo, css, Bus, Css, Element, Length, Padding, Widget}; pub use iced_style::text_input::{Style, StyleSheet}; @@ -35,7 +35,7 @@ pub struct TextInput<'a, Message> { is_secure: bool, width: Length, max_width: u32, - padding: u16, + padding: Padding, size: Option, on_change: Rc Message>>, on_submit: Option, @@ -66,7 +66,7 @@ impl<'a, Message> TextInput<'a, Message> { is_secure: false, width: Length::Fill, max_width: u32::MAX, - padding: 0, + padding: Padding::ZERO, size: None, on_change: Rc::new(Box::new(on_change)), on_submit: None, @@ -92,9 +92,14 @@ impl<'a, Message> TextInput<'a, Message> { 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 } @@ -131,15 +136,6 @@ where use dodrio::builder::*; use wasm_bindgen::JsCast; - let class = { - use dodrio::bumpalo::collections::String; - - let padding_class = - style_sheet.insert(bump, css::Rule::Padding(self.padding)); - - String::from_str_in(&padding_class, bump).into_bump_str() - }; - let placeholder = { use dodrio::bumpalo::collections::String; @@ -159,16 +155,16 @@ where let style = self.style_sheet.active(); input(bump) - .attr("class", class) .attr( "style", bumpalo::format!( in bump, - "width: {}; max-width: {}; font-size: {}px; \ + "width: {}; max-width: {}; padding: {}; font-size: {}px; \ background: {}; border-width: {}px; border-color: {}; \ border-radius: {}px; color: {}", css::length(self.width), css::max_length(self.max_width), + css::padding(self.padding), self.size.unwrap_or(20), css::background(style.background), style.border_width, -- cgit From d83e263abe3458808799e2afca159c2ddab5e10f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Tue, 1 Jun 2021 19:13:11 +0700 Subject: Introduce `vertical` and `horizontal` methods to `Padding` --- core/src/padding.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/src/padding.rs b/core/src/padding.rs index 083f7eab..6eafd44d 100644 --- a/core/src/padding.rs +++ b/core/src/padding.rs @@ -29,6 +29,16 @@ impl Padding { left: padding, } } + + /// Returns the total amount of vertical [`Padding`]. + pub fn vertical(self) -> u16 { + self.top + self.bottom + } + + /// Returns the total amount of horizontal [`Padding`]. + pub fn horizontal(self) -> u16 { + self.left + self.right + } } impl std::convert::From for Padding { -- 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(-) 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 0a14492343e30bf0856088c91406c2da7c7f5860 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Tue, 1 Jun 2021 19:13:52 +0700 Subject: Fix `Tooltip` widget --- graphics/src/widget/tooltip.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graphics/src/widget/tooltip.rs b/graphics/src/widget/tooltip.rs index 1a1b5352..493a6389 100644 --- a/graphics/src/widget/tooltip.rs +++ b/graphics/src/widget/tooltip.rs @@ -5,7 +5,7 @@ use crate::{Primitive, Renderer, Vector}; use iced_native::container; use iced_native::layout::{self, Layout}; -use iced_native::{Element, Point, Rectangle, Size, Text}; +use iced_native::{Element, Padding, Point, Rectangle, Size, Text}; /// An element decorating some content. /// @@ -49,7 +49,6 @@ where use iced_native::Widget; let gap = f32::from(gap); - let padding = f32::from(padding); let style = style_sheet.style(); let defaults = Defaults { @@ -62,9 +61,10 @@ where tooltip, self, &layout::Limits::new(Size::ZERO, viewport.size()) - .pad(f32::from(padding)), + .pad(Padding::new(padding)), ); + let padding = f32::from(padding); let text_bounds = text_layout.bounds(); let x_center = bounds.x + (bounds.width - text_bounds.width) / 2.0; let y_center = -- cgit From 2e17d7860b6f857315f14341813645ca980a15df Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Tue, 1 Jun 2021 19:14:04 +0700 Subject: Fix unused variable warning in `iced_web::text_input` --- web/src/widget/text_input.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/widget/text_input.rs b/web/src/widget/text_input.rs index c671f80e..591fc21d 100644 --- a/web/src/widget/text_input.rs +++ b/web/src/widget/text_input.rs @@ -131,7 +131,7 @@ where &self, bump: &'b bumpalo::Bump, bus: &Bus, - style_sheet: &mut Css<'b>, + _style_sheet: &mut Css<'b>, ) -> dodrio::Node<'b> { use dodrio::builder::*; use wasm_bindgen::JsCast; -- 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 --- core/src/size.rs | 4 ++-- graphics/src/overlay/menu.rs | 4 ++-- graphics/src/widget/pick_list.rs | 3 +-- native/src/layout/limits.rs | 4 ++-- native/src/overlay/menu.rs | 9 +++------ 5 files changed, 10 insertions(+), 14 deletions(-) diff --git a/core/src/size.rs b/core/src/size.rs index 712caee2..6745c6c8 100644 --- a/core/src/size.rs +++ b/core/src/size.rs @@ -30,8 +30,8 @@ impl Size { /// Increments the [`Size`] to account for the given padding. pub fn pad(&self, padding: Padding) -> Self { Size { - width: self.width + (padding.left + padding.right) as f32, - height: self.height + (padding.top + padding.bottom) as f32, + width: self.width + padding.horizontal() as f32, + height: self.height + padding.vertical() as f32, } } } diff --git a/graphics/src/overlay/menu.rs b/graphics/src/overlay/menu.rs index 443f1746..9e91a0ef 100644 --- a/graphics/src/overlay/menu.rs +++ b/graphics/src/overlay/menu.rs @@ -53,7 +53,7 @@ where use std::f32; let is_mouse_over = bounds.contains(cursor_position); - let option_height = (text_size + padding.top + padding.bottom) as usize; + let option_height = (text_size + padding.vertical()) as usize; let mut primitives = Vec::new(); @@ -72,7 +72,7 @@ where x: bounds.x, y: bounds.y + (option_height * i) as f32, width: bounds.width, - height: f32::from(text_size + padding.top + padding.bottom), + height: f32::from(text_size + padding.vertical()), }; if is_selected { diff --git a/graphics/src/widget/pick_list.rs b/graphics/src/widget/pick_list.rs index c6fbcf76..32dfbdf9 100644 --- a/graphics/src/widget/pick_list.rs +++ b/graphics/src/widget/pick_list.rs @@ -57,8 +57,7 @@ where font: B::ICON_FONT, size: bounds.height * style.icon_size, bounds: Rectangle { - x: bounds.x + bounds.width - - f32::from(padding.left + padding.right), + x: bounds.x + bounds.width - f32::from(padding.horizontal()), y: bounds.center_y(), ..bounds }, 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` --- core/src/padding.rs | 32 ++++++++++++++++++++++++++++++++ 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 ----- web/src/widget/button.rs | 5 ----- web/src/widget/column.rs | 5 ----- web/src/widget/container.rs | 5 ----- web/src/widget/row.rs | 5 ----- web/src/widget/scrollable.rs | 5 ----- web/src/widget/text_input.rs | 5 ----- 16 files changed, 32 insertions(+), 75 deletions(-) diff --git a/core/src/padding.rs b/core/src/padding.rs index 6eafd44d..22467d6b 100644 --- a/core/src/padding.rs +++ b/core/src/padding.rs @@ -1,4 +1,36 @@ /// An amount of space to pad for each side of a box +/// +/// You can leverage the `From` trait to build [`Padding`] conveniently: +/// +/// ``` +/// # use iced_core::Padding; +/// # +/// let padding = Padding::from(20); // 20px on all sides +/// let padding = Padding::from([10, 20]); // top/bottom, left/right +/// let padding = Padding::from([5, 10, 15, 20]); // top, right, bottom, left +/// ``` +/// +/// Normally, the `padding` method of a widget will ask for an `Into`, +/// so you can easily write: +/// +/// ``` +/// # use iced_core::Padding; +/// # +/// # struct Widget; +/// # +/// impl Widget { +/// # pub fn new() -> Self { Self } +/// # +/// pub fn padding(mut self, padding: impl Into) -> Self { +/// // ... +/// self +/// } +/// } +/// +/// let widget = Widget::new().padding(20); // 20px on all sides +/// let widget = Widget::new().padding([10, 20]); // top/bottom, left/right +/// let widget = Widget::new().padding([5, 10, 15, 20]); // top, right, bottom, left +/// ``` #[derive(Debug, Hash, Copy, Clone)] pub struct Padding { /// Top padding 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 diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs index 0e75fa3f..8a36aab9 100644 --- a/web/src/widget/button.rs +++ b/web/src/widget/button.rs @@ -78,11 +78,6 @@ impl<'a, Message> Button<'a, Message> { } /// 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/web/src/widget/column.rs b/web/src/widget/column.rs index a4c99046..8738c2af 100644 --- a/web/src/widget/column.rs +++ b/web/src/widget/column.rs @@ -49,11 +49,6 @@ impl<'a, Message> Column<'a, Message> { } /// 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/web/src/widget/container.rs b/web/src/widget/container.rs index 9040f2ae..c006e011 100644 --- a/web/src/widget/container.rs +++ b/web/src/widget/container.rs @@ -42,11 +42,6 @@ impl<'a, Message> Container<'a, Message> { } /// 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/web/src/widget/row.rs b/web/src/widget/row.rs index e0df294e..ffb515cf 100644 --- a/web/src/widget/row.rs +++ b/web/src/widget/row.rs @@ -49,11 +49,6 @@ impl<'a, Message> Row<'a, Message> { } /// 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/web/src/widget/scrollable.rs b/web/src/widget/scrollable.rs index 10f633de..ce0a10d4 100644 --- a/web/src/widget/scrollable.rs +++ b/web/src/widget/scrollable.rs @@ -42,11 +42,6 @@ impl<'a, Message> Scrollable<'a, Message> { } /// 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/web/src/widget/text_input.rs b/web/src/widget/text_input.rs index 591fc21d..e8d8ca2f 100644 --- a/web/src/widget/text_input.rs +++ b/web/src/widget/text_input.rs @@ -93,11 +93,6 @@ impl<'a, Message> TextInput<'a, Message> { } /// 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 --- graphics/src/widget.rs | 3 + graphics/src/widget/toggler.rs | 94 +++++++++++++++ native/src/renderer/null.rs | 18 ++- native/src/widget.rs | 3 + native/src/widget/toggler.rs | 262 +++++++++++++++++++++++++++++++++++++++++ src/widget.rs | 6 +- style/src/lib.rs | 1 + style/src/toggler.rs | 57 +++++++++ wgpu/src/widget.rs | 3 + wgpu/src/widget/toggler.rs | 9 ++ 10 files changed, 452 insertions(+), 4 deletions(-) create mode 100644 graphics/src/widget/toggler.rs create mode 100644 native/src/widget/toggler.rs create mode 100644 style/src/toggler.rs create mode 100644 wgpu/src/widget/toggler.rs diff --git a/graphics/src/widget.rs b/graphics/src/widget.rs index 190ea9c0..e34d267f 100644 --- a/graphics/src/widget.rs +++ b/graphics/src/widget.rs @@ -20,6 +20,7 @@ pub mod scrollable; pub mod slider; pub mod svg; pub mod text_input; +pub mod toggler; pub mod tooltip; mod column; @@ -50,6 +51,8 @@ pub use slider::Slider; #[doc(no_inline)] pub use text_input::TextInput; #[doc(no_inline)] +pub use toggler::Toggler; +#[doc(no_inline)] pub use tooltip::Tooltip; pub use column::Column; diff --git a/graphics/src/widget/toggler.rs b/graphics/src/widget/toggler.rs new file mode 100644 index 00000000..a258443e --- /dev/null +++ b/graphics/src/widget/toggler.rs @@ -0,0 +1,94 @@ +//! Show toggle controls using togglers. +use crate::backend::{self, Backend}; +use crate::{Primitive, Renderer}; +use iced_native::mouse; +use iced_native::toggler; +use iced_native::Rectangle; + +pub use iced_style::toggler::{Style, StyleSheet}; + +/// Makes sure that the border radius of the toggler looks good at every size. +const BORDER_RADIUS_RATIO: f32 = 32.0 / 13.0; + +/// The space ratio between the background Quad and the Toggler bounds, and +/// between the background Quad and foreground Quad. +const SPACE_RATIO: f32 = 0.05; + +/// A toggler that can be toggled. +/// +/// This is an alias of an `iced_native` toggler with an `iced_wgpu::Renderer`. +pub type Toggler = + iced_native::Toggler>; + +impl toggler::Renderer for Renderer +where + B: Backend + backend::Text, +{ + type Style = Box; + + const DEFAULT_SIZE: u16 = 20; + + fn draw( + &mut self, + bounds: Rectangle, + is_active: bool, + is_mouse_over: bool, + (label, _): Self::Output, + style_sheet: &Self::Style, + ) -> Self::Output { + let style = if is_mouse_over { + style_sheet.hovered(is_active) + } else { + style_sheet.active(is_active) + }; + + let border_radius = bounds.height as f32 / BORDER_RADIUS_RATIO; + let space = SPACE_RATIO * bounds.height as f32; + + let toggler_background_bounds = Rectangle { + x: bounds.x + space, + y: bounds.y + space, + width: bounds.width - (2.0 * space), + height: bounds.height - (2.0 * space), + }; + + let toggler_background = Primitive::Quad { + bounds: toggler_background_bounds, + background: style.background.into(), + border_radius, + border_width: 1.0, + border_color: style.background_border.unwrap_or(style.background), + }; + + let toggler_foreground_bounds = Rectangle { + x: bounds.x + + if is_active { + bounds.width - 2.0 * space - (bounds.height - (4.0 * space)) + } else { + 2.0 * space + }, + y: bounds.y + (2.0 * space), + width: bounds.height - (4.0 * space), + height: bounds.height - (4.0 * space), + }; + + let toggler_foreground = Primitive::Quad { + bounds: toggler_foreground_bounds, + background: style.foreground.into(), + border_radius, + border_width: 1.0, + border_color: style.foreground_border.unwrap_or(style.foreground), + }; + + ( + Primitive::Group { + primitives: vec![label, toggler_background, toggler_foreground], + }, + if is_mouse_over { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + }, + ) + } +} 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) + } +} diff --git a/src/widget.rs b/src/widget.rs index eac50d57..db052106 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -17,8 +17,8 @@ mod platform { pub use crate::renderer::widget::{ button, checkbox, container, pane_grid, pick_list, progress_bar, radio, - rule, scrollable, slider, text_input, tooltip, Column, Row, Space, - Text, + rule, scrollable, slider, text_input, toggler, tooltip, Column, Row, + Space, Text, }; #[cfg(any(feature = "canvas", feature = "glow_canvas"))] @@ -53,7 +53,7 @@ mod platform { button::Button, checkbox::Checkbox, container::Container, image::Image, pane_grid::PaneGrid, pick_list::PickList, progress_bar::ProgressBar, radio::Radio, rule::Rule, scrollable::Scrollable, slider::Slider, - svg::Svg, text_input::TextInput, tooltip::Tooltip, + svg::Svg, text_input::TextInput, toggler::Toggler, tooltip::Tooltip, }; #[cfg(any(feature = "canvas", feature = "glow_canvas"))] diff --git a/style/src/lib.rs b/style/src/lib.rs index f09b5f9d..08d9f044 100644 --- a/style/src/lib.rs +++ b/style/src/lib.rs @@ -18,3 +18,4 @@ pub mod rule; pub mod scrollable; pub mod slider; pub mod text_input; +pub mod toggler; diff --git a/style/src/toggler.rs b/style/src/toggler.rs new file mode 100644 index 00000000..5a155123 --- /dev/null +++ b/style/src/toggler.rs @@ -0,0 +1,57 @@ +//! Show toggle controls using togglers. +use iced_core::Color; + +/// The appearance of a toggler. +#[derive(Debug)] +pub struct Style { + pub background: Color, + pub background_border: Option, + pub foreground: Color, + pub foreground_border: Option, +} + +/// A set of rules that dictate the style of a toggler. +pub trait StyleSheet { + fn active(&self, is_active: bool) -> Style; + + fn hovered(&self, is_active: bool) -> Style; +} + +struct Default; + +impl StyleSheet for Default { + fn active(&self, is_active: bool) -> Style { + Style { + background: if is_active { + Color::from_rgb(0.0, 1.0, 0.0) + } else { + Color::from_rgb(0.7, 0.7, 0.7) + }, + background_border: None, + foreground: Color::WHITE, + foreground_border: None, + } + } + + fn hovered(&self, is_active: bool) -> Style { + Style { + foreground: Color::from_rgb(0.95, 0.95, 0.95), + ..self.active(is_active) + } + } +} + +impl std::default::Default for Box { + fn default() -> Self { + Box::new(Default) + } +} + +impl From for Box +where + T: 'static + StyleSheet, +{ + fn from(style: T) -> Self { + Box::new(style) + } +} diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs index 304bb726..a575d036 100644 --- a/wgpu/src/widget.rs +++ b/wgpu/src/widget.rs @@ -20,6 +20,7 @@ pub mod rule; pub mod scrollable; pub mod slider; pub mod text_input; +pub mod toggler; pub mod tooltip; #[doc(no_inline)] @@ -45,6 +46,8 @@ pub use slider::Slider; #[doc(no_inline)] pub use text_input::TextInput; #[doc(no_inline)] +pub use toggler::Toggler; +#[doc(no_inline)] pub use tooltip::Tooltip; #[cfg(feature = "canvas")] diff --git a/wgpu/src/widget/toggler.rs b/wgpu/src/widget/toggler.rs new file mode 100644 index 00000000..dfcf759b --- /dev/null +++ b/wgpu/src/widget/toggler.rs @@ -0,0 +1,9 @@ +//! Show toggle controls using togglers. +use crate::Renderer; + +pub use iced_graphics::toggler::{Style, StyleSheet}; + +/// A toggler that can be toggled +/// +/// This is an alias of an `iced_native` toggler with an `iced_wgpu::Renderer`. +pub type Toggler = iced_native::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(-) 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 88da268724527e1b41c303ebec9056a606a01486 Mon Sep 17 00:00:00 2001 From: Kaiden42 Date: Sat, 19 Sep 2020 20:16:36 +0200 Subject: add missing glow support --- glow/src/widget.rs | 3 +++ glow/src/widget/toggler.rs | 9 +++++++++ 2 files changed, 12 insertions(+) create mode 100644 glow/src/widget/toggler.rs diff --git a/glow/src/widget.rs b/glow/src/widget.rs index 5481216a..a77511e8 100644 --- a/glow/src/widget.rs +++ b/glow/src/widget.rs @@ -20,6 +20,7 @@ pub mod rule; pub mod scrollable; pub mod slider; pub mod text_input; +pub mod toggler; pub mod tooltip; #[doc(no_inline)] @@ -45,6 +46,8 @@ pub use slider::Slider; #[doc(no_inline)] pub use text_input::TextInput; #[doc(no_inline)] +pub use toggler::Toggler; +#[doc(no_inline)] pub use tooltip::Tooltip; #[cfg(feature = "canvas")] diff --git a/glow/src/widget/toggler.rs b/glow/src/widget/toggler.rs new file mode 100644 index 00000000..0bd5b758 --- /dev/null +++ b/glow/src/widget/toggler.rs @@ -0,0 +1,9 @@ +//! Show toggle controls using togglers. +use crate::Renderer; + +pub use iced_graphics::toggler::{Style, StyleSheet}; + +/// A toggler that can be toggled. +/// +/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`. +pub type Toggler = iced_native::Toggler; \ No newline at end of file -- cgit From bab71971fb6653c81e74a517c9ace93be63b49fc Mon Sep 17 00:00:00 2001 From: Kaiden42 Date: Sat, 19 Sep 2020 20:20:14 +0200 Subject: fix format --- glow/src/widget/toggler.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/glow/src/widget/toggler.rs b/glow/src/widget/toggler.rs index 0bd5b758..1cd8711b 100644 --- a/glow/src/widget/toggler.rs +++ b/glow/src/widget/toggler.rs @@ -4,6 +4,6 @@ use crate::Renderer; pub use iced_graphics::toggler::{Style, StyleSheet}; /// A toggler that can be toggled. -/// +/// /// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`. -pub type Toggler = iced_native::Toggler; \ No newline at end of file +pub type Toggler = iced_native::Toggler; -- 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(-) 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 --- graphics/src/widget/toggler.rs | 9 +++-- native/src/renderer/null.rs | 2 +- native/src/widget/toggler.rs | 74 +++++++++++++++++++++++++----------------- 3 files changed, 53 insertions(+), 32 deletions(-) diff --git a/graphics/src/widget/toggler.rs b/graphics/src/widget/toggler.rs index a258443e..852d18ee 100644 --- a/graphics/src/widget/toggler.rs +++ b/graphics/src/widget/toggler.rs @@ -33,7 +33,7 @@ where bounds: Rectangle, is_active: bool, is_mouse_over: bool, - (label, _): Self::Output, + label: Option, style_sheet: &Self::Style, ) -> Self::Output { let style = if is_mouse_over { @@ -82,7 +82,12 @@ where ( Primitive::Group { - primitives: vec![label, toggler_background, toggler_foreground], + primitives: match label { + Some((l, _)) => { + vec![l, toggler_background, toggler_foreground] + } + None => vec![toggler_background, toggler_foreground], + }, }, if is_mouse_over { mouse::Interaction::Pointer 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 1ef38cc207ad5a393cb54ea779ae87eec2cd5ad9 Mon Sep 17 00:00:00 2001 From: Kaiden42 Date: Fri, 25 Sep 2020 15:45:46 +0200 Subject: Add `Toggler` to styling example --- examples/styling/src/main.rs | 73 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs index 4d7dfc48..7bc49281 100644 --- a/examples/styling/src/main.rs +++ b/examples/styling/src/main.rs @@ -1,7 +1,7 @@ use iced::{ button, scrollable, slider, text_input, Align, Button, Checkbox, Column, Container, Element, Length, ProgressBar, Radio, Row, Rule, Sandbox, - Scrollable, Settings, Slider, Space, Text, TextInput, + Scrollable, Settings, Slider, Space, Text, TextInput, Toggler, }; pub fn main() -> iced::Result { @@ -17,7 +17,8 @@ struct Styling { button: button::State, slider: slider::State, slider_value: f32, - toggle_value: bool, + checkbox_value: bool, + toggler_value: bool, } #[derive(Debug, Clone)] @@ -27,6 +28,7 @@ enum Message { ButtonPressed, SliderChanged(f32), CheckboxToggled(bool), + TogglerToggled(bool), } impl Sandbox for Styling { @@ -46,7 +48,8 @@ impl Sandbox for Styling { Message::InputChanged(value) => self.input_value = value, Message::ButtonPressed => {} Message::SliderChanged(value) => self.slider_value = value, - Message::CheckboxToggled(value) => self.toggle_value = value, + Message::CheckboxToggled(value) => self.checkbox_value = value, + Message::TogglerToggled(value) => self.toggler_value = value, } } @@ -101,11 +104,19 @@ impl Sandbox for Styling { .push(Text::new("You did it!")); let checkbox = Checkbox::new( - self.toggle_value, - "Toggle me!", + self.checkbox_value, + "Check me!", Message::CheckboxToggled, ) - .width(Length::Fill) + .style(self.theme); + + let toggler = Toggler::new( + self.toggler_value, + String::from("Toggle me!"), + Message::TogglerToggled, + ) + .width(Length::Shrink) + .spacing(10) .style(self.theme); let content = Column::new() @@ -124,7 +135,13 @@ impl Sandbox for Styling { .align_items(Align::Center) .push(scrollable) .push(Rule::vertical(38).style(self.theme)) - .push(checkbox), + .push( + Column::new() + .width(Length::Shrink) + .spacing(20) + .push(checkbox) + .push(toggler), + ), ); Container::new(content) @@ -140,7 +157,7 @@ impl Sandbox for Styling { mod style { use iced::{ button, checkbox, container, progress_bar, radio, rule, scrollable, - slider, text_input, + slider, text_input, toggler, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -231,6 +248,15 @@ mod style { } } + impl From for Box { + fn from(theme: Theme) -> Self { + match theme { + Theme::Light => Default::default(), + Theme::Dark => dark::Toggler.into(), + } + } + } + impl From for Box { fn from(theme: Theme) -> Self { match theme { @@ -269,7 +295,7 @@ mod style { mod dark { use iced::{ button, checkbox, container, progress_bar, radio, rule, scrollable, - slider, text_input, Color, + slider, text_input, toggler, Color, }; const SURFACE: Color = Color::from_rgb( @@ -520,6 +546,35 @@ mod style { } } + pub struct Toggler; + + impl toggler::StyleSheet for Toggler { + fn active(&self, is_active: bool) -> toggler::Style { + toggler::Style { + background: if is_active { ACTIVE } else { SURFACE }, + background_border: None, + foreground: if is_active { Color::WHITE } else { ACTIVE }, + foreground_border: None, + } + } + + fn hovered(&self, is_active: bool) -> toggler::Style { + toggler::Style { + background: if is_active { ACTIVE } else { SURFACE }, + background_border: None, + foreground: if is_active { + Color { + a: 0.5, + ..Color::WHITE + } + } else { + Color { a: 0.5, ..ACTIVE } + }, + foreground_border: None, + } + } + } + pub struct Rule; impl rule::StyleSheet for Rule { -- cgit From be3ee9adf1659cf8eaa7a75cbb2aa1ecc33e4c42 Mon Sep 17 00:00:00 2001 From: Kaiden42 Date: Fri, 25 Sep 2020 16:35:58 +0200 Subject: Add `Toggler` to tour example --- examples/tour/src/main.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index e8755d39..de935444 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -1,7 +1,7 @@ use iced::{ button, scrollable, slider, text_input, Button, Checkbox, Color, Column, Container, Element, HorizontalAlignment, Image, Length, Radio, Row, - Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput, + Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput, Toggler, }; pub fn main() -> iced::Result { @@ -135,6 +135,9 @@ impl Steps { color: Color::BLACK, }, Step::Radio { selection: None }, + Step::Toggler { + can_continue: false, + }, Step::Image { width: 300, slider: slider::State::new(), @@ -206,6 +209,9 @@ enum Step { Radio { selection: Option, }, + Toggler { + can_continue: bool, + }, Image { width: u16, slider: slider::State, @@ -232,6 +238,7 @@ pub enum StepMessage { InputChanged(String), ToggleSecureInput(bool), DebugToggled(bool), + TogglerChanged(bool), } impl<'a> Step { @@ -287,6 +294,11 @@ impl<'a> Step { *is_secure = toggle; } } + StepMessage::TogglerChanged(value) => { + if let Step::Toggler { can_continue, .. } = self { + *can_continue = value; + } + } }; } @@ -294,6 +306,7 @@ impl<'a> Step { match self { Step::Welcome => "Welcome", Step::Radio { .. } => "Radio button", + Step::Toggler { .. } => "Toggler", Step::Slider { .. } => "Slider", Step::Text { .. } => "Text", Step::Image { .. } => "Image", @@ -309,6 +322,7 @@ impl<'a> Step { match self { Step::Welcome => true, Step::Radio { selection } => *selection == Some(Language::Rust), + Step::Toggler { can_continue } => *can_continue, Step::Slider { .. } => true, Step::Text { .. } => true, Step::Image { .. } => true, @@ -324,6 +338,7 @@ impl<'a> Step { match self { Step::Welcome => Self::welcome(), Step::Radio { selection } => Self::radio(*selection), + Step::Toggler { can_continue } => Self::toggler(*can_continue), Step::Slider { state, value } => Self::slider(state, *value), Step::Text { size_slider, @@ -545,6 +560,18 @@ impl<'a> Step { )) } + fn toggler(can_continue: bool) -> Column<'a, StepMessage> { + Self::container("Toggler") + .push(Text::new( + "A toggler is mostly used to enable or disable something.", + )) + .push(Toggler::new( + can_continue, + String::from("Toggle me to continue..."), + StepMessage::TogglerChanged, + )) + } + fn image( width: u16, slider: &'a mut slider::State, -- 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(-) 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 e00fca637202d7cd20fd10b2d7e2b2963f11dd33 Mon Sep 17 00:00:00 2001 From: Kaiden42 Date: Sat, 3 Oct 2020 18:26:31 +0200 Subject: Add `Toggler` widget to `iced_web` --- web/src/css.rs | 44 ++++++++++++ web/src/widget.rs | 3 + web/src/widget/toggler.rs | 168 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 215 insertions(+) create mode 100644 web/src/widget/toggler.rs diff --git a/web/src/css.rs b/web/src/css.rs index 66c363f2..21f51f85 100644 --- a/web/src/css.rs +++ b/web/src/css.rs @@ -14,6 +14,9 @@ pub enum Rule { /// Spacing between elements Spacing(u16), + + /// Toggler input for a specific size + Toggler(u16), } impl Rule { @@ -23,6 +26,7 @@ impl Rule { Rule::Column => String::from("c"), Rule::Row => String::from("r"), Rule::Spacing(spacing) => format!("s-{}", spacing), + Rule::Toggler(size) => format!("toggler-{}", size), } } @@ -55,6 +59,46 @@ impl Rule { class ) .into_bump_str(), + Rule::Toggler(size) => bumpalo::format!( + in bump, + ".toggler-{} {{ display: flex; cursor: pointer; justify-content: space-between; }} \ + .toggler-{} input {{ display:none; }} \ + .toggler-{} span {{ background-color: #b1b1b1; position: relative; display: inline-flex; width:{}px; height: {}px; border-radius: {}px;}} \ + .toggler-{} span > span {{ background-color: #FFFFFF; width: {}px; height: {}px; border-radius: 50%; top: 1px; left: 1px;}} \ + .toggler-{}:hover span > span {{ background-color: #f1f1f1 !important; }} \ + .toggler-{} input:checked + span {{ background-color: #00FF00; }} \ + .toggler-{} input:checked + span > span {{ -webkit-transform: translateX({}px); -ms-transform:translateX({}px); transform: translateX({}px); }} + ", + // toggler + size, + + // toggler input + size, + + // toggler span + size, + size*2, + size, + size, + + // toggler span > span + size, + size-2, + size-2, + + // toggler: hover + span > span + size, + + // toggler input:checked + span + size, + + // toggler input:checked + span > span + size, + size, + size, + size + ) + .into_bump_str(), } } } diff --git a/web/src/widget.rs b/web/src/widget.rs index 023f5f13..4cb0a9cc 100644 --- a/web/src/widget.rs +++ b/web/src/widget.rs @@ -24,6 +24,7 @@ pub mod radio; pub mod scrollable; pub mod slider; pub mod text_input; +pub mod toggler; mod column; mod row; @@ -40,6 +41,8 @@ pub use slider::Slider; pub use text::Text; #[doc(no_inline)] pub use text_input::TextInput; +#[doc(no_inline)] +pub use toggler::Toggler; pub use checkbox::Checkbox; pub use column::Column; diff --git a/web/src/widget/toggler.rs b/web/src/widget/toggler.rs new file mode 100644 index 00000000..5bcc1fc7 --- /dev/null +++ b/web/src/widget/toggler.rs @@ -0,0 +1,168 @@ +//! Show toggle controls using togglers. +use crate::{css, Bus, Css, Element, Length, Widget}; + +pub use iced_style::toggler::{Style, StyleSheet}; + +use dodrio::bumpalo; +use std::rc::Rc; + +/// A toggler that can be toggled. +/// +/// # Example +/// +/// ``` +/// # use iced_web::Toggler; +/// +/// pub enum Message { +/// TogglerToggled(bool), +/// } +/// +/// let is_active = true; +/// +/// Toggler::new(is_active, String::from("Toggle me!"), Message::TogglerToggled); +/// ``` +/// +#[allow(missing_debug_implementations)] +pub struct Toggler { + is_active: bool, + on_toggle: Rc Message>, + label: Option, + id: Option, + width: Length, + style: Box, +} + +impl Toggler { + /// Creates a new [`Toggler`]. + /// + /// It expects: + /// * a boolean describing whether the [`Toggler`] is active or not + /// * An optional label for the [`Toggler`] + /// * a function that will be called when the [`Toggler`] is toggled. It + /// will receive the new state of the [`Toggler`] and must produce a + /// `Message`. + /// + /// [`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: Rc::new(f), + label: label.into(), + id: None, + width: Length::Shrink, + style: Default::default(), + } + } + + /// Sets the width of the [`Toggler`]. + /// + /// [`Toggler`]: struct.Toggler.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + 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 + } + + /// Sets the id of the [`Toggler`]. + /// + /// [`Toggler`]: struct.Toggler.html + pub fn id(mut self, id: impl Into) -> Self { + self.id = Some(id.into()); + self + } +} + +impl Widget for Toggler +where + Message: 'static, +{ + fn node<'b>( + &self, + bump: &'b bumpalo::Bump, + bus: &Bus, + style_sheet: &mut Css<'b>, + ) -> dodrio::Node<'b> { + use dodrio::builder::*; + use dodrio::bumpalo::collections::String; + + let toggler_label = &self.label.as_ref().map(|label| { + String::from_str_in(&label, bump).into_bump_str() + }); + + let event_bus = bus.clone(); + let on_toggle = self.on_toggle.clone(); + let is_active = self.is_active; + + let row_class = style_sheet.insert(bump, css::Rule::Row); + let toggler_class = style_sheet.insert(bump, css::Rule::Toggler(16)); + + let (label, input) = if let Some(id) = &self.id { + let id = String::from_str_in(id, bump).into_bump_str(); + + (label(bump).attr("for", id), input(bump).attr("id", id)) + } else { + (label(bump), input(bump)) + }; + + let checkbox = input + .attr("type", "checkbox") + .bool_attr("checked", self.is_active) + .on("click", move |_root, vdom, _event| { + let msg = on_toggle(!is_active); + event_bus.publish(msg); + + vdom.schedule_render(); + }) + .finish(); + + let toggler = span(bump) + .children(vec![span(bump).finish()]) + .finish(); + + label + .attr( + "class", + bumpalo::format!(in bump, "{} {}", row_class, toggler_class) + .into_bump_str(), + ) + .attr( + "style", + bumpalo::format!(in bump, "width: {}; align-items: center", css::length(self.width)) + .into_bump_str() + ) + .children( + if let Some(label) = toggler_label { + vec![ + text(label), + checkbox, + toggler, + ] + } else { + vec![ + checkbox, + toggler, + ] + } + ) + .finish() + } +} + +impl<'a, Message> From> for Element<'a, Message> +where + Message: 'static, +{ + fn from(toggler: Toggler) -> Element<'a, Message> { + Element::new(toggler) + } +} \ No newline at end of file -- cgit From 2a5aa69024fe8c73929a4c616f02c50eb5ce43ae Mon Sep 17 00:00:00 2001 From: Kaiden42 Date: Sat, 3 Oct 2020 20:30:08 +0200 Subject: Fix format --- web/src/widget/toggler.rs | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/web/src/widget/toggler.rs b/web/src/widget/toggler.rs index 5bcc1fc7..0a198079 100644 --- a/web/src/widget/toggler.rs +++ b/web/src/widget/toggler.rs @@ -7,21 +7,21 @@ use dodrio::bumpalo; use std::rc::Rc; /// A toggler that can be toggled. -/// +/// /// # Example -/// +/// /// ``` /// # use iced_web::Toggler; -/// +/// /// pub enum Message { /// TogglerToggled(bool), /// } -/// +/// /// let is_active = true; -/// +/// /// Toggler::new(is_active, String::from("Toggle me!"), Message::TogglerToggled); /// ``` -/// +/// #[allow(missing_debug_implementations)] pub struct Toggler { is_active: bool, @@ -34,16 +34,20 @@ pub struct Toggler { impl Toggler { /// Creates a new [`Toggler`]. - /// + /// /// It expects: /// * a boolean describing whether the [`Toggler`] is active or not /// * An optional label for the [`Toggler`] /// * a function that will be called when the [`Toggler`] is toggled. It /// will receive the new state of the [`Toggler`] and must produce a /// `Message`. - /// + /// /// [`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, { @@ -58,7 +62,7 @@ impl Toggler { } /// Sets the width of the [`Toggler`]. - /// + /// /// [`Toggler`]: struct.Toggler.html pub fn width(mut self, width: Length) -> Self { self.width = width; @@ -66,7 +70,7 @@ impl Toggler { } /// Sets the style of the [`Toggler`]. - /// + /// /// [`Toggler`]: struct.Toggler.html pub fn style(mut self, style: impl Into>) -> Self { self.style = style.into(); @@ -74,7 +78,7 @@ impl Toggler { } /// Sets the id of the [`Toggler`]. - /// + /// /// [`Toggler`]: struct.Toggler.html pub fn id(mut self, id: impl Into) -> Self { self.id = Some(id.into()); @@ -95,9 +99,10 @@ where use dodrio::builder::*; use dodrio::bumpalo::collections::String; - let toggler_label = &self.label.as_ref().map(|label| { - String::from_str_in(&label, bump).into_bump_str() - }); + let toggler_label = &self + .label + .as_ref() + .map(|label| String::from_str_in(&label, bump).into_bump_str()); let event_bus = bus.clone(); let on_toggle = self.on_toggle.clone(); @@ -125,9 +130,7 @@ where }) .finish(); - let toggler = span(bump) - .children(vec![span(bump).finish()]) - .finish(); + let toggler = span(bump).children(vec![span(bump).finish()]).finish(); label .attr( @@ -140,7 +143,7 @@ where bumpalo::format!(in bump, "width: {}; align-items: center", css::length(self.width)) .into_bump_str() ) - .children( + .children( if let Some(label) = toggler_label { vec![ text(label), @@ -165,4 +168,4 @@ where fn from(toggler: Toggler) -> Element<'a, Message> { Element::new(toggler) } -} \ No newline at end of file +} -- 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(-) 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(-) 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 d3d6f3efb33f601ff3fca4a6496cfeef052501ee Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Thu, 3 Jun 2021 20:35:26 +0700 Subject: Add some horizontal padding to `toggler` section in `tour` example --- examples/tour/src/main.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index de935444..1215f83d 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -565,11 +565,14 @@ impl<'a> Step { .push(Text::new( "A toggler is mostly used to enable or disable something.", )) - .push(Toggler::new( - can_continue, - String::from("Toggle me to continue..."), - StepMessage::TogglerChanged, - )) + .push( + Container::new(Toggler::new( + can_continue, + String::from("Toggle me to continue..."), + StepMessage::TogglerChanged, + )) + .padding([0, 40]), + ) } fn image( -- 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(-) 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(-) 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 57510c43c853c7332890f8e7f36c6ba1f2a7f252 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Fri, 4 Jun 2021 20:15:06 +0700 Subject: Add buttons to control scrolling in `scrollable` example --- examples/scrollable/src/main.rs | 52 +++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index a570f0f6..32c44df8 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -17,6 +17,8 @@ struct ScrollableDemo { #[derive(Debug, Clone)] enum Message { ThemeChanged(style::Theme), + ScrollToTop(usize), + ScrollToBottom(usize), } impl Sandbox for ScrollableDemo { @@ -36,6 +38,16 @@ impl Sandbox for ScrollableDemo { fn update(&mut self, message: Message) { match message { Message::ThemeChanged(theme) => self.theme = theme, + Message::ScrollToTop(i) => { + if let Some(variant) = self.variants.get_mut(i) { + variant.scrollable.snap_to(0.0); + } + } + Message::ScrollToBottom(i) => { + if let Some(variant) = self.variants.get_mut(i) { + variant.scrollable.snap_to(1.0); + } + } } } @@ -62,7 +74,8 @@ impl Sandbox for ScrollableDemo { let scrollable_row = Row::with_children( variants .iter_mut() - .map(|variant| { + .enumerate() + .map(|(i, variant)| { let mut scrollable = Scrollable::new(&mut variant.scrollable) .padding(10) @@ -70,7 +83,16 @@ impl Sandbox for ScrollableDemo { .width(Length::Fill) .height(Length::Fill) .style(*theme) - .push(Text::new(variant.title)); + .push(Text::new(variant.title)) + .push( + Button::new( + &mut variant.scroll_to_bottom, + Text::new("Scroll to bottom"), + ) + .width(Length::Fill) + .padding(10) + .on_press(Message::ScrollToBottom(i)), + ); if let Some(scrollbar_width) = variant.scrollbar_width { scrollable = scrollable @@ -110,15 +132,16 @@ impl Sandbox for ScrollableDemo { .push(Space::with_height(Length::Units(1200))) .push(Text::new("Middle")) .push(Space::with_height(Length::Units(1200))) + .push(Text::new("The End.")) .push( Button::new( - &mut variant.button, - Text::new("I am a button"), + &mut variant.scroll_to_top, + Text::new("Scroll to top"), ) .width(Length::Fill) - .padding(10), - ) - .push(Text::new("The End.")); + .padding(10) + .on_press(Message::ScrollToTop(i)), + ); Container::new(scrollable) .width(Length::Fill) @@ -153,7 +176,8 @@ impl Sandbox for ScrollableDemo { struct Variant { title: &'static str, scrollable: scrollable::State, - button: button::State, + scroll_to_top: button::State, + scroll_to_bottom: button::State, scrollbar_width: Option, scrollbar_margin: Option, scroller_width: Option, @@ -165,7 +189,8 @@ impl Variant { Self { title: "Default Scrollbar", scrollable: scrollable::State::new(), - button: button::State::new(), + scroll_to_top: button::State::new(), + scroll_to_bottom: button::State::new(), scrollbar_width: None, scrollbar_margin: None, scroller_width: None, @@ -173,7 +198,8 @@ impl Variant { Self { title: "Slimmed & Margin", scrollable: scrollable::State::new(), - button: button::State::new(), + scroll_to_top: button::State::new(), + scroll_to_bottom: button::State::new(), scrollbar_width: Some(4), scrollbar_margin: Some(3), scroller_width: Some(4), @@ -181,7 +207,8 @@ impl Variant { Self { title: "Wide Scroller", scrollable: scrollable::State::new(), - button: button::State::new(), + scroll_to_top: button::State::new(), + scroll_to_bottom: button::State::new(), scrollbar_width: Some(4), scrollbar_margin: None, scroller_width: Some(10), @@ -189,7 +216,8 @@ impl Variant { Self { title: "Narrow Scroller", scrollable: scrollable::State::new(), - button: button::State::new(), + scroll_to_top: button::State::new(), + scroll_to_bottom: button::State::new(), scrollbar_width: Some(10), scrollbar_margin: None, scroller_width: Some(4), -- 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(-) 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 ce3a5f19b92889d03f564133a90d328d430137af Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Fri, 4 Jun 2021 20:46:47 +0700 Subject: Add scrolling progress indicators to `scrollable` example --- examples/scrollable/src/main.rs | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index 32c44df8..3416b83d 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -1,8 +1,8 @@ mod style; use iced::{ - button, scrollable, Button, Column, Container, Element, Length, Radio, Row, - Rule, Sandbox, Scrollable, Settings, Space, Text, + button, scrollable, Button, Column, Container, Element, Length, + ProgressBar, Radio, Row, Rule, Sandbox, Scrollable, Settings, Space, Text, }; pub fn main() -> iced::Result { @@ -19,6 +19,7 @@ enum Message { ThemeChanged(style::Theme), ScrollToTop(usize), ScrollToBottom(usize), + Scrolled(usize, f32), } impl Sandbox for ScrollableDemo { @@ -41,11 +42,20 @@ impl Sandbox for ScrollableDemo { Message::ScrollToTop(i) => { if let Some(variant) = self.variants.get_mut(i) { variant.scrollable.snap_to(0.0); + + variant.latest_offset = 0.0; } } Message::ScrollToBottom(i) => { if let Some(variant) = self.variants.get_mut(i) { variant.scrollable.snap_to(1.0); + + variant.latest_offset = 1.0; + } + } + Message::Scrolled(i, offset) => { + if let Some(variant) = self.variants.get_mut(i) { + variant.latest_offset = offset; } } } @@ -82,6 +92,9 @@ impl Sandbox for ScrollableDemo { .spacing(10) .width(Length::Fill) .height(Length::Fill) + .on_scroll(move |offset| { + Message::Scrolled(i, offset) + }) .style(*theme) .push(Text::new(variant.title)) .push( @@ -143,10 +156,20 @@ impl Sandbox for ScrollableDemo { .on_press(Message::ScrollToTop(i)), ); - Container::new(scrollable) + Column::new() .width(Length::Fill) .height(Length::Fill) - .style(*theme) + .spacing(10) + .push( + Container::new(scrollable) + .width(Length::Fill) + .height(Length::Fill) + .style(*theme), + ) + .push(ProgressBar::new( + 0.0..=1.0, + variant.latest_offset, + )) .into() }) .collect(), @@ -181,6 +204,7 @@ struct Variant { scrollbar_width: Option, scrollbar_margin: Option, scroller_width: Option, + latest_offset: f32, } impl Variant { @@ -194,6 +218,7 @@ impl Variant { scrollbar_width: None, scrollbar_margin: None, scroller_width: None, + latest_offset: 0.0, }, Self { title: "Slimmed & Margin", @@ -203,6 +228,7 @@ impl Variant { scrollbar_width: Some(4), scrollbar_margin: Some(3), scroller_width: Some(4), + latest_offset: 0.0, }, Self { title: "Wide Scroller", @@ -212,6 +238,7 @@ impl Variant { scrollbar_width: Some(4), scrollbar_margin: None, scroller_width: Some(10), + latest_offset: 0.0, }, Self { title: "Narrow Scroller", @@ -221,6 +248,7 @@ impl Variant { scrollbar_width: Some(10), scrollbar_margin: None, scroller_width: Some(4), + latest_offset: 0.0, }, ] } -- cgit From 656dc357f87849f2506ce402f8484a7d7484f250 Mon Sep 17 00:00:00 2001 From: nlevy Date: Thu, 8 Apr 2021 14:50:12 +0200 Subject: Attempt to fix scissor_rect validation error Update wgpu/src/backend.rs Cargo fmt --- wgpu/src/backend.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 534c6cb7..6cd6d976 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -110,7 +110,16 @@ impl Backend { target_width: u32, target_height: u32, ) { - let bounds = (layer.bounds * scale_factor).snap(); + let target_bounds = iced_graphics::Rectangle::with_size( + iced_graphics::Size::new(target_width as f32, target_height as f32), + ); + let mut bounds_float = layer.bounds * scale_factor; + bounds_float.width = + bounds_float.width.min(target_width as f32 - bounds_float.x); + bounds_float.height = bounds_float + .height + .min(target_height as f32 - bounds_float.y); + let bounds = bounds_float.snap(); if !layer.quads.is_empty() { self.quad_pipeline.draw( -- cgit From 5224cc7f26fc1549555c5bf3b25a1a80c9d26cd0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Wed, 9 Jun 2021 21:30:20 +0700 Subject: Floor `width` and `height` in `Rectangle::floor` --- core/src/rectangle.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index 0a7f5fe2..4e082051 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -105,8 +105,8 @@ impl Rectangle { Rectangle { x: self.x as u32, y: self.y as u32, - width: self.width.ceil() as u32, - height: self.height.ceil() as u32, + width: self.width as u32, + height: self.height as u32, } } } -- cgit From 56f673d819b241336f7cd041d990d47bdb2307b5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Wed, 9 Jun 2021 21:30:48 +0700 Subject: Revert "Attempt to fix scissor_rect validation error" This reverts commit 656dc357f87849f2506ce402f8484a7d7484f250. --- wgpu/src/backend.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 6cd6d976..534c6cb7 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -110,16 +110,7 @@ impl Backend { target_width: u32, target_height: u32, ) { - let target_bounds = iced_graphics::Rectangle::with_size( - iced_graphics::Size::new(target_width as f32, target_height as f32), - ); - let mut bounds_float = layer.bounds * scale_factor; - bounds_float.width = - bounds_float.width.min(target_width as f32 - bounds_float.x); - bounds_float.height = bounds_float - .height - .min(target_height as f32 - bounds_float.y); - let bounds = bounds_float.snap(); + let bounds = (layer.bounds * scale_factor).snap(); if !layer.quads.is_empty() { self.quad_pipeline.draw( -- 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 --- examples/tour/src/main.rs | 20 ++++++++++++-------- native/src/widget/button.rs | 8 ++++++++ web/src/widget/button.rs | 11 +++++++++++ 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 1215f83d..626d2b52 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -70,13 +70,15 @@ impl Sandbox for Tour { controls = controls.push(Space::with_width(Length::Fill)); - if steps.can_continue() { - controls = controls.push( - button(next_button, "Next") - .on_press(Message::NextPressed) - .style(style::Button::Primary), - ); - } + let mut button = button(next_button, "Next"); + button = if steps.can_continue() { + button + .style(style::Button::Primary) + .on_press(Message::NextPressed) + } else { + button.style(style::Button::Disabled) + }; + controls = controls.push(button); let content: Element<_> = Column::new() .max_width(540) @@ -790,6 +792,7 @@ mod style { pub enum Button { Primary, Secondary, + Disabled, } impl button::StyleSheet for Button { @@ -797,7 +800,8 @@ mod style { button::Style { background: Some(Background::Color(match self { Button::Primary => Color::from_rgb(0.11, 0.42, 0.87), - Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5), + Button::Secondary => Color::from_rgb(0.055, 0.21, 0.435), + Button::Disabled => Color::from_rgb(0.5, 0.5, 0.5), })), border_radius: 12.0, shadow_offset: Vector::new(1.0, 1.0), 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 diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs index 8a36aab9..7c8612f6 100644 --- a/web/src/widget/button.rs +++ b/web/src/widget/button.rs @@ -20,6 +20,14 @@ use dodrio::bumpalo; /// 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> { content: Element<'a, Message>, @@ -90,6 +98,7 @@ impl<'a, Message> Button<'a, Message> { } /// 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 @@ -153,6 +162,8 @@ where node = node.on("click", move |_root, _vdom, _event| { event_bus.publish(on_press.clone()); }); + } else { + node = node.attr("disabled", ""); } node.finish() -- 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 +++++++++++- web/src/widget/button.rs | 8 +++++++- 2 files changed, 18 insertions(+), 2 deletions(-) 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> { diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs index 7c8612f6..6afcaee3 100644 --- a/web/src/widget/button.rs +++ b/web/src/widget/button.rs @@ -24,8 +24,14 @@ use dodrio::bumpalo; /// Buttons can be disabled by not having an on_press. /// /// ``` +/// # use iced_web::{button, Button, Text}; +/// # +/// # 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)] -- 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 ++++++++++++++-------- web/src/widget/button.rs | 22 ++++++++++++++-------- 2 files changed, 28 insertions(+), 16 deletions(-) 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> { diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs index 6afcaee3..cd450b55 100644 --- a/web/src/widget/button.rs +++ b/web/src/widget/button.rs @@ -21,19 +21,25 @@ use dodrio::bumpalo; /// .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_web::{button, Button, Text}; /// # -/// # 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> { content: Element<'a, Message>, -- cgit From d1797dda877a7012d16981a399c5e6d4b8daf2bf Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Thu, 10 Jun 2021 18:47:39 +0700 Subject: Revert changes in `tour` example --- examples/tour/src/main.rs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 626d2b52..1215f83d 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -70,15 +70,13 @@ impl Sandbox for Tour { controls = controls.push(Space::with_width(Length::Fill)); - let mut button = button(next_button, "Next"); - button = if steps.can_continue() { - button - .style(style::Button::Primary) - .on_press(Message::NextPressed) - } else { - button.style(style::Button::Disabled) - }; - controls = controls.push(button); + if steps.can_continue() { + controls = controls.push( + button(next_button, "Next") + .on_press(Message::NextPressed) + .style(style::Button::Primary), + ); + } let content: Element<_> = Column::new() .max_width(540) @@ -792,7 +790,6 @@ mod style { pub enum Button { Primary, Secondary, - Disabled, } impl button::StyleSheet for Button { @@ -800,8 +797,7 @@ mod style { button::Style { background: Some(Background::Color(match self { Button::Primary => Color::from_rgb(0.11, 0.42, 0.87), - Button::Secondary => Color::from_rgb(0.055, 0.21, 0.435), - Button::Disabled => Color::from_rgb(0.5, 0.5, 0.5), + Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5), })), border_radius: 12.0, shadow_offset: Vector::new(1.0, 1.0), -- cgit From 6469e463cd1f99190c6eba4701d4c1059934d3ee Mon Sep 17 00:00:00 2001 From: Yusuf Bera Ertan Date: Mon, 14 Jun 2021 18:06:28 +0300 Subject: feat: expose draw_cache_multithread --- Cargo.toml | 4 ++++ glow/Cargo.toml | 1 + glow/src/text.rs | 4 +++- wgpu/Cargo.toml | 1 + wgpu/src/text.rs | 4 +++- 5 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6d894eba..acae878f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,8 @@ canvas = ["iced_wgpu/canvas"] qr_code = ["iced_wgpu/qr_code"] # Enables using system fonts default_system_font = ["iced_wgpu/default_system_font"] +# Enables draw_cache_multithread for glyph drawing +glyph_draw_cache_multithread = ["iced_wgpu/glyph_draw_cache_multithread"] # Enables the `iced_glow` renderer. Overrides `iced_wgpu` glow = ["iced_glow", "iced_glutin"] # Enables the `Canvas` widget for `iced_glow` @@ -33,6 +35,8 @@ glow_canvas = ["iced_glow/canvas"] glow_qr_code = ["iced_glow/qr_code"] # Enables using system fonts for `iced_glow` glow_default_system_font = ["iced_glow/default_system_font"] +# Enables draw_cache_multithread for `iced_glow` glyph drawing +glow_glyph_draw_cache_multithread = ["iced_glow/glyph_draw_cache_multithread"] # Enables a debug view in native platforms (press F12) debug = ["iced_winit/debug"] # Enables `tokio` as the `executor::Default` on native platforms diff --git a/glow/Cargo.toml b/glow/Cargo.toml index e40b8ba8..f5934f8f 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -11,6 +11,7 @@ repository = "https://github.com/hecrj/iced" canvas = ["iced_graphics/canvas"] qr_code = ["iced_graphics/qr_code"] default_system_font = ["iced_graphics/font-source"] +glyph_draw_cache_multithread = [] # Not supported yet! image = [] svg = [] diff --git a/glow/src/text.rs b/glow/src/text.rs index 925c7287..7c9574a2 100644 --- a/glow/src/text.rs +++ b/glow/src/text.rs @@ -41,7 +41,9 @@ impl Pipeline { let draw_brush = glow_glyph::GlyphBrushBuilder::using_font(font.clone()) .initial_cache_size((2048, 2048)) - .draw_cache_multithread(false) // TODO: Expose as a configuration flag + .draw_cache_multithread(cfg!( + feature = "glyph_draw_cache_multithread" + )) .build(&gl); let measure_brush = diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 3eacdd0f..fc9f7379 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -24,6 +24,7 @@ farbfeld = ["image_rs/farbfeld"] canvas = ["iced_graphics/canvas"] qr_code = ["iced_graphics/qr_code"] default_system_font = ["iced_graphics/font-source"] +glyph_draw_cache_multithread = [] [dependencies] wgpu = "0.8" diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 4d92d9e9..eed5de38 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -46,7 +46,9 @@ impl Pipeline { let draw_brush = wgpu_glyph::GlyphBrushBuilder::using_font(font.clone()) .initial_cache_size((2048, 2048)) - .draw_cache_multithread(false) // TODO: Expose as a configuration flag + .draw_cache_multithread(cfg!( + feature = "glyph_draw_cache_multithread" + )) .build(device, format); let measure_brush = -- 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 --- futures/src/subscription.rs | 6 +++--- glow/src/widget/pane_grid.rs | 2 +- graphics/src/widget/pane_grid.rs | 2 +- native/src/user_interface.rs | 2 +- native/src/widget.rs | 10 +++++----- native/src/widget/pane_grid.rs | 2 +- src/application.rs | 18 +++++++++--------- src/sandbox.rs | 22 +++++++++++----------- web/src/lib.rs | 2 +- wgpu/src/widget/pane_grid.rs | 2 +- 10 files changed, 34 insertions(+), 34 deletions(-) diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs index 27d2d295..e60ad79a 100644 --- a/futures/src/subscription.rs +++ b/futures/src/subscription.rs @@ -125,9 +125,9 @@ impl std::fmt::Debug for Subscription { /// - [`stopwatch`], a watch with start/stop and reset buttons showcasing how /// to listen to time. /// -/// [examples]: https://github.com/hecrj/iced/tree/0.2/examples -/// [`download_progress`]: https://github.com/hecrj/iced/tree/0.2/examples/download_progress -/// [`stopwatch`]: https://github.com/hecrj/iced/tree/0.2/examples/stopwatch +/// [examples]: https://github.com/hecrj/iced/tree/0.3/examples +/// [`download_progress`]: https://github.com/hecrj/iced/tree/0.3/examples/download_progress +/// [`stopwatch`]: https://github.com/hecrj/iced/tree/0.3/examples/stopwatch pub trait Recipe { /// The events that will be produced by a [`Subscription`] with this /// [`Recipe`]. diff --git a/glow/src/widget/pane_grid.rs b/glow/src/widget/pane_grid.rs index 44f9201c..fc36862c 100644 --- a/glow/src/widget/pane_grid.rs +++ b/glow/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 use crate::Renderer; pub use iced_graphics::pane_grid::{ diff --git a/graphics/src/widget/pane_grid.rs b/graphics/src/widget/pane_grid.rs index 93b4b672..92cdbb77 100644 --- a/graphics/src/widget/pane_grid.rs +++ b/graphics/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 use crate::defaults; use crate::{Backend, Color, Primitive, Renderer}; use iced_native::container; 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; diff --git a/src/application.rs b/src/application.rs index ee532e0b..bda8558c 100644 --- a/src/application.rs +++ b/src/application.rs @@ -39,15 +39,15 @@ use crate::{ /// to listen to time. /// - [`todos`], a todos tracker inspired by [TodoMVC]. /// -/// [The repository has a bunch of examples]: https://github.com/hecrj/iced/tree/0.2/examples -/// [`clock`]: https://github.com/hecrj/iced/tree/0.2/examples/clock -/// [`download_progress`]: https://github.com/hecrj/iced/tree/0.2/examples/download_progress -/// [`events`]: https://github.com/hecrj/iced/tree/0.2/examples/events -/// [`game_of_life`]: https://github.com/hecrj/iced/tree/0.2/examples/game_of_life -/// [`pokedex`]: https://github.com/hecrj/iced/tree/0.2/examples/pokedex -/// [`solar_system`]: https://github.com/hecrj/iced/tree/0.2/examples/solar_system -/// [`stopwatch`]: https://github.com/hecrj/iced/tree/0.2/examples/stopwatch -/// [`todos`]: https://github.com/hecrj/iced/tree/0.2/examples/todos +/// [The repository has a bunch of examples]: https://github.com/hecrj/iced/tree/0.3/examples +/// [`clock`]: https://github.com/hecrj/iced/tree/0.3/examples/clock +/// [`download_progress`]: https://github.com/hecrj/iced/tree/0.3/examples/download_progress +/// [`events`]: https://github.com/hecrj/iced/tree/0.3/examples/events +/// [`game_of_life`]: https://github.com/hecrj/iced/tree/0.3/examples/game_of_life +/// [`pokedex`]: https://github.com/hecrj/iced/tree/0.3/examples/pokedex +/// [`solar_system`]: https://github.com/hecrj/iced/tree/0.3/examples/solar_system +/// [`stopwatch`]: https://github.com/hecrj/iced/tree/0.3/examples/stopwatch +/// [`todos`]: https://github.com/hecrj/iced/tree/0.3/examples/todos /// [`Sandbox`]: crate::Sandbox /// [`Canvas`]: crate::widget::Canvas /// [PokéAPI]: https://pokeapi.co/ diff --git a/src/sandbox.rs b/src/sandbox.rs index 9dd17b7e..10b05a92 100644 --- a/src/sandbox.rs +++ b/src/sandbox.rs @@ -36,19 +36,19 @@ use crate::{ /// - [`tour`], a simple UI tour that can run both on native platforms and the /// web! /// -/// [The repository has a bunch of examples]: https://github.com/hecrj/iced/tree/0.2/examples -/// [`bezier_tool`]: https://github.com/hecrj/iced/tree/0.2/examples/bezier_tool -/// [`counter`]: https://github.com/hecrj/iced/tree/0.2/examples/counter -/// [`custom_widget`]: https://github.com/hecrj/iced/tree/0.2/examples/custom_widget -/// [`geometry`]: https://github.com/hecrj/iced/tree/0.2/examples/geometry -/// [`pane_grid`]: https://github.com/hecrj/iced/tree/0.2/examples/pane_grid -/// [`progress_bar`]: https://github.com/hecrj/iced/tree/0.2/examples/progress_bar -/// [`styling`]: https://github.com/hecrj/iced/tree/0.2/examples/styling -/// [`svg`]: https://github.com/hecrj/iced/tree/0.2/examples/svg -/// [`tour`]: https://github.com/hecrj/iced/tree/0.2/examples/tour +/// [The repository has a bunch of examples]: https://github.com/hecrj/iced/tree/0.3/examples +/// [`bezier_tool`]: https://github.com/hecrj/iced/tree/0.3/examples/bezier_tool +/// [`counter`]: https://github.com/hecrj/iced/tree/0.3/examples/counter +/// [`custom_widget`]: https://github.com/hecrj/iced/tree/0.3/examples/custom_widget +/// [`geometry`]: https://github.com/hecrj/iced/tree/0.3/examples/geometry +/// [`pane_grid`]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid +/// [`progress_bar`]: https://github.com/hecrj/iced/tree/0.3/examples/progress_bar +/// [`styling`]: https://github.com/hecrj/iced/tree/0.3/examples/styling +/// [`svg`]: https://github.com/hecrj/iced/tree/0.3/examples/svg +/// [`tour`]: https://github.com/hecrj/iced/tree/0.3/examples/tour /// [`Canvas widget`]: crate::widget::Canvas /// [the overview]: index.html#overview -/// [`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.2/wgpu +/// [`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.3/wgpu /// [`Svg` widget]: crate::widget::Svg /// [Ghostscript Tiger]: https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg /// diff --git a/web/src/lib.rs b/web/src/lib.rs index e4271d58..158416b9 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -49,7 +49,7 @@ //! //! [`wasm-pack`]: https://github.com/rustwasm/wasm-pack //! [`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen -//! [`tour` example]: https://github.com/hecrj/iced/tree/0.2/examples/tour +//! [`tour` example]: https://github.com/hecrj/iced/tree/0.3/examples/tour #![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] diff --git a/wgpu/src/widget/pane_grid.rs b/wgpu/src/widget/pane_grid.rs index 44f9201c..fc36862c 100644 --- a/wgpu/src/widget/pane_grid.rs +++ b/wgpu/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 use crate::Renderer; pub use iced_graphics::pane_grid::{ -- 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(-) 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 c70f90f3206f7f5176831b7896d8acc147dd2c53 Mon Sep 17 00:00:00 2001 From: Poly Date: Sun, 20 Jun 2021 11:21:02 +0200 Subject: Update wgpu 0.9 --- wgpu/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 3eacdd0f..9675797d 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -26,8 +26,8 @@ qr_code = ["iced_graphics/qr_code"] default_system_font = ["iced_graphics/font-source"] [dependencies] -wgpu = "0.8" -wgpu_glyph = "0.12" +wgpu = "0.9" +wgpu_glyph = "0.13" glyph_brush = "0.7" raw-window-handle = "0.3" log = "0.4" -- cgit From a53e7559fe01ee072d4f0533ecf1facec741b9e4 Mon Sep 17 00:00:00 2001 From: Poly Date: Sun, 20 Jun 2021 11:29:23 +0200 Subject: Use vertex_attr_array macro --- wgpu/src/image.rs | 34 +++++++--------------------------- wgpu/src/quad.rs | 40 ++++++++-------------------------------- wgpu/src/triangle.rs | 16 ++++------------ 3 files changed, 19 insertions(+), 71 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index cd7b3d23..85663bf5 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -165,33 +165,13 @@ impl Pipeline { wgpu::VertexBufferLayout { array_stride: mem::size_of::() as u64, step_mode: wgpu::InputStepMode::Instance, - attributes: &[ - wgpu::VertexAttribute { - shader_location: 1, - format: wgpu::VertexFormat::Float32x2, - offset: 0, - }, - wgpu::VertexAttribute { - shader_location: 2, - format: wgpu::VertexFormat::Float32x2, - offset: 4 * 2, - }, - wgpu::VertexAttribute { - shader_location: 3, - format: wgpu::VertexFormat::Float32x2, - offset: 4 * 4, - }, - wgpu::VertexAttribute { - shader_location: 4, - format: wgpu::VertexFormat::Float32x2, - offset: 4 * 6, - }, - wgpu::VertexAttribute { - shader_location: 5, - format: wgpu::VertexFormat::Sint32, - offset: 4 * 8, - }, - ], + attributes: &wgpu::vertex_attr_array!( + 1 => Float32x2, + 2 => Float32x2, + 3 => Float32x2, + 4 => Float32x2, + 5 => Sint32, + ), }, ], }, diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 6f221ca3..93942fba 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -87,38 +87,14 @@ impl Pipeline { wgpu::VertexBufferLayout { array_stride: mem::size_of::() as u64, step_mode: wgpu::InputStepMode::Instance, - attributes: &[ - wgpu::VertexAttribute { - shader_location: 1, - format: wgpu::VertexFormat::Float32x2, - offset: 0, - }, - wgpu::VertexAttribute { - shader_location: 2, - format: wgpu::VertexFormat::Float32x2, - offset: 4 * 2, - }, - wgpu::VertexAttribute { - shader_location: 3, - format: wgpu::VertexFormat::Float32x4, - offset: 4 * (2 + 2), - }, - wgpu::VertexAttribute { - shader_location: 4, - format: wgpu::VertexFormat::Float32x4, - offset: 4 * (2 + 2 + 4), - }, - wgpu::VertexAttribute { - shader_location: 5, - format: wgpu::VertexFormat::Float32, - offset: 4 * (2 + 2 + 4 + 4), - }, - wgpu::VertexAttribute { - shader_location: 6, - format: wgpu::VertexFormat::Float32, - offset: 4 * (2 + 2 + 4 + 4 + 1), - }, - ], + attributes: &wgpu::vertex_attr_array!( + 1 => Float32x2, + 2 => Float32x2, + 3 => Float32x4, + 4 => Float32x4, + 5 => Float32, + 6 => Float32, + ), }, ], }, diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 8636b331..65b2b7b0 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -150,20 +150,12 @@ impl Pipeline { buffers: &[wgpu::VertexBufferLayout { array_stride: mem::size_of::() as u64, step_mode: wgpu::InputStepMode::Vertex, - attributes: &[ + attributes: &wgpu::vertex_attr_array!( // Position - wgpu::VertexAttribute { - shader_location: 0, - format: wgpu::VertexFormat::Float32x2, - offset: 0, - }, + 0 => Float32x2, // Color - wgpu::VertexAttribute { - shader_location: 1, - format: wgpu::VertexFormat::Float32x4, - offset: 4 * 2, - }, - ], + 1 => Float32x4, + ), }], }, fragment: Some(wgpu::FragmentState { -- 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(-) 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 80df17ab5561ffe2bc814a164958418ebc25d38b Mon Sep 17 00:00:00 2001 From: Diego Fujii Date: Thu, 24 Jun 2021 00:16:08 +0900 Subject: fix-typo --- graphics/src/widget/canvas/program.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphics/src/widget/canvas/program.rs b/graphics/src/widget/canvas/program.rs index d703caad..85a2f67b 100644 --- a/graphics/src/widget/canvas/program.rs +++ b/graphics/src/widget/canvas/program.rs @@ -34,7 +34,7 @@ pub trait Program { /// [`Geometry`] can be easily generated with a [`Frame`] or stored in a /// [`Cache`]. /// - /// [`Frame`]: crate::widget::canvas::Cache + /// [`Frame`]: crate::widget::canvas::Frame /// [`Cache`]: crate::widget::canvas::Cache fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec; -- cgit From ba51661a2a7aa136d9c9d83f87df52ea60f7b51b Mon Sep 17 00:00:00 2001 From: Imbris Date: Tue, 15 Jun 2021 02:47:54 -0400 Subject: Bump winit to 0.25 --- glutin/Cargo.toml | 2 +- winit/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml index 5d2c5be7..92aa5018 100644 --- a/glutin/Cargo.toml +++ b/glutin/Cargo.toml @@ -14,7 +14,7 @@ categories = ["gui"] debug = ["iced_winit/debug"] [dependencies] -glutin = "0.26" +glutin = "0.27" [dependencies.iced_native] version = "0.4" diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 7f117fa1..4bb46029 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -14,7 +14,7 @@ categories = ["gui"] debug = ["iced_native/debug"] [dependencies] -winit = "0.24" +winit = "0.25" window_clipboard = "0.2" log = "0.4" thiserror = "1.0" -- 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 --- Cargo.toml | 4 +++ examples/url_handler/Cargo.toml | 12 +++++++ examples/url_handler/src/main.rs | 67 ++++++++++++++++++++++++++++++++++++++++ glutin/src/application.rs | 3 ++ native/src/event.rs | 4 +++ winit/src/application.rs | 3 ++ 6 files changed, 93 insertions(+) create mode 100644 examples/url_handler/Cargo.toml create mode 100644 examples/url_handler/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 6d894eba..f00a197c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,6 +84,7 @@ members = [ "examples/todos", "examples/tour", "examples/tooltip", + "examples/url_handler", ] [dependencies] @@ -91,6 +92,9 @@ iced_core = { version = "0.4", path = "core" } iced_futures = { version = "0.3", path = "futures" } thiserror = "1.0" +[patch.crates-io] +winit = { git="https://github.com/cryptowatch/winit", rev="f9180f3b3c0f4fb8fd8c65bd0adf641cd6b32dd0" } + [target.'cfg(not(target_arch = "wasm32"))'.dependencies] iced_winit = { version = "0.3", path = "winit" } iced_glutin = { version = "0.2", path = "glutin", optional = true } diff --git a/examples/url_handler/Cargo.toml b/examples/url_handler/Cargo.toml new file mode 100644 index 00000000..595bdac0 --- /dev/null +++ b/examples/url_handler/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "url_handler" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../.." } +iced_native = { path = "../../native" } +syslog="4.0" +log="0.4" \ No newline at end of file diff --git a/examples/url_handler/src/main.rs b/examples/url_handler/src/main.rs new file mode 100644 index 00000000..56d81031 --- /dev/null +++ b/examples/url_handler/src/main.rs @@ -0,0 +1,67 @@ +use iced::{ + executor, Application, Command, Clipboard, + Container, Element, Length, Settings, Subscription, Text, +}; +use iced_native::Event; + +pub fn main() -> iced::Result { + App::run(Settings::default()) +} + +#[derive(Debug, Default)] +struct App { + url: Option, +} + +#[derive(Debug, Clone)] +enum Message { + EventOccurred(iced_native::Event), +} + +impl Application for App { + type Executor = executor::Default; + type Message = Message; + type Flags = (); + + fn new(_flags: ()) -> (App, Command) { + (App::default(), Command::none()) + } + + fn title(&self) -> String { + String::from("Url - Iced") + } + + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command { + match message { + Message::EventOccurred(event) => { + if let Event::UrlReceived(url) = event{ + self.url = Some(url); + } + } + }; + + Command::none() + } + + fn subscription(&self) -> Subscription { + iced_native::subscription::events().map(Message::EventOccurred) + } + + fn view(&mut self) -> Element { + let content = match &self.url{ + Some(url) => Text::new(format!("{}", url)), + None => Text::new("No URL received yet!") + }; + + Container::new(content.size(48)) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 79fcf745..55293b3b 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -237,6 +237,9 @@ async fn run_instance( context.window().request_redraw(); } + event::Event::ReceivedUrl(url) => { + events.push(iced_native::Event::UrlReceived(url)); + } event::Event::UserEvent(message) => { messages.push(message); } 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. diff --git a/winit/src/application.rs b/winit/src/application.rs index b1d5f418..9f51ae68 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -310,6 +310,9 @@ async fn run_instance( window.request_redraw(); } + event::Event::ReceivedUrl(url) => { + events.push(iced_native::Event::UrlReceived(url)); + } event::Event::UserEvent(message) => { messages.push(message); } -- 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 --- Cargo.toml | 2 +- examples/url_handler/Cargo.toml | 4 +--- examples/url_handler/src/main.rs | 18 ++++++++++++------ glutin/src/application.rs | 5 +++-- native/src/event.rs | 25 +++++++++++++++++++++---- winit/src/application.rs | 5 +++-- 6 files changed, 41 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f00a197c..789ece88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,7 +93,7 @@ iced_futures = { version = "0.3", path = "futures" } thiserror = "1.0" [patch.crates-io] -winit = { git="https://github.com/cryptowatch/winit", rev="f9180f3b3c0f4fb8fd8c65bd0adf641cd6b32dd0" } +winit = { git="https://github.com/iced-rs/winit", rev="152eda9b2d995dd0f5b886a53bddac7c75738b47" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] iced_winit = { version = "0.3", path = "winit" } diff --git a/examples/url_handler/Cargo.toml b/examples/url_handler/Cargo.toml index 595bdac0..911b2f25 100644 --- a/examples/url_handler/Cargo.toml +++ b/examples/url_handler/Cargo.toml @@ -7,6 +7,4 @@ publish = false [dependencies] iced = { path = "../.." } -iced_native = { path = "../../native" } -syslog="4.0" -log="0.4" \ No newline at end of file +iced_native = { path = "../../native" } \ No newline at end of file diff --git a/examples/url_handler/src/main.rs b/examples/url_handler/src/main.rs index 56d81031..f14e5227 100644 --- a/examples/url_handler/src/main.rs +++ b/examples/url_handler/src/main.rs @@ -1,8 +1,11 @@ use iced::{ - executor, Application, Command, Clipboard, - Container, Element, Length, Settings, Subscription, Text, + executor, Application, Clipboard, Command, Container, Element, Length, + Settings, Subscription, Text, +}; +use iced_native::{ + event::{MacOS, PlatformSpecific}, + Event, }; -use iced_native::Event; pub fn main() -> iced::Result { App::run(Settings::default()) @@ -38,7 +41,10 @@ impl Application for App { ) -> Command { match message { Message::EventOccurred(event) => { - if let Event::UrlReceived(url) = event{ + if let Event::PlatformSpecific(PlatformSpecific::MacOS( + MacOS::ReceivedUrl(url), + )) = event + { self.url = Some(url); } } @@ -52,9 +58,9 @@ impl Application for App { } fn view(&mut self) -> Element { - let content = match &self.url{ + let content = match &self.url { Some(url) => Text::new(format!("{}", url)), - None => Text::new("No URL received yet!") + None => Text::new("No URL received yet!"), }; Container::new(content.size(48)) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 55293b3b..22dff149 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -237,8 +237,9 @@ async fn run_instance( context.window().request_redraw(); } - event::Event::ReceivedUrl(url) => { - events.push(iced_native::Event::UrlReceived(url)); + event::Event::PlatformSpecific(event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl(url))) => { + use iced_native::event; + events.push(iced_native::Event::PlatformSpecific(event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl(url)))); } event::Event::UserEvent(message) => { messages.push(message); 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. diff --git a/winit/src/application.rs b/winit/src/application.rs index 9f51ae68..ce57bd1d 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -310,8 +310,9 @@ async fn run_instance( window.request_redraw(); } - event::Event::ReceivedUrl(url) => { - events.push(iced_native::Event::UrlReceived(url)); + event::Event::PlatformSpecific(event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl(url))) => { + use iced_native::event; + events.push(iced_native::Event::PlatformSpecific(event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl(url)))); } event::Event::UserEvent(message) => { messages.push(message); -- cgit From 612585109ffc9a14a507c3c8423c6aa790c35cbf Mon Sep 17 00:00:00 2001 From: Richard Date: Mon, 14 Jun 2021 17:21:55 -0300 Subject: Use `winit` and `glutin` forks in `iced-rs` org --- Cargo.toml | 3 --- glutin/Cargo.toml | 6 ++++-- glutin/src/application.rs | 10 ++++++++-- winit/Cargo.toml | 6 +++++- winit/src/application.rs | 10 ++++++++-- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 789ece88..329877c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,9 +92,6 @@ iced_core = { version = "0.4", path = "core" } iced_futures = { version = "0.3", path = "futures" } thiserror = "1.0" -[patch.crates-io] -winit = { git="https://github.com/iced-rs/winit", rev="152eda9b2d995dd0f5b886a53bddac7c75738b47" } - [target.'cfg(not(target_arch = "wasm32"))'.dependencies] iced_winit = { version = "0.3", path = "winit" } iced_glutin = { version = "0.2", path = "glutin", optional = true } diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml index 92aa5018..913ff5b8 100644 --- a/glutin/Cargo.toml +++ b/glutin/Cargo.toml @@ -13,8 +13,10 @@ categories = ["gui"] [features] debug = ["iced_winit/debug"] -[dependencies] -glutin = "0.27" +[dependencies.glutin] +version = "0.27" +git = "https://github.com/iced-rs/glutin" +rev = "be6793b5b3defc9452cd1c896cd315ed7442d546" [dependencies.iced_native] version = "0.4" diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 22dff149..a8e5dbf9 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -237,9 +237,15 @@ async fn run_instance( context.window().request_redraw(); } - event::Event::PlatformSpecific(event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl(url))) => { + event::Event::PlatformSpecific(event::PlatformSpecific::MacOS( + event::MacOS::ReceivedUrl(url), + )) => { use iced_native::event; - events.push(iced_native::Event::PlatformSpecific(event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl(url)))); + events.push(iced_native::Event::PlatformSpecific( + event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl( + url, + )), + )); } event::Event::UserEvent(message) => { messages.push(message); diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 4bb46029..b926a9c5 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -14,11 +14,15 @@ categories = ["gui"] debug = ["iced_native/debug"] [dependencies] -winit = "0.25" window_clipboard = "0.2" log = "0.4" thiserror = "1.0" +[dependencies.winit] +version = "0.25" +git = "https://github.com/iced-rs/winit" +rev = "9c358959ed99736566d50a511b03d2fed3aac2ae" + [dependencies.iced_native] version = "0.4" path = "../native" diff --git a/winit/src/application.rs b/winit/src/application.rs index ce57bd1d..49f2f513 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -310,9 +310,15 @@ async fn run_instance( window.request_redraw(); } - event::Event::PlatformSpecific(event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl(url))) => { + event::Event::PlatformSpecific(event::PlatformSpecific::MacOS( + event::MacOS::ReceivedUrl(url), + )) => { use iced_native::event; - events.push(iced_native::Event::PlatformSpecific(event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl(url)))); + events.push(iced_native::Event::PlatformSpecific( + event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl( + url, + )), + )); } event::Event::UserEvent(message) => { messages.push(message); -- cgit From 52d44769a332d1d96a5e9292805a5884073b9185 Mon Sep 17 00:00:00 2001 From: TimUntersberger Date: Fri, 25 Jun 2021 15:38:30 +0200 Subject: add initial attempt at adding winit example --- Cargo.toml | 1 + examples/winit/Cargo.toml | 9 +++++++ examples/winit/README.md | 18 ++++++++++++++ examples/winit/src/main.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+) create mode 100644 examples/winit/Cargo.toml create mode 100644 examples/winit/README.md create mode 100644 examples/winit/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 329877c8..b5ceb20f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,7 @@ members = [ "examples/counter", "examples/custom_widget", "examples/download_progress", + "examples/winit", "examples/events", "examples/game_of_life", "examples/geometry", diff --git a/examples/winit/Cargo.toml b/examples/winit/Cargo.toml new file mode 100644 index 00000000..6d16a7a9 --- /dev/null +++ b/examples/winit/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "counter_winit" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced_winit = { path = "../../winit" } diff --git a/examples/winit/README.md b/examples/winit/README.md new file mode 100644 index 00000000..4d9fc5b9 --- /dev/null +++ b/examples/winit/README.md @@ -0,0 +1,18 @@ +## Counter + +The classic counter example explained in the [`README`](../../README.md). + +The __[`main`]__ file contains all the code of the example. + + + +You can run it with `cargo run`: +``` +cargo run --package counter +``` + +[`main`]: src/main.rs diff --git a/examples/winit/src/main.rs b/examples/winit/src/main.rs new file mode 100644 index 00000000..890ded79 --- /dev/null +++ b/examples/winit/src/main.rs @@ -0,0 +1,62 @@ +use iced_winit::{button, Align, Button, Column, Element, Application, Settings, Text, Renderer, Program, Command}; + +pub fn main() { + Counter::run(Settings::default()).unwrap() +} + +#[derive(Default)] +struct Counter { + value: i32, + increment_button: button::State, + decrement_button: button::State, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + IncrementPressed, + DecrementPressed, +} + +impl Application for Counter { + type Flags = (); + + fn new(flags: Self::Flags) -> (Self, Command) { + (Self::default(), Command::none()) + } + + fn title(&self) -> String { + String::from("Counter with winit - Iced") + } +} + +impl Program for Counter { + type Renderer = Renderer; + type Message = Message; + + fn update(&mut self, message: Message) { + match message { + Message::IncrementPressed => { + self.value += 1; + } + Message::DecrementPressed => { + self.value -= 1; + } + } + } + + fn view(&mut self) -> Element { + Column::new() + .padding(20) + .align_items(Align::Center) + .push( + Button::new(&mut self.increment_button, Text::new("Increment")) + .on_press(Message::IncrementPressed), + ) + .push(Text::new(self.value.to_string()).size(50)) + .push( + Button::new(&mut self.decrement_button, Text::new("Decrement")) + .on_press(Message::DecrementPressed), + ) + .into() + } +} -- cgit From 5c45d36d1a8cfd92cd1a454a7f4deedcd4d13fe7 Mon Sep 17 00:00:00 2001 From: TimUntersberger Date: Fri, 25 Jun 2021 17:16:44 +0200 Subject: wip --- examples/winit/Cargo.toml | 2 +- examples/winit/src/main.rs | 27 +++++++++++++++------------ src/window/settings.rs | 7 +++++++ winit/src/settings.rs | 4 ++++ 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/examples/winit/Cargo.toml b/examples/winit/Cargo.toml index 6d16a7a9..2d58f031 100644 --- a/examples/winit/Cargo.toml +++ b/examples/winit/Cargo.toml @@ -6,4 +6,4 @@ edition = "2018" publish = false [dependencies] -iced_winit = { path = "../../winit" } +iced = { path = "../.." } diff --git a/examples/winit/src/main.rs b/examples/winit/src/main.rs index 890ded79..202d9b5a 100644 --- a/examples/winit/src/main.rs +++ b/examples/winit/src/main.rs @@ -1,7 +1,15 @@ -use iced_winit::{button, Align, Button, Column, Element, Application, Settings, Text, Renderer, Program, Command}; +use iced::{button, Align, Button, Column, Element, Sandbox, Settings, window::Settings as WindowSettings, Text}; pub fn main() { - Counter::run(Settings::default()).unwrap() + let settings = Settings { + window: WindowSettings { + size: (400, 200), + position: (100, 100), + ..Default::default() + }, + ..Default::default() + }; + Counter::run(settings).unwrap() } #[derive(Default)] @@ -17,21 +25,16 @@ enum Message { DecrementPressed, } -impl Application for Counter { - type Flags = (); +impl Sandbox for Counter { + type Message = Message; - fn new(flags: Self::Flags) -> (Self, Command) { - (Self::default(), Command::none()) + fn new() -> Self { + Self::default() } fn title(&self) -> String { String::from("Counter with winit - Iced") } -} - -impl Program for Counter { - type Renderer = Renderer; - type Message = Message; fn update(&mut self, message: Message) { match message { @@ -44,7 +47,7 @@ impl Program for Counter { } } - fn view(&mut self) -> Element { + fn view(&mut self) -> Element { Column::new() .padding(20) .align_items(Align::Center) diff --git a/src/window/settings.rs b/src/window/settings.rs index 6b5d2985..981f0858 100644 --- a/src/window/settings.rs +++ b/src/window/settings.rs @@ -6,6 +6,11 @@ pub struct Settings { /// The initial size of the window. pub size: (u32, u32), + /// The initial position of the window. + /// + /// Note: this gets ignored on the web + pub position: (u32, u32), + /// The minimum size of the window. pub min_size: Option<(u32, u32)>, @@ -32,6 +37,7 @@ impl Default for Settings { fn default() -> Settings { Settings { size: (1024, 768), + position: (100, 100), min_size: None, max_size: None, resizable: true, @@ -48,6 +54,7 @@ impl From for iced_winit::settings::Window { fn from(settings: Settings) -> Self { Self { size: settings.size, + position: settings.position, min_size: settings.min_size, max_size: settings.max_size, resizable: settings.resizable, diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 941d88ce..0508cd9b 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -35,6 +35,9 @@ pub struct Window { /// The size of the window. pub size: (u32, u32), + /// The position of the window. + pub position: (u32, u32), + /// The minimum size of the window. pub min_size: Option<(u32, u32)>, @@ -75,6 +78,7 @@ impl Window { window_builder = window_builder .with_title(title) .with_inner_size(winit::dpi::LogicalSize { width, height }) + .with_outer_position(self.position) .with_resizable(self.resizable) .with_decorations(self.decorations) .with_transparent(self.transparent) -- cgit From de79a01b88e1610d374ed06077ac78f3e10b9c3d Mon Sep 17 00:00:00 2001 From: TimUntersberger Date: Fri, 25 Jun 2021 18:03:18 +0200 Subject: done --- examples/winit/src/main.rs | 63 ++++++++++++++++------------------------------ src/window/settings.rs | 17 ++++++++++++- winit/src/settings.rs | 5 ++-- 3 files changed, 41 insertions(+), 44 deletions(-) diff --git a/examples/winit/src/main.rs b/examples/winit/src/main.rs index 202d9b5a..a1364ea6 100644 --- a/examples/winit/src/main.rs +++ b/examples/winit/src/main.rs @@ -1,65 +1,46 @@ -use iced::{button, Align, Button, Column, Element, Sandbox, Settings, window::Settings as WindowSettings, Text}; +use iced::{Column, Element, Sandbox, Settings, window::Settings as WindowSettings}; + +const WINDOW_WIDTH: i32 = 200; +const WINDOW_HEIGHT: i32 = 200; +const DISPLAY_WIDTH: i32 = 1920; +const DISPLAY_HEIGHT: i32 = 1080; +// These numbers are specific to a 1920x1080 monitor +const BORDER_X: i32 = 8; +const BORDER_Y: i32 = 2; +const CAPTION_HEIGHT: i32 = 4; pub fn main() { + let x = DISPLAY_WIDTH / 2 - WINDOW_WIDTH / 2 - BORDER_X; + let y = DISPLAY_HEIGHT / 2 - WINDOW_HEIGHT / 2 - BORDER_Y - CAPTION_HEIGHT; let settings = Settings { window: WindowSettings { - size: (400, 200), - position: (100, 100), + size: (WINDOW_WIDTH as u32, WINDOW_HEIGHT as u32), + position: (x, y), ..Default::default() }, ..Default::default() }; - Counter::run(settings).unwrap() + Winit::run(settings).unwrap() } #[derive(Default)] -struct Counter { - value: i32, - increment_button: button::State, - decrement_button: button::State, -} - -#[derive(Debug, Clone, Copy)] -enum Message { - IncrementPressed, - DecrementPressed, -} +struct Winit; -impl Sandbox for Counter { - type Message = Message; +impl Sandbox for Winit { + type Message = (); fn new() -> Self { Self::default() } fn title(&self) -> String { - String::from("Counter with winit - Iced") + String::from("winit - Iced") } - fn update(&mut self, message: Message) { - match message { - Message::IncrementPressed => { - self.value += 1; - } - Message::DecrementPressed => { - self.value -= 1; - } - } + fn update(&mut self, _message: Self::Message) { } - fn view(&mut self) -> Element { - Column::new() - .padding(20) - .align_items(Align::Center) - .push( - Button::new(&mut self.increment_button, Text::new("Increment")) - .on_press(Message::IncrementPressed), - ) - .push(Text::new(self.value.to_string()).size(50)) - .push( - Button::new(&mut self.decrement_button, Text::new("Decrement")) - .on_press(Message::DecrementPressed), - ) - .into() + fn view(&mut self) -> Element { + Column::new().into() } } diff --git a/src/window/settings.rs b/src/window/settings.rs index 981f0858..0fecc3bb 100644 --- a/src/window/settings.rs +++ b/src/window/settings.rs @@ -8,8 +8,23 @@ pub struct Settings { /// The initial position of the window. /// + /// When the decorations of the window are enabled, Windows 10 will add some inivisble padding + /// to the window. This padding gets included in the position. So if you have decorations + /// enabled and want the window to be at 0,0 you would have to set the position to + /// -DPI_BORDER_X,-DPI_BORDER_Y. + /// + /// DPI_BORDER_X/DPI_BORDER_Y are the usual size of the padding, which changes based on the DPI of the display. + /// + /// On a 1920x1080 monitor you would have to set the position to -8,-2. + /// + /// For info on how you could implement positioning that supports all DPI monitors look at the + /// following WINAPI calls: + /// + /// * GetDpiForMonitor (with MDT_RAW_DPI) + /// * GetSystemMetricsForDpi (with SM_CXFRAME and SM_CYFRAME) + /// /// Note: this gets ignored on the web - pub position: (u32, u32), + pub position: (i32, i32), /// The minimum size of the window. pub min_size: Option<(u32, u32)>, diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 0508cd9b..1769c676 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -36,7 +36,7 @@ pub struct Window { pub size: (u32, u32), /// The position of the window. - pub position: (u32, u32), + pub position: (i32, i32), /// The minimum size of the window. pub min_size: Option<(u32, u32)>, @@ -78,7 +78,7 @@ impl Window { window_builder = window_builder .with_title(title) .with_inner_size(winit::dpi::LogicalSize { width, height }) - .with_outer_position(self.position) + .with_position(winit::dpi::LogicalPosition { x: self.position.0, y: self.position.1 }) .with_resizable(self.resizable) .with_decorations(self.decorations) .with_transparent(self.transparent) @@ -116,6 +116,7 @@ impl Default for Window { fn default() -> Window { Window { size: (1024, 768), + position: (100, 100), min_size: None, max_size: None, resizable: true, -- cgit From 552b7a1307ceaf83d00cbee88dc33ae5d92ee53e Mon Sep 17 00:00:00 2001 From: TimUntersberger Date: Fri, 25 Jun 2021 18:07:03 +0200 Subject: update cargo.toml of example --- examples/winit/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/winit/Cargo.toml b/examples/winit/Cargo.toml index 2d58f031..2286dfc9 100644 --- a/examples/winit/Cargo.toml +++ b/examples/winit/Cargo.toml @@ -1,7 +1,7 @@ [package] -name = "counter_winit" +name = "winit" version = "0.1.0" -authors = ["Héctor Ramón Jiménez "] +authors = ["Tim Untersberger "] edition = "2018" publish = false -- cgit From 1b6cf05f5f49272b500336b4f0704805f7a81b0b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 26 Jun 2021 12:16:31 +0200 Subject: Install `libxkbcommon-dev` for `ubuntu-latest` in CI --- .github/workflows/build.yml | 5 +++++ .github/workflows/test.yml | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3b0bf033..480a8710 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,6 +11,11 @@ jobs: - name: Install cargo-deb run: cargo install cargo-deb - uses: actions/checkout@master + - name: Install dependencies + run: | + export DEBIAN_FRONTED=noninteractive + sudo apt-get -qq update + sudo apt-get install -y libxkbcommon-dev - name: Enable Link Time Optimizations run: | echo "[profile.release]" >> Cargo.toml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bc531abf..0450f13d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,6 +12,12 @@ jobs: with: rust-version: ${{ matrix.rust }} - uses: actions/checkout@master + - name: Install dependencies + if: matrix.os == 'ubuntu-latest' + run: | + export DEBIAN_FRONTED=noninteractive + sudo apt-get -qq update + sudo apt-get install -y libxkbcommon-dev - name: Run tests run: | cargo test --verbose --all -- cgit From 4994d34abab3222f9a8fd7a9a3e63f969ca97ffc Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 30 Jun 2021 23:44:51 +0200 Subject: Update `winit` and `glutin` to latest `master` --- glutin/Cargo.toml | 2 +- winit/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml index 913ff5b8..78d5fe47 100644 --- a/glutin/Cargo.toml +++ b/glutin/Cargo.toml @@ -16,7 +16,7 @@ debug = ["iced_winit/debug"] [dependencies.glutin] version = "0.27" git = "https://github.com/iced-rs/glutin" -rev = "be6793b5b3defc9452cd1c896cd315ed7442d546" +rev = "2564d0ab87cf2ad824a2a58733aebe40dd2f29bb" [dependencies.iced_native] version = "0.4" diff --git a/winit/Cargo.toml b/winit/Cargo.toml index b926a9c5..c5c6ef70 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -21,7 +21,7 @@ thiserror = "1.0" [dependencies.winit] version = "0.25" git = "https://github.com/iced-rs/winit" -rev = "9c358959ed99736566d50a511b03d2fed3aac2ae" +rev = "44a9a6fc442fcfa3fa0dfc2d5a2f86fdf4aba10c" [dependencies.iced_native] version = "0.4" -- 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 --- Cargo.toml | 4 + core/src/keyboard.rs | 2 + core/src/keyboard/hotkey.rs | 18 ++++ examples/menu/Cargo.toml | 10 ++ examples/menu/src/main.rs | 123 ++++++++++++++++++++++ glutin/Cargo.toml | 4 +- native/src/lib.rs | 2 + native/src/menu.rs | 81 +++++++++++++++ src/application.rs | 11 +- src/lib.rs | 4 +- winit/Cargo.toml | 4 +- winit/src/application.rs | 6 ++ winit/src/application/state.rs | 2 + winit/src/conversion.rs | 224 ++++++++++++++++++++++++++++++++++++++++- 14 files changed, 487 insertions(+), 8 deletions(-) create mode 100644 core/src/keyboard/hotkey.rs create mode 100644 examples/menu/Cargo.toml create mode 100644 examples/menu/src/main.rs create mode 100644 native/src/menu.rs diff --git a/Cargo.toml b/Cargo.toml index 329877c8..075682e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,6 +85,7 @@ members = [ "examples/tour", "examples/tooltip", "examples/url_handler", + "examples/menu", ] [dependencies] @@ -92,6 +93,9 @@ iced_core = { version = "0.4", path = "core" } iced_futures = { version = "0.3", path = "futures" } thiserror = "1.0" +[patch.crates-io] +winit = { git = "https://github.com/iced-rs/winit", rev = "e351421a32bf01b428325dde44dea39ee2656153"} + [target.'cfg(not(target_arch = "wasm32"))'.dependencies] iced_winit = { version = "0.3", path = "winit" } iced_glutin = { version = "0.2", path = "glutin", optional = true } diff --git a/core/src/keyboard.rs b/core/src/keyboard.rs index 61e017ad..cb64701a 100644 --- a/core/src/keyboard.rs +++ b/core/src/keyboard.rs @@ -1,8 +1,10 @@ //! Reuse basic keyboard types. mod event; +mod hotkey; mod key_code; mod modifiers; pub use event::Event; +pub use hotkey::Hotkey; pub use key_code::KeyCode; pub use modifiers::Modifiers; diff --git a/core/src/keyboard/hotkey.rs b/core/src/keyboard/hotkey.rs new file mode 100644 index 00000000..310ef286 --- /dev/null +++ b/core/src/keyboard/hotkey.rs @@ -0,0 +1,18 @@ +use crate::keyboard::{KeyCode, Modifiers}; + +/// Representation of a hotkey, consists on the combination of a [`KeyCode`] and [`Modifiers`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Hotkey { + /// The key that represents this hotkey. + pub key: KeyCode, + + /// The list of modifiers that represents this hotkey. + pub modifiers: Modifiers, +} + +impl Hotkey { + /// Creates a new [`Hotkey`] with the given [`Modifiers`] and [`KeyCode`]. + pub fn new(modifiers: Modifiers, key: KeyCode) -> Self { + Self { modifiers, key } + } +} diff --git a/examples/menu/Cargo.toml b/examples/menu/Cargo.toml new file mode 100644 index 00000000..44597734 --- /dev/null +++ b/examples/menu/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "menu" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../.." } +iced_native = { path = "../../native" } \ No newline at end of file diff --git a/examples/menu/src/main.rs b/examples/menu/src/main.rs new file mode 100644 index 00000000..f8f70903 --- /dev/null +++ b/examples/menu/src/main.rs @@ -0,0 +1,123 @@ +use iced::{ + executor, Application, Clipboard, Command, Container, Element, Length, + Menu, Settings, Text, +}; +use iced_native::keyboard::{Hotkey, KeyCode, Modifiers}; + +pub fn main() -> iced::Result { + App::run(Settings::default()) +} + +#[derive(Debug, Default)] +struct App { + selected: Option, +} + +#[derive(Debug, Clone)] +enum Entry { + One, + Two, + Three, + A, + B, + C, +} + +#[derive(Debug, Clone)] +enum Message { + MenuActivated(Entry), +} + +impl Application for App { + type Executor = executor::Default; + type Message = Message; + type Flags = (); + + fn new(_flags: ()) -> (App, Command) { + (App::default(), Command::none()) + } + + fn title(&self) -> String { + String::from("Menu - Iced") + } + + fn menu(&self) -> Menu { + let alt = Modifiers { + alt: true, + control: false, + logo: false, + shift: false, + }; + let ctrl_shift = Modifiers { + control: true, + shift: true, + logo: false, + alt: false, + }; + + Menu::new() + .dropdown( + "First", + Menu::new() + .item( + "One", + Hotkey::new(alt, KeyCode::F1), + Message::MenuActivated(Entry::One), + ) + .item( + "Two", + Hotkey::new(alt, KeyCode::F2), + Message::MenuActivated(Entry::Two), + ) + .separator() + .item( + "Three", + Hotkey::new(alt, KeyCode::F3), + Message::MenuActivated(Entry::Three), + ), + ) + .dropdown( + "Second", + Menu::new() + .item( + "A", + Hotkey::new(ctrl_shift, KeyCode::A), + Message::MenuActivated(Entry::A), + ) + .item( + "B", + Hotkey::new(ctrl_shift, KeyCode::B), + Message::MenuActivated(Entry::B), + ) + .separator() + .item( + "C", + Hotkey::new(ctrl_shift, KeyCode::C), + Message::MenuActivated(Entry::C), + ), + ) + } + + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command { + match message { + Message::MenuActivated(entry) => self.selected = Some(entry), + } + + Command::none() + } + + fn view(&mut self) -> Element { + Container::new( + Text::new(format!("Selected {:?}", self.selected)).size(48), + ) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml index 78d5fe47..b42a6b36 100644 --- a/glutin/Cargo.toml +++ b/glutin/Cargo.toml @@ -15,8 +15,8 @@ debug = ["iced_winit/debug"] [dependencies.glutin] version = "0.27" -git = "https://github.com/iced-rs/glutin" -rev = "2564d0ab87cf2ad824a2a58733aebe40dd2f29bb" +# git = "https://github.com/iced-rs/glutin" +# rev = "2564d0ab87cf2ad824a2a58733aebe40dd2f29bb" [dependencies.iced_native] version = "0.4" 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, +} diff --git a/src/application.rs b/src/application.rs index bda8558c..b3dae1b3 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,6 +1,6 @@ use crate::window; use crate::{ - Clipboard, Color, Command, Element, Executor, Settings, Subscription, + Clipboard, Color, Command, Element, Executor, Menu, Settings, Subscription, }; /// An interactive cross-platform application. @@ -191,6 +191,11 @@ pub trait Application: Sized { false } + /// TODO + fn menu(&self) -> Menu { + Menu::new() + } + /// Runs the [`Application`]. /// /// On native platforms, this method will take control of the current thread @@ -296,6 +301,10 @@ where fn should_exit(&self) -> bool { self.0.should_exit() } + + fn menu(&self) -> Menu { + self.0.menu() + } } #[cfg(target_arch = "wasm32")] diff --git a/src/lib.rs b/src/lib.rs index 6a029afd..50952777 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -246,6 +246,6 @@ pub use settings::Settings; pub use runtime::{ futures, Align, Background, Clipboard, Color, Command, Font, - HorizontalAlignment, Length, Point, Rectangle, Size, Subscription, Vector, - VerticalAlignment, + HorizontalAlignment, Length, Menu, Point, Rectangle, Size, Subscription, + Vector, VerticalAlignment, }; diff --git a/winit/Cargo.toml b/winit/Cargo.toml index c5c6ef70..87fd23d5 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -20,8 +20,8 @@ thiserror = "1.0" [dependencies.winit] version = "0.25" -git = "https://github.com/iced-rs/winit" -rev = "44a9a6fc442fcfa3fa0dfc2d5a2f86fdf4aba10c" +# git = "https://github.com/iced-rs/winit" +# rev = "e351421a32bf01b428325dde44dea39ee2656153" [dependencies.iced_native] version = "0.4" diff --git a/winit/src/application.rs b/winit/src/application.rs index 49f2f513..108c6d64 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -14,6 +14,7 @@ use iced_futures::futures; use iced_futures::futures::channel::mpsc; use iced_graphics::window; use iced_native::program::Program; +use iced_native::Menu; use iced_native::{Cache, UserInterface}; use std::mem::ManuallyDrop; @@ -98,6 +99,11 @@ pub trait Application: Program { fn should_exit(&self) -> bool { false } + + /// TODO + fn menu(&self) -> Menu { + Menu::new() + } } /// Runs an [`Application`] with an executor, compositor, and the provided diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs index b54d3aed..2994080c 100644 --- a/winit/src/application/state.rs +++ b/winit/src/application/state.rs @@ -36,6 +36,8 @@ impl State { ) }; + window.set_menu(Some(conversion::menu(application.menu()))); + Self { title, mode, diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index b850a805..da09ac9d 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -6,7 +6,7 @@ use crate::keyboard; use crate::mouse; use crate::touch; use crate::window; -use crate::{Event, Mode, Point}; +use crate::{Event, Menu, MenuEntry, Mode, Point}; /// Converts a winit window event into an iced event. pub fn window_event( @@ -156,6 +156,51 @@ pub fn visible(mode: Mode) -> bool { } } +/// Converts a `Hotkey` from [`iced_native`] to a [`winit`] Hotkey. +/// +/// [`winit`]: https://github.com/rust-windowing/winit +/// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native +fn hotkey(hotkey: keyboard::Hotkey) -> winit::window::Hotkey { + use winit::event::ModifiersState; + + let mut modifiers = ModifiersState::empty(); + modifiers.set(ModifiersState::CTRL, hotkey.modifiers.control); + modifiers.set(ModifiersState::SHIFT, hotkey.modifiers.shift); + modifiers.set(ModifiersState::ALT, hotkey.modifiers.alt); + modifiers.set(ModifiersState::LOGO, hotkey.modifiers.logo); + + winit::window::Hotkey::new(modifiers, to_virtual_keycode(hotkey.key)) +} + +/// Converts a `Menu` from [`iced_native`] to a [`winit`] menu. +/// +/// [`winit`]: https://github.com/rust-windowing/winit +/// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native +pub fn menu(menu: Menu) -> winit::window::Menu { + let mut converted = winit::window::Menu::new(); + + for item in menu.iter() { + match item { + MenuEntry::Item { + content, hotkey, .. + } => { + let hotkey: Option<&keyboard::Hotkey> = hotkey.as_ref().into(); + converted.add_item( + 0, + content, + hotkey.map(|h| self::hotkey(*h)), + ); + } + MenuEntry::Dropdown { content, submenu } => { + converted.add_dropdown(content, self::menu(submenu)); + } + MenuEntry::Separator => converted.add_separator(), + } + } + + converted +} + /// Converts a `MouseCursor` from [`iced_native`] to a [`winit`] cursor icon. /// /// [`winit`]: https://github.com/rust-windowing/winit @@ -252,6 +297,183 @@ pub fn touch_event( } } +/// Converts a `KeyCode` from [`iced_native`] to an [`winit`] key code. +/// +/// [`winit`]: https://github.com/rust-windowing/winit +/// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native +fn to_virtual_keycode( + keycode: keyboard::KeyCode, +) -> winit::event::VirtualKeyCode { + use keyboard::KeyCode; + use winit::event::VirtualKeyCode; + + match keycode { + KeyCode::Key1 => VirtualKeyCode::Key1, + KeyCode::Key2 => VirtualKeyCode::Key2, + KeyCode::Key3 => VirtualKeyCode::Key3, + KeyCode::Key4 => VirtualKeyCode::Key4, + KeyCode::Key5 => VirtualKeyCode::Key5, + KeyCode::Key6 => VirtualKeyCode::Key6, + KeyCode::Key7 => VirtualKeyCode::Key7, + KeyCode::Key8 => VirtualKeyCode::Key8, + KeyCode::Key9 => VirtualKeyCode::Key9, + KeyCode::Key0 => VirtualKeyCode::Key0, + KeyCode::A => VirtualKeyCode::A, + KeyCode::B => VirtualKeyCode::B, + KeyCode::C => VirtualKeyCode::C, + KeyCode::D => VirtualKeyCode::D, + KeyCode::E => VirtualKeyCode::E, + KeyCode::F => VirtualKeyCode::F, + KeyCode::G => VirtualKeyCode::G, + KeyCode::H => VirtualKeyCode::H, + KeyCode::I => VirtualKeyCode::I, + KeyCode::J => VirtualKeyCode::J, + KeyCode::K => VirtualKeyCode::K, + KeyCode::L => VirtualKeyCode::L, + KeyCode::M => VirtualKeyCode::M, + KeyCode::N => VirtualKeyCode::N, + KeyCode::O => VirtualKeyCode::O, + KeyCode::P => VirtualKeyCode::P, + KeyCode::Q => VirtualKeyCode::Q, + KeyCode::R => VirtualKeyCode::R, + KeyCode::S => VirtualKeyCode::S, + KeyCode::T => VirtualKeyCode::T, + KeyCode::U => VirtualKeyCode::U, + KeyCode::V => VirtualKeyCode::V, + KeyCode::W => VirtualKeyCode::W, + KeyCode::X => VirtualKeyCode::X, + KeyCode::Y => VirtualKeyCode::Y, + KeyCode::Z => VirtualKeyCode::Z, + KeyCode::Escape => VirtualKeyCode::Escape, + KeyCode::F1 => VirtualKeyCode::F1, + KeyCode::F2 => VirtualKeyCode::F2, + KeyCode::F3 => VirtualKeyCode::F3, + KeyCode::F4 => VirtualKeyCode::F4, + KeyCode::F5 => VirtualKeyCode::F5, + KeyCode::F6 => VirtualKeyCode::F6, + KeyCode::F7 => VirtualKeyCode::F7, + KeyCode::F8 => VirtualKeyCode::F8, + KeyCode::F9 => VirtualKeyCode::F9, + KeyCode::F10 => VirtualKeyCode::F10, + KeyCode::F11 => VirtualKeyCode::F11, + KeyCode::F12 => VirtualKeyCode::F12, + KeyCode::F13 => VirtualKeyCode::F13, + KeyCode::F14 => VirtualKeyCode::F14, + KeyCode::F15 => VirtualKeyCode::F15, + KeyCode::F16 => VirtualKeyCode::F16, + KeyCode::F17 => VirtualKeyCode::F17, + KeyCode::F18 => VirtualKeyCode::F18, + KeyCode::F19 => VirtualKeyCode::F19, + KeyCode::F20 => VirtualKeyCode::F20, + KeyCode::F21 => VirtualKeyCode::F21, + KeyCode::F22 => VirtualKeyCode::F22, + KeyCode::F23 => VirtualKeyCode::F23, + KeyCode::F24 => VirtualKeyCode::F24, + KeyCode::Snapshot => VirtualKeyCode::Snapshot, + KeyCode::Scroll => VirtualKeyCode::Scroll, + KeyCode::Pause => VirtualKeyCode::Pause, + KeyCode::Insert => VirtualKeyCode::Insert, + KeyCode::Home => VirtualKeyCode::Home, + KeyCode::Delete => VirtualKeyCode::Delete, + KeyCode::End => VirtualKeyCode::End, + KeyCode::PageDown => VirtualKeyCode::PageDown, + KeyCode::PageUp => VirtualKeyCode::PageUp, + KeyCode::Left => VirtualKeyCode::Left, + KeyCode::Up => VirtualKeyCode::Up, + KeyCode::Right => VirtualKeyCode::Right, + KeyCode::Down => VirtualKeyCode::Down, + KeyCode::Backspace => VirtualKeyCode::Back, + KeyCode::Enter => VirtualKeyCode::Return, + KeyCode::Space => VirtualKeyCode::Space, + KeyCode::Compose => VirtualKeyCode::Compose, + KeyCode::Caret => VirtualKeyCode::Caret, + KeyCode::Numlock => VirtualKeyCode::Numlock, + KeyCode::Numpad0 => VirtualKeyCode::Numpad0, + KeyCode::Numpad1 => VirtualKeyCode::Numpad1, + KeyCode::Numpad2 => VirtualKeyCode::Numpad2, + KeyCode::Numpad3 => VirtualKeyCode::Numpad3, + KeyCode::Numpad4 => VirtualKeyCode::Numpad4, + KeyCode::Numpad5 => VirtualKeyCode::Numpad5, + KeyCode::Numpad6 => VirtualKeyCode::Numpad6, + KeyCode::Numpad7 => VirtualKeyCode::Numpad7, + KeyCode::Numpad8 => VirtualKeyCode::Numpad8, + KeyCode::Numpad9 => VirtualKeyCode::Numpad9, + KeyCode::AbntC1 => VirtualKeyCode::AbntC1, + KeyCode::AbntC2 => VirtualKeyCode::AbntC2, + KeyCode::NumpadAdd => VirtualKeyCode::NumpadAdd, + KeyCode::Plus => VirtualKeyCode::Plus, + KeyCode::Apostrophe => VirtualKeyCode::Apostrophe, + KeyCode::Apps => VirtualKeyCode::Apps, + KeyCode::At => VirtualKeyCode::At, + KeyCode::Ax => VirtualKeyCode::Ax, + KeyCode::Backslash => VirtualKeyCode::Backslash, + KeyCode::Calculator => VirtualKeyCode::Calculator, + KeyCode::Capital => VirtualKeyCode::Capital, + KeyCode::Colon => VirtualKeyCode::Colon, + KeyCode::Comma => VirtualKeyCode::Comma, + KeyCode::Convert => VirtualKeyCode::Convert, + KeyCode::NumpadDecimal => VirtualKeyCode::NumpadDecimal, + KeyCode::NumpadDivide => VirtualKeyCode::NumpadDivide, + KeyCode::Equals => VirtualKeyCode::Equals, + KeyCode::Grave => VirtualKeyCode::Grave, + KeyCode::Kana => VirtualKeyCode::Kana, + KeyCode::Kanji => VirtualKeyCode::Kanji, + KeyCode::LAlt => VirtualKeyCode::LAlt, + KeyCode::LBracket => VirtualKeyCode::LBracket, + KeyCode::LControl => VirtualKeyCode::LControl, + KeyCode::LShift => VirtualKeyCode::LShift, + KeyCode::LWin => VirtualKeyCode::LWin, + KeyCode::Mail => VirtualKeyCode::Mail, + KeyCode::MediaSelect => VirtualKeyCode::MediaSelect, + KeyCode::MediaStop => VirtualKeyCode::MediaStop, + KeyCode::Minus => VirtualKeyCode::Minus, + KeyCode::NumpadMultiply => VirtualKeyCode::NumpadMultiply, + KeyCode::Mute => VirtualKeyCode::Mute, + KeyCode::MyComputer => VirtualKeyCode::MyComputer, + KeyCode::NavigateForward => VirtualKeyCode::NavigateForward, + KeyCode::NavigateBackward => VirtualKeyCode::NavigateBackward, + KeyCode::NextTrack => VirtualKeyCode::NextTrack, + KeyCode::NoConvert => VirtualKeyCode::NoConvert, + KeyCode::NumpadComma => VirtualKeyCode::NumpadComma, + KeyCode::NumpadEnter => VirtualKeyCode::NumpadEnter, + KeyCode::NumpadEquals => VirtualKeyCode::NumpadEquals, + KeyCode::OEM102 => VirtualKeyCode::OEM102, + KeyCode::Period => VirtualKeyCode::Period, + KeyCode::PlayPause => VirtualKeyCode::PlayPause, + KeyCode::Power => VirtualKeyCode::Power, + KeyCode::PrevTrack => VirtualKeyCode::PrevTrack, + KeyCode::RAlt => VirtualKeyCode::RAlt, + KeyCode::RBracket => VirtualKeyCode::RBracket, + KeyCode::RControl => VirtualKeyCode::RControl, + KeyCode::RShift => VirtualKeyCode::RShift, + KeyCode::RWin => VirtualKeyCode::RWin, + KeyCode::Semicolon => VirtualKeyCode::Semicolon, + KeyCode::Slash => VirtualKeyCode::Slash, + KeyCode::Sleep => VirtualKeyCode::Sleep, + KeyCode::Stop => VirtualKeyCode::Stop, + KeyCode::NumpadSubtract => VirtualKeyCode::NumpadSubtract, + KeyCode::Sysrq => VirtualKeyCode::Sysrq, + KeyCode::Tab => VirtualKeyCode::Tab, + KeyCode::Underline => VirtualKeyCode::Underline, + KeyCode::Unlabeled => VirtualKeyCode::Unlabeled, + KeyCode::VolumeDown => VirtualKeyCode::VolumeDown, + KeyCode::VolumeUp => VirtualKeyCode::VolumeUp, + KeyCode::Wake => VirtualKeyCode::Wake, + KeyCode::WebBack => VirtualKeyCode::WebBack, + KeyCode::WebFavorites => VirtualKeyCode::WebFavorites, + KeyCode::WebForward => VirtualKeyCode::WebForward, + KeyCode::WebHome => VirtualKeyCode::WebHome, + KeyCode::WebRefresh => VirtualKeyCode::WebRefresh, + KeyCode::WebSearch => VirtualKeyCode::WebSearch, + KeyCode::WebStop => VirtualKeyCode::WebStop, + KeyCode::Yen => VirtualKeyCode::Yen, + KeyCode::Copy => VirtualKeyCode::Copy, + KeyCode::Paste => VirtualKeyCode::Paste, + KeyCode::Cut => VirtualKeyCode::Cut, + KeyCode::Asterisk => VirtualKeyCode::Asterisk, + } +} + /// Converts a `VirtualKeyCode` from [`winit`] to an [`iced_native`] key code. /// /// [`winit`]: https://github.com/rust-windowing/winit -- cgit From c4552a72d43e5f79faa7c64634be539d81f995b9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 12 Jul 2021 21:21:22 +0200 Subject: Update `winit` dependency in `iced-rs` --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 075682e4..1a4da1e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,7 +94,7 @@ iced_futures = { version = "0.3", path = "futures" } thiserror = "1.0" [patch.crates-io] -winit = { git = "https://github.com/iced-rs/winit", rev = "e351421a32bf01b428325dde44dea39ee2656153"} +winit = { git = "https://github.com/iced-rs/winit", rev = "327c8756f90953a6a03f818113f8566176e6eb0d"} [target.'cfg(not(target_arch = "wasm32"))'.dependencies] iced_winit = { version = "0.3", path = "winit" } -- 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 --- examples/menu/src/main.rs | 44 ++++++++++++++------------- native/src/lib.rs | 2 +- native/src/menu.rs | 75 ++++++++++++++++++++++++----------------------- src/lib.rs | 2 +- winit/src/conversion.rs | 9 +++--- 5 files changed, 70 insertions(+), 62 deletions(-) diff --git a/examples/menu/src/main.rs b/examples/menu/src/main.rs index f8f70903..9d4ed71b 100644 --- a/examples/menu/src/main.rs +++ b/examples/menu/src/main.rs @@ -1,6 +1,7 @@ +use iced::menu::{self, Menu}; use iced::{ executor, Application, Clipboard, Command, Container, Element, Length, - Menu, Settings, Text, + Settings, Text, }; use iced_native::keyboard::{Hotkey, KeyCode, Modifiers}; @@ -55,47 +56,50 @@ impl Application for App { alt: false, }; - Menu::new() - .dropdown( + Menu::with_entries(vec![ + menu::Entry::dropdown( "First", - Menu::new() - .item( + Menu::with_entries(vec![ + menu::Entry::item( "One", Hotkey::new(alt, KeyCode::F1), Message::MenuActivated(Entry::One), - ) - .item( + ), + menu::Entry::item( "Two", Hotkey::new(alt, KeyCode::F2), Message::MenuActivated(Entry::Two), - ) - .separator() - .item( + ), + menu::Entry::Separator, + menu::Entry::item( "Three", Hotkey::new(alt, KeyCode::F3), Message::MenuActivated(Entry::Three), ), - ) - .dropdown( + ]), + ), + menu::Entry::dropdown( "Second", - Menu::new() - .item( + Menu::with_entries(vec![ + menu::Entry::item( "A", Hotkey::new(ctrl_shift, KeyCode::A), Message::MenuActivated(Entry::A), - ) - .item( + ), + menu::Entry::item( "B", Hotkey::new(ctrl_shift, KeyCode::B), Message::MenuActivated(Entry::B), - ) - .separator() - .item( + ), + menu::Entry::Separator, + menu::Entry::item( "C", Hotkey::new(ctrl_shift, KeyCode::C), Message::MenuActivated(Entry::C), ), - ) + ]), + ), + ]) } fn update( 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 } + } +} diff --git a/src/lib.rs b/src/lib.rs index 50952777..8cd6aa70 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -245,7 +245,7 @@ pub use sandbox::Sandbox; pub use settings::Settings; pub use runtime::{ - futures, Align, Background, Clipboard, Color, Command, Font, + futures, menu, Align, Background, Clipboard, Color, Command, Font, HorizontalAlignment, Length, Menu, Point, Rectangle, Size, Subscription, Vector, VerticalAlignment, }; diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index da09ac9d..02c21c59 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -3,10 +3,11 @@ //! [`winit`]: https://github.com/rust-windowing/winit //! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native use crate::keyboard; +use crate::menu::{self, Menu}; use crate::mouse; use crate::touch; use crate::window; -use crate::{Event, Menu, MenuEntry, Mode, Point}; +use crate::{Event, Mode, Point}; /// Converts a winit window event into an iced event. pub fn window_event( @@ -181,7 +182,7 @@ pub fn menu(menu: Menu) -> winit::window::Menu { for item in menu.iter() { match item { - MenuEntry::Item { + menu::Entry::Item { content, hotkey, .. } => { let hotkey: Option<&keyboard::Hotkey> = hotkey.as_ref().into(); @@ -191,10 +192,10 @@ pub fn menu(menu: Menu) -> winit::window::Menu { hotkey.map(|h| self::hotkey(*h)), ); } - MenuEntry::Dropdown { content, submenu } => { + menu::Entry::Dropdown { content, submenu } => { converted.add_dropdown(content, self::menu(submenu)); } - MenuEntry::Separator => converted.add_separator(), + menu::Entry::Separator => converted.add_separator(), } } -- 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` --- core/src/lib.rs | 2 ++ core/src/menu.rs | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ native/src/lib.rs | 6 ++-- native/src/menu.rs | 84 ------------------------------------------------------ web/src/lib.rs | 4 +-- 5 files changed, 90 insertions(+), 90 deletions(-) create mode 100644 core/src/menu.rs delete mode 100644 native/src/menu.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index 6453d599..c4288158 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -15,6 +15,7 @@ #![forbid(unsafe_code)] #![forbid(rust_2018_idioms)] pub mod keyboard; +pub mod menu; pub mod mouse; mod align; @@ -33,6 +34,7 @@ pub use background::Background; pub use color::Color; pub use font::Font; pub use length::Length; +pub use menu::Menu; pub use padding::Padding; pub use point::Point; pub use rectangle::Rectangle; diff --git a/core/src/menu.rs b/core/src/menu.rs new file mode 100644 index 00000000..fe770216 --- /dev/null +++ b/core/src/menu.rs @@ -0,0 +1,84 @@ +//! 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 } + } +} 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 } - } -} diff --git a/web/src/lib.rs b/web/src/lib.rs index 158416b9..6b7d0115 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -74,8 +74,8 @@ pub use dodrio; pub use element::Element; pub use hasher::Hasher; pub use iced_core::{ - keyboard, mouse, Align, Background, Color, Font, HorizontalAlignment, - Length, Padding, Point, Rectangle, Size, Vector, VerticalAlignment, + keyboard, menu, mouse, Align, Background, Color, Font, HorizontalAlignment, + Length, Menu, Padding, Point, Rectangle, Size, Vector, VerticalAlignment, }; pub use iced_futures::{executor, futures, Command}; pub use subscription::Subscription; -- 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` --- core/Cargo.toml | 1 + core/src/keyboard/modifiers.rs | 76 ++++++++++++++++++++++++++--------------- examples/menu/src/main.rs | 14 ++------ examples/pane_grid/src/main.rs | 2 +- native/src/widget/text_input.rs | 35 +++++++------------ winit/src/conversion.rs | 22 ++++++------ 6 files changed, 78 insertions(+), 72 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 88f257b7..54d927af 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -8,6 +8,7 @@ license = "MIT" repository = "https://github.com/hecrj/iced" [dependencies] +bitflags = "1.2" [dependencies.palette] version = "0.5.0" diff --git a/core/src/keyboard/modifiers.rs b/core/src/keyboard/modifiers.rs index d2a0500e..383b9370 100644 --- a/core/src/keyboard/modifiers.rs +++ b/core/src/keyboard/modifiers.rs @@ -1,20 +1,53 @@ -/// The current state of the keyboard modifiers. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct Modifiers { - /// Whether a shift key is pressed - pub shift: bool, +use bitflags::bitflags; - /// Whether a control key is pressed - pub control: bool, - - /// Whether an alt key is pressed - pub alt: bool, - - /// Whether a logo key is pressed (e.g. windows key, command key...) - pub logo: bool, +bitflags! { + /// The current state of the keyboard modifiers. + #[derive(Default)] + pub struct Modifiers: u32{ + /// The "shift" key. + const SHIFT = 0b100 << 0; + // const LSHIFT = 0b010 << 0; + // const RSHIFT = 0b001 << 0; + // + /// The "control" key. + const CTRL = 0b100 << 3; + // const LCTRL = 0b010 << 3; + // const RCTRL = 0b001 << 3; + // + /// The "alt" key. + const ALT = 0b100 << 6; + // const LALT = 0b010 << 6; + // const RALT = 0b001 << 6; + // + /// The "windows" key on Windows, "command" key on Mac, and + /// "super" key on Linux. + const LOGO = 0b100 << 9; + // const LLOGO = 0b010 << 9; + // const RLOGO = 0b001 << 9; + } } impl Modifiers { + /// Returns true if the [`SHIFT`] key is pressed in the [`Modifiers`]. + pub fn shift(self) -> bool { + self.contains(Self::SHIFT) + } + + /// Returns true if the [`CTRL`] key is pressed in the [`Modifiers`]. + pub fn control(self) -> bool { + self.contains(Self::CTRL) + } + + /// Returns true if the [`ALT`] key is pressed in the [`Modifiers`]. + pub fn alt(self) -> bool { + self.contains(Self::ALT) + } + + /// Returns true if the [`LOGO`] key is pressed in the [`Modifiers`]. + pub fn logo(self) -> bool { + self.contains(Self::LOGO) + } + /// Returns true if a "command key" is pressed in the [`Modifiers`]. /// /// The "command key" is the main modifier key used to issue commands in the @@ -22,24 +55,13 @@ impl Modifiers { /// /// - It is the `logo` or command key (⌘) on macOS /// - It is the `control` key on other platforms - pub fn is_command_pressed(self) -> bool { + pub fn command(self) -> bool { #[cfg(target_os = "macos")] - let is_pressed = self.logo; + let is_pressed = self.logo(); #[cfg(not(target_os = "macos"))] - let is_pressed = self.control; + let is_pressed = self.control(); is_pressed } - - /// Returns true if the current [`Modifiers`] have at least the same - /// keys pressed as the provided ones, and false otherwise. - pub fn matches(&self, modifiers: Self) -> bool { - let shift = !modifiers.shift || self.shift; - let control = !modifiers.control || self.control; - let alt = !modifiers.alt || self.alt; - let logo = !modifiers.logo || self.logo; - - shift && control && alt && logo - } } diff --git a/examples/menu/src/main.rs b/examples/menu/src/main.rs index 9d4ed71b..7403713c 100644 --- a/examples/menu/src/main.rs +++ b/examples/menu/src/main.rs @@ -43,18 +43,8 @@ impl Application for App { } fn menu(&self) -> Menu { - let alt = Modifiers { - alt: true, - control: false, - logo: false, - shift: false, - }; - let ctrl_shift = Modifiers { - control: true, - shift: true, - logo: false, - alt: false, - }; + let alt = Modifiers::ALT; + let ctrl_shift = Modifiers::CTRL | Modifiers::SHIFT; Menu::with_entries(vec![ menu::Entry::dropdown( diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 81cf1770..3bd8aa25 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -146,7 +146,7 @@ impl Application for Example { Event::Keyboard(keyboard::Event::KeyPressed { modifiers, key_code, - }) if modifiers.is_command_pressed() => handle_hotkey(key_code), + }) if modifiers.command() => handle_hotkey(key_code), _ => None, } }) 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() } } } diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 02c21c59..3aa88c5c 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -165,10 +165,10 @@ fn hotkey(hotkey: keyboard::Hotkey) -> winit::window::Hotkey { use winit::event::ModifiersState; let mut modifiers = ModifiersState::empty(); - modifiers.set(ModifiersState::CTRL, hotkey.modifiers.control); - modifiers.set(ModifiersState::SHIFT, hotkey.modifiers.shift); - modifiers.set(ModifiersState::ALT, hotkey.modifiers.alt); - modifiers.set(ModifiersState::LOGO, hotkey.modifiers.logo); + modifiers.set(ModifiersState::CTRL, hotkey.modifiers.control()); + modifiers.set(ModifiersState::SHIFT, hotkey.modifiers.shift()); + modifiers.set(ModifiersState::ALT, hotkey.modifiers.alt()); + modifiers.set(ModifiersState::LOGO, hotkey.modifiers.logo()); winit::window::Hotkey::new(modifiers, to_virtual_keycode(hotkey.key)) } @@ -249,12 +249,14 @@ pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button { pub fn modifiers( modifiers: winit::event::ModifiersState, ) -> keyboard::Modifiers { - keyboard::Modifiers { - shift: modifiers.shift(), - control: modifiers.ctrl(), - alt: modifiers.alt(), - logo: modifiers.logo(), - } + let mut result = keyboard::Modifiers::empty(); + + result.set(keyboard::Modifiers::SHIFT, modifiers.shift()); + result.set(keyboard::Modifiers::CTRL, modifiers.ctrl()); + result.set(keyboard::Modifiers::ALT, modifiers.alt()); + result.set(keyboard::Modifiers::LOGO, modifiers.logo()); + + result } /// Converts a physical cursor position to a logical `Point`. -- cgit From b3ff522c182590ffcd03113b5147fa4599559059 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 12 Jul 2021 22:11:06 +0200 Subject: Simplify `Hotkey` conversion in `conversion::menu` --- winit/src/conversion.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 3aa88c5c..4dc8bbf5 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -185,12 +185,7 @@ pub fn menu(menu: Menu) -> winit::window::Menu { menu::Entry::Item { content, hotkey, .. } => { - let hotkey: Option<&keyboard::Hotkey> = hotkey.as_ref().into(); - converted.add_item( - 0, - content, - hotkey.map(|h| self::hotkey(*h)), - ); + converted.add_item(0, content, hotkey.map(self::hotkey)); } menu::Entry::Dropdown { content, submenu } => { converted.add_dropdown(content, self::menu(submenu)); -- cgit From 31997d255f263a0f47f5af0d8560f3d5bd37f077 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 12 Jul 2021 22:28:18 +0200 Subject: Store and synchronize `Menu` in `application::State` --- core/src/menu.rs | 40 ++++++++++++++++++++++++++++++++++++---- glutin/src/application.rs | 13 ++++++++----- winit/src/application.rs | 1 + winit/src/application/state.rs | 21 ++++++++++++++++++--- winit/src/conversion.rs | 2 +- 5 files changed, 64 insertions(+), 13 deletions(-) diff --git a/core/src/menu.rs b/core/src/menu.rs index fe770216..52186140 100644 --- a/core/src/menu.rs +++ b/core/src/menu.rs @@ -4,11 +4,17 @@ use crate::keyboard::Hotkey; /// Menu representation. /// /// This can be used by `shell` implementations to create a menu. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct Menu { entries: Vec>, } +impl PartialEq for Menu { + fn eq(&self, other: &Self) -> bool { + self.entries == other.entries + } +} + impl Menu { /// Creates an empty [`Menu`]. pub fn new() -> Self { @@ -27,13 +33,13 @@ impl Menu { } /// Returns a [`MenuEntry`] iterator. - pub fn iter(self) -> impl Iterator> { - self.entries.into_iter() + pub fn iter(&self) -> impl Iterator> { + self.entries.iter() } } /// Represents one of the possible entries used to build a [`Menu`]. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub enum Entry { /// Item for a [`Menu`] Item { @@ -82,3 +88,29 @@ impl Entry { Entry::Dropdown { content, submenu } } } + +impl PartialEq for Entry { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + ( + Entry::Item { + content, hotkey, .. + }, + Entry::Item { + content: other_content, + hotkey: other_hotkey, + .. + }, + ) => content == other_content && hotkey == other_hotkey, + ( + Entry::Dropdown { content, submenu }, + Entry::Dropdown { + content: other_content, + submenu: other_submenu, + }, + ) => content == other_content && submenu == other_submenu, + (Entry::Separator, Entry::Separator) => true, + _ => false, + } + } +} diff --git a/glutin/src/application.rs b/glutin/src/application.rs index a8e5dbf9..279b6c77 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -52,11 +52,14 @@ where runtime.track(subscription); let context = { - let builder = settings.window.into_builder( - &application.title(), - application.mode(), - event_loop.primary_monitor(), - ); + let builder = settings + .window + .into_builder( + &application.title(), + application.mode(), + event_loop.primary_monitor(), + ) + .with_menu(Some(conversion::menu(&application.menu()))); let context = ContextBuilder::new() .with_vsync(true) diff --git a/winit/src/application.rs b/winit/src/application.rs index 108c6d64..bf4b2489 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -151,6 +151,7 @@ where application.mode(), event_loop.primary_monitor(), ) + .with_menu(Some(conversion::menu(&application.menu()))) .build(&event_loop) .map_err(Error::WindowCreationFailed)?; diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs index 2994080c..f60f09be 100644 --- a/winit/src/application/state.rs +++ b/winit/src/application/state.rs @@ -1,5 +1,5 @@ use crate::conversion; -use crate::{Application, Color, Debug, Mode, Point, Size, Viewport}; +use crate::{Application, Color, Debug, Menu, Mode, Point, Size, Viewport}; use std::marker::PhantomData; use winit::event::{Touch, WindowEvent}; @@ -9,6 +9,7 @@ use winit::window::Window; #[derive(Debug, Clone)] pub struct State { title: String, + menu: Menu, mode: Mode, background_color: Color, scale_factor: f64, @@ -23,6 +24,7 @@ impl State { /// Creates a new [`State`] for the provided [`Application`] and window. pub fn new(application: &A, window: &Window) -> Self { let title = application.title(); + let menu = application.menu(); let mode = application.mode(); let background_color = application.background_color(); let scale_factor = application.scale_factor(); @@ -36,10 +38,9 @@ impl State { ) }; - window.set_menu(Some(conversion::menu(application.menu()))); - Self { title, + menu, mode, background_color, scale_factor, @@ -52,6 +53,11 @@ impl State { } } + /// Returns the current [`Menu`] of the [`State`]. + pub fn menu(&self) -> &Menu { + &self.menu + } + /// Returns the current background [`Color`] of the [`State`]. pub fn background_color(&self) -> Color { self.background_color @@ -205,5 +211,14 @@ impl State { self.scale_factor = new_scale_factor; } + + // Update menu + let new_menu = application.menu(); + + if self.menu != new_menu { + window.set_menu(Some(conversion::menu(&new_menu))); + + self.menu = new_menu; + } } } diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 4dc8bbf5..22118bf5 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -177,7 +177,7 @@ fn hotkey(hotkey: keyboard::Hotkey) -> winit::window::Hotkey { /// /// [`winit`]: https://github.com/rust-windowing/winit /// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native -pub fn menu(menu: Menu) -> winit::window::Menu { +pub fn menu(menu: &Menu) -> winit::window::Menu { let mut converted = winit::window::Menu::new(); for item in menu.iter() { -- cgit From f3b056a6fc06248aa068549fc47ab6864829b875 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 13 Jul 2021 20:46:23 +0200 Subject: Generate unique identifiers for entries in `conversion::menu` --- winit/src/conversion.rs | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 22118bf5..3c483086 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -178,22 +178,40 @@ fn hotkey(hotkey: keyboard::Hotkey) -> winit::window::Hotkey { /// [`winit`]: https://github.com/rust-windowing/winit /// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native pub fn menu(menu: &Menu) -> winit::window::Menu { - let mut converted = winit::window::Menu::new(); - - for item in menu.iter() { - match item { - menu::Entry::Item { - content, hotkey, .. - } => { - converted.add_item(0, content, hotkey.map(self::hotkey)); - } - menu::Entry::Dropdown { content, submenu } => { - converted.add_dropdown(content, self::menu(submenu)); + fn menu_i( + starting_id: usize, + menu: &Menu, + ) -> (winit::window::Menu, usize) { + let mut id = starting_id; + let mut converted = winit::window::Menu::new(); + + for item in menu.iter() { + match item { + menu::Entry::Item { + content, hotkey, .. + } => { + converted.add_item(id, content, hotkey.map(self::hotkey)); + + id += 1; + } + menu::Entry::Dropdown { content, submenu } => { + let (submenu, n_children) = menu_i(id, submenu); + + converted.add_dropdown(content, submenu); + + id += n_children; + } + menu::Entry::Separator => { + converted.add_separator(); + } } - menu::Entry::Separator => converted.add_separator(), } + + (converted, id - starting_id) } + let (converted, _) = menu_i(0, menu); + converted } -- cgit From 6221adf2b1b1e8150931d4175e1e36870d45f6e5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 13 Jul 2021 20:55:21 +0200 Subject: Draft `conversion::menu_message` in `iced_winit` ... and wire it up to the runtime loop --- glutin/src/application.rs | 10 ++++++++++ winit/src/application.rs | 10 ++++++++++ winit/src/conversion.rs | 11 +++++++++++ 3 files changed, 31 insertions(+) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 279b6c77..991c8705 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -306,6 +306,16 @@ async fn run_instance( // TODO: Handle animations! // Maybe we can use `ControlFlow::WaitUntil` for this. } + event::Event::WindowEvent { + event: event::WindowEvent::MenuEntryActivated(entry_id), + .. + } => { + if let Some(message) = + conversion::menu_message(state.menu(), entry_id) + { + messages.push(message); + } + } event::Event::WindowEvent { event: window_event, .. diff --git a/winit/src/application.rs b/winit/src/application.rs index bf4b2489..ada64dfc 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -386,6 +386,16 @@ async fn run_instance( // TODO: Handle animations! // Maybe we can use `ControlFlow::WaitUntil` for this. } + event::Event::WindowEvent { + event: event::WindowEvent::MenuEntryActivated(entry_id), + .. + } => { + if let Some(message) = + conversion::menu_message(state.menu(), entry_id) + { + messages.push(message); + } + } event::Event::WindowEvent { event: window_event, .. diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 3c483086..51db615b 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -215,6 +215,17 @@ pub fn menu(menu: &Menu) -> winit::window::Menu { converted } +/// Given a [`Menu`] and an identifier of a [`menu::Entry`], it returns the +/// `Message` that should be produced when that entry is activated. +pub fn menu_message( + _menu: &Menu, + id: isize, +) -> Option { + println!("Menu entry activated: {}", id); + + None +} + /// Converts a `MouseCursor` from [`iced_native`] to a [`winit`] cursor icon. /// /// [`winit`]: https://github.com/rust-windowing/winit -- cgit From 4abaee8b2354a666a75e4eb5ec9cc9a744936813 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 13 Jul 2021 21:11:13 +0200 Subject: Use `Menu::default` for root level menu in `conversion::menu` --- winit/src/conversion.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 51db615b..75419006 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -179,11 +179,11 @@ fn hotkey(hotkey: keyboard::Hotkey) -> winit::window::Hotkey { /// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native pub fn menu(menu: &Menu) -> winit::window::Menu { fn menu_i( + converted: &mut winit::window::Menu, starting_id: usize, menu: &Menu, - ) -> (winit::window::Menu, usize) { + ) -> usize { let mut id = starting_id; - let mut converted = winit::window::Menu::new(); for item in menu.iter() { match item { @@ -195,9 +195,11 @@ pub fn menu(menu: &Menu) -> winit::window::Menu { id += 1; } menu::Entry::Dropdown { content, submenu } => { - let (submenu, n_children) = menu_i(id, submenu); + let mut converted_submenu = winit::window::Menu::new(); + let n_children = + menu_i(&mut converted_submenu, id, submenu); - converted.add_dropdown(content, submenu); + converted.add_dropdown(content, converted_submenu); id += n_children; } @@ -207,10 +209,11 @@ pub fn menu(menu: &Menu) -> winit::window::Menu { } } - (converted, id - starting_id) + id - starting_id } - let (converted, _) = menu_i(0, menu); + let mut converted = winit::window::Menu::default(); + let _ = menu_i(&mut converted, 0, menu); converted } -- 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 +- src/application.rs | 2 +- src/sandbox.rs | 2 +- winit/src/conversion.rs | 5 ++++- 4 files changed, 7 insertions(+), 4 deletions(-) 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; diff --git a/src/application.rs b/src/application.rs index b3dae1b3..42c28f99 100644 --- a/src/application.rs +++ b/src/application.rs @@ -99,7 +99,7 @@ pub trait Application: Sized { type Executor: Executor; /// The type of __messages__ your [`Application`] will produce. - type Message: std::fmt::Debug + Send; + type Message: std::fmt::Debug + Clone + Send; /// The data needed to initialize your [`Application`]. type Flags; diff --git a/src/sandbox.rs b/src/sandbox.rs index 10b05a92..cb3cf624 100644 --- a/src/sandbox.rs +++ b/src/sandbox.rs @@ -88,7 +88,7 @@ use crate::{ /// ``` pub trait Sandbox { /// The type of __messages__ your [`Sandbox`] will produce. - type Message: std::fmt::Debug + Send; + type Message: std::fmt::Debug + Clone + Send; /// Initializes the [`Sandbox`]. /// diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 75419006..687a037e 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -223,7 +223,10 @@ pub fn menu(menu: &Menu) -> winit::window::Menu { pub fn menu_message( _menu: &Menu, id: isize, -) -> Option { +) -> Option +where + Message: Clone, +{ println!("Menu entry activated: {}", id); None -- cgit From 2e7eac7d2167b492149e064927e764eca67f98fe Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 13 Jul 2021 21:31:34 +0200 Subject: Implement `conversion::menu_message` --- winit/src/conversion.rs | 50 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 687a037e..dc7f90ad 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -220,16 +220,54 @@ pub fn menu(menu: &Menu) -> winit::window::Menu { /// Given a [`Menu`] and an identifier of a [`menu::Entry`], it returns the /// `Message` that should be produced when that entry is activated. -pub fn menu_message( - _menu: &Menu, - id: isize, -) -> Option +pub fn menu_message(menu: &Menu, id: isize) -> Option where Message: Clone, { - println!("Menu entry activated: {}", id); + use std::convert::TryFrom; - None + fn find_message( + target: usize, + starting_id: usize, + menu: &Menu, + ) -> Result + where + Message: Clone, + { + let mut id = starting_id; + + for entry in menu.iter() { + match entry { + menu::Entry::Item { on_activation, .. } => { + if id == target { + return Ok(on_activation.clone()); + } + + id += 1; + } + menu::Entry::Dropdown { submenu, .. } => { + match find_message(target, id, submenu) { + Ok(message) => { + return Ok(message); + } + Err(n_children) => { + id += n_children; + } + } + } + menu::Entry::Separator => {} + } + } + + Err(id - starting_id) + } + + // TODO: Does `winit` really need to provide an `isize`? + if let Ok(id) = usize::try_from(id) { + find_message(id, 0, menu).ok() + } else { + None + } } /// Converts a `MouseCursor` from [`iced_native`] to a [`winit`] cursor icon. -- cgit From e5c958e9d60409814574b94548f2b941e4c36545 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 13 Jul 2021 21:46:55 +0200 Subject: Add new sponsors page to `FUNDING` :tada: --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 4a3aad7c..8a891884 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,2 @@ +github: hecrj ko_fi: hecrj_ -- 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(-) 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(-) 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 3099f3610003f513a386125d3cb81bfbf0ffe887 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 19 Jul 2021 20:59:09 +0700 Subject: Implement `Menu::map` naively --- core/src/menu.rs | 44 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/core/src/menu.rs b/core/src/menu.rs index 52186140..e9d3d13a 100644 --- a/core/src/menu.rs +++ b/core/src/menu.rs @@ -26,15 +26,30 @@ impl Menu { Self { entries } } + /// Returns a [`MenuEntry`] iterator. + pub fn iter(&self) -> impl Iterator> { + self.entries.iter() + } + /// 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.iter() + /// Maps the `Message` of the [`Menu`] using the provided function. + /// + /// This is useful to compose menus and split them into different + /// abstraction levels. + pub fn map(self, f: &impl Fn(Message) -> B) -> Menu { + // TODO: Use a boxed trait to avoid reallocation of entries + Menu { + entries: self + .entries + .into_iter() + .map(|entry| entry.map(f)) + .collect(), + } } } @@ -71,7 +86,7 @@ impl Entry { let content = content.into(); let hotkey = hotkey.into(); - Entry::Item { + Self::Item { content, hotkey, on_activation, @@ -85,7 +100,26 @@ impl Entry { ) -> Self { let content = content.into(); - Entry::Dropdown { content, submenu } + Self::Dropdown { content, submenu } + } + + fn map(self, f: &impl Fn(Message) -> B) -> Entry { + match self { + Self::Item { + content, + hotkey, + on_activation, + } => Entry::Item { + content, + hotkey, + on_activation: f(on_activation), + }, + Self::Dropdown { content, submenu } => Entry::Dropdown { + content, + submenu: submenu.map(f), + }, + Self::Separator => Entry::Separator, + } } } -- cgit From a2f49a74d08a0cff2e892932b484a88a4034f627 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 19 Jul 2021 21:01:24 +0700 Subject: Replace `content` with `title` in `menu` module --- core/src/menu.rs | 41 ++++++++++++++++++----------------------- winit/src/conversion.rs | 10 ++++------ 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/core/src/menu.rs b/core/src/menu.rs index e9d3d13a..3ad7b7a2 100644 --- a/core/src/menu.rs +++ b/core/src/menu.rs @@ -59,7 +59,7 @@ pub enum Entry { /// Item for a [`Menu`] Item { /// The title of the item - content: String, + title: String, /// The [`Hotkey`] to activate the item, if any hotkey: Option, /// The message generated when the item is activated @@ -68,7 +68,7 @@ pub enum Entry { /// Dropdown for a [`Menu`] Dropdown { /// Title of the dropdown - content: String, + title: String, /// The submenu of the dropdown submenu: Menu, }, @@ -79,43 +79,40 @@ pub enum Entry { impl Entry { /// Creates an [`Entry::Item`]. pub fn item>( - content: S, + title: S, hotkey: impl Into>, on_activation: Message, ) -> Self { - let content = content.into(); + let title = title.into(); let hotkey = hotkey.into(); Self::Item { - content, + title, hotkey, on_activation, } } /// Creates an [`Entry::Dropdown`]. - pub fn dropdown>( - content: S, - submenu: Menu, - ) -> Self { - let content = content.into(); + pub fn dropdown>(title: S, submenu: Menu) -> Self { + let title = title.into(); - Self::Dropdown { content, submenu } + Self::Dropdown { title, submenu } } fn map(self, f: &impl Fn(Message) -> B) -> Entry { match self { Self::Item { - content, + title, hotkey, on_activation, } => Entry::Item { - content, + title, hotkey, on_activation: f(on_activation), }, - Self::Dropdown { content, submenu } => Entry::Dropdown { - content, + Self::Dropdown { title, submenu } => Entry::Dropdown { + title, submenu: submenu.map(f), }, Self::Separator => Entry::Separator, @@ -127,22 +124,20 @@ impl PartialEq for Entry { fn eq(&self, other: &Self) -> bool { match (self, other) { ( + Entry::Item { title, hotkey, .. }, Entry::Item { - content, hotkey, .. - }, - Entry::Item { - content: other_content, + title: other_title, hotkey: other_hotkey, .. }, - ) => content == other_content && hotkey == other_hotkey, + ) => title == other_title && hotkey == other_hotkey, ( - Entry::Dropdown { content, submenu }, + Entry::Dropdown { title, submenu }, Entry::Dropdown { - content: other_content, + title: other_title, submenu: other_submenu, }, - ) => content == other_content && submenu == other_submenu, + ) => title == other_title && submenu == other_submenu, (Entry::Separator, Entry::Separator) => true, _ => false, } diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index dc7f90ad..e61611aa 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -187,19 +187,17 @@ pub fn menu(menu: &Menu) -> winit::window::Menu { for item in menu.iter() { match item { - menu::Entry::Item { - content, hotkey, .. - } => { - converted.add_item(id, content, hotkey.map(self::hotkey)); + menu::Entry::Item { title, hotkey, .. } => { + converted.add_item(id, title, hotkey.map(self::hotkey)); id += 1; } - menu::Entry::Dropdown { content, submenu } => { + menu::Entry::Dropdown { title, submenu } => { let mut converted_submenu = winit::window::Menu::new(); let n_children = menu_i(&mut converted_submenu, id, submenu); - converted.add_dropdown(content, converted_submenu); + converted.add_dropdown(title, converted_submenu); id += n_children; } -- cgit From c8ac77e4e99414746adedf38cf69ac8dcd1601a4 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 19 Jul 2021 21:05:16 +0700 Subject: Write documentation for `menu` method in `Application` --- src/application.rs | 4 +++- winit/src/application.rs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/application.rs b/src/application.rs index 42c28f99..ae85c841 100644 --- a/src/application.rs +++ b/src/application.rs @@ -191,7 +191,9 @@ pub trait Application: Sized { false } - /// TODO + /// Returns the current system [`Menu`] of the [`Application`]. + /// + /// By default, it returns an empty [`Menu`]. fn menu(&self) -> Menu { Menu::new() } diff --git a/winit/src/application.rs b/winit/src/application.rs index ada64dfc..5d1aabf9 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -100,7 +100,9 @@ pub trait Application: Program { false } - /// TODO + /// Returns the current system [`Menu`] of the [`Application`]. + /// + /// By default, it returns an empty [`Menu`]. fn menu(&self) -> Menu { Menu::new() } -- cgit From b97954a1ee3ec7bc85d1d41b397e994752ff1831 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 19 Jul 2021 21:18:54 +0700 Subject: Add a presets `Menu` to the `game_of_life` example --- core/src/menu.rs | 4 ++-- examples/game_of_life/src/main.rs | 8 ++++++++ examples/game_of_life/src/preset.rs | 13 +++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/core/src/menu.rs b/core/src/menu.rs index 3ad7b7a2..8a679085 100644 --- a/core/src/menu.rs +++ b/core/src/menu.rs @@ -41,7 +41,7 @@ impl Menu { /// /// This is useful to compose menus and split them into different /// abstraction levels. - pub fn map(self, f: &impl Fn(Message) -> B) -> Menu { + pub fn map(self, f: impl Fn(Message) -> B + Copy) -> Menu { // TODO: Use a boxed trait to avoid reallocation of entries Menu { entries: self @@ -100,7 +100,7 @@ impl Entry { Self::Dropdown { title, submenu } } - fn map(self, f: &impl Fn(Message) -> B) -> Entry { + fn map(self, f: impl Fn(Message) -> B + Copy) -> Entry { match self { Self::Item { title, diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 64599163..877aa2d2 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -6,6 +6,7 @@ mod style; use grid::Grid; use iced::button::{self, Button}; use iced::executor; +use iced::menu::{self, Menu}; use iced::pick_list::{self, PickList}; use iced::slider::{self, Slider}; use iced::time; @@ -128,6 +129,13 @@ impl Application for GameOfLife { } } + fn menu(&self) -> Menu { + Menu::with_entries(vec![menu::Entry::dropdown( + "Presets", + Preset::menu().map(Message::PresetPicked), + )]) + } + fn view(&mut self) -> Element { let version = self.version; let selected_speed = self.next_speed.unwrap_or(self.speed); diff --git a/examples/game_of_life/src/preset.rs b/examples/game_of_life/src/preset.rs index 05157b6a..1c199a72 100644 --- a/examples/game_of_life/src/preset.rs +++ b/examples/game_of_life/src/preset.rs @@ -1,3 +1,5 @@ +use iced::menu::{self, Menu}; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Preset { Custom, @@ -26,6 +28,17 @@ pub static ALL: &[Preset] = &[ ]; impl Preset { + pub fn menu() -> Menu { + Menu::with_entries( + ALL.iter() + .copied() + .map(|preset| { + menu::Entry::item(preset.to_string(), None, preset) + }) + .collect(), + ) + } + pub fn life(self) -> Vec<(isize, isize)> { #[rustfmt::skip] let cells = match self { -- cgit From 82db3c78b6cfa2cc55ece6ffa46811bfb5195f60 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 20 Jul 2021 21:34:20 +0700 Subject: Update `winit` and `glutin` dependencies ... and remove crates.io patch --- Cargo.toml | 3 --- glutin/Cargo.toml | 4 ++-- winit/Cargo.toml | 4 ++-- winit/src/conversion.rs | 17 +++++------------ 4 files changed, 9 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1a4da1e3..5b1cfb0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,9 +93,6 @@ iced_core = { version = "0.4", path = "core" } iced_futures = { version = "0.3", path = "futures" } thiserror = "1.0" -[patch.crates-io] -winit = { git = "https://github.com/iced-rs/winit", rev = "327c8756f90953a6a03f818113f8566176e6eb0d"} - [target.'cfg(not(target_arch = "wasm32"))'.dependencies] iced_winit = { version = "0.3", path = "winit" } iced_glutin = { version = "0.2", path = "glutin", optional = true } diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml index b42a6b36..b2a7f307 100644 --- a/glutin/Cargo.toml +++ b/glutin/Cargo.toml @@ -15,8 +15,8 @@ debug = ["iced_winit/debug"] [dependencies.glutin] version = "0.27" -# git = "https://github.com/iced-rs/glutin" -# rev = "2564d0ab87cf2ad824a2a58733aebe40dd2f29bb" +git = "https://github.com/iced-rs/glutin" +rev = "03437d8a1826d83c62017b2bb7bf18bfc9e352cc" [dependencies.iced_native] version = "0.4" diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 87fd23d5..b1192135 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -20,8 +20,8 @@ thiserror = "1.0" [dependencies.winit] version = "0.25" -# git = "https://github.com/iced-rs/winit" -# rev = "e351421a32bf01b428325dde44dea39ee2656153" +git = "https://github.com/iced-rs/winit" +rev = "844485272a7412cb35cdbfac3524decdf59475ca" [dependencies.iced_native] version = "0.4" diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index e61611aa..5a8b9fd8 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -218,17 +218,15 @@ pub fn menu(menu: &Menu) -> winit::window::Menu { /// Given a [`Menu`] and an identifier of a [`menu::Entry`], it returns the /// `Message` that should be produced when that entry is activated. -pub fn menu_message(menu: &Menu, id: isize) -> Option +pub fn menu_message(menu: &Menu, id: u32) -> Option where Message: Clone, { - use std::convert::TryFrom; - fn find_message( - target: usize, - starting_id: usize, + target: u32, + starting_id: u32, menu: &Menu, - ) -> Result + ) -> Result where Message: Clone, { @@ -260,12 +258,7 @@ where Err(id - starting_id) } - // TODO: Does `winit` really need to provide an `isize`? - if let Ok(id) = usize::try_from(id) { - find_message(id, 0, menu).ok() - } else { - None - } + find_message(id, 0, menu).ok() } /// Converts a `MouseCursor` from [`iced_native`] to a [`winit`] cursor icon. -- cgit From 665422e256b5eb8fed23f1a13900838e0e3bcb44 Mon Sep 17 00:00:00 2001 From: aentity Date: Tue, 13 Jul 2021 22:27:48 -0400 Subject: Use ceil on svg dimensions, fix svg memory usage Calls ceil() on dimension bounds as this appears fix svg memory unbounded usage because no longer cache miss. The height and width return from resvg seem to always be ceiling of float dimensions, so we try to match. --- wgpu/src/image/vector.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 8c7de617..cd511a45 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -75,8 +75,8 @@ impl Cache { let id = handle.id(); let (width, height) = ( - (scale * width).round() as u32, - (scale * height).round() as u32, + (scale * width).ceil() as u32, + (scale * height).ceil() as u32, ); // TODO: Optimize! @@ -122,6 +122,7 @@ impl Cache { device, encoder, )?; + log::debug!("allocating {} {}x{}", id, width, height); let _ = self.svg_hits.insert(id); let _ = self.rasterized_hits.insert((id, width, height)); -- cgit From 2d0be87551998c3e888c5358969e5f071b18df23 Mon Sep 17 00:00:00 2001 From: Luni-4 Date: Wed, 21 Jul 2021 10:24:52 +0200 Subject: CI: Open the todos example via double-click on MacOS --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 480a8710..a00e2a22 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -72,6 +72,8 @@ jobs: env: MACOSX_DEPLOYMENT_TARGET: 10.14 run: cargo build --verbose --release --package todos + - name: Open binary via double-click + run: chmod +x target/release/todos - name: Archive todos binary uses: actions/upload-artifact@v1 with: -- cgit From 4de27142133b2dd6f780f0f955f620787102e581 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 21 Jul 2021 18:11:31 +0700 Subject: Remove `winit` example ... we can extend an existing example instead! --- Cargo.toml | 1 - examples/winit/Cargo.toml | 9 --------- examples/winit/README.md | 18 ------------------ examples/winit/src/main.rs | 46 ---------------------------------------------- 4 files changed, 74 deletions(-) delete mode 100644 examples/winit/Cargo.toml delete mode 100644 examples/winit/README.md delete mode 100644 examples/winit/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index b5ceb20f..329877c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,7 +67,6 @@ members = [ "examples/counter", "examples/custom_widget", "examples/download_progress", - "examples/winit", "examples/events", "examples/game_of_life", "examples/geometry", diff --git a/examples/winit/Cargo.toml b/examples/winit/Cargo.toml deleted file mode 100644 index 2286dfc9..00000000 --- a/examples/winit/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "winit" -version = "0.1.0" -authors = ["Tim Untersberger "] -edition = "2018" -publish = false - -[dependencies] -iced = { path = "../.." } diff --git a/examples/winit/README.md b/examples/winit/README.md deleted file mode 100644 index 4d9fc5b9..00000000 --- a/examples/winit/README.md +++ /dev/null @@ -1,18 +0,0 @@ -## Counter - -The classic counter example explained in the [`README`](../../README.md). - -The __[`main`]__ file contains all the code of the example. - - - -You can run it with `cargo run`: -``` -cargo run --package counter -``` - -[`main`]: src/main.rs diff --git a/examples/winit/src/main.rs b/examples/winit/src/main.rs deleted file mode 100644 index a1364ea6..00000000 --- a/examples/winit/src/main.rs +++ /dev/null @@ -1,46 +0,0 @@ -use iced::{Column, Element, Sandbox, Settings, window::Settings as WindowSettings}; - -const WINDOW_WIDTH: i32 = 200; -const WINDOW_HEIGHT: i32 = 200; -const DISPLAY_WIDTH: i32 = 1920; -const DISPLAY_HEIGHT: i32 = 1080; -// These numbers are specific to a 1920x1080 monitor -const BORDER_X: i32 = 8; -const BORDER_Y: i32 = 2; -const CAPTION_HEIGHT: i32 = 4; - -pub fn main() { - let x = DISPLAY_WIDTH / 2 - WINDOW_WIDTH / 2 - BORDER_X; - let y = DISPLAY_HEIGHT / 2 - WINDOW_HEIGHT / 2 - BORDER_Y - CAPTION_HEIGHT; - let settings = Settings { - window: WindowSettings { - size: (WINDOW_WIDTH as u32, WINDOW_HEIGHT as u32), - position: (x, y), - ..Default::default() - }, - ..Default::default() - }; - Winit::run(settings).unwrap() -} - -#[derive(Default)] -struct Winit; - -impl Sandbox for Winit { - type Message = (); - - fn new() -> Self { - Self::default() - } - - fn title(&self) -> String { - String::from("winit - Iced") - } - - fn update(&mut self, _message: Self::Message) { - } - - fn view(&mut self) -> Element { - Column::new().into() - } -} -- cgit From 6793a7e00dc96425ae720309182ec8b03c076111 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 21 Jul 2021 18:15:32 +0700 Subject: Remove DPI from docs in `window::Settings::position` --- src/window/settings.rs | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/window/settings.rs b/src/window/settings.rs index 0fecc3bb..59654edc 100644 --- a/src/window/settings.rs +++ b/src/window/settings.rs @@ -8,22 +8,11 @@ pub struct Settings { /// The initial position of the window. /// - /// When the decorations of the window are enabled, Windows 10 will add some inivisble padding - /// to the window. This padding gets included in the position. So if you have decorations - /// enabled and want the window to be at 0,0 you would have to set the position to - /// -DPI_BORDER_X,-DPI_BORDER_Y. - /// - /// DPI_BORDER_X/DPI_BORDER_Y are the usual size of the padding, which changes based on the DPI of the display. - /// - /// On a 1920x1080 monitor you would have to set the position to -8,-2. - /// - /// For info on how you could implement positioning that supports all DPI monitors look at the - /// following WINAPI calls: - /// - /// * GetDpiForMonitor (with MDT_RAW_DPI) - /// * GetSystemMetricsForDpi (with SM_CXFRAME and SM_CYFRAME) - /// - /// Note: this gets ignored on the web + /// When the decorations of the window are enabled, Windows 10 will add some + /// invisible padding to the window. This padding gets included in the + /// position. So if you have decorations enabled and want the window to be + /// at (0, 0) you would have to set the position to + /// `(PADDING_X, PADDING_Y)`. pub position: (i32, i32), /// The minimum size of the window. -- cgit From 72b3bf95de3f11812b1541fe2ffea76a515aee79 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 21 Jul 2021 18:59:24 +0700 Subject: Improve `window::Position` API --- src/window.rs | 2 ++ src/window/position.rs | 33 +++++++++++++++++++++++++++++++++ src/window/settings.rs | 14 ++++---------- winit/src/conversion.rs | 45 ++++++++++++++++++++++++++++++++++++++++++++- winit/src/lib.rs | 2 ++ winit/src/position.rs | 22 ++++++++++++++++++++++ winit/src/settings.rs | 19 ++++++++++++++----- 7 files changed, 121 insertions(+), 16 deletions(-) create mode 100644 src/window/position.rs create mode 100644 winit/src/position.rs diff --git a/src/window.rs b/src/window.rs index a2883b62..7d441062 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,9 +1,11 @@ //! Configure the window of your application in native platforms. mod mode; +mod position; mod settings; pub mod icon; pub use icon::Icon; pub use mode::Mode; +pub use position::Position; pub use settings::Settings; diff --git a/src/window/position.rs b/src/window/position.rs new file mode 100644 index 00000000..8535ef6a --- /dev/null +++ b/src/window/position.rs @@ -0,0 +1,33 @@ +/// The position of a window in a given screen. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Position { + /// The platform-specific default position for a new window. + Default, + /// The window is completely centered on the screen. + Centered, + /// The window is positioned with specific coordinates: `(X, Y)`. + /// + /// When the decorations of the window are enabled, Windows 10 will add some + /// invisible padding to the window. This padding gets included in the + /// position. So if you have decorations enabled and want the window to be + /// at (0, 0) you would have to set the position to + /// `(PADDING_X, PADDING_Y)`. + Specific(i32, i32), +} + +impl Default for Position { + fn default() -> Self { + Self::Default + } +} + +#[cfg(not(target_arch = "wasm32"))] +impl From for iced_winit::Position { + fn from(position: Position) -> Self { + match position { + Position::Default => Self::Default, + Position::Centered => Self::Centered, + Position::Specific(x, y) => Self::Specific(x, y), + } + } +} diff --git a/src/window/settings.rs b/src/window/settings.rs index 59654edc..ec6c3071 100644 --- a/src/window/settings.rs +++ b/src/window/settings.rs @@ -1,4 +1,4 @@ -use crate::window::Icon; +use crate::window::{Icon, Position}; /// The window settings of an application. #[derive(Debug, Clone)] @@ -7,13 +7,7 @@ pub struct Settings { pub size: (u32, u32), /// The initial position of the window. - /// - /// When the decorations of the window are enabled, Windows 10 will add some - /// invisible padding to the window. This padding gets included in the - /// position. So if you have decorations enabled and want the window to be - /// at (0, 0) you would have to set the position to - /// `(PADDING_X, PADDING_Y)`. - pub position: (i32, i32), + pub position: Position, /// The minimum size of the window. pub min_size: Option<(u32, u32)>, @@ -41,7 +35,7 @@ impl Default for Settings { fn default() -> Settings { Settings { size: (1024, 768), - position: (100, 100), + position: Position::default(), min_size: None, max_size: None, resizable: true, @@ -58,7 +52,7 @@ impl From for iced_winit::settings::Window { fn from(settings: Settings) -> Self { Self { size: settings.size, - position: settings.position, + position: iced_winit::Position::from(settings.position), min_size: settings.min_size, max_size: settings.max_size, resizable: settings.resizable, diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index b850a805..f785fce6 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -6,7 +6,7 @@ use crate::keyboard; use crate::mouse; use crate::touch; use crate::window; -use crate::{Event, Mode, Point}; +use crate::{Event, Mode, Point, Position}; /// Converts a winit window event into an iced event. pub fn window_event( @@ -133,6 +133,49 @@ pub fn window_event( } } +/// Converts a [`Position`] to a [`winit`] logical position for a given monitor. +/// +/// [`winit`]: https://github.com/rust-windowing/winit +pub fn position( + monitor: Option<&winit::monitor::MonitorHandle>, + (width, height): (u32, u32), + position: Position, +) -> Option { + match position { + Position::Default => None, + Position::Specific(x, y) => { + Some(winit::dpi::Position::Logical(winit::dpi::LogicalPosition { + x: f64::from(x), + y: f64::from(y), + })) + } + Position::Centered => { + if let Some(monitor) = monitor { + let start = monitor.position(); + + let resolution: winit::dpi::LogicalSize = + monitor.size().to_logical(monitor.scale_factor()); + + let centered: winit::dpi::PhysicalPosition = + winit::dpi::LogicalPosition { + x: (resolution.width - f64::from(width)) / 2.0, + y: (resolution.height - f64::from(height)) / 2.0, + } + .to_physical(monitor.scale_factor()); + + Some(winit::dpi::Position::Physical( + winit::dpi::PhysicalPosition { + x: start.x + centered.x, + y: start.y + centered.y, + }, + )) + } else { + None + } + } + } +} + /// Converts a [`Mode`] to a [`winit`] fullscreen mode. /// /// [`winit`]: https://github.com/rust-windowing/winit diff --git a/winit/src/lib.rs b/winit/src/lib.rs index c9f324dd..1707846a 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -31,12 +31,14 @@ pub mod settings; mod clipboard; mod error; mod mode; +mod position; mod proxy; pub use application::Application; pub use clipboard::Clipboard; pub use error::Error; pub use mode::Mode; +pub use position::Position; pub use proxy::Proxy; pub use settings::Settings; diff --git a/winit/src/position.rs b/winit/src/position.rs new file mode 100644 index 00000000..c260c29e --- /dev/null +++ b/winit/src/position.rs @@ -0,0 +1,22 @@ +/// The position of a window in a given screen. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Position { + /// The platform-specific default position for a new window. + Default, + /// The window is completely centered on the screen. + Centered, + /// The window is positioned with specific coordinates: `(X, Y)`. + /// + /// When the decorations of the window are enabled, Windows 10 will add some + /// invisible padding to the window. This padding gets included in the + /// position. So if you have decorations enabled and want the window to be + /// at (0, 0) you would have to set the position to + /// `(PADDING_X, PADDING_Y)`. + Specific(i32, i32), +} + +impl Default for Position { + fn default() -> Self { + Self::Default + } +} diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 1769c676..743f79bc 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -9,7 +9,7 @@ mod platform; pub use platform::PlatformSpecific; use crate::conversion; -use crate::Mode; +use crate::{Mode, Position}; use winit::monitor::MonitorHandle; use winit::window::WindowBuilder; @@ -36,7 +36,7 @@ pub struct Window { pub size: (u32, u32), /// The position of the window. - pub position: (i32, i32), + pub position: Position, /// The minimum size of the window. pub min_size: Option<(u32, u32)>, @@ -78,15 +78,21 @@ impl Window { window_builder = window_builder .with_title(title) .with_inner_size(winit::dpi::LogicalSize { width, height }) - .with_position(winit::dpi::LogicalPosition { x: self.position.0, y: self.position.1 }) .with_resizable(self.resizable) .with_decorations(self.decorations) .with_transparent(self.transparent) .with_window_icon(self.icon) .with_always_on_top(self.always_on_top) - .with_fullscreen(conversion::fullscreen(primary_monitor, mode)) .with_visible(conversion::visible(mode)); + if let Some(position) = conversion::position( + primary_monitor.as_ref(), + self.size, + self.position, + ) { + window_builder = window_builder.with_position(position); + } + if let Some((width, height)) = self.min_size { window_builder = window_builder .with_min_inner_size(winit::dpi::LogicalSize { width, height }); @@ -108,6 +114,9 @@ impl Window { .with_drag_and_drop(self.platform_specific.drag_and_drop); } + window_builder = window_builder + .with_fullscreen(conversion::fullscreen(primary_monitor, mode)); + window_builder } } @@ -116,7 +125,7 @@ impl Default for Window { fn default() -> Window { Window { size: (1024, 768), - position: (100, 100), + position: Position::default(), min_size: None, max_size: None, resizable: true, -- cgit From 39b8f7de50ca53c67214c4ec2af2d3a0944f006a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 21 Jul 2021 19:10:11 +0700 Subject: Center window in `game_of_life` example --- examples/game_of_life/src/main.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 64599163..642262b0 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -9,6 +9,7 @@ use iced::executor; use iced::pick_list::{self, PickList}; use iced::slider::{self, Slider}; use iced::time; +use iced::window; use iced::{ Align, Application, Checkbox, Clipboard, Column, Command, Container, Element, Length, Row, Settings, Subscription, Text, @@ -19,6 +20,10 @@ use std::time::{Duration, Instant}; pub fn main() -> iced::Result { GameOfLife::run(Settings { antialiasing: true, + window: window::Settings { + position: window::Position::Centered, + ..window::Settings::default() + }, ..Settings::default() }) } -- cgit From 217f5be8272f48a5b043d066ed1788cc127e1164 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 22 Jul 2021 18:21:50 +0700 Subject: Add `text_multithreading` to `Settings` in `iced_glow` and `iced_wgpu` --- Cargo.toml | 4 ---- glow/Cargo.toml | 1 - glow/src/backend.rs | 7 ++++++- glow/src/settings.rs | 5 +++++ glow/src/text.rs | 10 ++++++---- wgpu/Cargo.toml | 1 - wgpu/src/backend.rs | 9 +++++++-- wgpu/src/settings.rs | 5 +++++ wgpu/src/text.rs | 5 ++--- 9 files changed, 31 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index acae878f..6d894eba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,8 +25,6 @@ canvas = ["iced_wgpu/canvas"] qr_code = ["iced_wgpu/qr_code"] # Enables using system fonts default_system_font = ["iced_wgpu/default_system_font"] -# Enables draw_cache_multithread for glyph drawing -glyph_draw_cache_multithread = ["iced_wgpu/glyph_draw_cache_multithread"] # Enables the `iced_glow` renderer. Overrides `iced_wgpu` glow = ["iced_glow", "iced_glutin"] # Enables the `Canvas` widget for `iced_glow` @@ -35,8 +33,6 @@ glow_canvas = ["iced_glow/canvas"] glow_qr_code = ["iced_glow/qr_code"] # Enables using system fonts for `iced_glow` glow_default_system_font = ["iced_glow/default_system_font"] -# Enables draw_cache_multithread for `iced_glow` glyph drawing -glow_glyph_draw_cache_multithread = ["iced_glow/glyph_draw_cache_multithread"] # Enables a debug view in native platforms (press F12) debug = ["iced_winit/debug"] # Enables `tokio` as the `executor::Default` on native platforms diff --git a/glow/Cargo.toml b/glow/Cargo.toml index f5934f8f..e40b8ba8 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -11,7 +11,6 @@ repository = "https://github.com/hecrj/iced" canvas = ["iced_graphics/canvas"] qr_code = ["iced_graphics/qr_code"] default_system_font = ["iced_graphics/font-source"] -glyph_draw_cache_multithread = [] # Not supported yet! image = [] svg = [] diff --git a/glow/src/backend.rs b/glow/src/backend.rs index 92bb993e..1680fc00 100644 --- a/glow/src/backend.rs +++ b/glow/src/backend.rs @@ -24,7 +24,12 @@ pub struct Backend { impl Backend { /// Creates a new [`Backend`]. pub fn new(gl: &glow::Context, settings: Settings) -> Self { - let text_pipeline = text::Pipeline::new(gl, settings.default_font); + let text_pipeline = text::Pipeline::new( + gl, + settings.default_font, + settings.text_multithreading, + ); + let quad_pipeline = quad::Pipeline::new(gl); let triangle_pipeline = triangle::Pipeline::new(gl); diff --git a/glow/src/settings.rs b/glow/src/settings.rs index 8477eb57..cf8e891f 100644 --- a/glow/src/settings.rs +++ b/glow/src/settings.rs @@ -16,6 +16,10 @@ pub struct Settings { /// By default, it will be set to 20. pub default_text_size: u16, + /// If enabled, spread text workload in multiple threads when multiple cores + /// are available. + pub text_multithreading: bool, + /// The antialiasing strategy that will be used for triangle primitives. pub antialiasing: Option, } @@ -25,6 +29,7 @@ impl Default for Settings { Settings { default_font: None, default_text_size: 20, + text_multithreading: false, antialiasing: None, } } diff --git a/glow/src/text.rs b/glow/src/text.rs index 7c9574a2..a4c39dfe 100644 --- a/glow/src/text.rs +++ b/glow/src/text.rs @@ -11,7 +11,11 @@ pub struct Pipeline { } impl Pipeline { - pub fn new(gl: &glow::Context, default_font: Option<&[u8]>) -> Self { + pub fn new( + gl: &glow::Context, + default_font: Option<&[u8]>, + multithreading: bool, + ) -> Self { let default_font = default_font.map(|slice| slice.to_vec()); // TODO: Font customization @@ -41,9 +45,7 @@ impl Pipeline { let draw_brush = glow_glyph::GlyphBrushBuilder::using_font(font.clone()) .initial_cache_size((2048, 2048)) - .draw_cache_multithread(cfg!( - feature = "glyph_draw_cache_multithread" - )) + .draw_cache_multithread(multithreading) .build(&gl); let measure_brush = diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index fc9f7379..3eacdd0f 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -24,7 +24,6 @@ farbfeld = ["image_rs/farbfeld"] canvas = ["iced_graphics/canvas"] qr_code = ["iced_graphics/qr_code"] default_system_font = ["iced_graphics/font-source"] -glyph_draw_cache_multithread = [] [dependencies] wgpu = "0.8" diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 534c6cb7..783079f3 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -31,8 +31,13 @@ pub struct Backend { impl Backend { /// Creates a new [`Backend`]. pub fn new(device: &wgpu::Device, settings: Settings) -> Self { - let text_pipeline = - text::Pipeline::new(device, settings.format, settings.default_font); + let text_pipeline = text::Pipeline::new( + device, + settings.format, + settings.default_font, + settings.text_multithreading, + ); + let quad_pipeline = quad::Pipeline::new(device, settings.format); let triangle_pipeline = triangle::Pipeline::new( device, diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index 6c97d895..b490e54e 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -29,6 +29,10 @@ pub struct Settings { /// By default, it will be set to 20. pub default_text_size: u16, + /// If enabled, spread text workload in multiple threads when multiple cores + /// are available. + pub text_multithreading: bool, + /// The antialiasing strategy that will be used for triangle primitives. pub antialiasing: Option, } @@ -65,6 +69,7 @@ impl Default for Settings { internal_backend: wgpu::BackendBit::PRIMARY, default_font: None, default_text_size: 20, + text_multithreading: false, antialiasing: None, } } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index eed5de38..2b5b94c9 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -15,6 +15,7 @@ impl Pipeline { device: &wgpu::Device, format: wgpu::TextureFormat, default_font: Option<&[u8]>, + multithreading: bool, ) -> Self { let default_font = default_font.map(|slice| slice.to_vec()); @@ -46,9 +47,7 @@ impl Pipeline { let draw_brush = wgpu_glyph::GlyphBrushBuilder::using_font(font.clone()) .initial_cache_size((2048, 2048)) - .draw_cache_multithread(cfg!( - feature = "glyph_draw_cache_multithread" - )) + .draw_cache_multithread(multithreading) .build(device, format); let measure_brush = -- cgit From 357a8a95c9820651110fe4d80d8d33a2678827c0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 22 Jul 2021 18:27:33 +0700 Subject: Introduce `text_multithreading` to `Settings` --- glow/src/settings.rs | 4 ++++ src/application.rs | 1 + src/settings.rs | 20 +++++++++++++++----- wgpu/src/settings.rs | 4 ++++ 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/glow/src/settings.rs b/glow/src/settings.rs index cf8e891f..f3dddfaf 100644 --- a/glow/src/settings.rs +++ b/glow/src/settings.rs @@ -18,9 +18,13 @@ pub struct Settings { /// If enabled, spread text workload in multiple threads when multiple cores /// are available. + /// + /// By default, it is disabled. pub text_multithreading: bool, /// The antialiasing strategy that will be used for triangle primitives. + /// + /// By default, it is `None`. pub antialiasing: Option, } diff --git a/src/application.rs b/src/application.rs index ee532e0b..14bed2cb 100644 --- a/src/application.rs +++ b/src/application.rs @@ -208,6 +208,7 @@ pub trait Application: Sized { let renderer_settings = crate::renderer::Settings { default_font: settings.default_font, default_text_size: settings.default_text_size, + text_multithreading: settings.text_multithreading, antialiasing: if settings.antialiasing { Some(crate::renderer::settings::Antialiasing::MSAAx4) } else { diff --git a/src/settings.rs b/src/settings.rs index 2b32258d..480bf813 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -25,9 +25,11 @@ pub struct Settings { /// The default value is 20. pub default_text_size: u16, - /// Whether the [`Application`] should exit when the user requests the - /// window to close (e.g. the user presses the close button). - pub exit_on_close_request: bool, + /// If enabled, spread text workload in multiple threads when multiple cores + /// are available. + /// + /// By default, it is disabled. + pub text_multithreading: bool, /// If set to true, the renderer will try to perform antialiasing for some /// primitives. @@ -39,6 +41,12 @@ pub struct Settings { /// /// [`Canvas`]: crate::widget::Canvas pub antialiasing: bool, + + /// Whether the [`Application`] should exit when the user requests the + /// window to close (e.g. the user presses the close button). + /// + /// By default, it is enabled. + pub exit_on_close_request: bool, } impl Settings { @@ -53,8 +61,9 @@ impl Settings { window: default_settings.window, default_font: default_settings.default_font, default_text_size: default_settings.default_text_size, - exit_on_close_request: default_settings.exit_on_close_request, + text_multithreading: default_settings.text_multithreading, antialiasing: default_settings.antialiasing, + exit_on_close_request: default_settings.exit_on_close_request, } } } @@ -69,8 +78,9 @@ where window: Default::default(), default_font: Default::default(), default_text_size: 20, - exit_on_close_request: true, + text_multithreading: false, antialiasing: false, + exit_on_close_request: true, } } } diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index b490e54e..9a7eed34 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -31,9 +31,13 @@ pub struct Settings { /// If enabled, spread text workload in multiple threads when multiple cores /// are available. + /// + /// By default, it is disabled. pub text_multithreading: bool, /// The antialiasing strategy that will be used for triangle primitives. + /// + /// By default, it is `None`. pub antialiasing: Option, } -- 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 --- graphics/src/widget/pick_list.rs | 3 ++- native/src/widget/pick_list.rs | 32 +++++++++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/graphics/src/widget/pick_list.rs b/graphics/src/widget/pick_list.rs index 32dfbdf9..a7fe3e93 100644 --- a/graphics/src/widget/pick_list.rs +++ b/graphics/src/widget/pick_list.rs @@ -31,6 +31,7 @@ where bounds: Rectangle, cursor_position: Point, selected: Option, + placeholder: Option, padding: Padding, text_size: u16, font: Font, @@ -68,7 +69,7 @@ where ( Primitive::Group { - primitives: if let Some(label) = selected { + primitives: if let Some(label) = selected.or(placeholder) { let label = Primitive::Text { content: label, size: f32::from(text_size), 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 9c75ddcb3370c0e2caefdb0a3c1f30ea877faf02 Mon Sep 17 00:00:00 2001 From: Jon Pacheco Date: Sat, 22 May 2021 19:28:27 +0100 Subject: feat: add placeholder to pick_list example --- examples/pick_list/src/main.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/pick_list/src/main.rs b/examples/pick_list/src/main.rs index 68662602..1eec9791 100644 --- a/examples/pick_list/src/main.rs +++ b/examples/pick_list/src/main.rs @@ -11,7 +11,7 @@ pub fn main() -> iced::Result { struct Example { scroll: scrollable::State, pick_list: pick_list::State, - selected_language: Language, + selected_language: Option, } #[derive(Debug, Clone, Copy)] @@ -33,7 +33,7 @@ impl Sandbox for Example { fn update(&mut self, message: Message) { match message { Message::LanguageSelected(language) => { - self.selected_language = language; + self.selected_language = Some(language); } } } @@ -42,9 +42,10 @@ impl Sandbox for Example { let pick_list = PickList::new( &mut self.pick_list, &Language::ALL[..], - Some(self.selected_language), + self.selected_language, Message::LanguageSelected, - ); + ) + .placeholder("Choose a language..."); let mut content = Scrollable::new(&mut self.scroll) .width(Length::Fill) -- 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(+) 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(-) 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 1b3606884747374f1e5599e3c783f36a2f2cac6f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 22 Jul 2021 20:13:14 +0700 Subject: Introduce `placeholder_color` to `pick_list::Style` --- examples/game_of_life/src/style.rs | 1 + graphics/src/widget/pick_list.rs | 5 ++++- style/src/pick_list.rs | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/game_of_life/src/style.rs b/examples/game_of_life/src/style.rs index 6605826f..be9a0e96 100644 --- a/examples/game_of_life/src/style.rs +++ b/examples/game_of_life/src/style.rs @@ -171,6 +171,7 @@ impl pick_list::StyleSheet for PickList { }, border_radius: 2.0, icon_size: 0.5, + ..pick_list::Style::default() } } diff --git a/graphics/src/widget/pick_list.rs b/graphics/src/widget/pick_list.rs index a7fe3e93..01283bd5 100644 --- a/graphics/src/widget/pick_list.rs +++ b/graphics/src/widget/pick_list.rs @@ -38,6 +38,7 @@ where style: &Box, ) -> Self::Output { let is_mouse_over = bounds.contains(cursor_position); + let is_selected = selected.is_some(); let style = if is_mouse_over { style.hovered() @@ -74,7 +75,9 @@ where content: label, size: f32::from(text_size), font, - color: style.text_color, + color: is_selected + .then(|| style.text_color) + .unwrap_or(style.placeholder_color), bounds: Rectangle { x: bounds.x + f32::from(padding.left), y: bounds.center_y(), diff --git a/style/src/pick_list.rs b/style/src/pick_list.rs index a757ba98..d1801e5f 100644 --- a/style/src/pick_list.rs +++ b/style/src/pick_list.rs @@ -5,6 +5,7 @@ use iced_core::{Background, Color}; #[derive(Debug, Clone, Copy)] pub struct Style { pub text_color: Color, + pub placeholder_color: Color, pub background: Background, pub border_radius: f32, pub border_width: f32, @@ -16,6 +17,7 @@ impl std::default::Default for Style { fn default() -> Self { Self { text_color: Color::BLACK, + placeholder_color: [0.4, 0.4, 0.4].into(), background: Background::Color([0.87, 0.87, 0.87].into()), border_radius: 0.0, border_width: 1.0, -- 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` --- graphics/src/widget/pick_list.rs | 6 ++++-- native/src/widget/pick_list.rs | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/graphics/src/widget/pick_list.rs b/graphics/src/widget/pick_list.rs index 01283bd5..88a590b5 100644 --- a/graphics/src/widget/pick_list.rs +++ b/graphics/src/widget/pick_list.rs @@ -31,7 +31,7 @@ where bounds: Rectangle, cursor_position: Point, selected: Option, - placeholder: Option, + placeholder: Option<&str>, padding: Padding, text_size: u16, font: Font, @@ -70,7 +70,9 @@ where ( Primitive::Group { - primitives: if let Some(label) = selected.or(placeholder) { + primitives: if let Some(label) = + selected.or_else(|| placeholder.map(str::to_string)) + { let label = Primitive::Text { content: label, size: f32::from(text_size), 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 29cc840cfa129a5ee3ecd1befe6e0fe85cb0e2d7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 22 Jul 2021 20:32:54 +0700 Subject: Add empty `select_all` implementation for `TextInput` in `iced_web` --- web/src/widget/text_input.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web/src/widget/text_input.rs b/web/src/widget/text_input.rs index bc2048a8..d61ee2fd 100644 --- a/web/src/widget/text_input.rs +++ b/web/src/widget/text_input.rs @@ -232,4 +232,9 @@ impl State { // TODO Self::default() } + + /// Selects all the content of the [`TextInput`]. + pub fn select_all(&mut self) { + // TODO + } } -- cgit From 72d5d9b4bd03f6596369360018ee101a4a3053ae Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 22 Jul 2021 20:37:12 +0700 Subject: Add newline to `todos` example ... just for readability --- examples/todos/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index ee0022d5..97415475 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -267,6 +267,7 @@ impl Task { TaskMessage::Edit => { let mut text_input = text_input::State::focused(); text_input.select_all(); + self.state = TaskState::Editing { text_input, delete_button: button::State::new(), -- 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(-) 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(-) 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