summaryrefslogtreecommitdiff
path: root/crates/iced_drop/src/widget
diff options
context:
space:
mode:
authorpml68 <contact@pml68.dev>2025-06-13 00:26:44 +0200
committerpml68 <contact@pml68.dev>2025-06-13 00:26:44 +0200
commita0501112cf3d74b9c79f7a985283ca2f41f5a7d3 (patch)
tree581042549e95602ec495317b260a4c45d965f352 /crates/iced_drop/src/widget
parentchore: update deps (diff)
downloadiced-builder-a0501112cf3d74b9c79f7a985283ca2f41f5a7d3.tar.gz
chore: extract `iced_drop` into separate repo (`pml68/iced_drop`)
Diffstat (limited to '')
-rw-r--r--crates/iced_drop/src/widget.rs2
-rw-r--r--crates/iced_drop/src/widget/droppable.rs565
-rw-r--r--crates/iced_drop/src/widget/operation.rs1
-rw-r--r--crates/iced_drop/src/widget/operation/drop.rs88
4 files changed, 0 insertions, 656 deletions
diff --git a/crates/iced_drop/src/widget.rs b/crates/iced_drop/src/widget.rs
deleted file mode 100644
index 6b3fed2..0000000
--- a/crates/iced_drop/src/widget.rs
+++ /dev/null
@@ -1,2 +0,0 @@
-pub mod droppable;
-pub mod operation;
diff --git a/crates/iced_drop/src/widget/droppable.rs b/crates/iced_drop/src/widget/droppable.rs
deleted file mode 100644
index 0e93bd0..0000000
--- a/crates/iced_drop/src/widget/droppable.rs
+++ /dev/null
@@ -1,565 +0,0 @@
-//! Encapsulates a widget that can be dragged and dropped.
-use std::fmt::Debug;
-use std::vec;
-
-use iced::advanced::widget::{Operation, Tree, Widget};
-use iced::advanced::{self, Layout, layout, mouse, overlay, renderer};
-use iced::{Element, Point, Rectangle, Size, Vector};
-
-/// An element that can be dragged and dropped on a [`DropZone`]
-pub struct Droppable<
- 'a,
- Message,
- Theme = iced::Theme,
- Renderer = iced::Renderer,
-> where
- Message: Clone,
- Renderer: renderer::Renderer,
-{
- content: Element<'a, Message, Theme, Renderer>,
- id: Option<iced::advanced::widget::Id>,
- on_click: Option<Message>,
- on_drop: Option<Box<dyn Fn(Point, Rectangle) -> Message + 'a>>,
- on_drag: Option<Box<dyn Fn(Point, Rectangle) -> Message + 'a>>,
- on_cancel: Option<Message>,
- drag_mode: Option<(bool, bool)>,
- drag_overlay: bool,
- drag_hide: bool,
- drag_center: bool,
- drag_size: Option<Size>,
- reset_delay: usize,
- status: Option<Status>,
-}
-
-impl<'a, Message, Theme, Renderer> Droppable<'a, Message, Theme, Renderer>
-where
- Message: Clone,
- Renderer: renderer::Renderer,
-{
- /// Creates a new [`Droppable`].
- pub fn new(
- content: impl Into<Element<'a, Message, Theme, Renderer>>,
- ) -> Self {
- Self {
- content: content.into(),
- id: None,
- on_click: None,
- on_drop: None,
- on_drag: None,
- on_cancel: None,
- drag_mode: Some((true, true)),
- drag_overlay: true,
- drag_hide: false,
- drag_center: false,
- drag_size: None,
- reset_delay: 0,
- status: None,
- }
- }
-
- /// Sets the unique identifier of the [`Droppable`].
- pub fn id(mut self, id: iced::advanced::widget::Id) -> Self {
- self.id = Some(id);
- self
- }
-
- /// Sets the message that will be produced when the [`Droppable`] is clicked.
- pub fn on_click(mut self, message: Message) -> Self {
- self.on_click = Some(message);
- self
- }
-
- /// Sets the message that will be produced when the [`Droppable`] is dropped on a [`DropZone`].
- ///
- /// Unless this is set, the [`Droppable`] will be disabled.
- pub fn on_drop<F>(mut self, message: F) -> Self
- where
- F: Fn(Point, Rectangle) -> Message + 'a,
- {
- self.on_drop = Some(Box::new(message));
- self
- }
-
- /// Sets the message that will be produced when the [`Droppable`] is dragged.
- pub fn on_drag<F>(mut self, message: F) -> Self
- where
- F: Fn(Point, Rectangle) -> Message + 'a,
- {
- self.on_drag = Some(Box::new(message));
- self
- }
-
- /// Sets the message that will be produced when the user right clicks while dragging the [`Droppable`].
- pub fn on_cancel(mut self, message: Message) -> Self {
- self.on_cancel = Some(message);
- self
- }
-
- /// Sets whether the [`Droppable`] should be drawn under the cursor while dragging.
- pub fn drag_overlay(mut self, drag_overlay: bool) -> Self {
- self.drag_overlay = drag_overlay;
- self
- }
-
- /// Sets whether the [`Droppable`] should be hidden while dragging.
- pub fn drag_hide(mut self, drag_hide: bool) -> Self {
- self.drag_hide = drag_hide;
- self
- }
-
- /// Sets whether the [`Droppable`] should be centered on the cursor while dragging.
- pub fn drag_center(mut self, drag_center: bool) -> Self {
- self.drag_center = drag_center;
- self
- }
-
- // Sets whether the [`Droppable`] can be dragged along individual axes.
- pub fn drag_mode(mut self, drag_x: bool, drag_y: bool) -> Self {
- self.drag_mode = Some((drag_x, drag_y));
- self
- }
-
- /// Sets whether the [`Droppable`] should be be resized to a given size while dragging.
- pub fn drag_size(mut self, hide_size: Size) -> Self {
- self.drag_size = Some(hide_size);
- self
- }
-
- /// Sets the number of frames/layout calls to wait before resetting the size of the [`Droppable`] after dropping.
- ///
- /// This is useful for cases where the [`Droppable`] is being moved to a new location after some widget operation.
- /// In this case, the [`Droppable`] will mainting the 'drag_size' for the given number of frames before resetting to its original size.
- /// This prevents the [`Droppable`] from 'jumping' back to its original size before the new location is rendered which
- /// prevents flickering.
- ///
- /// Warning: this should only be set if there's is some noticeble flickering when the [`Droppable`] is dropped. That is, if the
- /// [`Droppable`] returns to its original size before it's moved to it's new location.
- pub fn reset_delay(mut self, reset_delay: usize) -> Self {
- self.reset_delay = reset_delay;
- self
- }
-}
-
-impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for Droppable<'a, Message, Theme, Renderer>
-where
- Message: Clone,
- Renderer: renderer::Renderer,
-{
- fn state(&self) -> iced::advanced::widget::tree::State {
- advanced::widget::tree::State::new(State::default())
- }
-
- fn tag(&self) -> iced::advanced::widget::tree::Tag {
- advanced::widget::tree::Tag::of::<State>()
- }
-
- fn children(&self) -> Vec<iced::advanced::widget::Tree> {
- vec![advanced::widget::Tree::new(&self.content)]
- }
-
- fn diff(&self, tree: &mut iced::advanced::widget::Tree) {
- tree.diff_children(std::slice::from_ref(&self.content))
- }
-
- fn size(&self) -> iced::Size<iced::Length> {
- self.content.as_widget().size()
- }
-
- fn update(
- &mut self,
- tree: &mut iced::advanced::widget::Tree,
- event: &iced::Event,
- layout: iced::advanced::Layout<'_>,
- cursor: iced::advanced::mouse::Cursor,
- _renderer: &Renderer,
- _clipboard: &mut dyn iced::advanced::Clipboard,
- shell: &mut iced::advanced::Shell<'_, Message>,
- _viewport: &iced::Rectangle,
- ) {
- // handle the on event of the content first, in case that the droppable is nested
- self.content.as_widget_mut().update(
- &mut tree.children[0],
- event,
- layout,
- cursor,
- _renderer,
- _clipboard,
- shell,
- _viewport,
- );
- // this should really only be captured if the droppable is nested or it contains some other
- // widget that captures the event
- if shell.is_event_captured() {
- return;
- }
-
- if let Some(on_drop) = self.on_drop.as_deref() {
- let state = tree.state.downcast_mut::<State>();
- if let iced::Event::Mouse(mouse) = event {
- match mouse {
- mouse::Event::ButtonPressed(btn) => {
- if *btn == mouse::Button::Left
- && cursor.is_over(layout.bounds())
- {
- // select the droppable and store the position of the widget before dragging
- state.action =
- Action::Select(cursor.position().unwrap());
- let bounds = layout.bounds();
- state.widget_pos = bounds.position();
- state.overlay_bounds.width = bounds.width;
- state.overlay_bounds.height = bounds.height;
-
- if let Some(on_click) = self.on_click.clone() {
- shell.publish(on_click);
- }
- shell.capture_event();
- } else if *btn == mouse::Button::Right {
- if let Action::Drag(_, _) = state.action {
- shell.invalidate_layout();
- state.action = Action::None;
- if let Some(on_cancel) = self.on_cancel.clone()
- {
- shell.publish(on_cancel);
- }
- }
- }
- }
- &mouse::Event::CursorMoved { mut position } => match state
- .action
- {
- Action::Select(start) | Action::Drag(start, _) => {
- // calculate the new position of the widget after dragging
-
- if let Some((drag_x, drag_y)) = self.drag_mode {
- position = Point {
- x: if drag_x {
- position.x
- } else {
- start.x
- },
- y: if drag_y {
- position.y
- } else {
- start.y
- },
- };
- }
-
- state.action = Action::Drag(start, position);
- // update the position of the overlay since the cursor was moved
- if self.drag_center {
- state.overlay_bounds.x = position.x
- - state.overlay_bounds.width / 2.0;
- state.overlay_bounds.y = position.y
- - state.overlay_bounds.height / 2.0;
- } else {
- state.overlay_bounds.x =
- state.widget_pos.x + position.x - start.x;
- state.overlay_bounds.y =
- state.widget_pos.y + position.y - start.y;
- }
- // send on drag msg
- if let Some(on_drag) = self.on_drag.as_deref() {
- let message =
- (on_drag)(position, state.overlay_bounds);
- shell.publish(message);
- }
-
- shell.request_redraw();
- }
- _ => (),
- },
- mouse::Event::ButtonReleased(mouse::Button::Left) => {
- match state.action {
- Action::Select(_) => {
- state.action = Action::None;
- }
- Action::Drag(_, current) => {
- // send on drop msg
- let message =
- (on_drop)(current, state.overlay_bounds);
- shell.publish(message);
-
- if self.reset_delay == 0 {
- state.action = Action::None;
- } else {
- state.action =
- Action::Wait(self.reset_delay);
- }
- }
- _ => (),
- }
- }
- _ => {}
- }
- }
- }
-
- let current_status = if cursor.is_over(layout.bounds()) {
- if self.on_drop.is_none() {
- Status::Disabled
- } else {
- let state = tree.state.downcast_ref::<State>();
-
- if let Action::Drag(_, _) = state.action {
- Status::Dragged
- } else {
- Status::Hovered
- }
- }
- } else {
- Status::Active
- };
-
- if let iced::Event::Window(iced::window::Event::RedrawRequested(_now)) =
- event
- {
- self.status = Some(current_status);
- } else if self.status.is_some_and(|status| status != current_status) {
- shell.request_redraw();
- }
- }
-
- fn layout(
- &self,
- tree: &mut iced::advanced::widget::Tree,
- renderer: &Renderer,
- limits: &iced::advanced::layout::Limits,
- ) -> iced::advanced::layout::Node {
- let state: &mut State = tree.state.downcast_mut::<State>();
- let content_node = self.content.as_widget().layout(
- &mut tree.children[0],
- renderer,
- limits,
- );
-
- // Adjust the size of the original widget if it's being dragged or we're wating to reset the size
- if let Some(new_size) = self.drag_size {
- match state.action {
- Action::Drag(_, _) => {
- return iced::advanced::layout::Node::with_children(
- new_size,
- content_node.children().to_vec(),
- );
- }
- Action::Wait(reveal_index) => {
- if reveal_index <= 1 {
- state.action = Action::None;
- } else {
- state.action = Action::Wait(reveal_index - 1);
- }
-
- return iced::advanced::layout::Node::with_children(
- new_size,
- content_node.children().to_vec(),
- );
- }
- _ => (),
- }
- }
-
- content_node
- }
-
- fn operate(
- &self,
- tree: &mut Tree,
- layout: Layout<'_>,
- renderer: &Renderer,
- operation: &mut dyn Operation,
- ) {
- let state = tree.state.downcast_mut::<State>();
- operation.custom(self.id.as_ref(), layout.bounds(), state);
- operation.container(
- self.id.as_ref(),
- layout.bounds(),
- &mut |operation| {
- self.content.as_widget().operate(
- &mut tree.children[0],
- layout,
- renderer,
- operation,
- );
- },
- );
- }
-
- fn draw(
- &self,
- tree: &iced::advanced::widget::Tree,
- renderer: &mut Renderer,
- theme: &Theme,
- style: &renderer::Style,
- layout: iced::advanced::Layout<'_>,
- cursor: iced::advanced::mouse::Cursor,
- viewport: &iced::Rectangle,
- ) {
- let state: &State = tree.state.downcast_ref::<State>();
- if let Action::Drag(_, _) = state.action {
- if self.drag_hide {
- return;
- }
- }
-
- self.content.as_widget().draw(
- &tree.children[0],
- renderer,
- theme,
- style,
- layout,
- cursor,
- &viewport,
- );
- }
-
- fn overlay<'b>(
- &'b mut self,
- tree: &'b mut Tree,
- layout: Layout<'b>,
- renderer: &Renderer,
- _viewport: &iced::Rectangle,
- _translation: Vector,
- ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
- let state: &mut State = tree.state.downcast_mut::<State>();
- if self.drag_overlay {
- if let Action::Drag(_, _) = state.action {
- return Some(overlay::Element::new(Box::new(Overlay {
- content: &self.content,
- tree: &mut tree.children[0],
- overlay_bounds: state.overlay_bounds,
- })));
- }
- }
- self.content.as_widget_mut().overlay(
- &mut tree.children[0],
- layout,
- renderer,
- _viewport,
- _translation,
- )
- }
-
- fn mouse_interaction(
- &self,
- tree: &iced::advanced::widget::Tree,
- layout: iced::advanced::Layout<'_>,
- cursor: iced::advanced::mouse::Cursor,
- _viewport: &iced::Rectangle,
- _renderer: &Renderer,
- ) -> iced::advanced::mouse::Interaction {
- let child_interact = self.content.as_widget().mouse_interaction(
- &tree.children[0],
- layout,
- cursor,
- _viewport,
- _renderer,
- );
-
- if child_interact != mouse::Interaction::default() {
- return child_interact;
- }
-
- let state = tree.state.downcast_ref::<State>();
-
- if cursor.is_over(layout.bounds()) {
- if self.on_drop.is_none() {
- mouse::Interaction::NotAllowed
- } else if let Action::Drag(_, _) = state.action {
- mouse::Interaction::Grabbing
- } else {
- mouse::Interaction::Pointer
- }
- } else {
- mouse::Interaction::default()
- }
- }
-}
-
-impl<'a, Message, Theme, Renderer> From<Droppable<'a, Message, Theme, Renderer>>
- for Element<'a, Message, Theme, Renderer>
-where
- Message: 'a + Clone,
- Theme: 'a,
- Renderer: 'a + renderer::Renderer,
-{
- fn from(
- droppable: Droppable<'a, Message, Theme, Renderer>,
- ) -> Element<'a, Message, Theme, Renderer> {
- Element::new(droppable)
- }
-}
-
-#[derive(Default, Clone, Copy, PartialEq, Debug)]
-pub struct State {
- widget_pos: Point,
- overlay_bounds: Rectangle,
- action: Action,
-}
-
-#[derive(Default, Clone, Copy, PartialEq, Debug)]
-pub enum Status {
- #[default]
- Active,
- Hovered,
- Dragged,
- Disabled,
-}
-
-#[derive(Default, Clone, Copy, PartialEq, Debug)]
-pub enum Action {
- #[default]
- None,
- /// (point clicked)
- Select(Point),
- /// (start pos, current pos)
- Drag(Point, Point),
- /// (frames to wait)
- Wait(usize),
-}
-
-struct Overlay<'a, 'b, Message, Theme, Renderer>
-where
- Renderer: renderer::Renderer,
-{
- content: &'b Element<'a, Message, Theme, Renderer>,
- tree: &'b mut advanced::widget::Tree,
- overlay_bounds: Rectangle,
-}
-
-impl<'a, 'b, Message, Theme, Renderer>
- overlay::Overlay<Message, Theme, Renderer>
- for Overlay<'a, 'b, Message, Theme, Renderer>
-where
- Renderer: renderer::Renderer,
-{
- fn layout(&mut self, renderer: &Renderer, _bounds: Size) -> layout::Node {
- Widget::<Message, Theme, Renderer>::layout(
- self.content.as_widget(),
- self.tree,
- renderer,
- &layout::Limits::new(Size::ZERO, self.overlay_bounds.size()),
- )
- .move_to(self.overlay_bounds.position())
- }
-
- fn draw(
- &self,
- renderer: &mut Renderer,
- theme: &Theme,
- inherited_style: &renderer::Style,
- layout: Layout<'_>,
- cursor_position: mouse::Cursor,
- ) {
- Widget::<Message, Theme, Renderer>::draw(
- self.content.as_widget(),
- self.tree,
- renderer,
- theme,
- inherited_style,
- layout,
- cursor_position,
- &Rectangle::with_size(Size::INFINITY),
- );
- }
-}
diff --git a/crates/iced_drop/src/widget/operation.rs b/crates/iced_drop/src/widget/operation.rs
deleted file mode 100644
index 3d7dcff..0000000
--- a/crates/iced_drop/src/widget/operation.rs
+++ /dev/null
@@ -1 +0,0 @@
-pub mod drop;
diff --git a/crates/iced_drop/src/widget/operation/drop.rs b/crates/iced_drop/src/widget/operation/drop.rs
deleted file mode 100644
index ead412c..0000000
--- a/crates/iced_drop/src/widget/operation/drop.rs
+++ /dev/null
@@ -1,88 +0,0 @@
-use iced::advanced::widget::operation::{Outcome, Scrollable};
-use iced::advanced::widget::{Id, Operation};
-use iced::{Rectangle, Vector};
-
-/// Produces an [`Operation`] that will find the drop zones that pass a filter on the zone's bounds.
-/// For any drop zone to be considered, the Element must have some Id.
-/// If `options` is `None`, all drop zones will be considered.
-/// Depth determines how how deep into nested drop zones to go.
-/// If 'depth' is `None`, nested dropzones will be fully explored
-pub fn find_zones<F>(
- filter: F,
- options: Option<Vec<Id>>,
- depth: Option<usize>,
-) -> impl Operation<Vec<(Id, Rectangle)>>
-where
- F: Fn(&Rectangle) -> bool + Send + 'static,
-{
- struct FindDropZone<F> {
- filter: F,
- options: Option<Vec<Id>>,
- zones: Vec<(Id, Rectangle)>,
- max_depth: Option<usize>,
- c_depth: usize,
- offset: Vector,
- }
-
- impl<F> Operation<Vec<(Id, Rectangle)>> for FindDropZone<F>
- where
- F: Fn(&Rectangle) -> bool + Send + 'static,
- {
- fn container(
- &mut self,
- id: Option<&Id>,
- bounds: iced::Rectangle,
- operate_on_children: &mut dyn FnMut(
- &mut dyn Operation<Vec<(Id, Rectangle)>>,
- ),
- ) {
- match id {
- Some(id) => {
- let is_option = match &self.options {
- Some(options) => options.contains(id),
- None => true,
- };
- let bounds = bounds - self.offset;
- if is_option && (self.filter)(&bounds) {
- self.c_depth += 1;
- self.zones.push((id.clone(), bounds));
- }
- }
- None => (),
- }
- let goto_next = match &self.max_depth {
- Some(m_depth) => self.c_depth < *m_depth,
- None => true,
- };
- if goto_next {
- operate_on_children(self);
- }
- }
-
- fn finish(&self) -> Outcome<Vec<(Id, Rectangle)>> {
- Outcome::Some(self.zones.clone())
- }
-
- fn scrollable(
- &mut self,
- _id: Option<&Id>,
- bounds: Rectangle,
- _content_bounds: Rectangle,
- translation: Vector,
- _state: &mut dyn Scrollable,
- ) {
- if (self.filter)(&bounds) {
- self.offset = self.offset + translation;
- }
- }
- }
-
- FindDropZone {
- filter,
- options,
- zones: vec![],
- max_depth: depth,
- c_depth: 0,
- offset: Vector { x: 0.0, y: 0.0 },
- }
-}