fix(segmented button): tab dnd
This commit is contained in:
parent
689f25be53
commit
d71c42102d
1 changed files with 75 additions and 42 deletions
|
|
@ -297,11 +297,8 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enable drag-and-drop support for tabs using the provided payload builder.
|
/// Enable drag-and-drop support for tabs using the provided payload builder.
|
||||||
pub fn enable_tab_drag(
|
pub fn enable_tab_drag(mut self, mime: String) -> Self {
|
||||||
mut self,
|
self.tab_drag = Some(TabDragSource::new(mime));
|
||||||
payload: impl Fn(Entity) -> Option<(String, Vec<u8>)> + 'static,
|
|
||||||
) -> Self {
|
|
||||||
self.tab_drag = Some(TabDragSource::new(payload));
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -664,28 +661,29 @@ where
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
cursor: Point,
|
cursor: Point,
|
||||||
) -> Option<DropHint> {
|
) -> Option<DropHint> {
|
||||||
let dragging = state.dragging_tab?;
|
let _ = state.dragging_tab?;
|
||||||
|
|
||||||
self.variant_bounds(state, bounds)
|
self.variant_bounds(state, bounds)
|
||||||
.filter_map(|item| match item {
|
.filter_map(|item| match item {
|
||||||
ItemBounds::Button(entity, rect) if rect.contains(cursor) => Some((entity, rect)),
|
ItemBounds::Button(entity, rect) if rect.contains(cursor) => Some((entity, rect)),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.find_map(|(entity, rect)| {
|
.map(|(entity, rect)| {
|
||||||
let before = if Self::VERTICAL {
|
let before = if Self::VERTICAL {
|
||||||
cursor.y < rect.center_y()
|
cursor.y < rect.center_y()
|
||||||
} else {
|
} else {
|
||||||
cursor.x < rect.center_x()
|
cursor.x < rect.center_x()
|
||||||
};
|
};
|
||||||
Some(DropHint {
|
DropHint {
|
||||||
entity,
|
entity,
|
||||||
side: if before {
|
side: if before {
|
||||||
DropSide::Before
|
DropSide::Before
|
||||||
} else {
|
} else {
|
||||||
DropSide::After
|
DropSide::After
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
})
|
})
|
||||||
|
.next()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_tab_drag(
|
fn start_tab_drag(
|
||||||
|
|
@ -713,33 +711,24 @@ where
|
||||||
tab_drag.threshold
|
tab_drag.threshold
|
||||||
);
|
);
|
||||||
|
|
||||||
let Some((mime, data)) = (tab_drag.payload)(entity) else {
|
let data_len = 0;
|
||||||
log::trace!(
|
|
||||||
target: TAB_REORDER_LOG_TARGET,
|
|
||||||
"start_tab_drag aborted entity={:?}: payload builder returned None",
|
|
||||||
entity
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
let data_len = data.len();
|
|
||||||
let mime_label = mime.clone();
|
|
||||||
|
|
||||||
iced_core::clipboard::start_dnd::<crate::Theme, crate::Renderer>(
|
iced_core::clipboard::start_dnd::<crate::Theme, crate::Renderer>(
|
||||||
clipboard,
|
clipboard,
|
||||||
false,
|
false,
|
||||||
Some(iced_core::clipboard::DndSource::Widget(self.id.0.clone())),
|
Some(iced_core::clipboard::DndSource::Widget(self.id.0.clone())),
|
||||||
None,
|
None,
|
||||||
Box::new(SimpleDragData::new(mime, data)),
|
Box::new(SimpleDragData::new(tab_drag.mime.clone(), vec![1])),
|
||||||
DndAction::Move,
|
DndAction::Move,
|
||||||
);
|
);
|
||||||
log::trace!(
|
log::trace!(
|
||||||
target: TAB_REORDER_LOG_TARGET,
|
target: TAB_REORDER_LOG_TARGET,
|
||||||
"tab drag started entity={:?} mime={} bytes={}",
|
"tab drag started entity={:?} mime={} bytes={}",
|
||||||
entity,
|
entity,
|
||||||
mime_label,
|
tab_drag.mime,
|
||||||
data_len
|
data_len
|
||||||
);
|
);
|
||||||
|
|
||||||
state.dragging_tab = Some(entity);
|
state.dragging_tab = Some(entity);
|
||||||
state.tab_drag_candidate = None;
|
state.tab_drag_candidate = None;
|
||||||
state.pressed_item = None;
|
state.pressed_item = None;
|
||||||
|
|
@ -815,6 +804,7 @@ where
|
||||||
tab_drag_candidate: None,
|
tab_drag_candidate: None,
|
||||||
dragging_tab: None,
|
dragging_tab: None,
|
||||||
drop_hint: None,
|
drop_hint: None,
|
||||||
|
offer_mimes: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -966,26 +956,29 @@ where
|
||||||
"offer enter id={my_id:?} entity={entity:?} @ ({x},{y}) mimes={mime_types:?}"
|
"offer enter id={my_id:?} entity={entity:?} @ ({x},{y}) mimes={mime_types:?}"
|
||||||
);
|
);
|
||||||
|
|
||||||
let on_dnd_enter =
|
let on_dnd_enter = self
|
||||||
self.on_dnd_enter
|
.on_dnd_enter
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.zip(entity)
|
.zip(entity)
|
||||||
.map(|(on_enter, entity)| {
|
.map(|(on_enter, entity)| move |_, _, mimes| on_enter(entity, mimes));
|
||||||
move |_, _, mime_types| on_enter(entity, mime_types)
|
let mimes = if let Some(mime) = self.tab_drag.as_ref().map(|d| &d.mime)
|
||||||
});
|
&& mime_types.is_empty()
|
||||||
|
{
|
||||||
|
vec![mime.clone()]
|
||||||
|
} else {
|
||||||
|
mime_types.clone()
|
||||||
|
};
|
||||||
|
state.offer_mimes = mimes.clone();
|
||||||
|
|
||||||
_ = state.dnd_state.on_enter::<Message>(
|
_ = state
|
||||||
*x,
|
.dnd_state
|
||||||
*y,
|
.on_enter::<Message>(*x, *y, mimes, on_dnd_enter, entity);
|
||||||
mime_types.clone(),
|
|
||||||
on_dnd_enter,
|
|
||||||
entity,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
DndEvent::Offer(id, OfferEvent::LeaveDestination) if Some(my_id) != *id => {}
|
DndEvent::Offer(id, OfferEvent::LeaveDestination) if Some(my_id) != *id => {}
|
||||||
DndEvent::Offer(id, OfferEvent::Leave | OfferEvent::LeaveDestination)
|
DndEvent::Offer(id, OfferEvent::Leave | OfferEvent::LeaveDestination)
|
||||||
if Some(my_id) == *id =>
|
if Some(my_id) == *id =>
|
||||||
{
|
{
|
||||||
|
state.dragging_tab = None;
|
||||||
state.drop_hint = None;
|
state.drop_hint = None;
|
||||||
self.emit_drop_hint(shell, state.drop_hint);
|
self.emit_drop_hint(shell, state.drop_hint);
|
||||||
if let Some(Some(entity)) = entity {
|
if let Some(Some(entity)) = entity {
|
||||||
|
|
@ -999,7 +992,6 @@ where
|
||||||
);
|
);
|
||||||
_ = state.dnd_state.on_leave::<Message>(None);
|
_ = state.dnd_state.on_leave::<Message>(None);
|
||||||
}
|
}
|
||||||
DndEvent::Offer(_, OfferEvent::Leave | OfferEvent::LeaveDestination) => {}
|
|
||||||
DndEvent::Offer(id, OfferEvent::Motion { x, y }) if Some(my_id) == *id => {
|
DndEvent::Offer(id, OfferEvent::Motion { x, y }) if Some(my_id) == *id => {
|
||||||
log::trace!(
|
log::trace!(
|
||||||
target: TAB_REORDER_LOG_TARGET,
|
target: TAB_REORDER_LOG_TARGET,
|
||||||
|
|
@ -1034,7 +1026,7 @@ where
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|dnd| dnd.selected_action);
|
.map(|dnd| dnd.selected_action);
|
||||||
if let Some(on_dnd_enter) = self.on_dnd_enter.as_ref() {
|
if let Some(on_dnd_enter) = self.on_dnd_enter.as_ref() {
|
||||||
shell.publish(on_dnd_enter(new_entity, Vec::new()));
|
shell.publish(on_dnd_enter(new_entity, state.offer_mimes.clone()));
|
||||||
}
|
}
|
||||||
if let Some(dnd) = state.dnd_state.drag_offer.as_mut() {
|
if let Some(dnd) = state.dnd_state.drag_offer.as_mut() {
|
||||||
dnd.data = Some(new_entity);
|
dnd.data = Some(new_entity);
|
||||||
|
|
@ -1097,7 +1089,11 @@ where
|
||||||
.drag_offer
|
.drag_offer
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|offer| offer.selected_action.contains(DndAction::Move));
|
.is_some_and(|offer| offer.selected_action.contains(DndAction::Move));
|
||||||
let pending_reorder = if allow_reorder && self.on_reorder.is_some() {
|
let pending_reorder = if allow_reorder
|
||||||
|
&& self.on_reorder.is_some()
|
||||||
|
&& self.tab_drag.as_ref().is_some_and(|d| d.mime == *mime_type)
|
||||||
|
&& state.dragging_tab.is_some()
|
||||||
|
{
|
||||||
drop_entity.and_then(|target| self.reorder_event_for_drop(state, target))
|
drop_entity.and_then(|target| self.reorder_event_for_drop(state, target))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -1122,6 +1118,8 @@ where
|
||||||
shell.publish(msg);
|
shell.publish(msg);
|
||||||
}
|
}
|
||||||
state.drop_hint = None;
|
state.drop_hint = None;
|
||||||
|
state.dragging_tab = None;
|
||||||
|
|
||||||
self.emit_drop_hint(shell, state.drop_hint);
|
self.emit_drop_hint(shell, state.drop_hint);
|
||||||
if let Some(event) = pending_reorder {
|
if let Some(event) = pending_reorder {
|
||||||
if let Some(on_reorder) = self.on_reorder.as_ref() {
|
if let Some(on_reorder) = self.on_reorder.as_ref() {
|
||||||
|
|
@ -1135,6 +1133,8 @@ where
|
||||||
"data received without entity id={my_id:?}"
|
"data received without entity id={my_id:?}"
|
||||||
);
|
);
|
||||||
state.drop_hint = None;
|
state.drop_hint = None;
|
||||||
|
state.dragging_tab = None;
|
||||||
|
|
||||||
self.emit_drop_hint(shell, state.drop_hint);
|
self.emit_drop_hint(shell, state.drop_hint);
|
||||||
if let Some(event) = pending_reorder {
|
if let Some(event) = pending_reorder {
|
||||||
if let Some(on_reorder) = self.on_reorder.as_ref() {
|
if let Some(on_reorder) = self.on_reorder.as_ref() {
|
||||||
|
|
@ -2118,6 +2118,36 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(mime) = self.tab_drag.as_ref().map(|d| &d.mime) {
|
||||||
|
for item in self.variant_bounds(local_state, layout.bounds()) {
|
||||||
|
if let ItemBounds::Button(_entity, rect) = item {
|
||||||
|
pushed = true;
|
||||||
|
log::trace!(
|
||||||
|
target: TAB_REORDER_LOG_TARGET,
|
||||||
|
"register drag destination id={:?} bounds=({:.2},{:.2},{:.2},{:.2}) mimes={:?}",
|
||||||
|
my_id,
|
||||||
|
rect.x,
|
||||||
|
rect.y,
|
||||||
|
rect.width,
|
||||||
|
rect.height,
|
||||||
|
mime
|
||||||
|
);
|
||||||
|
dnd_rectangles.push(DndDestinationRectangle {
|
||||||
|
id: my_id,
|
||||||
|
rectangle: dnd::Rectangle {
|
||||||
|
x: f64::from(rect.x),
|
||||||
|
y: f64::from(rect.y),
|
||||||
|
width: f64::from(rect.width),
|
||||||
|
height: f64::from(rect.height),
|
||||||
|
},
|
||||||
|
mime_types: vec![Cow::Owned(mime.clone())],
|
||||||
|
actions: DndAction::Copy | DndAction::Move,
|
||||||
|
preferred: DndAction::Move,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !pushed {
|
if !pushed {
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
log::trace!(
|
log::trace!(
|
||||||
|
|
@ -2165,15 +2195,15 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TabDragSource<Message> {
|
struct TabDragSource<Message> {
|
||||||
payload: Box<dyn Fn(Entity) -> Option<(String, Vec<u8>)>>,
|
mime: String,
|
||||||
threshold: f32,
|
threshold: f32,
|
||||||
_marker: PhantomData<Message>,
|
_marker: PhantomData<Message>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message> TabDragSource<Message> {
|
impl<Message> TabDragSource<Message> {
|
||||||
fn new(payload: impl Fn(Entity) -> Option<(String, Vec<u8>)> + 'static) -> Self {
|
fn new(mime: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
payload: Box::new(payload),
|
mime,
|
||||||
threshold: 8.0,
|
threshold: 8.0,
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
}
|
}
|
||||||
|
|
@ -2254,6 +2284,8 @@ pub struct LocalState {
|
||||||
wheel_timestamp: Option<Instant>,
|
wheel_timestamp: Option<Instant>,
|
||||||
/// Dnd state
|
/// Dnd state
|
||||||
pub dnd_state: crate::widget::dnd_destination::State<Option<Entity>>,
|
pub dnd_state: crate::widget::dnd_destination::State<Option<Entity>>,
|
||||||
|
/// Dnd state
|
||||||
|
pub offer_mimes: Vec<String>,
|
||||||
/// Tracks multi-touch events
|
/// Tracks multi-touch events
|
||||||
fingers_pressed: HashSet<Finger>,
|
fingers_pressed: HashSet<Finger>,
|
||||||
/// The currently pressed item
|
/// The currently pressed item
|
||||||
|
|
@ -2391,6 +2423,7 @@ mod tests {
|
||||||
tab_drag_candidate: None,
|
tab_drag_candidate: None,
|
||||||
dragging_tab: Some(dragging),
|
dragging_tab: Some(dragging),
|
||||||
drop_hint: None,
|
drop_hint: None,
|
||||||
|
offer_mimes: Vec::new(),
|
||||||
};
|
};
|
||||||
state.buttons_visible = len;
|
state.buttons_visible = len;
|
||||||
state.known_length = len;
|
state.known_length = len;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue