feat: dnd for segmented buttons and nav
This commit is contained in:
parent
f15aeb4247
commit
d625291266
4 changed files with 305 additions and 20 deletions
|
|
@ -1,4 +1,7 @@
|
||||||
use std::borrow::Cow;
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
sync::atomic::{AtomicU64, Ordering},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
iced::{
|
iced::{
|
||||||
|
|
@ -33,6 +36,24 @@ pub fn dnd_destination_for_data<T: AllowedMimeTypes, Message: 'static>(
|
||||||
DndDestination::for_data(child, on_finish)
|
DndDestination::for_data(child, on_finish)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static DRAG_ID_COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct DragId(pub u128);
|
||||||
|
|
||||||
|
impl DragId {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
DragId(u128::from(u64::MAX) + u128::from(DRAG_ID_COUNTER.fetch_add(1, Ordering::Relaxed)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::new_without_default)]
|
||||||
|
impl Default for DragId {
|
||||||
|
fn default() -> Self {
|
||||||
|
DragId::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct DndDestination<'a, Message> {
|
pub struct DndDestination<'a, Message> {
|
||||||
id: Id,
|
id: Id,
|
||||||
drag_id: Option<u64>,
|
drag_id: Option<u64>,
|
||||||
|
|
@ -228,7 +249,7 @@ impl<'a, Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tag(&self) -> iced_core::widget::tree::Tag {
|
fn tag(&self) -> iced_core::widget::tree::Tag {
|
||||||
tree::Tag::of::<State>()
|
tree::Tag::of::<State<()>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diff(&mut self, tree: &mut Tree) {
|
fn diff(&mut self, tree: &mut Tree) {
|
||||||
|
|
@ -236,7 +257,7 @@ impl<'a, Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
}
|
}
|
||||||
|
|
||||||
fn state(&self) -> iced_core::widget::tree::State {
|
fn state(&self) -> iced_core::widget::tree::State {
|
||||||
tree::State::new(State::new())
|
tree::State::new(State::<()>::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size(&self) -> iced_core::Size<Length> {
|
fn size(&self) -> iced_core::Size<Length> {
|
||||||
|
|
@ -294,7 +315,7 @@ impl<'a, Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
return event::Status::Captured;
|
return event::Status::Captured;
|
||||||
}
|
}
|
||||||
|
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State<()>>();
|
||||||
|
|
||||||
let my_id = self.get_drag_id();
|
let my_id = self.get_drag_id();
|
||||||
|
|
||||||
|
|
@ -310,6 +331,7 @@ impl<'a, Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
y,
|
y,
|
||||||
mime_types,
|
mime_types,
|
||||||
self.on_enter.as_ref().map(std::convert::AsRef::as_ref),
|
self.on_enter.as_ref().map(std::convert::AsRef::as_ref),
|
||||||
|
(),
|
||||||
) {
|
) {
|
||||||
shell.publish(msg);
|
shell.publish(msg);
|
||||||
}
|
}
|
||||||
|
|
@ -357,6 +379,7 @@ impl<'a, Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
y,
|
y,
|
||||||
self.on_motion.as_ref().map(std::convert::AsRef::as_ref),
|
self.on_motion.as_ref().map(std::convert::AsRef::as_ref),
|
||||||
self.on_enter.as_ref().map(std::convert::AsRef::as_ref),
|
self.on_enter.as_ref().map(std::convert::AsRef::as_ref),
|
||||||
|
(),
|
||||||
) {
|
) {
|
||||||
shell.publish(msg);
|
shell.publish(msg);
|
||||||
}
|
}
|
||||||
|
|
@ -516,18 +539,19 @@ impl<'a, Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct State {
|
pub struct State<T> {
|
||||||
pub drag_offer: Option<DragOffer>,
|
pub drag_offer: Option<DragOffer<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DragOffer {
|
pub struct DragOffer<T> {
|
||||||
pub x: f64,
|
pub x: f64,
|
||||||
pub y: f64,
|
pub y: f64,
|
||||||
pub dropped: bool,
|
pub dropped: bool,
|
||||||
pub selected_action: DndAction,
|
pub selected_action: DndAction,
|
||||||
|
pub data: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl<T> State<T> {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { drag_offer: None }
|
Self { drag_offer: None }
|
||||||
|
|
@ -538,13 +562,15 @@ impl State {
|
||||||
x: f64,
|
x: f64,
|
||||||
y: f64,
|
y: f64,
|
||||||
mime_types: Vec<String>,
|
mime_types: Vec<String>,
|
||||||
on_enter: Option<&dyn Fn(f64, f64, Vec<String>) -> Message>,
|
on_enter: Option<impl Fn(f64, f64, Vec<String>) -> Message>,
|
||||||
|
data: T,
|
||||||
) -> Option<Message> {
|
) -> Option<Message> {
|
||||||
self.drag_offer = Some(DragOffer {
|
self.drag_offer = Some(DragOffer {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
dropped: false,
|
dropped: false,
|
||||||
selected_action: DndAction::empty(),
|
selected_action: DndAction::empty(),
|
||||||
|
data,
|
||||||
});
|
});
|
||||||
on_enter.map(|f| f(x, y, mime_types))
|
on_enter.map(|f| f(x, y, mime_types))
|
||||||
}
|
}
|
||||||
|
|
@ -562,8 +588,9 @@ impl State {
|
||||||
&mut self,
|
&mut self,
|
||||||
x: f64,
|
x: f64,
|
||||||
y: f64,
|
y: f64,
|
||||||
on_motion: Option<&dyn Fn(f64, f64) -> Message>,
|
on_motion: Option<impl Fn(f64, f64) -> Message>,
|
||||||
on_enter: Option<&dyn Fn(f64, f64, Vec<String>) -> Message>,
|
on_enter: Option<impl Fn(f64, f64, Vec<String>) -> Message>,
|
||||||
|
data: T,
|
||||||
) -> Option<Message> {
|
) -> Option<Message> {
|
||||||
if let Some(s) = self.drag_offer.as_mut() {
|
if let Some(s) = self.drag_offer.as_mut() {
|
||||||
s.x = x;
|
s.x = x;
|
||||||
|
|
@ -574,6 +601,7 @@ impl State {
|
||||||
y,
|
y,
|
||||||
dropped: false,
|
dropped: false,
|
||||||
selected_action: DndAction::empty(),
|
selected_action: DndAction::empty(),
|
||||||
|
data,
|
||||||
});
|
});
|
||||||
if let Some(f) = on_enter {
|
if let Some(f) = on_enter {
|
||||||
return Some(f(x, y, vec![]));
|
return Some(f(x, y, vec![]));
|
||||||
|
|
@ -584,7 +612,7 @@ impl State {
|
||||||
|
|
||||||
pub fn on_drop<Message>(
|
pub fn on_drop<Message>(
|
||||||
&mut self,
|
&mut self,
|
||||||
on_drop: Option<&dyn Fn(f64, f64) -> Message>,
|
on_drop: Option<impl Fn(f64, f64) -> Message>,
|
||||||
) -> Option<Message> {
|
) -> Option<Message> {
|
||||||
if let Some(offer) = self.drag_offer.as_mut() {
|
if let Some(offer) = self.drag_offer.as_mut() {
|
||||||
offer.dropped = true;
|
offer.dropped = true;
|
||||||
|
|
@ -598,7 +626,7 @@ impl State {
|
||||||
pub fn on_action_selected<Message>(
|
pub fn on_action_selected<Message>(
|
||||||
&mut self,
|
&mut self,
|
||||||
action: DndAction,
|
action: DndAction,
|
||||||
on_action_selected: Option<&dyn Fn(DndAction) -> Message>,
|
on_action_selected: Option<impl Fn(DndAction) -> Message>,
|
||||||
) -> Option<Message> {
|
) -> Option<Message> {
|
||||||
if let Some(s) = self.drag_offer.as_mut() {
|
if let Some(s) = self.drag_offer.as_mut() {
|
||||||
s.selected_action = action;
|
s.selected_action = action;
|
||||||
|
|
@ -614,8 +642,8 @@ impl State {
|
||||||
&mut self,
|
&mut self,
|
||||||
mime: String,
|
mime: String,
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
on_data_received: Option<&dyn Fn(String, Vec<u8>) -> Message>,
|
on_data_received: Option<impl Fn(String, Vec<u8>) -> Message>,
|
||||||
on_finish: Option<&dyn Fn(String, Vec<u8>, DndAction, f64, f64) -> Message>,
|
on_finish: Option<impl Fn(String, Vec<u8>, DndAction, f64, f64) -> Message>,
|
||||||
) -> (Option<Message>, event::Status) {
|
) -> (Option<Message>, event::Status) {
|
||||||
let Some(dnd) = self.drag_offer.as_ref() else {
|
let Some(dnd) = self.drag_offer.as_ref() else {
|
||||||
self.drag_offer = None;
|
self.drag_offer = None;
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,12 @@ pub mod divider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod dnd_destination;
|
||||||
|
pub use dnd_destination::{dnd_destination, DndDestination};
|
||||||
|
|
||||||
|
pub mod dnd_source;
|
||||||
|
pub use dnd_source::{dnd_source, DndSource};
|
||||||
|
|
||||||
pub mod dropdown;
|
pub mod dropdown;
|
||||||
pub use dropdown::{dropdown, Dropdown};
|
pub use dropdown::{dropdown, Dropdown};
|
||||||
|
|
||||||
|
|
@ -137,7 +143,7 @@ pub use list::*;
|
||||||
pub mod menu;
|
pub mod menu;
|
||||||
|
|
||||||
pub mod nav_bar;
|
pub mod nav_bar;
|
||||||
pub use nav_bar::nav_bar;
|
pub use nav_bar::{nav_bar, nav_bar_dnd};
|
||||||
|
|
||||||
pub mod nav_bar_toggle;
|
pub mod nav_bar_toggle;
|
||||||
pub use nav_bar_toggle::{nav_bar_toggle, NavBarToggle};
|
pub use nav_bar_toggle::{nav_bar_toggle, NavBarToggle};
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
use apply::Apply;
|
use apply::Apply;
|
||||||
use iced::{
|
use iced::{
|
||||||
|
clipboard::{dnd::DndAction, mime::AllowedMimeTypes},
|
||||||
widget::{container, scrollable},
|
widget::{container, scrollable},
|
||||||
Background, Length,
|
Background, Length,
|
||||||
};
|
};
|
||||||
|
|
@ -14,6 +15,8 @@ use iced_core::{Border, Color, Shadow};
|
||||||
|
|
||||||
use crate::{theme, widget::segmented_button, Theme};
|
use crate::{theme, widget::segmented_button, Theme};
|
||||||
|
|
||||||
|
use super::dnd_destination::DragId;
|
||||||
|
|
||||||
pub type Id = segmented_button::Entity;
|
pub type Id = segmented_button::Entity;
|
||||||
pub type Model = segmented_button::SingleSelectModel;
|
pub type Model = segmented_button::SingleSelectModel;
|
||||||
|
|
||||||
|
|
@ -46,6 +49,44 @@ where
|
||||||
.style(theme::Container::custom(nav_bar_style))
|
.style(theme::Container::custom(nav_bar_style))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Navigation side panel for switching between views.
|
||||||
|
/// Can receive drag and drop events.
|
||||||
|
pub fn nav_bar_dnd<Message, D: AllowedMimeTypes>(
|
||||||
|
model: &segmented_button::SingleSelectModel,
|
||||||
|
on_activate: fn(segmented_button::Entity) -> Message,
|
||||||
|
on_dnd_enter: impl Fn(segmented_button::Entity, Vec<String>) -> Message + 'static,
|
||||||
|
on_dnd_leave: impl Fn(segmented_button::Entity) -> Message + 'static,
|
||||||
|
on_dnd_drop: impl Fn(segmented_button::Entity, Option<D>, DndAction) -> Message + 'static,
|
||||||
|
id: DragId,
|
||||||
|
) -> iced::widget::Container<Message, crate::Theme, crate::Renderer>
|
||||||
|
where
|
||||||
|
Message: Clone + 'static,
|
||||||
|
{
|
||||||
|
let theme = crate::theme::active();
|
||||||
|
let space_s = theme.cosmic().space_s();
|
||||||
|
let space_xxs = theme.cosmic().space_xxs();
|
||||||
|
|
||||||
|
let nav_buttons = segmented_button::vertical(model)
|
||||||
|
.button_height(32)
|
||||||
|
.button_padding([space_s, space_xxs, space_s, space_xxs])
|
||||||
|
.button_spacing(space_xxs)
|
||||||
|
.spacing(space_xxs)
|
||||||
|
.on_activate(on_activate)
|
||||||
|
.style(crate::theme::SegmentedButton::TabBar)
|
||||||
|
.on_dnd_enter(on_dnd_enter)
|
||||||
|
.on_dnd_leave(on_dnd_leave)
|
||||||
|
.on_dnd_drop(on_dnd_drop)
|
||||||
|
.drag_id(id);
|
||||||
|
|
||||||
|
nav_buttons
|
||||||
|
.apply(scrollable)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.apply(container)
|
||||||
|
.padding(space_xxs)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.style(theme::Container::custom(nav_bar_style))
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn nav_bar_style(theme: &Theme) -> iced_style::container::Appearance {
|
pub fn nav_bar_style(theme: &Theme) -> iced_style::container::Appearance {
|
||||||
let cosmic = &theme.cosmic();
|
let cosmic = &theme.cosmic();
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,14 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use super::model::{Entity, Model, Selectable};
|
use super::model::{Entity, Model, Selectable};
|
||||||
|
use crate::iced_core::id::Internal;
|
||||||
use crate::theme::{SegmentedButton as Style, THEME};
|
use crate::theme::{SegmentedButton as Style, THEME};
|
||||||
|
use crate::widget::dnd_destination::DragId;
|
||||||
use crate::widget::{icon, Icon};
|
use crate::widget::{icon, Icon};
|
||||||
use crate::{Element, Renderer};
|
use crate::{Element, Renderer};
|
||||||
use derive_setters::Setters;
|
use derive_setters::Setters;
|
||||||
|
use iced::clipboard::dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent};
|
||||||
|
use iced::clipboard::mime::AllowedMimeTypes;
|
||||||
use iced::{
|
use iced::{
|
||||||
alignment, event, keyboard, mouse, touch, Alignment, Background, Color, Command, Event, Length,
|
alignment, event, keyboard, mouse, touch, Alignment, Background, Color, Command, Event, Length,
|
||||||
Padding, Rectangle, Size,
|
Padding, Rectangle, Size,
|
||||||
|
|
@ -16,9 +20,11 @@ use iced_core::widget::{self, operation, tree};
|
||||||
use iced_core::{layout, renderer, widget::Tree, Clipboard, Layout, Shell, Widget};
|
use iced_core::{layout, renderer, widget::Tree, Clipboard, Layout, Shell, Widget};
|
||||||
use iced_core::{Border, Gradient, Point, Renderer as IcedRenderer, Shadow, Text};
|
use iced_core::{Border, Gradient, Point, Renderer as IcedRenderer, Shadow, Text};
|
||||||
use slotmap::{Key, SecondaryMap};
|
use slotmap::{Key, SecondaryMap};
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use std::collections::hash_map::DefaultHasher;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
use std::mem;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
/// A command that focuses a segmented item stored in a widget.
|
/// A command that focuses a segmented item stored in a widget.
|
||||||
|
|
@ -67,7 +73,7 @@ where
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
pub(super) model: &'a Model<SelectionMode>,
|
pub(super) model: &'a Model<SelectionMode>,
|
||||||
/// iced widget ID
|
/// iced widget ID
|
||||||
pub(super) id: Option<Id>,
|
pub(super) id: Id,
|
||||||
/// The icon used for the close button.
|
/// The icon used for the close button.
|
||||||
pub(super) close_icon: Icon,
|
pub(super) close_icon: Icon,
|
||||||
/// Scrolling switches focus between tabs.
|
/// Scrolling switches focus between tabs.
|
||||||
|
|
@ -118,6 +124,16 @@ where
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
pub(super) on_close: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
|
pub(super) on_close: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
|
pub(super) on_dnd_drop:
|
||||||
|
Option<Box<dyn Fn(Entity, Vec<u8>, String, DndAction) -> Message + 'static>>,
|
||||||
|
pub(super) mimes: Vec<String>,
|
||||||
|
#[setters(skip)]
|
||||||
|
pub(super) on_dnd_enter: Option<Box<dyn Fn(Entity, Vec<String>) -> Message + 'static>>,
|
||||||
|
#[setters(skip)]
|
||||||
|
pub(super) on_dnd_leave: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
|
||||||
|
#[setters(strip_option)]
|
||||||
|
pub(super) drag_id: Option<DragId>,
|
||||||
|
#[setters(skip)]
|
||||||
/// Defines the implementation of this struct
|
/// Defines the implementation of this struct
|
||||||
variant: PhantomData<Variant>,
|
variant: PhantomData<Variant>,
|
||||||
}
|
}
|
||||||
|
|
@ -131,7 +147,7 @@ where
|
||||||
pub fn new(model: &'a Model<SelectionMode>) -> Self {
|
pub fn new(model: &'a Model<SelectionMode>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
model,
|
model,
|
||||||
id: None,
|
id: Id::unique(),
|
||||||
close_icon: icon::from_name("window-close-symbolic").size(16).icon(),
|
close_icon: icon::from_name("window-close-symbolic").size(16).icon(),
|
||||||
scrollable_focus: false,
|
scrollable_focus: false,
|
||||||
show_close_icon_on_hover: false,
|
show_close_icon_on_hover: false,
|
||||||
|
|
@ -155,7 +171,12 @@ where
|
||||||
style: Style::default(),
|
style: Style::default(),
|
||||||
on_activate: None,
|
on_activate: None,
|
||||||
on_close: None,
|
on_close: None,
|
||||||
|
on_dnd_drop: None,
|
||||||
|
on_dnd_enter: None,
|
||||||
|
on_dnd_leave: None,
|
||||||
|
mimes: Vec::new(),
|
||||||
variant: PhantomData,
|
variant: PhantomData,
|
||||||
|
drag_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -182,6 +203,33 @@ where
|
||||||
self.model.items.get(key).map_or(false, |item| item.enabled)
|
self.model.items.get(key).map_or(false, |item| item.enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handle the dnd drop event.
|
||||||
|
pub fn on_dnd_drop<D: AllowedMimeTypes>(
|
||||||
|
mut self,
|
||||||
|
dnd_drop_handler: impl Fn(Entity, Option<D>, DndAction) -> Message + 'static,
|
||||||
|
) -> Self {
|
||||||
|
self.on_dnd_drop = Some(Box::new(move |entity, data, mime, action| {
|
||||||
|
dnd_drop_handler(entity, D::try_from((data, mime)).ok(), action)
|
||||||
|
}));
|
||||||
|
self.mimes = D::allowed().iter().map(|mime| mime.to_string()).collect();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle the dnd enter event.
|
||||||
|
pub fn on_dnd_enter(
|
||||||
|
mut self,
|
||||||
|
dnd_enter_handler: impl Fn(Entity, Vec<String>) -> Message + 'static,
|
||||||
|
) -> Self {
|
||||||
|
self.on_dnd_enter = Some(Box::new(dnd_enter_handler));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle the dnd leave event.
|
||||||
|
pub fn on_dnd_leave(mut self, dnd_leave_handler: impl Fn(Entity) -> Message + 'static) -> Self {
|
||||||
|
self.on_dnd_leave = Some(Box::new(dnd_leave_handler));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Item the previous item in the widget.
|
/// Item the previous item in the widget.
|
||||||
fn focus_previous(&mut self, state: &mut LocalState) -> event::Status {
|
fn focus_previous(&mut self, state: &mut LocalState) -> event::Status {
|
||||||
match state.focused_item {
|
match state.focused_item {
|
||||||
|
|
@ -416,6 +464,28 @@ where
|
||||||
|
|
||||||
fn button_is_hovered(&self, state: &LocalState, key: Entity) -> bool {
|
fn button_is_hovered(&self, state: &LocalState, key: Entity) -> bool {
|
||||||
self.on_activate.is_some() && state.hovered == Item::Tab(key)
|
self.on_activate.is_some() && state.hovered == Item::Tab(key)
|
||||||
|
|| state
|
||||||
|
.dnd_state
|
||||||
|
.drag_offer
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|id| id.data == key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the drag id of the destination.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if the destination has been assigned a Set id, which is invalid.
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_drag_id(&self) -> u128 {
|
||||||
|
self.drag_id.map_or_else(
|
||||||
|
|| {
|
||||||
|
u128::from(match &self.id.0 .0 {
|
||||||
|
Internal::Unique(id) | Internal::Custom(id, _) => *id,
|
||||||
|
Internal::Set(_) => panic!("Invalid Id assigned to dnd destination."),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|id| id.0,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -507,7 +577,7 @@ where
|
||||||
fn on_event(
|
fn on_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
mut event: Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: mouse::Cursor,
|
cursor_position: mouse::Cursor,
|
||||||
_renderer: &Renderer,
|
_renderer: &Renderer,
|
||||||
|
|
@ -519,6 +589,120 @@ where
|
||||||
let state = tree.state.downcast_mut::<LocalState>();
|
let state = tree.state.downcast_mut::<LocalState>();
|
||||||
state.hovered = Item::None;
|
state.hovered = Item::None;
|
||||||
|
|
||||||
|
let my_id = self.get_drag_id();
|
||||||
|
|
||||||
|
if let Event::Dnd(e) = &mut event {
|
||||||
|
let entity = state
|
||||||
|
.dnd_state
|
||||||
|
.drag_offer
|
||||||
|
.as_ref()
|
||||||
|
.map(|dnd_state| dnd_state.data);
|
||||||
|
match e {
|
||||||
|
DndEvent::Offer(
|
||||||
|
id,
|
||||||
|
OfferEvent::Enter {
|
||||||
|
x, y, mime_types, ..
|
||||||
|
},
|
||||||
|
) if Some(my_id) == *id => {
|
||||||
|
let entity = self
|
||||||
|
.variant_bounds(state, bounds)
|
||||||
|
.filter_map(|item| match item {
|
||||||
|
ItemBounds::Button(entity, bounds) => Some((entity, bounds)),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.find(|(_key, bounds)| bounds.contains(Point::new(*x as f32, *y as f32)))
|
||||||
|
.map(|(key, _)| key);
|
||||||
|
if let Some(entity) = entity {
|
||||||
|
let on_dnd_enter = self
|
||||||
|
.on_dnd_enter
|
||||||
|
.as_ref()
|
||||||
|
.map(|on_enter| |_, _, mime_types| on_enter(entity, mime_types));
|
||||||
|
|
||||||
|
_ = state.dnd_state.on_enter::<Message>(
|
||||||
|
*x,
|
||||||
|
*y,
|
||||||
|
mime_types.clone(),
|
||||||
|
on_dnd_enter,
|
||||||
|
entity,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DndEvent::Offer(id, OfferEvent::Leave | OfferEvent::LeaveDestination)
|
||||||
|
if Some(my_id) == *id =>
|
||||||
|
{
|
||||||
|
if let Some(entity) = entity {
|
||||||
|
if let Some(on_dnd_leave) = self.on_dnd_leave.as_ref() {
|
||||||
|
shell.publish(on_dnd_leave(entity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = state.dnd_state.on_leave::<Message>(None);
|
||||||
|
}
|
||||||
|
DndEvent::Offer(id, OfferEvent::Motion { x, y }) if Some(my_id) == *id => {
|
||||||
|
let new = self
|
||||||
|
.variant_bounds(state, bounds)
|
||||||
|
.filter_map(|item| match item {
|
||||||
|
ItemBounds::Button(entity, bounds) => Some((entity, bounds)),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.find(|(_key, bounds)| bounds.contains(Point::new(*x as f32, *y as f32)))
|
||||||
|
.map(|(key, _)| key);
|
||||||
|
if let Some(new_entity) = new {
|
||||||
|
state.dnd_state.on_motion::<Message>(
|
||||||
|
*x,
|
||||||
|
*y,
|
||||||
|
None::<fn(_, _) -> Message>,
|
||||||
|
None::<fn(_, _, _) -> Message>,
|
||||||
|
new_entity,
|
||||||
|
);
|
||||||
|
if Some(new_entity) != entity {
|
||||||
|
if let Some(on_dnd_enter) = self.on_dnd_enter.as_ref() {
|
||||||
|
shell.publish(on_dnd_enter(new_entity, Vec::new()));
|
||||||
|
}
|
||||||
|
if let Some(dnd) = state.dnd_state.drag_offer.as_mut() {
|
||||||
|
dnd.data = new_entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if entity.is_some() {
|
||||||
|
state.dnd_state.drag_offer = None;
|
||||||
|
if let Some(on_dnd_leave) = self.on_dnd_leave.as_ref() {
|
||||||
|
shell.publish(on_dnd_leave(entity.unwrap()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DndEvent::Offer(id, OfferEvent::Drop) if Some(my_id) == *id => {
|
||||||
|
_ = state
|
||||||
|
.dnd_state
|
||||||
|
.on_drop::<Message>(None::<fn(_, _) -> Message>);
|
||||||
|
}
|
||||||
|
DndEvent::Offer(id, OfferEvent::SelectedAction(action)) if Some(my_id) == *id => {
|
||||||
|
if let Some(entity) = entity {
|
||||||
|
_ = state
|
||||||
|
.dnd_state
|
||||||
|
.on_action_selected::<Message>(*action, None::<fn(_) -> Message>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DndEvent::Offer(id, OfferEvent::Data { data, mime_type }) if Some(my_id) == *id => {
|
||||||
|
if let Some(entity) = entity {
|
||||||
|
let on_drop = self.on_dnd_drop.as_ref();
|
||||||
|
let on_drop = on_drop.map(|on_drop| {
|
||||||
|
|mime, data, action, _, _| on_drop(entity, data, mime, action)
|
||||||
|
});
|
||||||
|
|
||||||
|
if let (Some(msg), ret) = state.dnd_state.on_data_received(
|
||||||
|
mem::take(mime_type),
|
||||||
|
mem::take(data),
|
||||||
|
None::<fn(_, _) -> Message>,
|
||||||
|
on_drop,
|
||||||
|
) {
|
||||||
|
shell.publish(msg);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if cursor_position.is_over(bounds) {
|
if cursor_position.is_over(bounds) {
|
||||||
// Check for clicks on the previous and next tab buttons, when tabs are collapsed.
|
// Check for clicks on the previous and next tab buttons, when tabs are collapsed.
|
||||||
if state.collapsed {
|
if state.collapsed {
|
||||||
|
|
@ -730,7 +914,7 @@ where
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
let state = tree.state.downcast_mut::<LocalState>();
|
let state = tree.state.downcast_mut::<LocalState>();
|
||||||
operation.focusable(state, self.id.as_ref().map(|id| &id.0));
|
operation.focusable(state, Some(&self.id.0));
|
||||||
|
|
||||||
if let Item::Set = state.focused_item {
|
if let Item::Set = state.focused_item {
|
||||||
if self.prev_tab_sensitive(state) {
|
if self.prev_tab_sensitive(state) {
|
||||||
|
|
@ -1173,6 +1357,30 @@ where
|
||||||
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn drag_destinations(
|
||||||
|
&self,
|
||||||
|
_state: &Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
||||||
|
) {
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
|
let my_id = self.get_drag_id();
|
||||||
|
let dnd_rect = DndDestinationRectangle {
|
||||||
|
id: my_id,
|
||||||
|
rectangle: dnd::Rectangle {
|
||||||
|
x: f64::from(bounds.x),
|
||||||
|
y: f64::from(bounds.y),
|
||||||
|
width: f64::from(bounds.width),
|
||||||
|
height: f64::from(bounds.height),
|
||||||
|
},
|
||||||
|
mime_types: self.mimes.clone().into_iter().map(Cow::Owned).collect(),
|
||||||
|
actions: DndAction::Copy | DndAction::Move,
|
||||||
|
preferred: DndAction::Move,
|
||||||
|
};
|
||||||
|
dnd_rectangles.push(dnd_rect);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Variant, SelectionMode, Message> From<SegmentedButton<'a, Variant, SelectionMode, Message>>
|
impl<'a, Variant, SelectionMode, Message> From<SegmentedButton<'a, Variant, SelectionMode, Message>>
|
||||||
|
|
@ -1218,6 +1426,8 @@ pub struct LocalState {
|
||||||
text_hashes: SecondaryMap<Entity, u64>,
|
text_hashes: SecondaryMap<Entity, u64>,
|
||||||
/// Time since last tab activation from wheel movements.
|
/// Time since last tab activation from wheel movements.
|
||||||
wheel_timestamp: Option<Instant>,
|
wheel_timestamp: Option<Instant>,
|
||||||
|
/// Dnd state
|
||||||
|
pub dnd_state: crate::widget::dnd_destination::State<Entity>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq)]
|
#[derive(Debug, Default, PartialEq)]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue