feat: support external DnD drops on segmented_button nav_bar
Enable drag-and-drop from file list to sidebar nav_bar for adding favorites. Three changes: 1. dnd_source: Don't clear press position when drag_content is not yet available. This allows single-motion click-hold-drag by preserving the press tracking across view rebuilds. 2. segmented_button: Accept DnD offers regardless of Wayland offer ID when on_dnd_drop is configured. The compositor may assign a different ID than the registered DragId for external DnD sources. 3. segmented_button: Show drop hint indicator for external DnD operations, not just internal tab reorder. This provides visual feedback showing where the dropped item will be inserted. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9aa87cd66b
commit
fa7bfba6c7
2 changed files with 25 additions and 20 deletions
|
|
@ -256,9 +256,7 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
|
|||
}
|
||||
mouse::Event::CursorMoved { .. } => {
|
||||
if let Some(position) = cursor.position() {
|
||||
// We ignore motion if we do not possess drag content by now.
|
||||
if self.drag_content.is_none() {
|
||||
state.left_pressed_position = None;
|
||||
return;
|
||||
}
|
||||
if let Some(left_pressed_position) = state.left_pressed_position
|
||||
|
|
|
|||
|
|
@ -771,7 +771,10 @@ where
|
|||
bounds: Rectangle,
|
||||
cursor: Point,
|
||||
) -> Option<DropHint> {
|
||||
let _ = state.dragging_tab?;
|
||||
// Allow drop hints for both internal tab reorder and external DnD
|
||||
if state.dragging_tab.is_none() && state.dnd_state.drag_offer.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.variant_bounds(state, bounds)
|
||||
.filter_map(|item| match item {
|
||||
|
|
@ -983,18 +986,20 @@ where
|
|||
|
||||
let my_id = self.get_drag_id();
|
||||
|
||||
if let Event::Dnd(e) = &mut event {
|
||||
if let Event::Dnd(e) = &event {
|
||||
// When on_dnd_drop is configured (external DnD, not tab reorder),
|
||||
// accept offers regardless of ID, as the Wayland compositor may
|
||||
// assign a different ID than our registered DragId.
|
||||
let id_matches = |id: &Option<u128>| -> bool {
|
||||
Some(my_id) == *id || (self.on_dnd_drop.is_some() && id.is_some())
|
||||
};
|
||||
let _ = id_matches; // used below in match arms
|
||||
|
||||
let entity = state
|
||||
.dnd_state
|
||||
.drag_offer
|
||||
.as_ref()
|
||||
.map(|dnd_state| dnd_state.data);
|
||||
log::trace!(
|
||||
target: TAB_REORDER_LOG_TARGET,
|
||||
"segmented button {:?} received DnD event: {:?} entity={entity:?}",
|
||||
my_id,
|
||||
e
|
||||
);
|
||||
match e {
|
||||
DndEvent::Source(SourceEvent::Cancelled | SourceEvent::Finished) => {
|
||||
if state.dragging_tab.take().is_some() {
|
||||
|
|
@ -1015,13 +1020,15 @@ where
|
|||
OfferEvent::Enter {
|
||||
x, y, mime_types, ..
|
||||
},
|
||||
) if Some(my_id) == *id => {
|
||||
let entity = self
|
||||
) if id_matches(id) => {
|
||||
let buttons: Vec<_> = self
|
||||
.variant_bounds(state, my_bounds)
|
||||
.filter_map(|item| match item {
|
||||
ItemBounds::Button(entity, bounds) => Some((entity, bounds)),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
let entity = buttons.into_iter()
|
||||
.find(|(_key, bounds)| bounds.contains(Point::new(*x as f32, *y as f32)))
|
||||
.map(|(key, _)| key);
|
||||
state.drop_hint = self.drop_hint_for_position(
|
||||
|
|
@ -1060,10 +1067,10 @@ where
|
|||
.dnd_state
|
||||
.on_enter::<Message>(*x, *y, mimes, on_dnd_enter, entity);
|
||||
}
|
||||
DndEvent::Offer(id, OfferEvent::LeaveDestination) if Some(my_id) != *id => {}
|
||||
DndEvent::Offer(id, OfferEvent::LeaveDestination) if !id_matches(id) => {}
|
||||
DndEvent::Offer(id, leave)
|
||||
if matches!(leave, OfferEvent::Leave | OfferEvent::LeaveDestination)
|
||||
&& Some(my_id) == *id =>
|
||||
&& id_matches(id) =>
|
||||
{
|
||||
state.drop_hint = None;
|
||||
self.emit_drop_hint(shell, state.drop_hint);
|
||||
|
|
@ -1082,7 +1089,7 @@ where
|
|||
}
|
||||
_ = state.dnd_state.on_leave::<Message>(None);
|
||||
}
|
||||
DndEvent::Offer(id, OfferEvent::Motion { x, y }) if Some(my_id) == *id => {
|
||||
DndEvent::Offer(id, OfferEvent::Motion { x, y }) if id_matches(id) => {
|
||||
log::trace!(
|
||||
target: TAB_REORDER_LOG_TARGET,
|
||||
"offer motion id={my_id:?} cursor=({x},{y}) current_entity={entity:?}"
|
||||
|
|
@ -1154,7 +1161,7 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
DndEvent::Offer(id, OfferEvent::Drop) if Some(my_id) == *id => {
|
||||
DndEvent::Offer(id, OfferEvent::Drop) if id_matches(id) => {
|
||||
log::trace!(
|
||||
target: TAB_REORDER_LOG_TARGET,
|
||||
"offer drop id={my_id:?} entity={entity:?}"
|
||||
|
|
@ -1163,7 +1170,7 @@ where
|
|||
.dnd_state
|
||||
.on_drop::<Message>(None::<fn(_, _) -> Message>);
|
||||
}
|
||||
DndEvent::Offer(id, OfferEvent::SelectedAction(action)) if Some(my_id) == *id => {
|
||||
DndEvent::Offer(id, OfferEvent::SelectedAction(action)) if id_matches(id) => {
|
||||
if state.dnd_state.drag_offer.is_some() {
|
||||
log::trace!(
|
||||
target: TAB_REORDER_LOG_TARGET,
|
||||
|
|
@ -1174,7 +1181,7 @@ where
|
|||
.on_action_selected::<Message>(*action, None::<fn(_) -> Message>);
|
||||
}
|
||||
}
|
||||
DndEvent::Offer(id, OfferEvent::Data { data, mime_type }) if Some(my_id) == *id => {
|
||||
DndEvent::Offer(id, OfferEvent::Data { data, mime_type }) if id_matches(id) => {
|
||||
log::trace!(
|
||||
target: TAB_REORDER_LOG_TARGET,
|
||||
"offer data id={my_id:?} entity={entity:?} mime={mime_type:?}"
|
||||
|
|
@ -1662,13 +1669,13 @@ where
|
|||
let appearance = Self::variant_appearance(theme, &self.style);
|
||||
let bounds: Rectangle = layout.bounds();
|
||||
let button_amount = self.model.items.len();
|
||||
let show_drop_hint = state.dragging_tab.is_some();
|
||||
let show_drop_hint = state.dragging_tab.is_some()
|
||||
|| state.dnd_state.drag_offer.is_some();
|
||||
let drop_hint = if show_drop_hint {
|
||||
state.drop_hint
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Draw the background, if a background was defined.
|
||||
if let Some(background) = appearance.background {
|
||||
renderer.fill_quad(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue