diff options
author | 2023-03-04 05:37:11 +0100 | |
---|---|---|
committer | 2023-03-04 05:37:11 +0100 | |
commit | 3a0d34c0240f4421737a6a08761f99d6f8140d02 (patch) | |
tree | c9a4a6b8e9c1db1b8fcd05bc98e3f131d5ef4bd5 /native | |
parent | c54409d1711e1f615c7ea4b02c082954e340632a (diff) | |
download | iced-3a0d34c0240f4421737a6a08761f99d6f8140d02.tar.gz iced-3a0d34c0240f4421737a6a08761f99d6f8140d02.tar.bz2 iced-3a0d34c0240f4421737a6a08761f99d6f8140d02.zip |
Create `iced_widget` subcrate and re-organize the whole codebase
Diffstat (limited to 'native')
80 files changed, 70 insertions, 16210 deletions
diff --git a/native/Cargo.toml b/native/Cargo.toml index 1eedf0da..bc4e7ca1 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -11,9 +11,6 @@ repository = "https://github.com/iced-rs/iced" debug = [] [dependencies] -twox-hash = { version = "1.5", default-features = false } -unicode-segmentation = "1.6" -num-traits = "0.2" thiserror = "1" [dependencies.iced_core] @@ -24,7 +21,3 @@ path = "../core" version = "0.6" path = "../futures" features = ["thread-pool"] - -[dependencies.iced_style] -version = "0.7" -path = "../style" diff --git a/native/src/clipboard.rs b/native/src/clipboard.rs index c9105bc0..e727c4a7 100644 --- a/native/src/clipboard.rs +++ b/native/src/clipboard.rs @@ -3,28 +3,6 @@ use iced_futures::MaybeSend; use std::fmt; -/// A buffer for short-term storage and transfer within and between -/// applications. -pub trait Clipboard { - /// Reads the current content of the [`Clipboard`] as text. - fn read(&self) -> Option<String>; - - /// Writes the given text contents to the [`Clipboard`]. - fn write(&mut self, contents: String); -} - -/// A null implementation of the [`Clipboard`] trait. -#[derive(Debug, Clone, Copy)] -pub struct Null; - -impl Clipboard for Null { - fn read(&self) -> Option<String> { - None - } - - fn write(&mut self, _contents: String) {} -} - /// A clipboard action to be performed by some [`Command`]. /// /// [`Command`]: crate::Command diff --git a/native/src/command.rs b/native/src/command.rs index ca9d0b64..39bee8f6 100644 --- a/native/src/command.rs +++ b/native/src/command.rs @@ -28,7 +28,9 @@ impl<T> Command<T> { } /// Creates a [`Command`] that performs a [`widget::Operation`]. - pub fn widget(operation: impl widget::Operation<T> + 'static) -> Self { + pub fn widget( + operation: impl iced_core::widget::Operation<T> + 'static, + ) -> Self { Self(iced_futures::Command::single(Action::Widget( widget::Action::new(operation), ))) diff --git a/native/src/debug/basic.rs b/native/src/debug/basic.rs index 92f614da..32f725a1 100644 --- a/native/src/debug/basic.rs +++ b/native/src/debug/basic.rs @@ -1,5 +1,5 @@ #![allow(missing_docs)] -use crate::time; +use crate::core::time; use std::collections::VecDeque; diff --git a/native/src/element.rs b/native/src/element.rs deleted file mode 100644 index 0a677d20..00000000 --- a/native/src/element.rs +++ /dev/null @@ -1,583 +0,0 @@ -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::widget; -use crate::widget::tree::{self, Tree}; -use crate::{ - Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Widget, -}; - -use std::any::Any; -use std::borrow::Borrow; - -/// A generic [`Widget`]. -/// -/// It is useful to build composable user interfaces that do not leak -/// implementation details in their __view logic__. -/// -/// If you have a [built-in widget], you should be able to use `Into<Element>` -/// to turn it into an [`Element`]. -/// -/// [built-in widget]: crate::widget -#[allow(missing_debug_implementations)] -pub struct Element<'a, Message, Renderer> { - widget: Box<dyn Widget<Message, Renderer> + 'a>, -} - -impl<'a, Message, Renderer> Element<'a, Message, Renderer> { - /// Creates a new [`Element`] containing the given [`Widget`]. - pub fn new(widget: impl Widget<Message, Renderer> + 'a) -> Self - where - Renderer: crate::Renderer, - { - Self { - widget: Box::new(widget), - } - } - - /// Returns a reference to the [`Widget`] of the [`Element`], - pub fn as_widget(&self) -> &dyn Widget<Message, Renderer> { - self.widget.as_ref() - } - - /// Returns a mutable reference to the [`Widget`] of the [`Element`], - pub fn as_widget_mut(&mut self) -> &mut dyn Widget<Message, Renderer> { - self.widget.as_mut() - } - - /// Applies a transformation to the produced message of the [`Element`]. - /// - /// This method is useful when you want to decouple different parts of your - /// UI and make them __composable__. - /// - /// # Example - /// Imagine we want to use [our counter](index.html#usage). But instead of - /// showing a single counter, we want to display many of them. We can reuse - /// the `Counter` type as it is! - /// - /// We use composition to model the __state__ of our new application: - /// - /// ``` - /// # mod counter { - /// # pub struct Counter; - /// # } - /// use counter::Counter; - /// - /// struct ManyCounters { - /// counters: Vec<Counter>, - /// } - /// ``` - /// - /// We can store the state of multiple counters now. However, the - /// __messages__ we implemented before describe the user interactions - /// of a __single__ counter. Right now, we need to also identify which - /// counter is receiving user interactions. Can we use composition again? - /// Yes. - /// - /// ``` - /// # mod counter { - /// # #[derive(Debug, Clone, Copy)] - /// # pub enum Message {} - /// # } - /// #[derive(Debug, Clone, Copy)] - /// pub enum Message { - /// Counter(usize, counter::Message) - /// } - /// ``` - /// - /// We compose the previous __messages__ with the index of the counter - /// producing them. Let's implement our __view logic__ now: - /// - /// ``` - /// # mod counter { - /// # type Text<'a> = iced_native::widget::Text<'a, iced_native::renderer::Null>; - /// # - /// # #[derive(Debug, Clone, Copy)] - /// # pub enum Message {} - /// # pub struct Counter; - /// # - /// # impl Counter { - /// # pub fn view(&mut self) -> Text { - /// # Text::new("") - /// # } - /// # } - /// # } - /// # - /// # mod iced_wgpu { - /// # pub use iced_native::renderer::Null as Renderer; - /// # } - /// # - /// # use counter::Counter; - /// # - /// # struct ManyCounters { - /// # counters: Vec<Counter>, - /// # } - /// # - /// # #[derive(Debug, Clone, Copy)] - /// # pub enum Message { - /// # Counter(usize, counter::Message) - /// # } - /// use iced_native::Element; - /// use iced_native::widget::Row; - /// use iced_wgpu::Renderer; - /// - /// impl ManyCounters { - /// pub fn view(&mut self) -> Row<Message, Renderer> { - /// // We can quickly populate a `Row` by folding over our counters - /// self.counters.iter_mut().enumerate().fold( - /// Row::new().spacing(20), - /// |row, (index, counter)| { - /// // We display the counter - /// let element: Element<counter::Message, Renderer> = - /// counter.view().into(); - /// - /// row.push( - /// // Here we turn our `Element<counter::Message>` into - /// // an `Element<Message>` by combining the `index` and the - /// // message of the `element`. - /// element.map(move |message| Message::Counter(index, message)) - /// ) - /// } - /// ) - /// } - /// } - /// ``` - /// - /// Finally, our __update logic__ is pretty straightforward: simple - /// delegation. - /// - /// ``` - /// # mod counter { - /// # #[derive(Debug, Clone, Copy)] - /// # pub enum Message {} - /// # pub struct Counter; - /// # - /// # impl Counter { - /// # pub fn update(&mut self, _message: Message) {} - /// # } - /// # } - /// # - /// # use counter::Counter; - /// # - /// # struct ManyCounters { - /// # counters: Vec<Counter>, - /// # } - /// # - /// # #[derive(Debug, Clone, Copy)] - /// # pub enum Message { - /// # Counter(usize, counter::Message) - /// # } - /// impl ManyCounters { - /// pub fn update(&mut self, message: Message) { - /// match message { - /// Message::Counter(index, counter_msg) => { - /// if let Some(counter) = self.counters.get_mut(index) { - /// counter.update(counter_msg); - /// } - /// } - /// } - /// } - /// } - /// ``` - pub fn map<B>( - self, - f: impl Fn(Message) -> B + 'a, - ) -> Element<'a, B, Renderer> - where - Message: 'a, - Renderer: crate::Renderer + 'a, - B: 'a, - { - Element::new(Map::new(self.widget, f)) - } - - /// Marks the [`Element`] as _to-be-explained_. - /// - /// The [`Renderer`] will explain the layout of the [`Element`] graphically. - /// This can be very useful for debugging your layout! - /// - /// [`Renderer`]: crate::Renderer - pub fn explain<C: Into<Color>>( - self, - color: C, - ) -> Element<'a, Message, Renderer> - where - Message: 'static, - Renderer: crate::Renderer + 'a, - { - Element { - widget: Box::new(Explain::new(self, color.into())), - } - } -} - -impl<'a, Message, Renderer> Borrow<dyn Widget<Message, Renderer> + 'a> - for Element<'a, Message, Renderer> -{ - fn borrow(&self) -> &(dyn Widget<Message, Renderer> + 'a) { - self.widget.borrow() - } -} - -impl<'a, Message, Renderer> Borrow<dyn Widget<Message, Renderer> + 'a> - for &Element<'a, Message, Renderer> -{ - fn borrow(&self) -> &(dyn Widget<Message, Renderer> + 'a) { - self.widget.borrow() - } -} - -struct Map<'a, A, B, Renderer> { - widget: Box<dyn Widget<A, Renderer> + 'a>, - mapper: Box<dyn Fn(A) -> B + 'a>, -} - -impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> { - pub fn new<F>( - widget: Box<dyn Widget<A, Renderer> + 'a>, - mapper: F, - ) -> Map<'a, A, B, Renderer> - where - F: 'a + Fn(A) -> B, - { - Map { - widget, - mapper: Box::new(mapper), - } - } -} - -impl<'a, A, B, Renderer> Widget<B, Renderer> for Map<'a, A, B, Renderer> -where - Renderer: crate::Renderer + 'a, - A: 'a, - B: 'a, -{ - fn tag(&self) -> tree::Tag { - self.widget.tag() - } - - fn state(&self) -> tree::State { - self.widget.state() - } - - fn children(&self) -> Vec<Tree> { - self.widget.children() - } - - fn diff(&self, tree: &mut Tree) { - self.widget.diff(tree) - } - - fn width(&self) -> Length { - self.widget.width() - } - - fn height(&self) -> Length { - self.widget.height() - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - self.widget.layout(renderer, limits) - } - - fn operate( - &self, - tree: &mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn widget::Operation<B>, - ) { - struct MapOperation<'a, B> { - operation: &'a mut dyn widget::Operation<B>, - } - - impl<'a, T, B> widget::Operation<T> for MapOperation<'a, B> { - fn container( - &mut self, - id: Option<&widget::Id>, - operate_on_children: &mut dyn FnMut( - &mut dyn widget::Operation<T>, - ), - ) { - self.operation.container(id, &mut |operation| { - operate_on_children(&mut MapOperation { operation }); - }); - } - - fn focusable( - &mut self, - state: &mut dyn widget::operation::Focusable, - id: Option<&widget::Id>, - ) { - self.operation.focusable(state, id); - } - - fn scrollable( - &mut self, - state: &mut dyn widget::operation::Scrollable, - id: Option<&widget::Id>, - ) { - self.operation.scrollable(state, id); - } - - fn text_input( - &mut self, - state: &mut dyn widget::operation::TextInput, - id: Option<&widget::Id>, - ) { - self.operation.text_input(state, id); - } - - fn custom(&mut self, state: &mut dyn Any, id: Option<&widget::Id>) { - self.operation.custom(state, id); - } - } - - self.widget.operate( - tree, - layout, - renderer, - &mut MapOperation { operation }, - ); - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, B>, - ) -> event::Status { - let mut local_messages = Vec::new(); - let mut local_shell = Shell::new(&mut local_messages); - - let status = self.widget.on_event( - tree, - event, - layout, - cursor_position, - renderer, - clipboard, - &mut local_shell, - ); - - shell.merge(local_shell, &self.mapper); - - status - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ) { - self.widget.draw( - tree, - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ) - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.widget.mouse_interaction( - tree, - layout, - cursor_position, - viewport, - renderer, - ) - } - - fn overlay<'b>( - &'b mut self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option<overlay::Element<'b, B, Renderer>> { - let mapper = &self.mapper; - - self.widget - .overlay(tree, layout, renderer) - .map(move |overlay| overlay.map(mapper)) - } -} - -struct Explain<'a, Message, Renderer: crate::Renderer> { - element: Element<'a, Message, Renderer>, - color: Color, -} - -impl<'a, Message, Renderer> Explain<'a, Message, Renderer> -where - Renderer: crate::Renderer, -{ - fn new(element: Element<'a, Message, Renderer>, color: Color) -> Self { - Explain { element, color } - } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> - for Explain<'a, Message, Renderer> -where - Renderer: crate::Renderer, -{ - fn width(&self) -> Length { - self.element.widget.width() - } - - fn height(&self) -> Length { - self.element.widget.height() - } - - fn tag(&self) -> tree::Tag { - self.element.widget.tag() - } - - fn state(&self) -> tree::State { - self.element.widget.state() - } - - fn children(&self) -> Vec<Tree> { - self.element.widget.children() - } - - fn diff(&self, tree: &mut Tree) { - self.element.widget.diff(tree); - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - self.element.widget.layout(renderer, limits) - } - - fn operate( - &self, - state: &mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn widget::Operation<Message>, - ) { - self.element - .widget - .operate(state, layout, renderer, operation) - } - - fn on_event( - &mut self, - state: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - self.element.widget.on_event( - state, - event, - layout, - cursor_position, - renderer, - clipboard, - shell, - ) - } - - fn draw( - &self, - state: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ) { - fn explain_layout<Renderer: crate::Renderer>( - renderer: &mut Renderer, - color: Color, - layout: Layout<'_>, - ) { - renderer.fill_quad( - renderer::Quad { - bounds: layout.bounds(), - border_color: color, - border_width: 1.0, - border_radius: 0.0.into(), - }, - Color::TRANSPARENT, - ); - - for child in layout.children() { - explain_layout(renderer, color, child); - } - } - - self.element.widget.draw( - state, - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ); - - explain_layout(renderer, self.color, layout); - } - - fn mouse_interaction( - &self, - state: &Tree, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.element.widget.mouse_interaction( - state, - layout, - cursor_position, - viewport, - renderer, - ) - } - - fn overlay<'b>( - &'b mut self, - state: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option<overlay::Element<'b, Message, Renderer>> { - self.element.widget.overlay(state, layout, renderer) - } -} diff --git a/native/src/event.rs b/native/src/event.rs deleted file mode 100644 index bcfaf891..00000000 --- a/native/src/event.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! Handle events of a user interface. -use crate::keyboard; -use crate::mouse; -use crate::touch; -use crate::window; - -/// A user interface 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/iced-rs/iced/issues -#[derive(Debug, Clone, PartialEq)] -pub enum Event { - /// A keyboard event - Keyboard(keyboard::Event), - - /// A mouse event - Mouse(mouse::Event), - - /// A window event - Window(window::Event), - - /// A touch event - Touch(touch::Event), - - /// A platform specific event - PlatformSpecific(PlatformSpecific), -} - -/// A platform specific event -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum PlatformSpecific { - /// A MacOS specific event - MacOS(MacOS), -} - -/// Describes an event specific to MacOS -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum MacOS { - /// Triggered when the app receives an URL from the system - /// - /// _**Note:** For this event to be triggered, the executable needs to be properly [bundled]!_ - /// - /// [bundled]: https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW19 - ReceivedUrl(String), -} - -/// The status of an [`Event`] after being processed. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Status { - /// The [`Event`] was **NOT** handled by any widget. - Ignored, - - /// The [`Event`] was handled and processed by a widget. - Captured, -} - -impl Status { - /// Merges two [`Status`] into one. - /// - /// `Captured` takes precedence over `Ignored`: - /// - /// ``` - /// use iced_native::event::Status; - /// - /// assert_eq!(Status::Ignored.merge(Status::Ignored), Status::Ignored); - /// assert_eq!(Status::Ignored.merge(Status::Captured), Status::Captured); - /// assert_eq!(Status::Captured.merge(Status::Ignored), Status::Captured); - /// assert_eq!(Status::Captured.merge(Status::Captured), Status::Captured); - /// ``` - pub fn merge(self, b: Self) -> Self { - match self { - Status::Ignored => b, - Status::Captured => Status::Captured, - } - } -} diff --git a/native/src/hasher.rs b/native/src/hasher.rs deleted file mode 100644 index fa52f16d..00000000 --- a/native/src/hasher.rs +++ /dev/null @@ -1,13 +0,0 @@ -/// The hasher used to compare layouts. -#[derive(Debug, Default)] -pub struct Hasher(twox_hash::XxHash64); - -impl core::hash::Hasher for Hasher { - fn write(&mut self, bytes: &[u8]) { - self.0.write(bytes) - } - - fn finish(&self) -> u64 { - self.0.finish() - } -} diff --git a/native/src/image.rs b/native/src/image.rs deleted file mode 100644 index 70fbade0..00000000 --- a/native/src/image.rs +++ /dev/null @@ -1,174 +0,0 @@ -//! Load and draw raster graphics. -use crate::{Hasher, Rectangle, Size}; - -use std::hash::{Hash, Hasher as _}; -use std::path::PathBuf; -use std::sync::Arc; - -/// A handle of some image data. -#[derive(Debug, Clone)] -pub struct Handle { - id: u64, - data: Data, -} - -impl Handle { - /// Creates an image [`Handle`] pointing to the image of the given path. - /// - /// Makes an educated guess about the image format by examining the data in the file. - pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle { - Self::from_data(Data::Path(path.into())) - } - - /// Creates an image [`Handle`] containing the image pixels directly. This - /// function expects the input data to be provided as a `Vec<u8>` of RGBA - /// pixels. - /// - /// This is useful if you have already decoded your image. - pub fn from_pixels( - width: u32, - height: u32, - pixels: impl AsRef<[u8]> + Send + Sync + 'static, - ) -> Handle { - Self::from_data(Data::Rgba { - width, - height, - pixels: Bytes::new(pixels), - }) - } - - /// Creates an image [`Handle`] containing the image data directly. - /// - /// Makes an educated guess about the image format by examining the given data. - /// - /// This is useful if you already have your image loaded in-memory, maybe - /// because you downloaded or generated it procedurally. - pub fn from_memory( - bytes: impl AsRef<[u8]> + Send + Sync + 'static, - ) -> Handle { - Self::from_data(Data::Bytes(Bytes::new(bytes))) - } - - fn from_data(data: Data) -> Handle { - let mut hasher = Hasher::default(); - data.hash(&mut hasher); - - Handle { - id: hasher.finish(), - data, - } - } - - /// Returns the unique identifier of the [`Handle`]. - pub fn id(&self) -> u64 { - self.id - } - - /// Returns a reference to the image [`Data`]. - pub fn data(&self) -> &Data { - &self.data - } -} - -impl<T> From<T> for Handle -where - T: Into<PathBuf>, -{ - fn from(path: T) -> Handle { - Handle::from_path(path.into()) - } -} - -impl Hash for Handle { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - self.id.hash(state); - } -} - -/// A wrapper around raw image data. -/// -/// It behaves like a `&[u8]`. -#[derive(Clone)] -pub struct Bytes(Arc<dyn AsRef<[u8]> + Send + Sync + 'static>); - -impl Bytes { - /// Creates new [`Bytes`] around `data`. - pub fn new(data: impl AsRef<[u8]> + Send + Sync + 'static) -> Self { - Self(Arc::new(data)) - } -} - -impl std::fmt::Debug for Bytes { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.as_ref().as_ref().fmt(f) - } -} - -impl std::hash::Hash for Bytes { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - self.0.as_ref().as_ref().hash(state); - } -} - -impl AsRef<[u8]> for Bytes { - fn as_ref(&self) -> &[u8] { - self.0.as_ref().as_ref() - } -} - -impl std::ops::Deref for Bytes { - type Target = [u8]; - - fn deref(&self) -> &[u8] { - self.0.as_ref().as_ref() - } -} - -/// The data of a raster image. -#[derive(Clone, Hash)] -pub enum Data { - /// File data - Path(PathBuf), - - /// In-memory data - Bytes(Bytes), - - /// Decoded image pixels in RGBA format. - Rgba { - /// The width of the image. - width: u32, - /// The height of the image. - height: u32, - /// The pixels. - pixels: Bytes, - }, -} - -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::Rgba { width, height, .. } => { - write!(f, "Pixels({width} * {height})") - } - } - } -} - -/// A [`Renderer`] that can render raster graphics. -/// -/// [renderer]: crate::renderer -pub trait Renderer: crate::Renderer { - /// The image Handle to be displayed. Iced exposes its own default implementation of a [`Handle`] - /// - /// [`Handle`]: Self::Handle - type Handle: Clone + Hash; - - /// Returns the dimensions of an image for the given [`Handle`]. - fn dimensions(&self, handle: &Self::Handle) -> Size<u32>; - - /// Draws an image with the given [`Handle`] and inside the provided - /// `bounds`. - fn draw(&mut self, handle: Self::Handle, bounds: Rectangle); -} diff --git a/native/src/layout.rs b/native/src/layout.rs deleted file mode 100644 index 04954fb9..00000000 --- a/native/src/layout.rs +++ /dev/null @@ -1,65 +0,0 @@ -//! Position your widgets properly. -mod limits; -mod node; - -pub mod flex; - -pub use limits::Limits; -pub use node::Node; - -use crate::{Point, Rectangle, Vector}; - -/// The bounds of a [`Node`] and its children, using absolute coordinates. -#[derive(Debug, Clone, Copy)] -pub struct Layout<'a> { - position: Point, - node: &'a Node, -} - -impl<'a> Layout<'a> { - /// Creates a new [`Layout`] for the given [`Node`] at the origin. - pub fn new(node: &'a Node) -> Self { - Self::with_offset(Vector::new(0.0, 0.0), node) - } - - /// Creates a new [`Layout`] for the given [`Node`] with the provided offset - /// from the origin. - pub fn with_offset(offset: Vector, node: &'a Node) -> Self { - let bounds = node.bounds(); - - Self { - position: Point::new(bounds.x, bounds.y) + offset, - node, - } - } - - /// Returns the position of the [`Layout`]. - pub fn position(&self) -> Point { - self.position - } - - /// Returns the bounds of the [`Layout`]. - /// - /// The returned [`Rectangle`] describes the position and size of a - /// [`Node`]. - pub fn bounds(&self) -> Rectangle { - let bounds = self.node.bounds(); - - Rectangle { - x: self.position.x, - y: self.position.y, - width: bounds.width, - height: bounds.height, - } - } - - /// Returns an iterator over the [`Layout`] of the children of a [`Node`]. - pub fn children(self) -> impl Iterator<Item = Layout<'a>> { - self.node.children().iter().map(move |node| { - Layout::with_offset( - Vector::new(self.position.x, self.position.y), - node, - ) - }) - } -} diff --git a/native/src/layout/DRUID_LICENSE b/native/src/layout/DRUID_LICENSE deleted file mode 100644 index d6456956..00000000 --- a/native/src/layout/DRUID_LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/native/src/layout/flex.rs b/native/src/layout/flex.rs deleted file mode 100644 index 5d70c2fc..00000000 --- a/native/src/layout/flex.rs +++ /dev/null @@ -1,232 +0,0 @@ -//! Distribute elements using a flex-based layout. -// This code is heavily inspired by the [`druid`] codebase. -// -// [`druid`]: https://github.com/xi-editor/druid -// -// Copyright 2018 The xi-editor Authors, Héctor Ramón -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -use crate::Element; - -use crate::layout::{Limits, Node}; -use crate::{Alignment, Padding, Point, Size}; - -/// The main axis of a flex layout. -#[derive(Debug)] -pub enum Axis { - /// The horizontal axis - Horizontal, - - /// The vertical axis - Vertical, -} - -impl Axis { - fn main(&self, size: Size) -> f32 { - match self { - Axis::Horizontal => size.width, - Axis::Vertical => size.height, - } - } - - fn cross(&self, size: Size) -> f32 { - match self { - Axis::Horizontal => size.height, - Axis::Vertical => size.width, - } - } - - fn pack(&self, main: f32, cross: f32) -> (f32, f32) { - match self { - Axis::Horizontal => (main, cross), - Axis::Vertical => (cross, main), - } - } -} - -/// Computes the flex layout with the given axis and limits, applying spacing, -/// padding and alignment to the items as needed. -/// -/// It returns a new layout [`Node`]. -pub fn resolve<Message, Renderer>( - axis: Axis, - renderer: &Renderer, - limits: &Limits, - padding: Padding, - spacing: f32, - align_items: Alignment, - items: &[Element<'_, Message, Renderer>], -) -> Node -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 fill_sum = 0; - 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()); - - if align_items == Alignment::Fill { - let mut fill_cross = axis.cross(limits.min()); - - items.iter().for_each(|child| { - let cross_fill_factor = match axis { - Axis::Horizontal => child.as_widget().height(), - Axis::Vertical => child.as_widget().width(), - } - .fill_factor(); - - if cross_fill_factor == 0 { - 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.as_widget().layout(renderer, &child_limits); - let size = layout.size(); - - fill_cross = fill_cross.max(axis.cross(size)); - } - }); - - cross = fill_cross; - } - - for (i, child) in items.iter().enumerate() { - let fill_factor = match axis { - Axis::Horizontal => child.as_widget().width(), - Axis::Vertical => child.as_widget().height(), - } - .fill_factor(); - - if fill_factor == 0 { - let (min_width, min_height) = if align_items == Alignment::Fill { - axis.pack(0.0, cross) - } else { - axis.pack(0.0, 0.0) - }; - - let (max_width, max_height) = if align_items == Alignment::Fill { - axis.pack(available, cross) - } else { - axis.pack(available, max_cross) - }; - - let child_limits = Limits::new( - Size::new(min_width, min_height), - Size::new(max_width, max_height), - ); - - let layout = child.as_widget().layout(renderer, &child_limits); - let size = layout.size(); - - available -= axis.main(size); - - if align_items != Alignment::Fill { - cross = cross.max(axis.cross(size)); - } - - nodes[i] = layout; - } else { - fill_sum += fill_factor; - } - } - - let remaining = available.max(0.0); - - for (i, child) in items.iter().enumerate() { - let fill_factor = match axis { - Axis::Horizontal => child.as_widget().width(), - Axis::Vertical => child.as_widget().height(), - } - .fill_factor(); - - if fill_factor != 0 { - let max_main = remaining * fill_factor as f32 / fill_sum as f32; - let min_main = if max_main.is_infinite() { - 0.0 - } else { - max_main - }; - - let (min_width, min_height) = if align_items == Alignment::Fill { - axis.pack(min_main, cross) - } else { - axis.pack(min_main, axis.cross(limits.min())) - }; - - let (max_width, max_height) = if align_items == Alignment::Fill { - axis.pack(max_main, cross) - } else { - axis.pack(max_main, max_cross) - }; - - let child_limits = Limits::new( - Size::new(min_width, min_height), - Size::new(max_width, max_height), - ); - - let layout = child.as_widget().layout(renderer, &child_limits); - - if align_items != Alignment::Fill { - cross = cross.max(axis.cross(layout.size())); - } - - nodes[i] = layout; - } - } - - let pad = axis.pack(padding.left, padding.top); - let mut main = pad.0; - - for (i, node) in nodes.iter_mut().enumerate() { - if i > 0 { - main += spacing; - } - - let (x, y) = axis.pack(main, pad.1); - - node.move_to(Point::new(x, y)); - - match axis { - Axis::Horizontal => { - node.align( - Alignment::Start, - align_items, - Size::new(0.0, cross), - ); - } - Axis::Vertical => { - node.align( - align_items, - Alignment::Start, - Size::new(cross, 0.0), - ); - } - } - - let size = node.size(); - - main += axis.main(size); - } - - let (width, height) = axis.pack(main - pad.0, cross); - let size = limits.resolve(Size::new(width, height)); - - Node::with_children(size.pad(padding), nodes) -} diff --git a/native/src/layout/limits.rs b/native/src/layout/limits.rs deleted file mode 100644 index 5d3c1556..00000000 --- a/native/src/layout/limits.rs +++ /dev/null @@ -1,163 +0,0 @@ -#![allow(clippy::manual_clamp)] -use crate::{Length, Padding, Size}; - -/// A set of size constraints for layouting. -#[derive(Debug, Clone, Copy)] -pub struct Limits { - min: Size, - max: Size, - fill: Size, -} - -impl Limits { - /// No limits - pub const NONE: Limits = Limits { - min: Size::ZERO, - max: Size::INFINITY, - fill: Size::INFINITY, - }; - - /// Creates new [`Limits`] with the given minimum and maximum [`Size`]. - pub const fn new(min: Size, max: Size) -> Limits { - Limits { - min, - max, - fill: Size::INFINITY, - } - } - - /// Returns the minimum [`Size`] of the [`Limits`]. - pub fn min(&self) -> Size { - self.min - } - - /// Returns the maximum [`Size`] of the [`Limits`]. - pub fn max(&self) -> Size { - self.max - } - - /// Returns the fill [`Size`] of the [`Limits`]. - pub fn fill(&self) -> Size { - self.fill - } - - /// Applies a width constraint to the current [`Limits`]. - pub fn width(mut self, width: impl Into<Length>) -> Limits { - match width.into() { - Length::Shrink => { - self.fill.width = self.min.width; - } - Length::Fill | Length::FillPortion(_) => { - self.fill.width = self.fill.width.min(self.max.width); - } - Length::Fixed(amount) => { - let new_width = amount.min(self.max.width).max(self.min.width); - - self.min.width = new_width; - self.max.width = new_width; - self.fill.width = new_width; - } - } - - self - } - - /// Applies a height constraint to the current [`Limits`]. - pub fn height(mut self, height: impl Into<Length>) -> Limits { - match height.into() { - Length::Shrink => { - self.fill.height = self.min.height; - } - Length::Fill | Length::FillPortion(_) => { - self.fill.height = self.fill.height.min(self.max.height); - } - Length::Fixed(amount) => { - let new_height = - amount.min(self.max.height).max(self.min.height); - - self.min.height = new_height; - self.max.height = new_height; - self.fill.height = new_height; - } - } - - self - } - - /// Applies a minimum width constraint to the current [`Limits`]. - pub fn min_width(mut self, min_width: f32) -> Limits { - self.min.width = self.min.width.max(min_width).min(self.max.width); - - self - } - - /// Applies a maximum width constraint to the current [`Limits`]. - pub fn max_width(mut self, max_width: f32) -> Limits { - self.max.width = self.max.width.min(max_width).max(self.min.width); - - self - } - - /// Applies a minimum height constraint to the current [`Limits`]. - pub fn min_height(mut self, min_height: f32) -> Limits { - self.min.height = self.min.height.max(min_height).min(self.max.height); - - self - } - - /// Applies a maximum height constraint to the current [`Limits`]. - pub fn max_height(mut self, max_height: f32) -> Limits { - self.max.height = self.max.height.min(max_height).max(self.min.height); - - self - } - - /// Shrinks the current [`Limits`] to account for the given padding. - pub fn pad(&self, padding: Padding) -> Limits { - self.shrink(Size::new(padding.horizontal(), padding.vertical())) - } - - /// Shrinks the current [`Limits`] by the given [`Size`]. - pub fn shrink(&self, size: Size) -> Limits { - let min = Size::new( - (self.min().width - size.width).max(0.0), - (self.min().height - size.height).max(0.0), - ); - - let max = Size::new( - (self.max().width - size.width).max(0.0), - (self.max().height - size.height).max(0.0), - ); - - let fill = Size::new( - (self.fill.width - size.width).max(0.0), - (self.fill.height - size.height).max(0.0), - ); - - Limits { min, max, fill } - } - - /// Removes the minimum width constraint for the current [`Limits`]. - pub fn loose(&self) -> Limits { - Limits { - min: Size::ZERO, - max: self.max, - fill: self.fill, - } - } - - /// Computes the resulting [`Size`] that fits the [`Limits`] given the - /// intrinsic size of some content. - pub fn resolve(&self, intrinsic_size: Size) -> Size { - Size::new( - intrinsic_size - .width - .min(self.max.width) - .max(self.fill.width), - intrinsic_size - .height - .min(self.max.height) - .max(self.fill.height), - ) - } -} diff --git a/native/src/layout/node.rs b/native/src/layout/node.rs deleted file mode 100644 index e0c7dcb2..00000000 --- a/native/src/layout/node.rs +++ /dev/null @@ -1,91 +0,0 @@ -use crate::{Alignment, Point, Rectangle, Size, Vector}; - -/// The bounds of an element and its children. -#[derive(Debug, Clone, Default)] -pub struct Node { - bounds: Rectangle, - children: Vec<Node>, -} - -impl Node { - /// Creates a new [`Node`] with the given [`Size`]. - pub const fn new(size: Size) -> Self { - Self::with_children(size, Vec::new()) - } - - /// Creates a new [`Node`] with the given [`Size`] and children. - pub const fn with_children(size: Size, children: Vec<Node>) -> Self { - Node { - bounds: Rectangle { - x: 0.0, - y: 0.0, - width: size.width, - height: size.height, - }, - children, - } - } - - /// Returns the [`Size`] of the [`Node`]. - pub fn size(&self) -> Size { - Size::new(self.bounds.width, self.bounds.height) - } - - /// Returns the bounds of the [`Node`]. - pub fn bounds(&self) -> Rectangle { - self.bounds - } - - /// Returns the children of the [`Node`]. - pub fn children(&self) -> &[Node] { - &self.children - } - - /// Aligns the [`Node`] in the given space. - pub fn align( - &mut self, - horizontal_alignment: Alignment, - vertical_alignment: Alignment, - space: Size, - ) { - match horizontal_alignment { - Alignment::Start => {} - Alignment::Center => { - self.bounds.x += (space.width - self.bounds.width) / 2.0; - } - Alignment::End => { - self.bounds.x += space.width - self.bounds.width; - } - Alignment::Fill => { - self.bounds.width = space.width; - } - } - - match vertical_alignment { - Alignment::Start => {} - Alignment::Center => { - self.bounds.y += (space.height - self.bounds.height) / 2.0; - } - Alignment::End => { - self.bounds.y += space.height - self.bounds.height; - } - Alignment::Fill => { - self.bounds.height = space.height; - } - } - } - - /// Moves the [`Node`] to the given position. - pub fn move_to(&mut self, position: Point) { - self.bounds.x = position.x; - self.bounds.y = position.y; - } - - /// Translates the [`Node`] by the given translation. - pub fn translate(self, translation: Vector) -> Self { - Self { - bounds: self.bounds + translation, - ..self - } - } -} diff --git a/native/src/lib.rs b/native/src/lib.rs index c98827e7..0fc4f324 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -42,32 +42,19 @@ clippy::useless_conversion )] #![forbid(unsafe_code, rust_2018_idioms)] -#![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_cfg))] pub mod clipboard; pub mod command; -pub mod event; pub mod font; -pub mod image; pub mod keyboard; -pub mod layout; -pub mod mouse; -pub mod overlay; pub mod program; -pub mod renderer; pub mod subscription; -pub mod svg; pub mod system; -pub mod text; -pub mod touch; pub mod user_interface; pub mod widget; pub mod window; -mod element; -mod hasher; mod runtime; -mod shell; // We disable debug capabilities on release builds unless the `debug` feature // is explicitly enabled. @@ -78,35 +65,13 @@ mod debug; #[path = "debug/null.rs"] mod debug; -pub use iced_core::alignment; -pub use iced_core::gradient; -pub use iced_core::time; -pub use iced_core::{ - color, Alignment, Background, Color, ContentFit, Length, Padding, Pixels, - Point, Rectangle, Size, Vector, -}; -pub use iced_futures::{executor, futures}; -pub use iced_style::application; -pub use iced_style::theme; +pub use iced_core as core; +pub use iced_futures as futures; -#[doc(no_inline)] -pub use executor::Executor; - -pub use clipboard::Clipboard; pub use command::Command; pub use debug::Debug; -pub use element::Element; -pub use event::Event; pub use font::Font; -pub use gradient::Gradient; -pub use hasher::Hasher; -pub use layout::Layout; -pub use overlay::Overlay; pub use program::Program; -pub use renderer::Renderer; pub use runtime::Runtime; -pub use shell::Shell; pub use subscription::Subscription; -pub use theme::Theme; pub use user_interface::UserInterface; -pub use widget::Widget; diff --git a/native/src/mouse.rs b/native/src/mouse.rs deleted file mode 100644 index 9ee406cf..00000000 --- a/native/src/mouse.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Track mouse events. - -pub mod click; - -pub use click::Click; -pub use iced_core::mouse::*; diff --git a/native/src/mouse/click.rs b/native/src/mouse/click.rs deleted file mode 100644 index 4a7d796c..00000000 --- a/native/src/mouse/click.rs +++ /dev/null @@ -1,76 +0,0 @@ -//! Track mouse clicks. -use crate::time::Instant; -use crate::Point; - -/// A mouse click. -#[derive(Debug, Clone, Copy)] -pub struct Click { - kind: Kind, - position: Point, - time: Instant, -} - -/// The kind of mouse click. -#[derive(Debug, Clone, Copy)] -pub enum Kind { - /// A single click - Single, - - /// A double click - Double, - - /// A triple click - Triple, -} - -impl Kind { - fn next(&self) -> Kind { - match self { - Kind::Single => Kind::Double, - Kind::Double => Kind::Triple, - Kind::Triple => Kind::Double, - } - } -} - -impl Click { - /// Creates a new [`Click`] with the given position and previous last - /// [`Click`]. - pub fn new(position: Point, previous: Option<Click>) -> Click { - let time = Instant::now(); - - let kind = if let Some(previous) = previous { - if previous.is_consecutive(position, time) { - previous.kind.next() - } else { - Kind::Single - } - } else { - Kind::Single - }; - - Click { - kind, - position, - time, - } - } - - /// Returns the [`Kind`] of [`Click`]. - pub fn kind(&self) -> Kind { - self.kind - } - - fn is_consecutive(&self, new_position: Point, time: Instant) -> bool { - let duration = if time > self.time { - Some(time - self.time) - } else { - None - }; - - self.position == new_position - && duration - .map(|duration| duration.as_millis() <= 300) - .unwrap_or(false) - } -} diff --git a/native/src/overlay.rs b/native/src/overlay.rs deleted file mode 100644 index 6cada416..00000000 --- a/native/src/overlay.rs +++ /dev/null @@ -1,125 +0,0 @@ -//! Display interactive elements on top of other widgets. -mod element; -mod group; - -pub mod menu; - -pub use element::Element; -pub use group::Group; -pub use menu::Menu; - -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::widget; -use crate::widget::Tree; -use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size}; - -/// An interactive component that can be displayed on top of other widgets. -pub trait Overlay<Message, Renderer> -where - Renderer: crate::Renderer, -{ - /// Returns the layout [`Node`] of the [`Overlay`]. - /// - /// This [`Node`] is used by the runtime to compute the [`Layout`] of the - /// user interface. - /// - /// [`Node`]: layout::Node - fn layout( - &self, - renderer: &Renderer, - bounds: Size, - position: Point, - ) -> layout::Node; - - /// Draws the [`Overlay`] using the associated `Renderer`. - fn draw( - &self, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - ); - - /// Applies a [`widget::Operation`] to the [`Overlay`]. - fn operate( - &mut self, - _layout: Layout<'_>, - _renderer: &Renderer, - _operation: &mut dyn widget::Operation<Message>, - ) { - } - - /// Processes a runtime [`Event`]. - /// - /// It receives: - /// * an [`Event`] describing user interaction - /// * the computed [`Layout`] of the [`Overlay`] - /// * the current cursor position - /// * a mutable `Message` list, allowing the [`Overlay`] to produce - /// new messages based on user interaction. - /// * the `Renderer` - /// * a [`Clipboard`], if available - /// - /// By default, it does nothing. - fn on_event( - &mut self, - _event: Event, - _layout: Layout<'_>, - _cursor_position: Point, - _renderer: &Renderer, - _clipboard: &mut dyn Clipboard, - _shell: &mut Shell<'_, Message>, - ) -> event::Status { - event::Status::Ignored - } - - /// Returns the current [`mouse::Interaction`] of the [`Overlay`]. - /// - /// By default, it returns [`mouse::Interaction::Idle`]. - fn mouse_interaction( - &self, - _layout: Layout<'_>, - _cursor_position: Point, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - mouse::Interaction::Idle - } - - /// Returns true if the cursor is over the [`Overlay`]. - /// - /// By default, it returns true if the bounds of the `layout` contain - /// the `cursor_position`. - fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { - layout.bounds().contains(cursor_position) - } -} - -/// Returns a [`Group`] of overlay [`Element`] children. -/// -/// This method will generally only be used by advanced users that are -/// implementing the [`Widget`](crate::Widget) trait. -pub fn from_children<'a, Message, Renderer>( - children: &'a mut [crate::Element<'_, Message, Renderer>], - tree: &'a mut Tree, - layout: Layout<'_>, - renderer: &Renderer, -) -> Option<Element<'a, Message, Renderer>> -where - Renderer: crate::Renderer, -{ - let children = children - .iter_mut() - .zip(&mut tree.children) - .zip(layout.children()) - .filter_map(|((child, state), layout)| { - child.as_widget_mut().overlay(state, layout, renderer) - }) - .collect::<Vec<_>>(); - - (!children.is_empty()).then(|| Group::with_children(children).overlay()) -} diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs deleted file mode 100644 index 237d25d1..00000000 --- a/native/src/overlay/element.rs +++ /dev/null @@ -1,270 +0,0 @@ -pub use crate::Overlay; - -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::widget; -use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector}; - -use std::any::Any; - -/// A generic [`Overlay`]. -#[allow(missing_debug_implementations)] -pub struct Element<'a, Message, Renderer> { - position: Point, - overlay: Box<dyn Overlay<Message, Renderer> + 'a>, -} - -impl<'a, Message, Renderer> Element<'a, Message, Renderer> -where - Renderer: crate::Renderer, -{ - /// Creates a new [`Element`] containing the given [`Overlay`]. - pub fn new( - position: Point, - overlay: Box<dyn Overlay<Message, Renderer> + 'a>, - ) -> Self { - Self { position, overlay } - } - - /// Returns the position of the [`Element`]. - pub fn position(&self) -> Point { - self.position - } - - /// Translates the [`Element`]. - pub fn translate(mut self, translation: Vector) -> Self { - self.position = self.position + translation; - self - } - - /// Applies a transformation to the produced message of the [`Element`]. - pub fn map<B>(self, f: &'a dyn Fn(Message) -> B) -> Element<'a, B, Renderer> - where - Message: 'a, - Renderer: 'a, - B: 'a, - { - Element { - position: self.position, - overlay: Box::new(Map::new(self.overlay, f)), - } - } - - /// Computes the layout of the [`Element`] in the given bounds. - pub fn layout( - &self, - renderer: &Renderer, - bounds: Size, - translation: Vector, - ) -> layout::Node { - self.overlay - .layout(renderer, bounds, self.position + translation) - } - - /// Processes a runtime [`Event`]. - pub fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - self.overlay.on_event( - event, - layout, - cursor_position, - renderer, - clipboard, - shell, - ) - } - - /// Returns the current [`mouse::Interaction`] of the [`Element`]. - pub fn mouse_interaction( - &self, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.overlay.mouse_interaction( - layout, - cursor_position, - viewport, - renderer, - ) - } - - /// Draws the [`Element`] and its children using the given [`Layout`]. - pub fn draw( - &self, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - ) { - self.overlay - .draw(renderer, theme, style, layout, cursor_position) - } - - /// Applies a [`widget::Operation`] to the [`Element`]. - pub fn operate( - &mut self, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn widget::Operation<Message>, - ) { - self.overlay.operate(layout, renderer, operation); - } - - /// Returns true if the cursor is over the [`Element`]. - pub fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { - self.overlay.is_over(layout, cursor_position) - } -} - -struct Map<'a, A, B, Renderer> { - content: Box<dyn Overlay<A, Renderer> + 'a>, - mapper: &'a dyn Fn(A) -> B, -} - -impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> { - pub fn new( - content: Box<dyn Overlay<A, Renderer> + 'a>, - mapper: &'a dyn Fn(A) -> B, - ) -> Map<'a, A, B, Renderer> { - Map { content, mapper } - } -} - -impl<'a, A, B, Renderer> Overlay<B, Renderer> for Map<'a, A, B, Renderer> -where - Renderer: crate::Renderer, -{ - fn layout( - &self, - renderer: &Renderer, - bounds: Size, - position: Point, - ) -> layout::Node { - self.content.layout(renderer, bounds, position) - } - - fn operate( - &mut self, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn widget::Operation<B>, - ) { - struct MapOperation<'a, B> { - operation: &'a mut dyn widget::Operation<B>, - } - - impl<'a, T, B> widget::Operation<T> for MapOperation<'a, B> { - fn container( - &mut self, - id: Option<&widget::Id>, - operate_on_children: &mut dyn FnMut( - &mut dyn widget::Operation<T>, - ), - ) { - self.operation.container(id, &mut |operation| { - operate_on_children(&mut MapOperation { operation }); - }); - } - - fn focusable( - &mut self, - state: &mut dyn widget::operation::Focusable, - id: Option<&widget::Id>, - ) { - self.operation.focusable(state, id); - } - - fn scrollable( - &mut self, - state: &mut dyn widget::operation::Scrollable, - id: Option<&widget::Id>, - ) { - self.operation.scrollable(state, id); - } - - fn text_input( - &mut self, - state: &mut dyn widget::operation::TextInput, - id: Option<&widget::Id>, - ) { - self.operation.text_input(state, id) - } - - fn custom(&mut self, state: &mut dyn Any, id: Option<&widget::Id>) { - self.operation.custom(state, id); - } - } - - self.content - .operate(layout, renderer, &mut MapOperation { operation }); - } - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, B>, - ) -> event::Status { - let mut local_messages = Vec::new(); - let mut local_shell = Shell::new(&mut local_messages); - - let event_status = self.content.on_event( - event, - layout, - cursor_position, - renderer, - clipboard, - &mut local_shell, - ); - - shell.merge(local_shell, self.mapper); - - event_status - } - - fn mouse_interaction( - &self, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.content.mouse_interaction( - layout, - cursor_position, - viewport, - renderer, - ) - } - - fn draw( - &self, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - ) { - self.content - .draw(renderer, theme, style, layout, cursor_position) - } - - fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { - self.content.is_over(layout, cursor_position) - } -} diff --git a/native/src/overlay/group.rs b/native/src/overlay/group.rs deleted file mode 100644 index 1126f0cf..00000000 --- a/native/src/overlay/group.rs +++ /dev/null @@ -1,174 +0,0 @@ -use iced_core::{Point, Rectangle, Size}; - -use crate::event; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::widget; -use crate::{Clipboard, Event, Layout, Overlay, Shell}; - -/// An [`Overlay`] container that displays multiple overlay [`overlay::Element`] -/// children. -#[allow(missing_debug_implementations)] -pub struct Group<'a, Message, Renderer> { - children: Vec<overlay::Element<'a, Message, Renderer>>, -} - -impl<'a, Message, Renderer> Group<'a, Message, Renderer> -where - Renderer: 'a + crate::Renderer, - Message: 'a, -{ - /// Creates an empty [`Group`]. - pub fn new() -> Self { - Self::default() - } - - /// Creates a [`Group`] with the given elements. - pub fn with_children( - children: Vec<overlay::Element<'a, Message, Renderer>>, - ) -> Self { - Group { children } - } - - /// Adds an [`overlay::Element`] to the [`Group`]. - pub fn push( - mut self, - child: impl Into<overlay::Element<'a, Message, Renderer>>, - ) -> Self { - self.children.push(child.into()); - self - } - - /// Turns the [`Group`] into an overlay [`overlay::Element`]. - pub fn overlay(self) -> overlay::Element<'a, Message, Renderer> { - overlay::Element::new(Point::ORIGIN, Box::new(self)) - } -} - -impl<'a, Message, Renderer> Default for Group<'a, Message, Renderer> -where - Renderer: 'a + crate::Renderer, - Message: 'a, -{ - fn default() -> Self { - Self::with_children(Vec::new()) - } -} - -impl<'a, Message, Renderer> Overlay<Message, Renderer> - for Group<'a, Message, Renderer> -where - Renderer: crate::Renderer, -{ - fn layout( - &self, - renderer: &Renderer, - bounds: Size, - position: Point, - ) -> layout::Node { - let translation = position - Point::ORIGIN; - - layout::Node::with_children( - bounds, - self.children - .iter() - .map(|child| child.layout(renderer, bounds, translation)) - .collect(), - ) - } - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - self.children - .iter_mut() - .zip(layout.children()) - .map(|(child, layout)| { - child.on_event( - event.clone(), - layout, - cursor_position, - renderer, - clipboard, - shell, - ) - }) - .fold(event::Status::Ignored, event::Status::merge) - } - - fn draw( - &self, - renderer: &mut Renderer, - theme: &<Renderer as crate::Renderer>::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - ) { - for (child, layout) in self.children.iter().zip(layout.children()) { - child.draw(renderer, theme, style, layout, cursor_position); - } - } - - fn mouse_interaction( - &self, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.children - .iter() - .zip(layout.children()) - .map(|(child, layout)| { - child.mouse_interaction( - layout, - cursor_position, - viewport, - renderer, - ) - }) - .max() - .unwrap_or_default() - } - - fn operate( - &mut self, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn widget::Operation<Message>, - ) { - operation.container(None, &mut |operation| { - self.children.iter_mut().zip(layout.children()).for_each( - |(child, layout)| { - child.operate(layout, renderer, operation); - }, - ) - }); - } - - fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { - self.children - .iter() - .zip(layout.children()) - .any(|(child, layout)| child.is_over(layout, cursor_position)) - } -} - -impl<'a, Message, Renderer> From<Group<'a, Message, Renderer>> - for overlay::Element<'a, Message, Renderer> -where - Renderer: 'a + crate::Renderer, - Message: 'a, -{ - fn from(group: Group<'a, Message, Renderer>) -> Self { - group.overlay() - } -} diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs deleted file mode 100644 index bd58a122..00000000 --- a/native/src/overlay/menu.rs +++ /dev/null @@ -1,519 +0,0 @@ -//! Build and show dropdown menus. -use crate::alignment; -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::text::{self, Text}; -use crate::touch; -use crate::widget::container::{self, Container}; -use crate::widget::scrollable::{self, Scrollable}; -use crate::widget::Tree; -use crate::{ - Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, - Rectangle, Shell, Size, Vector, Widget, -}; - -pub use iced_style::menu::{Appearance, StyleSheet}; - -/// A list of selectable options. -#[allow(missing_debug_implementations)] -pub struct Menu<'a, T, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - state: &'a mut State, - options: &'a [T], - hovered_option: &'a mut Option<usize>, - last_selection: &'a mut Option<T>, - width: f32, - padding: Padding, - text_size: Option<f32>, - font: Option<Renderer::Font>, - style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, T, Renderer> Menu<'a, T, Renderer> -where - T: ToString + Clone, - Renderer: text::Renderer + 'a, - Renderer::Theme: - StyleSheet + container::StyleSheet + scrollable::StyleSheet, -{ - /// Creates a new [`Menu`] with the given [`State`], a list of options, and - /// the message to produced when an option is selected. - pub fn new( - state: &'a mut State, - options: &'a [T], - hovered_option: &'a mut Option<usize>, - last_selection: &'a mut Option<T>, - ) -> Self { - Menu { - state, - options, - hovered_option, - last_selection, - width: 0.0, - padding: Padding::ZERO, - text_size: None, - font: None, - style: Default::default(), - } - } - - /// Sets the width of the [`Menu`]. - pub fn width(mut self, width: f32) -> Self { - self.width = width; - self - } - - /// Sets the [`Padding`] of the [`Menu`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the text size of the [`Menu`]. - pub fn text_size(mut self, text_size: impl Into<Pixels>) -> Self { - self.text_size = Some(text_size.into().0); - self - } - - /// Sets the font of the [`Menu`]. - pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self { - self.font = Some(font.into()); - self - } - - /// Sets the style of the [`Menu`]. - pub fn style( - mut self, - style: impl Into<<Renderer::Theme as StyleSheet>::Style>, - ) -> Self { - self.style = style.into(); - self - } - - /// Turns the [`Menu`] into an overlay [`Element`] at the given target - /// position. - /// - /// The `target_height` will be used to display the menu either on top - /// of the target or under it, depending on the screen position and the - /// dimensions of the [`Menu`]. - pub fn overlay<Message: 'a>( - self, - position: Point, - target_height: f32, - ) -> overlay::Element<'a, Message, Renderer> { - overlay::Element::new( - position, - Box::new(Overlay::new(self, target_height)), - ) - } -} - -/// The local state of a [`Menu`]. -#[derive(Debug)] -pub struct State { - tree: Tree, -} - -impl State { - /// Creates a new [`State`] for a [`Menu`]. - pub fn new() -> Self { - Self { - tree: Tree::empty(), - } - } -} - -impl Default for State { - fn default() -> Self { - Self::new() - } -} - -struct Overlay<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet + container::StyleSheet, -{ - state: &'a mut Tree, - container: Container<'a, Message, Renderer>, - width: f32, - target_height: f32, - style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, Message, Renderer> Overlay<'a, Message, Renderer> -where - Message: 'a, - Renderer: 'a, - Renderer: text::Renderer, - Renderer::Theme: - StyleSheet + container::StyleSheet + scrollable::StyleSheet, -{ - pub fn new<T>(menu: Menu<'a, T, Renderer>, target_height: f32) -> Self - where - T: Clone + ToString, - { - let Menu { - state, - options, - hovered_option, - last_selection, - width, - padding, - font, - text_size, - style, - } = menu; - - let container = Container::new(Scrollable::new(List { - options, - hovered_option, - last_selection, - font, - text_size, - padding, - style: style.clone(), - })); - - state.tree.diff(&container as &dyn Widget<_, _>); - - Self { - state: &mut state.tree, - container, - width, - target_height, - style, - } - } -} - -impl<'a, Message, Renderer> crate::Overlay<Message, Renderer> - for Overlay<'a, Message, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet + container::StyleSheet, -{ - fn layout( - &self, - renderer: &Renderer, - bounds: Size, - position: Point, - ) -> layout::Node { - let space_below = bounds.height - (position.y + self.target_height); - let space_above = position.y; - - let limits = layout::Limits::new( - Size::ZERO, - Size::new( - bounds.width - position.x, - if space_below > space_above { - space_below - } else { - space_above - }, - ), - ) - .width(self.width); - - let mut node = self.container.layout(renderer, &limits); - - node.move_to(if space_below > space_above { - position + Vector::new(0.0, self.target_height) - } else { - position - Vector::new(0.0, node.size().height) - }); - - node - } - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - self.container.on_event( - self.state, - event, - layout, - cursor_position, - renderer, - clipboard, - shell, - ) - } - - fn mouse_interaction( - &self, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.container.mouse_interaction( - self.state, - layout, - cursor_position, - viewport, - renderer, - ) - } - - fn draw( - &self, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - ) { - let appearance = theme.appearance(&self.style); - let bounds = layout.bounds(); - - renderer.fill_quad( - renderer::Quad { - bounds, - border_color: appearance.border_color, - border_width: appearance.border_width, - border_radius: appearance.border_radius.into(), - }, - appearance.background, - ); - - self.container.draw( - self.state, - renderer, - theme, - style, - layout, - cursor_position, - &bounds, - ); - } -} - -struct List<'a, T, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - options: &'a [T], - hovered_option: &'a mut Option<usize>, - last_selection: &'a mut Option<T>, - padding: Padding, - text_size: Option<f32>, - font: Option<Renderer::Font>, - style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, T, Message, Renderer> Widget<Message, Renderer> - for List<'a, T, Renderer> -where - T: Clone + ToString, - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - fn width(&self) -> Length { - Length::Fill - } - - fn height(&self) -> Length { - Length::Shrink - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - use std::f32; - - let limits = limits.width(Length::Fill).height(Length::Shrink); - let text_size = - self.text_size.unwrap_or_else(|| renderer.default_size()); - - let size = { - let intrinsic = Size::new( - 0.0, - (text_size * 1.2 + self.padding.vertical()) - * self.options.len() as f32, - ); - - limits.resolve(intrinsic) - }; - - layout::Node::new(size) - } - - fn on_event( - &mut self, - _state: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - _clipboard: &mut dyn Clipboard, - _shell: &mut Shell<'_, Message>, - ) -> event::Status { - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { - let bounds = layout.bounds(); - - if bounds.contains(cursor_position) { - if let Some(index) = *self.hovered_option { - if let Some(option) = self.options.get(index) { - *self.last_selection = Some(option.clone()); - } - } - } - } - Event::Mouse(mouse::Event::CursorMoved { .. }) => { - let bounds = layout.bounds(); - - if bounds.contains(cursor_position) { - let text_size = self - .text_size - .unwrap_or_else(|| renderer.default_size()); - - *self.hovered_option = Some( - ((cursor_position.y - bounds.y) - / (text_size * 1.2 + self.padding.vertical())) - as usize, - ); - } - } - Event::Touch(touch::Event::FingerPressed { .. }) => { - let bounds = layout.bounds(); - - if bounds.contains(cursor_position) { - let text_size = self - .text_size - .unwrap_or_else(|| renderer.default_size()); - - *self.hovered_option = Some( - ((cursor_position.y - bounds.y) - / (text_size * 1.2 + self.padding.vertical())) - as usize, - ); - - if let Some(index) = *self.hovered_option { - if let Some(option) = self.options.get(index) { - *self.last_selection = Some(option.clone()); - } - } - } - } - _ => {} - } - - event::Status::Ignored - } - - fn mouse_interaction( - &self, - _state: &Tree, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - let is_mouse_over = layout.bounds().contains(cursor_position); - - if is_mouse_over { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - } - } - - fn draw( - &self, - _state: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - _style: &renderer::Style, - layout: Layout<'_>, - _cursor_position: Point, - viewport: &Rectangle, - ) { - let appearance = theme.appearance(&self.style); - let bounds = layout.bounds(); - - let text_size = - self.text_size.unwrap_or_else(|| renderer.default_size()); - let option_height = - (text_size * 1.2 + self.padding.vertical()) as usize; - - let offset = viewport.y - bounds.y; - let start = (offset / option_height as f32) as usize; - let end = - ((offset + viewport.height) / option_height as f32).ceil() as usize; - - let visible_options = &self.options[start..end.min(self.options.len())]; - - for (i, option) in visible_options.iter().enumerate() { - let i = start + i; - let is_selected = *self.hovered_option == Some(i); - - let bounds = Rectangle { - x: bounds.x, - y: bounds.y + (option_height * i) as f32, - width: bounds.width, - height: text_size * 1.2 + self.padding.vertical(), - }; - - if is_selected { - renderer.fill_quad( - renderer::Quad { - bounds, - border_color: Color::TRANSPARENT, - border_width: 0.0, - border_radius: appearance.border_radius.into(), - }, - appearance.selected_background, - ); - } - - renderer.fill_text(Text { - content: &option.to_string(), - bounds: Rectangle { - x: bounds.x + self.padding.left, - y: bounds.center_y(), - width: f32::INFINITY, - ..bounds - }, - size: text_size, - font: self.font.unwrap_or_else(|| renderer.default_font()), - color: if is_selected { - appearance.selected_text_color - } else { - appearance.text_color - }, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, - }); - } - } -} - -impl<'a, T, Message, Renderer> From<List<'a, T, Renderer>> - for Element<'a, Message, Renderer> -where - T: ToString + Clone, - Message: 'a, - Renderer: 'a + text::Renderer, - Renderer::Theme: StyleSheet, -{ - fn from(list: List<'a, T, Renderer>) -> Self { - Element::new(list) - } -} diff --git a/native/src/program.rs b/native/src/program.rs index 25cab332..44585cc5 100644 --- a/native/src/program.rs +++ b/native/src/program.rs @@ -1,6 +1,8 @@ //! Build interactive programs using The Elm Architecture. -use crate::text; -use crate::{Command, Element, Renderer}; +use crate::Command; + +use iced_core::text; +use iced_core::{Element, Renderer}; mod state; diff --git a/native/src/program/state.rs b/native/src/program/state.rs index 8ae1cacb..2fa9934d 100644 --- a/native/src/program/state.rs +++ b/native/src/program/state.rs @@ -1,9 +1,9 @@ -use crate::application; -use crate::event::{self, Event}; -use crate::mouse; -use crate::renderer; +use crate::core::event::{self, Event}; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::{Clipboard, Point, Size}; use crate::user_interface::{self, UserInterface}; -use crate::{Clipboard, Command, Debug, Point, Program, Size}; +use crate::{Command, Debug, Program}; /// The execution state of a [`Program`]. It leverages caching, event /// processing, and rendering primitive storage. @@ -22,7 +22,6 @@ where impl<P> State<P> where P: Program + 'static, - <P::Renderer as crate::Renderer>::Theme: application::StyleSheet, { /// Creates a new [`State`] with the provided [`Program`], initializing its /// primitive with the given logical bounds and renderer. @@ -91,7 +90,7 @@ where bounds: Size, cursor_position: Point, renderer: &mut P::Renderer, - theme: &<P::Renderer as crate::Renderer>::Theme, + theme: &<P::Renderer as iced_core::Renderer>::Theme, style: &renderer::Style, clipboard: &mut dyn Clipboard, debug: &mut Debug, @@ -182,10 +181,7 @@ fn build_user_interface<'a, P: Program>( renderer: &mut P::Renderer, size: Size, debug: &mut Debug, -) -> UserInterface<'a, P::Message, P::Renderer> -where - <P::Renderer as crate::Renderer>::Theme: application::StyleSheet, -{ +) -> UserInterface<'a, P::Message, P::Renderer> { debug.view_started(); let view = program.view(); debug.view_finished(); diff --git a/native/src/renderer.rs b/native/src/renderer.rs deleted file mode 100644 index 2ac78982..00000000 --- a/native/src/renderer.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! Write your own renderer. -#[cfg(debug_assertions)] -mod null; -#[cfg(debug_assertions)] -pub use null::Null; - -use crate::layout; -use crate::{Background, Color, Element, Rectangle, Vector}; - -/// A component that can be used by widgets to draw themselves on a screen. -pub trait Renderer: Sized { - /// The supported theme of the [`Renderer`]. - type Theme; - - /// Lays out the elements of a user interface. - /// - /// You should override this if you need to perform any operations before or - /// after layouting. For instance, trimming the measurements cache. - fn layout<Message>( - &mut self, - element: &Element<'_, Message, Self>, - limits: &layout::Limits, - ) -> layout::Node { - element.as_widget().layout(self, limits) - } - - /// Draws the primitives recorded in the given closure in a new layer. - /// - /// The layer will clip its contents to the provided `bounds`. - fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)); - - /// Applies a `translation` to the primitives recorded in the given closure. - fn with_translation( - &mut self, - translation: Vector, - f: impl FnOnce(&mut Self), - ); - - /// Fills a [`Quad`] with the provided [`Background`]. - fn fill_quad(&mut self, quad: Quad, background: impl Into<Background>); - - /// Clears all of the recorded primitives in the [`Renderer`]. - fn clear(&mut self); -} - -/// A polygon with four sides. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Quad { - /// The bounds of the [`Quad`]. - pub bounds: Rectangle, - - /// The border radius of the [`Quad`]. - pub border_radius: BorderRadius, - - /// The border width of the [`Quad`]. - pub border_width: f32, - - /// The border color of the [`Quad`]. - pub border_color: Color, -} - -/// The border radi for the corners of a graphics primitive in the order: -/// top-left, top-right, bottom-right, bottom-left. -#[derive(Debug, Clone, Copy, PartialEq, Default)] -pub struct BorderRadius([f32; 4]); - -impl From<f32> for BorderRadius { - fn from(w: f32) -> Self { - Self([w; 4]) - } -} - -impl From<[f32; 4]> for BorderRadius { - fn from(radi: [f32; 4]) -> Self { - Self(radi) - } -} - -impl From<BorderRadius> for [f32; 4] { - fn from(radi: BorderRadius) -> Self { - radi.0 - } -} - -/// The styling attributes of a [`Renderer`]. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Style { - /// The text color - pub text_color: Color, -} - -impl Default for Style { - fn default() -> Self { - Style { - text_color: Color::BLACK, - } - } -} diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs deleted file mode 100644 index 150ee786..00000000 --- a/native/src/renderer/null.rs +++ /dev/null @@ -1,82 +0,0 @@ -use crate::renderer::{self, Renderer}; -use crate::text::{self, Text}; -use crate::{Background, Font, Point, Rectangle, Size, Theme, Vector}; - -use std::borrow::Cow; - -/// A renderer that does nothing. -/// -/// It can be useful if you are writing tests! -#[derive(Debug, Clone, Copy, Default)] -pub struct Null; - -impl Null { - /// Creates a new [`Null`] renderer. - pub fn new() -> Null { - Null - } -} - -impl Renderer for Null { - type Theme = Theme; - - fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {} - - fn with_translation( - &mut self, - _translation: Vector, - _f: impl FnOnce(&mut Self), - ) { - } - - fn clear(&mut self) {} - - fn fill_quad( - &mut self, - _quad: renderer::Quad, - _background: impl Into<Background>, - ) { - } -} - -impl text::Renderer for Null { - type Font = Font; - - const ICON_FONT: Font = Font::SansSerif; - const CHECKMARK_ICON: char = '0'; - const ARROW_DOWN_ICON: char = '0'; - - fn default_font(&self) -> Self::Font { - Font::SansSerif - } - - fn default_size(&self) -> f32 { - 16.0 - } - - fn load_font(&mut self, _font: Cow<'static, [u8]>) {} - - fn measure( - &self, - _content: &str, - _size: f32, - _font: Font, - _bounds: Size, - ) -> (f32, f32) { - (0.0, 20.0) - } - - fn hit_test( - &self, - _contents: &str, - _size: f32, - _font: Self::Font, - _bounds: Size, - _point: Point, - _nearest_only: bool, - ) -> Option<text::Hit> { - None - } - - fn fill_text(&mut self, _text: Text<'_, Self::Font>) {} -} diff --git a/native/src/runtime.rs b/native/src/runtime.rs index 5b0a6925..1b81314f 100644 --- a/native/src/runtime.rs +++ b/native/src/runtime.rs @@ -1,6 +1,6 @@ //! Run commands and subscriptions. -use crate::event::{self, Event}; -use crate::Hasher; +use iced_core::event::{self, Event}; +use iced_core::Hasher; /// A native runtime with a generic executor and receiver of results. /// diff --git a/native/src/shell.rs b/native/src/shell.rs deleted file mode 100644 index 74a5c616..00000000 --- a/native/src/shell.rs +++ /dev/null @@ -1,108 +0,0 @@ -use crate::window; - -/// A connection to the state of a shell. -/// -/// A [`Widget`] can leverage a [`Shell`] to trigger changes in an application, -/// like publishing messages or invalidating the current layout. -/// -/// [`Widget`]: crate::Widget -#[derive(Debug)] -pub struct Shell<'a, Message> { - messages: &'a mut Vec<Message>, - redraw_request: Option<window::RedrawRequest>, - is_layout_invalid: bool, - are_widgets_invalid: bool, -} - -impl<'a, Message> Shell<'a, Message> { - /// Creates a new [`Shell`] with the provided buffer of messages. - pub fn new(messages: &'a mut Vec<Message>) -> Self { - Self { - messages, - redraw_request: None, - is_layout_invalid: false, - are_widgets_invalid: false, - } - } - - /// Returns true if the [`Shell`] contains no published messages - pub fn is_empty(&self) -> bool { - self.messages.is_empty() - } - - /// Publish the given `Message` for an application to process it. - pub fn publish(&mut self, message: Message) { - self.messages.push(message); - } - - /// Requests a new frame to be drawn at the given [`Instant`]. - pub fn request_redraw(&mut self, request: window::RedrawRequest) { - match self.redraw_request { - None => { - self.redraw_request = Some(request); - } - Some(current) if request < current => { - self.redraw_request = Some(request); - } - _ => {} - } - } - - /// Returns the requested [`Instant`] a redraw should happen, if any. - pub fn redraw_request(&self) -> Option<window::RedrawRequest> { - self.redraw_request - } - - /// Returns whether the current layout is invalid or not. - pub fn is_layout_invalid(&self) -> bool { - self.is_layout_invalid - } - - /// Invalidates the current application layout. - /// - /// The shell will relayout the application widgets. - pub fn invalidate_layout(&mut self) { - self.is_layout_invalid = true; - } - - /// Triggers the given function if the layout is invalid, cleaning it in the - /// process. - pub fn revalidate_layout(&mut self, f: impl FnOnce()) { - if self.is_layout_invalid { - self.is_layout_invalid = false; - - f() - } - } - - /// Returns whether the widgets of the current application have been - /// invalidated. - pub fn are_widgets_invalid(&self) -> bool { - self.are_widgets_invalid - } - - /// Invalidates the current application widgets. - /// - /// The shell will rebuild and relayout the widget tree. - pub fn invalidate_widgets(&mut self) { - self.are_widgets_invalid = true; - } - - /// Merges the current [`Shell`] with another one by applying the given - /// function to the messages of the latter. - /// - /// This method is useful for composition. - pub fn merge<B>(&mut self, other: Shell<'_, B>, f: impl Fn(B) -> Message) { - self.messages.extend(other.messages.drain(..).map(f)); - - if let Some(at) = other.redraw_request { - self.request_redraw(at); - } - - self.is_layout_invalid = - self.is_layout_invalid || other.is_layout_invalid; - - self.are_widgets_invalid = - self.are_widgets_invalid || other.are_widgets_invalid; - } -} diff --git a/native/src/subscription.rs b/native/src/subscription.rs index 16e78e82..b16bcb03 100644 --- a/native/src/subscription.rs +++ b/native/src/subscription.rs @@ -1,10 +1,9 @@ //! Listen to external events in your application. -use crate::event::{self, Event}; -use crate::window; -use crate::Hasher; - -use iced_futures::futures::{self, Future, Stream}; -use iced_futures::{BoxStream, MaybeSend}; +use crate::core::event::{self, Event}; +use crate::core::window; +use crate::core::Hasher; +use crate::futures::futures::{self, Future, Stream}; +use crate::futures::{BoxStream, MaybeSend}; use std::hash::Hash; @@ -144,7 +143,9 @@ where /// /// ``` /// use iced_native::subscription::{self, Subscription}; -/// use iced_native::futures::channel::mpsc; +/// use iced_native::futures::futures; +/// +/// use futures::channel::mpsc; /// /// pub enum Event { /// Ready(mpsc::Sender<Input>), @@ -174,7 +175,7 @@ where /// (Some(Event::Ready(sender)), State::Ready(receiver)) /// } /// State::Ready(mut receiver) => { -/// use iced_native::futures::StreamExt; +/// use futures::StreamExt; /// /// // Read next input sent from `Application` /// let input = receiver.select_next_some().await; diff --git a/native/src/svg.rs b/native/src/svg.rs deleted file mode 100644 index 9b98877a..00000000 --- a/native/src/svg.rs +++ /dev/null @@ -1,89 +0,0 @@ -//! Load and draw vector graphics. -use crate::{Color, Hasher, Rectangle, Size}; - -use std::borrow::Cow; -use std::hash::{Hash, Hasher as _}; -use std::path::PathBuf; -use std::sync::Arc; - -/// A handle of Svg data. -#[derive(Debug, Clone)] -pub struct Handle { - id: u64, - data: Arc<Data>, -} - -impl Handle { - /// Creates an SVG [`Handle`] pointing to the vector image of the given - /// path. - pub fn from_path(path: impl Into<PathBuf>) -> Handle { - Self::from_data(Data::Path(path.into())) - } - - /// Creates an SVG [`Handle`] from raw bytes containing either an SVG string - /// or gzip compressed data. - /// - /// This is useful if you already have your SVG data in-memory, maybe - /// because you downloaded or generated it procedurally. - pub fn from_memory(bytes: impl Into<Cow<'static, [u8]>>) -> Handle { - Self::from_data(Data::Bytes(bytes.into())) - } - - fn from_data(data: Data) -> Handle { - let mut hasher = Hasher::default(); - data.hash(&mut hasher); - - Handle { - id: hasher.finish(), - data: Arc::new(data), - } - } - - /// Returns the unique identifier of the [`Handle`]. - pub fn id(&self) -> u64 { - self.id - } - - /// Returns a reference to the SVG [`Data`]. - pub fn data(&self) -> &Data { - &self.data - } -} - -impl Hash for Handle { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - self.id.hash(state); - } -} - -/// The data of a vectorial image. -#[derive(Clone, Hash)] -pub enum Data { - /// File data - Path(PathBuf), - - /// In-memory data - /// - /// Can contain an SVG string or a gzip compressed data. - Bytes(Cow<'static, [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(...)"), - } - } -} - -/// A [`Renderer`] that can render vector graphics. -/// -/// [renderer]: crate::renderer -pub trait Renderer: crate::Renderer { - /// Returns the default dimensions of an SVG for the given [`Handle`]. - fn dimensions(&self, handle: &Handle) -> Size<u32>; - - /// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`. - fn draw(&mut self, handle: Handle, color: Option<Color>, bounds: Rectangle); -} diff --git a/native/src/text.rs b/native/src/text.rs deleted file mode 100644 index 4c72abc3..00000000 --- a/native/src/text.rs +++ /dev/null @@ -1,111 +0,0 @@ -//! Draw and interact with text. -use crate::alignment; -use crate::{Color, Point, Rectangle, Size}; - -use std::borrow::Cow; - -/// A paragraph. -#[derive(Debug, Clone, Copy)] -pub struct Text<'a, Font> { - /// The content of the paragraph. - pub content: &'a str, - - /// The bounds of the paragraph. - pub bounds: Rectangle, - - /// The size of the [`Text`]. - pub size: f32, - - /// The color of the [`Text`]. - pub color: Color, - - /// The font of the [`Text`]. - pub font: Font, - - /// The horizontal alignment of the [`Text`]. - pub horizontal_alignment: alignment::Horizontal, - - /// The vertical alignment of the [`Text`]. - pub vertical_alignment: alignment::Vertical, -} - -/// The result of hit testing on text. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Hit { - /// The point was within the bounds of the returned character index. - CharOffset(usize), -} - -impl Hit { - /// Computes the cursor position of the [`Hit`] . - pub fn cursor(self) -> usize { - match self { - Self::CharOffset(i) => i, - } - } -} - -/// A renderer capable of measuring and drawing [`Text`]. -pub trait Renderer: crate::Renderer { - /// The font type used. - type Font: Copy; - - /// The icon font of the backend. - const ICON_FONT: Self::Font; - - /// The `char` representing a ✔ icon in the [`ICON_FONT`]. - /// - /// [`ICON_FONT`]: Self::ICON_FONT - const CHECKMARK_ICON: char; - - /// The `char` representing a ▼ icon in the built-in [`ICON_FONT`]. - /// - /// [`ICON_FONT`]: Self::ICON_FONT - const ARROW_DOWN_ICON: char; - - /// Returns the default [`Self::Font`]. - fn default_font(&self) -> Self::Font; - - /// Returns the default size of [`Text`]. - fn default_size(&self) -> f32; - - /// Measures the text in the given bounds and returns the minimum boundaries - /// that can fit the contents. - fn measure( - &self, - content: &str, - size: f32, - font: Self::Font, - bounds: Size, - ) -> (f32, f32); - - /// Measures the width of the text as if it were laid out in a single line. - fn measure_width(&self, content: &str, size: f32, font: Self::Font) -> f32 { - let (width, _) = self.measure(content, size, font, Size::INFINITY); - - width - } - - /// Tests whether the provided point is within the boundaries of text - /// laid out with the given parameters, returning information about - /// the nearest character. - /// - /// If `nearest_only` is true, the hit test does not consider whether the - /// the point is interior to any glyph bounds, returning only the character - /// with the nearest centeroid. - fn hit_test( - &self, - contents: &str, - size: f32, - font: Self::Font, - bounds: Size, - point: Point, - nearest_only: bool, - ) -> Option<Hit>; - - /// Loads a [`Self::Font`] from its bytes. - fn load_font(&mut self, font: Cow<'static, [u8]>); - - /// Draws the given [`Text`]. - fn fill_text(&mut self, text: Text<'_, Self::Font>); -} diff --git a/native/src/touch.rs b/native/src/touch.rs deleted file mode 100644 index 18120644..00000000 --- a/native/src/touch.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! Build touch events. -use crate::Point; - -/// A touch interaction. -#[derive(Debug, Clone, Copy, PartialEq)] -#[allow(missing_docs)] -pub enum Event { - /// A touch interaction was started. - FingerPressed { id: Finger, position: Point }, - - /// An on-going touch interaction was moved. - FingerMoved { id: Finger, position: Point }, - - /// A touch interaction was ended. - FingerLifted { id: Finger, position: Point }, - - /// A touch interaction was canceled. - FingerLost { id: Finger, position: Point }, -} - -/// A unique identifier representing a finger on a touch interaction. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct Finger(pub u64); diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 68ccda55..315027fa 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -1,14 +1,12 @@ //! Implement your own event loop to drive a user interface. -use crate::application; -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::widget; -use crate::window; -use crate::{ - Clipboard, Element, Layout, Point, Rectangle, Shell, Size, Vector, -}; +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::widget; +use crate::core::window; +use crate::core::{Clipboard, Point, Rectangle, Size, Vector}; +use crate::core::{Element, Layout, Shell}; /// A set of interactive graphical elements with a specific [`Layout`]. /// @@ -34,8 +32,7 @@ pub struct UserInterface<'a, Message, Renderer> { impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> where - Renderer: crate::Renderer, - Renderer::Theme: application::StyleSheet, + Renderer: iced_core::Renderer, { /// Builds a user interface for an [`Element`]. /// @@ -48,24 +45,21 @@ where /// is naive way to set up our application loop: /// /// ```no_run - /// use iced_native::Size; - /// use iced_native::user_interface::{self, UserInterface}; - /// use iced_wgpu::Renderer; - /// /// # mod iced_wgpu { - /// # pub use iced_native::renderer::Null as Renderer; + /// # pub use iced_native::core::renderer::Null as Renderer; /// # } /// # - /// # use iced_native::widget::Column; - /// # /// # pub struct Counter; /// # /// # impl Counter { /// # pub fn new() -> Self { Counter } - /// # pub fn view(&self) -> Column<(), Renderer> { - /// # Column::new() - /// # } + /// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() } + /// # pub fn update(&mut self, _: ()) {} /// # } + /// use iced_native::core::Size; + /// use iced_native::user_interface::{self, UserInterface}; + /// use iced_wgpu::Renderer; + /// /// // Initialization /// let mut counter = Counter::new(); /// let mut cache = user_interface::Cache::new(); @@ -124,25 +118,21 @@ where /// completing [the previous example](#example): /// /// ```no_run - /// use iced_native::{clipboard, Size, Point}; - /// use iced_native::user_interface::{self, UserInterface}; - /// use iced_wgpu::Renderer; - /// /// # mod iced_wgpu { - /// # pub use iced_native::renderer::Null as Renderer; + /// # pub use iced_native::core::renderer::Null as Renderer; /// # } /// # - /// # use iced_native::widget::Column; - /// # /// # pub struct Counter; /// # /// # impl Counter { /// # pub fn new() -> Self { Counter } - /// # pub fn view(&self) -> Column<(), Renderer> { - /// # Column::new() - /// # } - /// # pub fn update(&mut self, message: ()) {} + /// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() } + /// # pub fn update(&mut self, _: ()) {} /// # } + /// use iced_native::core::{clipboard, Size, Point}; + /// use iced_native::user_interface::{self, UserInterface}; + /// use iced_wgpu::Renderer; + /// /// let mut counter = Counter::new(); /// let mut cache = user_interface::Cache::new(); /// let mut renderer = Renderer::new(); @@ -357,27 +347,24 @@ where /// [completing the last example](#example-1): /// /// ```no_run - /// use iced_native::clipboard; - /// use iced_native::renderer; - /// use iced_native::user_interface::{self, UserInterface}; - /// use iced_native::{Size, Point, Theme}; - /// use iced_wgpu::Renderer; - /// /// # mod iced_wgpu { - /// # pub use iced_native::renderer::Null as Renderer; + /// # pub use iced_native::core::renderer::Null as Renderer; + /// # pub type Theme = (); /// # } /// # - /// # use iced_native::widget::Column; - /// # /// # pub struct Counter; /// # /// # impl Counter { /// # pub fn new() -> Self { Counter } - /// # pub fn view(&self) -> Column<(), Renderer> { - /// # Column::new() - /// # } - /// # pub fn update(&mut self, message: ()) {} + /// # pub fn view(&self) -> Element<(), Renderer> { unimplemented!() } + /// # pub fn update(&mut self, _: ()) {} /// # } + /// use iced_native::core::clipboard; + /// use iced_native::core::renderer; + /// use iced_native::core::{Element, Size, Point}; + /// use iced_native::user_interface::{self, UserInterface}; + /// use iced_wgpu::{Renderer, Theme}; + /// /// let mut counter = Counter::new(); /// let mut cache = user_interface::Cache::new(); /// let mut renderer = Renderer::new(); @@ -386,6 +373,7 @@ where /// let mut clipboard = clipboard::Null; /// let mut events = Vec::new(); /// let mut messages = Vec::new(); + /// let mut theme = Theme::default(); /// /// loop { /// // Obtain system events... @@ -407,7 +395,7 @@ where /// ); /// /// // Draw the user interface - /// let mouse_cursor = user_interface.draw(&mut renderer, &Theme::default(), &renderer::Style::default(), cursor_position); + /// let mouse_cursor = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor_position); /// /// cache = user_interface.into_cache(); /// diff --git a/native/src/widget.rs b/native/src/widget.rs index 2b3ca7be..0fdade54 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -11,212 +11,6 @@ //! source of inspiration. //! //! [renderer]: crate::renderer -pub mod button; -pub mod checkbox; -pub mod column; -pub mod container; -pub mod helpers; -pub mod image; -pub mod operation; -pub mod pane_grid; -pub mod pick_list; -pub mod progress_bar; -pub mod radio; -pub mod row; -pub mod rule; -pub mod scrollable; -pub mod slider; -pub mod space; -pub mod svg; -pub mod text; -pub mod text_input; -pub mod toggler; -pub mod tooltip; -pub mod tree; -pub mod vertical_slider; - mod action; -mod id; - -#[doc(no_inline)] -pub use button::Button; -#[doc(no_inline)] -pub use checkbox::Checkbox; -#[doc(no_inline)] -pub use column::Column; -#[doc(no_inline)] -pub use container::Container; -#[doc(no_inline)] -pub use helpers::*; -#[doc(no_inline)] -pub use image::Image; -#[doc(no_inline)] -pub use pane_grid::PaneGrid; -#[doc(no_inline)] -pub use pick_list::PickList; -#[doc(no_inline)] -pub use progress_bar::ProgressBar; -#[doc(no_inline)] -pub use radio::Radio; -#[doc(no_inline)] -pub use row::Row; -#[doc(no_inline)] -pub use rule::Rule; -#[doc(no_inline)] -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; -#[doc(no_inline)] -pub use toggler::Toggler; -#[doc(no_inline)] -pub use tooltip::Tooltip; -#[doc(no_inline)] -pub use tree::Tree; -#[doc(no_inline)] -pub use vertical_slider::VerticalSlider; pub use action::Action; -pub use id::Id; -pub use operation::Operation; - -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::{Clipboard, Layout, Length, Point, Rectangle, Shell}; - -/// A component that displays information and allows interaction. -/// -/// If you want to build your own widgets, you will need to implement this -/// trait. -/// -/// # Examples -/// The repository has some [examples] showcasing how to implement a custom -/// widget: -/// -/// - [`bezier_tool`], a Paint-like tool for drawing Bézier curves using -/// [`lyon`]. -/// - [`custom_widget`], a demonstration of how to build a custom widget that -/// draws a circle. -/// - [`geometry`], a custom widget showcasing how to draw geometry with the -/// `Mesh2D` primitive in [`iced_wgpu`]. -/// -/// [examples]: https://github.com/iced-rs/iced/tree/0.8/examples -/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.8/examples/bezier_tool -/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.8/examples/custom_widget -/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.8/examples/geometry -/// [`lyon`]: https://github.com/nical/lyon -/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.8/wgpu -pub trait Widget<Message, Renderer> -where - Renderer: crate::Renderer, -{ - /// Returns the width of the [`Widget`]. - fn width(&self) -> Length; - - /// Returns the height of the [`Widget`]. - fn height(&self) -> Length; - - /// Returns the [`layout::Node`] of the [`Widget`]. - /// - /// This [`layout::Node`] is used by the runtime to compute the [`Layout`] of the - /// user interface. - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node; - - /// Draws the [`Widget`] using the associated `Renderer`. - fn draw( - &self, - state: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ); - - /// Returns the [`Tag`] of the [`Widget`]. - /// - /// [`Tag`]: tree::Tag - fn tag(&self) -> tree::Tag { - tree::Tag::stateless() - } - - /// Returns the [`State`] of the [`Widget`]. - /// - /// [`State`]: tree::State - fn state(&self) -> tree::State { - tree::State::None - } - - /// Returns the state [`Tree`] of the children of the [`Widget`]. - fn children(&self) -> Vec<Tree> { - Vec::new() - } - - /// Reconciliates the [`Widget`] with the provided [`Tree`]. - fn diff(&self, _tree: &mut Tree) {} - - /// Applies an [`Operation`] to the [`Widget`]. - fn operate( - &self, - _state: &mut Tree, - _layout: Layout<'_>, - _renderer: &Renderer, - _operation: &mut dyn Operation<Message>, - ) { - } - - /// Processes a runtime [`Event`]. - /// - /// By default, it does nothing. - fn on_event( - &mut self, - _state: &mut Tree, - _event: Event, - _layout: Layout<'_>, - _cursor_position: Point, - _renderer: &Renderer, - _clipboard: &mut dyn Clipboard, - _shell: &mut Shell<'_, Message>, - ) -> event::Status { - event::Status::Ignored - } - - /// Returns the current [`mouse::Interaction`] of the [`Widget`]. - /// - /// By default, it returns [`mouse::Interaction::Idle`]. - fn mouse_interaction( - &self, - _state: &Tree, - _layout: Layout<'_>, - _cursor_position: Point, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - mouse::Interaction::Idle - } - - /// Returns the overlay of the [`Widget`], if there is any. - fn overlay<'a>( - &'a mut self, - _state: &'a mut Tree, - _layout: Layout<'_>, - _renderer: &Renderer, - ) -> Option<overlay::Element<'a, Message, Renderer>> { - None - } -} diff --git a/native/src/widget/action.rs b/native/src/widget/action.rs index 3f1b6b6c..f50d7aec 100644 --- a/native/src/widget/action.rs +++ b/native/src/widget/action.rs @@ -1,8 +1,7 @@ -use crate::widget::operation::{ +use iced_core::widget::operation::{ self, Focusable, Operation, Scrollable, TextInput, }; -use crate::widget::Id; - +use iced_core::widget::Id; use iced_futures::MaybeSend; use std::any::Any; diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs deleted file mode 100644 index 39387173..00000000 --- a/native/src/widget/button.rs +++ /dev/null @@ -1,455 +0,0 @@ -//! Allow your users to perform actions by pressing a button. -//! -//! A [`Button`] has some local [`State`]. -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::touch; -use crate::widget::tree::{self, Tree}; -use crate::widget::Operation; -use crate::{ - Background, Clipboard, Color, Element, Layout, Length, Padding, Point, - Rectangle, Shell, Vector, Widget, -}; - -pub use iced_style::button::{Appearance, StyleSheet}; - -/// A generic widget that produces a message when pressed. -/// -/// ``` -/// # type Button<'a, Message> = -/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>; -/// # -/// #[derive(Clone)] -/// enum Message { -/// ButtonPressed, -/// } -/// -/// let button = Button::new("Press me!").on_press(Message::ButtonPressed); -/// ``` -/// -/// If a [`Button::on_press`] handler is not set, the resulting [`Button`] will -/// be disabled: -/// -/// ``` -/// # type Button<'a, Message> = -/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>; -/// # -/// #[derive(Clone)] -/// enum Message { -/// ButtonPressed, -/// } -/// -/// fn disabled_button<'a>() -> Button<'a, Message> { -/// Button::new("I'm disabled!") -/// } -/// -/// fn enabled_button<'a>() -> Button<'a, Message> { -/// disabled_button().on_press(Message::ButtonPressed) -/// } -/// ``` -#[allow(missing_debug_implementations)] -pub struct Button<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - content: Element<'a, Message, Renderer>, - on_press: Option<Message>, - width: Length, - height: Length, - padding: Padding, - style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, Message, Renderer> Button<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - /// Creates a new [`Button`] with the given content. - pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self { - Button { - content: content.into(), - on_press: None, - width: Length::Shrink, - height: Length::Shrink, - padding: Padding::new(5.0), - style: <Renderer::Theme as StyleSheet>::Style::default(), - } - } - - /// Sets the width of the [`Button`]. - pub fn width(mut self, width: impl Into<Length>) -> Self { - self.width = width.into(); - self - } - - /// Sets the height of the [`Button`]. - pub fn height(mut self, height: impl Into<Length>) -> Self { - self.height = height.into(); - self - } - - /// Sets the [`Padding`] of the [`Button`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the message that will be produced when the [`Button`] is pressed. - /// - /// Unless `on_press` is called, the [`Button`] will be disabled. - pub fn on_press(mut self, msg: Message) -> Self { - self.on_press = Some(msg); - self - } - - /// Sets the style variant of this [`Button`]. - pub fn style( - mut self, - style: <Renderer::Theme as StyleSheet>::Style, - ) -> Self { - self.style = style; - self - } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> - for Button<'a, Message, Renderer> -where - Message: 'a + Clone, - Renderer: 'a + crate::Renderer, - Renderer::Theme: StyleSheet, -{ - fn tag(&self) -> tree::Tag { - tree::Tag::of::<State>() - } - - fn state(&self) -> tree::State { - tree::State::new(State::new()) - } - - fn children(&self) -> Vec<Tree> { - vec![Tree::new(&self.content)] - } - - fn diff(&self, tree: &mut Tree) { - tree.diff_children(std::slice::from_ref(&self.content)) - } - - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - layout( - renderer, - limits, - self.width, - self.height, - self.padding, - |renderer, limits| { - self.content.as_widget().layout(renderer, limits) - }, - ) - } - - fn operate( - &self, - tree: &mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn Operation<Message>, - ) { - operation.container(None, &mut |operation| { - self.content.as_widget().operate( - &mut tree.children[0], - layout.children().next().unwrap(), - renderer, - operation, - ); - }); - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - if let event::Status::Captured = self.content.as_widget_mut().on_event( - &mut tree.children[0], - event.clone(), - layout.children().next().unwrap(), - cursor_position, - renderer, - clipboard, - shell, - ) { - return event::Status::Captured; - } - - update( - event, - layout, - cursor_position, - shell, - &self.on_press, - || tree.state.downcast_mut::<State>(), - ) - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - _style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - ) { - let bounds = layout.bounds(); - let content_layout = layout.children().next().unwrap(); - - let styling = draw( - renderer, - bounds, - cursor_position, - self.on_press.is_some(), - theme, - &self.style, - || tree.state.downcast_ref::<State>(), - ); - - self.content.as_widget().draw( - &tree.children[0], - renderer, - theme, - &renderer::Style { - text_color: styling.text_color, - }, - content_layout, - cursor_position, - &bounds, - ); - } - - fn mouse_interaction( - &self, - _tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - mouse_interaction(layout, cursor_position, self.on_press.is_some()) - } - - fn overlay<'b>( - &'b mut self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option<overlay::Element<'b, Message, Renderer>> { - self.content.as_widget_mut().overlay( - &mut tree.children[0], - layout.children().next().unwrap(), - renderer, - ) - } -} - -impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>> - for Element<'a, Message, Renderer> -where - Message: Clone + 'a, - Renderer: crate::Renderer + 'a, - Renderer::Theme: StyleSheet, -{ - fn from(button: Button<'a, Message, Renderer>) -> Self { - Self::new(button) - } -} - -/// The local state of a [`Button`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct State { - is_pressed: bool, -} - -impl State { - /// Creates a new [`State`]. - pub fn new() -> State { - State::default() - } -} - -/// Processes the given [`Event`] and updates the [`State`] of a [`Button`] -/// accordingly. -pub fn update<'a, Message: Clone>( - event: Event, - layout: Layout<'_>, - cursor_position: Point, - shell: &mut Shell<'_, Message>, - on_press: &Option<Message>, - state: impl FnOnce() -> &'a mut State, -) -> event::Status { - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - if on_press.is_some() { - let bounds = layout.bounds(); - - if bounds.contains(cursor_position) { - let state = state(); - - state.is_pressed = true; - - return event::Status::Captured; - } - } - } - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) => { - if let Some(on_press) = on_press.clone() { - let state = state(); - - if state.is_pressed { - state.is_pressed = false; - - let bounds = layout.bounds(); - - if bounds.contains(cursor_position) { - shell.publish(on_press); - } - - return event::Status::Captured; - } - } - } - Event::Touch(touch::Event::FingerLost { .. }) => { - let state = state(); - - state.is_pressed = false; - } - _ => {} - } - - event::Status::Ignored -} - -/// Draws a [`Button`]. -pub fn draw<'a, Renderer: crate::Renderer>( - renderer: &mut Renderer, - bounds: Rectangle, - cursor_position: Point, - is_enabled: bool, - style_sheet: &dyn StyleSheet< - Style = <Renderer::Theme as StyleSheet>::Style, - >, - style: &<Renderer::Theme as StyleSheet>::Style, - state: impl FnOnce() -> &'a State, -) -> Appearance -where - Renderer::Theme: StyleSheet, -{ - let is_mouse_over = bounds.contains(cursor_position); - - let styling = if !is_enabled { - style_sheet.disabled(style) - } else if is_mouse_over { - let state = state(); - - if state.is_pressed { - style_sheet.pressed(style) - } else { - style_sheet.hovered(style) - } - } else { - style_sheet.active(style) - }; - - if styling.background.is_some() || styling.border_width > 0.0 { - if styling.shadow_offset != Vector::default() { - // TODO: Implement proper shadow support - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: bounds.x + styling.shadow_offset.x, - y: bounds.y + styling.shadow_offset.y, - ..bounds - }, - border_radius: styling.border_radius.into(), - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - Background::Color([0.0, 0.0, 0.0, 0.5].into()), - ); - } - - renderer.fill_quad( - renderer::Quad { - bounds, - border_radius: styling.border_radius.into(), - border_width: styling.border_width, - border_color: styling.border_color, - }, - styling - .background - .unwrap_or(Background::Color(Color::TRANSPARENT)), - ); - } - - styling -} - -/// Computes the layout of a [`Button`]. -pub fn layout<Renderer>( - renderer: &Renderer, - limits: &layout::Limits, - width: Length, - height: Length, - padding: Padding, - layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, -) -> layout::Node { - let limits = limits.width(width).height(height); - - let mut content = layout_content(renderer, &limits.pad(padding)); - let padding = padding.fit(content.size(), limits.max()); - let size = limits.pad(padding).resolve(content.size()).pad(padding); - - content.move_to(Point::new(padding.left, padding.top)); - - layout::Node::with_children(size, vec![content]) -} - -/// Returns the [`mouse::Interaction`] of a [`Button`]. -pub fn mouse_interaction( - layout: Layout<'_>, - cursor_position: Point, - is_enabled: bool, -) -> mouse::Interaction { - let is_mouse_over = layout.bounds().contains(cursor_position); - - if is_mouse_over && is_enabled { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - } -} diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs deleted file mode 100644 index cd8b9c6b..00000000 --- a/native/src/widget/checkbox.rs +++ /dev/null @@ -1,321 +0,0 @@ -//! Show toggle controls using checkboxes. -use crate::alignment; -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::text; -use crate::touch; -use crate::widget::{self, Row, Text, Tree}; -use crate::{ - Alignment, Clipboard, Element, Layout, Length, Pixels, Point, Rectangle, - Shell, Widget, -}; - -pub use iced_style::checkbox::{Appearance, StyleSheet}; - -/// The icon in a [`Checkbox`]. -#[derive(Debug, Clone, PartialEq)] -pub struct Icon<Font> { - /// Font that will be used to display the `code_point`, - pub font: Font, - /// The unicode code point that will be used as the icon. - pub code_point: char, - /// Font size of the content. - pub size: Option<f32>, -} - -/// A box that can be checked. -/// -/// # Example -/// -/// ``` -/// # type Checkbox<'a, Message> = iced_native::widget::Checkbox<'a, Message, iced_native::renderer::Null>; -/// # -/// pub enum Message { -/// CheckboxToggled(bool), -/// } -/// -/// let is_checked = true; -/// -/// Checkbox::new("Toggle me!", is_checked, Message::CheckboxToggled); -/// ``` -/// -///  -#[allow(missing_debug_implementations)] -pub struct Checkbox<'a, Message, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, -{ - is_checked: bool, - on_toggle: Box<dyn Fn(bool) -> Message + 'a>, - label: String, - width: Length, - size: f32, - spacing: f32, - text_size: Option<f32>, - font: Option<Renderer::Font>, - icon: Icon<Renderer::Font>, - style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, Message, Renderer> Checkbox<'a, Message, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, -{ - /// The default size of a [`Checkbox`]. - const DEFAULT_SIZE: f32 = 20.0; - - /// The default spacing of a [`Checkbox`]. - const DEFAULT_SPACING: f32 = 15.0; - - /// Creates a new [`Checkbox`]. - /// - /// It expects: - /// * a boolean describing whether the [`Checkbox`] is checked or not - /// * the label of the [`Checkbox`] - /// * a function that will be called when the [`Checkbox`] is toggled. It - /// will receive the new state of the [`Checkbox`] and must produce a - /// `Message`. - pub fn new<F>(label: impl Into<String>, is_checked: bool, f: F) -> Self - where - F: 'a + Fn(bool) -> Message, - { - Checkbox { - is_checked, - on_toggle: Box::new(f), - label: label.into(), - width: Length::Shrink, - size: Self::DEFAULT_SIZE, - spacing: Self::DEFAULT_SPACING, - text_size: None, - font: None, - icon: Icon { - font: Renderer::ICON_FONT, - code_point: Renderer::CHECKMARK_ICON, - size: None, - }, - style: Default::default(), - } - } - - /// Sets the size of the [`Checkbox`]. - pub fn size(mut self, size: impl Into<Pixels>) -> Self { - self.size = size.into().0; - self - } - - /// Sets the width of the [`Checkbox`]. - pub fn width(mut self, width: impl Into<Length>) -> Self { - self.width = width.into(); - self - } - - /// Sets the spacing between the [`Checkbox`] and the text. - pub fn spacing(mut self, spacing: impl Into<Pixels>) -> Self { - self.spacing = spacing.into().0; - self - } - - /// Sets the text size of the [`Checkbox`]. - pub fn text_size(mut self, text_size: impl Into<Pixels>) -> Self { - self.text_size = Some(text_size.into().0); - self - } - - /// Sets the [`Font`] of the text of the [`Checkbox`]. - /// - /// [`Font`]: crate::text::Renderer::Font - pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self { - self.font = Some(font.into()); - self - } - - /// Sets the [`Icon`] of the [`Checkbox`]. - pub fn icon(mut self, icon: Icon<Renderer::Font>) -> Self { - self.icon = icon; - self - } - - /// Sets the style of the [`Checkbox`]. - pub fn style( - mut self, - style: impl Into<<Renderer::Theme as StyleSheet>::Style>, - ) -> Self { - self.style = style.into(); - self - } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> - for Checkbox<'a, Message, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, -{ - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - Length::Shrink - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - Row::<(), Renderer>::new() - .width(self.width) - .spacing(self.spacing) - .align_items(Alignment::Center) - .push(Row::new().width(self.size).height(self.size)) - .push( - Text::new(&self.label) - .font(self.font.unwrap_or_else(|| renderer.default_font())) - .width(self.width) - .size( - self.text_size - .unwrap_or_else(|| renderer.default_size()), - ), - ) - .layout(renderer, limits) - } - - fn on_event( - &mut self, - _tree: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - _renderer: &Renderer, - _clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - let mouse_over = layout.bounds().contains(cursor_position); - - if mouse_over { - shell.publish((self.on_toggle)(!self.is_checked)); - - return event::Status::Captured; - } - } - _ => {} - } - - event::Status::Ignored - } - - fn mouse_interaction( - &self, - _tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - if layout.bounds().contains(cursor_position) { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - } - } - - fn draw( - &self, - _tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - ) { - let bounds = layout.bounds(); - let is_mouse_over = bounds.contains(cursor_position); - - let mut children = layout.children(); - - let custom_style = if is_mouse_over { - theme.hovered(&self.style, self.is_checked) - } else { - theme.active(&self.style, self.is_checked) - }; - - { - let layout = children.next().unwrap(); - let bounds = layout.bounds(); - - renderer.fill_quad( - renderer::Quad { - bounds, - border_radius: custom_style.border_radius.into(), - border_width: custom_style.border_width, - border_color: custom_style.border_color, - }, - custom_style.background, - ); - - let Icon { - font, - code_point, - size, - } = &self.icon; - let size = size.unwrap_or(bounds.height * 0.7); - - if self.is_checked { - renderer.fill_text(text::Text { - content: &code_point.to_string(), - font: *font, - size, - bounds: Rectangle { - x: bounds.center_x(), - y: bounds.center_y(), - ..bounds - }, - color: custom_style.icon_color, - horizontal_alignment: alignment::Horizontal::Center, - vertical_alignment: alignment::Vertical::Center, - }); - } - } - - { - let label_layout = children.next().unwrap(); - - widget::text::draw( - renderer, - style, - label_layout, - &self.label, - self.text_size, - self.font, - widget::text::Appearance { - color: custom_style.text_color, - }, - alignment::Horizontal::Left, - alignment::Vertical::Center, - ); - } - } -} - -impl<'a, Message, Renderer> From<Checkbox<'a, Message, Renderer>> - for Element<'a, Message, Renderer> -where - Message: 'a, - Renderer: 'a + text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, -{ - fn from( - checkbox: Checkbox<'a, Message, Renderer>, - ) -> Element<'a, Message, Renderer> { - Element::new(checkbox) - } -} diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs deleted file mode 100644 index ebe579d5..00000000 --- a/native/src/widget/column.rs +++ /dev/null @@ -1,264 +0,0 @@ -//! Distribute content vertically. -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::widget::{Operation, Tree}; -use crate::{ - Alignment, Clipboard, Element, Layout, Length, Padding, Pixels, Point, - Rectangle, Shell, Widget, -}; - -/// A container that distributes its contents vertically. -#[allow(missing_debug_implementations)] -pub struct Column<'a, Message, Renderer> { - spacing: f32, - padding: Padding, - width: Length, - height: Length, - max_width: f32, - align_items: Alignment, - children: Vec<Element<'a, Message, Renderer>>, -} - -impl<'a, Message, Renderer> Column<'a, Message, Renderer> { - /// Creates an empty [`Column`]. - pub fn new() -> Self { - Self::with_children(Vec::new()) - } - - /// Creates a [`Column`] with the given elements. - pub fn with_children( - children: Vec<Element<'a, Message, Renderer>>, - ) -> Self { - Column { - spacing: 0.0, - padding: Padding::ZERO, - width: Length::Shrink, - height: Length::Shrink, - max_width: f32::INFINITY, - align_items: Alignment::Start, - children, - } - } - - /// Sets the vertical spacing _between_ elements. - /// - /// Custom margins per element do not exist in iced. You should use this - /// method instead! While less flexible, it helps you keep spacing between - /// elements consistent. - pub fn spacing(mut self, amount: impl Into<Pixels>) -> Self { - self.spacing = amount.into().0; - self - } - - /// Sets the [`Padding`] of the [`Column`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the width of the [`Column`]. - pub fn width(mut self, width: impl Into<Length>) -> Self { - self.width = width.into(); - self - } - - /// Sets the height of the [`Column`]. - pub fn height(mut self, height: impl Into<Length>) -> Self { - self.height = height.into(); - self - } - - /// Sets the maximum width of the [`Column`]. - pub fn max_width(mut self, max_width: impl Into<Pixels>) -> Self { - self.max_width = max_width.into().0; - self - } - - /// Sets the horizontal alignment of the contents of the [`Column`] . - pub fn align_items(mut self, align: Alignment) -> Self { - self.align_items = align; - self - } - - /// Adds an element to the [`Column`]. - pub fn push( - mut self, - child: impl Into<Element<'a, Message, Renderer>>, - ) -> Self { - self.children.push(child.into()); - self - } -} - -impl<'a, Message, Renderer> Default for Column<'a, Message, Renderer> { - fn default() -> Self { - Self::new() - } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> - for Column<'a, Message, Renderer> -where - Renderer: crate::Renderer, -{ - fn children(&self) -> Vec<Tree> { - self.children.iter().map(Tree::new).collect() - } - - fn diff(&self, tree: &mut Tree) { - tree.diff_children(&self.children); - } - - 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 - .max_width(self.max_width) - .width(self.width) - .height(self.height); - - layout::flex::resolve( - layout::flex::Axis::Vertical, - renderer, - &limits, - self.padding, - self.spacing, - self.align_items, - &self.children, - ) - } - - fn operate( - &self, - tree: &mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn Operation<Message>, - ) { - operation.container(None, &mut |operation| { - self.children - .iter() - .zip(&mut tree.children) - .zip(layout.children()) - .for_each(|((child, state), layout)| { - child - .as_widget() - .operate(state, layout, renderer, operation); - }) - }); - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - self.children - .iter_mut() - .zip(&mut tree.children) - .zip(layout.children()) - .map(|((child, state), layout)| { - child.as_widget_mut().on_event( - state, - event.clone(), - layout, - cursor_position, - renderer, - clipboard, - shell, - ) - }) - .fold(event::Status::Ignored, event::Status::merge) - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.children - .iter() - .zip(&tree.children) - .zip(layout.children()) - .map(|((child, state), layout)| { - child.as_widget().mouse_interaction( - state, - layout, - cursor_position, - viewport, - renderer, - ) - }) - .max() - .unwrap_or_default() - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ) { - for ((child, state), layout) in self - .children - .iter() - .zip(&tree.children) - .zip(layout.children()) - { - child.as_widget().draw( - state, - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ); - } - } - - fn overlay<'b>( - &'b mut self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option<overlay::Element<'b, Message, Renderer>> { - overlay::from_children(&mut self.children, tree, layout, renderer) - } -} - -impl<'a, Message, Renderer> From<Column<'a, Message, Renderer>> - for Element<'a, Message, Renderer> -where - Message: 'a, - Renderer: crate::Renderer + 'a, -{ - fn from(column: Column<'a, Message, Renderer>) -> Self { - Self::new(column) - } -} diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs deleted file mode 100644 index b77bf50d..00000000 --- a/native/src/widget/container.rs +++ /dev/null @@ -1,368 +0,0 @@ -//! Decorate content and apply alignment. -use crate::alignment::{self, Alignment}; -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::widget::{self, Operation, Tree}; -use crate::{ - Background, Clipboard, Color, Element, Layout, Length, Padding, Pixels, - Point, Rectangle, Shell, Widget, -}; - -pub use iced_style::container::{Appearance, StyleSheet}; - -/// An element decorating some content. -/// -/// It is normally used for alignment purposes. -#[allow(missing_debug_implementations)] -pub struct Container<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - id: Option<Id>, - padding: Padding, - width: Length, - height: Length, - max_width: f32, - max_height: f32, - horizontal_alignment: alignment::Horizontal, - vertical_alignment: alignment::Vertical, - style: <Renderer::Theme as StyleSheet>::Style, - content: Element<'a, Message, Renderer>, -} - -impl<'a, Message, Renderer> Container<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - /// Creates an empty [`Container`]. - pub fn new<T>(content: T) -> Self - where - T: Into<Element<'a, Message, Renderer>>, - { - Container { - id: None, - padding: Padding::ZERO, - width: Length::Shrink, - height: Length::Shrink, - max_width: f32::INFINITY, - max_height: f32::INFINITY, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, - style: Default::default(), - content: content.into(), - } - } - - /// Sets the [`Id`] of the [`Container`]. - pub fn id(mut self, id: Id) -> Self { - self.id = Some(id); - self - } - - /// Sets the [`Padding`] of the [`Container`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the width of the [`Container`]. - pub fn width(mut self, width: impl Into<Length>) -> Self { - self.width = width.into(); - self - } - - /// Sets the height of the [`Container`]. - pub fn height(mut self, height: impl Into<Length>) -> Self { - self.height = height.into(); - self - } - - /// Sets the maximum width of the [`Container`]. - pub fn max_width(mut self, max_width: impl Into<Pixels>) -> Self { - self.max_width = max_width.into().0; - self - } - - /// Sets the maximum height of the [`Container`]. - pub fn max_height(mut self, max_height: impl Into<Pixels>) -> Self { - self.max_height = max_height.into().0; - self - } - - /// Sets the content alignment for the horizontal axis of the [`Container`]. - pub fn align_x(mut self, alignment: alignment::Horizontal) -> Self { - self.horizontal_alignment = alignment; - self - } - - /// Sets the content alignment for the vertical axis of the [`Container`]. - pub fn align_y(mut self, alignment: alignment::Vertical) -> Self { - self.vertical_alignment = alignment; - self - } - - /// Centers the contents in the horizontal axis of the [`Container`]. - pub fn center_x(mut self) -> Self { - self.horizontal_alignment = alignment::Horizontal::Center; - self - } - - /// Centers the contents in the vertical axis of the [`Container`]. - pub fn center_y(mut self) -> Self { - self.vertical_alignment = alignment::Vertical::Center; - self - } - - /// Sets the style of the [`Container`]. - pub fn style( - mut self, - style: impl Into<<Renderer::Theme as StyleSheet>::Style>, - ) -> Self { - self.style = style.into(); - self - } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> - for Container<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - fn children(&self) -> Vec<Tree> { - vec![Tree::new(&self.content)] - } - - fn diff(&self, tree: &mut Tree) { - tree.diff_children(std::slice::from_ref(&self.content)) - } - - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - layout( - renderer, - limits, - self.width, - self.height, - self.max_width, - self.max_height, - self.padding, - self.horizontal_alignment, - self.vertical_alignment, - |renderer, limits| { - self.content.as_widget().layout(renderer, limits) - }, - ) - } - - fn operate( - &self, - tree: &mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn Operation<Message>, - ) { - operation.container( - self.id.as_ref().map(|id| &id.0), - &mut |operation| { - self.content.as_widget().operate( - &mut tree.children[0], - layout.children().next().unwrap(), - renderer, - operation, - ); - }, - ); - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - self.content.as_widget_mut().on_event( - &mut tree.children[0], - event, - layout.children().next().unwrap(), - cursor_position, - renderer, - clipboard, - shell, - ) - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.content.as_widget().mouse_interaction( - &tree.children[0], - layout.children().next().unwrap(), - cursor_position, - viewport, - renderer, - ) - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - renderer_style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ) { - let style = theme.appearance(&self.style); - - draw_background(renderer, &style, layout.bounds()); - - self.content.as_widget().draw( - &tree.children[0], - renderer, - theme, - &renderer::Style { - text_color: style - .text_color - .unwrap_or(renderer_style.text_color), - }, - layout.children().next().unwrap(), - cursor_position, - viewport, - ); - } - - fn overlay<'b>( - &'b mut self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option<overlay::Element<'b, Message, Renderer>> { - self.content.as_widget_mut().overlay( - &mut tree.children[0], - layout.children().next().unwrap(), - renderer, - ) - } -} - -impl<'a, Message, Renderer> From<Container<'a, Message, Renderer>> - for Element<'a, Message, Renderer> -where - Message: 'a, - Renderer: 'a + crate::Renderer, - Renderer::Theme: StyleSheet, -{ - fn from( - column: Container<'a, Message, Renderer>, - ) -> Element<'a, Message, Renderer> { - Element::new(column) - } -} - -/// Computes the layout of a [`Container`]. -pub fn layout<Renderer>( - renderer: &Renderer, - limits: &layout::Limits, - width: Length, - height: Length, - max_width: f32, - max_height: f32, - padding: Padding, - horizontal_alignment: alignment::Horizontal, - vertical_alignment: alignment::Vertical, - layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, -) -> layout::Node { - let limits = limits - .loose() - .max_width(max_width) - .max_height(max_height) - .width(width) - .height(height); - - let mut content = layout_content(renderer, &limits.pad(padding).loose()); - let padding = padding.fit(content.size(), limits.max()); - let size = limits.pad(padding).resolve(content.size()); - - content.move_to(Point::new(padding.left, padding.top)); - content.align( - Alignment::from(horizontal_alignment), - Alignment::from(vertical_alignment), - size, - ); - - layout::Node::with_children(size.pad(padding), vec![content]) -} - -/// Draws the background of a [`Container`] given its [`Appearance`] and its `bounds`. -pub fn draw_background<Renderer>( - renderer: &mut Renderer, - appearance: &Appearance, - bounds: Rectangle, -) where - Renderer: crate::Renderer, -{ - if appearance.background.is_some() || appearance.border_width > 0.0 { - renderer.fill_quad( - renderer::Quad { - bounds, - border_radius: appearance.border_radius.into(), - border_width: appearance.border_width, - border_color: appearance.border_color, - }, - appearance - .background - .unwrap_or(Background::Color(Color::TRANSPARENT)), - ); - } -} - -/// The identifier of a [`Container`]. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Id(widget::Id); - -impl Id { - /// Creates a custom [`Id`]. - pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self { - Self(widget::Id::new(id)) - } - - /// Creates a unique [`Id`]. - /// - /// This function produces a different [`Id`] every time it is called. - pub fn unique() -> Self { - Self(widget::Id::unique()) - } -} - -impl From<Id> for widget::Id { - fn from(id: Id) -> Self { - id.0 - } -} diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs deleted file mode 100644 index d13eca75..00000000 --- a/native/src/widget/helpers.rs +++ /dev/null @@ -1,317 +0,0 @@ -//! Helper functions to create pure widgets. -use crate::overlay; -use crate::widget; -use crate::{Element, Length, Pixels}; - -use std::borrow::Cow; -use std::ops::RangeInclusive; - -/// Creates a [`Column`] with the given children. -/// -/// [`Column`]: widget::Column -#[macro_export] -macro_rules! column { - () => ( - $crate::widget::Column::new() - ); - ($($x:expr),+ $(,)?) => ( - $crate::widget::Column::with_children(vec![$($crate::Element::from($x)),+]) - ); -} - -/// Creates a [`Row`] with the given children. -/// -/// [`Row`]: widget::Row -#[macro_export] -macro_rules! row { - () => ( - $crate::widget::Row::new() - ); - ($($x:expr),+ $(,)?) => ( - $crate::widget::Row::with_children(vec![$($crate::Element::from($x)),+]) - ); -} - -/// Creates a new [`Container`] with the provided content. -/// -/// [`Container`]: widget::Container -pub fn container<'a, Message, Renderer>( - content: impl Into<Element<'a, Message, Renderer>>, -) -> widget::Container<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: widget::container::StyleSheet, -{ - widget::Container::new(content) -} - -/// Creates a new [`Column`] with the given children. -/// -/// [`Column`]: widget::Column -pub fn column<Message, Renderer>( - children: Vec<Element<'_, Message, Renderer>>, -) -> widget::Column<'_, Message, Renderer> { - widget::Column::with_children(children) -} - -/// Creates a new [`Row`] with the given children. -/// -/// [`Row`]: widget::Row -pub fn row<Message, Renderer>( - children: Vec<Element<'_, Message, Renderer>>, -) -> widget::Row<'_, Message, Renderer> { - widget::Row::with_children(children) -} - -/// Creates a new [`Scrollable`] with the provided content. -/// -/// [`Scrollable`]: widget::Scrollable -pub fn scrollable<'a, Message, Renderer>( - content: impl Into<Element<'a, Message, Renderer>>, -) -> widget::Scrollable<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: widget::scrollable::StyleSheet, -{ - widget::Scrollable::new(content) -} - -/// Creates a new [`Button`] with the provided content. -/// -/// [`Button`]: widget::Button -pub fn button<'a, Message, Renderer>( - content: impl Into<Element<'a, Message, Renderer>>, -) -> widget::Button<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: widget::button::StyleSheet, - <Renderer::Theme as widget::button::StyleSheet>::Style: Default, -{ - widget::Button::new(content) -} - -/// Creates a new [`Tooltip`] with the provided content, tooltip text, and [`tooltip::Position`]. -/// -/// [`Tooltip`]: widget::Tooltip -/// [`tooltip::Position`]: widget::tooltip::Position -pub fn tooltip<'a, Message, Renderer>( - content: impl Into<Element<'a, Message, Renderer>>, - tooltip: impl ToString, - position: widget::tooltip::Position, -) -> widget::Tooltip<'a, Message, Renderer> -where - Renderer: crate::text::Renderer, - Renderer::Theme: widget::container::StyleSheet + widget::text::StyleSheet, -{ - widget::Tooltip::new(content, tooltip.to_string(), position) -} - -/// Creates a new [`Text`] widget with the provided content. -/// -/// [`Text`]: widget::Text -pub fn text<'a, Renderer>(text: impl ToString) -> widget::Text<'a, Renderer> -where - Renderer: crate::text::Renderer, - Renderer::Theme: widget::text::StyleSheet, -{ - widget::Text::new(text.to_string()) -} - -/// Creates a new [`Checkbox`]. -/// -/// [`Checkbox`]: widget::Checkbox -pub fn checkbox<'a, Message, Renderer>( - label: impl Into<String>, - is_checked: bool, - f: impl Fn(bool) -> Message + 'a, -) -> widget::Checkbox<'a, Message, Renderer> -where - Renderer: crate::text::Renderer, - Renderer::Theme: widget::checkbox::StyleSheet + widget::text::StyleSheet, -{ - widget::Checkbox::new(label, is_checked, f) -} - -/// Creates a new [`Radio`]. -/// -/// [`Radio`]: widget::Radio -pub fn radio<Message, Renderer, V>( - label: impl Into<String>, - value: V, - selected: Option<V>, - on_click: impl FnOnce(V) -> Message, -) -> widget::Radio<Message, Renderer> -where - Message: Clone, - Renderer: crate::text::Renderer, - Renderer::Theme: widget::radio::StyleSheet, - V: Copy + Eq, -{ - widget::Radio::new(value, label, selected, on_click) -} - -/// Creates a new [`Toggler`]. -/// -/// [`Toggler`]: widget::Toggler -pub fn toggler<'a, Message, Renderer>( - label: impl Into<Option<String>>, - is_checked: bool, - f: impl Fn(bool) -> Message + 'a, -) -> widget::Toggler<'a, Message, Renderer> -where - Renderer: crate::text::Renderer, - Renderer::Theme: widget::toggler::StyleSheet, -{ - widget::Toggler::new(label, is_checked, f) -} - -/// Creates a new [`TextInput`]. -/// -/// [`TextInput`]: widget::TextInput -pub fn text_input<'a, Message, Renderer>( - placeholder: &str, - value: &str, - on_change: impl Fn(String) -> Message + 'a, -) -> widget::TextInput<'a, Message, Renderer> -where - Message: Clone, - Renderer: crate::text::Renderer, - Renderer::Theme: widget::text_input::StyleSheet, -{ - widget::TextInput::new(placeholder, value, on_change) -} - -/// Creates a new [`Slider`]. -/// -/// [`Slider`]: widget::Slider -pub fn slider<'a, T, Message, Renderer>( - range: std::ops::RangeInclusive<T>, - value: T, - on_change: impl Fn(T) -> Message + 'a, -) -> widget::Slider<'a, T, Message, Renderer> -where - T: Copy + From<u8> + std::cmp::PartialOrd, - Message: Clone, - Renderer: crate::Renderer, - Renderer::Theme: widget::slider::StyleSheet, -{ - widget::Slider::new(range, value, on_change) -} - -/// Creates a new [`VerticalSlider`]. -/// -/// [`VerticalSlider`]: widget::VerticalSlider -pub fn vertical_slider<'a, T, Message, Renderer>( - range: std::ops::RangeInclusive<T>, - value: T, - on_change: impl Fn(T) -> Message + 'a, -) -> widget::VerticalSlider<'a, T, Message, Renderer> -where - T: Copy + From<u8> + std::cmp::PartialOrd, - Message: Clone, - Renderer: crate::Renderer, - Renderer::Theme: widget::slider::StyleSheet, -{ - widget::VerticalSlider::new(range, value, on_change) -} - -/// Creates a new [`PickList`]. -/// -/// [`PickList`]: widget::PickList -pub fn pick_list<'a, Message, Renderer, T>( - options: impl Into<Cow<'a, [T]>>, - selected: Option<T>, - on_selected: impl Fn(T) -> Message + 'a, -) -> widget::PickList<'a, T, Message, Renderer> -where - T: ToString + Eq + 'static, - [T]: ToOwned<Owned = Vec<T>>, - Renderer: crate::text::Renderer, - Renderer::Theme: widget::pick_list::StyleSheet - + widget::scrollable::StyleSheet - + overlay::menu::StyleSheet - + widget::container::StyleSheet, - <Renderer::Theme as overlay::menu::StyleSheet>::Style: - From<<Renderer::Theme as widget::pick_list::StyleSheet>::Style>, -{ - widget::PickList::new(options, selected, on_selected) -} - -/// Creates a new [`Image`]. -/// -/// [`Image`]: widget::Image -pub fn image<Handle>(handle: impl Into<Handle>) -> widget::Image<Handle> { - widget::Image::new(handle.into()) -} - -/// Creates a new horizontal [`Space`] with the given [`Length`]. -/// -/// [`Space`]: widget::Space -pub fn horizontal_space(width: impl Into<Length>) -> widget::Space { - widget::Space::with_width(width) -} - -/// Creates a new vertical [`Space`] with the given [`Length`]. -/// -/// [`Space`]: widget::Space -pub fn vertical_space(height: impl Into<Length>) -> widget::Space { - widget::Space::with_height(height) -} - -/// Creates a horizontal [`Rule`] with the given height. -/// -/// [`Rule`]: widget::Rule -pub fn horizontal_rule<Renderer>( - height: impl Into<Pixels>, -) -> widget::Rule<Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: widget::rule::StyleSheet, -{ - widget::Rule::horizontal(height) -} - -/// Creates a vertical [`Rule`] with the given width. -/// -/// [`Rule`]: widget::Rule -pub fn vertical_rule<Renderer>( - width: impl Into<Pixels>, -) -> widget::Rule<Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: widget::rule::StyleSheet, -{ - widget::Rule::vertical(width) -} - -/// Creates a new [`ProgressBar`]. -/// -/// It expects: -/// * an inclusive range of possible values, and -/// * the current value of the [`ProgressBar`]. -/// -/// [`ProgressBar`]: widget::ProgressBar -pub fn progress_bar<Renderer>( - range: RangeInclusive<f32>, - value: f32, -) -> widget::ProgressBar<Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: widget::progress_bar::StyleSheet, -{ - widget::ProgressBar::new(range, value) -} - -/// Creates a new [`Svg`] widget from the given [`Handle`]. -/// -/// [`Svg`]: widget::Svg -/// [`Handle`]: widget::svg::Handle -pub fn svg<Renderer>( - handle: impl Into<widget::svg::Handle>, -) -> widget::Svg<Renderer> -where - Renderer: crate::svg::Renderer, - Renderer::Theme: widget::svg::StyleSheet, -{ - widget::Svg::new(handle) -} diff --git a/native/src/widget/id.rs b/native/src/widget/id.rs deleted file mode 100644 index 4b8fedf1..00000000 --- a/native/src/widget/id.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::borrow; -use std::sync::atomic::{self, AtomicUsize}; - -static NEXT_ID: AtomicUsize = AtomicUsize::new(0); - -/// The identifier of a generic widget. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Id(Internal); - -impl Id { - /// Creates a custom [`Id`]. - pub fn new(id: impl Into<borrow::Cow<'static, str>>) -> Self { - Self(Internal::Custom(id.into())) - } - - /// Creates a unique [`Id`]. - /// - /// This function produces a different [`Id`] every time it is called. - pub fn unique() -> Self { - let id = NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed); - - Self(Internal::Unique(id)) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Internal { - Unique(usize), - Custom(borrow::Cow<'static, str>), -} - -#[cfg(test)] -mod tests { - use super::Id; - - #[test] - fn unique_generates_different_ids() { - let a = Id::unique(); - let b = Id::unique(); - - assert_ne!(a, b); - } -} diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs deleted file mode 100644 index 73257a74..00000000 --- a/native/src/widget/image.rs +++ /dev/null @@ -1,204 +0,0 @@ -//! Display images in your user interface. -pub mod viewer; -pub use viewer::Viewer; - -use crate::image; -use crate::layout; -use crate::renderer; -use crate::widget::Tree; -use crate::{ - ContentFit, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, -}; - -use std::hash::Hash; - -/// Creates a new [`Viewer`] with the given image `Handle`. -pub fn viewer<Handle>(handle: Handle) -> Viewer<Handle> { - Viewer::new(handle) -} - -/// A frame that displays an image while keeping aspect ratio. -/// -/// # Example -/// -/// ``` -/// # use iced_native::widget::Image; -/// # use iced_native::image; -/// # -/// let image = Image::<image::Handle>::new("resources/ferris.png"); -/// ``` -/// -/// <img src="https://github.com/iced-rs/iced/blob/9712b319bb7a32848001b96bd84977430f14b623/examples/resources/ferris.png?raw=true" width="300"> -#[derive(Debug)] -pub struct Image<Handle> { - handle: Handle, - width: Length, - height: Length, - content_fit: ContentFit, -} - -impl<Handle> Image<Handle> { - /// Creates a new [`Image`] with the given path. - pub fn new<T: Into<Handle>>(handle: T) -> Self { - Image { - handle: handle.into(), - width: Length::Shrink, - height: Length::Shrink, - content_fit: ContentFit::Contain, - } - } - - /// Sets the width of the [`Image`] boundaries. - pub fn width(mut self, width: impl Into<Length>) -> Self { - self.width = width.into(); - self - } - - /// Sets the height of the [`Image`] boundaries. - pub fn height(mut self, height: impl Into<Length>) -> Self { - self.height = height.into(); - self - } - - /// Sets the [`ContentFit`] of the [`Image`]. - /// - /// Defaults to [`ContentFit::Contain`] - pub fn content_fit(self, content_fit: ContentFit) -> Self { - Self { - content_fit, - ..self - } - } -} - -/// Computes the layout of an [`Image`]. -pub fn layout<Renderer, Handle>( - renderer: &Renderer, - limits: &layout::Limits, - handle: &Handle, - width: Length, - height: Length, - content_fit: ContentFit, -) -> layout::Node -where - Renderer: image::Renderer<Handle = Handle>, -{ - // The raw w/h of the underlying image - let image_size = { - let Size { width, height } = renderer.dimensions(handle); - - Size::new(width as f32, height as f32) - }; - - // The size to be available to the widget prior to `Shrink`ing - let raw_size = limits.width(width).height(height).resolve(image_size); - - // The uncropped size of the image when fit to the bounds above - let full_size = content_fit.fit(image_size, raw_size); - - // Shrink the widget to fit the resized image, if requested - let final_size = Size { - width: match width { - Length::Shrink => f32::min(raw_size.width, full_size.width), - _ => raw_size.width, - }, - height: match height { - Length::Shrink => f32::min(raw_size.height, full_size.height), - _ => raw_size.height, - }, - }; - - layout::Node::new(final_size) -} - -/// Draws an [`Image`] -pub fn draw<Renderer, Handle>( - renderer: &mut Renderer, - layout: Layout<'_>, - handle: &Handle, - content_fit: ContentFit, -) where - Renderer: image::Renderer<Handle = Handle>, - Handle: Clone + Hash, -{ - let Size { width, height } = renderer.dimensions(handle); - let image_size = Size::new(width as f32, height as f32); - - let bounds = layout.bounds(); - let adjusted_fit = content_fit.fit(image_size, bounds.size()); - - let render = |renderer: &mut Renderer| { - let offset = Vector::new( - (bounds.width - adjusted_fit.width).max(0.0) / 2.0, - (bounds.height - adjusted_fit.height).max(0.0) / 2.0, - ); - - let drawing_bounds = Rectangle { - width: adjusted_fit.width, - height: adjusted_fit.height, - ..bounds - }; - - renderer.draw(handle.clone(), drawing_bounds + offset) - }; - - if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height - { - renderer.with_layer(bounds, render); - } else { - render(renderer) - } -} - -impl<Message, Renderer, Handle> Widget<Message, Renderer> for Image<Handle> -where - Renderer: image::Renderer<Handle = Handle>, - Handle: Clone + Hash, -{ - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - layout( - renderer, - limits, - &self.handle, - self.width, - self.height, - self.content_fit, - ) - } - - fn draw( - &self, - _state: &Tree, - renderer: &mut Renderer, - _theme: &Renderer::Theme, - _style: &renderer::Style, - layout: Layout<'_>, - _cursor_position: Point, - _viewport: &Rectangle, - ) { - draw(renderer, layout, &self.handle, self.content_fit) - } -} - -impl<'a, Message, Renderer, Handle> From<Image<Handle>> - for Element<'a, Message, Renderer> -where - Renderer: image::Renderer<Handle = Handle>, - Handle: Clone + Hash + 'a, -{ - fn from(image: Image<Handle>) -> Element<'a, Message, Renderer> { - Element::new(image) - } -} diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs deleted file mode 100644 index 1f8d5d7a..00000000 --- a/native/src/widget/image/viewer.rs +++ /dev/null @@ -1,428 +0,0 @@ -//! Zoom and pan on an image. -use crate::event::{self, Event}; -use crate::image; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::widget::tree::{self, Tree}; -use crate::{ - Clipboard, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size, - Vector, Widget, -}; - -use std::hash::Hash; - -/// A frame that displays an image with the ability to zoom in/out and pan. -#[allow(missing_debug_implementations)] -pub struct Viewer<Handle> { - padding: f32, - width: Length, - height: Length, - min_scale: f32, - max_scale: f32, - scale_step: f32, - handle: Handle, -} - -impl<Handle> Viewer<Handle> { - /// Creates a new [`Viewer`] with the given [`State`]. - pub fn new(handle: Handle) -> Self { - Viewer { - padding: 0.0, - width: Length::Shrink, - height: Length::Shrink, - min_scale: 0.25, - max_scale: 10.0, - scale_step: 0.10, - handle, - } - } - - /// Sets the padding of the [`Viewer`]. - pub fn padding(mut self, padding: impl Into<Pixels>) -> Self { - self.padding = padding.into().0; - self - } - - /// Sets the width of the [`Viewer`]. - pub fn width(mut self, width: impl Into<Length>) -> Self { - self.width = width.into(); - self - } - - /// Sets the height of the [`Viewer`]. - pub fn height(mut self, height: impl Into<Length>) -> Self { - self.height = height.into(); - self - } - - /// Sets the max scale applied to the image of the [`Viewer`]. - /// - /// Default is `10.0` - pub fn max_scale(mut self, max_scale: f32) -> Self { - self.max_scale = max_scale; - self - } - - /// Sets the min scale applied to the image of the [`Viewer`]. - /// - /// Default is `0.25` - pub fn min_scale(mut self, min_scale: f32) -> Self { - self.min_scale = min_scale; - self - } - - /// Sets the percentage the image of the [`Viewer`] will be scaled by - /// when zoomed in / out. - /// - /// Default is `0.10` - pub fn scale_step(mut self, scale_step: f32) -> Self { - self.scale_step = scale_step; - self - } -} - -impl<Message, Renderer, Handle> Widget<Message, Renderer> for Viewer<Handle> -where - Renderer: image::Renderer<Handle = Handle>, - Handle: Clone + Hash, -{ - fn tag(&self) -> tree::Tag { - tree::Tag::of::<State>() - } - - fn state(&self) -> tree::State { - tree::State::new(State::new()) - } - - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - let Size { width, height } = renderer.dimensions(&self.handle); - - let mut size = limits - .width(self.width) - .height(self.height) - .resolve(Size::new(width as f32, height as f32)); - - let expansion_size = if height > width { - self.width - } else { - self.height - }; - - // Only calculate viewport sizes if the images are constrained to a limited space. - // If they are Fill|Portion let them expand within their alotted space. - match expansion_size { - Length::Shrink | Length::Fixed(_) => { - let aspect_ratio = width as f32 / height as f32; - let viewport_aspect_ratio = size.width / size.height; - if viewport_aspect_ratio > aspect_ratio { - size.width = width as f32 * size.height / height as f32; - } else { - size.height = height as f32 * size.width / width as f32; - } - } - Length::Fill | Length::FillPortion(_) => {} - } - - layout::Node::new(size) - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - _clipboard: &mut dyn Clipboard, - _shell: &mut Shell<'_, Message>, - ) -> event::Status { - let bounds = layout.bounds(); - let is_mouse_over = bounds.contains(cursor_position); - - match event { - Event::Mouse(mouse::Event::WheelScrolled { delta }) - if is_mouse_over => - { - match delta { - mouse::ScrollDelta::Lines { y, .. } - | mouse::ScrollDelta::Pixels { y, .. } => { - let state = tree.state.downcast_mut::<State>(); - let previous_scale = state.scale; - - if y < 0.0 && previous_scale > self.min_scale - || y > 0.0 && previous_scale < self.max_scale - { - state.scale = (if y > 0.0 { - state.scale * (1.0 + self.scale_step) - } else { - state.scale / (1.0 + self.scale_step) - }) - .clamp(self.min_scale, self.max_scale); - - let image_size = image_size( - renderer, - &self.handle, - state, - bounds.size(), - ); - - let factor = state.scale / previous_scale - 1.0; - - let cursor_to_center = - cursor_position - bounds.center(); - - let adjustment = cursor_to_center * factor - + state.current_offset * factor; - - state.current_offset = Vector::new( - if image_size.width > bounds.width { - state.current_offset.x + adjustment.x - } else { - 0.0 - }, - if image_size.height > bounds.height { - state.current_offset.y + adjustment.y - } else { - 0.0 - }, - ); - } - } - } - - event::Status::Captured - } - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - if is_mouse_over => - { - let state = tree.state.downcast_mut::<State>(); - - state.cursor_grabbed_at = Some(cursor_position); - state.starting_offset = state.current_offset; - - event::Status::Captured - } - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => { - let state = tree.state.downcast_mut::<State>(); - - if state.cursor_grabbed_at.is_some() { - state.cursor_grabbed_at = None; - - event::Status::Captured - } else { - event::Status::Ignored - } - } - Event::Mouse(mouse::Event::CursorMoved { position }) => { - let state = tree.state.downcast_mut::<State>(); - - if let Some(origin) = state.cursor_grabbed_at { - let image_size = image_size( - renderer, - &self.handle, - state, - bounds.size(), - ); - - let hidden_width = (image_size.width - bounds.width / 2.0) - .max(0.0) - .round(); - - let hidden_height = (image_size.height - - bounds.height / 2.0) - .max(0.0) - .round(); - - let delta = position - origin; - - let x = if bounds.width < image_size.width { - (state.starting_offset.x - delta.x) - .clamp(-hidden_width, hidden_width) - } else { - 0.0 - }; - - let y = if bounds.height < image_size.height { - (state.starting_offset.y - delta.y) - .clamp(-hidden_height, hidden_height) - } else { - 0.0 - }; - - state.current_offset = Vector::new(x, y); - - event::Status::Captured - } else { - event::Status::Ignored - } - } - _ => event::Status::Ignored, - } - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - let state = tree.state.downcast_ref::<State>(); - let bounds = layout.bounds(); - let is_mouse_over = bounds.contains(cursor_position); - - if state.is_cursor_grabbed() { - mouse::Interaction::Grabbing - } else if is_mouse_over { - mouse::Interaction::Grab - } else { - mouse::Interaction::Idle - } - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - _theme: &Renderer::Theme, - _style: &renderer::Style, - layout: Layout<'_>, - _cursor_position: Point, - _viewport: &Rectangle, - ) { - let state = tree.state.downcast_ref::<State>(); - let bounds = layout.bounds(); - - let image_size = - image_size(renderer, &self.handle, state, bounds.size()); - - let translation = { - let image_top_left = Vector::new( - bounds.width / 2.0 - image_size.width / 2.0, - bounds.height / 2.0 - image_size.height / 2.0, - ); - - image_top_left - state.offset(bounds, image_size) - }; - - renderer.with_layer(bounds, |renderer| { - renderer.with_translation(translation, |renderer| { - image::Renderer::draw( - renderer, - self.handle.clone(), - Rectangle { - x: bounds.x, - y: bounds.y, - ..Rectangle::with_size(image_size) - }, - ) - }); - }); - } -} - -/// The local state of a [`Viewer`]. -#[derive(Debug, Clone, Copy)] -pub struct State { - scale: f32, - starting_offset: Vector, - current_offset: Vector, - cursor_grabbed_at: Option<Point>, -} - -impl Default for State { - fn default() -> Self { - Self { - scale: 1.0, - starting_offset: Vector::default(), - current_offset: Vector::default(), - cursor_grabbed_at: None, - } - } -} - -impl State { - /// Creates a new [`State`]. - pub fn new() -> Self { - State::default() - } - - /// Returns the current offset of the [`State`], given the bounds - /// of the [`Viewer`] and its image. - fn offset(&self, bounds: Rectangle, image_size: Size) -> Vector { - let hidden_width = - (image_size.width - bounds.width / 2.0).max(0.0).round(); - - let hidden_height = - (image_size.height - bounds.height / 2.0).max(0.0).round(); - - Vector::new( - self.current_offset.x.clamp(-hidden_width, hidden_width), - self.current_offset.y.clamp(-hidden_height, hidden_height), - ) - } - - /// Returns if the cursor is currently grabbed by the [`Viewer`]. - pub fn is_cursor_grabbed(&self) -> bool { - self.cursor_grabbed_at.is_some() - } -} - -impl<'a, Message, Renderer, Handle> From<Viewer<Handle>> - for Element<'a, Message, Renderer> -where - Renderer: 'a + image::Renderer<Handle = Handle>, - Message: 'a, - Handle: Clone + Hash + 'a, -{ - fn from(viewer: Viewer<Handle>) -> Element<'a, Message, Renderer> { - Element::new(viewer) - } -} - -/// Returns the bounds of the underlying image, given the bounds of -/// the [`Viewer`]. Scaling will be applied and original aspect ratio -/// will be respected. -pub fn image_size<Renderer>( - renderer: &Renderer, - handle: &<Renderer as image::Renderer>::Handle, - state: &State, - bounds: Size, -) -> Size -where - Renderer: image::Renderer, -{ - let Size { width, height } = renderer.dimensions(handle); - - let (width, height) = { - let dimensions = (width as f32, height as f32); - - let width_ratio = bounds.width / dimensions.0; - let height_ratio = bounds.height / dimensions.1; - - let ratio = width_ratio.min(height_ratio); - let scale = state.scale; - - if ratio < 1.0 { - (dimensions.0 * ratio * scale, dimensions.1 * ratio * scale) - } else { - (dimensions.0 * scale, dimensions.1 * scale) - } - }; - - Size::new(width, height) -} diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs deleted file mode 100644 index 53688a21..00000000 --- a/native/src/widget/operation.rs +++ /dev/null @@ -1,112 +0,0 @@ -//! Query or update internal widget state. -pub mod focusable; -pub mod scrollable; -pub mod text_input; - -pub use focusable::Focusable; -pub use scrollable::Scrollable; -pub use text_input::TextInput; - -use crate::widget::Id; - -use std::any::Any; -use std::fmt; - -/// A piece of logic that can traverse the widget tree of an application in -/// order to query or update some widget state. -pub trait Operation<T> { - /// Operates on a widget that contains other widgets. - /// - /// The `operate_on_children` function can be called to return control to - /// the widget tree and keep traversing it. - fn container( - &mut self, - id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), - ); - - /// Operates on a widget that can be focused. - fn focusable(&mut self, _state: &mut dyn Focusable, _id: Option<&Id>) {} - - /// Operates on a widget that can be scrolled. - fn scrollable(&mut self, _state: &mut dyn Scrollable, _id: Option<&Id>) {} - - /// Operates on a widget that has text input. - fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {} - - /// Operates on a custom widget with some state. - fn custom(&mut self, _state: &mut dyn Any, _id: Option<&Id>) {} - - /// Finishes the [`Operation`] and returns its [`Outcome`]. - fn finish(&self) -> Outcome<T> { - Outcome::None - } -} - -/// The result of an [`Operation`]. -pub enum Outcome<T> { - /// The [`Operation`] produced no result. - None, - - /// The [`Operation`] produced some result. - Some(T), - - /// The [`Operation`] needs to be followed by another [`Operation`]. - Chain(Box<dyn Operation<T>>), -} - -impl<T> fmt::Debug for Outcome<T> -where - T: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::None => write!(f, "Outcome::None"), - Self::Some(output) => write!(f, "Outcome::Some({output:?})"), - Self::Chain(_) => write!(f, "Outcome::Chain(...)"), - } - } -} - -/// Produces an [`Operation`] that applies the given [`Operation`] to the -/// children of a container with the given [`Id`]. -pub fn scoped<T: 'static>( - target: Id, - operation: impl Operation<T> + 'static, -) -> impl Operation<T> { - struct ScopedOperation<Message> { - target: Id, - operation: Box<dyn Operation<Message>>, - } - - impl<Message: 'static> Operation<Message> for ScopedOperation<Message> { - fn container( - &mut self, - id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation<Message>), - ) { - if id == Some(&self.target) { - operate_on_children(self.operation.as_mut()); - } else { - operate_on_children(self); - } - } - - fn finish(&self) -> Outcome<Message> { - match self.operation.finish() { - Outcome::Chain(next) => { - Outcome::Chain(Box::new(ScopedOperation { - target: self.target.clone(), - operation: next, - })) - } - outcome => outcome, - } - } - } - - ScopedOperation { - target, - operation: Box::new(operation), - } -} diff --git a/native/src/widget/operation/focusable.rs b/native/src/widget/operation/focusable.rs deleted file mode 100644 index 312e4894..00000000 --- a/native/src/widget/operation/focusable.rs +++ /dev/null @@ -1,203 +0,0 @@ -//! Operate on widgets that can be focused. -use crate::widget::operation::{Operation, Outcome}; -use crate::widget::Id; - -/// The internal state of a widget that can be focused. -pub trait Focusable { - /// Returns whether the widget is focused or not. - fn is_focused(&self) -> bool; - - /// Focuses the widget. - fn focus(&mut self); - - /// Unfocuses the widget. - fn unfocus(&mut self); -} - -/// A summary of the focusable widgets present on a widget tree. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct Count { - /// The index of the current focused widget, if any. - pub focused: Option<usize>, - - /// The total amount of focusable widgets. - pub total: usize, -} - -/// Produces an [`Operation`] that focuses the widget with the given [`Id`]. -pub fn focus<T>(target: Id) -> impl Operation<T> { - struct Focus { - target: Id, - } - - impl<T> Operation<T> for Focus { - fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { - match id { - Some(id) if id == &self.target => { - state.focus(); - } - _ => { - state.unfocus(); - } - } - } - - fn container( - &mut self, - _id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), - ) { - operate_on_children(self) - } - } - - Focus { target } -} - -/// Produces an [`Operation`] that generates a [`Count`] and chains it with the -/// provided function to build a new [`Operation`]. -pub fn count<T, O>(f: fn(Count) -> O) -> impl Operation<T> -where - O: Operation<T> + 'static, -{ - struct CountFocusable<O> { - count: Count, - next: fn(Count) -> O, - } - - impl<T, O> Operation<T> for CountFocusable<O> - where - O: Operation<T> + 'static, - { - fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) { - if state.is_focused() { - self.count.focused = Some(self.count.total); - } - - self.count.total += 1; - } - - fn container( - &mut self, - _id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), - ) { - operate_on_children(self) - } - - fn finish(&self) -> Outcome<T> { - Outcome::Chain(Box::new((self.next)(self.count))) - } - } - - CountFocusable { - count: Count::default(), - next: f, - } -} - -/// Produces an [`Operation`] that searches for the current focused widget, and -/// - if found, focuses the previous focusable widget. -/// - if not found, focuses the last focusable widget. -pub fn focus_previous<T>() -> impl Operation<T> { - struct FocusPrevious { - count: Count, - current: usize, - } - - impl<T> Operation<T> for FocusPrevious { - fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) { - if self.count.total == 0 { - return; - } - - match self.count.focused { - None if self.current == self.count.total - 1 => state.focus(), - Some(0) if self.current == 0 => state.unfocus(), - Some(0) => {} - Some(focused) if focused == self.current => state.unfocus(), - Some(focused) if focused - 1 == self.current => state.focus(), - _ => {} - } - - self.current += 1; - } - - fn container( - &mut self, - _id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), - ) { - operate_on_children(self) - } - } - - count(|count| FocusPrevious { count, current: 0 }) -} - -/// Produces an [`Operation`] that searches for the current focused widget, and -/// - if found, focuses the next focusable widget. -/// - if not found, focuses the first focusable widget. -pub fn focus_next<T>() -> impl Operation<T> { - struct FocusNext { - count: Count, - current: usize, - } - - impl<T> Operation<T> for FocusNext { - fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) { - match self.count.focused { - None if self.current == 0 => state.focus(), - Some(focused) if focused == self.current => state.unfocus(), - Some(focused) if focused + 1 == self.current => state.focus(), - _ => {} - } - - self.current += 1; - } - - fn container( - &mut self, - _id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), - ) { - operate_on_children(self) - } - } - - count(|count| FocusNext { count, current: 0 }) -} - -/// Produces an [`Operation`] that searches for the current focused widget -/// and stores its ID. This ignores widgets that do not have an ID. -pub fn find_focused() -> impl Operation<Id> { - struct FindFocused { - focused: Option<Id>, - } - - impl Operation<Id> for FindFocused { - fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { - if state.is_focused() && id.is_some() { - self.focused = id.cloned(); - } - } - - fn container( - &mut self, - _id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation<Id>), - ) { - operate_on_children(self) - } - - fn finish(&self) -> Outcome<Id> { - if let Some(id) = &self.focused { - Outcome::Some(id.clone()) - } else { - Outcome::None - } - } - } - - FindFocused { focused: None } -} diff --git a/native/src/widget/operation/scrollable.rs b/native/src/widget/operation/scrollable.rs deleted file mode 100644 index 3b20631f..00000000 --- a/native/src/widget/operation/scrollable.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! Operate on widgets that can be scrolled. -use crate::widget::{Id, Operation}; - -/// The internal state of a widget that can be scrolled. -pub trait Scrollable { - /// Snaps the scroll of the widget to the given `percentage` along the horizontal & vertical axis. - fn snap_to(&mut self, offset: RelativeOffset); -} - -/// Produces an [`Operation`] that snaps the widget with the given [`Id`] to -/// the provided `percentage`. -pub fn snap_to<T>(target: Id, offset: RelativeOffset) -> impl Operation<T> { - struct SnapTo { - target: Id, - offset: RelativeOffset, - } - - impl<T> Operation<T> for SnapTo { - fn container( - &mut self, - _id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), - ) { - operate_on_children(self) - } - - fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) { - if Some(&self.target) == id { - state.snap_to(self.offset); - } - } - } - - SnapTo { target, offset } -} - -/// The amount of offset in each direction of a [`Scrollable`]. -/// -/// A value of `0.0` means start, while `1.0` means end. -#[derive(Debug, Clone, Copy, PartialEq, Default)] -pub struct RelativeOffset { - /// The amount of horizontal offset - pub x: f32, - /// The amount of vertical offset - pub y: f32, -} - -impl RelativeOffset { - /// A relative offset that points to the top-left of a [`Scrollable`]. - pub const START: Self = Self { x: 0.0, y: 0.0 }; - - /// A relative offset that points to the bottom-right of a [`Scrollable`]. - pub const END: Self = Self { x: 1.0, y: 1.0 }; -} diff --git a/native/src/widget/operation/text_input.rs b/native/src/widget/operation/text_input.rs deleted file mode 100644 index 4c773e99..00000000 --- a/native/src/widget/operation/text_input.rs +++ /dev/null @@ -1,131 +0,0 @@ -//! Operate on widgets that have text input. -use crate::widget::operation::Operation; -use crate::widget::Id; - -/// The internal state of a widget that has text input. -pub trait TextInput { - /// Moves the cursor of the text input to the front of the input text. - fn move_cursor_to_front(&mut self); - /// Moves the cursor of the text input to the end of the input text. - fn move_cursor_to_end(&mut self); - /// Moves the cursor of the text input to an arbitrary location. - fn move_cursor_to(&mut self, position: usize); - /// Selects all the content of the text input. - fn select_all(&mut self); -} - -/// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the -/// front. -pub fn move_cursor_to_front<T>(target: Id) -> impl Operation<T> { - struct MoveCursor { - target: Id, - } - - impl<T> Operation<T> for MoveCursor { - fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { - match id { - Some(id) if id == &self.target => { - state.move_cursor_to_front(); - } - _ => {} - } - } - - fn container( - &mut self, - _id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), - ) { - operate_on_children(self) - } - } - - MoveCursor { target } -} - -/// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the -/// end. -pub fn move_cursor_to_end<T>(target: Id) -> impl Operation<T> { - struct MoveCursor { - target: Id, - } - - impl<T> Operation<T> for MoveCursor { - fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { - match id { - Some(id) if id == &self.target => { - state.move_cursor_to_end(); - } - _ => {} - } - } - - fn container( - &mut self, - _id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), - ) { - operate_on_children(self) - } - } - - MoveCursor { target } -} - -/// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the -/// provided position. -pub fn move_cursor_to<T>(target: Id, position: usize) -> impl Operation<T> { - struct MoveCursor { - target: Id, - position: usize, - } - - impl<T> Operation<T> for MoveCursor { - fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { - match id { - Some(id) if id == &self.target => { - state.move_cursor_to(self.position); - } - _ => {} - } - } - - fn container( - &mut self, - _id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), - ) { - operate_on_children(self) - } - } - - MoveCursor { target, position } -} - -/// Produces an [`Operation`] that selects all the content of the widget with the given [`Id`]. -pub fn select_all<T>(target: Id) -> impl Operation<T> { - struct MoveCursor { - target: Id, - } - - impl<T> Operation<T> for MoveCursor { - fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { - match id { - Some(id) if id == &self.target => { - state.select_all(); - } - _ => {} - } - } - - fn container( - &mut self, - _id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), - ) { - operate_on_children(self) - } - } - - MoveCursor { target } -} diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs deleted file mode 100644 index bcb17ebd..00000000 --- a/native/src/widget/pane_grid.rs +++ /dev/null @@ -1,991 +0,0 @@ -//! Let your users split regions of your application and organize layout dynamically. -//! -//! [](https://gfycat.com/mixedflatjellyfish) -//! -//! # Example -//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing, -//! drag and drop, and hotkey support. -//! -//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.8/examples/pane_grid -mod axis; -mod configuration; -mod content; -mod direction; -mod draggable; -mod node; -mod pane; -mod split; -mod title_bar; - -pub mod state; - -pub use axis::Axis; -pub use configuration::Configuration; -pub use content::Content; -pub use direction::Direction; -pub use draggable::Draggable; -pub use node::Node; -pub use pane::Pane; -pub use split::Split; -pub use state::State; -pub use title_bar::TitleBar; - -pub use iced_style::pane_grid::{Line, StyleSheet}; - -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay::{self, Group}; -use crate::renderer; -use crate::touch; -use crate::widget; -use crate::widget::container; -use crate::widget::tree::{self, Tree}; -use crate::{ - Clipboard, Color, Element, Layout, Length, Pixels, Point, Rectangle, Shell, - Size, Vector, Widget, -}; - -/// A collection of panes distributed using either vertical or horizontal splits -/// to completely fill the space available. -/// -/// [](https://gfycat.com/frailfreshairedaleterrier) -/// -/// This distribution of space is common in tiling window managers (like -/// [`awesome`](https://awesomewm.org/), [`i3`](https://i3wm.org/), or even -/// [`tmux`](https://github.com/tmux/tmux)). -/// -/// A [`PaneGrid`] supports: -/// -/// * Vertical and horizontal splits -/// * Tracking of the last active pane -/// * Mouse-based resizing -/// * Drag and drop to reorganize panes -/// * Hotkey support -/// * Configurable modifier keys -/// * [`State`] API to perform actions programmatically (`split`, `swap`, `resize`, etc.) -/// -/// ## Example -/// -/// ``` -/// # use iced_native::widget::{pane_grid, text}; -/// # -/// # type PaneGrid<'a, Message> = -/// # iced_native::widget::PaneGrid<'a, Message, iced_native::renderer::Null>; -/// # -/// enum PaneState { -/// SomePane, -/// AnotherKindOfPane, -/// } -/// -/// enum Message { -/// PaneDragged(pane_grid::DragEvent), -/// PaneResized(pane_grid::ResizeEvent), -/// } -/// -/// let (mut state, _) = pane_grid::State::new(PaneState::SomePane); -/// -/// let pane_grid = -/// PaneGrid::new(&state, |pane, state, is_maximized| { -/// pane_grid::Content::new(match state { -/// PaneState::SomePane => text("This is some pane"), -/// PaneState::AnotherKindOfPane => text("This is another kind of pane"), -/// }) -/// }) -/// .on_drag(Message::PaneDragged) -/// .on_resize(10, Message::PaneResized); -/// ``` -#[allow(missing_debug_implementations)] -pub struct PaneGrid<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet + container::StyleSheet, -{ - contents: Contents<'a, Content<'a, Message, Renderer>>, - width: Length, - height: Length, - spacing: f32, - on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>, - on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, - on_resize: Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>, - style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet + container::StyleSheet, -{ - /// Creates a [`PaneGrid`] with the given [`State`] and view function. - /// - /// The view function will be called to display each [`Pane`] present in the - /// [`State`]. [`bool`] is set if the pane is maximized. - pub fn new<T>( - state: &'a State<T>, - view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Renderer>, - ) -> Self { - let contents = if let Some((pane, pane_state)) = - state.maximized.and_then(|pane| { - state.panes.get(&pane).map(|pane_state| (pane, pane_state)) - }) { - Contents::Maximized( - pane, - view(pane, pane_state, true), - Node::Pane(pane), - ) - } else { - Contents::All( - state - .panes - .iter() - .map(|(pane, pane_state)| { - (*pane, view(*pane, pane_state, false)) - }) - .collect(), - &state.internal, - ) - }; - - Self { - contents, - width: Length::Fill, - height: Length::Fill, - spacing: 0.0, - on_click: None, - on_drag: None, - on_resize: None, - style: Default::default(), - } - } - - /// Sets the width of the [`PaneGrid`]. - pub fn width(mut self, width: impl Into<Length>) -> Self { - self.width = width.into(); - self - } - - /// Sets the height of the [`PaneGrid`]. - pub fn height(mut self, height: impl Into<Length>) -> Self { - self.height = height.into(); - self - } - - /// Sets the spacing _between_ the panes of the [`PaneGrid`]. - pub fn spacing(mut self, amount: impl Into<Pixels>) -> Self { - self.spacing = amount.into().0; - self - } - - /// Sets the message that will be produced when a [`Pane`] of the - /// [`PaneGrid`] is clicked. - pub fn on_click<F>(mut self, f: F) -> Self - where - F: 'a + Fn(Pane) -> Message, - { - self.on_click = Some(Box::new(f)); - self - } - - /// Enables the drag and drop interactions of the [`PaneGrid`], which will - /// use the provided function to produce messages. - pub fn on_drag<F>(mut self, f: F) -> Self - where - F: 'a + Fn(DragEvent) -> Message, - { - self.on_drag = Some(Box::new(f)); - self - } - - /// Enables the resize interactions of the [`PaneGrid`], which will - /// use the provided function to produce messages. - /// - /// The `leeway` describes the amount of space around a split that can be - /// used to grab it. - /// - /// The grabbable area of a split will have a length of `spacing + leeway`, - /// properly centered. In other words, a length of - /// `(spacing + leeway) / 2.0` on either side of the split line. - pub fn on_resize<F>(mut self, leeway: impl Into<Pixels>, f: F) -> Self - where - F: 'a + Fn(ResizeEvent) -> Message, - { - self.on_resize = Some((leeway.into().0, Box::new(f))); - self - } - - /// Sets the style of the [`PaneGrid`]. - pub fn style( - mut self, - style: impl Into<<Renderer::Theme as StyleSheet>::Style>, - ) -> Self { - self.style = style.into(); - self - } - - fn drag_enabled(&self) -> bool { - (!self.contents.is_maximized()) - .then(|| self.on_drag.is_some()) - .unwrap_or_default() - } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> - for PaneGrid<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet + container::StyleSheet, -{ - fn tag(&self) -> tree::Tag { - tree::Tag::of::<state::Action>() - } - - fn state(&self) -> tree::State { - tree::State::new(state::Action::Idle) - } - - fn children(&self) -> Vec<Tree> { - self.contents - .iter() - .map(|(_, content)| content.state()) - .collect() - } - - fn diff(&self, tree: &mut Tree) { - match &self.contents { - Contents::All(contents, _) => tree.diff_children_custom( - contents, - |state, (_, content)| content.diff(state), - |(_, content)| content.state(), - ), - Contents::Maximized(_, content, _) => tree.diff_children_custom( - &[content], - |state, content| content.diff(state), - |content| content.state(), - ), - } - } - - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - layout( - renderer, - limits, - self.contents.layout(), - self.width, - self.height, - self.spacing, - self.contents.iter(), - |content, renderer, limits| content.layout(renderer, limits), - ) - } - - fn operate( - &self, - tree: &mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn widget::Operation<Message>, - ) { - operation.container(None, &mut |operation| { - self.contents - .iter() - .zip(&mut tree.children) - .zip(layout.children()) - .for_each(|(((_pane, content), state), layout)| { - content.operate(state, layout, renderer, operation); - }) - }); - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - let action = tree.state.downcast_mut::<state::Action>(); - - let on_drag = if self.drag_enabled() { - &self.on_drag - } else { - &None - }; - - let event_status = update( - action, - self.contents.layout(), - &event, - layout, - cursor_position, - shell, - self.spacing, - self.contents.iter(), - &self.on_click, - on_drag, - &self.on_resize, - ); - - let picked_pane = action.picked_pane().map(|(pane, _)| pane); - - self.contents - .iter_mut() - .zip(&mut tree.children) - .zip(layout.children()) - .map(|(((pane, content), tree), layout)| { - let is_picked = picked_pane == Some(pane); - - content.on_event( - tree, - event.clone(), - layout, - cursor_position, - renderer, - clipboard, - shell, - is_picked, - ) - }) - .fold(event_status, event::Status::merge) - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - mouse_interaction( - tree.state.downcast_ref(), - self.contents.layout(), - layout, - cursor_position, - self.spacing, - self.on_resize.as_ref().map(|(leeway, _)| *leeway), - ) - .unwrap_or_else(|| { - self.contents - .iter() - .zip(&tree.children) - .zip(layout.children()) - .map(|(((_pane, content), tree), layout)| { - content.mouse_interaction( - tree, - layout, - cursor_position, - viewport, - renderer, - self.drag_enabled(), - ) - }) - .max() - .unwrap_or_default() - }) - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ) { - draw( - tree.state.downcast_ref(), - self.contents.layout(), - layout, - cursor_position, - renderer, - theme, - style, - viewport, - self.spacing, - self.on_resize.as_ref().map(|(leeway, _)| *leeway), - &self.style, - self.contents - .iter() - .zip(&tree.children) - .map(|((pane, content), tree)| (pane, (content, tree))), - |(content, tree), - renderer, - style, - layout, - cursor_position, - rectangle| { - content.draw( - tree, - renderer, - theme, - style, - layout, - cursor_position, - rectangle, - ); - }, - ) - } - - fn overlay<'b>( - &'b mut self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option<overlay::Element<'_, Message, Renderer>> { - let children = self - .contents - .iter_mut() - .zip(&mut tree.children) - .zip(layout.children()) - .filter_map(|(((_, content), state), layout)| { - content.overlay(state, layout, renderer) - }) - .collect::<Vec<_>>(); - - (!children.is_empty()).then(|| Group::with_children(children).overlay()) - } -} - -impl<'a, Message, Renderer> From<PaneGrid<'a, Message, Renderer>> - for Element<'a, Message, Renderer> -where - Message: 'a, - Renderer: 'a + crate::Renderer, - Renderer::Theme: StyleSheet + container::StyleSheet, -{ - fn from( - pane_grid: PaneGrid<'a, Message, Renderer>, - ) -> Element<'a, Message, Renderer> { - Element::new(pane_grid) - } -} - -/// Calculates the [`Layout`] of a [`PaneGrid`]. -pub fn layout<Renderer, T>( - renderer: &Renderer, - limits: &layout::Limits, - node: &Node, - width: Length, - height: Length, - spacing: f32, - contents: impl Iterator<Item = (Pane, T)>, - layout_content: impl Fn(T, &Renderer, &layout::Limits) -> layout::Node, -) -> layout::Node { - let limits = limits.width(width).height(height); - let size = limits.resolve(Size::ZERO); - - let regions = node.pane_regions(spacing, size); - let children = contents - .filter_map(|(pane, content)| { - let region = regions.get(&pane)?; - let size = Size::new(region.width, region.height); - - let mut node = layout_content( - content, - renderer, - &layout::Limits::new(size, size), - ); - - node.move_to(Point::new(region.x, region.y)); - - Some(node) - }) - .collect(); - - layout::Node::with_children(size, children) -} - -/// Processes an [`Event`] and updates the [`state`] of a [`PaneGrid`] -/// accordingly. -pub fn update<'a, Message, T: Draggable>( - action: &mut state::Action, - node: &Node, - event: &Event, - layout: Layout<'_>, - cursor_position: Point, - shell: &mut Shell<'_, Message>, - spacing: f32, - contents: impl Iterator<Item = (Pane, T)>, - on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>, - on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, - on_resize: &Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>, -) -> event::Status { - let mut event_status = event::Status::Ignored; - - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - let bounds = layout.bounds(); - - if bounds.contains(cursor_position) { - event_status = event::Status::Captured; - - match on_resize { - Some((leeway, _)) => { - let relative_cursor = Point::new( - cursor_position.x - bounds.x, - cursor_position.y - bounds.y, - ); - - let splits = node.split_regions( - spacing, - Size::new(bounds.width, bounds.height), - ); - - let clicked_split = hovered_split( - splits.iter(), - spacing + leeway, - relative_cursor, - ); - - if let Some((split, axis, _)) = clicked_split { - if action.picked_pane().is_none() { - *action = - state::Action::Resizing { split, axis }; - } - } else { - click_pane( - action, - layout, - cursor_position, - shell, - contents, - on_click, - on_drag, - ); - } - } - None => { - click_pane( - action, - layout, - cursor_position, - shell, - contents, - on_click, - on_drag, - ); - } - } - } - } - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) - | Event::Touch(touch::Event::FingerLost { .. }) => { - if let Some((pane, _)) = action.picked_pane() { - if let Some(on_drag) = on_drag { - let mut dropped_region = contents - .zip(layout.children()) - .filter(|(_, layout)| { - layout.bounds().contains(cursor_position) - }); - - let event = match dropped_region.next() { - Some(((target, _), _)) if pane != target => { - DragEvent::Dropped { pane, target } - } - _ => DragEvent::Canceled { pane }, - }; - - shell.publish(on_drag(event)); - } - - *action = state::Action::Idle; - - event_status = event::Status::Captured; - } else if action.picked_split().is_some() { - *action = state::Action::Idle; - - event_status = event::Status::Captured; - } - } - Event::Mouse(mouse::Event::CursorMoved { .. }) - | Event::Touch(touch::Event::FingerMoved { .. }) => { - if let Some((_, on_resize)) = on_resize { - if let Some((split, _)) = action.picked_split() { - let bounds = layout.bounds(); - - let splits = node.split_regions( - spacing, - Size::new(bounds.width, bounds.height), - ); - - if let Some((axis, rectangle, _)) = splits.get(&split) { - let ratio = match axis { - Axis::Horizontal => { - let position = - cursor_position.y - bounds.y - rectangle.y; - - (position / rectangle.height).clamp(0.1, 0.9) - } - Axis::Vertical => { - let position = - cursor_position.x - bounds.x - rectangle.x; - - (position / rectangle.width).clamp(0.1, 0.9) - } - }; - - shell.publish(on_resize(ResizeEvent { split, ratio })); - - event_status = event::Status::Captured; - } - } - } - } - _ => {} - } - - event_status -} - -fn click_pane<'a, Message, T>( - action: &mut state::Action, - layout: Layout<'_>, - cursor_position: Point, - shell: &mut Shell<'_, Message>, - contents: impl Iterator<Item = (Pane, T)>, - on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>, - on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, -) where - T: Draggable, -{ - let mut clicked_region = contents - .zip(layout.children()) - .filter(|(_, layout)| layout.bounds().contains(cursor_position)); - - if let Some(((pane, content), layout)) = clicked_region.next() { - if let Some(on_click) = &on_click { - shell.publish(on_click(pane)); - } - - if let Some(on_drag) = &on_drag { - if content.can_be_dragged_at(layout, cursor_position) { - let pane_position = layout.position(); - - let origin = cursor_position - - Vector::new(pane_position.x, pane_position.y); - - *action = state::Action::Dragging { pane, origin }; - - shell.publish(on_drag(DragEvent::Picked { pane })); - } - } - } -} - -/// Returns the current [`mouse::Interaction`] of a [`PaneGrid`]. -pub fn mouse_interaction( - action: &state::Action, - node: &Node, - layout: Layout<'_>, - cursor_position: Point, - spacing: f32, - resize_leeway: Option<f32>, -) -> Option<mouse::Interaction> { - if action.picked_pane().is_some() { - return Some(mouse::Interaction::Grabbing); - } - - let resize_axis = - action.picked_split().map(|(_, axis)| axis).or_else(|| { - resize_leeway.and_then(|leeway| { - let bounds = layout.bounds(); - - let splits = node.split_regions(spacing, bounds.size()); - - let relative_cursor = Point::new( - cursor_position.x - bounds.x, - cursor_position.y - bounds.y, - ); - - hovered_split(splits.iter(), spacing + leeway, relative_cursor) - .map(|(_, axis, _)| axis) - }) - }); - - if let Some(resize_axis) = resize_axis { - return Some(match resize_axis { - Axis::Horizontal => mouse::Interaction::ResizingVertically, - Axis::Vertical => mouse::Interaction::ResizingHorizontally, - }); - } - - None -} - -/// Draws a [`PaneGrid`]. -pub fn draw<Renderer, T>( - action: &state::Action, - node: &Node, - layout: Layout<'_>, - cursor_position: Point, - renderer: &mut Renderer, - theme: &Renderer::Theme, - default_style: &renderer::Style, - viewport: &Rectangle, - spacing: f32, - resize_leeway: Option<f32>, - style: &<Renderer::Theme as StyleSheet>::Style, - contents: impl Iterator<Item = (Pane, T)>, - draw_pane: impl Fn( - T, - &mut Renderer, - &renderer::Style, - Layout<'_>, - Point, - &Rectangle, - ), -) where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - let picked_pane = action.picked_pane(); - - let picked_split = action - .picked_split() - .and_then(|(split, axis)| { - let bounds = layout.bounds(); - - let splits = node.split_regions(spacing, bounds.size()); - - let (_axis, region, ratio) = splits.get(&split)?; - - let region = axis.split_line_bounds(*region, *ratio, spacing); - - Some((axis, region + Vector::new(bounds.x, bounds.y), true)) - }) - .or_else(|| match resize_leeway { - Some(leeway) => { - let bounds = layout.bounds(); - - let relative_cursor = Point::new( - cursor_position.x - bounds.x, - cursor_position.y - bounds.y, - ); - - let splits = node.split_regions(spacing, bounds.size()); - - let (_split, axis, region) = hovered_split( - splits.iter(), - spacing + leeway, - relative_cursor, - )?; - - Some((axis, region + Vector::new(bounds.x, bounds.y), false)) - } - None => None, - }); - - let pane_cursor_position = if picked_pane.is_some() { - // TODO: Remove once cursor availability is encoded in the type - // system - Point::new(-1.0, -1.0) - } else { - cursor_position - }; - - let mut render_picked_pane = None; - - for ((id, pane), layout) in contents.zip(layout.children()) { - match picked_pane { - Some((dragging, origin)) if id == dragging => { - render_picked_pane = Some((pane, origin, layout)); - } - _ => { - draw_pane( - pane, - renderer, - default_style, - layout, - pane_cursor_position, - viewport, - ); - } - } - } - - // Render picked pane last - if let Some((pane, origin, layout)) = render_picked_pane { - let bounds = layout.bounds(); - - renderer.with_translation( - cursor_position - - Point::new(bounds.x + origin.x, bounds.y + origin.y), - |renderer| { - renderer.with_layer(bounds, |renderer| { - draw_pane( - pane, - renderer, - default_style, - layout, - pane_cursor_position, - viewport, - ); - }); - }, - ); - }; - - if let Some((axis, split_region, is_picked)) = picked_split { - let highlight = if is_picked { - theme.picked_split(style) - } else { - theme.hovered_split(style) - }; - - if let Some(highlight) = highlight { - renderer.fill_quad( - renderer::Quad { - bounds: match axis { - Axis::Horizontal => Rectangle { - x: split_region.x, - y: (split_region.y - + (split_region.height - highlight.width) - / 2.0) - .round(), - width: split_region.width, - height: highlight.width, - }, - Axis::Vertical => Rectangle { - x: (split_region.x - + (split_region.width - highlight.width) / 2.0) - .round(), - y: split_region.y, - width: highlight.width, - height: split_region.height, - }, - }, - border_radius: 0.0.into(), - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - highlight.color, - ); - } - } -} - -/// An event produced during a drag and drop interaction of a [`PaneGrid`]. -#[derive(Debug, Clone, Copy)] -pub enum DragEvent { - /// A [`Pane`] was picked for dragging. - Picked { - /// The picked [`Pane`]. - pane: Pane, - }, - - /// A [`Pane`] was dropped on top of another [`Pane`]. - Dropped { - /// The picked [`Pane`]. - pane: Pane, - - /// The [`Pane`] where the picked one was dropped on. - target: Pane, - }, - - /// A [`Pane`] was picked and then dropped outside of other [`Pane`] - /// boundaries. - Canceled { - /// The picked [`Pane`]. - pane: Pane, - }, -} - -/// An event produced during a resize interaction of a [`PaneGrid`]. -#[derive(Debug, Clone, Copy)] -pub struct ResizeEvent { - /// The [`Split`] that is being dragged for resizing. - pub split: Split, - - /// The new ratio of the [`Split`]. - /// - /// The ratio is a value in [0, 1], representing the exact position of a - /// [`Split`] between two panes. - pub ratio: f32, -} - -/* - * Helpers - */ -fn hovered_split<'a>( - splits: impl Iterator<Item = (&'a Split, &'a (Axis, Rectangle, f32))>, - spacing: f32, - cursor_position: Point, -) -> Option<(Split, Axis, Rectangle)> { - splits - .filter_map(|(split, (axis, region, ratio))| { - let bounds = axis.split_line_bounds(*region, *ratio, spacing); - - if bounds.contains(cursor_position) { - Some((*split, *axis, bounds)) - } else { - None - } - }) - .next() -} - -/// The visible contents of the [`PaneGrid`] -#[derive(Debug)] -pub enum Contents<'a, T> { - /// All panes are visible - All(Vec<(Pane, T)>, &'a state::Internal), - /// A maximized pane is visible - Maximized(Pane, T, Node), -} - -impl<'a, T> Contents<'a, T> { - /// Returns the layout [`Node`] of the [`Contents`] - pub fn layout(&self) -> &Node { - match self { - Contents::All(_, state) => state.layout(), - Contents::Maximized(_, _, layout) => layout, - } - } - - /// Returns an iterator over the values of the [`Contents`] - pub fn iter(&self) -> Box<dyn Iterator<Item = (Pane, &T)> + '_> { - match self { - Contents::All(contents, _) => Box::new( - contents.iter().map(|(pane, content)| (*pane, content)), - ), - Contents::Maximized(pane, content, _) => { - Box::new(std::iter::once((*pane, content))) - } - } - } - - fn iter_mut(&mut self) -> Box<dyn Iterator<Item = (Pane, &mut T)> + '_> { - match self { - Contents::All(contents, _) => Box::new( - contents.iter_mut().map(|(pane, content)| (*pane, content)), - ), - Contents::Maximized(pane, content, _) => { - Box::new(std::iter::once((*pane, content))) - } - } - } - - fn is_maximized(&self) -> bool { - matches!(self, Self::Maximized(..)) - } -} diff --git a/native/src/widget/pane_grid/axis.rs b/native/src/widget/pane_grid/axis.rs deleted file mode 100644 index 02bde064..00000000 --- a/native/src/widget/pane_grid/axis.rs +++ /dev/null @@ -1,241 +0,0 @@ -use crate::Rectangle; - -/// A fixed reference line for the measurement of coordinates. -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub enum Axis { - /// The horizontal axis: — - Horizontal, - /// The vertical axis: | - Vertical, -} - -impl Axis { - /// Splits the provided [`Rectangle`] on the current [`Axis`] with the - /// given `ratio` and `spacing`. - pub fn split( - &self, - rectangle: &Rectangle, - ratio: f32, - spacing: f32, - ) -> (Rectangle, Rectangle) { - match self { - Axis::Horizontal => { - let height_top = - (rectangle.height * ratio - spacing / 2.0).round(); - let height_bottom = rectangle.height - height_top - spacing; - - ( - Rectangle { - height: height_top, - ..*rectangle - }, - Rectangle { - y: rectangle.y + height_top + spacing, - height: height_bottom, - ..*rectangle - }, - ) - } - Axis::Vertical => { - let width_left = - (rectangle.width * ratio - spacing / 2.0).round(); - let width_right = rectangle.width - width_left - spacing; - - ( - Rectangle { - width: width_left, - ..*rectangle - }, - Rectangle { - x: rectangle.x + width_left + spacing, - width: width_right, - ..*rectangle - }, - ) - } - } - } - - /// Calculates the bounds of the split line in a [`Rectangle`] region. - pub fn split_line_bounds( - &self, - rectangle: Rectangle, - ratio: f32, - spacing: f32, - ) -> Rectangle { - match self { - Axis::Horizontal => Rectangle { - x: rectangle.x, - y: (rectangle.y + rectangle.height * ratio - spacing / 2.0) - .round(), - width: rectangle.width, - height: spacing, - }, - Axis::Vertical => Rectangle { - x: (rectangle.x + rectangle.width * ratio - spacing / 2.0) - .round(), - y: rectangle.y, - width: spacing, - height: rectangle.height, - }, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - enum Case { - Horizontal { - overall_height: f32, - spacing: f32, - top_height: f32, - bottom_y: f32, - bottom_height: f32, - }, - Vertical { - overall_width: f32, - spacing: f32, - left_width: f32, - right_x: f32, - right_width: f32, - }, - } - - #[test] - fn split() { - let cases = vec![ - // Even height, even spacing - Case::Horizontal { - overall_height: 10.0, - spacing: 2.0, - top_height: 4.0, - bottom_y: 6.0, - bottom_height: 4.0, - }, - // Odd height, even spacing - Case::Horizontal { - overall_height: 9.0, - spacing: 2.0, - top_height: 4.0, - bottom_y: 6.0, - bottom_height: 3.0, - }, - // Even height, odd spacing - Case::Horizontal { - overall_height: 10.0, - spacing: 1.0, - top_height: 5.0, - bottom_y: 6.0, - bottom_height: 4.0, - }, - // Odd height, odd spacing - Case::Horizontal { - overall_height: 9.0, - spacing: 1.0, - top_height: 4.0, - bottom_y: 5.0, - bottom_height: 4.0, - }, - // Even width, even spacing - Case::Vertical { - overall_width: 10.0, - spacing: 2.0, - left_width: 4.0, - right_x: 6.0, - right_width: 4.0, - }, - // Odd width, even spacing - Case::Vertical { - overall_width: 9.0, - spacing: 2.0, - left_width: 4.0, - right_x: 6.0, - right_width: 3.0, - }, - // Even width, odd spacing - Case::Vertical { - overall_width: 10.0, - spacing: 1.0, - left_width: 5.0, - right_x: 6.0, - right_width: 4.0, - }, - // Odd width, odd spacing - Case::Vertical { - overall_width: 9.0, - spacing: 1.0, - left_width: 4.0, - right_x: 5.0, - right_width: 4.0, - }, - ]; - for case in cases { - match case { - Case::Horizontal { - overall_height, - spacing, - top_height, - bottom_y, - bottom_height, - } => { - let a = Axis::Horizontal; - let r = Rectangle { - x: 0.0, - y: 0.0, - width: 10.0, - height: overall_height, - }; - let (top, bottom) = a.split(&r, 0.5, spacing); - assert_eq!( - top, - Rectangle { - height: top_height, - ..r - } - ); - assert_eq!( - bottom, - Rectangle { - y: bottom_y, - height: bottom_height, - ..r - } - ); - } - Case::Vertical { - overall_width, - spacing, - left_width, - right_x, - right_width, - } => { - let a = Axis::Vertical; - let r = Rectangle { - x: 0.0, - y: 0.0, - width: overall_width, - height: 10.0, - }; - let (left, right) = a.split(&r, 0.5, spacing); - assert_eq!( - left, - Rectangle { - width: left_width, - ..r - } - ); - assert_eq!( - right, - Rectangle { - x: right_x, - width: right_width, - ..r - } - ); - } - } - } - } -} diff --git a/native/src/widget/pane_grid/configuration.rs b/native/src/widget/pane_grid/configuration.rs deleted file mode 100644 index 7d68fb46..00000000 --- a/native/src/widget/pane_grid/configuration.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::widget::pane_grid::Axis; - -/// The arrangement of a [`PaneGrid`]. -/// -/// [`PaneGrid`]: crate::widget::PaneGrid -#[derive(Debug, Clone)] -pub enum Configuration<T> { - /// A split of the available space. - Split { - /// The direction of the split. - axis: Axis, - - /// The ratio of the split in [0.0, 1.0]. - ratio: f32, - - /// The left/top [`Configuration`] of the split. - a: Box<Configuration<T>>, - - /// The right/bottom [`Configuration`] of the split. - b: Box<Configuration<T>>, - }, - /// A [`Pane`]. - /// - /// [`Pane`]: crate::widget::pane_grid::Pane - Pane(T), -} diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs deleted file mode 100644 index c9b0df07..00000000 --- a/native/src/widget/pane_grid/content.rs +++ /dev/null @@ -1,373 +0,0 @@ -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::widget::container; -use crate::widget::pane_grid::{Draggable, TitleBar}; -use crate::widget::{self, Tree}; -use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; - -/// The content of a [`Pane`]. -/// -/// [`Pane`]: crate::widget::pane_grid::Pane -#[allow(missing_debug_implementations)] -pub struct Content<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: container::StyleSheet, -{ - title_bar: Option<TitleBar<'a, Message, Renderer>>, - body: Element<'a, Message, Renderer>, - style: <Renderer::Theme as container::StyleSheet>::Style, -} - -impl<'a, Message, Renderer> Content<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: container::StyleSheet, -{ - /// Creates a new [`Content`] with the provided body. - pub fn new(body: impl Into<Element<'a, Message, Renderer>>) -> Self { - Self { - title_bar: None, - body: body.into(), - style: Default::default(), - } - } - - /// Sets the [`TitleBar`] of this [`Content`]. - pub fn title_bar( - mut self, - title_bar: TitleBar<'a, Message, Renderer>, - ) -> Self { - self.title_bar = Some(title_bar); - self - } - - /// Sets the style of the [`Content`]. - pub fn style( - mut self, - style: impl Into<<Renderer::Theme as container::StyleSheet>::Style>, - ) -> Self { - self.style = style.into(); - self - } -} - -impl<'a, Message, Renderer> Content<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: container::StyleSheet, -{ - pub(super) fn state(&self) -> Tree { - let children = if let Some(title_bar) = self.title_bar.as_ref() { - vec![Tree::new(&self.body), title_bar.state()] - } else { - vec![Tree::new(&self.body), Tree::empty()] - }; - - Tree { - children, - ..Tree::empty() - } - } - - pub(super) fn diff(&self, tree: &mut Tree) { - if tree.children.len() == 2 { - if let Some(title_bar) = self.title_bar.as_ref() { - title_bar.diff(&mut tree.children[1]); - } - - tree.children[0].diff(&self.body); - } else { - *tree = self.state(); - } - } - - /// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`]. - /// - /// [`Renderer`]: crate::Renderer - pub fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ) { - use container::StyleSheet; - - let bounds = layout.bounds(); - - { - let style = theme.appearance(&self.style); - - container::draw_background(renderer, &style, bounds); - } - - if let Some(title_bar) = &self.title_bar { - let mut children = layout.children(); - let title_bar_layout = children.next().unwrap(); - let body_layout = children.next().unwrap(); - - let show_controls = bounds.contains(cursor_position); - - self.body.as_widget().draw( - &tree.children[0], - renderer, - theme, - style, - body_layout, - cursor_position, - viewport, - ); - - title_bar.draw( - &tree.children[1], - renderer, - theme, - style, - title_bar_layout, - cursor_position, - viewport, - show_controls, - ); - } else { - self.body.as_widget().draw( - &tree.children[0], - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ); - } - } - - pub(crate) fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - if let Some(title_bar) = &self.title_bar { - let max_size = limits.max(); - - let title_bar_layout = title_bar - .layout(renderer, &layout::Limits::new(Size::ZERO, max_size)); - - let title_bar_size = title_bar_layout.size(); - - let mut body_layout = self.body.as_widget().layout( - renderer, - &layout::Limits::new( - Size::ZERO, - Size::new( - max_size.width, - max_size.height - title_bar_size.height, - ), - ), - ); - - body_layout.move_to(Point::new(0.0, title_bar_size.height)); - - layout::Node::with_children( - max_size, - vec![title_bar_layout, body_layout], - ) - } else { - self.body.as_widget().layout(renderer, limits) - } - } - - pub(crate) fn operate( - &self, - tree: &mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn widget::Operation<Message>, - ) { - let body_layout = if let Some(title_bar) = &self.title_bar { - let mut children = layout.children(); - - title_bar.operate( - &mut tree.children[1], - children.next().unwrap(), - renderer, - operation, - ); - - children.next().unwrap() - } else { - layout - }; - - self.body.as_widget().operate( - &mut tree.children[0], - body_layout, - renderer, - operation, - ); - } - - pub(crate) fn on_event( - &mut self, - tree: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - is_picked: bool, - ) -> event::Status { - let mut event_status = event::Status::Ignored; - - let body_layout = if let Some(title_bar) = &mut self.title_bar { - let mut children = layout.children(); - - event_status = title_bar.on_event( - &mut tree.children[1], - event.clone(), - children.next().unwrap(), - cursor_position, - renderer, - clipboard, - shell, - ); - - children.next().unwrap() - } else { - layout - }; - - let body_status = if is_picked { - event::Status::Ignored - } else { - self.body.as_widget_mut().on_event( - &mut tree.children[0], - event, - body_layout, - cursor_position, - renderer, - clipboard, - shell, - ) - }; - - event_status.merge(body_status) - } - - pub(crate) fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - drag_enabled: bool, - ) -> mouse::Interaction { - let (body_layout, title_bar_interaction) = - if let Some(title_bar) = &self.title_bar { - let mut children = layout.children(); - let title_bar_layout = children.next().unwrap(); - - let is_over_pick_area = title_bar - .is_over_pick_area(title_bar_layout, cursor_position); - - if is_over_pick_area && drag_enabled { - return mouse::Interaction::Grab; - } - - let mouse_interaction = title_bar.mouse_interaction( - &tree.children[1], - title_bar_layout, - cursor_position, - viewport, - renderer, - ); - - (children.next().unwrap(), mouse_interaction) - } else { - (layout, mouse::Interaction::default()) - }; - - self.body - .as_widget() - .mouse_interaction( - &tree.children[0], - body_layout, - cursor_position, - viewport, - renderer, - ) - .max(title_bar_interaction) - } - - pub(crate) fn overlay<'b>( - &'b mut self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option<overlay::Element<'b, Message, Renderer>> { - if let Some(title_bar) = self.title_bar.as_mut() { - let mut children = layout.children(); - let title_bar_layout = children.next()?; - - let mut states = tree.children.iter_mut(); - let body_state = states.next().unwrap(); - let title_bar_state = states.next().unwrap(); - - match title_bar.overlay(title_bar_state, title_bar_layout, renderer) - { - Some(overlay) => Some(overlay), - None => self.body.as_widget_mut().overlay( - body_state, - children.next()?, - renderer, - ), - } - } else { - self.body.as_widget_mut().overlay( - &mut tree.children[0], - layout, - renderer, - ) - } - } -} - -impl<'a, Message, Renderer> Draggable for &Content<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: container::StyleSheet, -{ - fn can_be_dragged_at( - &self, - layout: Layout<'_>, - cursor_position: Point, - ) -> bool { - if let Some(title_bar) = &self.title_bar { - let mut children = layout.children(); - let title_bar_layout = children.next().unwrap(); - - title_bar.is_over_pick_area(title_bar_layout, cursor_position) - } else { - false - } - } -} - -impl<'a, T, Message, Renderer> From<T> for Content<'a, Message, Renderer> -where - T: Into<Element<'a, Message, Renderer>>, - Renderer: crate::Renderer, - Renderer::Theme: container::StyleSheet, -{ - fn from(element: T) -> Self { - Self::new(element) - } -} diff --git a/native/src/widget/pane_grid/direction.rs b/native/src/widget/pane_grid/direction.rs deleted file mode 100644 index b31a8737..00000000 --- a/native/src/widget/pane_grid/direction.rs +++ /dev/null @@ -1,12 +0,0 @@ -/// A four cardinal direction. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Direction { - /// ↑ - Up, - /// ↓ - Down, - /// ← - Left, - /// → - Right, -} diff --git a/native/src/widget/pane_grid/draggable.rs b/native/src/widget/pane_grid/draggable.rs deleted file mode 100644 index 6044871d..00000000 --- a/native/src/widget/pane_grid/draggable.rs +++ /dev/null @@ -1,12 +0,0 @@ -use crate::{Layout, Point}; - -/// A pane that can be dragged. -pub trait Draggable { - /// Returns whether the [`Draggable`] with the given [`Layout`] can be picked - /// at the provided cursor position. - fn can_be_dragged_at( - &self, - layout: Layout<'_>, - cursor_position: Point, - ) -> bool; -} diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs deleted file mode 100644 index cc304b96..00000000 --- a/native/src/widget/pane_grid/node.rs +++ /dev/null @@ -1,250 +0,0 @@ -use crate::widget::pane_grid::{Axis, Pane, Split}; -use crate::{Rectangle, Size}; - -use std::collections::BTreeMap; - -/// A layout node of a [`PaneGrid`]. -/// -/// [`PaneGrid`]: crate::widget::PaneGrid -#[derive(Debug, Clone)] -pub enum Node { - /// The region of this [`Node`] is split into two. - Split { - /// The [`Split`] of this [`Node`]. - id: Split, - - /// The direction of the split. - axis: Axis, - - /// The ratio of the split in [0.0, 1.0]. - ratio: f32, - - /// The left/top [`Node`] of the split. - a: Box<Node>, - - /// The right/bottom [`Node`] of the split. - b: Box<Node>, - }, - /// The region of this [`Node`] is taken by a [`Pane`]. - Pane(Pane), -} - -impl Node { - /// Returns an iterator over each [`Split`] in this [`Node`]. - pub fn splits(&self) -> impl Iterator<Item = &Split> { - let mut unvisited_nodes = vec![self]; - - std::iter::from_fn(move || { - while let Some(node) = unvisited_nodes.pop() { - if let Node::Split { id, a, b, .. } = node { - unvisited_nodes.push(a); - unvisited_nodes.push(b); - - return Some(id); - } - } - - None - }) - } - - /// Returns the rectangular region for each [`Pane`] in the [`Node`] given - /// the spacing between panes and the total available space. - pub fn pane_regions( - &self, - spacing: f32, - size: Size, - ) -> BTreeMap<Pane, Rectangle> { - let mut regions = BTreeMap::new(); - - self.compute_regions( - spacing, - &Rectangle { - x: 0.0, - y: 0.0, - width: size.width, - height: size.height, - }, - &mut regions, - ); - - regions - } - - /// Returns the axis, rectangular region, and ratio for each [`Split`] in - /// the [`Node`] given the spacing between panes and the total available - /// space. - pub fn split_regions( - &self, - spacing: f32, - size: Size, - ) -> BTreeMap<Split, (Axis, Rectangle, f32)> { - let mut splits = BTreeMap::new(); - - self.compute_splits( - spacing, - &Rectangle { - x: 0.0, - y: 0.0, - width: size.width, - height: size.height, - }, - &mut splits, - ); - - splits - } - - pub(crate) fn find(&mut self, pane: &Pane) -> Option<&mut Node> { - match self { - Node::Split { a, b, .. } => { - a.find(pane).or_else(move || b.find(pane)) - } - Node::Pane(p) => { - if p == pane { - Some(self) - } else { - None - } - } - } - } - - pub(crate) fn split(&mut self, id: Split, axis: Axis, new_pane: Pane) { - *self = Node::Split { - id, - axis, - ratio: 0.5, - a: Box::new(self.clone()), - b: Box::new(Node::Pane(new_pane)), - }; - } - - pub(crate) fn update(&mut self, f: &impl Fn(&mut Node)) { - if let Node::Split { a, b, .. } = self { - a.update(f); - b.update(f); - } - - f(self); - } - - pub(crate) fn resize(&mut self, split: &Split, percentage: f32) -> bool { - match self { - Node::Split { - id, ratio, a, b, .. - } => { - if id == split { - *ratio = percentage; - - true - } else if a.resize(split, percentage) { - true - } else { - b.resize(split, percentage) - } - } - Node::Pane(_) => false, - } - } - - pub(crate) fn remove(&mut self, pane: &Pane) -> Option<Pane> { - match self { - Node::Split { a, b, .. } => { - if a.pane() == Some(*pane) { - *self = *b.clone(); - Some(self.first_pane()) - } else if b.pane() == Some(*pane) { - *self = *a.clone(); - Some(self.first_pane()) - } else { - a.remove(pane).or_else(|| b.remove(pane)) - } - } - Node::Pane(_) => None, - } - } - - fn pane(&self) -> Option<Pane> { - match self { - Node::Split { .. } => None, - Node::Pane(pane) => Some(*pane), - } - } - - fn first_pane(&self) -> Pane { - match self { - Node::Split { a, .. } => a.first_pane(), - Node::Pane(pane) => *pane, - } - } - - fn compute_regions( - &self, - spacing: f32, - current: &Rectangle, - regions: &mut BTreeMap<Pane, Rectangle>, - ) { - match self { - Node::Split { - axis, ratio, a, b, .. - } => { - let (region_a, region_b) = axis.split(current, *ratio, spacing); - - a.compute_regions(spacing, ®ion_a, regions); - b.compute_regions(spacing, ®ion_b, regions); - } - Node::Pane(pane) => { - let _ = regions.insert(*pane, *current); - } - } - } - - fn compute_splits( - &self, - spacing: f32, - current: &Rectangle, - splits: &mut BTreeMap<Split, (Axis, Rectangle, f32)>, - ) { - match self { - Node::Split { - axis, - ratio, - a, - b, - id, - } => { - let (region_a, region_b) = axis.split(current, *ratio, spacing); - - let _ = splits.insert(*id, (*axis, *current, *ratio)); - - a.compute_splits(spacing, ®ion_a, splits); - b.compute_splits(spacing, ®ion_b, splits); - } - Node::Pane(_) => {} - } - } -} - -impl std::hash::Hash for Node { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - match self { - Node::Split { - id, - axis, - ratio, - a, - b, - } => { - id.hash(state); - axis.hash(state); - ((ratio * 100_000.0) as u32).hash(state); - a.hash(state); - b.hash(state); - } - Node::Pane(pane) => { - pane.hash(state); - } - } - } -} diff --git a/native/src/widget/pane_grid/pane.rs b/native/src/widget/pane_grid/pane.rs deleted file mode 100644 index d6fbab83..00000000 --- a/native/src/widget/pane_grid/pane.rs +++ /dev/null @@ -1,5 +0,0 @@ -/// A rectangular region in a [`PaneGrid`] used to display widgets. -/// -/// [`PaneGrid`]: crate::widget::PaneGrid -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Pane(pub(super) usize); diff --git a/native/src/widget/pane_grid/split.rs b/native/src/widget/pane_grid/split.rs deleted file mode 100644 index 8132272a..00000000 --- a/native/src/widget/pane_grid/split.rs +++ /dev/null @@ -1,5 +0,0 @@ -/// A divider that splits a region in a [`PaneGrid`] into two different panes. -/// -/// [`PaneGrid`]: crate::widget::PaneGrid -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Split(pub(super) usize); diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs deleted file mode 100644 index c4ae0a0e..00000000 --- a/native/src/widget/pane_grid/state.rs +++ /dev/null @@ -1,350 +0,0 @@ -//! The state of a [`PaneGrid`]. -//! -//! [`PaneGrid`]: crate::widget::PaneGrid -use crate::widget::pane_grid::{ - Axis, Configuration, Direction, Node, Pane, Split, -}; -use crate::{Point, Size}; - -use std::collections::HashMap; - -/// The state of a [`PaneGrid`]. -/// -/// It keeps track of the state of each [`Pane`] and the position of each -/// [`Split`]. -/// -/// The [`State`] needs to own any mutable contents a [`Pane`] may need. This is -/// why this struct is generic over the type `T`. Values of this type are -/// provided to the view function of [`PaneGrid::new`] for displaying each -/// [`Pane`]. -/// -/// [`PaneGrid`]: crate::widget::PaneGrid -/// [`PaneGrid::new`]: crate::widget::PaneGrid::new -#[derive(Debug, Clone)] -pub struct State<T> { - /// The panes of the [`PaneGrid`]. - /// - /// [`PaneGrid`]: crate::widget::PaneGrid - pub panes: HashMap<Pane, T>, - - /// The internal state of the [`PaneGrid`]. - /// - /// [`PaneGrid`]: crate::widget::PaneGrid - pub internal: Internal, - - /// The maximized [`Pane`] of the [`PaneGrid`]. - /// - /// [`PaneGrid`]: crate::widget::PaneGrid - pub(super) maximized: Option<Pane>, -} - -impl<T> State<T> { - /// Creates a new [`State`], initializing the first pane with the provided - /// state. - /// - /// Alongside the [`State`], it returns the first [`Pane`] identifier. - pub fn new(first_pane_state: T) -> (Self, Pane) { - ( - Self::with_configuration(Configuration::Pane(first_pane_state)), - Pane(0), - ) - } - - /// Creates a new [`State`] with the given [`Configuration`]. - pub fn with_configuration(config: impl Into<Configuration<T>>) -> Self { - let mut panes = HashMap::new(); - - let internal = - Internal::from_configuration(&mut panes, config.into(), 0); - - State { - panes, - internal, - maximized: None, - } - } - - /// Returns the total amount of panes in the [`State`]. - pub fn len(&self) -> usize { - self.panes.len() - } - - /// Returns `true` if the amount of panes in the [`State`] is 0. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Returns the internal state of the given [`Pane`], if it exists. - pub fn get(&self, pane: &Pane) -> Option<&T> { - self.panes.get(pane) - } - - /// Returns the internal state of the given [`Pane`] with mutability, if it - /// exists. - pub fn get_mut(&mut self, pane: &Pane) -> Option<&mut T> { - self.panes.get_mut(pane) - } - - /// Returns an iterator over all the panes of the [`State`], alongside its - /// internal state. - pub fn iter(&self) -> impl Iterator<Item = (&Pane, &T)> { - self.panes.iter() - } - - /// Returns a mutable iterator over all the panes of the [`State`], - /// alongside its internal state. - pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Pane, &mut T)> { - self.panes.iter_mut() - } - - /// Returns the layout of the [`State`]. - pub fn layout(&self) -> &Node { - &self.internal.layout - } - - /// Returns the adjacent [`Pane`] of another [`Pane`] in the given - /// direction, if there is one. - pub fn adjacent(&self, pane: &Pane, direction: Direction) -> Option<Pane> { - let regions = self - .internal - .layout - .pane_regions(0.0, Size::new(4096.0, 4096.0)); - - let current_region = regions.get(pane)?; - - let target = match direction { - Direction::Left => { - Point::new(current_region.x - 1.0, current_region.y + 1.0) - } - Direction::Right => Point::new( - current_region.x + current_region.width + 1.0, - current_region.y + 1.0, - ), - Direction::Up => { - Point::new(current_region.x + 1.0, current_region.y - 1.0) - } - Direction::Down => Point::new( - current_region.x + 1.0, - current_region.y + current_region.height + 1.0, - ), - }; - - let mut colliding_regions = - regions.iter().filter(|(_, region)| region.contains(target)); - - let (pane, _) = colliding_regions.next()?; - - Some(*pane) - } - - /// Splits the given [`Pane`] into two in the given [`Axis`] and - /// initializing the new [`Pane`] with the provided internal state. - pub fn split( - &mut self, - axis: Axis, - pane: &Pane, - state: T, - ) -> Option<(Pane, Split)> { - let node = self.internal.layout.find(pane)?; - - let new_pane = { - self.internal.last_id = self.internal.last_id.checked_add(1)?; - - Pane(self.internal.last_id) - }; - - let new_split = { - self.internal.last_id = self.internal.last_id.checked_add(1)?; - - Split(self.internal.last_id) - }; - - node.split(new_split, axis, new_pane); - - let _ = self.panes.insert(new_pane, state); - let _ = self.maximized.take(); - - Some((new_pane, new_split)) - } - - /// Swaps the position of the provided panes in the [`State`]. - /// - /// If you want to swap panes on drag and drop in your [`PaneGrid`], you - /// will need to call this method when handling a [`DragEvent`]. - /// - /// [`PaneGrid`]: crate::widget::PaneGrid - /// [`DragEvent`]: crate::widget::pane_grid::DragEvent - pub fn swap(&mut self, a: &Pane, b: &Pane) { - self.internal.layout.update(&|node| match node { - Node::Split { .. } => {} - Node::Pane(pane) => { - if pane == a { - *node = Node::Pane(*b); - } else if pane == b { - *node = Node::Pane(*a); - } - } - }); - } - - /// Resizes two panes by setting the position of the provided [`Split`]. - /// - /// The ratio is a value in [0, 1], representing the exact position of a - /// [`Split`] between two panes. - /// - /// If you want to enable resize interactions in your [`PaneGrid`], you will - /// need to call this method when handling a [`ResizeEvent`]. - /// - /// [`PaneGrid`]: crate::widget::PaneGrid - /// [`ResizeEvent`]: crate::widget::pane_grid::ResizeEvent - pub fn resize(&mut self, split: &Split, ratio: f32) { - let _ = self.internal.layout.resize(split, ratio); - } - - /// Closes the given [`Pane`] and returns its internal state and its closest - /// sibling, if it exists. - pub fn close(&mut self, pane: &Pane) -> Option<(T, Pane)> { - if self.maximized == Some(*pane) { - let _ = self.maximized.take(); - } - - if let Some(sibling) = self.internal.layout.remove(pane) { - self.panes.remove(pane).map(|state| (state, sibling)) - } else { - None - } - } - - /// Maximize the given [`Pane`]. Only this pane will be rendered by the - /// [`PaneGrid`] until [`Self::restore()`] is called. - /// - /// [`PaneGrid`]: crate::widget::PaneGrid - pub fn maximize(&mut self, pane: &Pane) { - self.maximized = Some(*pane); - } - - /// Restore the currently maximized [`Pane`] to it's normal size. All panes - /// will be rendered by the [`PaneGrid`]. - /// - /// [`PaneGrid`]: crate::widget::PaneGrid - pub fn restore(&mut self) { - let _ = self.maximized.take(); - } - - /// Returns the maximized [`Pane`] of the [`PaneGrid`]. - /// - /// [`PaneGrid`]: crate::widget::PaneGrid - pub fn maximized(&self) -> Option<Pane> { - self.maximized - } -} - -/// The internal state of a [`PaneGrid`]. -/// -/// [`PaneGrid`]: crate::widget::PaneGrid -#[derive(Debug, Clone)] -pub struct Internal { - layout: Node, - last_id: usize, -} - -impl Internal { - /// Initializes the [`Internal`] state of a [`PaneGrid`] from a - /// [`Configuration`]. - /// - /// [`PaneGrid`]: crate::widget::PaneGrid - pub fn from_configuration<T>( - panes: &mut HashMap<Pane, T>, - content: Configuration<T>, - next_id: usize, - ) -> Self { - let (layout, last_id) = match content { - Configuration::Split { axis, ratio, a, b } => { - let Internal { - layout: a, - last_id: next_id, - .. - } = Self::from_configuration(panes, *a, next_id); - - let Internal { - layout: b, - last_id: next_id, - .. - } = Self::from_configuration(panes, *b, next_id); - - ( - Node::Split { - id: Split(next_id), - axis, - ratio, - a: Box::new(a), - b: Box::new(b), - }, - next_id + 1, - ) - } - Configuration::Pane(state) => { - let id = Pane(next_id); - let _ = panes.insert(id, state); - - (Node::Pane(id), next_id + 1) - } - }; - - Self { layout, last_id } - } -} - -/// The current action of a [`PaneGrid`]. -/// -/// [`PaneGrid`]: crate::widget::PaneGrid -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Action { - /// The [`PaneGrid`] is idle. - /// - /// [`PaneGrid`]: crate::widget::PaneGrid - Idle, - /// A [`Pane`] in the [`PaneGrid`] is being dragged. - /// - /// [`PaneGrid`]: crate::widget::PaneGrid - Dragging { - /// The [`Pane`] being dragged. - pane: Pane, - /// The starting [`Point`] of the drag interaction. - origin: Point, - }, - /// A [`Split`] in the [`PaneGrid`] is being dragged. - /// - /// [`PaneGrid`]: crate::widget::PaneGrid - Resizing { - /// The [`Split`] being dragged. - split: Split, - /// The [`Axis`] of the [`Split`]. - axis: Axis, - }, -} - -impl Action { - /// Returns the current [`Pane`] that is being dragged, if any. - pub fn picked_pane(&self) -> Option<(Pane, Point)> { - match *self { - Action::Dragging { pane, origin, .. } => Some((pane, origin)), - _ => None, - } - } - - /// Returns the current [`Split`] that is being dragged, if any. - pub fn picked_split(&self) -> Option<(Split, Axis)> { - match *self { - Action::Resizing { split, axis, .. } => Some((split, axis)), - _ => None, - } - } -} - -impl Internal { - /// The layout [`Node`] of the [`Internal`] state - pub fn layout(&self) -> &Node { - &self.layout - } -} diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs deleted file mode 100644 index 107078ef..00000000 --- a/native/src/widget/pane_grid/title_bar.rs +++ /dev/null @@ -1,432 +0,0 @@ -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::widget::container; -use crate::widget::{self, Tree}; -use crate::{ - Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size, -}; - -/// The title bar of a [`Pane`]. -/// -/// [`Pane`]: crate::widget::pane_grid::Pane -#[allow(missing_debug_implementations)] -pub struct TitleBar<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: container::StyleSheet, -{ - content: Element<'a, Message, Renderer>, - controls: Option<Element<'a, Message, Renderer>>, - padding: Padding, - always_show_controls: bool, - style: <Renderer::Theme as container::StyleSheet>::Style, -} - -impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: container::StyleSheet, -{ - /// Creates a new [`TitleBar`] with the given content. - pub fn new<E>(content: E) -> Self - where - E: Into<Element<'a, Message, Renderer>>, - { - Self { - content: content.into(), - controls: None, - padding: Padding::ZERO, - always_show_controls: false, - style: Default::default(), - } - } - - /// Sets the controls of the [`TitleBar`]. - pub fn controls( - mut self, - controls: impl Into<Element<'a, Message, Renderer>>, - ) -> Self { - self.controls = Some(controls.into()); - self - } - - /// Sets the [`Padding`] of the [`TitleBar`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the style of the [`TitleBar`]. - pub fn style( - mut self, - style: impl Into<<Renderer::Theme as container::StyleSheet>::Style>, - ) -> Self { - self.style = style.into(); - self - } - - /// Sets whether or not the [`controls`] attached to this [`TitleBar`] are - /// always visible. - /// - /// By default, the controls are only visible when the [`Pane`] of this - /// [`TitleBar`] is hovered. - /// - /// [`controls`]: Self::controls - /// [`Pane`]: crate::widget::pane_grid::Pane - pub fn always_show_controls(mut self) -> Self { - self.always_show_controls = true; - self - } -} - -impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: container::StyleSheet, -{ - pub(super) fn state(&self) -> Tree { - let children = if let Some(controls) = self.controls.as_ref() { - vec![Tree::new(&self.content), Tree::new(controls)] - } else { - vec![Tree::new(&self.content), Tree::empty()] - }; - - Tree { - children, - ..Tree::empty() - } - } - - pub(super) fn diff(&self, tree: &mut Tree) { - if tree.children.len() == 2 { - if let Some(controls) = self.controls.as_ref() { - tree.children[1].diff(controls); - } - - tree.children[0].diff(&self.content); - } else { - *tree = self.state(); - } - } - - /// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`]. - /// - /// [`Renderer`]: crate::Renderer - pub fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - inherited_style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - show_controls: bool, - ) { - use container::StyleSheet; - - let bounds = layout.bounds(); - let style = theme.appearance(&self.style); - let inherited_style = renderer::Style { - text_color: style.text_color.unwrap_or(inherited_style.text_color), - }; - - container::draw_background(renderer, &style, bounds); - - let mut children = layout.children(); - let padded = children.next().unwrap(); - - let mut children = padded.children(); - let title_layout = children.next().unwrap(); - let mut show_title = true; - - if let Some(controls) = &self.controls { - if show_controls || self.always_show_controls { - let controls_layout = children.next().unwrap(); - if title_layout.bounds().width + controls_layout.bounds().width - > padded.bounds().width - { - show_title = false; - } - - controls.as_widget().draw( - &tree.children[1], - renderer, - theme, - &inherited_style, - controls_layout, - cursor_position, - viewport, - ); - } - } - - if show_title { - self.content.as_widget().draw( - &tree.children[0], - renderer, - theme, - &inherited_style, - title_layout, - cursor_position, - viewport, - ); - } - } - - /// Returns whether the mouse cursor is over the pick area of the - /// [`TitleBar`] or not. - /// - /// The whole [`TitleBar`] is a pick area, except its controls. - pub fn is_over_pick_area( - &self, - layout: Layout<'_>, - cursor_position: Point, - ) -> bool { - if layout.bounds().contains(cursor_position) { - let mut children = layout.children(); - let padded = children.next().unwrap(); - let mut children = padded.children(); - let title_layout = children.next().unwrap(); - - if self.controls.is_some() { - let controls_layout = children.next().unwrap(); - - if title_layout.bounds().width + controls_layout.bounds().width - > padded.bounds().width - { - !controls_layout.bounds().contains(cursor_position) - } else { - !controls_layout.bounds().contains(cursor_position) - && !title_layout.bounds().contains(cursor_position) - } - } else { - !title_layout.bounds().contains(cursor_position) - } - } else { - false - } - } - - pub(crate) fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - let limits = limits.pad(self.padding); - let max_size = limits.max(); - - let title_layout = self - .content - .as_widget() - .layout(renderer, &layout::Limits::new(Size::ZERO, max_size)); - - let title_size = title_layout.size(); - - let mut node = if let Some(controls) = &self.controls { - let mut controls_layout = controls - .as_widget() - .layout(renderer, &layout::Limits::new(Size::ZERO, max_size)); - - let controls_size = controls_layout.size(); - let space_before_controls = max_size.width - controls_size.width; - - let height = title_size.height.max(controls_size.height); - - controls_layout.move_to(Point::new(space_before_controls, 0.0)); - - layout::Node::with_children( - Size::new(max_size.width, height), - vec![title_layout, controls_layout], - ) - } else { - layout::Node::with_children( - Size::new(max_size.width, title_size.height), - vec![title_layout], - ) - }; - - node.move_to(Point::new(self.padding.left, self.padding.top)); - - layout::Node::with_children(node.size().pad(self.padding), vec![node]) - } - - pub(crate) fn operate( - &self, - tree: &mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn widget::Operation<Message>, - ) { - let mut children = layout.children(); - let padded = children.next().unwrap(); - - let mut children = padded.children(); - let title_layout = children.next().unwrap(); - let mut show_title = true; - - if let Some(controls) = &self.controls { - let controls_layout = children.next().unwrap(); - - if title_layout.bounds().width + controls_layout.bounds().width - > padded.bounds().width - { - show_title = false; - } - - controls.as_widget().operate( - &mut tree.children[1], - controls_layout, - renderer, - operation, - ) - }; - - if show_title { - self.content.as_widget().operate( - &mut tree.children[0], - title_layout, - renderer, - operation, - ) - } - } - - pub(crate) fn on_event( - &mut self, - tree: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - let mut children = layout.children(); - let padded = children.next().unwrap(); - - let mut children = padded.children(); - let title_layout = children.next().unwrap(); - let mut show_title = true; - - let control_status = if let Some(controls) = &mut self.controls { - let controls_layout = children.next().unwrap(); - if title_layout.bounds().width + controls_layout.bounds().width - > padded.bounds().width - { - show_title = false; - } - - controls.as_widget_mut().on_event( - &mut tree.children[1], - event.clone(), - controls_layout, - cursor_position, - renderer, - clipboard, - shell, - ) - } else { - event::Status::Ignored - }; - - let title_status = if show_title { - self.content.as_widget_mut().on_event( - &mut tree.children[0], - event, - title_layout, - cursor_position, - renderer, - clipboard, - shell, - ) - } else { - event::Status::Ignored - }; - - control_status.merge(title_status) - } - - pub(crate) fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - let mut children = layout.children(); - let padded = children.next().unwrap(); - - let mut children = padded.children(); - let title_layout = children.next().unwrap(); - - let title_interaction = self.content.as_widget().mouse_interaction( - &tree.children[0], - title_layout, - cursor_position, - viewport, - renderer, - ); - - if let Some(controls) = &self.controls { - let controls_layout = children.next().unwrap(); - let controls_interaction = controls.as_widget().mouse_interaction( - &tree.children[1], - controls_layout, - cursor_position, - viewport, - renderer, - ); - - if title_layout.bounds().width + controls_layout.bounds().width - > padded.bounds().width - { - controls_interaction - } else { - controls_interaction.max(title_interaction) - } - } else { - title_interaction - } - } - - pub(crate) fn overlay<'b>( - &'b mut self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option<overlay::Element<'b, Message, Renderer>> { - let mut children = layout.children(); - let padded = children.next()?; - - let mut children = padded.children(); - let title_layout = children.next()?; - - let Self { - content, controls, .. - } = self; - - let mut states = tree.children.iter_mut(); - let title_state = states.next().unwrap(); - let controls_state = states.next().unwrap(); - - content - .as_widget_mut() - .overlay(title_state, title_layout, renderer) - .or_else(move || { - controls.as_mut().and_then(|controls| { - let controls_layout = children.next()?; - - controls.as_widget_mut().overlay( - controls_state, - controls_layout, - renderer, - ) - }) - }) - } -} diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs deleted file mode 100644 index 8ff82f3b..00000000 --- a/native/src/widget/pick_list.rs +++ /dev/null @@ -1,657 +0,0 @@ -//! Display a dropdown list of selectable values. -use crate::alignment; -use crate::event::{self, Event}; -use crate::keyboard; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::overlay::menu::{self, Menu}; -use crate::renderer; -use crate::text::{self, Text}; -use crate::touch; -use crate::widget::container; -use crate::widget::scrollable; -use crate::widget::tree::{self, Tree}; -use crate::{ - Clipboard, Element, Layout, Length, Padding, Pixels, Point, Rectangle, - Shell, Size, Widget, -}; -use std::borrow::Cow; - -pub use iced_style::pick_list::{Appearance, StyleSheet}; - -/// A widget for selecting a single value from a list of options. -#[allow(missing_debug_implementations)] -pub struct PickList<'a, T, Message, Renderer> -where - [T]: ToOwned<Owned = Vec<T>>, - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - on_selected: Box<dyn Fn(T) -> Message + 'a>, - options: Cow<'a, [T]>, - placeholder: Option<String>, - selected: Option<T>, - width: Length, - padding: Padding, - text_size: Option<f32>, - font: Option<Renderer::Font>, - handle: Handle<Renderer::Font>, - style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, T: 'a, Message, Renderer> PickList<'a, T, Message, Renderer> -where - T: ToString + Eq, - [T]: ToOwned<Owned = Vec<T>>, - Renderer: text::Renderer, - Renderer::Theme: StyleSheet - + scrollable::StyleSheet - + menu::StyleSheet - + container::StyleSheet, - <Renderer::Theme as menu::StyleSheet>::Style: - From<<Renderer::Theme as StyleSheet>::Style>, -{ - /// The default padding of a [`PickList`]. - pub const DEFAULT_PADDING: Padding = Padding::new(5.0); - - /// Creates a new [`PickList`] with the given list of options, the current - /// selected value, and the message to produce when an option is selected. - pub fn new( - options: impl Into<Cow<'a, [T]>>, - selected: Option<T>, - on_selected: impl Fn(T) -> Message + 'a, - ) -> Self { - Self { - on_selected: Box::new(on_selected), - options: options.into(), - placeholder: None, - selected, - width: Length::Shrink, - padding: Self::DEFAULT_PADDING, - text_size: None, - font: None, - handle: Default::default(), - style: Default::default(), - } - } - - /// Sets the placeholder of the [`PickList`]. - pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self { - self.placeholder = Some(placeholder.into()); - self - } - - /// Sets the width of the [`PickList`]. - pub fn width(mut self, width: impl Into<Length>) -> Self { - self.width = width.into(); - self - } - - /// Sets the [`Padding`] of the [`PickList`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the text size of the [`PickList`]. - pub fn text_size(mut self, size: impl Into<Pixels>) -> Self { - self.text_size = Some(size.into().0); - self - } - - /// Sets the font of the [`PickList`]. - pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self { - self.font = Some(font.into()); - self - } - - /// Sets the [`Handle`] of the [`PickList`]. - pub fn handle(mut self, handle: Handle<Renderer::Font>) -> Self { - self.handle = handle; - self - } - - /// Sets the style of the [`PickList`]. - pub fn style( - mut self, - style: impl Into<<Renderer::Theme as StyleSheet>::Style>, - ) -> Self { - self.style = style.into(); - self - } -} - -impl<'a, T: 'a, Message, Renderer> Widget<Message, Renderer> - for PickList<'a, T, Message, Renderer> -where - T: Clone + ToString + Eq + 'static, - [T]: ToOwned<Owned = Vec<T>>, - Message: 'a, - Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet - + scrollable::StyleSheet - + menu::StyleSheet - + container::StyleSheet, - <Renderer::Theme as menu::StyleSheet>::Style: - From<<Renderer::Theme as StyleSheet>::Style>, -{ - fn tag(&self) -> tree::Tag { - tree::Tag::of::<State<T>>() - } - - fn state(&self) -> tree::State { - tree::State::new(State::<T>::new()) - } - - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - Length::Shrink - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - layout( - renderer, - limits, - self.width, - self.padding, - self.text_size, - self.font, - self.placeholder.as_deref(), - &self.options, - ) - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - _renderer: &Renderer, - _clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - update( - event, - layout, - cursor_position, - shell, - self.on_selected.as_ref(), - self.selected.as_ref(), - &self.options, - || tree.state.downcast_mut::<State<T>>(), - ) - } - - fn mouse_interaction( - &self, - _tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - mouse_interaction(layout, cursor_position) - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - _style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - ) { - let font = self.font.unwrap_or_else(|| renderer.default_font()); - draw( - renderer, - theme, - layout, - cursor_position, - self.padding, - self.text_size, - font, - self.placeholder.as_deref(), - self.selected.as_ref(), - &self.handle, - &self.style, - || tree.state.downcast_ref::<State<T>>(), - ) - } - - fn overlay<'b>( - &'b mut self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option<overlay::Element<'b, Message, Renderer>> { - let state = tree.state.downcast_mut::<State<T>>(); - - overlay( - layout, - state, - self.padding, - self.text_size, - self.font.unwrap_or_else(|| renderer.default_font()), - &self.options, - self.style.clone(), - ) - } -} - -impl<'a, T: 'a, Message, Renderer> From<PickList<'a, T, Message, Renderer>> - for Element<'a, Message, Renderer> -where - T: Clone + ToString + Eq + 'static, - [T]: ToOwned<Owned = Vec<T>>, - Message: 'a, - Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet - + scrollable::StyleSheet - + menu::StyleSheet - + container::StyleSheet, - <Renderer::Theme as menu::StyleSheet>::Style: - From<<Renderer::Theme as StyleSheet>::Style>, -{ - fn from(pick_list: PickList<'a, T, Message, Renderer>) -> Self { - Self::new(pick_list) - } -} - -/// The local state of a [`PickList`]. -#[derive(Debug)] -pub struct State<T> { - menu: menu::State, - keyboard_modifiers: keyboard::Modifiers, - is_open: bool, - hovered_option: Option<usize>, - last_selection: Option<T>, -} - -impl<T> State<T> { - /// Creates a new [`State`] for a [`PickList`]. - pub fn new() -> Self { - Self { - menu: menu::State::default(), - keyboard_modifiers: keyboard::Modifiers::default(), - is_open: bool::default(), - hovered_option: Option::default(), - last_selection: Option::default(), - } - } -} - -impl<T> Default for State<T> { - fn default() -> Self { - Self::new() - } -} - -/// The handle to the right side of the [`PickList`]. -#[derive(Debug, Clone, PartialEq)] -pub enum Handle<Font> { - /// Displays an arrow icon (▼). - /// - /// This is the default. - Arrow { - /// Font size of the content. - size: Option<f32>, - }, - /// A custom static handle. - Static(Icon<Font>), - /// A custom dynamic handle. - Dynamic { - /// The [`Icon`] used when [`PickList`] is closed. - closed: Icon<Font>, - /// The [`Icon`] used when [`PickList`] is open. - open: Icon<Font>, - }, - /// No handle will be shown. - None, -} - -impl<Font> Default for Handle<Font> { - fn default() -> Self { - Self::Arrow { size: None } - } -} - -/// The icon of a [`Handle`]. -#[derive(Debug, Clone, PartialEq)] -pub struct Icon<Font> { - /// Font that will be used to display the `code_point`, - pub font: Font, - /// The unicode code point that will be used as the icon. - pub code_point: char, - /// Font size of the content. - pub size: Option<f32>, -} - -/// Computes the layout of a [`PickList`]. -pub fn layout<Renderer, T>( - renderer: &Renderer, - limits: &layout::Limits, - width: Length, - padding: Padding, - text_size: Option<f32>, - font: Option<Renderer::Font>, - placeholder: Option<&str>, - options: &[T], -) -> layout::Node -where - Renderer: text::Renderer, - T: ToString, -{ - use std::f32; - - let limits = limits.width(width).height(Length::Shrink).pad(padding); - let text_size = text_size.unwrap_or_else(|| renderer.default_size()); - - let max_width = match width { - Length::Shrink => { - let measure = |label: &str| -> f32 { - let (width, _) = renderer.measure( - label, - text_size, - font.unwrap_or_else(|| renderer.default_font()), - Size::new(f32::INFINITY, f32::INFINITY), - ); - - width.round() - }; - - let labels = options.iter().map(ToString::to_string); - - let labels_width = labels - .map(|label| measure(&label)) - .fold(100.0, |candidate, current| current.max(candidate)); - - let placeholder_width = placeholder.map(measure).unwrap_or(100.0); - - labels_width.max(placeholder_width) - } - _ => 0.0, - }; - - let size = { - let intrinsic = - Size::new(max_width + text_size + padding.left, text_size * 1.2); - - limits.resolve(intrinsic).pad(padding) - }; - - layout::Node::new(size) -} - -/// Processes an [`Event`] and updates the [`State`] of a [`PickList`] -/// accordingly. -pub fn update<'a, T, Message>( - event: Event, - layout: Layout<'_>, - cursor_position: Point, - shell: &mut Shell<'_, Message>, - on_selected: &dyn Fn(T) -> Message, - selected: Option<&T>, - options: &[T], - state: impl FnOnce() -> &'a mut State<T>, -) -> event::Status -where - T: PartialEq + Clone + 'a, -{ - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - let state = state(); - - let event_status = if state.is_open { - // Event wasn't processed by overlay, so cursor was clicked either outside it's - // bounds or on the drop-down, either way we close the overlay. - state.is_open = false; - - event::Status::Captured - } else if layout.bounds().contains(cursor_position) { - state.is_open = true; - state.hovered_option = - options.iter().position(|option| Some(option) == selected); - - event::Status::Captured - } else { - event::Status::Ignored - }; - - if let Some(last_selection) = state.last_selection.take() { - shell.publish((on_selected)(last_selection)); - - state.is_open = false; - - event::Status::Captured - } else { - event_status - } - } - Event::Mouse(mouse::Event::WheelScrolled { - delta: mouse::ScrollDelta::Lines { y, .. }, - }) => { - let state = state(); - - if state.keyboard_modifiers.command() - && layout.bounds().contains(cursor_position) - && !state.is_open - { - fn find_next<'a, T: PartialEq>( - selected: &'a T, - mut options: impl Iterator<Item = &'a T>, - ) -> Option<&'a T> { - let _ = options.find(|&option| option == selected); - - options.next() - } - - let next_option = if y < 0.0 { - if let Some(selected) = selected { - find_next(selected, options.iter()) - } else { - options.first() - } - } else if y > 0.0 { - if let Some(selected) = selected { - find_next(selected, options.iter().rev()) - } else { - options.last() - } - } else { - None - }; - - if let Some(next_option) = next_option { - shell.publish((on_selected)(next_option.clone())); - } - - event::Status::Captured - } else { - event::Status::Ignored - } - } - Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { - let state = state(); - - state.keyboard_modifiers = modifiers; - - event::Status::Ignored - } - _ => event::Status::Ignored, - } -} - -/// Returns the current [`mouse::Interaction`] of a [`PickList`]. -pub fn mouse_interaction( - layout: Layout<'_>, - cursor_position: Point, -) -> mouse::Interaction { - let bounds = layout.bounds(); - let is_mouse_over = bounds.contains(cursor_position); - - if is_mouse_over { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - } -} - -/// Returns the current overlay of a [`PickList`]. -pub fn overlay<'a, T, Message, Renderer>( - layout: Layout<'_>, - state: &'a mut State<T>, - padding: Padding, - text_size: Option<f32>, - font: Renderer::Font, - options: &'a [T], - style: <Renderer::Theme as StyleSheet>::Style, -) -> Option<overlay::Element<'a, Message, Renderer>> -where - T: Clone + ToString, - Message: 'a, - Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet - + scrollable::StyleSheet - + menu::StyleSheet - + container::StyleSheet, - <Renderer::Theme as menu::StyleSheet>::Style: - From<<Renderer::Theme as StyleSheet>::Style>, -{ - if state.is_open { - let bounds = layout.bounds(); - - let mut menu = Menu::new( - &mut state.menu, - options, - &mut state.hovered_option, - &mut state.last_selection, - ) - .width(bounds.width) - .padding(padding) - .font(font) - .style(style); - - if let Some(text_size) = text_size { - menu = menu.text_size(text_size); - } - - Some(menu.overlay(layout.position(), bounds.height)) - } else { - None - } -} - -/// Draws a [`PickList`]. -pub fn draw<'a, T, Renderer>( - renderer: &mut Renderer, - theme: &Renderer::Theme, - layout: Layout<'_>, - cursor_position: Point, - padding: Padding, - text_size: Option<f32>, - font: Renderer::Font, - placeholder: Option<&str>, - selected: Option<&T>, - handle: &Handle<Renderer::Font>, - style: &<Renderer::Theme as StyleSheet>::Style, - state: impl FnOnce() -> &'a State<T>, -) where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, - T: ToString + 'a, -{ - let bounds = layout.bounds(); - let is_mouse_over = bounds.contains(cursor_position); - let is_selected = selected.is_some(); - - let style = if is_mouse_over { - theme.hovered(style) - } else { - theme.active(style) - }; - - renderer.fill_quad( - renderer::Quad { - bounds, - border_color: style.border_color, - border_width: style.border_width, - border_radius: style.border_radius.into(), - }, - style.background, - ); - - let handle = match handle { - Handle::Arrow { size } => { - Some((Renderer::ICON_FONT, Renderer::ARROW_DOWN_ICON, *size)) - } - Handle::Static(Icon { - font, - code_point, - size, - }) => Some((*font, *code_point, *size)), - Handle::Dynamic { open, closed } => { - if state().is_open { - Some((open.font, open.code_point, open.size)) - } else { - Some((closed.font, closed.code_point, closed.size)) - } - } - Handle::None => None, - }; - - if let Some((font, code_point, size)) = handle { - let size = size.unwrap_or_else(|| renderer.default_size()); - - renderer.fill_text(Text { - content: &code_point.to_string(), - size, - font, - color: style.handle_color, - bounds: Rectangle { - x: bounds.x + bounds.width - padding.horizontal(), - y: bounds.center_y(), - height: size * 1.2, - ..bounds - }, - horizontal_alignment: alignment::Horizontal::Right, - vertical_alignment: alignment::Vertical::Center, - }); - } - - let label = selected.map(ToString::to_string); - - if let Some(label) = label.as_deref().or(placeholder) { - let text_size = text_size.unwrap_or_else(|| renderer.default_size()); - - renderer.fill_text(Text { - content: label, - size: text_size, - font, - color: if is_selected { - style.text_color - } else { - style.placeholder_color - }, - bounds: Rectangle { - x: bounds.x + padding.left, - y: bounds.center_y(), - width: bounds.width - padding.horizontal(), - height: text_size * 1.2, - }, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, - }); - } -} diff --git a/native/src/widget/progress_bar.rs b/native/src/widget/progress_bar.rs deleted file mode 100644 index dd46fa76..00000000 --- a/native/src/widget/progress_bar.rs +++ /dev/null @@ -1,168 +0,0 @@ -//! Provide progress feedback to your users. -use crate::layout; -use crate::renderer; -use crate::widget::Tree; -use crate::{Color, Element, Layout, Length, Point, Rectangle, Size, Widget}; - -use std::ops::RangeInclusive; - -pub use iced_style::progress_bar::{Appearance, StyleSheet}; - -/// A bar that displays progress. -/// -/// # Example -/// ``` -/// # type ProgressBar = iced_native::widget::ProgressBar<iced_native::renderer::Null>; -/// let value = 50.0; -/// -/// ProgressBar::new(0.0..=100.0, value); -/// ``` -/// -///  -#[allow(missing_debug_implementations)] -pub struct ProgressBar<Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - range: RangeInclusive<f32>, - value: f32, - width: Length, - height: Option<Length>, - style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<Renderer> ProgressBar<Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - /// The default height of a [`ProgressBar`]. - pub const DEFAULT_HEIGHT: f32 = 30.0; - - /// Creates a new [`ProgressBar`]. - /// - /// It expects: - /// * an inclusive range of possible values - /// * the current value of the [`ProgressBar`] - pub fn new(range: RangeInclusive<f32>, value: f32) -> Self { - ProgressBar { - value: value.clamp(*range.start(), *range.end()), - range, - width: Length::Fill, - height: None, - style: Default::default(), - } - } - - /// Sets the width of the [`ProgressBar`]. - pub fn width(mut self, width: impl Into<Length>) -> Self { - self.width = width.into(); - self - } - - /// Sets the height of the [`ProgressBar`]. - pub fn height(mut self, height: impl Into<Length>) -> Self { - self.height = Some(height.into()); - self - } - - /// Sets the style of the [`ProgressBar`]. - pub fn style( - mut self, - style: impl Into<<Renderer::Theme as StyleSheet>::Style>, - ) -> Self { - self.style = style.into(); - self - } -} - -impl<Message, Renderer> Widget<Message, Renderer> for ProgressBar<Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height.unwrap_or(Length::Fixed(Self::DEFAULT_HEIGHT)) - } - - fn layout( - &self, - _renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - let limits = limits - .width(self.width) - .height(self.height.unwrap_or(Length::Fixed(Self::DEFAULT_HEIGHT))); - - let size = limits.resolve(Size::ZERO); - - layout::Node::new(size) - } - - fn draw( - &self, - _state: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - _style: &renderer::Style, - layout: Layout<'_>, - _cursor_position: Point, - _viewport: &Rectangle, - ) { - let bounds = layout.bounds(); - let (range_start, range_end) = self.range.clone().into_inner(); - - let active_progress_width = if range_start >= range_end { - 0.0 - } else { - bounds.width * (self.value - range_start) - / (range_end - range_start) - }; - - let style = theme.appearance(&self.style); - - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { ..bounds }, - border_radius: style.border_radius.into(), - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - style.background, - ); - - if active_progress_width > 0.0 { - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - width: active_progress_width, - ..bounds - }, - border_radius: style.border_radius.into(), - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - style.bar, - ); - } - } -} - -impl<'a, Message, Renderer> From<ProgressBar<Renderer>> - for Element<'a, Message, Renderer> -where - Message: 'a, - Renderer: 'a + crate::Renderer, - Renderer::Theme: StyleSheet, -{ - 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 deleted file mode 100644 index 5f60eaef..00000000 --- a/native/src/widget/radio.rs +++ /dev/null @@ -1,299 +0,0 @@ -//! Create choices using radio buttons. -use crate::alignment; -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::text; -use crate::touch; -use crate::widget::{self, Row, Text, Tree}; -use crate::{ - Alignment, Clipboard, Color, Element, Layout, Length, Pixels, Point, - Rectangle, Shell, Widget, -}; - -pub use iced_style::radio::{Appearance, StyleSheet}; - -/// A circular button representing a choice. -/// -/// # Example -/// ``` -/// # type Radio<Message> = -/// # iced_native::widget::Radio<Message, iced_native::renderer::Null>; -/// # -/// #[derive(Debug, Clone, Copy, PartialEq, Eq)] -/// pub enum Choice { -/// A, -/// B, -/// } -/// -/// #[derive(Debug, Clone, Copy)] -/// pub enum Message { -/// RadioSelected(Choice), -/// } -/// -/// let selected_choice = Some(Choice::A); -/// -/// Radio::new(Choice::A, "This is A", selected_choice, Message::RadioSelected); -/// -/// Radio::new(Choice::B, "This is B", selected_choice, Message::RadioSelected); -/// ``` -/// -///  -#[allow(missing_debug_implementations)] -pub struct Radio<Message, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - is_selected: bool, - on_click: Message, - label: String, - width: Length, - size: f32, - spacing: f32, - text_size: Option<f32>, - font: Option<Renderer::Font>, - style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<Message, Renderer> Radio<Message, Renderer> -where - Message: Clone, - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - /// The default size of a [`Radio`] button. - pub const DEFAULT_SIZE: f32 = 28.0; - - /// The default spacing of a [`Radio`] button. - pub const DEFAULT_SPACING: f32 = 15.0; - - /// Creates a new [`Radio`] button. - /// - /// It expects: - /// * the value related to the [`Radio`] button - /// * the label of the [`Radio`] button - /// * the current selected value - /// * a function that will be called when the [`Radio`] is selected. It - /// receives the value of the radio and must produce a `Message`. - pub fn new<F, V>( - value: V, - label: impl Into<String>, - selected: Option<V>, - f: F, - ) -> Self - where - V: Eq + Copy, - F: FnOnce(V) -> Message, - { - Radio { - is_selected: Some(value) == selected, - on_click: f(value), - label: label.into(), - width: Length::Shrink, - size: Self::DEFAULT_SIZE, - spacing: Self::DEFAULT_SPACING, //15 - text_size: None, - font: None, - style: Default::default(), - } - } - - /// Sets the size of the [`Radio`] button. - pub fn size(mut self, size: impl Into<Pixels>) -> Self { - self.size = size.into().0; - self - } - - /// Sets the width of the [`Radio`] button. - pub fn width(mut self, width: impl Into<Length>) -> Self { - self.width = width.into(); - self - } - - /// Sets the spacing between the [`Radio`] button and the text. - pub fn spacing(mut self, spacing: impl Into<Pixels>) -> Self { - self.spacing = spacing.into().0; - self - } - - /// Sets the text size of the [`Radio`] button. - pub fn text_size(mut self, text_size: impl Into<Pixels>) -> Self { - self.text_size = Some(text_size.into().0); - self - } - - /// Sets the text font of the [`Radio`] button. - pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self { - self.font = Some(font.into()); - self - } - - /// Sets the style of the [`Radio`] button. - pub fn style( - mut self, - style: impl Into<<Renderer::Theme as StyleSheet>::Style>, - ) -> Self { - self.style = style.into(); - self - } -} - -impl<Message, Renderer> Widget<Message, Renderer> for Radio<Message, Renderer> -where - Message: Clone, - Renderer: text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, -{ - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - Length::Shrink - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - Row::<(), Renderer>::new() - .width(self.width) - .spacing(self.spacing) - .align_items(Alignment::Center) - .push(Row::new().width(self.size).height(self.size)) - .push(Text::new(&self.label).width(self.width).size( - self.text_size.unwrap_or_else(|| renderer.default_size()), - )) - .layout(renderer, limits) - } - - fn on_event( - &mut self, - _state: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - _renderer: &Renderer, - _clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - if layout.bounds().contains(cursor_position) { - shell.publish(self.on_click.clone()); - - return event::Status::Captured; - } - } - _ => {} - } - - event::Status::Ignored - } - - fn mouse_interaction( - &self, - _state: &Tree, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - if layout.bounds().contains(cursor_position) { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - } - } - - fn draw( - &self, - _state: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - ) { - let bounds = layout.bounds(); - let is_mouse_over = bounds.contains(cursor_position); - - let mut children = layout.children(); - - let custom_style = if is_mouse_over { - theme.hovered(&self.style, self.is_selected) - } else { - theme.active(&self.style, self.is_selected) - }; - - { - let layout = children.next().unwrap(); - let bounds = layout.bounds(); - - let size = bounds.width; - let dot_size = size / 2.0; - - renderer.fill_quad( - renderer::Quad { - bounds, - border_radius: (size / 2.0).into(), - border_width: custom_style.border_width, - border_color: custom_style.border_color, - }, - custom_style.background, - ); - - if self.is_selected { - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: bounds.x + dot_size / 2.0, - y: bounds.y + dot_size / 2.0, - width: bounds.width - dot_size, - height: bounds.height - dot_size, - }, - border_radius: (dot_size / 2.0).into(), - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - custom_style.dot_color, - ); - } - } - - { - let label_layout = children.next().unwrap(); - - widget::text::draw( - renderer, - style, - label_layout, - &self.label, - self.text_size, - self.font, - widget::text::Appearance { - color: custom_style.text_color, - }, - alignment::Horizontal::Left, - alignment::Vertical::Center, - ); - } - } -} - -impl<'a, Message, Renderer> From<Radio<Message, Renderer>> - for Element<'a, Message, Renderer> -where - Message: 'a + Clone, - Renderer: 'a + text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, -{ - 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 deleted file mode 100644 index 286c1c2d..00000000 --- a/native/src/widget/row.rs +++ /dev/null @@ -1,253 +0,0 @@ -//! Distribute content horizontally. -use crate::event::{self, Event}; -use crate::layout::{self, Layout}; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::widget::{Operation, Tree}; -use crate::{ - Alignment, Clipboard, Element, Length, Padding, Pixels, Point, Rectangle, - Shell, Widget, -}; - -/// A container that distributes its contents horizontally. -#[allow(missing_debug_implementations)] -pub struct Row<'a, Message, Renderer> { - spacing: f32, - padding: Padding, - width: Length, - height: Length, - align_items: Alignment, - children: Vec<Element<'a, Message, Renderer>>, -} - -impl<'a, Message, Renderer> Row<'a, Message, Renderer> { - /// Creates an empty [`Row`]. - pub fn new() -> Self { - Self::with_children(Vec::new()) - } - - /// Creates a [`Row`] with the given elements. - pub fn with_children( - children: Vec<Element<'a, Message, Renderer>>, - ) -> Self { - Row { - spacing: 0.0, - padding: Padding::ZERO, - width: Length::Shrink, - height: Length::Shrink, - align_items: Alignment::Start, - children, - } - } - - /// Sets the horizontal spacing _between_ elements. - /// - /// Custom margins per element do not exist in iced. You should use this - /// method instead! While less flexible, it helps you keep spacing between - /// elements consistent. - pub fn spacing(mut self, amount: impl Into<Pixels>) -> Self { - self.spacing = amount.into().0; - self - } - - /// Sets the [`Padding`] of the [`Row`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the width of the [`Row`]. - pub fn width(mut self, width: impl Into<Length>) -> Self { - self.width = width.into(); - self - } - - /// Sets the height of the [`Row`]. - pub fn height(mut self, height: impl Into<Length>) -> Self { - self.height = height.into(); - self - } - - /// Sets the vertical alignment of the contents of the [`Row`] . - pub fn align_items(mut self, align: Alignment) -> Self { - self.align_items = align; - self - } - - /// Adds an [`Element`] to the [`Row`]. - pub fn push( - mut self, - child: impl Into<Element<'a, Message, Renderer>>, - ) -> Self { - self.children.push(child.into()); - self - } -} - -impl<'a, Message, Renderer> Default for Row<'a, Message, Renderer> { - fn default() -> Self { - Self::new() - } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> - for Row<'a, Message, Renderer> -where - Renderer: crate::Renderer, -{ - fn children(&self) -> Vec<Tree> { - self.children.iter().map(Tree::new).collect() - } - - fn diff(&self, tree: &mut Tree) { - tree.diff_children(&self.children) - } - - 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::flex::resolve( - layout::flex::Axis::Horizontal, - renderer, - &limits, - self.padding, - self.spacing, - self.align_items, - &self.children, - ) - } - - fn operate( - &self, - tree: &mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn Operation<Message>, - ) { - operation.container(None, &mut |operation| { - self.children - .iter() - .zip(&mut tree.children) - .zip(layout.children()) - .for_each(|((child, state), layout)| { - child - .as_widget() - .operate(state, layout, renderer, operation); - }) - }); - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - self.children - .iter_mut() - .zip(&mut tree.children) - .zip(layout.children()) - .map(|((child, state), layout)| { - child.as_widget_mut().on_event( - state, - event.clone(), - layout, - cursor_position, - renderer, - clipboard, - shell, - ) - }) - .fold(event::Status::Ignored, event::Status::merge) - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.children - .iter() - .zip(&tree.children) - .zip(layout.children()) - .map(|((child, state), layout)| { - child.as_widget().mouse_interaction( - state, - layout, - cursor_position, - viewport, - renderer, - ) - }) - .max() - .unwrap_or_default() - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ) { - for ((child, state), layout) in self - .children - .iter() - .zip(&tree.children) - .zip(layout.children()) - { - child.as_widget().draw( - state, - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ); - } - } - - fn overlay<'b>( - &'b mut self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option<overlay::Element<'b, Message, Renderer>> { - overlay::from_children(&mut self.children, tree, layout, renderer) - } -} - -impl<'a, Message, Renderer> From<Row<'a, Message, Renderer>> - for Element<'a, Message, Renderer> -where - Message: 'a, - Renderer: crate::Renderer + 'a, -{ - fn from(row: Row<'a, Message, Renderer>) -> Self { - Self::new(row) - } -} diff --git a/native/src/widget/rule.rs b/native/src/widget/rule.rs deleted file mode 100644 index 1ab6a0d3..00000000 --- a/native/src/widget/rule.rs +++ /dev/null @@ -1,147 +0,0 @@ -//! Display a horizontal or vertical rule for dividing content. -use crate::layout; -use crate::renderer; -use crate::widget::Tree; -use crate::{ - Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget, -}; - -pub use iced_style::rule::{Appearance, FillMode, StyleSheet}; - -/// Display a horizontal or vertical rule for dividing content. -#[allow(missing_debug_implementations)] -pub struct Rule<Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - width: Length, - height: Length, - is_horizontal: bool, - style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<Renderer> Rule<Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - /// Creates a horizontal [`Rule`] with the given height. - pub fn horizontal(height: impl Into<Pixels>) -> Self { - Rule { - width: Length::Fill, - height: Length::Fixed(height.into().0), - is_horizontal: true, - style: Default::default(), - } - } - - /// Creates a vertical [`Rule`] with the given width. - pub fn vertical(width: impl Into<Pixels>) -> Self { - Rule { - width: Length::Fixed(width.into().0), - height: Length::Fill, - is_horizontal: false, - style: Default::default(), - } - } - - /// Sets the style of the [`Rule`]. - pub fn style( - mut self, - style: impl Into<<Renderer::Theme as StyleSheet>::Style>, - ) -> Self { - self.style = style.into(); - self - } -} - -impl<Message, Renderer> Widget<Message, Renderer> for Rule<Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - 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, - _state: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - _style: &renderer::Style, - layout: Layout<'_>, - _cursor_position: Point, - _viewport: &Rectangle, - ) { - let bounds = layout.bounds(); - let style = theme.appearance(&self.style); - - let bounds = if self.is_horizontal { - let line_y = (bounds.y + (bounds.height / 2.0) - - (style.width as f32 / 2.0)) - .round(); - - let (offset, line_width) = style.fill_mode.fill(bounds.width); - let line_x = bounds.x + offset; - - Rectangle { - x: line_x, - y: line_y, - width: line_width, - height: style.width as f32, - } - } else { - let line_x = (bounds.x + (bounds.width / 2.0) - - (style.width as f32 / 2.0)) - .round(); - - let (offset, line_height) = style.fill_mode.fill(bounds.height); - let line_y = bounds.y + offset; - - Rectangle { - x: line_x, - y: line_y, - width: style.width as f32, - height: line_height, - } - }; - - renderer.fill_quad( - renderer::Quad { - bounds, - border_radius: style.radius.into(), - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - style.color, - ); - } -} - -impl<'a, Message, Renderer> From<Rule<Renderer>> - for Element<'a, Message, Renderer> -where - Message: 'a, - Renderer: 'a + crate::Renderer, - Renderer::Theme: StyleSheet, -{ - fn from(rule: Rule<Renderer>) -> Element<'a, Message, Renderer> { - Element::new(rule) - } -} diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs deleted file mode 100644 index c1df8c39..00000000 --- a/native/src/widget/scrollable.rs +++ /dev/null @@ -1,1327 +0,0 @@ -//! Navigate an endless amount of content with a scrollbar. -use crate::event::{self, Event}; -use crate::keyboard; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::touch; -use crate::widget; -use crate::widget::operation::{self, Operation}; -use crate::widget::tree::{self, Tree}; -use crate::{ - Background, Clipboard, Color, Command, Element, Layout, Length, Pixels, - Point, Rectangle, Shell, Size, Vector, Widget, -}; - -pub use iced_style::scrollable::StyleSheet; -pub use operation::scrollable::RelativeOffset; - -pub mod style { - //! The styles of a [`Scrollable`]. - //! - //! [`Scrollable`]: crate::widget::Scrollable - pub use iced_style::scrollable::{Scrollbar, Scroller}; -} - -/// A widget that can vertically display an infinite amount of content with a -/// scrollbar. -#[allow(missing_debug_implementations)] -pub struct Scrollable<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - id: Option<Id>, - height: Length, - vertical: Properties, - horizontal: Option<Properties>, - content: Element<'a, Message, Renderer>, - on_scroll: Option<Box<dyn Fn(RelativeOffset) -> Message + 'a>>, - style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - /// Creates a new [`Scrollable`]. - pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self { - Scrollable { - id: None, - height: Length::Shrink, - vertical: Properties::default(), - horizontal: None, - content: content.into(), - on_scroll: None, - style: Default::default(), - } - } - - /// Sets the [`Id`] of the [`Scrollable`]. - pub fn id(mut self, id: Id) -> Self { - self.id = Some(id); - self - } - - /// Sets the height of the [`Scrollable`]. - pub fn height(mut self, height: impl Into<Length>) -> Self { - self.height = height.into(); - self - } - - /// Configures the vertical scrollbar of the [`Scrollable`] . - pub fn vertical_scroll(mut self, properties: Properties) -> Self { - self.vertical = properties; - self - } - - /// Configures the horizontal scrollbar of the [`Scrollable`] . - pub fn horizontal_scroll(mut self, properties: Properties) -> Self { - self.horizontal = Some(properties); - self - } - - /// Sets a function to call when the [`Scrollable`] is scrolled. - /// - /// The function takes the new relative x & y offset of the [`Scrollable`] - /// (e.g. `0` means beginning, while `1` means end). - pub fn on_scroll( - mut self, - f: impl Fn(RelativeOffset) -> Message + 'a, - ) -> Self { - self.on_scroll = Some(Box::new(f)); - self - } - - /// Sets the style of the [`Scrollable`] . - pub fn style( - mut self, - style: impl Into<<Renderer::Theme as StyleSheet>::Style>, - ) -> Self { - self.style = style.into(); - self - } -} - -/// Properties of a scrollbar within a [`Scrollable`]. -#[derive(Debug)] -pub struct Properties { - width: f32, - margin: f32, - scroller_width: f32, -} - -impl Default for Properties { - fn default() -> Self { - Self { - width: 10.0, - margin: 0.0, - scroller_width: 10.0, - } - } -} - -impl Properties { - /// Creates new [`Properties`] for use in a [`Scrollable`]. - pub fn new() -> Self { - Self::default() - } - - /// Sets the scrollbar width of the [`Scrollable`] . - /// Silently enforces a minimum width of 1. - pub fn width(mut self, width: impl Into<Pixels>) -> Self { - self.width = width.into().0.max(1.0); - self - } - - /// Sets the scrollbar margin of the [`Scrollable`] . - pub fn margin(mut self, margin: impl Into<Pixels>) -> Self { - self.margin = margin.into().0; - self - } - - /// Sets the scroller width of the [`Scrollable`] . - /// Silently enforces a minimum width of 1. - pub fn scroller_width(mut self, scroller_width: impl Into<Pixels>) -> Self { - self.scroller_width = scroller_width.into().0.max(1.0); - self - } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> - for Scrollable<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - fn tag(&self) -> tree::Tag { - tree::Tag::of::<State>() - } - - fn state(&self) -> tree::State { - tree::State::new(State::new()) - } - - fn children(&self) -> Vec<Tree> { - vec![Tree::new(&self.content)] - } - - fn diff(&self, tree: &mut Tree) { - tree.diff_children(std::slice::from_ref(&self.content)) - } - - fn width(&self) -> Length { - self.content.as_widget().width() - } - - fn height(&self) -> Length { - self.height - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - layout( - renderer, - limits, - Widget::<Message, Renderer>::width(self), - self.height, - self.horizontal.is_some(), - |renderer, limits| { - self.content.as_widget().layout(renderer, limits) - }, - ) - } - - fn operate( - &self, - tree: &mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn Operation<Message>, - ) { - let state = tree.state.downcast_mut::<State>(); - - operation.scrollable(state, self.id.as_ref().map(|id| &id.0)); - - operation.container( - self.id.as_ref().map(|id| &id.0), - &mut |operation| { - self.content.as_widget().operate( - &mut tree.children[0], - layout.children().next().unwrap(), - renderer, - operation, - ); - }, - ); - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - update( - tree.state.downcast_mut::<State>(), - event, - layout, - cursor_position, - clipboard, - shell, - &self.vertical, - self.horizontal.as_ref(), - &self.on_scroll, - |event, layout, cursor_position, clipboard, shell| { - self.content.as_widget_mut().on_event( - &mut tree.children[0], - event, - layout, - cursor_position, - renderer, - clipboard, - shell, - ) - }, - ) - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - ) { - draw( - tree.state.downcast_ref::<State>(), - renderer, - theme, - layout, - cursor_position, - &self.vertical, - self.horizontal.as_ref(), - &self.style, - |renderer, layout, cursor_position, viewport| { - self.content.as_widget().draw( - &tree.children[0], - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ) - }, - ) - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - mouse_interaction( - tree.state.downcast_ref::<State>(), - layout, - cursor_position, - &self.vertical, - self.horizontal.as_ref(), - |layout, cursor_position, viewport| { - self.content.as_widget().mouse_interaction( - &tree.children[0], - layout, - cursor_position, - viewport, - renderer, - ) - }, - ) - } - - fn overlay<'b>( - &'b mut self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option<overlay::Element<'b, Message, Renderer>> { - self.content - .as_widget_mut() - .overlay( - &mut tree.children[0], - layout.children().next().unwrap(), - renderer, - ) - .map(|overlay| { - let bounds = layout.bounds(); - let content_layout = layout.children().next().unwrap(); - let content_bounds = content_layout.bounds(); - let offset = tree - .state - .downcast_ref::<State>() - .offset(bounds, content_bounds); - - overlay.translate(Vector::new(-offset.x, -offset.y)) - }) - } -} - -impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>> - for Element<'a, Message, Renderer> -where - Message: 'a, - Renderer: 'a + crate::Renderer, - Renderer::Theme: StyleSheet, -{ - fn from( - text_input: Scrollable<'a, Message, Renderer>, - ) -> Element<'a, Message, Renderer> { - Element::new(text_input) - } -} - -/// The identifier of a [`Scrollable`]. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Id(widget::Id); - -impl Id { - /// Creates a custom [`Id`]. - pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self { - Self(widget::Id::new(id)) - } - - /// Creates a unique [`Id`]. - /// - /// This function produces a different [`Id`] every time it is called. - pub fn unique() -> Self { - Self(widget::Id::unique()) - } -} - -impl From<Id> for widget::Id { - fn from(id: Id) -> Self { - id.0 - } -} - -/// Produces a [`Command`] that snaps the [`Scrollable`] with the given [`Id`] -/// to the provided `percentage` along the x & y axis. -pub fn snap_to<Message: 'static>( - id: Id, - offset: RelativeOffset, -) -> Command<Message> { - Command::widget(operation::scrollable::snap_to(id.0, offset)) -} - -/// Computes the layout of a [`Scrollable`]. -pub fn layout<Renderer>( - renderer: &Renderer, - limits: &layout::Limits, - width: Length, - height: Length, - horizontal_enabled: bool, - layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, -) -> layout::Node { - let limits = limits - .max_height(f32::INFINITY) - .max_width(if horizontal_enabled { - f32::INFINITY - } else { - limits.max().width - }) - .width(width) - .height(height); - - let child_limits = layout::Limits::new( - Size::new(limits.min().width, 0.0), - Size::new( - if horizontal_enabled { - f32::INFINITY - } else { - limits.max().width - }, - f32::MAX, - ), - ); - - let content = layout_content(renderer, &child_limits); - let size = limits.resolve(content.size()); - - layout::Node::with_children(size, vec![content]) -} - -/// Processes an [`Event`] and updates the [`State`] of a [`Scrollable`] -/// accordingly. -pub fn update<Message>( - state: &mut State, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - vertical: &Properties, - horizontal: Option<&Properties>, - on_scroll: &Option<Box<dyn Fn(RelativeOffset) -> Message + '_>>, - update_content: impl FnOnce( - Event, - Layout<'_>, - Point, - &mut dyn Clipboard, - &mut Shell<'_, Message>, - ) -> event::Status, -) -> event::Status { - let bounds = layout.bounds(); - let mouse_over_scrollable = bounds.contains(cursor_position); - - let content = layout.children().next().unwrap(); - let content_bounds = content.bounds(); - - let scrollbars = - Scrollbars::new(state, vertical, horizontal, bounds, content_bounds); - - let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) = - scrollbars.is_mouse_over(cursor_position); - - let event_status = { - let cursor_position = if mouse_over_scrollable - && !(mouse_over_y_scrollbar || mouse_over_x_scrollbar) - { - cursor_position + state.offset(bounds, content_bounds) - } else { - // TODO: Make `cursor_position` an `Option<Point>` so we can encode - // cursor availability. - // This will probably happen naturally once we add multi-window - // support. - Point::new(-1.0, -1.0) - }; - - update_content( - event.clone(), - content, - cursor_position, - clipboard, - shell, - ) - }; - - if let event::Status::Captured = event_status { - return event::Status::Captured; - } - - if let Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) = event - { - state.keyboard_modifiers = modifiers; - - return event::Status::Ignored; - } - - if mouse_over_scrollable { - match event { - Event::Mouse(mouse::Event::WheelScrolled { delta }) => { - let delta = match delta { - mouse::ScrollDelta::Lines { x, y } => { - // TODO: Configurable speed/friction (?) - let movement = if state.keyboard_modifiers.shift() { - Vector::new(y, x) - } else { - Vector::new(x, y) - }; - - movement * 60.0 - } - mouse::ScrollDelta::Pixels { x, y } => Vector::new(x, y), - }; - - state.scroll(delta, bounds, content_bounds); - - notify_on_scroll( - state, - on_scroll, - bounds, - content_bounds, - shell, - ); - - return event::Status::Captured; - } - Event::Touch(event) - if state.scroll_area_touched_at.is_some() - || !mouse_over_y_scrollbar && !mouse_over_x_scrollbar => - { - match event { - touch::Event::FingerPressed { .. } => { - state.scroll_area_touched_at = Some(cursor_position); - } - touch::Event::FingerMoved { .. } => { - if let Some(scroll_box_touched_at) = - state.scroll_area_touched_at - { - let delta = Vector::new( - cursor_position.x - scroll_box_touched_at.x, - cursor_position.y - scroll_box_touched_at.y, - ); - - state.scroll(delta, bounds, content_bounds); - - state.scroll_area_touched_at = - Some(cursor_position); - - notify_on_scroll( - state, - on_scroll, - bounds, - content_bounds, - shell, - ); - } - } - touch::Event::FingerLifted { .. } - | touch::Event::FingerLost { .. } => { - state.scroll_area_touched_at = None; - } - } - - return event::Status::Captured; - } - _ => {} - } - } - - if let Some(scroller_grabbed_at) = state.y_scroller_grabbed_at { - match event { - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) - | Event::Touch(touch::Event::FingerLost { .. }) => { - state.y_scroller_grabbed_at = None; - - return event::Status::Captured; - } - Event::Mouse(mouse::Event::CursorMoved { .. }) - | Event::Touch(touch::Event::FingerMoved { .. }) => { - if let Some(scrollbar) = scrollbars.y { - state.scroll_y_to( - scrollbar.scroll_percentage_y( - scroller_grabbed_at, - cursor_position, - ), - bounds, - content_bounds, - ); - - notify_on_scroll( - state, - on_scroll, - bounds, - content_bounds, - shell, - ); - - return event::Status::Captured; - } - } - _ => {} - } - } else if mouse_over_y_scrollbar { - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - if let (Some(scroller_grabbed_at), Some(scrollbar)) = - (scrollbars.grab_y_scroller(cursor_position), scrollbars.y) - { - state.scroll_y_to( - scrollbar.scroll_percentage_y( - scroller_grabbed_at, - cursor_position, - ), - bounds, - content_bounds, - ); - - state.y_scroller_grabbed_at = Some(scroller_grabbed_at); - - notify_on_scroll( - state, - on_scroll, - bounds, - content_bounds, - shell, - ); - } - - return event::Status::Captured; - } - _ => {} - } - } - - if let Some(scroller_grabbed_at) = state.x_scroller_grabbed_at { - match event { - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) - | Event::Touch(touch::Event::FingerLost { .. }) => { - state.x_scroller_grabbed_at = None; - - return event::Status::Captured; - } - Event::Mouse(mouse::Event::CursorMoved { .. }) - | Event::Touch(touch::Event::FingerMoved { .. }) => { - if let Some(scrollbar) = scrollbars.x { - state.scroll_x_to( - scrollbar.scroll_percentage_x( - scroller_grabbed_at, - cursor_position, - ), - bounds, - content_bounds, - ); - - notify_on_scroll( - state, - on_scroll, - bounds, - content_bounds, - shell, - ); - } - - return event::Status::Captured; - } - _ => {} - } - } else if mouse_over_x_scrollbar { - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - if let (Some(scroller_grabbed_at), Some(scrollbar)) = - (scrollbars.grab_x_scroller(cursor_position), scrollbars.x) - { - state.scroll_x_to( - scrollbar.scroll_percentage_x( - scroller_grabbed_at, - cursor_position, - ), - bounds, - content_bounds, - ); - - state.x_scroller_grabbed_at = Some(scroller_grabbed_at); - - notify_on_scroll( - state, - on_scroll, - bounds, - content_bounds, - shell, - ); - - return event::Status::Captured; - } - } - _ => {} - } - } - - event::Status::Ignored -} - -/// Computes the current [`mouse::Interaction`] of a [`Scrollable`]. -pub fn mouse_interaction( - state: &State, - layout: Layout<'_>, - cursor_position: Point, - vertical: &Properties, - horizontal: Option<&Properties>, - content_interaction: impl FnOnce( - Layout<'_>, - Point, - &Rectangle, - ) -> mouse::Interaction, -) -> mouse::Interaction { - let bounds = layout.bounds(); - let mouse_over_scrollable = bounds.contains(cursor_position); - - let content_layout = layout.children().next().unwrap(); - let content_bounds = content_layout.bounds(); - - let scrollbars = - Scrollbars::new(state, vertical, horizontal, bounds, content_bounds); - - let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) = - scrollbars.is_mouse_over(cursor_position); - - if (mouse_over_x_scrollbar || mouse_over_y_scrollbar) - || state.scrollers_grabbed() - { - mouse::Interaction::Idle - } else { - let offset = state.offset(bounds, content_bounds); - - let cursor_position = if mouse_over_scrollable - && !(mouse_over_y_scrollbar || mouse_over_x_scrollbar) - { - cursor_position + offset - } else { - Point::new(-1.0, -1.0) - }; - - content_interaction( - content_layout, - cursor_position, - &Rectangle { - y: bounds.y + offset.y, - x: bounds.x + offset.x, - ..bounds - }, - ) - } -} - -/// Draws a [`Scrollable`]. -pub fn draw<Renderer>( - state: &State, - renderer: &mut Renderer, - theme: &Renderer::Theme, - layout: Layout<'_>, - cursor_position: Point, - vertical: &Properties, - horizontal: Option<&Properties>, - style: &<Renderer::Theme as StyleSheet>::Style, - draw_content: impl FnOnce(&mut Renderer, Layout<'_>, Point, &Rectangle), -) where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - let bounds = layout.bounds(); - let content_layout = layout.children().next().unwrap(); - let content_bounds = content_layout.bounds(); - - let scrollbars = - Scrollbars::new(state, vertical, horizontal, bounds, content_bounds); - - let mouse_over_scrollable = bounds.contains(cursor_position); - let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) = - scrollbars.is_mouse_over(cursor_position); - - let offset = state.offset(bounds, content_bounds); - - let cursor_position = if mouse_over_scrollable - && !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) - { - cursor_position + offset - } else { - Point::new(-1.0, -1.0) - }; - - // Draw inner content - if scrollbars.active() { - renderer.with_layer(bounds, |renderer| { - renderer.with_translation( - Vector::new(-offset.x, -offset.y), - |renderer| { - draw_content( - renderer, - content_layout, - cursor_position, - &Rectangle { - y: bounds.y + offset.y, - x: bounds.x + offset.x, - ..bounds - }, - ); - }, - ); - }); - - let draw_scrollbar = - |renderer: &mut Renderer, - style: style::Scrollbar, - scrollbar: &Scrollbar| { - //track - if style.background.is_some() - || (style.border_color != Color::TRANSPARENT - && style.border_width > 0.0) - { - renderer.fill_quad( - renderer::Quad { - bounds: scrollbar.bounds, - border_radius: style.border_radius.into(), - border_width: style.border_width, - border_color: style.border_color, - }, - style - .background - .unwrap_or(Background::Color(Color::TRANSPARENT)), - ); - } - - //thumb - if style.scroller.color != Color::TRANSPARENT - || (style.scroller.border_color != Color::TRANSPARENT - && style.scroller.border_width > 0.0) - { - renderer.fill_quad( - renderer::Quad { - bounds: scrollbar.scroller.bounds, - border_radius: style.scroller.border_radius.into(), - border_width: style.scroller.border_width, - border_color: style.scroller.border_color, - }, - style.scroller.color, - ); - } - }; - - renderer.with_layer( - Rectangle { - width: bounds.width + 2.0, - height: bounds.height + 2.0, - ..bounds - }, - |renderer| { - //draw y scrollbar - if let Some(scrollbar) = scrollbars.y { - let style = if state.y_scroller_grabbed_at.is_some() { - theme.dragging(style) - } else if mouse_over_y_scrollbar { - theme.hovered(style) - } else { - theme.active(style) - }; - - draw_scrollbar(renderer, style, &scrollbar); - } - - //draw x scrollbar - if let Some(scrollbar) = scrollbars.x { - let style = if state.x_scroller_grabbed_at.is_some() { - theme.dragging_horizontal(style) - } else if mouse_over_x_scrollbar { - theme.hovered_horizontal(style) - } else { - theme.active_horizontal(style) - }; - - draw_scrollbar(renderer, style, &scrollbar); - } - }, - ); - } else { - draw_content( - renderer, - content_layout, - cursor_position, - &Rectangle { - x: bounds.x + offset.x, - y: bounds.y + offset.y, - ..bounds - }, - ); - } -} - -fn notify_on_scroll<Message>( - state: &State, - on_scroll: &Option<Box<dyn Fn(RelativeOffset) -> Message + '_>>, - bounds: Rectangle, - content_bounds: Rectangle, - shell: &mut Shell<'_, Message>, -) { - if let Some(on_scroll) = on_scroll { - if content_bounds.width <= bounds.width - && content_bounds.height <= bounds.height - { - return; - } - - let x = state.offset_x.absolute(bounds.width, content_bounds.width) - / (content_bounds.width - bounds.width); - - let y = state - .offset_y - .absolute(bounds.height, content_bounds.height) - / (content_bounds.height - bounds.height); - - shell.publish(on_scroll(RelativeOffset { x, y })) - } -} - -/// The local state of a [`Scrollable`]. -#[derive(Debug, Clone, Copy)] -pub struct State { - scroll_area_touched_at: Option<Point>, - offset_y: Offset, - y_scroller_grabbed_at: Option<f32>, - offset_x: Offset, - x_scroller_grabbed_at: Option<f32>, - keyboard_modifiers: keyboard::Modifiers, -} - -impl Default for State { - fn default() -> Self { - Self { - scroll_area_touched_at: None, - offset_y: Offset::Absolute(0.0), - y_scroller_grabbed_at: None, - offset_x: Offset::Absolute(0.0), - x_scroller_grabbed_at: None, - keyboard_modifiers: keyboard::Modifiers::default(), - } - } -} - -impl operation::Scrollable for State { - fn snap_to(&mut self, offset: RelativeOffset) { - State::snap_to(self, offset); - } -} - -#[derive(Debug, Clone, Copy)] -enum Offset { - Absolute(f32), - Relative(f32), -} - -impl Offset { - fn absolute(self, window: f32, content: f32) -> f32 { - match self { - Offset::Absolute(absolute) => { - absolute.min((content - window).max(0.0)) - } - Offset::Relative(percentage) => { - ((content - window) * percentage).max(0.0) - } - } - } -} - -impl State { - /// Creates a new [`State`] with the scrollbar(s) at the beginning. - pub fn new() -> Self { - State::default() - } - - /// Apply a scrolling offset to the current [`State`], given the bounds of - /// the [`Scrollable`] and its contents. - pub fn scroll( - &mut self, - delta: Vector<f32>, - bounds: Rectangle, - content_bounds: Rectangle, - ) { - if bounds.height < content_bounds.height { - self.offset_y = Offset::Absolute( - (self.offset_y.absolute(bounds.height, content_bounds.height) - - delta.y) - .clamp(0.0, content_bounds.height - bounds.height), - ) - } - - if bounds.width < content_bounds.width { - self.offset_x = Offset::Absolute( - (self.offset_x.absolute(bounds.width, content_bounds.width) - - delta.x) - .clamp(0.0, content_bounds.width - bounds.width), - ); - } - } - - /// Scrolls the [`Scrollable`] to a relative amount along the y axis. - /// - /// `0` represents scrollbar at the beginning, while `1` represents scrollbar at - /// the end. - pub fn scroll_y_to( - &mut self, - percentage: f32, - bounds: Rectangle, - content_bounds: Rectangle, - ) { - self.offset_y = Offset::Relative(percentage.clamp(0.0, 1.0)); - self.unsnap(bounds, content_bounds); - } - - /// Scrolls the [`Scrollable`] to a relative amount along the x axis. - /// - /// `0` represents scrollbar at the beginning, while `1` represents scrollbar at - /// the end. - pub fn scroll_x_to( - &mut self, - percentage: f32, - bounds: Rectangle, - content_bounds: Rectangle, - ) { - self.offset_x = Offset::Relative(percentage.clamp(0.0, 1.0)); - self.unsnap(bounds, content_bounds); - } - - /// Snaps the scroll position to a [`RelativeOffset`]. - pub fn snap_to(&mut self, offset: RelativeOffset) { - self.offset_x = Offset::Relative(offset.x.clamp(0.0, 1.0)); - self.offset_y = Offset::Relative(offset.y.clamp(0.0, 1.0)); - } - - /// Unsnaps the current scroll position, if snapped, given the bounds of the - /// [`Scrollable`] and its contents. - pub fn unsnap(&mut self, bounds: Rectangle, content_bounds: Rectangle) { - self.offset_x = Offset::Absolute( - self.offset_x.absolute(bounds.width, content_bounds.width), - ); - self.offset_y = Offset::Absolute( - self.offset_y.absolute(bounds.height, content_bounds.height), - ); - } - - /// Returns the scrolling offset of the [`State`], given the bounds of the - /// [`Scrollable`] and its contents. - pub fn offset( - &self, - bounds: Rectangle, - content_bounds: Rectangle, - ) -> Vector { - Vector::new( - self.offset_x.absolute(bounds.width, content_bounds.width), - self.offset_y.absolute(bounds.height, content_bounds.height), - ) - } - - /// Returns whether any scroller is currently grabbed or not. - pub fn scrollers_grabbed(&self) -> bool { - self.x_scroller_grabbed_at.is_some() - || self.y_scroller_grabbed_at.is_some() - } -} - -#[derive(Debug)] -/// State of both [`Scrollbar`]s. -struct Scrollbars { - y: Option<Scrollbar>, - x: Option<Scrollbar>, -} - -impl Scrollbars { - /// Create y and/or x scrollbar(s) if content is overflowing the [`Scrollable`] bounds. - fn new( - state: &State, - vertical: &Properties, - horizontal: Option<&Properties>, - bounds: Rectangle, - content_bounds: Rectangle, - ) -> Self { - let offset = state.offset(bounds, content_bounds); - - let show_scrollbar_x = horizontal.and_then(|h| { - if content_bounds.width > bounds.width { - Some(h) - } else { - None - } - }); - - let y_scrollbar = if content_bounds.height > bounds.height { - let Properties { - width, - margin, - scroller_width, - } = *vertical; - - // Adjust the height of the vertical scrollbar if the horizontal scrollbar - // is present - let x_scrollbar_height = show_scrollbar_x - .map_or(0.0, |h| h.width.max(h.scroller_width) + h.margin); - - let total_scrollbar_width = - width.max(scroller_width) + 2.0 * margin; - - // Total bounds of the scrollbar + margin + scroller width - let total_scrollbar_bounds = Rectangle { - x: bounds.x + bounds.width - total_scrollbar_width, - y: bounds.y, - width: total_scrollbar_width, - height: (bounds.height - x_scrollbar_height).max(0.0), - }; - - // Bounds of just the scrollbar - let scrollbar_bounds = Rectangle { - x: bounds.x + bounds.width - - total_scrollbar_width / 2.0 - - width / 2.0, - y: bounds.y, - width, - height: (bounds.height - x_scrollbar_height).max(0.0), - }; - - let ratio = bounds.height / content_bounds.height; - // min height for easier grabbing with super tall content - let scroller_height = (bounds.height * ratio).max(2.0); - let scroller_offset = offset.y * ratio; - - let scroller_bounds = Rectangle { - x: bounds.x + bounds.width - - total_scrollbar_width / 2.0 - - scroller_width / 2.0, - y: (scrollbar_bounds.y + scroller_offset - x_scrollbar_height) - .max(0.0), - width: scroller_width, - height: scroller_height, - }; - - Some(Scrollbar { - total_bounds: total_scrollbar_bounds, - bounds: scrollbar_bounds, - scroller: Scroller { - bounds: scroller_bounds, - }, - }) - } else { - None - }; - - let x_scrollbar = if let Some(horizontal) = show_scrollbar_x { - let Properties { - width, - margin, - scroller_width, - } = *horizontal; - - // Need to adjust the width of the horizontal scrollbar if the vertical scrollbar - // is present - let scrollbar_y_width = y_scrollbar.map_or(0.0, |_| { - vertical.width.max(vertical.scroller_width) + vertical.margin - }); - - let total_scrollbar_height = - width.max(scroller_width) + 2.0 * margin; - - // Total bounds of the scrollbar + margin + scroller width - let total_scrollbar_bounds = Rectangle { - x: bounds.x, - y: bounds.y + bounds.height - total_scrollbar_height, - width: (bounds.width - scrollbar_y_width).max(0.0), - height: total_scrollbar_height, - }; - - // Bounds of just the scrollbar - let scrollbar_bounds = Rectangle { - x: bounds.x, - y: bounds.y + bounds.height - - total_scrollbar_height / 2.0 - - width / 2.0, - width: (bounds.width - scrollbar_y_width).max(0.0), - height: width, - }; - - let ratio = bounds.width / content_bounds.width; - // min width for easier grabbing with extra wide content - let scroller_length = (bounds.width * ratio).max(2.0); - let scroller_offset = offset.x * ratio; - - let scroller_bounds = Rectangle { - x: (scrollbar_bounds.x + scroller_offset - scrollbar_y_width) - .max(0.0), - y: bounds.y + bounds.height - - total_scrollbar_height / 2.0 - - scroller_width / 2.0, - width: scroller_length, - height: scroller_width, - }; - - Some(Scrollbar { - total_bounds: total_scrollbar_bounds, - bounds: scrollbar_bounds, - scroller: Scroller { - bounds: scroller_bounds, - }, - }) - } else { - None - }; - - Self { - y: y_scrollbar, - x: x_scrollbar, - } - } - - fn is_mouse_over(&self, cursor_position: Point) -> (bool, bool) { - ( - self.y - .as_ref() - .map(|scrollbar| scrollbar.is_mouse_over(cursor_position)) - .unwrap_or(false), - self.x - .as_ref() - .map(|scrollbar| scrollbar.is_mouse_over(cursor_position)) - .unwrap_or(false), - ) - } - - fn grab_y_scroller(&self, cursor_position: Point) -> Option<f32> { - self.y.and_then(|scrollbar| { - if scrollbar.total_bounds.contains(cursor_position) { - Some(if scrollbar.scroller.bounds.contains(cursor_position) { - (cursor_position.y - scrollbar.scroller.bounds.y) - / scrollbar.scroller.bounds.height - } else { - 0.5 - }) - } else { - None - } - }) - } - - fn grab_x_scroller(&self, cursor_position: Point) -> Option<f32> { - self.x.and_then(|scrollbar| { - if scrollbar.total_bounds.contains(cursor_position) { - Some(if scrollbar.scroller.bounds.contains(cursor_position) { - (cursor_position.x - scrollbar.scroller.bounds.x) - / scrollbar.scroller.bounds.width - } else { - 0.5 - }) - } else { - None - } - }) - } - - fn active(&self) -> bool { - self.y.is_some() || self.x.is_some() - } -} - -/// The scrollbar of a [`Scrollable`]. -#[derive(Debug, Copy, Clone)] -struct Scrollbar { - /// The total bounds of the [`Scrollbar`], including the scrollbar, the scroller, - /// and the scrollbar margin. - total_bounds: Rectangle, - - /// The bounds of just the [`Scrollbar`]. - bounds: Rectangle, - - /// The state of this scrollbar's [`Scroller`]. - scroller: Scroller, -} - -impl Scrollbar { - /// Returns whether the mouse is over the scrollbar or not. - fn is_mouse_over(&self, cursor_position: Point) -> bool { - self.total_bounds.contains(cursor_position) - } - - /// Returns the y-axis scrolled percentage from the cursor position. - fn scroll_percentage_y( - &self, - grabbed_at: f32, - cursor_position: Point, - ) -> f32 { - if cursor_position.x < 0.0 && cursor_position.y < 0.0 { - // cursor position is unavailable! Set to either end or beginning of scrollbar depending - // on where the thumb currently is in the track - (self.scroller.bounds.y / self.total_bounds.height).round() - } else { - (cursor_position.y - - self.bounds.y - - self.scroller.bounds.height * grabbed_at) - / (self.bounds.height - self.scroller.bounds.height) - } - } - - /// Returns the x-axis scrolled percentage from the cursor position. - fn scroll_percentage_x( - &self, - grabbed_at: f32, - cursor_position: Point, - ) -> f32 { - if cursor_position.x < 0.0 && cursor_position.y < 0.0 { - (self.scroller.bounds.x / self.total_bounds.width).round() - } else { - (cursor_position.x - - self.bounds.x - - self.scroller.bounds.width * grabbed_at) - / (self.bounds.width - self.scroller.bounds.width) - } - } -} - -/// The handle of a [`Scrollbar`]. -#[derive(Debug, Clone, Copy)] -struct Scroller { - /// The bounds of the [`Scroller`]. - bounds: Rectangle, -} diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs deleted file mode 100644 index d3715b1c..00000000 --- a/native/src/widget/slider.rs +++ /dev/null @@ -1,473 +0,0 @@ -//! Display an interactive selector of a single value from a range of values. -//! -//! A [`Slider`] has some local [`State`]. -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::touch; -use crate::widget::tree::{self, Tree}; -use crate::{ - Background, Clipboard, Color, Element, Layout, Length, Pixels, Point, - Rectangle, Shell, Size, Widget, -}; - -use std::ops::RangeInclusive; - -pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet}; - -/// An horizontal bar and a handle that selects a single value from a range of -/// values. -/// -/// A [`Slider`] will try to fill the horizontal space of its container. -/// -/// The [`Slider`] range of numeric values is generic and its step size defaults -/// to 1 unit. -/// -/// # Example -/// ``` -/// # use iced_native::widget::slider; -/// # use iced_native::renderer::Null; -/// # -/// # type Slider<'a, T, Message> = slider::Slider<'a, T, Message, Null>; -/// # -/// #[derive(Clone)] -/// pub enum Message { -/// SliderChanged(f32), -/// } -/// -/// let value = 50.0; -/// -/// Slider::new(0.0..=100.0, value, Message::SliderChanged); -/// ``` -/// -///  -#[allow(missing_debug_implementations)] -pub struct Slider<'a, T, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - range: RangeInclusive<T>, - step: T, - value: T, - on_change: Box<dyn Fn(T) -> Message + 'a>, - on_release: Option<Message>, - width: Length, - height: f32, - style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, T, Message, Renderer> Slider<'a, T, Message, Renderer> -where - T: Copy + From<u8> + std::cmp::PartialOrd, - Message: Clone, - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - /// The default height of a [`Slider`]. - pub const DEFAULT_HEIGHT: f32 = 22.0; - - /// Creates a new [`Slider`]. - /// - /// It expects: - /// * an inclusive range of possible values - /// * the current value of the [`Slider`] - /// * a function that will be called when the [`Slider`] is dragged. - /// It receives the new value of the [`Slider`] and must produce a - /// `Message`. - pub fn new<F>(range: RangeInclusive<T>, value: T, on_change: F) -> Self - where - F: 'a + Fn(T) -> Message, - { - let value = if value >= *range.start() { - value - } else { - *range.start() - }; - - let value = if value <= *range.end() { - value - } else { - *range.end() - }; - - Slider { - value, - range, - step: T::from(1), - on_change: Box::new(on_change), - on_release: None, - width: Length::Fill, - height: Self::DEFAULT_HEIGHT, - style: Default::default(), - } - } - - /// Sets the release message of the [`Slider`]. - /// This is called when the mouse is released from the slider. - /// - /// Typically, the user's interaction with the slider is finished when this message is produced. - /// This is useful if you need to spawn a long-running task from the slider's result, where - /// the default on_change message could create too many events. - pub fn on_release(mut self, on_release: Message) -> Self { - self.on_release = Some(on_release); - self - } - - /// Sets the width of the [`Slider`]. - pub fn width(mut self, width: impl Into<Length>) -> Self { - self.width = width.into(); - self - } - - /// Sets the height of the [`Slider`]. - pub fn height(mut self, height: impl Into<Pixels>) -> Self { - self.height = height.into().0; - self - } - - /// Sets the style of the [`Slider`]. - pub fn style( - mut self, - style: impl Into<<Renderer::Theme as StyleSheet>::Style>, - ) -> Self { - self.style = style.into(); - self - } - - /// Sets the step size of the [`Slider`]. - pub fn step(mut self, step: T) -> Self { - self.step = step; - self - } -} - -impl<'a, T, Message, Renderer> Widget<Message, Renderer> - for Slider<'a, T, Message, Renderer> -where - T: Copy + Into<f64> + num_traits::FromPrimitive, - Message: Clone, - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - fn tag(&self) -> tree::Tag { - tree::Tag::of::<State>() - } - - fn state(&self) -> tree::State { - tree::State::new(State::new()) - } - - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - Length::Shrink - } - - fn layout( - &self, - _renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - let limits = limits.width(self.width).height(self.height); - let size = limits.resolve(Size::ZERO); - - layout::Node::new(size) - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - _renderer: &Renderer, - _clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - update( - event, - layout, - cursor_position, - shell, - tree.state.downcast_mut::<State>(), - &mut self.value, - &self.range, - self.step, - self.on_change.as_ref(), - &self.on_release, - ) - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - _style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - ) { - draw( - renderer, - layout, - cursor_position, - tree.state.downcast_ref::<State>(), - self.value, - &self.range, - theme, - &self.style, - ) - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - mouse_interaction( - layout, - cursor_position, - tree.state.downcast_ref::<State>(), - ) - } -} - -impl<'a, T, Message, Renderer> From<Slider<'a, T, Message, Renderer>> - for Element<'a, Message, Renderer> -where - T: 'a + Copy + Into<f64> + num_traits::FromPrimitive, - Message: 'a + Clone, - Renderer: 'a + crate::Renderer, - Renderer::Theme: StyleSheet, -{ - fn from( - slider: Slider<'a, T, Message, Renderer>, - ) -> Element<'a, Message, Renderer> { - Element::new(slider) - } -} - -/// Processes an [`Event`] and updates the [`State`] of a [`Slider`] -/// accordingly. -pub fn update<Message, T>( - event: Event, - layout: Layout<'_>, - cursor_position: Point, - shell: &mut Shell<'_, Message>, - state: &mut State, - value: &mut T, - range: &RangeInclusive<T>, - step: T, - on_change: &dyn Fn(T) -> Message, - on_release: &Option<Message>, -) -> event::Status -where - T: Copy + Into<f64> + num_traits::FromPrimitive, - Message: Clone, -{ - let is_dragging = state.is_dragging; - - let mut change = || { - let bounds = layout.bounds(); - let new_value = if cursor_position.x <= bounds.x { - *range.start() - } else if cursor_position.x >= bounds.x + bounds.width { - *range.end() - } else { - let step = step.into(); - let start = (*range.start()).into(); - let end = (*range.end()).into(); - - let percent = f64::from(cursor_position.x - bounds.x) - / f64::from(bounds.width); - - let steps = (percent * (end - start) / step).round(); - let value = steps * step + start; - - if let Some(value) = T::from_f64(value) { - value - } else { - return; - } - }; - - if ((*value).into() - new_value.into()).abs() > f64::EPSILON { - shell.publish((on_change)(new_value)); - - *value = new_value; - } - }; - - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - if layout.bounds().contains(cursor_position) { - change(); - state.is_dragging = true; - - return event::Status::Captured; - } - } - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) - | Event::Touch(touch::Event::FingerLost { .. }) => { - if is_dragging { - if let Some(on_release) = on_release.clone() { - shell.publish(on_release); - } - state.is_dragging = false; - - return event::Status::Captured; - } - } - Event::Mouse(mouse::Event::CursorMoved { .. }) - | Event::Touch(touch::Event::FingerMoved { .. }) => { - if is_dragging { - change(); - - return event::Status::Captured; - } - } - _ => {} - } - - event::Status::Ignored -} - -/// Draws a [`Slider`]. -pub fn draw<T, R>( - renderer: &mut R, - layout: Layout<'_>, - cursor_position: Point, - state: &State, - value: T, - range: &RangeInclusive<T>, - style_sheet: &dyn StyleSheet<Style = <R::Theme as StyleSheet>::Style>, - style: &<R::Theme as StyleSheet>::Style, -) where - T: Into<f64> + Copy, - R: crate::Renderer, - R::Theme: StyleSheet, -{ - let bounds = layout.bounds(); - let is_mouse_over = bounds.contains(cursor_position); - - let style = if state.is_dragging { - style_sheet.dragging(style) - } else if is_mouse_over { - style_sheet.hovered(style) - } else { - style_sheet.active(style) - }; - - let rail_y = bounds.y + (bounds.height / 2.0).round(); - - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: bounds.x, - y: rail_y - 1.0, - width: bounds.width, - height: 2.0, - }, - border_radius: 0.0.into(), - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - style.rail_colors.0, - ); - - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: bounds.x, - y: rail_y + 1.0, - width: bounds.width, - height: 2.0, - }, - border_radius: 0.0.into(), - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - Background::Color(style.rail_colors.1), - ); - - let (handle_width, handle_height, handle_border_radius) = match style - .handle - .shape - { - HandleShape::Circle { radius } => (radius * 2.0, radius * 2.0, radius), - HandleShape::Rectangle { - width, - border_radius, - } => (f32::from(width), bounds.height, border_radius), - }; - - let value = value.into() as f32; - let (range_start, range_end) = { - let (start, end) = range.clone().into_inner(); - - (start.into() as f32, end.into() as f32) - }; - - let handle_offset = if range_start >= range_end { - 0.0 - } else { - (bounds.width - handle_width) * (value - range_start) - / (range_end - range_start) - }; - - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: bounds.x + handle_offset.round(), - y: rail_y - handle_height / 2.0, - width: handle_width, - height: handle_height, - }, - border_radius: handle_border_radius.into(), - border_width: style.handle.border_width, - border_color: style.handle.border_color, - }, - style.handle.color, - ); -} - -/// Computes the current [`mouse::Interaction`] of a [`Slider`]. -pub fn mouse_interaction( - layout: Layout<'_>, - cursor_position: Point, - state: &State, -) -> mouse::Interaction { - let bounds = layout.bounds(); - let is_mouse_over = bounds.contains(cursor_position); - - if state.is_dragging { - mouse::Interaction::Grabbing - } else if is_mouse_over { - mouse::Interaction::Grab - } else { - mouse::Interaction::default() - } -} - -/// The local state of a [`Slider`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct State { - is_dragging: bool, -} - -impl State { - /// Creates a new [`State`]. - pub fn new() -> State { - State::default() - } -} diff --git a/native/src/widget/space.rs b/native/src/widget/space.rs deleted file mode 100644 index a6fc977e..00000000 --- a/native/src/widget/space.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! Distribute content vertically. -use crate::layout; -use crate::renderer; -use crate::widget::Tree; -use crate::{Element, 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. - pub fn new(width: impl Into<Length>, height: impl Into<Length>) -> Self { - Space { - width: width.into(), - height: height.into(), - } - } - - /// Creates an amount of horizontal [`Space`]. - pub fn with_width(width: impl Into<Length>) -> Self { - Space { - width: width.into(), - height: Length::Shrink, - } - } - - /// Creates an amount of vertical [`Space`]. - pub fn with_height(height: impl Into<Length>) -> Self { - Space { - width: Length::Shrink, - height: height.into(), - } - } -} - -impl<Message, Renderer> Widget<Message, Renderer> for Space -where - Renderer: crate::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, - _state: &Tree, - _renderer: &mut Renderer, - _theme: &Renderer::Theme, - _style: &renderer::Style, - _layout: Layout<'_>, - _cursor_position: Point, - _viewport: &Rectangle, - ) { - } -} - -impl<'a, Message, Renderer> From<Space> for Element<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Message: 'a, -{ - 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 deleted file mode 100644 index f5ed0a6c..00000000 --- a/native/src/widget/svg.rs +++ /dev/null @@ -1,195 +0,0 @@ -//! Display vector graphics in your application. -use crate::layout; -use crate::renderer; -use crate::svg; -use crate::widget::Tree; -use crate::{ - ContentFit, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, -}; - -use std::path::PathBuf; - -pub use iced_style::svg::{Appearance, StyleSheet}; -pub use svg::Handle; - -/// 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. -#[allow(missing_debug_implementations)] -pub struct Svg<Renderer> -where - Renderer: svg::Renderer, - Renderer::Theme: StyleSheet, -{ - handle: Handle, - width: Length, - height: Length, - content_fit: ContentFit, - style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<Renderer> Svg<Renderer> -where - Renderer: svg::Renderer, - Renderer::Theme: StyleSheet, -{ - /// Creates a new [`Svg`] from the given [`Handle`]. - pub fn new(handle: impl Into<Handle>) -> Self { - Svg { - handle: handle.into(), - width: Length::Fill, - height: Length::Shrink, - content_fit: ContentFit::Contain, - style: Default::default(), - } - } - - /// Creates a new [`Svg`] that will display the contents of the file at the - /// provided path. - #[must_use] - pub fn from_path(path: impl Into<PathBuf>) -> Self { - Self::new(Handle::from_path(path)) - } - - /// Sets the width of the [`Svg`]. - #[must_use] - pub fn width(mut self, width: impl Into<Length>) -> Self { - self.width = width.into(); - self - } - - /// Sets the height of the [`Svg`]. - #[must_use] - pub fn height(mut self, height: impl Into<Length>) -> Self { - self.height = height.into(); - self - } - - /// Sets the [`ContentFit`] of the [`Svg`]. - /// - /// Defaults to [`ContentFit::Contain`] - #[must_use] - pub fn content_fit(self, content_fit: ContentFit) -> Self { - Self { - content_fit, - ..self - } - } - - /// Sets the style variant of this [`Svg`]. - #[must_use] - pub fn style( - mut self, - style: <Renderer::Theme as StyleSheet>::Style, - ) -> Self { - self.style = style; - self - } -} - -impl<Message, Renderer> Widget<Message, Renderer> for Svg<Renderer> -where - Renderer: svg::Renderer, - Renderer::Theme: iced_style::svg::StyleSheet, -{ - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - // The raw w/h of the underlying image - let Size { width, height } = renderer.dimensions(&self.handle); - let image_size = Size::new(width as f32, height as f32); - - // The size to be available to the widget prior to `Shrink`ing - let raw_size = limits - .width(self.width) - .height(self.height) - .resolve(image_size); - - // The uncropped size of the image when fit to the bounds above - let full_size = self.content_fit.fit(image_size, raw_size); - - // Shrink the widget to fit the resized image, if requested - let final_size = Size { - width: match self.width { - Length::Shrink => f32::min(raw_size.width, full_size.width), - _ => raw_size.width, - }, - height: match self.height { - Length::Shrink => f32::min(raw_size.height, full_size.height), - _ => raw_size.height, - }, - }; - - layout::Node::new(final_size) - } - - fn draw( - &self, - _state: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - _style: &renderer::Style, - layout: Layout<'_>, - _cursor_position: Point, - _viewport: &Rectangle, - ) { - let Size { width, height } = renderer.dimensions(&self.handle); - let image_size = Size::new(width as f32, height as f32); - - let bounds = layout.bounds(); - let adjusted_fit = self.content_fit.fit(image_size, bounds.size()); - - let render = |renderer: &mut Renderer| { - let offset = Vector::new( - (bounds.width - adjusted_fit.width).max(0.0) / 2.0, - (bounds.height - adjusted_fit.height).max(0.0) / 2.0, - ); - - let drawing_bounds = Rectangle { - width: adjusted_fit.width, - height: adjusted_fit.height, - ..bounds - }; - - let appearance = theme.appearance(&self.style); - - renderer.draw( - self.handle.clone(), - appearance.color, - drawing_bounds + offset, - ); - }; - - if adjusted_fit.width > bounds.width - || adjusted_fit.height > bounds.height - { - renderer.with_layer(bounds, render); - } else { - render(renderer); - } - } -} - -impl<'a, Message, Renderer> From<Svg<Renderer>> - for Element<'a, Message, Renderer> -where - Renderer: svg::Renderer + 'a, - Renderer::Theme: iced_style::svg::StyleSheet, -{ - fn from(icon: Svg<Renderer>) -> Element<'a, Message, Renderer> { - Element::new(icon) - } -} diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs deleted file mode 100644 index aede754a..00000000 --- a/native/src/widget/text.rs +++ /dev/null @@ -1,263 +0,0 @@ -//! Write some text for your users to read. -use crate::alignment; -use crate::layout; -use crate::renderer; -use crate::text; -use crate::widget::Tree; -use crate::{Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget}; - -use std::borrow::Cow; - -pub use iced_style::text::{Appearance, StyleSheet}; - -/// A paragraph of text. -/// -/// # Example -/// -/// ``` -/// # use iced_native::Color; -/// # -/// # type Text<'a> = iced_native::widget::Text<'a, iced_native::renderer::Null>; -/// # -/// Text::new("I <3 iced!") -/// .size(40) -/// .style(Color::from([0.0, 0.0, 1.0])); -/// ``` -/// -///  -#[allow(missing_debug_implementations)] -pub struct Text<'a, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - content: Cow<'a, str>, - size: Option<f32>, - width: Length, - height: Length, - horizontal_alignment: alignment::Horizontal, - vertical_alignment: alignment::Vertical, - font: Option<Renderer::Font>, - style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, Renderer> Text<'a, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - /// Create a new fragment of [`Text`] with the given contents. - pub fn new(content: impl Into<Cow<'a, str>>) -> Self { - Text { - content: content.into(), - size: None, - font: None, - width: Length::Shrink, - height: Length::Shrink, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, - style: Default::default(), - } - } - - /// Sets the size of the [`Text`]. - pub fn size(mut self, size: impl Into<Pixels>) -> Self { - self.size = Some(size.into().0); - self - } - - /// Sets the [`Font`] of the [`Text`]. - /// - /// [`Font`]: crate::text::Renderer::Font - pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self { - self.font = Some(font.into()); - self - } - - /// Sets the style of the [`Text`]. - pub fn style( - mut self, - style: impl Into<<Renderer::Theme as StyleSheet>::Style>, - ) -> Self { - self.style = style.into(); - self - } - - /// Sets the width of the [`Text`] boundaries. - pub fn width(mut self, width: impl Into<Length>) -> Self { - self.width = width.into(); - self - } - - /// Sets the height of the [`Text`] boundaries. - pub fn height(mut self, height: impl Into<Length>) -> Self { - self.height = height.into(); - self - } - - /// Sets the [`alignment::Horizontal`] of the [`Text`]. - pub fn horizontal_alignment( - mut self, - alignment: alignment::Horizontal, - ) -> Self { - self.horizontal_alignment = alignment; - self - } - - /// Sets the [`alignment::Vertical`] of the [`Text`]. - pub fn vertical_alignment( - mut self, - alignment: alignment::Vertical, - ) -> Self { - self.vertical_alignment = alignment; - self - } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> for Text<'a, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - 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); - - let size = self.size.unwrap_or_else(|| renderer.default_size()); - - let bounds = limits.max(); - - let (width, height) = renderer.measure( - &self.content, - size, - self.font.unwrap_or_else(|| renderer.default_font()), - bounds, - ); - - let size = limits.resolve(Size::new(width, height)); - - layout::Node::new(size) - } - - fn draw( - &self, - _state: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - _cursor_position: Point, - _viewport: &Rectangle, - ) { - draw( - renderer, - style, - layout, - &self.content, - self.size, - self.font, - theme.appearance(self.style), - self.horizontal_alignment, - self.vertical_alignment, - ); - } -} - -/// Draws text using the same logic as the [`Text`] widget. -/// -/// Specifically: -/// -/// * If no `size` is provided, the default text size of the `Renderer` will be -/// used. -/// * If no `color` is provided, the [`renderer::Style::text_color`] will be -/// used. -/// * The alignment attributes do not affect the position of the bounds of the -/// [`Layout`]. -pub fn draw<Renderer>( - renderer: &mut Renderer, - style: &renderer::Style, - layout: Layout<'_>, - content: &str, - size: Option<f32>, - font: Option<Renderer::Font>, - appearance: Appearance, - horizontal_alignment: alignment::Horizontal, - vertical_alignment: alignment::Vertical, -) where - Renderer: text::Renderer, -{ - let bounds = layout.bounds(); - - let x = match horizontal_alignment { - alignment::Horizontal::Left => bounds.x, - alignment::Horizontal::Center => bounds.center_x(), - alignment::Horizontal::Right => bounds.x + bounds.width, - }; - - let y = match vertical_alignment { - alignment::Vertical::Top => bounds.y, - alignment::Vertical::Center => bounds.center_y(), - alignment::Vertical::Bottom => bounds.y + bounds.height, - }; - - renderer.fill_text(crate::text::Text { - content, - size: size.unwrap_or_else(|| renderer.default_size()), - bounds: Rectangle { x, y, ..bounds }, - color: appearance.color.unwrap_or(style.text_color), - font: font.unwrap_or_else(|| renderer.default_font()), - horizontal_alignment, - vertical_alignment, - }); -} - -impl<'a, Message, Renderer> From<Text<'a, Renderer>> - for Element<'a, Message, Renderer> -where - Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet, -{ - fn from(text: Text<'a, Renderer>) -> Element<'a, Message, Renderer> { - Element::new(text) - } -} - -impl<'a, Renderer> Clone for Text<'a, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - fn clone(&self) -> Self { - Self { - content: self.content.clone(), - size: self.size, - width: self.width, - height: self.height, - horizontal_alignment: self.horizontal_alignment, - vertical_alignment: self.vertical_alignment, - font: self.font, - style: self.style, - } - } -} - -impl<'a, Message, Renderer> From<&'a str> for Element<'a, Message, Renderer> -where - Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet, -{ - fn from(contents: &'a str) -> Self { - Text::new(contents).into() - } -} diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs deleted file mode 100644 index 65a9bd3b..00000000 --- a/native/src/widget/text_input.rs +++ /dev/null @@ -1,1218 +0,0 @@ -//! Display fields that can be filled with text. -//! -//! A [`TextInput`] has some local [`State`]. -mod editor; -mod value; - -pub mod cursor; - -pub use cursor::Cursor; -pub use value::Value; - -use editor::Editor; - -use crate::alignment; -use crate::event::{self, Event}; -use crate::keyboard; -use crate::layout; -use crate::mouse::{self, click}; -use crate::renderer; -use crate::text::{self, Text}; -use crate::time::{Duration, Instant}; -use crate::touch; -use crate::widget; -use crate::widget::operation::{self, Operation}; -use crate::widget::tree::{self, Tree}; -use crate::window; -use crate::{ - Clipboard, Color, Command, Element, Layout, Length, Padding, Pixels, Point, - Rectangle, Shell, Size, Vector, Widget, -}; - -pub use iced_style::text_input::{Appearance, StyleSheet}; - -/// A field that can be filled with text. -/// -/// # Example -/// ``` -/// # pub type TextInput<'a, Message> = iced_native::widget::TextInput<'a, Message, iced_native::renderer::Null>; -/// #[derive(Debug, Clone)] -/// enum Message { -/// TextInputChanged(String), -/// } -/// -/// let value = "Some text"; -/// -/// let input = TextInput::new( -/// "This is the placeholder...", -/// value, -/// Message::TextInputChanged, -/// ) -/// .padding(10); -/// ``` -///  -#[allow(missing_debug_implementations)] -pub struct TextInput<'a, Message, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - id: Option<Id>, - placeholder: String, - value: Value, - is_secure: bool, - font: Option<Renderer::Font>, - width: Length, - padding: Padding, - size: Option<f32>, - on_change: Box<dyn Fn(String) -> Message + 'a>, - on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>, - on_submit: Option<Message>, - style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, Message, Renderer> TextInput<'a, Message, Renderer> -where - Message: Clone, - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - /// Creates a new [`TextInput`]. - /// - /// It expects: - /// - a placeholder, - /// - the current value, and - /// - a function that produces a message when the [`TextInput`] changes. - pub fn new<F>(placeholder: &str, value: &str, on_change: F) -> Self - where - F: 'a + Fn(String) -> Message, - { - TextInput { - id: None, - placeholder: String::from(placeholder), - value: Value::new(value), - is_secure: false, - font: None, - width: Length::Fill, - padding: Padding::new(5.0), - size: None, - on_change: Box::new(on_change), - on_paste: None, - on_submit: None, - style: Default::default(), - } - } - - /// Sets the [`Id`] of the [`TextInput`]. - pub fn id(mut self, id: Id) -> Self { - self.id = Some(id); - self - } - - /// Converts the [`TextInput`] into a secure password input. - pub fn password(mut self) -> Self { - self.is_secure = true; - self - } - - /// Sets the message that should be produced when some text is pasted into - /// the [`TextInput`]. - pub fn on_paste( - mut self, - on_paste: impl Fn(String) -> Message + 'a, - ) -> Self { - self.on_paste = Some(Box::new(on_paste)); - self - } - - /// Sets the [`Font`] of the [`TextInput`]. - /// - /// [`Font`]: text::Renderer::Font - pub fn font(mut self, font: Renderer::Font) -> Self { - self.font = Some(font); - self - } - /// Sets the width of the [`TextInput`]. - pub fn width(mut self, width: impl Into<Length>) -> Self { - self.width = width.into(); - self - } - - /// Sets the [`Padding`] of the [`TextInput`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the text size of the [`TextInput`]. - pub fn size(mut self, size: impl Into<Pixels>) -> Self { - self.size = Some(size.into().0); - self - } - - /// Sets the message that should be produced when the [`TextInput`] is - /// focused and the enter key is pressed. - pub fn on_submit(mut self, message: Message) -> Self { - self.on_submit = Some(message); - self - } - - /// Sets the style of the [`TextInput`]. - pub fn style( - mut self, - style: impl Into<<Renderer::Theme as StyleSheet>::Style>, - ) -> Self { - self.style = style.into(); - self - } - - /// Draws the [`TextInput`] with the given [`Renderer`], overriding its - /// [`Value`] if provided. - /// - /// [`Renderer`]: text::Renderer - pub fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - layout: Layout<'_>, - cursor_position: Point, - value: Option<&Value>, - ) { - draw( - renderer, - theme, - layout, - cursor_position, - tree.state.downcast_ref::<State>(), - value.unwrap_or(&self.value), - &self.placeholder, - self.size, - self.font, - self.is_secure, - &self.style, - ) - } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> - for TextInput<'a, Message, Renderer> -where - Message: Clone, - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - fn tag(&self) -> tree::Tag { - tree::Tag::of::<State>() - } - - fn state(&self) -> tree::State { - tree::State::new(State::new()) - } - - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - Length::Shrink - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - layout(renderer, limits, self.width, self.padding, self.size) - } - - fn operate( - &self, - tree: &mut Tree, - _layout: Layout<'_>, - _renderer: &Renderer, - operation: &mut dyn Operation<Message>, - ) { - let state = tree.state.downcast_mut::<State>(); - - operation.focusable(state, self.id.as_ref().map(|id| &id.0)); - operation.text_input(state, self.id.as_ref().map(|id| &id.0)); - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - update( - event, - layout, - cursor_position, - renderer, - clipboard, - shell, - &mut self.value, - self.size, - self.font, - self.is_secure, - self.on_change.as_ref(), - self.on_paste.as_deref(), - &self.on_submit, - || tree.state.downcast_mut::<State>(), - ) - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - _style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - ) { - draw( - renderer, - theme, - layout, - cursor_position, - tree.state.downcast_ref::<State>(), - &self.value, - &self.placeholder, - self.size, - self.font, - self.is_secure, - &self.style, - ) - } - - fn mouse_interaction( - &self, - _state: &Tree, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - mouse_interaction(layout, cursor_position) - } -} - -impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>> - for Element<'a, Message, Renderer> -where - Message: 'a + Clone, - Renderer: 'a + text::Renderer, - Renderer::Theme: StyleSheet, -{ - fn from( - text_input: TextInput<'a, Message, Renderer>, - ) -> Element<'a, Message, Renderer> { - Element::new(text_input) - } -} - -/// The identifier of a [`TextInput`]. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Id(widget::Id); - -impl Id { - /// Creates a custom [`Id`]. - pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self { - Self(widget::Id::new(id)) - } - - /// Creates a unique [`Id`]. - /// - /// This function produces a different [`Id`] every time it is called. - pub fn unique() -> Self { - Self(widget::Id::unique()) - } -} - -impl From<Id> for widget::Id { - fn from(id: Id) -> Self { - id.0 - } -} - -/// Produces a [`Command`] that focuses the [`TextInput`] with the given [`Id`]. -pub fn focus<Message: 'static>(id: Id) -> Command<Message> { - Command::widget(operation::focusable::focus(id.0)) -} - -/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the -/// end. -pub fn move_cursor_to_end<Message: 'static>(id: Id) -> Command<Message> { - Command::widget(operation::text_input::move_cursor_to_end(id.0)) -} - -/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the -/// front. -pub fn move_cursor_to_front<Message: 'static>(id: Id) -> Command<Message> { - Command::widget(operation::text_input::move_cursor_to_front(id.0)) -} - -/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the -/// provided position. -pub fn move_cursor_to<Message: 'static>( - id: Id, - position: usize, -) -> Command<Message> { - Command::widget(operation::text_input::move_cursor_to(id.0, position)) -} - -/// Produces a [`Command`] that selects all the content of the [`TextInput`] with the given [`Id`]. -pub fn select_all<Message: 'static>(id: Id) -> Command<Message> { - Command::widget(operation::text_input::select_all(id.0)) -} - -/// Computes the layout of a [`TextInput`]. -pub fn layout<Renderer>( - renderer: &Renderer, - limits: &layout::Limits, - width: Length, - padding: Padding, - size: Option<f32>, -) -> layout::Node -where - Renderer: text::Renderer, -{ - let text_size = size.unwrap_or_else(|| renderer.default_size()); - let padding = padding.fit(Size::ZERO, limits.max()); - let limits = limits.width(width).pad(padding).height(text_size * 1.2); - - let mut text = layout::Node::new(limits.resolve(Size::ZERO)); - text.move_to(Point::new(padding.left, padding.top)); - - layout::Node::with_children(text.size().pad(padding), vec![text]) -} - -/// Processes an [`Event`] and updates the [`State`] of a [`TextInput`] -/// accordingly. -pub fn update<'a, Message, Renderer>( - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - value: &mut Value, - size: Option<f32>, - font: Option<Renderer::Font>, - is_secure: bool, - on_change: &dyn Fn(String) -> Message, - on_paste: Option<&dyn Fn(String) -> Message>, - on_submit: &Option<Message>, - state: impl FnOnce() -> &'a mut State, -) -> event::Status -where - Message: Clone, - Renderer: text::Renderer, -{ - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - let state = state(); - let is_clicked = layout.bounds().contains(cursor_position); - - state.is_focused = if is_clicked { - state.is_focused.or_else(|| { - let now = Instant::now(); - - Some(Focus { - updated_at: now, - now, - }) - }) - } else { - None - }; - - if is_clicked { - let text_layout = layout.children().next().unwrap(); - let target = cursor_position.x - text_layout.bounds().x; - - let click = - mouse::Click::new(cursor_position, state.last_click); - - match click.kind() { - click::Kind::Single => { - let position = if target > 0.0 { - let value = if is_secure { - value.secure() - } else { - value.clone() - }; - - find_cursor_position( - renderer, - text_layout.bounds(), - font, - size, - &value, - state, - target, - ) - } else { - None - } - .unwrap_or(0); - - if state.keyboard_modifiers.shift() { - state.cursor.select_range( - state.cursor.start(value), - position, - ); - } else { - state.cursor.move_to(position); - } - state.is_dragging = true; - } - click::Kind::Double => { - if is_secure { - state.cursor.select_all(value); - } else { - let position = find_cursor_position( - renderer, - text_layout.bounds(), - font, - size, - value, - state, - target, - ) - .unwrap_or(0); - - state.cursor.select_range( - value.previous_start_of_word(position), - value.next_end_of_word(position), - ); - } - - state.is_dragging = false; - } - click::Kind::Triple => { - state.cursor.select_all(value); - state.is_dragging = false; - } - } - - state.last_click = Some(click); - - return event::Status::Captured; - } - } - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) - | Event::Touch(touch::Event::FingerLost { .. }) => { - state().is_dragging = false; - } - Event::Mouse(mouse::Event::CursorMoved { position }) - | Event::Touch(touch::Event::FingerMoved { position, .. }) => { - let state = state(); - - if state.is_dragging { - let text_layout = layout.children().next().unwrap(); - let target = position.x - text_layout.bounds().x; - - let value = if is_secure { - value.secure() - } else { - value.clone() - }; - - let position = find_cursor_position( - renderer, - text_layout.bounds(), - font, - size, - &value, - state, - target, - ) - .unwrap_or(0); - - state - .cursor - .select_range(state.cursor.start(&value), position); - - return event::Status::Captured; - } - } - Event::Keyboard(keyboard::Event::CharacterReceived(c)) => { - let state = state(); - - if let Some(focus) = &mut state.is_focused { - if state.is_pasting.is_none() - && !state.keyboard_modifiers.command() - && !c.is_control() - { - let mut editor = Editor::new(value, &mut state.cursor); - - editor.insert(c); - - let message = (on_change)(editor.contents()); - shell.publish(message); - - focus.updated_at = Instant::now(); - - return event::Status::Captured; - } - } - } - Event::Keyboard(keyboard::Event::KeyPressed { key_code, .. }) => { - let state = state(); - - if let Some(focus) = &mut state.is_focused { - let modifiers = state.keyboard_modifiers; - focus.updated_at = Instant::now(); - - match key_code { - keyboard::KeyCode::Enter - | keyboard::KeyCode::NumpadEnter => { - if let Some(on_submit) = on_submit.clone() { - shell.publish(on_submit); - } - } - keyboard::KeyCode::Backspace => { - if platform::is_jump_modifier_pressed(modifiers) - && state.cursor.selection(value).is_none() - { - if is_secure { - let cursor_pos = state.cursor.end(value); - state.cursor.select_range(0, cursor_pos); - } else { - state.cursor.select_left_by_words(value); - } - } - - let mut editor = Editor::new(value, &mut state.cursor); - editor.backspace(); - - let message = (on_change)(editor.contents()); - shell.publish(message); - } - keyboard::KeyCode::Delete => { - if platform::is_jump_modifier_pressed(modifiers) - && state.cursor.selection(value).is_none() - { - if is_secure { - let cursor_pos = state.cursor.end(value); - state - .cursor - .select_range(cursor_pos, value.len()); - } else { - state.cursor.select_right_by_words(value); - } - } - - let mut editor = Editor::new(value, &mut state.cursor); - editor.delete(); - - let message = (on_change)(editor.contents()); - shell.publish(message); - } - keyboard::KeyCode::Left => { - if platform::is_jump_modifier_pressed(modifiers) - && !is_secure - { - if modifiers.shift() { - state.cursor.select_left_by_words(value); - } else { - state.cursor.move_left_by_words(value); - } - } else if modifiers.shift() { - state.cursor.select_left(value) - } else { - state.cursor.move_left(value); - } - } - keyboard::KeyCode::Right => { - if platform::is_jump_modifier_pressed(modifiers) - && !is_secure - { - if modifiers.shift() { - state.cursor.select_right_by_words(value); - } else { - state.cursor.move_right_by_words(value); - } - } else if modifiers.shift() { - state.cursor.select_right(value) - } else { - state.cursor.move_right(value); - } - } - keyboard::KeyCode::Home => { - if modifiers.shift() { - state - .cursor - .select_range(state.cursor.start(value), 0); - } else { - state.cursor.move_to(0); - } - } - keyboard::KeyCode::End => { - if modifiers.shift() { - state.cursor.select_range( - state.cursor.start(value), - value.len(), - ); - } else { - state.cursor.move_to(value.len()); - } - } - keyboard::KeyCode::C - if state.keyboard_modifiers.command() => - { - if let Some((start, end)) = - state.cursor.selection(value) - { - clipboard - .write(value.select(start, end).to_string()); - } - } - keyboard::KeyCode::X - if state.keyboard_modifiers.command() => - { - if let Some((start, end)) = - state.cursor.selection(value) - { - clipboard - .write(value.select(start, end).to_string()); - } - - let mut editor = Editor::new(value, &mut state.cursor); - editor.delete(); - - let message = (on_change)(editor.contents()); - shell.publish(message); - } - keyboard::KeyCode::V => { - if state.keyboard_modifiers.command() { - let content = match state.is_pasting.take() { - Some(content) => content, - None => { - let content: String = clipboard - .read() - .unwrap_or_default() - .chars() - .filter(|c| !c.is_control()) - .collect(); - - Value::new(&content) - } - }; - - let mut editor = - Editor::new(value, &mut state.cursor); - - editor.paste(content.clone()); - - let message = if let Some(paste) = &on_paste { - (paste)(editor.contents()) - } else { - (on_change)(editor.contents()) - }; - shell.publish(message); - - state.is_pasting = Some(content); - } else { - state.is_pasting = None; - } - } - keyboard::KeyCode::A - if state.keyboard_modifiers.command() => - { - state.cursor.select_all(value); - } - keyboard::KeyCode::Escape => { - state.is_focused = None; - state.is_dragging = false; - state.is_pasting = None; - - state.keyboard_modifiers = - keyboard::Modifiers::default(); - } - keyboard::KeyCode::Tab - | keyboard::KeyCode::Up - | keyboard::KeyCode::Down => { - return event::Status::Ignored; - } - _ => {} - } - - return event::Status::Captured; - } - } - Event::Keyboard(keyboard::Event::KeyReleased { key_code, .. }) => { - let state = state(); - - if state.is_focused.is_some() { - match key_code { - keyboard::KeyCode::V => { - state.is_pasting = None; - } - keyboard::KeyCode::Tab - | keyboard::KeyCode::Up - | keyboard::KeyCode::Down => { - return event::Status::Ignored; - } - _ => {} - } - - return event::Status::Captured; - } else { - state.is_pasting = None; - } - } - Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { - let state = state(); - - state.keyboard_modifiers = modifiers; - } - Event::Window(window::Event::RedrawRequested(now)) => { - let state = state(); - - if let Some(focus) = &mut state.is_focused { - focus.now = now; - - let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS - - (now - focus.updated_at).as_millis() - % CURSOR_BLINK_INTERVAL_MILLIS; - - shell.request_redraw(window::RedrawRequest::At( - now + Duration::from_millis(millis_until_redraw as u64), - )); - } - } - _ => {} - } - - event::Status::Ignored -} - -/// Draws the [`TextInput`] with the given [`Renderer`], overriding its -/// [`Value`] if provided. -/// -/// [`Renderer`]: text::Renderer -pub fn draw<Renderer>( - renderer: &mut Renderer, - theme: &Renderer::Theme, - layout: Layout<'_>, - cursor_position: Point, - state: &State, - value: &Value, - placeholder: &str, - size: Option<f32>, - font: Option<Renderer::Font>, - is_secure: bool, - style: &<Renderer::Theme as StyleSheet>::Style, -) where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - let secure_value = is_secure.then(|| value.secure()); - let value = secure_value.as_ref().unwrap_or(value); - - let bounds = layout.bounds(); - let text_bounds = layout.children().next().unwrap().bounds(); - - let is_mouse_over = bounds.contains(cursor_position); - - let appearance = if state.is_focused() { - theme.focused(style) - } else if is_mouse_over { - theme.hovered(style) - } else { - theme.active(style) - }; - - renderer.fill_quad( - renderer::Quad { - bounds, - border_radius: appearance.border_radius.into(), - border_width: appearance.border_width, - border_color: appearance.border_color, - }, - appearance.background, - ); - - let text = value.to_string(); - let font = font.unwrap_or_else(|| renderer.default_font()); - let size = size.unwrap_or_else(|| renderer.default_size()); - - let (cursor, offset) = if let Some(focus) = &state.is_focused { - match state.cursor.state(value) { - cursor::State::Index(position) => { - let (text_value_width, offset) = - measure_cursor_and_scroll_offset( - renderer, - text_bounds, - value, - size, - position, - font, - ); - - let is_cursor_visible = ((focus.now - focus.updated_at) - .as_millis() - / CURSOR_BLINK_INTERVAL_MILLIS) - % 2 - == 0; - - let cursor = if is_cursor_visible { - Some(( - renderer::Quad { - bounds: Rectangle { - x: text_bounds.x + text_value_width, - y: text_bounds.y, - width: 1.0, - height: text_bounds.height, - }, - border_radius: 0.0.into(), - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - theme.value_color(style), - )) - } else { - None - }; - - (cursor, offset) - } - cursor::State::Selection { start, end } => { - let left = start.min(end); - let right = end.max(start); - - let (left_position, left_offset) = - measure_cursor_and_scroll_offset( - renderer, - text_bounds, - value, - size, - left, - font, - ); - - let (right_position, right_offset) = - measure_cursor_and_scroll_offset( - renderer, - text_bounds, - value, - size, - right, - font, - ); - - let width = right_position - left_position; - - ( - Some(( - renderer::Quad { - bounds: Rectangle { - x: text_bounds.x + left_position, - y: text_bounds.y, - width, - height: text_bounds.height, - }, - border_radius: 0.0.into(), - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - theme.selection_color(style), - )), - if end == right { - right_offset - } else { - left_offset - }, - ) - } - } - } else { - (None, 0.0) - }; - - let text_width = renderer.measure_width( - if text.is_empty() { placeholder } else { &text }, - size, - font, - ); - - let render = |renderer: &mut Renderer| { - if let Some((cursor, color)) = cursor { - renderer.fill_quad(cursor, color); - } - - renderer.fill_text(Text { - content: if text.is_empty() { placeholder } else { &text }, - color: if text.is_empty() { - theme.placeholder_color(style) - } else { - theme.value_color(style) - }, - font, - bounds: Rectangle { - y: text_bounds.center_y(), - width: f32::INFINITY, - ..text_bounds - }, - size, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, - }); - }; - - if text_width > text_bounds.width { - renderer.with_layer(text_bounds, |renderer| { - renderer.with_translation(Vector::new(-offset, 0.0), render) - }); - } else { - render(renderer); - } -} - -/// Computes the current [`mouse::Interaction`] of the [`TextInput`]. -pub fn mouse_interaction( - layout: Layout<'_>, - cursor_position: Point, -) -> mouse::Interaction { - if layout.bounds().contains(cursor_position) { - mouse::Interaction::Text - } else { - mouse::Interaction::default() - } -} - -/// The state of a [`TextInput`]. -#[derive(Debug, Default, Clone)] -pub struct State { - is_focused: Option<Focus>, - is_dragging: bool, - is_pasting: Option<Value>, - last_click: Option<mouse::Click>, - cursor: Cursor, - keyboard_modifiers: keyboard::Modifiers, - // TODO: Add stateful horizontal scrolling offset -} - -#[derive(Debug, Clone, Copy)] -struct Focus { - updated_at: Instant, - now: Instant, -} - -impl State { - /// Creates a new [`State`], representing an unfocused [`TextInput`]. - pub fn new() -> Self { - Self::default() - } - - /// Creates a new [`State`], representing a focused [`TextInput`]. - pub fn focused() -> Self { - Self { - is_focused: None, - is_dragging: false, - is_pasting: None, - last_click: None, - cursor: Cursor::default(), - keyboard_modifiers: keyboard::Modifiers::default(), - } - } - - /// Returns whether the [`TextInput`] is currently focused or not. - pub fn is_focused(&self) -> bool { - self.is_focused.is_some() - } - - /// Returns the [`Cursor`] of the [`TextInput`]. - pub fn cursor(&self) -> Cursor { - self.cursor - } - - /// Focuses the [`TextInput`]. - pub fn focus(&mut self) { - let now = Instant::now(); - - self.is_focused = Some(Focus { - updated_at: now, - now, - }); - - self.move_cursor_to_end(); - } - - /// Unfocuses the [`TextInput`]. - pub fn unfocus(&mut self) { - self.is_focused = None; - } - - /// Moves the [`Cursor`] of the [`TextInput`] to the front of the input text. - pub fn move_cursor_to_front(&mut self) { - self.cursor.move_to(0); - } - - /// Moves the [`Cursor`] of the [`TextInput`] to the end of the input text. - pub fn move_cursor_to_end(&mut self) { - self.cursor.move_to(usize::MAX); - } - - /// Moves the [`Cursor`] of the [`TextInput`] to an arbitrary location. - pub fn move_cursor_to(&mut self, position: usize) { - self.cursor.move_to(position); - } - - /// Selects all the content of the [`TextInput`]. - pub fn select_all(&mut self) { - self.cursor.select_range(0, usize::MAX); - } -} - -impl operation::Focusable for State { - fn is_focused(&self) -> bool { - State::is_focused(self) - } - - fn focus(&mut self) { - State::focus(self) - } - - fn unfocus(&mut self) { - State::unfocus(self) - } -} - -impl operation::TextInput for State { - fn move_cursor_to_front(&mut self) { - State::move_cursor_to_front(self) - } - - fn move_cursor_to_end(&mut self) { - State::move_cursor_to_end(self) - } - - fn move_cursor_to(&mut self, position: usize) { - State::move_cursor_to(self, position) - } - - fn select_all(&mut self) { - State::select_all(self) - } -} - -mod platform { - use crate::keyboard; - - pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool { - if cfg!(target_os = "macos") { - modifiers.alt() - } else { - modifiers.control() - } - } -} - -fn offset<Renderer>( - renderer: &Renderer, - text_bounds: Rectangle, - font: Renderer::Font, - size: f32, - value: &Value, - state: &State, -) -> f32 -where - Renderer: text::Renderer, -{ - if state.is_focused() { - let cursor = state.cursor(); - - let focus_position = match cursor.state(value) { - cursor::State::Index(i) => i, - cursor::State::Selection { end, .. } => end, - }; - - let (_, offset) = measure_cursor_and_scroll_offset( - renderer, - text_bounds, - value, - size, - focus_position, - font, - ); - - offset - } else { - 0.0 - } -} - -fn measure_cursor_and_scroll_offset<Renderer>( - renderer: &Renderer, - text_bounds: Rectangle, - value: &Value, - size: f32, - cursor_index: usize, - font: Renderer::Font, -) -> (f32, f32) -where - Renderer: text::Renderer, -{ - let text_before_cursor = value.until(cursor_index).to_string(); - - let text_value_width = - renderer.measure_width(&text_before_cursor, size, font); - - let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0); - - (text_value_width, offset) -} - -/// Computes the position of the text cursor at the given X coordinate of -/// a [`TextInput`]. -fn find_cursor_position<Renderer>( - renderer: &Renderer, - text_bounds: Rectangle, - font: Option<Renderer::Font>, - size: Option<f32>, - value: &Value, - state: &State, - x: f32, -) -> Option<usize> -where - Renderer: text::Renderer, -{ - let font = font.unwrap_or_else(|| renderer.default_font()); - let size = size.unwrap_or_else(|| renderer.default_size()); - - let offset = offset(renderer, text_bounds, font, size, value, state); - let value = value.to_string(); - - let char_offset = renderer - .hit_test( - &value, - size, - font, - Size::INFINITY, - Point::new(x + offset, text_bounds.height / 2.0), - true, - ) - .map(text::Hit::cursor)?; - - Some( - unicode_segmentation::UnicodeSegmentation::graphemes( - &value[..char_offset], - true, - ) - .count(), - ) -} - -const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500; diff --git a/native/src/widget/text_input/cursor.rs b/native/src/widget/text_input/cursor.rs deleted file mode 100644 index 4f3b159b..00000000 --- a/native/src/widget/text_input/cursor.rs +++ /dev/null @@ -1,189 +0,0 @@ -//! Track the cursor of a text input. -use crate::widget::text_input::Value; - -/// The cursor of a text input. -#[derive(Debug, Copy, Clone)] -pub struct Cursor { - state: State, -} - -/// The state of a [`Cursor`]. -#[derive(Debug, Copy, Clone)] -pub enum State { - /// Cursor without a selection - Index(usize), - - /// Cursor selecting a range of text - Selection { - /// The start of the selection - start: usize, - /// The end of the selection - end: usize, - }, -} - -impl Default for Cursor { - fn default() -> Self { - Cursor { - state: State::Index(0), - } - } -} - -impl Cursor { - /// Returns the [`State`] of the [`Cursor`]. - pub fn state(&self, value: &Value) -> State { - match self.state { - State::Index(index) => State::Index(index.min(value.len())), - State::Selection { start, end } => { - let start = start.min(value.len()); - let end = end.min(value.len()); - - if start == end { - State::Index(start) - } else { - State::Selection { start, end } - } - } - } - } - - /// Returns the current selection of the [`Cursor`] for the given [`Value`]. - /// - /// `start` is guaranteed to be <= than `end`. - pub fn selection(&self, value: &Value) -> Option<(usize, usize)> { - match self.state(value) { - State::Selection { start, end } => { - Some((start.min(end), start.max(end))) - } - _ => None, - } - } - - pub(crate) fn move_to(&mut self, position: usize) { - self.state = State::Index(position); - } - - pub(crate) fn move_right(&mut self, value: &Value) { - self.move_right_by_amount(value, 1) - } - - pub(crate) fn move_right_by_words(&mut self, value: &Value) { - self.move_to(value.next_end_of_word(self.right(value))) - } - - pub(crate) fn move_right_by_amount( - &mut self, - value: &Value, - amount: usize, - ) { - match self.state(value) { - State::Index(index) => { - self.move_to(index.saturating_add(amount).min(value.len())) - } - State::Selection { start, end } => self.move_to(end.max(start)), - } - } - - pub(crate) fn move_left(&mut self, value: &Value) { - match self.state(value) { - State::Index(index) if index > 0 => self.move_to(index - 1), - State::Selection { start, end } => self.move_to(start.min(end)), - _ => self.move_to(0), - } - } - - pub(crate) fn move_left_by_words(&mut self, value: &Value) { - self.move_to(value.previous_start_of_word(self.left(value))); - } - - pub(crate) fn select_range(&mut self, start: usize, end: usize) { - if start == end { - self.state = State::Index(start); - } else { - self.state = State::Selection { start, end }; - } - } - - pub(crate) fn select_left(&mut self, value: &Value) { - match self.state(value) { - State::Index(index) if index > 0 => { - self.select_range(index, index - 1) - } - State::Selection { start, end } if end > 0 => { - self.select_range(start, end - 1) - } - _ => {} - } - } - - pub(crate) fn select_right(&mut self, value: &Value) { - match self.state(value) { - State::Index(index) if index < value.len() => { - self.select_range(index, index + 1) - } - State::Selection { start, end } if end < value.len() => { - self.select_range(start, end + 1) - } - _ => {} - } - } - - pub(crate) fn select_left_by_words(&mut self, value: &Value) { - match self.state(value) { - State::Index(index) => { - self.select_range(index, value.previous_start_of_word(index)) - } - State::Selection { start, end } => { - self.select_range(start, value.previous_start_of_word(end)) - } - } - } - - pub(crate) fn select_right_by_words(&mut self, value: &Value) { - match self.state(value) { - State::Index(index) => { - self.select_range(index, value.next_end_of_word(index)) - } - State::Selection { start, end } => { - self.select_range(start, value.next_end_of_word(end)) - } - } - } - - pub(crate) fn select_all(&mut self, value: &Value) { - self.select_range(0, value.len()); - } - - pub(crate) fn start(&self, value: &Value) -> usize { - let start = match self.state { - State::Index(index) => index, - State::Selection { start, .. } => start, - }; - - start.min(value.len()) - } - - pub(crate) fn end(&self, value: &Value) -> usize { - let end = match self.state { - State::Index(index) => index, - State::Selection { end, .. } => end, - }; - - end.min(value.len()) - } - - fn left(&self, value: &Value) -> usize { - match self.state(value) { - State::Index(index) => index, - State::Selection { start, end } => start.min(end), - } - } - - fn right(&self, value: &Value) -> usize { - match self.state(value) { - State::Index(index) => index, - State::Selection { start, end } => start.max(end), - } - } -} diff --git a/native/src/widget/text_input/editor.rs b/native/src/widget/text_input/editor.rs deleted file mode 100644 index d53fa8d9..00000000 --- a/native/src/widget/text_input/editor.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::widget::text_input::{Cursor, Value}; - -pub struct Editor<'a> { - value: &'a mut Value, - cursor: &'a mut Cursor, -} - -impl<'a> Editor<'a> { - pub fn new(value: &'a mut Value, cursor: &'a mut Cursor) -> Editor<'a> { - Editor { value, cursor } - } - - pub fn contents(&self) -> String { - self.value.to_string() - } - - pub fn insert(&mut self, character: char) { - if let Some((left, right)) = self.cursor.selection(self.value) { - self.cursor.move_left(self.value); - self.value.remove_many(left, right); - } - - self.value.insert(self.cursor.end(self.value), character); - self.cursor.move_right(self.value); - } - - pub fn paste(&mut self, content: Value) { - let length = content.len(); - if let Some((left, right)) = self.cursor.selection(self.value) { - self.cursor.move_left(self.value); - self.value.remove_many(left, right); - } - - self.value.insert_many(self.cursor.end(self.value), content); - - self.cursor.move_right_by_amount(self.value, length); - } - - pub fn backspace(&mut self) { - match self.cursor.selection(self.value) { - Some((start, end)) => { - self.cursor.move_left(self.value); - self.value.remove_many(start, end); - } - None => { - let start = self.cursor.start(self.value); - - if start > 0 { - self.cursor.move_left(self.value); - self.value.remove(start - 1); - } - } - } - } - - pub fn delete(&mut self) { - match self.cursor.selection(self.value) { - Some(_) => { - self.backspace(); - } - None => { - let end = self.cursor.end(self.value); - - if end < self.value.len() { - self.value.remove(end); - } - } - } - } -} diff --git a/native/src/widget/text_input/value.rs b/native/src/widget/text_input/value.rs deleted file mode 100644 index cf4da562..00000000 --- a/native/src/widget/text_input/value.rs +++ /dev/null @@ -1,133 +0,0 @@ -use unicode_segmentation::UnicodeSegmentation; - -/// The value of a [`TextInput`]. -/// -/// [`TextInput`]: crate::widget::TextInput -// TODO: Reduce allocations, cache results (?) -#[derive(Debug, Clone)] -pub struct Value { - graphemes: Vec<String>, -} - -impl Value { - /// Creates a new [`Value`] from a string slice. - pub fn new(string: &str) -> Self { - let graphemes = UnicodeSegmentation::graphemes(string, true) - .map(String::from) - .collect(); - - Self { graphemes } - } - - /// Returns whether the [`Value`] is empty or not. - /// - /// A [`Value`] is empty when it contains no graphemes. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Returns the total amount of graphemes in the [`Value`]. - pub fn len(&self) -> usize { - self.graphemes.len() - } - - /// Returns the position of the previous start of a word from the given - /// grapheme `index`. - 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 the position of the next end of a word from the given grapheme - /// `index`. - 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) - .find(|(_, word)| !word.trim_start().is_empty()) - .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 from `start` until the - /// given `end`. - pub fn select(&self, start: usize, end: usize) -> Self { - let graphemes = - self.graphemes[start.min(self.len())..end.min(self.len())].to_vec(); - - Self { graphemes } - } - - /// Returns a new [`Value`] containing the graphemes until the given - /// `index`. - pub fn until(&self, index: usize) -> Self { - let graphemes = self.graphemes[..index.min(self.len())].to_vec(); - - Self { graphemes } - } - - /// Converts the [`Value`] into a `String`. - pub fn to_string(&self) -> String { - self.graphemes.concat() - } - - /// Inserts a new `char` at the given grapheme `index`. - pub fn insert(&mut self, index: usize, c: char) { - self.graphemes.insert(index, c.to_string()); - - self.graphemes = - UnicodeSegmentation::graphemes(&self.to_string() as &str, true) - .map(String::from) - .collect(); - } - - /// 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`. - pub fn remove(&mut self, index: usize) { - let _ = self.graphemes.remove(index); - } - - /// Removes the graphemes from `start` to `end`. - pub fn remove_many(&mut self, start: usize, end: usize) { - let _ = self.graphemes.splice(start..end, std::iter::empty()); - } - - /// Returns a new [`Value`] with all its graphemes replaced with the - /// dot ('•') character. - pub fn secure(&self) -> Self { - Self { - graphemes: std::iter::repeat(String::from("•")) - .take(self.graphemes.len()) - .collect(), - } - } -} diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs deleted file mode 100644 index d9c80ebe..00000000 --- a/native/src/widget/toggler.rs +++ /dev/null @@ -1,324 +0,0 @@ -//! Show toggle controls using togglers. -use crate::alignment; -use crate::event; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::text; -use crate::widget::{self, Row, Text, Tree}; -use crate::{ - Alignment, Clipboard, Element, Event, Layout, Length, Pixels, Point, - Rectangle, Shell, Widget, -}; - -pub use iced_style::toggler::{Appearance, StyleSheet}; - -/// A toggler widget. -/// -/// # Example -/// -/// ``` -/// # type Toggler<'a, Message> = iced_native::widget::Toggler<'a, Message, iced_native::renderer::Null>; -/// # -/// pub enum Message { -/// TogglerToggled(bool), -/// } -/// -/// let is_toggled = true; -/// -/// Toggler::new(String::from("Toggle me!"), is_toggled, |b| Message::TogglerToggled(b)); -/// ``` -#[allow(missing_debug_implementations)] -pub struct Toggler<'a, Message, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - is_toggled: bool, - on_toggle: Box<dyn Fn(bool) -> Message + 'a>, - label: Option<String>, - width: Length, - size: f32, - text_size: Option<f32>, - text_alignment: alignment::Horizontal, - spacing: f32, - font: Option<Renderer::Font>, - style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, Message, Renderer> Toggler<'a, Message, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - /// The default size of a [`Toggler`]. - pub const DEFAULT_SIZE: f32 = 20.0; - - /// Creates a new [`Toggler`]. - /// - /// It expects: - /// * a boolean describing whether the [`Toggler`] is checked or not - /// * An optional label for the [`Toggler`] - /// * a function that will be called when the [`Toggler`] is toggled. It - /// will receive the new state of the [`Toggler`] and must produce a - /// `Message`. - pub fn new<F>( - label: impl Into<Option<String>>, - is_toggled: bool, - f: F, - ) -> Self - where - F: 'a + Fn(bool) -> Message, - { - Toggler { - is_toggled, - on_toggle: Box::new(f), - label: label.into(), - width: Length::Fill, - size: Self::DEFAULT_SIZE, - text_size: None, - text_alignment: alignment::Horizontal::Left, - spacing: 0.0, - font: None, - style: Default::default(), - } - } - - /// Sets the size of the [`Toggler`]. - pub fn size(mut self, size: impl Into<Pixels>) -> Self { - self.size = size.into().0; - self - } - - /// Sets the width of the [`Toggler`]. - pub fn width(mut self, width: impl Into<Length>) -> Self { - self.width = width.into(); - self - } - - /// Sets the text size o the [`Toggler`]. - pub fn text_size(mut self, text_size: impl Into<Pixels>) -> Self { - self.text_size = Some(text_size.into().0); - self - } - - /// Sets the horizontal alignment of the text of the [`Toggler`] - pub fn text_alignment(mut self, alignment: alignment::Horizontal) -> Self { - self.text_alignment = alignment; - self - } - - /// Sets the spacing between the [`Toggler`] and the text. - pub fn spacing(mut self, spacing: impl Into<Pixels>) -> Self { - self.spacing = spacing.into().0; - self - } - - /// Sets the [`Font`] of the text of the [`Toggler`] - /// - /// [`Font`]: crate::text::Renderer::Font - pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self { - self.font = Some(font.into()); - self - } - - /// Sets the style of the [`Toggler`]. - pub fn style( - mut self, - style: impl Into<<Renderer::Theme as StyleSheet>::Style>, - ) -> Self { - self.style = style.into(); - self - } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> - for Toggler<'a, Message, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, -{ - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - Length::Shrink - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - let mut row = Row::<(), Renderer>::new() - .width(self.width) - .spacing(self.spacing) - .align_items(Alignment::Center); - - if let Some(label) = &self.label { - row = row.push( - Text::new(label) - .horizontal_alignment(self.text_alignment) - .font(self.font.unwrap_or_else(|| renderer.default_font())) - .width(self.width) - .size( - self.text_size - .unwrap_or_else(|| renderer.default_size()), - ), - ); - } - - row = row.push(Row::new().width(2.0 * self.size).height(self.size)); - - row.layout(renderer, limits) - } - - fn on_event( - &mut self, - _state: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - _renderer: &Renderer, - _clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { - let mouse_over = layout.bounds().contains(cursor_position); - - if mouse_over { - shell.publish((self.on_toggle)(!self.is_toggled)); - - event::Status::Captured - } else { - event::Status::Ignored - } - } - _ => event::Status::Ignored, - } - } - - fn mouse_interaction( - &self, - _state: &Tree, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - if layout.bounds().contains(cursor_position) { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - } - } - - fn draw( - &self, - _state: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - ) { - /// Makes sure that the border radius of the toggler looks good at every size. - const BORDER_RADIUS_RATIO: f32 = 32.0 / 13.0; - - /// The space ratio between the background Quad and the Toggler bounds, and - /// between the background Quad and foreground Quad. - const SPACE_RATIO: f32 = 0.05; - - let mut children = layout.children(); - - if let Some(label) = &self.label { - let label_layout = children.next().unwrap(); - - crate::widget::text::draw( - renderer, - style, - label_layout, - label, - self.text_size, - self.font, - Default::default(), - self.text_alignment, - alignment::Vertical::Center, - ); - } - - let toggler_layout = children.next().unwrap(); - let bounds = toggler_layout.bounds(); - - let is_mouse_over = bounds.contains(cursor_position); - - let style = if is_mouse_over { - theme.hovered(&self.style, self.is_toggled) - } else { - theme.active(&self.style, self.is_toggled) - }; - - let border_radius = bounds.height / BORDER_RADIUS_RATIO; - let space = SPACE_RATIO * bounds.height; - - let toggler_background_bounds = Rectangle { - x: bounds.x + space, - y: bounds.y + space, - width: bounds.width - (2.0 * space), - height: bounds.height - (2.0 * space), - }; - - renderer.fill_quad( - renderer::Quad { - bounds: toggler_background_bounds, - border_radius: border_radius.into(), - border_width: 1.0, - border_color: style - .background_border - .unwrap_or(style.background), - }, - style.background, - ); - - let toggler_foreground_bounds = Rectangle { - x: bounds.x - + if self.is_toggled { - bounds.width - 2.0 * space - (bounds.height - (4.0 * space)) - } else { - 2.0 * space - }, - y: bounds.y + (2.0 * space), - width: bounds.height - (4.0 * space), - height: bounds.height - (4.0 * space), - }; - - renderer.fill_quad( - renderer::Quad { - bounds: toggler_foreground_bounds, - border_radius: border_radius.into(), - border_width: 1.0, - border_color: style - .foreground_border - .unwrap_or(style.foreground), - }, - style.foreground, - ); - } -} - -impl<'a, Message, Renderer> From<Toggler<'a, Message, Renderer>> - for Element<'a, Message, Renderer> -where - Message: 'a, - Renderer: 'a + text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, -{ - fn from( - toggler: Toggler<'a, Message, Renderer>, - ) -> Element<'a, Message, Renderer> { - Element::new(toggler) - } -} diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs deleted file mode 100644 index 2a24c055..00000000 --- a/native/src/widget/tooltip.rs +++ /dev/null @@ -1,387 +0,0 @@ -//! Display a widget over another. -use crate::event; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::text; -use crate::widget; -use crate::widget::container; -use crate::widget::overlay; -use crate::widget::{Text, Tree}; -use crate::{ - Clipboard, Element, Event, Layout, Length, Padding, Pixels, Point, - Rectangle, Shell, Size, Vector, Widget, -}; - -use std::borrow::Cow; - -/// An element to display a widget over another. -#[allow(missing_debug_implementations)] -pub struct Tooltip<'a, Message, Renderer: text::Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, -{ - content: Element<'a, Message, Renderer>, - tooltip: Text<'a, Renderer>, - position: Position, - gap: f32, - padding: f32, - snap_within_viewport: bool, - style: <Renderer::Theme as container::StyleSheet>::Style, -} - -impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, -{ - /// The default padding of a [`Tooltip`] drawn by this renderer. - const DEFAULT_PADDING: f32 = 5.0; - - /// Creates a new [`Tooltip`]. - /// - /// [`Tooltip`]: struct.Tooltip.html - pub fn new( - content: impl Into<Element<'a, Message, Renderer>>, - tooltip: impl Into<Cow<'a, str>>, - position: Position, - ) -> Self { - Tooltip { - content: content.into(), - tooltip: Text::new(tooltip), - position, - gap: 0.0, - padding: Self::DEFAULT_PADDING, - snap_within_viewport: true, - style: Default::default(), - } - } - - /// Sets the size of the text of the [`Tooltip`]. - pub fn size(mut self, size: impl Into<Pixels>) -> Self { - self.tooltip = self.tooltip.size(size); - self - } - - /// Sets the font of the [`Tooltip`]. - /// - /// [`Font`]: Renderer::Font - pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self { - self.tooltip = self.tooltip.font(font); - self - } - - /// Sets the gap between the content and its [`Tooltip`]. - pub fn gap(mut self, gap: impl Into<Pixels>) -> Self { - self.gap = gap.into().0; - self - } - - /// Sets the padding of the [`Tooltip`]. - pub fn padding(mut self, padding: impl Into<Pixels>) -> Self { - self.padding = padding.into().0; - self - } - - /// Sets whether the [`Tooltip`] is snapped within the viewport. - pub fn snap_within_viewport(mut self, snap: bool) -> Self { - self.snap_within_viewport = snap; - self - } - - /// Sets the style of the [`Tooltip`]. - pub fn style( - mut self, - style: impl Into<<Renderer::Theme as container::StyleSheet>::Style>, - ) -> Self { - self.style = style.into(); - self - } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> - for Tooltip<'a, Message, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, -{ - fn children(&self) -> Vec<Tree> { - vec![Tree::new(&self.content)] - } - - fn diff(&self, tree: &mut Tree) { - tree.diff_children(std::slice::from_ref(&self.content)) - } - - fn width(&self) -> Length { - self.content.as_widget().width() - } - - fn height(&self) -> Length { - self.content.as_widget().height() - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - self.content.as_widget().layout(renderer, limits) - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - self.content.as_widget_mut().on_event( - &mut tree.children[0], - event, - layout, - cursor_position, - renderer, - clipboard, - shell, - ) - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.content.as_widget().mouse_interaction( - &tree.children[0], - layout, - cursor_position, - viewport, - renderer, - ) - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - inherited_style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ) { - self.content.as_widget().draw( - &tree.children[0], - renderer, - theme, - inherited_style, - layout, - cursor_position, - viewport, - ); - - let tooltip = &self.tooltip; - - draw( - renderer, - theme, - inherited_style, - layout, - cursor_position, - viewport, - self.position, - self.gap, - self.padding, - self.snap_within_viewport, - &self.style, - |renderer, limits| { - Widget::<(), Renderer>::layout(tooltip, renderer, limits) - }, - |renderer, defaults, layout, cursor_position, viewport| { - Widget::<(), Renderer>::draw( - tooltip, - &Tree::empty(), - renderer, - theme, - defaults, - layout, - cursor_position, - viewport, - ); - }, - ); - } - - fn overlay<'b>( - &'b mut self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option<overlay::Element<'b, Message, Renderer>> { - self.content.as_widget_mut().overlay( - &mut tree.children[0], - layout, - renderer, - ) - } -} - -impl<'a, Message, Renderer> From<Tooltip<'a, Message, Renderer>> - for Element<'a, Message, Renderer> -where - Message: 'a, - Renderer: 'a + text::Renderer, - Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, -{ - fn from( - tooltip: Tooltip<'a, Message, Renderer>, - ) -> Element<'a, Message, Renderer> { - Element::new(tooltip) - } -} - -/// The position of the tooltip. Defaults to following the cursor. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Position { - /// The tooltip will follow the cursor. - FollowCursor, - /// The tooltip will appear on the top of the widget. - Top, - /// The tooltip will appear on the bottom of the widget. - Bottom, - /// The tooltip will appear on the left of the widget. - Left, - /// The tooltip will appear on the right of the widget. - Right, -} - -/// Draws a [`Tooltip`]. -pub fn draw<Renderer>( - renderer: &mut Renderer, - theme: &Renderer::Theme, - inherited_style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - position: Position, - gap: f32, - padding: f32, - snap_within_viewport: bool, - style: &<Renderer::Theme as container::StyleSheet>::Style, - layout_text: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, - draw_text: impl FnOnce( - &mut Renderer, - &renderer::Style, - Layout<'_>, - Point, - &Rectangle, - ), -) where - Renderer: crate::Renderer, - Renderer::Theme: container::StyleSheet, -{ - use container::StyleSheet; - - let bounds = layout.bounds(); - - if bounds.contains(cursor_position) { - let style = theme.appearance(style); - - let defaults = renderer::Style { - text_color: style.text_color.unwrap_or(inherited_style.text_color), - }; - - let text_layout = layout_text( - renderer, - &layout::Limits::new( - Size::ZERO, - snap_within_viewport - .then(|| viewport.size()) - .unwrap_or(Size::INFINITY), - ) - .pad(Padding::new(padding)), - ); - - let text_bounds = text_layout.bounds(); - let x_center = bounds.x + (bounds.width - text_bounds.width) / 2.0; - let y_center = bounds.y + (bounds.height - text_bounds.height) / 2.0; - - let mut tooltip_bounds = { - let offset = match position { - Position::Top => Vector::new( - x_center, - bounds.y - text_bounds.height - gap - padding, - ), - Position::Bottom => Vector::new( - x_center, - bounds.y + bounds.height + gap + padding, - ), - Position::Left => Vector::new( - bounds.x - text_bounds.width - gap - padding, - y_center, - ), - Position::Right => Vector::new( - bounds.x + bounds.width + gap + padding, - y_center, - ), - Position::FollowCursor => Vector::new( - cursor_position.x, - cursor_position.y - text_bounds.height, - ), - }; - - Rectangle { - x: offset.x - padding, - y: offset.y - padding, - width: text_bounds.width + padding * 2.0, - height: text_bounds.height + padding * 2.0, - } - }; - - if snap_within_viewport { - if tooltip_bounds.x < viewport.x { - tooltip_bounds.x = viewport.x; - } else if viewport.x + viewport.width - < tooltip_bounds.x + tooltip_bounds.width - { - tooltip_bounds.x = - viewport.x + viewport.width - tooltip_bounds.width; - } - - if tooltip_bounds.y < viewport.y { - tooltip_bounds.y = viewport.y; - } else if viewport.y + viewport.height - < tooltip_bounds.y + tooltip_bounds.height - { - tooltip_bounds.y = - viewport.y + viewport.height - tooltip_bounds.height; - } - } - - renderer.with_layer(Rectangle::with_size(Size::INFINITY), |renderer| { - container::draw_background(renderer, &style, tooltip_bounds); - - draw_text( - renderer, - &defaults, - Layout::with_offset( - Vector::new( - tooltip_bounds.x + padding, - tooltip_bounds.y + padding, - ), - &text_layout, - ), - cursor_position, - viewport, - ) - }); - } -} diff --git a/native/src/widget/tree.rs b/native/src/widget/tree.rs deleted file mode 100644 index 0af40c33..00000000 --- a/native/src/widget/tree.rs +++ /dev/null @@ -1,187 +0,0 @@ -//! Store internal widget state in a state tree to ensure continuity. -use crate::Widget; - -use std::any::{self, Any}; -use std::borrow::Borrow; -use std::fmt; - -/// A persistent state widget tree. -/// -/// A [`Tree`] is normally associated with a specific widget in the widget tree. -#[derive(Debug)] -pub struct Tree { - /// The tag of the [`Tree`]. - pub tag: Tag, - - /// The [`State`] of the [`Tree`]. - pub state: State, - - /// The children of the root widget of the [`Tree`]. - pub children: Vec<Tree>, -} - -impl Tree { - /// Creates an empty, stateless [`Tree`] with no children. - pub fn empty() -> Self { - Self { - tag: Tag::stateless(), - state: State::None, - children: Vec::new(), - } - } - - /// Creates a new [`Tree`] for the provided [`Widget`]. - pub fn new<'a, Message, Renderer>( - widget: impl Borrow<dyn Widget<Message, Renderer> + 'a>, - ) -> Self - where - Renderer: crate::Renderer, - { - let widget = widget.borrow(); - - Self { - tag: widget.tag(), - state: widget.state(), - children: widget.children(), - } - } - - /// Reconciliates the current tree with the provided [`Widget`]. - /// - /// If the tag of the [`Widget`] matches the tag of the [`Tree`], then the - /// [`Widget`] proceeds with the reconciliation (i.e. [`Widget::diff`] is called). - /// - /// Otherwise, the whole [`Tree`] is recreated. - /// - /// [`Widget::diff`]: crate::Widget::diff - pub fn diff<'a, Message, Renderer>( - &mut self, - new: impl Borrow<dyn Widget<Message, Renderer> + 'a>, - ) where - Renderer: crate::Renderer, - { - if self.tag == new.borrow().tag() { - new.borrow().diff(self) - } else { - *self = Self::new(new); - } - } - - /// Reconciliates the children of the tree with the provided list of widgets. - pub fn diff_children<'a, Message, Renderer>( - &mut self, - new_children: &[impl Borrow<dyn Widget<Message, Renderer> + 'a>], - ) where - Renderer: crate::Renderer, - { - self.diff_children_custom( - new_children, - |tree, widget| tree.diff(widget.borrow()), - |widget| Self::new(widget.borrow()), - ) - } - - /// Reconciliates the children of the tree with the provided list of widgets using custom - /// logic both for diffing and creating new widget state. - pub fn diff_children_custom<T>( - &mut self, - new_children: &[T], - diff: impl Fn(&mut Tree, &T), - new_state: impl Fn(&T) -> Self, - ) { - if self.children.len() > new_children.len() { - self.children.truncate(new_children.len()); - } - - for (child_state, new) in - self.children.iter_mut().zip(new_children.iter()) - { - diff(child_state, new); - } - - if self.children.len() < new_children.len() { - self.children.extend( - new_children[self.children.len()..].iter().map(new_state), - ); - } - } -} - -/// The identifier of some widget state. -#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] -pub struct Tag(any::TypeId); - -impl Tag { - /// Creates a [`Tag`] for a state of type `T`. - pub fn of<T>() -> Self - where - T: 'static, - { - Self(any::TypeId::of::<T>()) - } - - /// Creates a [`Tag`] for a stateless widget. - pub fn stateless() -> Self { - Self::of::<()>() - } -} - -/// The internal [`State`] of a widget. -pub enum State { - /// No meaningful internal state. - None, - - /// Some meaningful internal state. - Some(Box<dyn Any>), -} - -impl State { - /// Creates a new [`State`]. - pub fn new<T>(state: T) -> Self - where - T: 'static, - { - State::Some(Box::new(state)) - } - - /// Downcasts the [`State`] to `T` and returns a reference to it. - /// - /// # Panics - /// This method will panic if the downcast fails or the [`State`] is [`State::None`]. - pub fn downcast_ref<T>(&self) -> &T - where - T: 'static, - { - match self { - State::None => panic!("Downcast on stateless state"), - State::Some(state) => { - state.downcast_ref().expect("Downcast widget state") - } - } - } - - /// Downcasts the [`State`] to `T` and returns a mutable reference to it. - /// - /// # Panics - /// This method will panic if the downcast fails or the [`State`] is [`State::None`]. - pub fn downcast_mut<T>(&mut self) -> &mut T - where - T: 'static, - { - match self { - State::None => panic!("Downcast on stateless state"), - State::Some(state) => { - state.downcast_mut().expect("Downcast widget state") - } - } - } -} - -impl fmt::Debug for State { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::None => write!(f, "State::None"), - Self::Some(_) => write!(f, "State::Some"), - } - } -} diff --git a/native/src/widget/vertical_slider.rs b/native/src/widget/vertical_slider.rs deleted file mode 100644 index f1687e38..00000000 --- a/native/src/widget/vertical_slider.rs +++ /dev/null @@ -1,468 +0,0 @@ -//! Display an interactive selector of a single value from a range of values. -//! -//! A [`VerticalSlider`] has some local [`State`]. -use std::ops::RangeInclusive; - -pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet}; - -use crate::event::{self, Event}; -use crate::widget::tree::{self, Tree}; -use crate::{ - layout, mouse, renderer, touch, Background, Clipboard, Color, Element, - Layout, Length, Pixels, Point, Rectangle, Shell, Size, Widget, -}; - -/// An vertical bar and a handle that selects a single value from a range of -/// values. -/// -/// A [`VerticalSlider`] will try to fill the vertical space of its container. -/// -/// The [`VerticalSlider`] range of numeric values is generic and its step size defaults -/// to 1 unit. -/// -/// # Example -/// ``` -/// # use iced_native::widget::vertical_slider; -/// # use iced_native::renderer::Null; -/// # -/// # type VerticalSlider<'a, T, Message> = vertical_slider::VerticalSlider<'a, T, Message, Null>; -/// # -/// #[derive(Clone)] -/// pub enum Message { -/// SliderChanged(f32), -/// } -/// -/// let value = 50.0; -/// -/// VerticalSlider::new(0.0..=100.0, value, Message::SliderChanged); -/// ``` -#[allow(missing_debug_implementations)] -pub struct VerticalSlider<'a, T, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - range: RangeInclusive<T>, - step: T, - value: T, - on_change: Box<dyn Fn(T) -> Message + 'a>, - on_release: Option<Message>, - width: f32, - height: Length, - style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, T, Message, Renderer> VerticalSlider<'a, T, Message, Renderer> -where - T: Copy + From<u8> + std::cmp::PartialOrd, - Message: Clone, - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - /// The default width of a [`VerticalSlider`]. - pub const DEFAULT_WIDTH: f32 = 22.0; - - /// Creates a new [`VerticalSlider`]. - /// - /// It expects: - /// * an inclusive range of possible values - /// * the current value of the [`VerticalSlider`] - /// * a function that will be called when the [`VerticalSlider`] is dragged. - /// It receives the new value of the [`VerticalSlider`] and must produce a - /// `Message`. - pub fn new<F>(range: RangeInclusive<T>, value: T, on_change: F) -> Self - where - F: 'a + Fn(T) -> Message, - { - let value = if value >= *range.start() { - value - } else { - *range.start() - }; - - let value = if value <= *range.end() { - value - } else { - *range.end() - }; - - VerticalSlider { - value, - range, - step: T::from(1), - on_change: Box::new(on_change), - on_release: None, - width: Self::DEFAULT_WIDTH, - height: Length::Fill, - style: Default::default(), - } - } - - /// Sets the release message of the [`VerticalSlider`]. - /// This is called when the mouse is released from the slider. - /// - /// Typically, the user's interaction with the slider is finished when this message is produced. - /// This is useful if you need to spawn a long-running task from the slider's result, where - /// the default on_change message could create too many events. - pub fn on_release(mut self, on_release: Message) -> Self { - self.on_release = Some(on_release); - self - } - - /// Sets the width of the [`VerticalSlider`]. - pub fn width(mut self, width: impl Into<Pixels>) -> Self { - self.width = width.into().0; - self - } - - /// Sets the height of the [`VerticalSlider`]. - pub fn height(mut self, height: impl Into<Length>) -> Self { - self.height = height.into(); - self - } - - /// Sets the style of the [`VerticalSlider`]. - pub fn style( - mut self, - style: impl Into<<Renderer::Theme as StyleSheet>::Style>, - ) -> Self { - self.style = style.into(); - self - } - - /// Sets the step size of the [`VerticalSlider`]. - pub fn step(mut self, step: T) -> Self { - self.step = step; - self - } -} - -impl<'a, T, Message, Renderer> Widget<Message, Renderer> - for VerticalSlider<'a, T, Message, Renderer> -where - T: Copy + Into<f64> + num_traits::FromPrimitive, - Message: Clone, - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - fn tag(&self) -> tree::Tag { - tree::Tag::of::<State>() - } - - fn state(&self) -> tree::State { - tree::State::new(State::new()) - } - - fn width(&self) -> Length { - Length::Shrink - } - - 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); - let size = limits.resolve(Size::ZERO); - - layout::Node::new(size) - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - _renderer: &Renderer, - _clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - update( - event, - layout, - cursor_position, - shell, - tree.state.downcast_mut::<State>(), - &mut self.value, - &self.range, - self.step, - self.on_change.as_ref(), - &self.on_release, - ) - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - _style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - ) { - draw( - renderer, - layout, - cursor_position, - tree.state.downcast_ref::<State>(), - self.value, - &self.range, - theme, - &self.style, - ) - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - mouse_interaction( - layout, - cursor_position, - tree.state.downcast_ref::<State>(), - ) - } -} - -impl<'a, T, Message, Renderer> From<VerticalSlider<'a, T, Message, Renderer>> - for Element<'a, Message, Renderer> -where - T: 'a + Copy + Into<f64> + num_traits::FromPrimitive, - Message: 'a + Clone, - Renderer: 'a + crate::Renderer, - Renderer::Theme: StyleSheet, -{ - fn from( - slider: VerticalSlider<'a, T, Message, Renderer>, - ) -> Element<'a, Message, Renderer> { - Element::new(slider) - } -} - -/// Processes an [`Event`] and updates the [`State`] of a [`VerticalSlider`] -/// accordingly. -pub fn update<Message, T>( - event: Event, - layout: Layout<'_>, - cursor_position: Point, - shell: &mut Shell<'_, Message>, - state: &mut State, - value: &mut T, - range: &RangeInclusive<T>, - step: T, - on_change: &dyn Fn(T) -> Message, - on_release: &Option<Message>, -) -> event::Status -where - T: Copy + Into<f64> + num_traits::FromPrimitive, - Message: Clone, -{ - let is_dragging = state.is_dragging; - - let mut change = || { - let bounds = layout.bounds(); - let new_value = if cursor_position.y >= bounds.y + bounds.height { - *range.start() - } else if cursor_position.y <= bounds.y { - *range.end() - } else { - let step = step.into(); - let start = (*range.start()).into(); - let end = (*range.end()).into(); - - let percent = 1.0 - - f64::from(cursor_position.y - bounds.y) - / f64::from(bounds.height); - - let steps = (percent * (end - start) / step).round(); - let value = steps * step + start; - - if let Some(value) = T::from_f64(value) { - value - } else { - return; - } - }; - - if ((*value).into() - new_value.into()).abs() > f64::EPSILON { - shell.publish((on_change)(new_value)); - - *value = new_value; - } - }; - - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - if layout.bounds().contains(cursor_position) { - change(); - state.is_dragging = true; - - return event::Status::Captured; - } - } - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) - | Event::Touch(touch::Event::FingerLost { .. }) => { - if is_dragging { - if let Some(on_release) = on_release.clone() { - shell.publish(on_release); - } - state.is_dragging = false; - - return event::Status::Captured; - } - } - Event::Mouse(mouse::Event::CursorMoved { .. }) - | Event::Touch(touch::Event::FingerMoved { .. }) => { - if is_dragging { - change(); - - return event::Status::Captured; - } - } - _ => {} - } - - event::Status::Ignored -} - -/// Draws a [`VerticalSlider`]. -pub fn draw<T, R>( - renderer: &mut R, - layout: Layout<'_>, - cursor_position: Point, - state: &State, - value: T, - range: &RangeInclusive<T>, - style_sheet: &dyn StyleSheet<Style = <R::Theme as StyleSheet>::Style>, - style: &<R::Theme as StyleSheet>::Style, -) where - T: Into<f64> + Copy, - R: crate::Renderer, - R::Theme: StyleSheet, -{ - let bounds = layout.bounds(); - let is_mouse_over = bounds.contains(cursor_position); - - let style = if state.is_dragging { - style_sheet.dragging(style) - } else if is_mouse_over { - style_sheet.hovered(style) - } else { - style_sheet.active(style) - }; - - let rail_x = bounds.x + (bounds.width / 2.0).round(); - - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: rail_x - 1.0, - y: bounds.y, - width: 2.0, - height: bounds.height, - }, - border_radius: 0.0.into(), - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - style.rail_colors.0, - ); - - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: rail_x + 1.0, - y: bounds.y, - width: 2.0, - height: bounds.height, - }, - border_radius: 0.0.into(), - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - Background::Color(style.rail_colors.1), - ); - - let (handle_width, handle_height, handle_border_radius) = match style - .handle - .shape - { - HandleShape::Circle { radius } => (radius * 2.0, radius * 2.0, radius), - HandleShape::Rectangle { - width, - border_radius, - } => (f32::from(width), bounds.width, border_radius), - }; - - let value = value.into() as f32; - let (range_start, range_end) = { - let (start, end) = range.clone().into_inner(); - - (start.into() as f32, end.into() as f32) - }; - - let handle_offset = if range_start >= range_end { - 0.0 - } else { - (bounds.height - handle_width) * (value - range_end) - / (range_start - range_end) - }; - - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: rail_x - (handle_height / 2.0), - y: bounds.y + handle_offset.round(), - width: handle_height, - height: handle_width, - }, - border_radius: handle_border_radius.into(), - border_width: style.handle.border_width, - border_color: style.handle.border_color, - }, - style.handle.color, - ); -} - -/// Computes the current [`mouse::Interaction`] of a [`VerticalSlider`]. -pub fn mouse_interaction( - layout: Layout<'_>, - cursor_position: Point, - state: &State, -) -> mouse::Interaction { - let bounds = layout.bounds(); - let is_mouse_over = bounds.contains(cursor_position); - - if state.is_dragging { - mouse::Interaction::Grabbing - } else if is_mouse_over { - mouse::Interaction::Grab - } else { - mouse::Interaction::default() - } -} - -/// The local state of a [`VerticalSlider`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct State { - is_dragging: bool, -} - -impl State { - /// Creates a new [`State`]. - pub fn new() -> State { - State::default() - } -} diff --git a/native/src/window.rs b/native/src/window.rs index a5cdc8ce..97a96e54 100644 --- a/native/src/window.rs +++ b/native/src/window.rs @@ -1,18 +1,11 @@ //! Build window-based GUI applications. mod action; -mod event; -mod mode; -mod redraw_request; -mod user_attention; pub use action::Action; -pub use event::Event; -pub use mode::Mode; -pub use redraw_request::RedrawRequest; -pub use user_attention::UserAttention; +use crate::core::time::Instant; +use crate::core::window::Event; use crate::subscription::{self, Subscription}; -use crate::time::Instant; /// Subscribes to the frames of the window of the running application. /// @@ -24,7 +17,7 @@ use crate::time::Instant; /// animations without missing any frames. pub fn frames() -> Subscription<Instant> { subscription::raw_events(|event, _status| match event { - crate::Event::Window(Event::RedrawRequested(at)) => Some(at), + iced_core::Event::Window(Event::RedrawRequested(at)) => Some(at), _ => None, }) } diff --git a/native/src/window/action.rs b/native/src/window/action.rs index ce36d129..c1dbd84f 100644 --- a/native/src/window/action.rs +++ b/native/src/window/action.rs @@ -1,6 +1,6 @@ -use crate::window::{Mode, UserAttention}; +use crate::core::window::{Mode, UserAttention}; +use crate::futures::MaybeSend; -use iced_futures::MaybeSend; use std::fmt; /// An operation to be performed on some window. diff --git a/native/src/window/event.rs b/native/src/window/event.rs deleted file mode 100644 index e2fb5e66..00000000 --- a/native/src/window/event.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::time::Instant; - -use std::path::PathBuf; - -/// A window-related event. -#[derive(PartialEq, Eq, Clone, Debug)] -pub enum Event { - /// A window was moved. - Moved { - /// The new logical x location of the window - x: i32, - /// The new logical y location of the window - y: i32, - }, - - /// A window was resized. - Resized { - /// The new logical width of the window - width: u32, - /// The new logical height of the window - height: u32, - }, - - /// A window redraw was requested. - /// - /// The [`Instant`] contains the current time. - RedrawRequested(Instant), - - /// The user has requested for the window to close. - /// - /// Usually, you will want to terminate the execution whenever this event - /// occurs. - CloseRequested, - - /// A window was focused. - Focused, - - /// A window was unfocused. - Unfocused, - - /// A file is being hovered over the window. - /// - /// When the user hovers multiple files at once, this event will be emitted - /// 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, -} diff --git a/native/src/window/mode.rs b/native/src/window/mode.rs deleted file mode 100644 index fdce8e23..00000000 --- a/native/src/window/mode.rs +++ /dev/null @@ -1,12 +0,0 @@ -/// The mode of a window-based application. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Mode { - /// The application appears in its own window. - Windowed, - - /// The application takes the whole screen of its current monitor. - Fullscreen, - - /// The application is hidden - Hidden, -} diff --git a/native/src/window/redraw_request.rs b/native/src/window/redraw_request.rs deleted file mode 100644 index 3b4f0fd3..00000000 --- a/native/src/window/redraw_request.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::time::Instant; - -/// A request to redraw a window. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum RedrawRequest { - /// Redraw the next frame. - NextFrame, - - /// Redraw at the given time. - At(Instant), -} - -#[cfg(test)] -mod tests { - use super::*; - use std::time::{Duration, Instant}; - - #[test] - fn ordering() { - let now = Instant::now(); - let later = now + Duration::from_millis(10); - - assert_eq!(RedrawRequest::NextFrame, RedrawRequest::NextFrame); - assert_eq!(RedrawRequest::At(now), RedrawRequest::At(now)); - - assert!(RedrawRequest::NextFrame < RedrawRequest::At(now)); - assert!(RedrawRequest::At(now) > RedrawRequest::NextFrame); - assert!(RedrawRequest::At(now) < RedrawRequest::At(later)); - assert!(RedrawRequest::At(later) > RedrawRequest::At(now)); - - assert!(RedrawRequest::NextFrame <= RedrawRequest::NextFrame); - assert!(RedrawRequest::NextFrame <= RedrawRequest::At(now)); - assert!(RedrawRequest::At(now) >= RedrawRequest::NextFrame); - assert!(RedrawRequest::At(now) <= RedrawRequest::At(now)); - assert!(RedrawRequest::At(now) <= RedrawRequest::At(later)); - assert!(RedrawRequest::At(later) >= RedrawRequest::At(now)); - } -} diff --git a/native/src/window/user_attention.rs b/native/src/window/user_attention.rs deleted file mode 100644 index b03dfeef..00000000 --- a/native/src/window/user_attention.rs +++ /dev/null @@ -1,21 +0,0 @@ -/// The type of user attention to request. -/// -/// ## Platform-specific -/// -/// - **X11:** Sets the WM's `XUrgencyHint`. No distinction between [`Critical`] and [`Informational`]. -/// -/// [`Critical`]: Self::Critical -/// [`Informational`]: Self::Informational -#[derive(Debug, Clone, Copy)] -pub enum UserAttention { - /// ## Platform-specific - /// - /// - **macOS:** Bounces the dock icon until the application is in focus. - /// - **Windows:** Flashes both the window and the taskbar button until the application is in focus. - Critical, - /// ## Platform-specific - /// - /// - **macOS:** Bounces the dock icon once. - /// - **Windows:** Flashes the taskbar button until the application is in focus. - Informational, -} |