summaryrefslogtreecommitdiffstats
path: root/native/src/widget
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/src/widget
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/src/widget')
-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
42 files changed, 2 insertions, 12443 deletions
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()
- }
-}