diff options
Diffstat (limited to 'native/src')
39 files changed, 1932 insertions, 476 deletions
diff --git a/native/src/clipboard.rs b/native/src/clipboard.rs new file mode 100644 index 00000000..4c574590 --- /dev/null +++ b/native/src/clipboard.rs @@ -0,0 +1,8 @@ +/// A buffer for short-term storage and transfer within and between +/// applications. +pub trait Clipboard { +    /// Returns the current content of the [`Clipboard`] as text. +    /// +    /// [`Clipboard`]: trait.Clipboard.html +    fn content(&self) -> Option<String>; +} diff --git a/native/src/element.rs b/native/src/element.rs index d4237fd0..276f7614 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -1,5 +1,5 @@  use crate::{ -    layout, renderer, Color, Event, Hasher, Layout, Length, Point, Widget, +    layout, Clipboard, Color, Event, Hasher, Layout, Length, Point, Widget,  };  /// A generic [`Widget`]. @@ -171,7 +171,7 @@ where      /// ```      pub fn map<F, B>(self, f: F) -> Element<'a, B, Renderer>      where -        Message: 'static + Clone, +        Message: 'static,          Renderer: 'a,          B: 'static,          F: 'static + Fn(Message) -> B, @@ -194,7 +194,7 @@ where      ) -> Element<'a, Message, Renderer>      where          Message: 'static, -        Renderer: 'a + renderer::Debugger, +        Renderer: 'a + layout::Debugger,      {          Element {              widget: Box::new(Explain::new(self, color.into())), @@ -234,13 +234,18 @@ where      pub fn draw(          &self,          renderer: &mut Renderer, +        defaults: &Renderer::Defaults,          layout: Layout<'_>,          cursor_position: Point,      ) -> Renderer::Output { -        self.widget.draw(renderer, layout, cursor_position) +        self.widget +            .draw(renderer, defaults, layout, cursor_position)      } -    pub(crate) fn hash_layout(&self, state: &mut Hasher) { +    /// Computes the _layout_ hash of the [`Element`]. +    ///  +    /// [`Element`]: struct.Element.html +    pub fn hash_layout(&self, state: &mut Hasher) {          self.widget.hash_layout(state);      }  } @@ -267,7 +272,6 @@ impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> {  impl<'a, A, B, Renderer> Widget<B, Renderer> for Map<'a, A, B, Renderer>  where -    A: Clone,      Renderer: crate::Renderer,  {      fn width(&self) -> Length { @@ -293,6 +297,7 @@ where          cursor_position: Point,          messages: &mut Vec<B>,          renderer: &Renderer, +        clipboard: Option<&dyn Clipboard>,      ) {          let mut original_messages = Vec::new(); @@ -302,21 +307,23 @@ where              cursor_position,              &mut original_messages,              renderer, +            clipboard,          );          original_messages -            .iter() -            .cloned() +            .drain(..)              .for_each(|message| messages.push((self.mapper)(message)));      }      fn draw(          &self,          renderer: &mut Renderer, +        defaults: &Renderer::Defaults,          layout: Layout<'_>,          cursor_position: Point,      ) -> Renderer::Output { -        self.widget.draw(renderer, layout, cursor_position) +        self.widget +            .draw(renderer, defaults, layout, cursor_position)      }      fn hash_layout(&self, state: &mut Hasher) { @@ -341,7 +348,7 @@ where  impl<'a, Message, Renderer> Widget<Message, Renderer>      for Explain<'a, Message, Renderer>  where -    Renderer: crate::Renderer + renderer::Debugger, +    Renderer: crate::Renderer + layout::Debugger,  {      fn width(&self) -> Length {          self.element.widget.width() @@ -366,6 +373,7 @@ where          cursor_position: Point,          messages: &mut Vec<Message>,          renderer: &Renderer, +        clipboard: Option<&dyn Clipboard>,      ) {          self.element.widget.on_event(              event, @@ -373,16 +381,19 @@ where              cursor_position,              messages,              renderer, +            clipboard,          )      }      fn draw(          &self,          renderer: &mut Renderer, +        defaults: &Renderer::Defaults,          layout: Layout<'_>,          cursor_position: Point,      ) -> Renderer::Output {          renderer.explain( +            defaults,              self.element.widget.as_ref(),              layout,              cursor_position, diff --git a/native/src/event.rs b/native/src/event.rs index 71f06006..b2550ead 100644 --- a/native/src/event.rs +++ b/native/src/event.rs @@ -1,4 +1,7 @@ -use crate::input::{keyboard, mouse}; +use crate::{ +    input::{keyboard, mouse}, +    window, +};  /// A user interface event.  /// @@ -6,11 +9,14 @@ use crate::input::{keyboard, mouse};  /// 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, Copy, Debug)] +#[derive(PartialEq, Clone, Debug)]  pub enum Event {      /// A keyboard event      Keyboard(keyboard::Event),      /// A mouse event      Mouse(mouse::Event), + +    /// A window event +    Window(window::Event),  } diff --git a/native/src/input/keyboard.rs b/native/src/input/keyboard.rs index 57c24484..432e75ba 100644 --- a/native/src/input/keyboard.rs +++ b/native/src/input/keyboard.rs @@ -1,6 +1,8 @@  //! Build keyboard events.  mod event;  mod key_code; +mod modifiers_state;  pub use event::Event;  pub use key_code::KeyCode; +pub use modifiers_state::ModifiersState; diff --git a/native/src/input/keyboard/event.rs b/native/src/input/keyboard/event.rs index 8118f112..862f30c4 100644 --- a/native/src/input/keyboard/event.rs +++ b/native/src/input/keyboard/event.rs @@ -1,13 +1,13 @@ -use super::KeyCode; +use super::{KeyCode, ModifiersState};  use crate::input::ButtonState; -#[derive(Debug, Clone, Copy, PartialEq)]  /// A keyboard event.  ///  /// _**Note:** This type is largely incomplete! If you need to track  /// additional events, feel free to [open an issue] and share your use case!_  ///  /// [open an issue]: https://github.com/hecrj/iced/issues +#[derive(Debug, Clone, Copy, PartialEq)]  pub enum Event {      /// A keyboard key was pressed or released.      Input { @@ -16,6 +16,9 @@ pub enum Event {          /// The key identifier          key_code: KeyCode, + +        /// The state of the modifier keys +        modifiers: ModifiersState,      },      /// A unicode character was received. diff --git a/native/src/input/keyboard/modifiers_state.rs b/native/src/input/keyboard/modifiers_state.rs new file mode 100644 index 00000000..4e3794b3 --- /dev/null +++ b/native/src/input/keyboard/modifiers_state.rs @@ -0,0 +1,15 @@ +/// The current state of the keyboard modifiers. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ModifiersState { +    /// Whether a shift key is pressed +    pub shift: bool, + +    /// 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, +} diff --git a/native/src/layout.rs b/native/src/layout.rs index e945706b..4a3ab94a 100644 --- a/native/src/layout.rs +++ b/native/src/layout.rs @@ -1,9 +1,11 @@  //! Position your widgets properly. +mod debugger;  mod limits;  mod node;  pub mod flex; +pub use debugger::Debugger;  pub use limits::Limits;  pub use node::Node; diff --git a/native/src/renderer/debugger.rs b/native/src/layout/debugger.rs index 4cc50661..e4b21609 100644 --- a/native/src/renderer/debugger.rs +++ b/native/src/layout/debugger.rs @@ -1,9 +1,9 @@ -use crate::{Color, Layout, Point, Widget}; +use crate::{Color, Layout, Point, Renderer, Widget};  /// A renderer able to graphically explain a [`Layout`].  /// -/// [`Layout`]: ../struct.Layout.html -pub trait Debugger: super::Renderer { +/// [`Layout`]: struct.Layout.html +pub trait Debugger: Renderer {      /// Explains the [`Layout`] of an [`Element`] for debugging purposes.      ///      /// This will be called when [`Element::explain`] has been used. It should @@ -13,10 +13,11 @@ pub trait Debugger: super::Renderer {      /// [`Layout`] and its children.      ///      /// [`Layout`]: struct.Layout.html -    /// [`Element`]: struct.Element.html -    /// [`Element::explain`]: struct.Element.html#method.explain +    /// [`Element`]: ../struct.Element.html +    /// [`Element::explain`]: ../struct.Element.html#method.explain      fn explain<Message>(          &mut self, +        defaults: &Self::Defaults,          widget: &dyn Widget<Message, Self>,          layout: Layout<'_>,          cursor_position: Point, diff --git a/native/src/layout/flex.rs b/native/src/layout/flex.rs index bc90553e..2f65f1c1 100644 --- a/native/src/layout/flex.rs +++ b/native/src/layout/flex.rs @@ -18,7 +18,7 @@  // limitations under the License.  use crate::{      layout::{Limits, Node}, -    Align, Element, Size, +    Align, Element, Point, Size,  };  /// The main axis of a flex layout. @@ -73,11 +73,12 @@ where      Renderer: crate::Renderer,  {      let limits = limits.pad(padding); +    let total_spacing = spacing * items.len().saturating_sub(1) as f32; +    let max_cross = axis.cross(limits.max()); -    let mut total_non_fill = -        spacing as f32 * (items.len() as i32 - 1).max(0) as f32;      let mut fill_sum = 0; -    let mut cross = axis.cross(limits.min()); +    let mut cross = axis.cross(limits.min()).max(axis.cross(limits.fill())); +    let mut available = axis.main(limits.max()) - total_spacing;      let mut nodes: Vec<Node> = Vec::with_capacity(items.len());      nodes.resize(items.len(), Node::default()); @@ -90,12 +91,15 @@ where          .fill_factor();          if fill_factor == 0 { -            let child_limits = Limits::new(Size::ZERO, limits.max()); +            let (max_width, max_height) = axis.pack(available, max_cross); + +            let child_limits = +                Limits::new(Size::ZERO, Size::new(max_width, max_height));              let layout = child.layout(renderer, &child_limits);              let size = layout.size(); -            total_non_fill += axis.main(size); +            available -= axis.main(size);              cross = cross.max(axis.cross(size));              nodes[i] = layout; @@ -104,8 +108,7 @@ where          }      } -    let available = axis.main(limits.max()); -    let remaining = (available - total_non_fill).max(0.0); +    let remaining = available.max(0.0);      for (i, child) in items.iter().enumerate() {          let fill_factor = match axis { @@ -149,8 +152,7 @@ where          let (x, y) = axis.pack(main, padding); -        node.bounds.x = x; -        node.bounds.y = y; +        node.move_to(Point::new(x, y));          match axis {              Axis::Horizontal => { @@ -166,13 +168,11 @@ where          main += axis.main(size);      } -    let (width, height) = axis.pack(main, cross); +    let (width, height) = axis.pack(main - padding, cross);      let size = limits.resolve(Size::new(width, height)); -    let (padding_x, padding_y) = axis.pack(padding, padding * 2.0); -      Node::with_children( -        Size::new(size.width + padding_x, size.height + padding_y), +        Size::new(size.width + padding * 2.0, size.height + padding * 2.0),          nodes,      )  } diff --git a/native/src/layout/limits.rs b/native/src/layout/limits.rs index 740417d4..664c881a 100644 --- a/native/src/layout/limits.rs +++ b/native/src/layout/limits.rs @@ -44,6 +44,14 @@ impl Limits {          self.max      } +    /// Returns the fill [`Size`] of the [`Limits`]. +    /// +    /// [`Limits`]: struct.Limits.html +    /// [`Size`]: ../struct.Size.html +    pub fn fill(&self) -> Size { +        self.fill +    } +      /// Applies a width constraint to the current [`Limits`].      ///      /// [`Limits`]: struct.Limits.html @@ -52,7 +60,7 @@ impl Limits {              Length::Shrink => {                  self.fill.width = self.min.width;              } -            Length::Fill => { +            Length::Fill | Length::FillPortion(_) => {                  self.fill.width = self.fill.width.min(self.max.width);              }              Length::Units(units) => { @@ -76,7 +84,7 @@ impl Limits {              Length::Shrink => {                  self.fill.height = self.min.height;              } -            Length::Fill => { +            Length::Fill | Length::FillPortion(_) => {                  self.fill.height = self.fill.height.min(self.max.height);              }              Length::Units(units) => { diff --git a/native/src/layout/node.rs b/native/src/layout/node.rs index acfd33bd..a265c46a 100644 --- a/native/src/layout/node.rs +++ b/native/src/layout/node.rs @@ -1,9 +1,9 @@ -use crate::{Align, Rectangle, Size}; +use crate::{Align, Point, Rectangle, Size};  /// The bounds of an element and its children.  #[derive(Debug, Clone, Default)]  pub struct Node { -    pub(crate) bounds: Rectangle, +    bounds: Rectangle,      children: Vec<Node>,  } @@ -54,7 +54,10 @@ impl Node {          &self.children      } -    pub(crate) fn align( +    /// Aligns the [`Node`] in the given space. +    /// +    /// [`Node`]: struct.Node.html +    pub fn align(          &mut self,          horizontal_alignment: Align,          vertical_alignment: Align, @@ -80,4 +83,12 @@ impl Node {              }          }      } + +    /// Moves the [`Node`] to the given position. +    /// +    /// [`Node`]: struct.Node.html +    pub fn move_to(&mut self, position: Point) { +        self.bounds.x = position.x; +        self.bounds.y = position.y; +    }  } diff --git a/native/src/lib.rs b/native/src/lib.rs index 45c3c699..e4e7baee 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -14,7 +14,7 @@  //! - A [`Widget`] trait, which is used to implement new widgets: from layout  //!   requirements to event and drawing logic.  //! - A bunch of `Renderer` traits, meant to keep the crate renderer-agnostic. -//! - A [`Windowed`] trait, leveraging [`raw-window-handle`], which can be +//! - A [`window::Renderer`] trait, leveraging [`raw-window-handle`], which can be  //!   implemented by graphical renderers that target _windows_. Window-based  //!   shells (like [`iced_winit`]) can use this trait to stay renderer-agnostic.  //! @@ -31,37 +31,46 @@  //! [`druid`]: https://github.com/xi-editor/druid  //! [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle  //! [`Widget`]: widget/trait.Widget.html -//! [`Windowed`]: renderer/trait.Windowed.html +//! [`window::Renderer`]: window/trait.Renderer.html  //! [`UserInterface`]: struct.UserInterface.html  //! [renderer]: renderer/index.html  #![deny(missing_docs)]  #![deny(missing_debug_implementations)]  #![deny(unused_results)] -#![deny(unsafe_code)] -#![deny(rust_2018_idioms)] +#![forbid(unsafe_code)] +#![forbid(rust_2018_idioms)]  pub mod input;  pub mod layout;  pub mod renderer; +pub mod subscription;  pub mod widget; +pub mod window; +mod clipboard;  mod element;  mod event;  mod hasher;  mod mouse_cursor; -mod size; +mod runtime;  mod user_interface;  pub use iced_core::{ -    Align, Background, Color, Command, Font, HorizontalAlignment, Length, -    Point, Rectangle, Vector, VerticalAlignment, +    Align, Background, Color, Font, HorizontalAlignment, Length, Point, +    Rectangle, Size, Vector, VerticalAlignment,  }; +pub use iced_futures::{executor, futures, Command}; +#[doc(no_inline)] +pub use executor::Executor; + +pub use clipboard::Clipboard;  pub use element::Element;  pub use event::Event;  pub use hasher::Hasher;  pub use layout::Layout;  pub use mouse_cursor::MouseCursor;  pub use renderer::Renderer; -pub use size::Size; +pub use runtime::Runtime; +pub use subscription::Subscription;  pub use user_interface::{Cache, UserInterface};  pub use widget::*; diff --git a/native/src/mouse_cursor.rs b/native/src/mouse_cursor.rs index a4740a27..c7297e0e 100644 --- a/native/src/mouse_cursor.rs +++ b/native/src/mouse_cursor.rs @@ -22,3 +22,9 @@ pub enum MouseCursor {      /// The cursor is over a text widget.      Text,  } + +impl Default for MouseCursor { +    fn default() -> MouseCursor { +        MouseCursor::OutOfBounds +    } +} diff --git a/native/src/renderer.rs b/native/src/renderer.rs index 7a68ada4..a16df72b 100644 --- a/native/src/renderer.rs +++ b/native/src/renderer.rs @@ -20,15 +20,10 @@  //! [`Checkbox`]: ../widget/checkbox/struct.Checkbox.html  //! [`checkbox::Renderer`]: ../widget/checkbox/trait.Renderer.html -mod debugger;  #[cfg(debug_assertions)]  mod null; -mod windowed; - -pub use debugger::Debugger;  #[cfg(debug_assertions)]  pub use null::Null; -pub use windowed::{Target, Windowed};  use crate::{layout, Element}; @@ -43,6 +38,13 @@ pub trait Renderer: Sized {      /// [`Renderer`]: trait.Renderer.html      type Output; +    /// The default styling attributes of the [`Renderer`]. +    /// +    /// This type can be leveraged to implement style inheritance. +    /// +    /// [`Renderer`]: trait.Renderer.html +    type Defaults: Default; +      /// Lays out the elements of a user interface.      ///      /// You should override this if you need to perform any operations before or @@ -50,7 +52,8 @@ pub trait Renderer: Sized {      fn layout<'a, Message>(          &mut self,          element: &Element<'a, Message, Self>, +        limits: &layout::Limits,      ) -> layout::Node { -        element.layout(self, &layout::Limits::NONE) +        element.layout(self, limits)      }  } diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs index 182f033a..0fcce5ad 100644 --- a/native/src/renderer/null.rs +++ b/native/src/renderer/null.rs @@ -1,20 +1,33 @@  use crate::{ -    button, checkbox, column, radio, row, scrollable, text, text_input, -    Background, Color, Element, Font, HorizontalAlignment, Layout, Point, +    button, checkbox, column, progress_bar, radio, row, scrollable, slider, +    text, text_input, Color, Element, Font, HorizontalAlignment, Layout, Point,      Rectangle, Renderer, Size, VerticalAlignment,  };  /// A renderer that does nothing. +/// +/// It can be useful if you are writing tests!  #[derive(Debug, Clone, Copy)]  pub struct Null; +impl Null { +    /// Creates a new [`Null`] renderer. +    /// +    /// [`Null`]: struct.Null.html +    pub fn new() -> Null { +        Null +    } +} +  impl Renderer for Null {      type Output = (); +    type Defaults = ();  }  impl column::Renderer for Null {      fn draw<Message>(          &mut self, +        _defaults: &Self::Defaults,          _content: &[Element<'_, Message, Self>],          _layout: Layout<'_>,          _cursor_position: Point, @@ -25,6 +38,7 @@ impl column::Renderer for Null {  impl row::Renderer for Null {      fn draw<Message>(          &mut self, +        _defaults: &Self::Defaults,          _content: &[Element<'_, Message, Self>],          _layout: Layout<'_>,          _cursor_position: Point, @@ -33,9 +47,7 @@ impl row::Renderer for Null {  }  impl text::Renderer for Null { -    fn default_size(&self) -> u16 { -        20 -    } +    const DEFAULT_SIZE: u16 = 20;      fn measure(          &self, @@ -49,6 +61,7 @@ impl text::Renderer for Null {      fn draw(          &mut self, +        _defaults: &Self::Defaults,          _bounds: Rectangle,          _content: &str,          _size: u16, @@ -61,13 +74,15 @@ impl text::Renderer for Null {  }  impl scrollable::Renderer for Null { -    fn is_mouse_over_scrollbar( +    type Style = (); + +    fn scrollbar(          &self,          _bounds: Rectangle,          _content_bounds: Rectangle, -        _cursor_position: Point, -    ) -> bool { -        false +        _offset: u32, +    ) -> Option<scrollable::Scrollbar> { +        None      }      fn draw( @@ -77,44 +92,73 @@ impl scrollable::Renderer for Null {          _content_bounds: Rectangle,          _is_mouse_over: bool,          _is_mouse_over_scrollbar: bool, +        _scrollbar: Option<scrollable::Scrollbar>,          _offset: u32, +        _style: &Self::Style,          _content: Self::Output,      ) {      }  }  impl text_input::Renderer for Null { +    type Style = (); +      fn default_size(&self) -> u16 {          20      } +    fn measure_value(&self, _value: &str, _size: u16, _font: Font) -> f32 { +        0.0 +    } + +    fn offset( +        &self, +        _text_bounds: Rectangle, +        _size: u16, +        _value: &text_input::Value, +        _state: &text_input::State, +        _font: Font, +    ) -> f32 { +        0.0 +    } +      fn draw(          &mut self,          _bounds: Rectangle,          _text_bounds: Rectangle,          _cursor_position: Point,          _size: u16, +        _font: Font,          _placeholder: &str,          _value: &text_input::Value,          _state: &text_input::State, +        _style: &Self::Style,      ) -> Self::Output {      }  }  impl button::Renderer for Null { -    fn draw( +    const DEFAULT_PADDING: u16 = 0; + +    type Style = (); + +    fn draw<Message>(          &mut self, +        _defaults: &Self::Defaults,          _bounds: Rectangle,          _cursor_position: Point, +        _is_disabled: bool,          _is_pressed: bool, -        _background: Option<Background>, -        _border_radius: u16, -        _content: Self::Output, +        _style: &Self::Style, +        _content: &Element<'_, Message, Self>, +        _content_layout: Layout<'_>,      ) -> Self::Output {      }  }  impl radio::Renderer for Null { +    type Style = (); +      fn default_size(&self) -> u32 {          20      } @@ -125,14 +169,16 @@ impl radio::Renderer for Null {          _is_selected: bool,          _is_mouse_over: bool,          _label: Self::Output, +        _style: &Self::Style,      ) {      }  }  impl checkbox::Renderer for Null { -    fn default_size(&self) -> u32 { -        20 -    } +    type Style = (); + +    const DEFAULT_SIZE: u16 = 20; +    const DEFAULT_SPACING: u16 = 15;      fn draw(          &mut self, @@ -140,6 +186,41 @@ impl checkbox::Renderer for Null {          _is_checked: bool,          _is_mouse_over: bool,          _label: Self::Output, +        _style: &Self::Style, +    ) { +    } +} + +impl slider::Renderer for Null { +    type Style = (); + +    fn height(&self) -> u32 { +        30 +    } + +    fn draw( +        &mut self, +        _bounds: Rectangle, +        _cursor_position: Point, +        _range: std::ops::RangeInclusive<f32>, +        _value: f32, +        _is_dragging: bool, +        _style_sheet: &Self::Style, +    ) { +    } +} + +impl progress_bar::Renderer for Null { +    type Style = (); + +    const DEFAULT_HEIGHT: u16 = 30; + +    fn draw( +        &self, +        _bounds: Rectangle, +        _range: std::ops::RangeInclusive<f32>, +        _value: f32, +        _style: &Self::Style,      ) {      }  } diff --git a/native/src/renderer/windowed.rs b/native/src/renderer/windowed.rs deleted file mode 100644 index 813a03f2..00000000 --- a/native/src/renderer/windowed.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::MouseCursor; - -use raw_window_handle::HasRawWindowHandle; - -/// A renderer that can target windows. -pub trait Windowed: super::Renderer + Sized { -    /// The type of target. -    type Target: Target<Renderer = Self>; - -    /// Creates a new [`Windowed`] renderer. -    /// -    /// [`Windowed`]: trait.Windowed.html -    fn new() -> Self; - -    /// Performs the drawing operations described in the output on the given -    /// target. -    /// -    /// The overlay can be a bunch of debug text logs. It should be rendered on -    /// top of the GUI on most scenarios. -    fn draw<T: AsRef<str>>( -        &mut self, -        output: &Self::Output, -        overlay: &[T], -        target: &mut Self::Target, -    ) -> MouseCursor; -} - -/// A rendering target. -pub trait Target { -    /// The renderer of this target. -    type Renderer; - -    /// Creates a new rendering [`Target`] from the given window handle, width, -    /// height and dpi factor. -    /// -    /// [`Target`]: trait.Target.html -    fn new<W: HasRawWindowHandle>( -        window: &W, -        width: u16, -        height: u16, -        dpi: f32, -        renderer: &Self::Renderer, -    ) -> Self; - -    /// Resizes the current [`Target`]. -    /// -    /// [`Target`]: trait.Target.html -    fn resize( -        &mut self, -        width: u16, -        height: u16, -        dpi: f32, -        renderer: &Self::Renderer, -    ); -} diff --git a/native/src/runtime.rs b/native/src/runtime.rs new file mode 100644 index 00000000..9fa031f4 --- /dev/null +++ b/native/src/runtime.rs @@ -0,0 +1,12 @@ +//! Run commands and subscriptions. +use crate::{Event, Hasher}; + +/// A native runtime with a generic executor and receiver of results. +/// +/// It can be used by shells to easily spawn a [`Command`] or track a +/// [`Subscription`]. +/// +/// [`Command`]: ../struct.Command.html +/// [`Subscription`]: ../struct.Subscription.html +pub type Runtime<Executor, Receiver, Message> = +    iced_futures::Runtime<Hasher, Event, Executor, Receiver, Message>; diff --git a/native/src/size.rs b/native/src/size.rs deleted file mode 100644 index 30e2a57e..00000000 --- a/native/src/size.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::f32; - -/// An amount of space in 2 dimensions. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Size { -    /// The width. -    pub width: f32, -    /// The height. -    pub height: f32, -} - -impl Size { -    /// A [`Size`] with zero width and height. -    /// -    /// [`Size`]: struct.Size.html -    pub const ZERO: Size = Size::new(0., 0.); - -    /// A [`Size`] with infinite width and height. -    /// -    /// [`Size`]: struct.Size.html -    pub const INFINITY: Size = Size::new(f32::INFINITY, f32::INFINITY); - -    /// A [`Size`] of infinite width and height. -    /// -    /// [`Size`]: struct.Size.html -    pub const fn new(width: f32, height: f32) -> Self { -        Size { width, height } -    } - -    /// Increments the [`Size`] to account for the given padding. -    /// -    /// [`Size`]: struct.Size.html -    pub fn pad(&self, padding: f32) -> Self { -        Size { -            width: self.width + padding * 2.0, -            height: self.height + padding * 2.0, -        } -    } -} diff --git a/native/src/subscription.rs b/native/src/subscription.rs new file mode 100644 index 00000000..0d002c6c --- /dev/null +++ b/native/src/subscription.rs @@ -0,0 +1,47 @@ +//! Listen to external events in your application. +use crate::{Event, Hasher}; +use iced_futures::futures::stream::BoxStream; + +/// A request to listen to external events. +/// +/// Besides performing async actions on demand with [`Command`], most +/// applications also need to listen to external events passively. +/// +/// A [`Subscription`] is normally provided to some runtime, like a [`Command`], +/// and it will generate events as long as the user keeps requesting it. +/// +/// For instance, you can use a [`Subscription`] to listen to a WebSocket +/// connection, keyboard presses, mouse events, time ticks, etc. +/// +/// [`Command`]: ../struct.Command.html +/// [`Subscription`]: struct.Subscription.html +pub type Subscription<T> = iced_futures::Subscription<Hasher, Event, T>; + +/// A stream of runtime events. +/// +/// It is the input of a [`Subscription`] in the native runtime. +/// +/// [`Subscription`]: type.Subscription.html +pub type EventStream = BoxStream<'static, Event>; + +/// A native [`Subscription`] tracker. +/// +/// [`Subscription`]: type.Subscription.html +pub type Tracker = iced_futures::subscription::Tracker<Hasher, Event>; + +pub use iced_futures::subscription::Recipe; + +mod events; + +use events::Events; + +/// Returns a [`Subscription`] to all the runtime events. +/// +/// This subscription will notify your application of any [`Event`] handled by +/// the runtime. +/// +/// [`Subscription`]: type.Subscription.html +/// [`Event`]: ../enum.Event.html +pub fn events() -> Subscription<Event> { +    Subscription::from_recipe(Events) +} diff --git a/native/src/subscription/events.rs b/native/src/subscription/events.rs new file mode 100644 index 00000000..7d33166e --- /dev/null +++ b/native/src/subscription/events.rs @@ -0,0 +1,24 @@ +use crate::{ +    subscription::{EventStream, Recipe}, +    Event, Hasher, +}; +use iced_futures::futures::stream::BoxStream; + +pub struct Events; + +impl Recipe<Hasher, Event> for Events { +    type Output = Event; + +    fn hash(&self, state: &mut Hasher) { +        use std::hash::Hash; + +        std::any::TypeId::of::<Self>().hash(state); +    } + +    fn stream( +        self: Box<Self>, +        event_stream: EventStream, +    ) -> BoxStream<'static, Self::Output> { +        event_stream +    } +} diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 9833c815..08914bed 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -1,4 +1,6 @@ -use crate::{input::mouse, layout, Element, Event, Layout, Point, Size}; +use crate::{ +    input::mouse, layout, Clipboard, Element, Event, Layout, Point, Size, +};  use std::hash::Hasher; @@ -15,6 +17,7 @@ pub struct UserInterface<'a, Message, Renderer> {      hash: u64,      root: Element<'a, Message, Renderer>,      layout: layout::Node, +    bounds: Size,      cursor_position: Point,  } @@ -37,28 +40,11 @@ where      /// is naive way to set up our application loop:      ///      /// ```no_run -    /// use iced_native::{UserInterface, Cache}; +    /// use iced_native::{UserInterface, Cache, Size};      /// use iced_wgpu::Renderer;      ///      /// # mod iced_wgpu { -    /// #     pub struct Renderer; -    /// # -    /// #     impl Renderer { -    /// #         pub fn new() -> Self { Renderer } -    /// #     } -    /// # -    /// #     impl iced_native::Renderer for Renderer { type Output = (); } -    /// # -    /// #     impl iced_native::column::Renderer for Renderer { -    /// #         fn draw<Message>( -    /// #             &mut self, -    /// #             _children: &[iced_native::Element<'_, Message, Self>], -    /// #             _layout: iced_native::Layout<'_>, -    /// #             _cursor_position: iced_native::Point, -    /// #         ) -> Self::Output { -    /// #             () -    /// #         } -    /// #     } +    /// #     pub use iced_native::renderer::Null as Renderer;      /// # }      /// #      /// # use iced_native::Column; @@ -75,6 +61,7 @@ where      /// let mut counter = Counter::new();      /// let mut cache = Cache::new();      /// let mut renderer = Renderer::new(); +    /// let mut window_size = Size::new(1024.0, 768.0);      ///      /// // Application loop      /// loop { @@ -83,6 +70,7 @@ where      ///     // Build the user interface      ///     let user_interface = UserInterface::build(      ///         counter.view(), +    ///         window_size,      ///         cache,      ///         &mut renderer,      ///     ); @@ -96,26 +84,30 @@ where      /// ```      pub fn build<E: Into<Element<'a, Message, Renderer>>>(          root: E, +        bounds: Size,          cache: Cache,          renderer: &mut Renderer,      ) -> Self {          let root = root.into(); -        let hasher = &mut crate::Hasher::default(); -        root.hash_layout(hasher); +        let hash = { +            let hasher = &mut crate::Hasher::default(); +            root.hash_layout(hasher); -        let hash = hasher.finish(); +            hasher.finish() +        }; -        let layout = if hash == cache.hash { +        let layout = if hash == cache.hash && bounds == cache.bounds {              cache.layout          } else { -            renderer.layout(&root) +            renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds))          };          UserInterface {              hash,              root,              layout, +            bounds,              cursor_position: cache.cursor_position,          }      } @@ -133,28 +125,11 @@ where      /// completing [the previous example](#example):      ///      /// ```no_run -    /// use iced_native::{UserInterface, Cache}; +    /// use iced_native::{UserInterface, Cache, Size};      /// use iced_wgpu::Renderer;      ///      /// # mod iced_wgpu { -    /// #     pub struct Renderer; -    /// # -    /// #     impl Renderer { -    /// #         pub fn new() -> Self { Renderer } -    /// #     } -    /// # -    /// #     impl iced_native::Renderer for Renderer { type Output = (); } -    /// # -    /// #     impl iced_native::column::Renderer for Renderer { -    /// #         fn draw<Message>( -    /// #             &mut self, -    /// #             _children: &[iced_native::Element<'_, Message, Self>], -    /// #             _layout: iced_native::Layout<'_>, -    /// #             _cursor_position: iced_native::Point, -    /// #         ) -> Self::Output { -    /// #             () -    /// #         } -    /// #     } +    /// #     pub use iced_native::renderer::Null as Renderer;      /// # }      /// #      /// # use iced_native::Column; @@ -171,6 +146,7 @@ where      /// let mut counter = Counter::new();      /// let mut cache = Cache::new();      /// let mut renderer = Renderer::new(); +    /// let mut window_size = Size::new(1024.0, 768.0);      ///      /// // Initialize our event storage      /// let mut events = Vec::new(); @@ -180,12 +156,13 @@ where      ///      ///     let mut user_interface = UserInterface::build(      ///         counter.view(), +    ///         window_size,      ///         cache,      ///         &mut renderer,      ///     );      ///      ///     // Update the user interface -    ///     let messages = user_interface.update(&renderer, events.drain(..)); +    ///     let messages = user_interface.update(events.drain(..), None, &renderer);      ///      ///     cache = user_interface.into_cache();      /// @@ -197,8 +174,9 @@ where      /// ```      pub fn update(          &mut self, +        events: impl IntoIterator<Item = Event>, +        clipboard: Option<&dyn Clipboard>,          renderer: &Renderer, -        events: impl Iterator<Item = Event>,      ) -> Vec<Message> {          let mut messages = Vec::new(); @@ -213,6 +191,7 @@ where                  self.cursor_position,                  &mut messages,                  renderer, +                clipboard,              );          } @@ -233,28 +212,11 @@ where      /// [completing the last example](#example-1):      ///      /// ```no_run -    /// use iced_native::{UserInterface, Cache}; +    /// use iced_native::{UserInterface, Cache, Size};      /// use iced_wgpu::Renderer;      ///      /// # mod iced_wgpu { -    /// #     pub struct Renderer; -    /// # -    /// #     impl Renderer { -    /// #         pub fn new() -> Self { Renderer } -    /// #     } -    /// # -    /// #     impl iced_native::Renderer for Renderer { type Output = (); } -    /// # -    /// #     impl iced_native::column::Renderer for Renderer { -    /// #         fn draw<Message>( -    /// #             &mut self, -    /// #             _children: &[iced_native::Element<'_, Message, Self>], -    /// #             _layout: iced_native::Layout<'_>, -    /// #             _cursor_position: iced_native::Point, -    /// #         ) -> Self::Output { -    /// #             () -    /// #         } -    /// #     } +    /// #     pub use iced_native::renderer::Null as Renderer;      /// # }      /// #      /// # use iced_native::Column; @@ -271,6 +233,7 @@ where      /// let mut counter = Counter::new();      /// let mut cache = Cache::new();      /// let mut renderer = Renderer::new(); +    /// let mut window_size = Size::new(1024.0, 768.0);      /// let mut events = Vec::new();      ///      /// loop { @@ -278,11 +241,12 @@ where      ///      ///     let mut user_interface = UserInterface::build(      ///         counter.view(), +    ///         window_size,      ///         cache,      ///         &mut renderer,      ///     );      /// -    ///     let messages = user_interface.update(&renderer, events.drain(..)); +    ///     let messages = user_interface.update(events.drain(..), None, &renderer);      ///      ///     // Draw the user interface      ///     let mouse_cursor = user_interface.draw(&mut renderer); @@ -300,6 +264,7 @@ where      pub fn draw(&self, renderer: &mut Renderer) -> Renderer::Output {          self.root.widget.draw(              renderer, +            &Renderer::Defaults::default(),              Layout::new(&self.layout),              self.cursor_position,          ) @@ -314,6 +279,7 @@ where          Cache {              hash: self.hash,              layout: self.layout, +            bounds: self.bounds,              cursor_position: self.cursor_position,          }      } @@ -326,6 +292,7 @@ where  pub struct Cache {      hash: u64,      layout: layout::Node, +    bounds: Size,      cursor_position: Point,  } @@ -341,6 +308,7 @@ impl Cache {          Cache {              hash: 0,              layout: layout::Node::new(Size::new(0.0, 0.0)), +            bounds: Size::ZERO,              cursor_position: Point::new(-1.0, -1.0),          }      } diff --git a/native/src/widget.rs b/native/src/widget.rs index 71dcdc0d..f9424b02 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -25,10 +25,13 @@ pub mod checkbox;  pub mod column;  pub mod container;  pub mod image; +pub mod progress_bar;  pub mod radio;  pub mod row;  pub mod scrollable;  pub mod slider; +pub mod space; +pub mod svg;  pub mod text;  pub mod text_input; @@ -43,6 +46,8 @@ pub use container::Container;  #[doc(no_inline)]  pub use image::Image;  #[doc(no_inline)] +pub use progress_bar::ProgressBar; +#[doc(no_inline)]  pub use radio::Radio;  #[doc(no_inline)]  pub use row::Row; @@ -51,11 +56,15 @@ pub use scrollable::Scrollable;  #[doc(no_inline)]  pub use slider::Slider;  #[doc(no_inline)] +pub use space::Space; +#[doc(no_inline)] +pub use svg::Svg; +#[doc(no_inline)]  pub use text::Text;  #[doc(no_inline)]  pub use text_input::TextInput; -use crate::{layout, Event, Hasher, Layout, Length, Point}; +use crate::{layout, Clipboard, Event, Hasher, Layout, Length, Point};  /// A component that displays information and allows interaction.  /// @@ -98,6 +107,7 @@ where      fn draw(          &self,          renderer: &mut Renderer, +        defaults: &Renderer::Defaults,          layout: Layout<'_>,          cursor_position: Point,      ) -> Renderer::Output; @@ -139,6 +149,7 @@ where          _cursor_position: Point,          _messages: &mut Vec<Message>,          _renderer: &Renderer, +        _clipboard: Option<&dyn Clipboard>,      ) {      }  } diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 023c4ee8..f1d46936 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -6,7 +6,7 @@  //! [`State`]: struct.State.html  use crate::{      input::{mouse, ButtonState}, -    layout, Background, Element, Event, Hasher, Layout, Length, Point, +    layout, Clipboard, Element, Event, Hasher, Layout, Length, Point,      Rectangle, Widget,  };  use std::hash::Hash; @@ -28,18 +28,22 @@ use std::hash::Hash;  ///     .on_press(Message::ButtonPressed);  /// ```  #[allow(missing_debug_implementations)] -pub struct Button<'a, Message, Renderer> { +pub struct Button<'a, Message, Renderer: self::Renderer> {      state: &'a mut State,      content: Element<'a, Message, Renderer>,      on_press: Option<Message>,      width: Length, +    height: Length,      min_width: u32, +    min_height: u32,      padding: u16, -    background: Option<Background>, -    border_radius: u16, +    style: Renderer::Style,  } -impl<'a, Message, Renderer> Button<'a, Message, Renderer> { +impl<'a, Message, Renderer> Button<'a, Message, Renderer> +where +    Renderer: self::Renderer, +{      /// Creates a new [`Button`] with some local [`State`] and the given      /// content.      /// @@ -54,10 +58,11 @@ impl<'a, Message, Renderer> Button<'a, Message, Renderer> {              content: content.into(),              on_press: None,              width: Length::Shrink, +            height: Length::Shrink,              min_width: 0, -            padding: 0, -            background: None, -            border_radius: 0, +            min_height: 0, +            padding: Renderer::DEFAULT_PADDING, +            style: Renderer::Style::default(),          }      } @@ -69,36 +74,35 @@ impl<'a, Message, Renderer> Button<'a, Message, Renderer> {          self      } -    /// Sets the minimum width of the [`Button`]. +    /// Sets the height of the [`Button`].      ///      /// [`Button`]: struct.Button.html -    pub fn min_width(mut self, min_width: u32) -> Self { -        self.min_width = min_width; +    pub fn height(mut self, height: Length) -> Self { +        self.height = height;          self      } -    /// Sets the padding of the [`Button`]. +    /// Sets the minimum width of the [`Button`].      ///      /// [`Button`]: struct.Button.html -    pub fn padding(mut self, padding: u16) -> Self { -        self.padding = padding; +    pub fn min_width(mut self, min_width: u32) -> Self { +        self.min_width = min_width;          self      } -    /// Sets the [`Background`] of the [`Button`]. +    /// Sets the minimum height of the [`Button`].      ///      /// [`Button`]: struct.Button.html -    /// [`Background`]: ../../struct.Background.html -    pub fn background(mut self, background: Background) -> Self { -        self.background = Some(background); +    pub fn min_height(mut self, min_height: u32) -> Self { +        self.min_height = min_height;          self      } -    /// Sets the border radius of the [`Button`]. +    /// Sets the padding of the [`Button`].      ///      /// [`Button`]: struct.Button.html -    pub fn border_radius(mut self, border_radius: u16) -> Self { -        self.border_radius = border_radius; +    pub fn padding(mut self, padding: u16) -> Self { +        self.padding = padding;          self      } @@ -109,6 +113,14 @@ impl<'a, Message, Renderer> Button<'a, Message, Renderer> {          self.on_press = Some(msg);          self      } + +    /// Sets the style of the [`Button`]. +    /// +    /// [`Button`]: struct.Button.html +    pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { +        self.style = style.into(); +        self +    }  }  /// The local state of a [`Button`]. @@ -139,7 +151,7 @@ where      }      fn height(&self) -> Length { -        Length::Shrink +        self.height      }      fn layout( @@ -150,14 +162,13 @@ where          let padding = f32::from(self.padding);          let limits = limits              .min_width(self.min_width) +            .min_height(self.min_height)              .width(self.width) -            .height(Length::Shrink) +            .height(self.height)              .pad(padding);          let mut content = self.content.layout(renderer, &limits); - -        content.bounds.x = padding; -        content.bounds.y = padding; +        content.move_to(Point::new(padding, padding));          let size = limits.resolve(content.size()).pad(padding); @@ -171,6 +182,7 @@ where          cursor_position: Point,          messages: &mut Vec<Message>,          _renderer: &Renderer, +        _clipboard: Option<&dyn Clipboard>,      ) {          match event {              Event::Mouse(mouse::Event::Input { @@ -205,22 +217,19 @@ where      fn draw(          &self,          renderer: &mut Renderer, +        defaults: &Renderer::Defaults,          layout: Layout<'_>,          cursor_position: Point,      ) -> Renderer::Output { -        let content = self.content.draw( -            renderer, -            layout.children().next().unwrap(), -            cursor_position, -        ); -          renderer.draw( +            defaults,              layout.bounds(),              cursor_position, +            self.on_press.is_none(),              self.state.is_pressed, -            self.background, -            self.border_radius, -            content, +            &self.style, +            &self.content, +            layout.children().next().unwrap(),          )      } @@ -238,17 +247,27 @@ where  /// [`Button`]: struct.Button.html  /// [renderer]: ../../renderer/index.html  pub trait Renderer: crate::Renderer + Sized { +    /// The default padding of a [`Button`]. +    /// +    /// [`Button`]: struct.Button.html +    const DEFAULT_PADDING: u16; + +    /// The style supported by this renderer. +    type Style: Default; +      /// Draws a [`Button`].      ///      /// [`Button`]: struct.Button.html -    fn draw( +    fn draw<Message>(          &mut self, +        defaults: &Self::Defaults,          bounds: Rectangle,          cursor_position: Point, +        is_disabled: bool,          is_pressed: bool, -        background: Option<Background>, -        border_radius: u16, -        content: Self::Output, +        style: &Self::Style, +        content: &Element<'_, Message, Self>, +        content_layout: Layout<'_>,      ) -> Self::Output;  } diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 9563291c..b36d10a4 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -3,7 +3,7 @@ use std::hash::Hash;  use crate::{      input::{mouse, ButtonState}, -    layout, row, text, Align, Color, Element, Event, Font, Hasher, +    layout, row, text, Align, Clipboard, Element, Event, Font, Hasher,      HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text,      VerticalAlignment, Widget,  }; @@ -13,7 +13,7 @@ use crate::{  /// # Example  ///  /// ``` -/// # use iced_native::Checkbox; +/// # type Checkbox<Message> = iced_native::Checkbox<Message, iced_native::renderer::Null>;  /// #  /// pub enum Message {  ///     CheckboxToggled(bool), @@ -26,14 +26,20 @@ use crate::{  ///  ///   #[allow(missing_debug_implementations)] -pub struct Checkbox<Message> { +pub struct Checkbox<Message, Renderer: self::Renderer + text::Renderer> {      is_checked: bool,      on_toggle: Box<dyn Fn(bool) -> Message>,      label: String, -    label_color: Option<Color>, +    width: Length, +    size: u16, +    spacing: u16, +    text_size: u16, +    style: Renderer::Style,  } -impl<Message> Checkbox<Message> { +impl<Message, Renderer: self::Renderer + text::Renderer> +    Checkbox<Message, Renderer> +{      /// Creates a new [`Checkbox`].      ///      /// It expects: @@ -52,25 +58,62 @@ impl<Message> Checkbox<Message> {              is_checked,              on_toggle: Box::new(f),              label: String::from(label), -            label_color: None, +            width: Length::Shrink, +            size: <Renderer as self::Renderer>::DEFAULT_SIZE, +            spacing: Renderer::DEFAULT_SPACING, +            text_size: <Renderer as text::Renderer>::DEFAULT_SIZE, +            style: Renderer::Style::default(),          }      } -    /// Sets the color of the label of the [`Checkbox`]. +    /// Sets the size of the [`Checkbox`]. +    /// +    /// [`Checkbox`]: struct.Checkbox.html +    pub fn size(mut self, size: u16) -> Self { +        self.size = size; +        self +    } + +    /// Sets the width of the [`Checkbox`].      ///      /// [`Checkbox`]: struct.Checkbox.html -    pub fn label_color<C: Into<Color>>(mut self, color: C) -> Self { -        self.label_color = Some(color.into()); +    pub fn width(mut self, width: Length) -> Self { +        self.width = width; +        self +    } + +    /// Sets the spacing between the [`Checkbox`] and the text. +    /// +    /// [`Checkbox`]: struct.Checkbox.html +    pub fn spacing(mut self, spacing: u16) -> Self { +        self.spacing = spacing; +        self +    } + +    /// Sets the text size of the [`Checkbox`]. +    /// +    /// [`Checkbox`]: struct.Checkbox.html +    pub fn text_size(mut self, text_size: u16) -> Self { +        self.text_size = text_size; +        self +    } + +    /// Sets the style of the [`Checkbox`]. +    /// +    /// [`Checkbox`]: struct.Checkbox.html +    pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { +        self.style = style.into();          self      }  } -impl<Message, Renderer> Widget<Message, Renderer> for Checkbox<Message> +impl<Message, Renderer> Widget<Message, Renderer> +    for Checkbox<Message, Renderer>  where      Renderer: self::Renderer + text::Renderer + row::Renderer,  {      fn width(&self) -> Length { -        Length::Fill +        self.width      }      fn height(&self) -> Length { @@ -82,17 +125,20 @@ where          renderer: &Renderer,          limits: &layout::Limits,      ) -> layout::Node { -        let size = self::Renderer::default_size(renderer); -          Row::<(), Renderer>::new() -            .spacing(15) +            .width(self.width) +            .spacing(self.spacing)              .align_items(Align::Center)              .push(                  Row::new() -                    .width(Length::Units(size as u16)) -                    .height(Length::Units(size as u16)), +                    .width(Length::Units(self.size)) +                    .height(Length::Units(self.size)), +            ) +            .push( +                Text::new(&self.label) +                    .width(self.width) +                    .size(self.text_size),              ) -            .push(Text::new(&self.label))              .layout(renderer, limits)      } @@ -103,6 +149,7 @@ where          cursor_position: Point,          messages: &mut Vec<Message>,          _renderer: &Renderer, +        _clipboard: Option<&dyn Clipboard>,      ) {          match event {              Event::Mouse(mouse::Event::Input { @@ -122,6 +169,7 @@ where      fn draw(          &self,          renderer: &mut Renderer, +        defaults: &Renderer::Defaults,          layout: Layout<'_>,          cursor_position: Point,      ) -> Renderer::Output { @@ -134,11 +182,12 @@ where          let label = text::Renderer::draw(              renderer, +            defaults,              label_layout.bounds(),              &self.label, -            text::Renderer::default_size(renderer), +            self.text_size,              Font::Default, -            self.label_color, +            None,              HorizontalAlignment::Left,              VerticalAlignment::Center,          ); @@ -151,6 +200,7 @@ where              self.is_checked,              is_mouse_over,              label, +            &self.style,          )      } @@ -167,10 +217,18 @@ where  /// [`Checkbox`]: struct.Checkbox.html  /// [renderer]: ../../renderer/index.html  pub trait Renderer: crate::Renderer { -    /// Returns the default size of a [`Checkbox`]. +    /// The style supported by this renderer. +    type Style: Default; + +    /// The default size of a [`Checkbox`].      ///      /// [`Checkbox`]: struct.Checkbox.html -    fn default_size(&self) -> u32; +    const DEFAULT_SIZE: u16; + +    /// The default spacing of a [`Checkbox`]. +    /// +    /// [`Checkbox`]: struct.Checkbox.html +    const DEFAULT_SPACING: u16;      /// Draws a [`Checkbox`].      /// @@ -187,16 +245,19 @@ pub trait Renderer: crate::Renderer {          is_checked: bool,          is_mouse_over: bool,          label: Self::Output, +        style: &Self::Style,      ) -> Self::Output;  } -impl<'a, Message, Renderer> From<Checkbox<Message>> +impl<'a, Message, Renderer> From<Checkbox<Message, Renderer>>      for Element<'a, Message, Renderer>  where -    Renderer: self::Renderer + text::Renderer + row::Renderer, +    Renderer: 'static + self::Renderer + text::Renderer + row::Renderer,      Message: 'static,  { -    fn from(checkbox: Checkbox<Message>) -> Element<'a, Message, Renderer> { +    fn from( +        checkbox: Checkbox<Message, Renderer>, +    ) -> Element<'a, Message, Renderer> {          Element::new(checkbox)      }  } diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index cdcf25af..104790d4 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -2,7 +2,8 @@  use std::hash::Hash;  use crate::{ -    layout, Align, Element, Event, Hasher, Layout, Length, Point, Widget, +    layout, Align, Clipboard, Element, Event, Hasher, Layout, Length, Point, +    Widget,  };  use std::u32; @@ -32,7 +33,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {          Column {              spacing: 0,              padding: 0, -            width: Length::Fill, +            width: Length::Shrink,              height: Length::Shrink,              max_width: u32::MAX,              max_height: u32::MAX, @@ -153,15 +154,17 @@ where          cursor_position: Point,          messages: &mut Vec<Message>,          renderer: &Renderer, +        clipboard: Option<&dyn Clipboard>,      ) {          self.children.iter_mut().zip(layout.children()).for_each(              |(child, layout)| {                  child.widget.on_event( -                    event, +                    event.clone(),                      layout,                      cursor_position,                      messages,                      renderer, +                    clipboard,                  )              },          ); @@ -170,10 +173,11 @@ where      fn draw(          &self,          renderer: &mut Renderer, +        defaults: &Renderer::Defaults,          layout: Layout<'_>,          cursor_position: Point,      ) -> Renderer::Output { -        renderer.draw(&self.children, layout, cursor_position) +        renderer.draw(defaults, &self.children, layout, cursor_position)      }      fn hash_layout(&self, state: &mut Hasher) { @@ -210,6 +214,7 @@ pub trait Renderer: crate::Renderer + Sized {      /// [`Layout`]: ../layout/struct.Layout.html      fn draw<Message>(          &mut self, +        defaults: &Self::Defaults,          content: &[Element<'_, Message, Self>],          layout: Layout<'_>,          cursor_position: Point, diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 7852eecf..3459a832 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -2,7 +2,8 @@  use std::hash::Hash;  use crate::{ -    layout, Align, Element, Event, Hasher, Layout, Length, Point, Widget, +    layout, Align, Clipboard, Element, Event, Hasher, Layout, Length, Point, +    Rectangle, Widget,  };  use std::u32; @@ -11,17 +12,21 @@ use std::u32;  ///  /// It is normally used for alignment purposes.  #[allow(missing_debug_implementations)] -pub struct Container<'a, Message, Renderer> { +pub struct Container<'a, Message, Renderer: self::Renderer> {      width: Length,      height: Length,      max_width: u32,      max_height: u32,      horizontal_alignment: Align,      vertical_alignment: Align, +    style: Renderer::Style,      content: Element<'a, Message, Renderer>,  } -impl<'a, Message, Renderer> Container<'a, Message, Renderer> { +impl<'a, Message, Renderer> Container<'a, Message, Renderer> +where +    Renderer: self::Renderer, +{      /// Creates an empty [`Container`].      ///      /// [`Container`]: struct.Container.html @@ -36,6 +41,7 @@ impl<'a, Message, Renderer> Container<'a, Message, Renderer> {              max_height: u32::MAX,              horizontal_alignment: Align::Start,              vertical_alignment: Align::Start, +            style: Renderer::Style::default(),              content: content.into(),          }      } @@ -71,13 +77,28 @@ impl<'a, Message, Renderer> Container<'a, Message, Renderer> {          self.max_height = max_height;          self      } +     +    /// Sets the content alignment for the horizontal axis of the [`Container`]. +    /// +    /// [`Container`]: struct.Container.html +    pub fn align_x(mut self, alignment: Align) -> Self { +        self.horizontal_alignment = alignment; +        self +    } + +    /// Sets the content alignment for the vertical axis of the [`Container`]. +    /// +    /// [`Container`]: struct.Container.html +    pub fn align_y(mut self, alignment: Align) -> Self { +        self.vertical_alignment = alignment; +        self +    }      /// Centers the contents in the horizontal axis of the [`Container`].      ///      /// [`Container`]: struct.Container.html      pub fn center_x(mut self) -> Self {          self.horizontal_alignment = Align::Center; -          self      } @@ -86,7 +107,14 @@ impl<'a, Message, Renderer> Container<'a, Message, Renderer> {      /// [`Container`]: struct.Container.html      pub fn center_y(mut self) -> Self {          self.vertical_alignment = Align::Center; +        self +    } +    /// Sets the style of the [`Container`]. +    /// +    /// [`Container`]: struct.Container.html +    pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { +        self.style = style.into();          self      }  } @@ -94,7 +122,7 @@ impl<'a, Message, Renderer> Container<'a, Message, Renderer> {  impl<'a, Message, Renderer> Widget<Message, Renderer>      for Container<'a, Message, Renderer>  where -    Renderer: crate::Renderer, +    Renderer: self::Renderer,  {      fn width(&self) -> Length {          self.width @@ -131,6 +159,7 @@ where          cursor_position: Point,          messages: &mut Vec<Message>,          renderer: &Renderer, +        clipboard: Option<&dyn Clipboard>,      ) {          self.content.widget.on_event(              event, @@ -138,19 +167,24 @@ where              cursor_position,              messages,              renderer, +            clipboard,          )      }      fn draw(          &self,          renderer: &mut Renderer, +        defaults: &Renderer::Defaults,          layout: Layout<'_>,          cursor_position: Point,      ) -> Renderer::Output { -        self.content.draw( -            renderer, -            layout.children().next().unwrap(), +        renderer.draw( +            defaults, +            layout.bounds(),              cursor_position, +            &self.style, +            &self.content, +            layout.children().next().unwrap(),          )      } @@ -165,10 +199,35 @@ where      }  } +/// The renderer of a [`Container`]. +/// +/// Your [renderer] will need to implement this trait before being +/// able to use a [`Container`] in your user interface. +/// +/// [`Container`]: struct.Container.html +/// [renderer]: ../../renderer/index.html +pub trait Renderer: crate::Renderer { +    /// The style supported by this renderer. +    type Style: Default; + +    /// Draws a [`Container`]. +    /// +    /// [`Container`]: struct.Container.html +    fn draw<Message>( +        &mut self, +        defaults: &Self::Defaults, +        bounds: Rectangle, +        cursor_position: Point, +        style: &Self::Style, +        content: &Element<'_, Message, Self>, +        content_layout: Layout<'_>, +    ) -> Self::Output; +} +  impl<'a, Message, Renderer> From<Container<'a, Message, Renderer>>      for Element<'a, Message, Renderer>  where -    Renderer: 'a + crate::Renderer, +    Renderer: 'a + self::Renderer,      Message: 'static,  {      fn from( diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 4c588c9d..fbe38bfc 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -1,8 +1,11 @@  //! Display images in your user interface. -  use crate::{layout, Element, Hasher, Layout, Length, Point, Size, Widget}; -use std::hash::Hash; +use std::{ +    hash::{Hash, Hasher as _}, +    path::PathBuf, +    sync::Arc, +};  /// A frame that displays an image while keeping aspect ratio.  /// @@ -15,9 +18,9 @@ use std::hash::Hash;  /// ```  ///  /// <img src="https://github.com/hecrj/iced/blob/9712b319bb7a32848001b96bd84977430f14b623/examples/resources/ferris.png?raw=true" width="300"> -#[derive(Debug)] +#[derive(Debug, Hash)]  pub struct Image { -    path: String, +    handle: Handle,      width: Length,      height: Length,  } @@ -26,9 +29,9 @@ impl Image {      /// Creates a new [`Image`] with the given path.      ///      /// [`Image`]: struct.Image.html -    pub fn new<T: Into<String>>(path: T) -> Self { +    pub fn new<T: Into<Handle>>(handle: T) -> Self {          Image { -            path: path.into(), +            handle: handle.into(),              width: Length::Shrink,              height: Length::Shrink,          } @@ -68,24 +71,22 @@ where          renderer: &Renderer,          limits: &layout::Limits,      ) -> layout::Node { -        let (width, height) = renderer.dimensions(&self.path); +        let (width, height) = renderer.dimensions(&self.handle);          let aspect_ratio = width as f32 / height as f32; -        // TODO: Deal with additional cases -        let (width, height) = match (self.width, self.height) { -            (Length::Units(width), _) => ( -                self.width, -                Length::Units((width as f32 / aspect_ratio).round() as u16), -            ), -            (_, _) => { -                (Length::Units(width as u16), Length::Units(height as u16)) -            } -        }; +        let mut size = limits +            .width(self.width) +            .height(self.height) +            .resolve(Size::new(width as f32, height as f32)); -        let mut size = limits.width(width).height(height).resolve(Size::ZERO); +        let viewport_aspect_ratio = size.width / size.height; -        size.height = size.width / aspect_ratio; +        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)      } @@ -93,18 +94,139 @@ where      fn draw(          &self,          renderer: &mut Renderer, +        _defaults: &Renderer::Defaults,          layout: Layout<'_>,          _cursor_position: Point,      ) -> Renderer::Output { -        renderer.draw(&self.path, layout) +        renderer.draw(self.handle.clone(), layout)      }      fn hash_layout(&self, state: &mut Hasher) { +        self.handle.hash(state);          self.width.hash(state);          self.height.hash(state);      }  } +/// An [`Image`] handle. +/// +/// [`Image`]: struct.Image.html +#[derive(Debug, Clone)] +pub struct Handle { +    id: u64, +    data: Arc<Data>, +} + +impl Handle { +    /// Creates an image [`Handle`] pointing to the image of the given path. +    /// +    /// [`Handle`]: struct.Handle.html +    pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle { +        Self::from_data(Data::Path(path.into())) +    } + +    /// Creates an image [`Handle`] containing the image pixels directly. This +    /// function expects the input data to be provided as a `Vec<u8>` of BGRA  +    /// pixels. +    /// +    /// This is useful if you have already decoded your image. +    /// +    /// [`Handle`]: struct.Handle.html +    pub fn from_pixels(width: u32, height: u32, pixels: Vec<u8>) -> Handle { +        Self::from_data(Data::Pixels { +            width, +            height, +            pixels, +        }) +    } + +    /// Creates an image [`Handle`] containing the image data directly. +    /// +    /// This is useful if you already have your image loaded in-memory, maybe +    /// because you downloaded or generated it procedurally. +    /// +    /// [`Handle`]: struct.Handle.html +    pub fn from_memory(bytes: Vec<u8>) -> Handle { +        Self::from_data(Data::Bytes(bytes)) +    } + +    fn from_data(data: Data) -> Handle { +        let mut hasher = Hasher::default(); +        data.hash(&mut hasher); + +        Handle { +            id: hasher.finish(), +            data: Arc::new(data), +        } +    } + +    /// Returns the unique identifier of the [`Handle`]. +    /// +    /// [`Handle`]: struct.Handle.html +    pub fn id(&self) -> u64 { +        self.id +    } + +    /// Returns a reference to the image [`Data`]. +    /// +    /// [`Data`]: enum.Data.html +    pub fn data(&self) -> &Data { +        &self.data +    } +} + +impl From<String> for Handle { +    fn from(path: String) -> Handle { +        Handle::from_path(path) +    } +} + +impl From<&str> for Handle { +    fn from(path: &str) -> Handle { +        Handle::from_path(path) +    } +} + +impl Hash for Handle { +    fn hash<H: std::hash::Hasher>(&self, state: &mut H) { +        self.id.hash(state); +    } +} + +/// The data of an [`Image`]. +/// +/// [`Image`]: struct.Image.html +#[derive(Clone, Hash)] +pub enum Data { +    /// File data +    Path(PathBuf), + +    /// In-memory data +    Bytes(Vec<u8>), + +    /// Decoded image pixels in BGRA format. +    Pixels { +        /// The width of the image. +        width: u32, +        /// The height of the image. +        height: u32, +        /// The pixels. +        pixels: Vec<u8>, +    }, +} + +impl std::fmt::Debug for Data { +    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +        match self { +            Data::Path(path) => write!(f, "Path({:?})", path), +            Data::Bytes(_) => write!(f, "Bytes(...)"), +            Data::Pixels { width, height, .. } => { +                write!(f, "Pixels({} * {})", width, height) +            } +        } +    } +} +  /// The renderer of an [`Image`].  ///  /// Your [renderer] will need to implement this trait before being able to use @@ -116,12 +238,12 @@ pub trait Renderer: crate::Renderer {      /// Returns the dimensions of an [`Image`] located on the given path.      ///      /// [`Image`]: struct.Image.html -    fn dimensions(&self, path: &str) -> (u32, u32); +    fn dimensions(&self, handle: &Handle) -> (u32, u32);      /// Draws an [`Image`].      ///      /// [`Image`]: struct.Image.html -    fn draw(&mut self, path: &str, layout: Layout<'_>) -> Self::Output; +    fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output;  }  impl<'a, Message, Renderer> From<Image> for Element<'a, Message, Renderer> diff --git a/native/src/widget/progress_bar.rs b/native/src/widget/progress_bar.rs new file mode 100644 index 00000000..67d1ab83 --- /dev/null +++ b/native/src/widget/progress_bar.rs @@ -0,0 +1,168 @@ +//! Provide progress feedback to your users. +use crate::{ +    layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, +}; + +use std::{hash::Hash, ops::RangeInclusive}; + +/// A bar that displays progress. +/// +/// # Example +/// ``` +/// # use iced_native::renderer::Null; +/// # +/// # pub type ProgressBar = iced_native::ProgressBar<Null>; +/// let value = 50.0; +/// +/// ProgressBar::new(0.0..=100.0, value); +/// ``` +/// +///  +#[allow(missing_debug_implementations)] +pub struct ProgressBar<Renderer: self::Renderer> { +    range: RangeInclusive<f32>, +    value: f32, +    width: Length, +    height: Option<Length>, +    style: Renderer::Style, +} + +impl<Renderer: self::Renderer> ProgressBar<Renderer> { +    /// Creates a new [`ProgressBar`]. +    /// +    /// It expects: +    ///   * an inclusive range of possible values +    ///   * the current value of the [`ProgressBar`] +    /// +    /// [`ProgressBar`]: struct.ProgressBar.html +    pub fn new(range: RangeInclusive<f32>, value: f32) -> Self { +        ProgressBar { +            value: value.max(*range.start()).min(*range.end()), +            range, +            width: Length::Fill, +            height: None, +            style: Renderer::Style::default(), +        } +    } + +    /// Sets the width of the [`ProgressBar`]. +    /// +    /// [`ProgressBar`]: struct.ProgressBar.html +    pub fn width(mut self, width: Length) -> Self { +        self.width = width; +        self +    } + +    /// Sets the height of the [`ProgressBar`]. +    /// +    /// [`ProgressBar`]: struct.ProgressBar.html +    pub fn height(mut self, height: Length) -> Self { +        self.height = Some(height); +        self +    } + +    /// Sets the style of the [`ProgressBar`]. +    /// +    /// [`ProgressBar`]: struct.ProgressBar.html +    pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { +        self.style = style.into(); +        self +    } +} + +impl<Message, Renderer> Widget<Message, Renderer> for ProgressBar<Renderer> +where +    Renderer: self::Renderer, +{ +    fn width(&self) -> Length { +        self.width +    } + +    fn height(&self) -> Length { +        self.height +            .unwrap_or(Length::Units(Renderer::DEFAULT_HEIGHT)) +    } + +    fn layout( +        &self, +        _renderer: &Renderer, +        limits: &layout::Limits, +    ) -> layout::Node { +        let limits = limits.width(self.width).height( +            self.height +                .unwrap_or(Length::Units(Renderer::DEFAULT_HEIGHT)), +        ); + +        let size = limits.resolve(Size::ZERO); + +        layout::Node::new(size) +    } + +    fn draw( +        &self, +        renderer: &mut Renderer, +        _defaults: &Renderer::Defaults, +        layout: Layout<'_>, +        _cursor_position: Point, +    ) -> Renderer::Output { +        renderer.draw( +            layout.bounds(), +            self.range.clone(), +            self.value, +            &self.style, +        ) +    } + +    fn hash_layout(&self, state: &mut Hasher) { +        self.width.hash(state); +        self.height.hash(state); +    } +} + +/// The renderer of a [`ProgressBar`]. +/// +/// Your [renderer] will need to implement this trait before being +/// able to use a [`ProgressBar`] in your user interface. +/// +/// [`ProgressBar`]: struct.ProgressBar.html +/// [renderer]: ../../renderer/index.html +pub trait Renderer: crate::Renderer { +    /// The style supported by this renderer. +    type Style: Default; + +    /// The default height of a [`ProgressBar`]. +    /// +    /// [`ProgressBar`]: struct.ProgressBar.html +    const DEFAULT_HEIGHT: u16; + +    /// Draws a [`ProgressBar`]. +    /// +    /// It receives: +    ///   * the bounds of the [`ProgressBar`] +    ///   * the range of values of the [`ProgressBar`] +    ///   * the current value of the [`ProgressBar`] +    ///   * maybe a specific background of the [`ProgressBar`] +    ///   * maybe a specific active color of the [`ProgressBar`] +    /// +    /// [`ProgressBar`]: struct.ProgressBar.html +    fn draw( +        &self, +        bounds: Rectangle, +        range: RangeInclusive<f32>, +        value: f32, +        style: &Self::Style, +    ) -> Self::Output; +} + +impl<'a, Message, Renderer> From<ProgressBar<Renderer>> +    for Element<'a, Message, Renderer> +where +    Renderer: 'static + self::Renderer, +    Message: 'static, +{ +    fn from( +        progress_bar: ProgressBar<Renderer>, +    ) -> Element<'a, Message, Renderer> { +        Element::new(progress_bar) +    } +} diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index a9d145db..cdc4862c 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -1,7 +1,7 @@  //! Create choices using radio buttons.  use crate::{      input::{mouse, ButtonState}, -    layout, row, text, Align, Color, Element, Event, Font, Hasher, +    layout, row, text, Align, Clipboard, Element, Event, Font, Hasher,      HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text,      VerticalAlignment, Widget,  }; @@ -12,7 +12,8 @@ use std::hash::Hash;  ///  /// # Example  /// ``` -/// # use iced_native::Radio; +/// # type Radio<Message> = +/// #     iced_native::Radio<Message, iced_native::renderer::Null>;  /// #  /// #[derive(Debug, Clone, Copy, PartialEq, Eq)]  /// pub enum Choice { @@ -34,14 +35,14 @@ use std::hash::Hash;  ///  ///   #[allow(missing_debug_implementations)] -pub struct Radio<Message> { +pub struct Radio<Message, Renderer: self::Renderer> {      is_selected: bool,      on_click: Message,      label: String, -    label_color: Option<Color>, +    style: Renderer::Style,  } -impl<Message> Radio<Message> { +impl<Message, Renderer: self::Renderer> Radio<Message, Renderer> {      /// Creates a new [`Radio`] button.      ///      /// It expects: @@ -61,20 +62,20 @@ impl<Message> Radio<Message> {              is_selected: Some(value) == selected,              on_click: f(value),              label: String::from(label), -            label_color: None, +            style: Renderer::Style::default(),          }      } -    /// Sets the `Color` of the label of the [`Radio`]. +    /// Sets the style of the [`Radio`] button.      ///      /// [`Radio`]: struct.Radio.html -    pub fn label_color<C: Into<Color>>(mut self, color: C) -> Self { -        self.label_color = Some(color.into()); +    pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { +        self.style = style.into();          self      }  } -impl<Message, Renderer> Widget<Message, Renderer> for Radio<Message> +impl<Message, Renderer> Widget<Message, Renderer> for Radio<Message, Renderer>  where      Renderer: self::Renderer + text::Renderer + row::Renderer,      Message: Clone, @@ -95,6 +96,7 @@ where          let size = self::Renderer::default_size(renderer);          Row::<(), Renderer>::new() +            .width(Length::Fill)              .spacing(15)              .align_items(Align::Center)              .push( @@ -113,6 +115,7 @@ where          cursor_position: Point,          messages: &mut Vec<Message>,          _renderer: &Renderer, +        _clipboard: Option<&dyn Clipboard>,      ) {          match event {              Event::Mouse(mouse::Event::Input { @@ -130,6 +133,7 @@ where      fn draw(          &self,          renderer: &mut Renderer, +        defaults: &Renderer::Defaults,          layout: Layout<'_>,          cursor_position: Point,      ) -> Renderer::Output { @@ -142,11 +146,12 @@ where          let label = text::Renderer::draw(              renderer, +            defaults,              label_layout.bounds(),              &self.label, -            text::Renderer::default_size(renderer), +            <Renderer as text::Renderer>::DEFAULT_SIZE,              Font::Default, -            self.label_color, +            None,              HorizontalAlignment::Left,              VerticalAlignment::Center,          ); @@ -159,6 +164,7 @@ where              self.is_selected,              is_mouse_over,              label, +            &self.style,          )      } @@ -175,6 +181,9 @@ where  /// [`Radio`]: struct.Radio.html  /// [renderer]: ../../renderer/index.html  pub trait Renderer: crate::Renderer { +    /// The style supported by this renderer. +    type Style: Default; +      /// Returns the default size of a [`Radio`] button.      ///      /// [`Radio`]: struct.Radio.html @@ -195,16 +204,17 @@ pub trait Renderer: crate::Renderer {          is_selected: bool,          is_mouse_over: bool,          label: Self::Output, +        style: &Self::Style,      ) -> Self::Output;  } -impl<'a, Message, Renderer> From<Radio<Message>> +impl<'a, Message, Renderer> From<Radio<Message, Renderer>>      for Element<'a, Message, Renderer>  where -    Renderer: self::Renderer + row::Renderer + text::Renderer, +    Renderer: 'static + self::Renderer + row::Renderer + text::Renderer,      Message: 'static + Clone,  { -    fn from(radio: Radio<Message>) -> Element<'a, Message, Renderer> { +    fn from(radio: Radio<Message, Renderer>) -> Element<'a, Message, Renderer> {          Element::new(radio)      }  } diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index c854aff7..775b953e 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -2,7 +2,8 @@  use std::hash::Hash;  use crate::{ -    layout, Align, Element, Event, Hasher, Layout, Length, Point, Widget, +    layout, Align, Clipboard, Element, Event, Hasher, Layout, Length, Point, +    Widget,  };  use std::u32; @@ -32,7 +33,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {          Row {              spacing: 0,              padding: 0, -            width: Length::Fill, +            width: Length::Shrink,              height: Length::Shrink,              max_width: u32::MAX,              max_height: u32::MAX, @@ -154,15 +155,17 @@ where          cursor_position: Point,          messages: &mut Vec<Message>,          renderer: &Renderer, +        clipboard: Option<&dyn Clipboard>,      ) {          self.children.iter_mut().zip(layout.children()).for_each(              |(child, layout)| {                  child.widget.on_event( -                    event, +                    event.clone(),                      layout,                      cursor_position,                      messages,                      renderer, +                    clipboard,                  )              },          ); @@ -171,10 +174,11 @@ where      fn draw(          &self,          renderer: &mut Renderer, +        defaults: &Renderer::Defaults,          layout: Layout<'_>,          cursor_position: Point,      ) -> Renderer::Output { -        renderer.draw(&self.children, layout, cursor_position) +        renderer.draw(defaults, &self.children, layout, cursor_position)      }      fn hash_layout(&self, state: &mut Hasher) { @@ -212,6 +216,7 @@ pub trait Renderer: crate::Renderer + Sized {      /// [`Layout`]: ../layout/struct.Layout.html      fn draw<Message>(          &mut self, +        defaults: &Self::Defaults,          children: &[Element<'_, Message, Self>],          layout: Layout<'_>,          cursor_position: Point, diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 678d837a..e83f25af 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -2,8 +2,8 @@  use crate::{      column,      input::{mouse, ButtonState}, -    layout, Align, Column, Element, Event, Hasher, Layout, Length, Point, -    Rectangle, Size, Widget, +    layout, Align, Clipboard, Column, Element, Event, Hasher, Layout, Length, +    Point, Rectangle, Size, Widget,  };  use std::{f32, hash::Hash, u32}; @@ -11,14 +11,15 @@ use std::{f32, hash::Hash, u32};  /// A widget that can vertically display an infinite amount of content with a  /// scrollbar.  #[allow(missing_debug_implementations)] -pub struct Scrollable<'a, Message, Renderer> { +pub struct Scrollable<'a, Message, Renderer: self::Renderer> {      state: &'a mut State,      height: Length,      max_height: u32,      content: Column<'a, Message, Renderer>, +    style: Renderer::Style,  } -impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> { +impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {      /// Creates a new [`Scrollable`] with the given [`State`].      ///      /// [`Scrollable`]: struct.Scrollable.html @@ -29,6 +30,7 @@ impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> {              height: Length::Shrink,              max_height: u32::MAX,              content: Column::new(), +            style: Renderer::Style::default(),          }      } @@ -90,6 +92,14 @@ impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> {          self      } +    /// Sets the style of the [`Scrollable`] . +    /// +    /// [`Scrollable`]: struct.Scrollable.html +    pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { +        self.style = style.into(); +        self +    } +      /// Adds an element to the [`Scrollable`].      ///      /// [`Scrollable`]: struct.Scrollable.html @@ -105,7 +115,7 @@ impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> {  impl<'a, Message, Renderer> Widget<Message, Renderer>      for Scrollable<'a, Message, Renderer>  where -    Renderer: self::Renderer + column::Renderer, +    Renderer: 'static + self::Renderer + column::Renderer,  {      fn width(&self) -> Length {          Length::Fill @@ -143,6 +153,7 @@ where          cursor_position: Point,          messages: &mut Vec<Message>,          renderer: &Renderer, +        clipboard: Option<&dyn Clipboard>,      ) {          let bounds = layout.bounds();          let is_mouse_over = bounds.contains(cursor_position); @@ -150,12 +161,6 @@ where          let content = layout.children().next().unwrap();          let content_bounds = content.bounds(); -        let is_mouse_over_scrollbar = renderer.is_mouse_over_scrollbar( -            bounds, -            content_bounds, -            cursor_position, -        ); -          // TODO: Event capture. Nested scrollables should capture scroll events.          if is_mouse_over {              match event { @@ -174,49 +179,66 @@ where              }          } -        if self.state.is_scrollbar_grabbed() || is_mouse_over_scrollbar { +        let offset = self.state.offset(bounds, content_bounds); +        let scrollbar = renderer.scrollbar(bounds, content_bounds, offset); +        let is_mouse_over_scrollbar = scrollbar +            .as_ref() +            .map(|scrollbar| scrollbar.is_mouse_over(cursor_position)) +            .unwrap_or(false); + +        if self.state.is_scroller_grabbed() {              match event {                  Event::Mouse(mouse::Event::Input {                      button: mouse::Button::Left, -                    state, -                }) => match state { -                    ButtonState::Pressed => { -                        self.state.scroll_to( -                            cursor_position.y / (bounds.y + bounds.height), -                            bounds, -                            content_bounds, -                        ); - -                        self.state.scrollbar_grabbed_at = Some(cursor_position); -                    } -                    ButtonState::Released => { -                        self.state.scrollbar_grabbed_at = None; -                    } -                }, +                    state: ButtonState::Released, +                }) => { +                    self.state.scroller_grabbed_at = None; +                }                  Event::Mouse(mouse::Event::CursorMoved { .. }) => { -                    if let Some(scrollbar_grabbed_at) = -                        self.state.scrollbar_grabbed_at +                    if let (Some(scrollbar), Some(scroller_grabbed_at)) = +                        (scrollbar, self.state.scroller_grabbed_at)                      { -                        let ratio = content_bounds.height / bounds.height; -                        let delta = scrollbar_grabbed_at.y - cursor_position.y; - -                        self.state.scroll( -                            delta * ratio, +                        self.state.scroll_to( +                            scrollbar.scroll_percentage( +                                scroller_grabbed_at, +                                cursor_position, +                            ),                              bounds,                              content_bounds,                          ); - -                        self.state.scrollbar_grabbed_at = Some(cursor_position); +                    } +                } +                _ => {} +            } +        } else if is_mouse_over_scrollbar { +            match event { +                Event::Mouse(mouse::Event::Input { +                    button: mouse::Button::Left, +                    state: ButtonState::Pressed, +                }) => { +                    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); +                        }                      }                  }                  _ => {}              }          } -        let cursor_position = if is_mouse_over -            && !(is_mouse_over_scrollbar -                || self.state.scrollbar_grabbed_at.is_some()) -        { +        let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {              Point::new(                  cursor_position.x,                  cursor_position.y @@ -236,12 +258,14 @@ where              cursor_position,              messages,              renderer, +            clipboard,          )      }      fn draw(          &self,          renderer: &mut Renderer, +        defaults: &Renderer::Defaults,          layout: Layout<'_>,          cursor_position: Point,      ) -> Renderer::Output { @@ -249,13 +273,13 @@ where          let content_layout = layout.children().next().unwrap();          let content_bounds = content_layout.bounds();          let offset = self.state.offset(bounds, content_bounds); +        let scrollbar = renderer.scrollbar(bounds, content_bounds, offset);          let is_mouse_over = bounds.contains(cursor_position); -        let is_mouse_over_scrollbar = renderer.is_mouse_over_scrollbar( -            bounds, -            content_bounds, -            cursor_position, -        ); +        let is_mouse_over_scrollbar = scrollbar +            .as_ref() +            .map(|scrollbar| scrollbar.is_mouse_over(cursor_position)) +            .unwrap_or(false);          let content = {              let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar { @@ -264,7 +288,12 @@ where                  Point::new(cursor_position.x, -1.0)              }; -            self.content.draw(renderer, content_layout, cursor_position) +            self.content.draw( +                renderer, +                defaults, +                content_layout, +                cursor_position, +            )          };          self::Renderer::draw( @@ -274,13 +303,15 @@ where              content_layout.bounds(),              is_mouse_over,              is_mouse_over_scrollbar, +            scrollbar,              offset, +            &self.style,              content,          )      }      fn hash_layout(&self, state: &mut Hasher) { -        std::any::TypeId::of::<Scrollable<'static, (), ()>>().hash(state); +        std::any::TypeId::of::<Scrollable<'static, (), Renderer>>().hash(state);          self.height.hash(state);          self.max_height.hash(state); @@ -294,7 +325,7 @@ where  /// [`Scrollable`]: struct.Scrollable.html  #[derive(Debug, Clone, Copy, Default)]  pub struct State { -    scrollbar_grabbed_at: Option<Point>, +    scroller_grabbed_at: Option<f32>,      offset: f32,  } @@ -356,12 +387,69 @@ impl State {          self.offset.min(hidden_content as f32) as u32      } -    /// Returns whether the scrollbar is currently grabbed or not. -    pub fn is_scrollbar_grabbed(&self) -> bool { -        self.scrollbar_grabbed_at.is_some() +    /// Returns whether the scroller is currently grabbed or not. +    pub fn is_scroller_grabbed(&self) -> bool { +        self.scroller_grabbed_at.is_some() +    } +} + +/// The scrollbar of a [`Scrollable`]. +/// +/// [`Scrollable`]: struct.Scrollable.html +#[derive(Debug)] +pub struct Scrollbar { +    /// The bounds of the [`Scrollbar`]. +    /// +    /// [`Scrollbar`]: struct.Scrollbar.html +    pub bounds: Rectangle, + +    /// The bounds of the [`Scroller`]. +    /// +    /// [`Scroller`]: struct.Scroller.html +    pub scroller: Scroller, +} + +impl Scrollbar { +    fn is_mouse_over(&self, cursor_position: Point) -> bool { +        self.bounds.contains(cursor_position) +    } + +    fn grab_scroller(&self, cursor_position: Point) -> Option<f32> { +        if self.bounds.contains(cursor_position) { +            Some(if self.scroller.bounds.contains(cursor_position) { +                (cursor_position.y - self.scroller.bounds.y) +                    / self.scroller.bounds.height +            } else { +                0.5 +            }) +        } else { +            None +        } +    } + +    fn scroll_percentage( +        &self, +        grabbed_at: f32, +        cursor_position: Point, +    ) -> f32 { +        (cursor_position.y +            - self.bounds.y +            - self.scroller.bounds.height * grabbed_at) +            / (self.bounds.height - self.scroller.bounds.height)      }  } +/// The handle of a [`Scrollbar`]. +/// +/// [`Scrollbar`]: struct.Scrollbar.html +#[derive(Debug, Clone, Copy)] +pub struct Scroller { +    /// The bounds of the [`Scroller`]. +    /// +    /// [`Scroller`]: struct.Scrollbar.html +    pub bounds: Rectangle, +} +  /// The renderer of a [`Scrollable`].  ///  /// Your [renderer] will need to implement this trait before being @@ -370,27 +458,34 @@ impl State {  /// [`Scrollable`]: struct.Scrollable.html  /// [renderer]: ../../renderer/index.html  pub trait Renderer: crate::Renderer + Sized { -    /// Returns whether the mouse is over the scrollbar given the bounds of -    /// the [`Scrollable`] and its contents. +    /// The style supported by this renderer. +    type Style: Default; + +    /// Returns the [`Scrollbar`] given the bounds and content bounds of a +    /// [`Scrollable`].      /// +    /// [`Scrollbar`]: struct.Scrollbar.html      /// [`Scrollable`]: struct.Scrollable.html -    fn is_mouse_over_scrollbar( +    fn scrollbar(          &self,          bounds: Rectangle,          content_bounds: Rectangle, -        cursor_position: Point, -    ) -> bool; +        offset: u32, +    ) -> Option<Scrollbar>;      /// Draws the [`Scrollable`].      ///      /// It receives:      /// - the [`State`] of the [`Scrollable`] -    /// - the bounds of the [`Scrollable`] +    /// - the bounds of the [`Scrollable`] widget +    /// - the bounds of the [`Scrollable`] content      /// - whether the mouse is over the [`Scrollable`] or not -    /// - whether the mouse is over the scrollbar or not +    /// - whether the mouse is over the [`Scrollbar`] or not +    /// - a optional [`Scrollbar`] to be rendered      /// - the scrolling offset      /// - the drawn content      /// +    /// [`Scrollbar`]: struct.Scrollbar.html      /// [`Scrollable`]: struct.Scrollable.html      /// [`State`]: struct.State.html      fn draw( @@ -400,7 +495,9 @@ pub trait Renderer: crate::Renderer + Sized {          content_bounds: Rectangle,          is_mouse_over: bool,          is_mouse_over_scrollbar: bool, +        scrollbar: Option<Scrollbar>,          offset: u32, +        style: &Self::Style,          content: Self::Output,      ) -> Self::Output;  } @@ -408,7 +505,7 @@ pub trait Renderer: crate::Renderer + Sized {  impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>>      for Element<'a, Message, Renderer>  where -    Renderer: 'a + self::Renderer + column::Renderer, +    Renderer: 'static + self::Renderer + column::Renderer,      Message: 'static,  {      fn from( diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index f07ea7cd..008203fe 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -6,8 +6,8 @@  //! [`State`]: struct.State.html  use crate::{      input::{mouse, ButtonState}, -    layout, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size, -    Widget, +    layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, +    Rectangle, Size, Widget,  };  use std::{hash::Hash, ops::RangeInclusive}; @@ -21,8 +21,9 @@ use std::{hash::Hash, ops::RangeInclusive};  ///  /// # Example  /// ``` -/// # use iced_native::{slider, Slider}; +/// # use iced_native::{slider, renderer::Null};  /// # +/// # pub type Slider<'a, Message> = iced_native::Slider<'a, Message, Null>;  /// pub enum Message {  ///     SliderChanged(f32),  /// } @@ -35,15 +36,16 @@ use std::{hash::Hash, ops::RangeInclusive};  ///  ///   #[allow(missing_debug_implementations)] -pub struct Slider<'a, Message> { +pub struct Slider<'a, Message, Renderer: self::Renderer> {      state: &'a mut State,      range: RangeInclusive<f32>,      value: f32,      on_change: Box<dyn Fn(f32) -> Message>,      width: Length, +    style: Renderer::Style,  } -impl<'a, Message> Slider<'a, Message> { +impl<'a, Message, Renderer: self::Renderer> Slider<'a, Message, Renderer> {      /// Creates a new [`Slider`].      ///      /// It expects: @@ -71,6 +73,7 @@ impl<'a, Message> Slider<'a, Message> {              range,              on_change: Box::new(on_change),              width: Length::Fill, +            style: Renderer::Style::default(),          }      } @@ -81,6 +84,14 @@ impl<'a, Message> Slider<'a, Message> {          self.width = width;          self      } + +    /// Sets the style of the [`Slider`]. +    /// +    /// [`Slider`]: struct.Slider.html +    pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { +        self.style = style.into(); +        self +    }  }  /// The local state of a [`Slider`]. @@ -100,7 +111,8 @@ impl State {      }  } -impl<'a, Message, Renderer> Widget<Message, Renderer> for Slider<'a, Message> +impl<'a, Message, Renderer> Widget<Message, Renderer> +    for Slider<'a, Message, Renderer>  where      Renderer: self::Renderer,  { @@ -133,6 +145,7 @@ where          cursor_position: Point,          messages: &mut Vec<Message>,          _renderer: &Renderer, +        _clipboard: Option<&dyn Clipboard>,      ) {          let mut change = || {              let bounds = layout.bounds(); @@ -177,6 +190,7 @@ where      fn draw(          &self,          renderer: &mut Renderer, +        _defaults: &Renderer::Defaults,          layout: Layout<'_>,          cursor_position: Point,      ) -> Renderer::Output { @@ -186,6 +200,7 @@ where              self.range.clone(),              self.value,              self.state.is_dragging, +            &self.style,          )      } @@ -202,6 +217,9 @@ where  /// [`Slider`]: struct.Slider.html  /// [renderer]: ../../renderer/index.html  pub trait Renderer: crate::Renderer { +    /// The style supported by this renderer. +    type Style: Default; +      /// Returns the height of the [`Slider`].      ///      /// [`Slider`]: struct.Slider.html @@ -226,16 +244,19 @@ pub trait Renderer: crate::Renderer {          range: RangeInclusive<f32>,          value: f32,          is_dragging: bool, +        style: &Self::Style,      ) -> Self::Output;  } -impl<'a, Message, Renderer> From<Slider<'a, Message>> +impl<'a, Message, Renderer> From<Slider<'a, Message, Renderer>>      for Element<'a, Message, Renderer>  where -    Renderer: self::Renderer, +    Renderer: 'static + self::Renderer,      Message: 'static,  { -    fn from(slider: Slider<'a, Message>) -> Element<'a, Message, Renderer> { +    fn from( +        slider: Slider<'a, Message, Renderer>, +    ) -> Element<'a, Message, Renderer> {          Element::new(slider)      }  } diff --git a/native/src/widget/space.rs b/native/src/widget/space.rs new file mode 100644 index 00000000..24c94bf6 --- /dev/null +++ b/native/src/widget/space.rs @@ -0,0 +1,105 @@ +//! Distribute content vertically. +use std::hash::Hash; + +use crate::{ +    layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, +}; + +/// An amount of empty space. +/// +/// It can be useful if you want to fill some space with nothing. +#[derive(Debug)] +pub struct Space { +    width: Length, +    height: Length, +} + +impl Space { +    /// Creates an amount of empty [`Space`] with the given width and height. +    /// +    /// [`Space`]: struct.Space.html +    pub fn new(width: Length, height: Length) -> Self { +        Space { width, height } +    } + +    /// Creates an amount of horizontal [`Space`]. +    /// +    /// [`Space`]: struct.Space.html +    pub fn with_width(width: Length) -> Self { +        Space { +            width, +            height: Length::Shrink, +        } +    } + +    /// Creates an amount of vertical [`Space`]. +    /// +    /// [`Space`]: struct.Space.html +    pub fn with_height(height: Length) -> Self { +        Space { +            width: Length::Shrink, +            height, +        } +    } +} + +impl<'a, Message, Renderer> Widget<Message, Renderer> for Space +where +    Renderer: self::Renderer, +{ +    fn width(&self) -> Length { +        self.width +    } + +    fn height(&self) -> Length { +        self.height +    } + +    fn layout( +        &self, +        _renderer: &Renderer, +        limits: &layout::Limits, +    ) -> layout::Node { +        let limits = limits.width(self.width).height(self.height); + +        layout::Node::new(limits.resolve(Size::ZERO)) +    } + +    fn draw( +        &self, +        renderer: &mut Renderer, +        _defaults: &Renderer::Defaults, +        layout: Layout<'_>, +        _cursor_position: Point, +    ) -> Renderer::Output { +        renderer.draw(layout.bounds()) +    } + +    fn hash_layout(&self, state: &mut Hasher) { +        std::any::TypeId::of::<Space>().hash(state); +        self.width.hash(state); +        self.height.hash(state); +    } +} + +/// The renderer of an amount of [`Space`]. +/// +/// [`Space`]: struct.Space.html +pub trait Renderer: crate::Renderer { +    /// Draws an amount of empty [`Space`]. +    /// +    /// You should most likely return an empty primitive here. +    /// +    /// [`Space`]: struct.Space.html +    fn draw(&mut self, bounds: Rectangle) -> Self::Output; +} + +impl<'a, Message, Renderer> From<Space> for Element<'a, Message, Renderer> +where +    Renderer: self::Renderer, +    Message: 'static, +{ +    fn from(space: Space) -> Element<'a, Message, Renderer> { +        Element::new(space) +    } +} diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs new file mode 100644 index 00000000..063730bb --- /dev/null +++ b/native/src/widget/svg.rs @@ -0,0 +1,188 @@ +//! Display vector graphics in your application. +use crate::{layout, Element, Hasher, Layout, Length, Point, Size, Widget}; + +use std::{ +    hash::Hash, +    path::{Path, PathBuf}, +}; + +/// A vector graphics image. +/// +/// An [`Svg`] image resizes smoothly without losing any quality. +/// +/// [`Svg`] images can have a considerable rendering cost when resized, +/// specially when they are complex. +/// +/// [`Svg`]: struct.Svg.html +#[derive(Debug, Clone)] +pub struct Svg { +    handle: Handle, +    width: Length, +    height: Length, +} + +impl Svg { +    /// Creates a new [`Svg`] from the given [`Handle`]. +    /// +    /// [`Svg`]: struct.Svg.html +    /// [`Handle`]: struct.Handle.html +    pub fn new(handle: impl Into<Handle>) -> Self { +        Svg { +            handle: handle.into(), +            width: Length::Fill, +            height: Length::Shrink, +        } +    } + +    /// Sets the width of the [`Svg`]. +    /// +    /// [`Svg`]: struct.Svg.html +    pub fn width(mut self, width: Length) -> Self { +        self.width = width; +        self +    } + +    /// Sets the height of the [`Svg`]. +    /// +    /// [`Svg`]: struct.Svg.html +    pub fn height(mut self, height: Length) -> Self { +        self.height = height; +        self +    } +} + +impl<Message, Renderer> Widget<Message, Renderer> for Svg +where +    Renderer: self::Renderer, +{ +    fn width(&self) -> Length { +        self.width +    } + +    fn height(&self) -> Length { +        self.height +    } + +    fn layout( +        &self, +        renderer: &Renderer, +        limits: &layout::Limits, +    ) -> layout::Node { +        let (width, height) = renderer.dimensions(&self.handle); + +        let 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; +        } else { +            size.height = height as f32 * size.width / width as f32; +        } + +        layout::Node::new(size) +    } + +    fn draw( +        &self, +        renderer: &mut Renderer, +        _defaults: &Renderer::Defaults, +        layout: Layout<'_>, +        _cursor_position: Point, +    ) -> Renderer::Output { +        renderer.draw(self.handle.clone(), layout) +    } + +    fn hash_layout(&self, state: &mut Hasher) { +        self.width.hash(state); +        self.height.hash(state); +    } +} + +/// An [`Svg`] handle. +/// +/// [`Svg`]: struct.Svg.html +#[derive(Debug, Clone)] +pub struct Handle { +    id: u64, +    path: PathBuf, +} + +impl Handle { +    /// Creates an SVG [`Handle`] pointing to the vector image of the given +    /// path. +    /// +    /// [`Handle`]: struct.Handle.html +    pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle { +        use std::hash::Hasher as _; + +        let path = path.into(); + +        let mut hasher = Hasher::default(); +        path.hash(&mut hasher); + +        Handle { +            id: hasher.finish(), +            path, +        } +    } + +    /// Returns the unique identifier of the [`Handle`]. +    /// +    /// [`Handle`]: struct.Handle.html +    pub fn id(&self) -> u64 { +        self.id +    } + +    /// Returns a reference to the path of the [`Handle`]. +    /// +    /// [`Handle`]: enum.Handle.html +    pub fn path(&self) -> &Path { +        &self.path +    } +} + +impl From<String> for Handle { +    fn from(path: String) -> Handle { +        Handle::from_path(path) +    } +} + +impl From<&str> for Handle { +    fn from(path: &str) -> Handle { +        Handle::from_path(path) +    } +} + +/// The renderer of an [`Svg`]. +/// +/// Your [renderer] will need to implement this trait before being able to use +/// an [`Svg`] in your user interface. +/// +/// [`Svg`]: struct.Svg.html +/// [renderer]: ../../renderer/index.html +pub trait Renderer: crate::Renderer { +    /// Returns the default dimensions of an [`Svg`] located on the given path. +    /// +    /// [`Svg`]: struct.Svg.html +    fn dimensions(&self, handle: &Handle) -> (u32, u32); + +    /// Draws an [`Svg`]. +    /// +    /// [`Svg`]: struct.Svg.html +    fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output; +} + +impl<'a, Message, Renderer> From<Svg> for Element<'a, Message, Renderer> +where +    Renderer: self::Renderer, +{ +    fn from(icon: Svg) -> Element<'a, Message, Renderer> { +        Element::new(icon) +    } +} diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs index cf9c9565..7d8cad6e 100644 --- a/native/src/widget/text.rs +++ b/native/src/widget/text.rs @@ -41,7 +41,7 @@ impl Text {              size: None,              color: None,              font: Font::Default, -            width: Length::Fill, +            width: Length::Shrink,              height: Length::Shrink,              horizontal_alignment: HorizontalAlignment::Left,              vertical_alignment: VerticalAlignment::Top, @@ -131,7 +131,7 @@ where      ) -> layout::Node {          let limits = limits.width(self.width).height(self.height); -        let size = self.size.unwrap_or(renderer.default_size()); +        let size = self.size.unwrap_or(Renderer::DEFAULT_SIZE);          let bounds = limits.max(); @@ -146,13 +146,15 @@ where      fn draw(          &self,          renderer: &mut Renderer, +        defaults: &Renderer::Defaults,          layout: Layout<'_>,          _cursor_position: Point,      ) -> Renderer::Output {          renderer.draw( +            defaults,              layout.bounds(),              &self.content, -            self.size.unwrap_or(renderer.default_size()), +            self.size.unwrap_or(Renderer::DEFAULT_SIZE),              self.font,              self.color,              self.horizontal_alignment, @@ -177,10 +179,10 @@ where  /// [renderer]: ../../renderer/index.html  /// [`UserInterface`]: ../../struct.UserInterface.html  pub trait Renderer: crate::Renderer { -    /// Returns the default size of the [`Text`]. +    /// The default size of [`Text`].      ///      /// [`Text`]: struct.Text.html -    fn default_size(&self) -> u16; +    const DEFAULT_SIZE: u16;      /// Measures the [`Text`] in the given bounds and returns the minimum      /// boundaries that can fit the contents. @@ -209,6 +211,7 @@ pub trait Renderer: crate::Renderer {      /// [`VerticalAlignment`]: enum.VerticalAlignment.html      fn draw(          &mut self, +        defaults: &Self::Defaults,          bounds: Rectangle,          content: &str,          size: u16, diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index f97ed424..c068b895 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -6,16 +6,20 @@  //! [`State`]: struct.State.html  use crate::{      input::{keyboard, mouse, ButtonState}, -    layout, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size, -    Widget, +    layout, Clipboard, Element, Event, Font, Hasher, Layout, Length, Point, +    Rectangle, Size, Widget,  }; +use std::u32; +use unicode_segmentation::UnicodeSegmentation; +  /// A field that can be filled with text.  ///  /// # Example  /// ``` -/// # use iced_native::{text_input, TextInput}; +/// # use iced_native::{text_input, renderer::Null};  /// # +/// # pub type TextInput<'a, Message> = iced_native::TextInput<'a, Message, Null>;  /// #[derive(Debug, Clone)]  /// enum Message {  ///     TextInputChanged(String), @@ -34,19 +38,22 @@ use crate::{  /// ```  ///   #[allow(missing_debug_implementations)] -pub struct TextInput<'a, Message> { +pub struct TextInput<'a, Message, Renderer: self::Renderer> {      state: &'a mut State,      placeholder: String,      value: Value, +    is_secure: bool, +    font: Font,      width: Length, -    max_width: Length, +    max_width: u32,      padding: u16,      size: Option<u16>,      on_change: Box<dyn Fn(String) -> Message>,      on_submit: Option<Message>, +    style: Renderer::Style,  } -impl<'a, Message> TextInput<'a, Message> { +impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {      /// Creates a new [`TextInput`].      ///      /// It expects: @@ -66,19 +73,38 @@ impl<'a, Message> TextInput<'a, Message> {      where          F: 'static + Fn(String) -> Message,      { -        Self { +        TextInput {              state,              placeholder: String::from(placeholder),              value: Value::new(value), +            is_secure: false, +            font: Font::Default,              width: Length::Fill, -            max_width: Length::Shrink, +            max_width: u32::MAX,              padding: 0,              size: None,              on_change: Box::new(on_change),              on_submit: None, +            style: Renderer::Style::default(),          }      } +    /// Converts the [`TextInput`] into a secure password input. +    /// +    /// [`TextInput`]: struct.TextInput.html +    pub fn password(mut self) -> Self { +        self.is_secure = true; +        self +    } + +    /// Sets the [`Font`] of the [`Text`]. +    /// +    /// [`Text`]: struct.Text.html +    /// [`Font`]: ../../struct.Font.html +    pub fn font(mut self, font: Font) -> Self { +        self.font = font; +        self +    }      /// Sets the width of the [`TextInput`].      ///      /// [`TextInput`]: struct.TextInput.html @@ -90,7 +116,7 @@ impl<'a, Message> TextInput<'a, Message> {      /// Sets the maximum width of the [`TextInput`].      ///      /// [`TextInput`]: struct.TextInput.html -    pub fn max_width(mut self, max_width: Length) -> Self { +    pub fn max_width(mut self, max_width: u32) -> Self {          self.max_width = max_width;          self      } @@ -119,11 +145,20 @@ impl<'a, Message> TextInput<'a, Message> {          self.on_submit = Some(message);          self      } + +    /// Sets the style of the [`TextInput`]. +    /// +    /// [`TextInput`]: struct.TextInput.html +    pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { +        self.style = style.into(); +        self +    }  } -impl<'a, Message, Renderer> Widget<Message, Renderer> for TextInput<'a, Message> +impl<'a, Message, Renderer> Widget<Message, Renderer> +    for TextInput<'a, Message, Renderer>  where -    Renderer: self::Renderer, +    Renderer: 'static + self::Renderer,      Message: Clone + std::fmt::Debug,  {      fn width(&self) -> Length { @@ -145,11 +180,11 @@ where          let limits = limits              .pad(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.bounds.x = padding; -        text.bounds.y = padding; +        text.move_to(Point::new(padding, padding));          layout::Node::with_children(text.size().pad(padding), vec![text])      } @@ -160,22 +195,57 @@ where          layout: Layout<'_>,          cursor_position: Point,          messages: &mut Vec<Message>, -        _renderer: &Renderer, +        renderer: &Renderer, +        clipboard: Option<&dyn Clipboard>,      ) {          match event {              Event::Mouse(mouse::Event::Input {                  button: mouse::Button::Left,                  state: ButtonState::Pressed,              }) => { -                self.state.is_focused = -                    layout.bounds().contains(cursor_position); - -                if self.state.cursor_position(&self.value) == 0 { -                    self.state.move_cursor_to_end(&self.value); +                let is_clicked = layout.bounds().contains(cursor_position); + +                if is_clicked { +                    let text_layout = layout.children().next().unwrap(); +                    let target = cursor_position.x - text_layout.bounds().x; + +                    if target > 0.0 { +                        let value = if self.is_secure { +                            self.value.secure() +                        } else { +                            self.value.clone() +                        }; + +                        let size = self.size.unwrap_or(renderer.default_size()); + +                        let offset = renderer.offset( +                            text_layout.bounds(), +                            size, +                            &value, +                            &self.state, +                            self.font, +                        ); + +                        self.state.cursor_position = find_cursor_position( +                            renderer, +                            target + offset, +                            &value, +                            size, +                            0, +                            self.value.len(), +                            self.font, +                        ); +                    } else { +                        self.state.cursor_position = 0; +                    }                  } + +                self.state.is_focused = is_clicked;              }              Event::Keyboard(keyboard::Event::CharacterReceived(c)) -                if self.state.is_focused && !c.is_control() => +                if self.state.is_focused +                    && self.state.is_pasting.is_none() +                    && !c.is_control() =>              {                  let cursor_position = self.state.cursor_position(&self.value); @@ -188,6 +258,7 @@ where              Event::Keyboard(keyboard::Event::Input {                  key_code,                  state: ButtonState::Pressed, +                modifiers,              }) if self.state.is_focused => match key_code {                  keyboard::KeyCode::Enter => {                      if let Some(on_submit) = self.on_submit.clone() { @@ -219,10 +290,75 @@ where                      }                  }                  keyboard::KeyCode::Left => { -                    self.state.move_cursor_left(&self.value); +                    if platform::is_jump_modifier_pressed(modifiers) +                        && !self.is_secure +                    { +                        self.state.move_cursor_left_by_words(&self.value); +                    } else { +                        self.state.move_cursor_left(&self.value); +                    }                  }                  keyboard::KeyCode::Right => { -                    self.state.move_cursor_right(&self.value); +                    if platform::is_jump_modifier_pressed(modifiers) +                        && !self.is_secure +                    { +                        self.state.move_cursor_right_by_words(&self.value); +                    } else { +                        self.state.move_cursor_right(&self.value); +                    } +                } +                keyboard::KeyCode::Home => { +                    self.state.cursor_position = 0; +                } +                keyboard::KeyCode::End => { +                    self.state.move_cursor_to_end(&self.value); +                } +                keyboard::KeyCode::V => { +                    if platform::is_copy_paste_modifier_pressed(modifiers) { +                        if let Some(clipboard) = clipboard { +                            let content = match self.state.is_pasting.take() { +                                Some(content) => content, +                                None => { +                                    let content: String = clipboard +                                        .content() +                                        .unwrap_or(String::new()) +                                        .chars() +                                        .filter(|c| !c.is_control()) +                                        .collect(); + +                                    Value::new(&content) +                                } +                            }; + +                            let cursor_position = +                                self.state.cursor_position(&self.value); + +                            self.value +                                .insert_many(cursor_position, content.clone()); + +                            self.state.move_cursor_right_by_amount( +                                &self.value, +                                content.len(), +                            ); +                            self.state.is_pasting = Some(content); + +                            let message = +                                (self.on_change)(self.value.to_string()); +                            messages.push(message); +                        } +                    } else { +                        self.state.is_pasting = None; +                    } +                } +                _ => {} +            }, +            Event::Keyboard(keyboard::Event::Input { +                key_code, +                state: ButtonState::Released, +                .. +            }) => match key_code { +                keyboard::KeyCode::V => { +                    self.state.is_pasting = None;                  }                  _ => {}              }, @@ -233,27 +369,44 @@ where      fn draw(          &self,          renderer: &mut Renderer, +        _defaults: &Renderer::Defaults,          layout: Layout<'_>,          cursor_position: Point,      ) -> Renderer::Output {          let bounds = layout.bounds();          let text_bounds = layout.children().next().unwrap().bounds(); -        renderer.draw( -            bounds, -            text_bounds, -            cursor_position, -            self.size.unwrap_or(renderer.default_size()), -            &self.placeholder, -            &self.value, -            &self.state, -        ) +        if self.is_secure { +            renderer.draw( +                bounds, +                text_bounds, +                cursor_position, +                self.size.unwrap_or(renderer.default_size()), +                self.font, +                &self.placeholder, +                &self.value.secure(), +                &self.state, +                &self.style, +            ) +        } else { +            renderer.draw( +                bounds, +                text_bounds, +                cursor_position, +                self.size.unwrap_or(renderer.default_size()), +                self.font, +                &self.placeholder, +                &self.value, +                &self.state, +                &self.style, +            ) +        }      }      fn hash_layout(&self, state: &mut Hasher) {          use std::{any::TypeId, hash::Hash}; -        TypeId::of::<TextInput<'static, ()>>().hash(state); +        TypeId::of::<TextInput<'static, (), Renderer>>().hash(state);          self.width.hash(state);          self.max_width.hash(state); @@ -270,11 +423,36 @@ where  /// [`TextInput`]: struct.TextInput.html  /// [renderer]: ../../renderer/index.html  pub trait Renderer: crate::Renderer + Sized { +    /// The style supported by this renderer. +    type Style: Default; +      /// Returns the default size of the text of the [`TextInput`].      ///      /// [`TextInput`]: struct.TextInput.html      fn default_size(&self) -> u16; +    /// Returns the width of the value of the [`TextInput`]. +    /// +    /// [`TextInput`]: struct.TextInput.html +    fn measure_value(&self, value: &str, size: u16, font: Font) -> f32; + +    /// Returns the current horizontal offset of the value of the +    /// [`TextInput`]. +    /// +    /// This is the amount of horizontal scrolling applied when the [`Value`] +    /// does not fit the [`TextInput`]. +    /// +    /// [`TextInput`]: struct.TextInput.html +    /// [`Value`]: struct.Value.html +    fn offset( +        &self, +        text_bounds: Rectangle, +        size: u16, +        value: &Value, +        state: &State, +        font: Font, +    ) -> f32; +      /// Draws a [`TextInput`].      ///      /// It receives: @@ -294,20 +472,22 @@ pub trait Renderer: crate::Renderer + Sized {          text_bounds: Rectangle,          cursor_position: Point,          size: u16, +        font: Font,          placeholder: &str,          value: &Value,          state: &State, +        style: &Self::Style,      ) -> Self::Output;  } -impl<'a, Message, Renderer> From<TextInput<'a, Message>> +impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>>      for Element<'a, Message, Renderer>  where      Renderer: 'static + self::Renderer,      Message: 'static + Clone + std::fmt::Debug,  {      fn from( -        text_input: TextInput<'a, Message>, +        text_input: TextInput<'a, Message, Renderer>,      ) -> Element<'a, Message, Renderer> {          Element::new(text_input)      } @@ -319,7 +499,9 @@ where  #[derive(Debug, Default, Clone)]  pub struct State {      is_focused: bool, +    is_pasting: Option<Value>,      cursor_position: usize, +    // TODO: Add stateful horizontal scrolling offset  }  impl State { @@ -338,6 +520,7 @@ impl State {          Self {              is_focused: true, +            is_pasting: None,              cursor_position: usize::MAX,          }      } @@ -356,26 +539,53 @@ impl State {          self.cursor_position.min(value.len())      } +    /// Moves the cursor of a [`TextInput`] to the left. +    /// +    /// [`TextInput`]: struct.TextInput.html +    pub(crate) fn move_cursor_left(&mut self, value: &Value) { +        let current = self.cursor_position(value); + +        if current > 0 { +            self.cursor_position = current - 1; +        } +    } +      /// Moves the cursor of a [`TextInput`] to the right.      ///      /// [`TextInput`]: struct.TextInput.html      pub(crate) fn move_cursor_right(&mut self, value: &Value) { +        self.move_cursor_right_by_amount(value, 1) +    } + +    pub(crate) fn move_cursor_right_by_amount( +        &mut self, +        value: &Value, +        amount: usize, +    ) {          let current = self.cursor_position(value); +        let new_position = current.saturating_add(amount); -        if current < value.len() { -            self.cursor_position = current + 1; +        if new_position < value.len() + 1 { +            self.cursor_position = new_position;          }      } -    /// Moves the cursor of a [`TextInput`] to the left. +    /// Moves the cursor of a [`TextInput`] to the previous start of a word.      ///      /// [`TextInput`]: struct.TextInput.html -    pub(crate) fn move_cursor_left(&mut self, value: &Value) { +    pub(crate) fn move_cursor_left_by_words(&mut self, value: &Value) {          let current = self.cursor_position(value); -        if current > 0 { -            self.cursor_position = current - 1; -        } +        self.cursor_position = value.previous_start_of_word(current); +    } + +    /// Moves the cursor of a [`TextInput`] to the next end of a word. +    /// +    /// [`TextInput`]: struct.TextInput.html +    pub(crate) fn move_cursor_right_by_words(&mut self, value: &Value) { +        let current = self.cursor_position(value); + +        self.cursor_position = value.next_end_of_word(current);      }      /// Moves the cursor of a [`TextInput`] to the end. @@ -389,51 +599,207 @@ impl State {  /// The value of a [`TextInput`].  ///  /// [`TextInput`]: struct.TextInput.html -// TODO: Use `unicode-segmentation` -#[derive(Debug)] -pub struct Value(Vec<char>); +// TODO: Reduce allocations, cache results (?) +#[derive(Debug, Clone)] +pub struct Value { +    graphemes: Vec<String>, +}  impl Value {      /// Creates a new [`Value`] from a string slice.      ///      /// [`Value`]: struct.Value.html      pub fn new(string: &str) -> Self { -        Self(string.chars().collect()) +        let graphemes = UnicodeSegmentation::graphemes(string, true) +            .map(String::from) +            .collect(); + +        Self { graphemes }      } -    /// Returns the total amount of `char` in the [`Value`]. +    /// Returns the total amount of graphemes in the [`Value`].      ///      /// [`Value`]: struct.Value.html      pub fn len(&self) -> usize { -        self.0.len() +        self.graphemes.len() +    } + +    /// Returns the position of the previous start of a word from the given +    /// grapheme `index`. +    /// +    /// [`Value`]: struct.Value.html +    pub fn previous_start_of_word(&self, index: usize) -> usize { +        let previous_string = +            &self.graphemes[..index.min(self.graphemes.len())].concat(); + +        UnicodeSegmentation::split_word_bound_indices(&previous_string as &str) +            .filter(|(_, word)| !word.trim_start().is_empty()) +            .next_back() +            .map(|(i, previous_word)| { +                index +                    - UnicodeSegmentation::graphemes(previous_word, true) +                        .count() +                    - UnicodeSegmentation::graphemes( +                        &previous_string[i + previous_word.len()..] as &str, +                        true, +                    ) +                    .count() +            }) +            .unwrap_or(0)      } -    /// Returns a new [`Value`] containing the `char` until the given `index`. +    /// Returns the position of the next end of a word from the given grapheme +    /// `index`. +    /// +    /// [`Value`]: struct.Value.html +    pub fn next_end_of_word(&self, index: usize) -> usize { +        let next_string = &self.graphemes[index..].concat(); + +        UnicodeSegmentation::split_word_bound_indices(&next_string as &str) +            .filter(|(_, word)| !word.trim_start().is_empty()) +            .next() +            .map(|(i, next_word)| { +                index +                    + UnicodeSegmentation::graphemes(next_word, true).count() +                    + UnicodeSegmentation::graphemes( +                        &next_string[..i] as &str, +                        true, +                    ) +                    .count() +            }) +            .unwrap_or(self.len()) +    } + +    /// Returns a new [`Value`] containing the graphemes until the given +    /// `index`.      ///      /// [`Value`]: struct.Value.html      pub fn until(&self, index: usize) -> Self { -        Self(self.0[..index.min(self.len())].to_vec()) +        let graphemes = self.graphemes[..index.min(self.len())].to_vec(); + +        Self { graphemes }      }      /// Converts the [`Value`] into a `String`.      ///      /// [`Value`]: struct.Value.html      pub fn to_string(&self) -> String { -        use std::iter::FromIterator; -        String::from_iter(self.0.iter()) +        self.graphemes.concat()      } -    /// Inserts a new `char` at the given `index`. -    /// -    /// [`Value`]: struct.Value.html +    /// Inserts a new `char` at the given grapheme `index`.      pub fn insert(&mut self, index: usize, c: char) { -        self.0.insert(index, c); +        self.graphemes.insert(index, c.to_string()); + +        self.graphemes = +            UnicodeSegmentation::graphemes(&self.to_string() as &str, true) +                .map(String::from) +                .collect();      } -    /// Removes the `char` at the given `index`. +    /// Inserts a bunch of graphemes at the given grapheme `index`. +    pub fn insert_many(&mut self, index: usize, mut value: Value) { +        let _ = self +            .graphemes +            .splice(index..index, value.graphemes.drain(..)); +    } + +    /// Removes the grapheme at the given `index`.      ///      /// [`Value`]: struct.Value.html      pub fn remove(&mut self, index: usize) { -        let _ = self.0.remove(index); +        let _ = self.graphemes.remove(index); +    } + +    /// Returns a new [`Value`] with all its graphemes replaced with the +    /// dot ('•') character. +    /// +    /// [`Value`]: struct.Value.html +    pub fn secure(&self) -> Self { +        Self { +            graphemes: std::iter::repeat(String::from("•")) +                .take(self.graphemes.len()) +                .collect(), +        } +    } +} + +// TODO: Reduce allocations +fn find_cursor_position<Renderer: self::Renderer>( +    renderer: &Renderer, +    target: f32, +    value: &Value, +    size: u16, +    start: usize, +    end: usize, +    font: Font, +) -> usize { +    if start >= end { +        if start == 0 { +            return 0; +        } + +        let prev = value.until(start - 1); +        let next = value.until(start); + +        let prev_width = renderer.measure_value(&prev.to_string(), size, font); +        let next_width = renderer.measure_value(&next.to_string(), size, font); + +        if next_width - target > target - prev_width { +            return start - 1; +        } else { +            return start; +        } +    } + +    let index = (end - start) / 2; +    let subvalue = value.until(start + index); + +    let width = renderer.measure_value(&subvalue.to_string(), size, font); + +    if width > target { +        find_cursor_position( +            renderer, +            target, +            value, +            size, +            start, +            start + index, +            font, +        ) +    } else { +        find_cursor_position( +            renderer, +            target, +            value, +            size, +            start + index + 1, +            end, +            font, +        ) +    } +} + +mod platform { +    use crate::input::keyboard; + +    pub fn is_jump_modifier_pressed( +        modifiers: keyboard::ModifiersState, +    ) -> bool { +        if cfg!(target_os = "macos") { +            modifiers.alt +        } else { +            modifiers.control +        } +    } + +    pub fn is_copy_paste_modifier_pressed( +        modifiers: keyboard::ModifiersState, +    ) -> bool { +        if cfg!(target_os = "macos") { +            modifiers.logo +        } else { +            modifiers.control +        }      }  } diff --git a/native/src/window.rs b/native/src/window.rs new file mode 100644 index 00000000..4dcae62f --- /dev/null +++ b/native/src/window.rs @@ -0,0 +1,6 @@ +//! Build window-based GUI applications. +mod backend; +mod event; + +pub use backend::Backend; +pub use event::Event; diff --git a/native/src/window/backend.rs b/native/src/window/backend.rs new file mode 100644 index 00000000..3bc691cd --- /dev/null +++ b/native/src/window/backend.rs @@ -0,0 +1,55 @@ +use crate::MouseCursor; + +use raw_window_handle::HasRawWindowHandle; + +/// A graphics backend that can render to windows. +pub trait Backend: Sized { +    /// The settings of the backend. +    type Settings: Default; + +    /// The iced renderer of the backend. +    type Renderer: crate::Renderer; + +    /// The surface of the backend. +    type Surface; + +    /// The swap chain of the backend. +    type SwapChain; + +    /// Creates a new [`Backend`] and an associated iced renderer. +    /// +    /// [`Backend`]: trait.Backend.html +    fn new(settings: Self::Settings) -> (Self, Self::Renderer); + +    /// Crates a new [`Surface`] for the given window. +    /// +    /// [`Surface`]: #associatedtype.Surface +    fn create_surface<W: HasRawWindowHandle>( +        &mut self, +        window: &W, +    ) -> Self::Surface; + +    /// Crates a new [`SwapChain`] for the given [`Surface`]. +    /// +    /// [`SwapChain`]: #associatedtype.SwapChain +    /// [`Surface`]: #associatedtype.Surface +    fn create_swap_chain( +        &mut self, +        surface: &Self::Surface, +        width: u32, +        height: u32, +    ) -> Self::SwapChain; + +    /// Draws the output primitives to the next frame of the given [`SwapChain`]. +    /// +    /// [`SwapChain`]: #associatedtype.SwapChain +    /// [`Surface`]: #associatedtype.Surface +    fn draw<T: AsRef<str>>( +        &mut self, +        renderer: &mut Self::Renderer, +        swap_chain: &mut Self::SwapChain, +        output: &<Self::Renderer as crate::Renderer>::Output, +        scale_factor: f64, +        overlay: &[T], +    ) -> MouseCursor; +} diff --git a/native/src/window/event.rs b/native/src/window/event.rs new file mode 100644 index 00000000..b177141a --- /dev/null +++ b/native/src/window/event.rs @@ -0,0 +1,32 @@ +use std::path::PathBuf; + +/// A window-related event. +#[derive(PartialEq, Clone, Debug)] +pub enum Event { +    /// A window was resized +    Resized { +        /// The new width of the window (in units) +        width: u32, + +        /// The new height of the window (in units) +        height: u32, +    }, + +    /// A file is being hovered over the window. +    /// +    /// When the user hovers multiple files at once, this event will be emitted +    /// for each file separately. +    FileHovered(PathBuf), + +    /// A file has beend dropped into the window. +    /// +    /// When the user drops multiple files at once, this event will be emitted +    /// for each file separately. +    FileDropped(PathBuf), + +    /// A file was hovered, but has exited the window. +    /// +    /// There will be a single `FilesHoveredLeft` event triggered even if +    /// multiple files were hovered. +    FilesHoveredLeft, +}  | 
