summaryrefslogtreecommitdiffstats
path: root/native
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2023-03-04 05:37:11 +0100
committerLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2023-03-04 05:37:11 +0100
commit3a0d34c0240f4421737a6a08761f99d6f8140d02 (patch)
treec9a4a6b8e9c1db1b8fcd05bc98e3f131d5ef4bd5 /native
parentc54409d1711e1f615c7ea4b02c082954e340632a (diff)
downloadiced-3a0d34c0240f4421737a6a08761f99d6f8140d02.tar.gz
iced-3a0d34c0240f4421737a6a08761f99d6f8140d02.tar.bz2
iced-3a0d34c0240f4421737a6a08761f99d6f8140d02.zip
Create `iced_widget` subcrate and re-organize the whole codebase
Diffstat (limited to 'native')
-rw-r--r--native/Cargo.toml7
-rw-r--r--native/src/clipboard.rs22
-rw-r--r--native/src/command.rs4
-rw-r--r--native/src/debug/basic.rs2
-rw-r--r--native/src/element.rs583
-rw-r--r--native/src/event.rs78
-rw-r--r--native/src/hasher.rs13
-rw-r--r--native/src/image.rs174
-rw-r--r--native/src/layout.rs65
-rw-r--r--native/src/layout/DRUID_LICENSE202
-rw-r--r--native/src/layout/flex.rs232
-rw-r--r--native/src/layout/limits.rs163
-rw-r--r--native/src/layout/node.rs91
-rw-r--r--native/src/lib.rs39
-rw-r--r--native/src/mouse.rs6
-rw-r--r--native/src/mouse/click.rs76
-rw-r--r--native/src/overlay.rs125
-rw-r--r--native/src/overlay/element.rs270
-rw-r--r--native/src/overlay/group.rs174
-rw-r--r--native/src/overlay/menu.rs519
-rw-r--r--native/src/program.rs6
-rw-r--r--native/src/program/state.rs18
-rw-r--r--native/src/renderer.rs98
-rw-r--r--native/src/renderer/null.rs82
-rw-r--r--native/src/runtime.rs4
-rw-r--r--native/src/shell.rs108
-rw-r--r--native/src/subscription.rs17
-rw-r--r--native/src/svg.rs89
-rw-r--r--native/src/text.rs111
-rw-r--r--native/src/touch.rs23
-rw-r--r--native/src/user_interface.rs82
-rw-r--r--native/src/widget.rs206
-rw-r--r--native/src/widget/action.rs5
-rw-r--r--native/src/widget/button.rs455
-rw-r--r--native/src/widget/checkbox.rs321
-rw-r--r--native/src/widget/column.rs264
-rw-r--r--native/src/widget/container.rs368
-rw-r--r--native/src/widget/helpers.rs317
-rw-r--r--native/src/widget/id.rs43
-rw-r--r--native/src/widget/image.rs204
-rw-r--r--native/src/widget/image/viewer.rs428
-rw-r--r--native/src/widget/operation.rs112
-rw-r--r--native/src/widget/operation/focusable.rs203
-rw-r--r--native/src/widget/operation/scrollable.rs54
-rw-r--r--native/src/widget/operation/text_input.rs131
-rw-r--r--native/src/widget/pane_grid.rs991
-rw-r--r--native/src/widget/pane_grid/axis.rs241
-rw-r--r--native/src/widget/pane_grid/configuration.rs26
-rw-r--r--native/src/widget/pane_grid/content.rs373
-rw-r--r--native/src/widget/pane_grid/direction.rs12
-rw-r--r--native/src/widget/pane_grid/draggable.rs12
-rw-r--r--native/src/widget/pane_grid/node.rs250
-rw-r--r--native/src/widget/pane_grid/pane.rs5
-rw-r--r--native/src/widget/pane_grid/split.rs5
-rw-r--r--native/src/widget/pane_grid/state.rs350
-rw-r--r--native/src/widget/pane_grid/title_bar.rs432
-rw-r--r--native/src/widget/pick_list.rs657
-rw-r--r--native/src/widget/progress_bar.rs168
-rw-r--r--native/src/widget/radio.rs299
-rw-r--r--native/src/widget/row.rs253
-rw-r--r--native/src/widget/rule.rs147
-rw-r--r--native/src/widget/scrollable.rs1327
-rw-r--r--native/src/widget/slider.rs473
-rw-r--r--native/src/widget/space.rs85
-rw-r--r--native/src/widget/svg.rs195
-rw-r--r--native/src/widget/text.rs263
-rw-r--r--native/src/widget/text_input.rs1218
-rw-r--r--native/src/widget/text_input/cursor.rs189
-rw-r--r--native/src/widget/text_input/editor.rs70
-rw-r--r--native/src/widget/text_input/value.rs133
-rw-r--r--native/src/widget/toggler.rs324
-rw-r--r--native/src/widget/tooltip.rs387
-rw-r--r--native/src/widget/tree.rs187
-rw-r--r--native/src/widget/vertical_slider.rs468
-rw-r--r--native/src/window.rs13
-rw-r--r--native/src/window/action.rs4
-rw-r--r--native/src/window/event.rs58
-rw-r--r--native/src/window/mode.rs12
-rw-r--r--native/src/window/redraw_request.rs38
-rw-r--r--native/src/window/user_attention.rs21
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);
-/// ```
-///
-/// ![Checkbox drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/checkbox.png?raw=true)
-#[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.
-//!
-//! [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](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.
-///
-/// [![Pane grid - Iced](https://thumbs.gfycat.com/FrailFreshAiredaleterrier-small.gif)](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, &region_a, regions);
- b.compute_regions(spacing, &region_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, &region_a, splits);
- b.compute_splits(spacing, &region_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);
-/// ```
-///
-/// ![Progress bar drawn with `iced_wgpu`](https://user-images.githubusercontent.com/18618951/71662391-a316c200-2d51-11ea-9cef-52758cab85e3.png)
-#[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);
-/// ```
-///
-/// ![Radio buttons drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/radio.png?raw=true)
-#[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);
-/// ```
-///
-/// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true)
-#[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]));
-/// ```
-///
-/// ![Text drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text.png?raw=true)
-#[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);
-/// ```
-/// ![Text input drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text_input.png?raw=true)
-#[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,
-}