diff --git a/Cargo.toml b/Cargo.toml index 927444e8..b6a01e32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -122,7 +122,6 @@ image = { version = "0.25.8", default-features = false, features = [ "png", ] } libc = { version = "0.2.175", optional = true } -log = "0.4" mime = { version = "0.3.17", optional = true } palette = "0.7.6" raw-window-handle = "0.6" diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index c943d2c7..ccc0fb18 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -39,7 +39,6 @@ pub fn dnd_destination_for_data<'a, T: AllowedMimeTypes, Message: 'static>( } static DRAG_ID_COUNTER: AtomicU64 = AtomicU64::new(0); -const DND_DEST_LOG_TARGET: &str = "libcosmic::widget::dnd_destination"; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct DragId(pub u128); @@ -76,12 +75,6 @@ pub struct DndDestination<'a, Message> { } impl<'a, Message: 'static> DndDestination<'a, Message> { - fn mime_matches(&self, offered: &[String]) -> bool { - self.mime_types.is_empty() - || offered - .iter() - .any(|mime| self.mime_types.iter().any(|allowed| allowed == mime)) - } pub fn new(child: impl Into>, mimes: Vec>) -> Self { Self { id: Id::unique(), @@ -331,12 +324,6 @@ impl Widget let my_id = self.get_drag_id(); - log::trace!( - target: DND_DEST_LOG_TARGET, - "dnd_destination id={:?}: event {:?}", - self.drag_id.unwrap_or_default(), - event - ); match event { Event::Dnd(DndEvent::Offer( id, @@ -344,18 +331,6 @@ impl Widget x, y, mime_types, .. }, )) if id == Some(my_id) => { - if !self.mime_matches(&mime_types) { - log::trace!( - target: DND_DEST_LOG_TARGET, - "offer enter id={my_id:?} ignored (mimes={mime_types:?} not in {:?})", - self.mime_types - ); - return event::Status::Ignored; - } - log::trace!( - target: DND_DEST_LOG_TARGET, - "offer enter id={my_id:?} coords=({x},{y}) mimes={mime_types:?}" - ); if let Some(msg) = state.on_enter( x, y, @@ -385,11 +360,6 @@ impl Widget return event::Status::Captured; } Event::Dnd(DndEvent::Offer(_, OfferEvent::Leave)) => { - log::trace!( - target: DND_DEST_LOG_TARGET, - "offer leave id={:?}", - my_id - ); if let Some(msg) = state.on_leave(self.on_leave.as_ref().map(std::convert::AsRef::as_ref)) { @@ -413,10 +383,6 @@ impl Widget return event::Status::Ignored; } Event::Dnd(DndEvent::Offer(id, OfferEvent::Motion { x, y })) if id == Some(my_id) => { - log::trace!( - target: DND_DEST_LOG_TARGET, - "offer motion id={my_id:?} coords=({x},{y})" - ); if let Some(msg) = state.on_motion( x, y, @@ -447,11 +413,6 @@ impl Widget return event::Status::Captured; } Event::Dnd(DndEvent::Offer(_, OfferEvent::LeaveDestination)) => { - log::trace!( - target: DND_DEST_LOG_TARGET, - "offer leave-destination id={:?}", - my_id - ); if let Some(msg) = state.on_leave(self.on_leave.as_ref().map(std::convert::AsRef::as_ref)) { @@ -460,10 +421,6 @@ impl Widget return event::Status::Ignored; } Event::Dnd(DndEvent::Offer(id, OfferEvent::Drop)) if id == Some(my_id) => { - log::trace!( - target: DND_DEST_LOG_TARGET, - "offer drop id={my_id:?}" - ); if let Some(msg) = state.on_drop(self.on_drop.as_ref().map(std::convert::AsRef::as_ref)) { @@ -474,10 +431,6 @@ impl Widget Event::Dnd(DndEvent::Offer(id, OfferEvent::SelectedAction(action))) if id == Some(my_id) => { - log::trace!( - target: DND_DEST_LOG_TARGET, - "offer selected-action id={my_id:?} action={action:?}" - ); if let Some(msg) = state.on_action_selected( action, self.on_action_selected @@ -491,11 +444,6 @@ impl Widget Event::Dnd(DndEvent::Offer(id, OfferEvent::Data { data, mime_type })) if id == Some(my_id) => { - log::trace!( - target: DND_DEST_LOG_TARGET, - "offer data id={my_id:?} mime={mime_type:?} bytes={}", - data.len() - ); if let (Some(msg), ret) = state.on_data_received( mime_type, data, @@ -573,16 +521,6 @@ impl Widget ) { let bounds = layout.bounds(); let my_id = self.get_drag_id(); - log::trace!( - target: DND_DEST_LOG_TARGET, - "register destination id={:?} bounds=({:.2},{:.2},{:.2},{:.2}) mimes={:?}", - my_id, - bounds.x, - bounds.y, - bounds.width, - bounds.height, - self.mime_types - ); let my_dest = DndDestinationRectangle { id: my_id, rectangle: dnd::Rectangle { @@ -597,14 +535,12 @@ impl Widget }; dnd_rectangles.push(my_dest); - if let Some(child_layout) = layout.children().next() { - self.container.as_widget().drag_destinations( - &state.children[0], - child_layout.with_virtual_offset(layout.virtual_offset()), - renderer, - dnd_rectangles, - ); - } + self.container.as_widget().drag_destinations( + &state.children[0], + layout, + renderer, + dnd_rectangles, + ); } fn id(&self) -> Option { @@ -760,71 +696,3 @@ impl<'a, Message: 'static> From> for Element<'a, Mes Element::new(wrapper) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Clone, Copy, Debug, PartialEq)] - enum TestMsg { - Data, - Finished, - } - - #[test] - fn data_before_drop_invokes_data_handler_only() { - let mut state: State<()> = State::new(); - assert!(state.drag_offer.is_none()); - state.on_enter::( - 4.0, - 2.0, - vec!["text/plain".into()], - Option:: TestMsg>::None, - (), - ); - let (message, status) = state.on_data_received( - "text/plain".into(), - vec![1], - Some(|mime, data| { - assert_eq!(mime, "text/plain"); - assert_eq!(data, vec![1]); - TestMsg::Data - }), - Option:: TestMsg>::None, - ); - assert!(matches!(message, Some(TestMsg::Data))); - assert_eq!(status, event::Status::Captured); - assert!(state.drag_offer.is_some()); - } - - #[test] - fn finish_only_emits_after_drop() { - let mut state: State<()> = State::new(); - state.on_enter::( - 5.0, - -1.0, - vec![], - Option:: TestMsg>::None, - (), - ); - state.on_action_selected::(DndAction::Move, Option:: TestMsg>::None); - state.on_drop::(Option:: TestMsg>::None); - - let (message, status) = state.on_data_received( - "application/x-test".into(), - vec![7], - Option:: TestMsg>::None, - Some(|mime, data, action, x, y| { - assert_eq!(mime, "application/x-test"); - assert_eq!(data, vec![7]); - assert_eq!(action, DndAction::Move); - assert_eq!(x, 5.0); - assert_eq!(y, -1.0); - TestMsg::Finished - }), - ); - assert!(matches!(message, Some(TestMsg::Finished))); - assert_eq!(status, event::Status::Captured); - assert!(state.drag_offer.is_none()); - } -} diff --git a/src/widget/segmented_button/mod.rs b/src/widget/segmented_button/mod.rs index 81c71be8..e609d70b 100644 --- a/src/widget/segmented_button/mod.rs +++ b/src/widget/segmented_button/mod.rs @@ -88,19 +88,6 @@ pub use self::style::{Appearance, ItemAppearance, ItemStatusAppearance, StyleShe pub use self::vertical::{VerticalSegmentedButton, vertical}; pub use self::widget::{Id, SegmentedButton, SegmentedVariant, focus}; -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum InsertPosition { - Before, - After, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct ReorderEvent { - pub dragged: Entity, - pub target: Entity, - pub position: InsertPosition, -} - /// Associates extra data with an external secondary map. /// /// The secondary map internally uses a `Vec`, so should only be used for data that diff --git a/src/widget/segmented_button/model/mod.rs b/src/widget/segmented_button/model/mod.rs index e0dd8c54..6b5a8a64 100644 --- a/src/widget/segmented_button/model/mod.rs +++ b/src/widget/segmented_button/model/mod.rs @@ -11,7 +11,6 @@ mod selection; pub use self::selection::{MultiSelect, Selectable, SingleSelect}; use crate::widget::Icon; -use crate::widget::segmented_button::InsertPosition; use slotmap::{SecondaryMap, SlotMap}; use std::any::{Any, TypeId}; use std::borrow::Cow; @@ -411,36 +410,6 @@ where true } - /// Reorder `dragged` relative to `target` based on the provided position. - /// - /// Returns `true` if the model changed, or `false` if the move was invalid. - pub fn reorder(&mut self, dragged: Entity, target: Entity, position: InsertPosition) -> bool { - if !self.contains_item(dragged) || !self.contains_item(target) || dragged == target { - return false; - } - - let len = self.iter().count(); - let target_pos = self.position(target).map(|pos| pos as usize).unwrap_or(len); - let from_pos = self - .position(dragged) - .map(|pos| pos as usize) - .unwrap_or(target_pos); - let mut insert_pos = match position { - InsertPosition::Before => target_pos, - InsertPosition::After => target_pos.saturating_add(1), - }; - if from_pos < insert_pos { - insert_pos = insert_pos.saturating_sub(1); - } - if len > 0 { - insert_pos = insert_pos.min(len.saturating_sub(1)); - } - - self.position_set(dragged, insert_pos as u16); - self.activate(dragged); - true - } - /// Removes an item from the model. /// /// The generation of the slot for the ID will be incremented, so this ID will no @@ -500,43 +469,3 @@ where self.text.remove(id) } } - -#[cfg(test)] -mod tests { - use super::*; - - fn sample_model() -> (Model, Vec) { - let mut ids = Vec::new(); - let model = Model::builder() - .insert(|b| b.text("Tab1").with_id(|id| ids.push(id))) - .insert(|b| b.text("Tab2").with_id(|id| ids.push(id))) - .insert(|b| b.text("Tab3").with_id(|id| ids.push(id))) - .insert(|b| b.text("Tab4").with_id(|id| ids.push(id))) - .build(); - (model, ids) - } - - fn order_of(model: &Model) -> Vec { - model.iter().collect() - } - - #[test] - fn reorder_inserts_before_target() { - let (mut model, ids) = sample_model(); - assert!(model.reorder(ids[3], ids[1], InsertPosition::Before)); - assert_eq!(order_of(&model), vec![ids[0], ids[3], ids[1], ids[2]]); - } - - #[test] - fn reorder_inserts_after_target() { - let (mut model, ids) = sample_model(); - assert!(model.reorder(ids[0], ids[2], InsertPosition::After)); - assert_eq!(order_of(&model), vec![ids[1], ids[2], ids[0], ids[3]]); - } - - #[test] - fn reorder_rejects_invalid_entities() { - let (mut model, ids) = sample_model(); - assert!(!model.reorder(ids[0], ids[0], InsertPosition::After)); - } -} diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 72bc7580..ff614840 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: MPL-2.0 use super::model::{Entity, Model, Selectable}; -use super::{InsertPosition, ReorderEvent}; use crate::iced_core::id::Internal; use crate::theme::{SegmentedButton as Style, THEME}; use crate::widget::dnd_destination::DragId; @@ -13,9 +12,7 @@ use crate::widget::menu::{ use crate::widget::{Icon, icon}; use crate::{Element, Renderer}; use derive_setters::Setters; -use iced::clipboard::dnd::{ - self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent, SourceEvent, -}; +use iced::clipboard::dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent}; use iced::clipboard::mime::AllowedMimeTypes; use iced::touch::Finger; use iced::{ @@ -44,8 +41,6 @@ thread_local! { static LAST_FOCUS_UPDATE: LazyCell> = LazyCell::new(|| Cell::new(Instant::now())); } -const TAB_REORDER_LOG_TARGET: &str = "libcosmic::widget::tab_reorder"; - /// A command that focuses a segmented item stored in a widget. pub fn focus(id: Id) -> Task { task::effect(Action::Widget(Box::new(operation::focusable::focus(id.0)))) @@ -56,27 +51,6 @@ pub enum ItemBounds { Divider(Rectangle, bool), } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum DropSide { - Before, - After, -} - -impl From for InsertPosition { - fn from(side: DropSide) -> Self { - match side { - DropSide::Before => InsertPosition::Before, - DropSide::After => InsertPosition::After, - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -struct DropHint { - entity: Entity, - side: DropSide, -} - /// Isolates variant-specific behaviors from [`SegmentedButton`]. pub trait SegmentedVariant { const VERTICAL: bool; @@ -183,12 +157,6 @@ where #[setters(strip_option)] pub(super) drag_id: Option, #[setters(skip)] - pub(super) tab_drag: Option>, - #[setters(skip)] - pub(super) on_drop_hint: Option) -> Message + 'static>>, - #[setters(skip)] - pub(super) on_reorder: Option Message + 'static>>, - #[setters(skip)] /// Defines the implementation of this struct variant: PhantomData, } @@ -236,9 +204,6 @@ where mimes: Vec::new(), variant: PhantomData, drag_id: None, - tab_drag: None, - on_drop_hint: None, - on_reorder: None, } } @@ -296,77 +261,6 @@ where self } - /// Enable drag-and-drop support for tabs using the provided payload builder. - pub fn enable_tab_drag( - mut self, - payload: impl Fn(Entity) -> Option<(String, Vec)> + 'static, - ) -> Self { - self.tab_drag = Some(TabDragSource::new(payload)); - self - } - - /// Receive drop hint updates during drag-and-drop. - pub fn on_drop_hint( - mut self, - callback: impl Fn(Option<(Entity, bool)>) -> Message + 'static, - ) -> Self { - self.on_drop_hint = Some(Box::new(callback)); - self - } - - /// Emit a message when a tab drag is dropped inside this widget. - pub fn on_reorder(mut self, callback: impl Fn(ReorderEvent) -> Message + 'static) -> Self { - self.on_reorder = Some(Box::new(callback)); - self - } - - /// Set the pointer distance threshold before a drag is started. - pub fn tab_drag_threshold(mut self, threshold: f32) -> Self { - if let Some(tab_drag) = self.tab_drag.as_mut() { - tab_drag.threshold = threshold.max(1.0); - } - self - } - - fn reorder_event_for_drop(&self, state: &LocalState, target: Entity) -> Option { - let dragged = state.dragging_tab?; - if dragged == target - || !self.model.contains_item(dragged) - || !self.model.contains_item(target) - { - return None; - } - let position = state - .drop_hint - .filter(|hint| hint.entity == target) - .map(|hint| InsertPosition::from(hint.side)) - .unwrap_or_else(|| self.default_insert_position(dragged, target)); - Some(ReorderEvent { - dragged, - target, - position, - }) - } - - fn default_insert_position(&self, dragged: Entity, target: Entity) -> InsertPosition { - let len = self.model.len(); - let target_pos = self - .model - .position(target) - .map(|pos| pos as usize) - .unwrap_or(len); - let from_pos = self - .model - .position(dragged) - .map(|pos| pos as usize) - .unwrap_or(target_pos); - if from_pos < target_pos { - InsertPosition::After - } else { - InsertPosition::Before - } - } - /// Check if an item is enabled. fn is_enabled(&self, key: Entity) -> bool { self.model.items.get(key).is_some_and(|item| item.enabled) @@ -651,101 +545,6 @@ where state.pressed_item == Some(Item::Tab(key)) } - fn emit_drop_hint(&self, shell: &mut Shell<'_, Message>, hint: Option) { - if let Some(on_hint) = self.on_drop_hint.as_ref() { - let mapped = hint.map(|hint| (hint.entity, matches!(hint.side, DropSide::After))); - shell.publish(on_hint(mapped)); - } - } - - fn drop_hint_for_position( - &self, - state: &LocalState, - bounds: Rectangle, - cursor: Point, - ) -> Option { - let dragging = state.dragging_tab?; - - self.variant_bounds(state, bounds) - .filter_map(|item| match item { - ItemBounds::Button(entity, rect) if rect.contains(cursor) => Some((entity, rect)), - _ => None, - }) - .find_map(|(entity, rect)| { - let before = if Self::VERTICAL { - cursor.y < rect.center_y() - } else { - cursor.x < rect.center_x() - }; - Some(DropHint { - entity, - side: if before { - DropSide::Before - } else { - DropSide::After - }, - }) - }) - } - - fn start_tab_drag( - &self, - state: &mut LocalState, - entity: Entity, - bounds: Rectangle, - cursor: Point, - clipboard: &mut dyn Clipboard, - ) -> bool { - let Some(tab_drag) = self.tab_drag.as_ref() else { - return false; - }; - - log::trace!( - target: TAB_REORDER_LOG_TARGET, - "start_tab_drag requested entity={:?} cursor=({:.2},{:.2}) bounds=({:.2},{:.2},{:.2},{:.2}) threshold={}", - entity, - cursor.x, - cursor.y, - bounds.x, - bounds.y, - bounds.width, - bounds.height, - tab_drag.threshold - ); - - let Some((mime, data)) = (tab_drag.payload)(entity) else { - 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::( - clipboard, - false, - Some(iced_core::clipboard::DndSource::Widget(self.id.0.clone())), - None, - Box::new(SimpleDragData::new(mime, data)), - DndAction::Move, - ); - log::trace!( - target: TAB_REORDER_LOG_TARGET, - "tab drag started entity={:?} mime={} bytes={}", - entity, - mime_label, - data_len - ); - state.dragging_tab = Some(entity); - state.tab_drag_candidate = None; - state.pressed_item = None; - true - } - /// Returns the drag id of the destination. /// /// # Panics @@ -812,9 +611,6 @@ where dnd_state: Default::default(), fingers_pressed: Default::default(), pressed_item: None, - tab_drag_candidate: None, - dragging_tab: None, - drop_hint: None, }) } @@ -905,7 +701,7 @@ where layout: Layout<'_>, cursor_position: mouse::Cursor, _renderer: &Renderer, - clipboard: &mut dyn Clipboard, + _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &iced::Rectangle, ) -> event::Status { @@ -921,26 +717,7 @@ where .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() { - state.tab_drag_candidate = None; - state.drop_hint = None; - self.emit_drop_hint(shell, state.drop_hint); - log::trace!( - target: TAB_REORDER_LOG_TARGET, - "tab drag source finished id={:?}", - my_id - ); - return event::Status::Captured; - } - } DndEvent::Offer( id, OfferEvent::Enter { @@ -955,16 +732,6 @@ where }) .find(|(_key, bounds)| bounds.contains(Point::new(*x as f32, *y as f32))) .map(|(key, _)| key); - state.drop_hint = self.drop_hint_for_position( - state, - bounds, - Point::new(*x as f32, *y as f32), - ); - self.emit_drop_hint(shell, state.drop_hint); - log::trace!( - target: TAB_REORDER_LOG_TARGET, - "offer enter id={my_id:?} entity={entity:?} @ ({x},{y}) mimes={mime_types:?}" - ); let on_dnd_enter = self.on_dnd_enter @@ -983,28 +750,15 @@ where ); } DndEvent::Offer(id, OfferEvent::LeaveDestination) if Some(my_id) != *id => {} - DndEvent::Offer(id, OfferEvent::Leave | OfferEvent::LeaveDestination) - if Some(my_id) == *id => - { - state.drop_hint = None; - self.emit_drop_hint(shell, state.drop_hint); + DndEvent::Offer(id, OfferEvent::Leave | OfferEvent::LeaveDestination) => { if let Some(Some(entity)) = entity { if let Some(on_dnd_leave) = self.on_dnd_leave.as_ref() { shell.publish(on_dnd_leave(entity)); } } - log::trace!( - target: TAB_REORDER_LOG_TARGET, - "offer leave id={my_id:?} entity={entity:?}" - ); _ = state.dnd_state.on_leave::(None); } - DndEvent::Offer(_, OfferEvent::Leave | OfferEvent::LeaveDestination) => {} DndEvent::Offer(id, OfferEvent::Motion { x, y }) if Some(my_id) == *id => { - log::trace!( - target: TAB_REORDER_LOG_TARGET, - "offer motion id={my_id:?} cursor=({x},{y}) current_entity={entity:?}" - ); let new = self .variant_bounds(state, bounds) .filter_map(|item| match item { @@ -1021,12 +775,6 @@ where None:: Message>, Some(new_entity), ); - state.drop_hint = self.drop_hint_for_position( - state, - bounds, - Point::new(*x as f32, *y as f32), - ); - self.emit_drop_hint(shell, state.drop_hint); if Some(Some(new_entity)) != entity { let prev_action = state .dnd_state @@ -1044,12 +792,6 @@ where } } } else if entity.is_some() { - log::trace!( - target: TAB_REORDER_LOG_TARGET, - "offer motion leaving id={my_id:?}" - ); - state.drop_hint = None; - self.emit_drop_hint(shell, state.drop_hint); state.dnd_state.on_motion::( *x, *y, @@ -1065,81 +807,32 @@ where } } DndEvent::Offer(id, OfferEvent::Drop) if Some(my_id) == *id => { - log::trace!( - target: TAB_REORDER_LOG_TARGET, - "offer drop id={my_id:?} entity={entity:?}" - ); _ = state .dnd_state .on_drop::(None:: Message>); } DndEvent::Offer(id, OfferEvent::SelectedAction(action)) if Some(my_id) == *id => { if state.dnd_state.drag_offer.is_some() { - log::trace!( - target: TAB_REORDER_LOG_TARGET, - "offer selected action id={my_id:?} action={action:?} entity={entity:?}" - ); _ = state .dnd_state .on_action_selected::(*action, None:: Message>); } } DndEvent::Offer(id, OfferEvent::Data { data, mime_type }) if Some(my_id) == *id => { - log::trace!( - target: TAB_REORDER_LOG_TARGET, - "offer data id={my_id:?} entity={entity:?} mime={mime_type:?}" - ); - let drop_entity = entity - .flatten() - .or_else(|| state.drop_hint.map(|hint| hint.entity)); - let allow_reorder = state - .dnd_state - .drag_offer - .as_ref() - .is_some_and(|offer| offer.selected_action.contains(DndAction::Move)); - let pending_reorder = if allow_reorder && self.on_reorder.is_some() { - drop_entity.and_then(|target| self.reorder_event_for_drop(state, target)) - } else { - None - }; - if let Some(entity) = drop_entity { + if let Some(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) }); - let (maybe_msg, ret) = state.dnd_state.on_data_received( + if let (Some(msg), ret) = state.dnd_state.on_data_received( mem::take(mime_type), mem::take(data), None:: Message>, on_drop, - ); - if let Some(msg) = maybe_msg { - log::trace!( - target: TAB_REORDER_LOG_TARGET, - "publishing drop message entity={entity:?}" - ); + ) { shell.publish(msg); - } - state.drop_hint = None; - self.emit_drop_hint(shell, state.drop_hint); - if let Some(event) = pending_reorder { - if let Some(on_reorder) = self.on_reorder.as_ref() { - shell.publish(on_reorder(event)); - } - } - return ret; - } else { - log::trace!( - target: TAB_REORDER_LOG_TARGET, - "data received without entity id={my_id:?}" - ); - state.drop_hint = None; - self.emit_drop_hint(shell, state.drop_hint); - if let Some(event) = pending_reorder { - if let Some(on_reorder) = self.on_reorder.as_ref() { - shell.publish(on_reorder(event)); - } + return ret; } } } @@ -1204,16 +897,12 @@ where // Record that the mouse is hovering over this button. state.hovered = Item::Tab(key); - let close_button_bounds = - close_bounds(bounds, f32::from(self.close_icon.size)); - let over_close_button = self.model.items[key].closable - && cursor_position.is_over(close_button_bounds); - // If marked as closable, show a close icon. if self.model.items[key].closable { // Emit close message if the close button is pressed. if let Some(on_close) = self.on_close.as_ref() { - if over_close_button + if cursor_position + .is_over(close_bounds(bounds, f32::from(self.close_icon.size))) && (left_button_released(&event) || (touch_lifted(&event) && fingers_pressed == 1)) { @@ -1238,36 +927,6 @@ where } } - if self.tab_drag.is_some() - && matches!( - event, - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - ) - && !over_close_button - { - if let Some(position) = cursor_position.position() { - state.tab_drag_candidate = Some(TabDragCandidate { - entity: key, - bounds, - origin: position, - }); - if let Some(tab_drag) = self.tab_drag.as_ref() { - log::trace!( - target: TAB_REORDER_LOG_TARGET, - "tab drag candidate entity={:?} origin=({:.2},{:.2}) bounds=({:.2},{:.2},{:.2},{:.2}) threshold={}", - key, - position.x, - position.y, - bounds.x, - bounds.y, - bounds.width, - bounds.height, - tab_drag.threshold - ); - } - } - } - if is_lifted(&event) { state.unfocus(); } @@ -1387,42 +1046,6 @@ where state.pressed_item = None; } - if let (Some(tab_drag), Some(candidate)) = - (self.tab_drag.as_ref(), state.tab_drag_candidate) - { - if let Event::Mouse(mouse::Event::CursorMoved { .. }) = event { - if let Some(position) = cursor_position.position() { - if position.distance(candidate.origin) >= tab_drag.threshold { - if let Some(candidate) = state.tab_drag_candidate.take() { - log::trace!( - target: TAB_REORDER_LOG_TARGET, - "tab drag threshold met entity={:?} distance={:.2} threshold={}", - candidate.entity, - position.distance(candidate.origin), - tab_drag.threshold - ); - if self.start_tab_drag( - state, - candidate.entity, - candidate.bounds, - position, - clipboard, - ) { - return event::Status::Captured; - } - } - } - } - } - } - - if matches!( - event, - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - ) { - state.tab_drag_candidate = None; - } - if state.is_focused() { if let Event::Keyboard(keyboard::Event::KeyPressed { key: keyboard::Key::Named(keyboard::key::Named::Tab), @@ -1497,7 +1120,6 @@ where ) { let state = tree.state.downcast_mut::(); operation.focusable(state, Some(&self.id.0)); - operation.custom(state, Some(&self.id.0)); if let Item::Set = state.focused_item { if self.prev_tab_sensitive(state) { @@ -1558,12 +1180,6 @@ 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 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 { @@ -1689,8 +1305,6 @@ where // Draw each of the items in the widget. let mut nth = 0; - let drop_hint_marker = drop_hint; - let show_drop_hint_marker = show_drop_hint; self.variant_bounds(state, bounds).for_each(move |item| { let (key, mut bounds) = match item { // Draw a button @@ -1718,27 +1332,8 @@ where } }; - let original_bounds = bounds; let center_y = bounds.center_y(); - if show_drop_hint_marker { - if matches!( - drop_hint_marker, - Some(DropHint { - entity, - side: DropSide::Before - }) if entity == key - ) { - draw_drop_indicator( - renderer, - original_bounds, - DropSide::Before, - Self::VERTICAL, - appearance.active.text_color, - ); - } - } - let menu_open = || { state.show_context == Some(key) && !tree.children.is_empty() @@ -1803,6 +1398,7 @@ where ); } + let original_bounds = bounds; bounds.x += f32::from(self.button_padding[0]); bounds.width -= f32::from(self.button_padding[0]) - f32::from(self.button_padding[2]); let mut indent_padding = 0.0; @@ -2000,24 +1596,6 @@ where ); } - if show_drop_hint_marker { - if matches!( - drop_hint_marker, - Some(DropHint { - entity, - side: DropSide::After - }) if entity == key - ) { - draw_drop_indicator( - renderer, - original_bounds, - DropSide::After, - Self::VERTICAL, - appearance.active.text_color, - ); - } - } - nth += 1; }); } @@ -2081,68 +1659,27 @@ where fn drag_destinations( &self, - tree: &Tree, + _state: &Tree, layout: Layout<'_>, _renderer: &Renderer, dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, ) { - let local_state = tree.state.downcast_ref::(); + let bounds = layout.bounds(); + let my_id = self.get_drag_id(); - let mut pushed = false; - - 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, - self.mimes - ); - 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: self.mimes.clone().into_iter().map(Cow::Owned).collect(), - actions: DndAction::Copy | DndAction::Move, - preferred: DndAction::Move, - }); - } - } - - if !pushed { - let bounds = layout.bounds(); - log::trace!( - target: TAB_REORDER_LOG_TARGET, - "register drag destination id={:?} bounds=({:.2},{:.2},{:.2},{:.2}) mimes={:?}", - my_id, - bounds.x, - bounds.y, - bounds.width, - bounds.height, - self.mimes - ); - dnd_rectangles.push(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, - }); - } + 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); } } @@ -2164,54 +1701,6 @@ where } } -struct TabDragSource { - payload: Box Option<(String, Vec)>>, - threshold: f32, - _marker: PhantomData, -} - -impl TabDragSource { - fn new(payload: impl Fn(Entity) -> Option<(String, Vec)> + 'static) -> Self { - Self { - payload: Box::new(payload), - threshold: 8.0, - _marker: PhantomData, - } - } -} - -struct SimpleDragData { - mime: String, - bytes: Vec, -} - -impl SimpleDragData { - fn new(mime: String, bytes: Vec) -> Self { - Self { mime, bytes } - } -} - -impl iced::clipboard::mime::AsMimeTypes for SimpleDragData { - fn available(&self) -> Cow<'static, [String]> { - Cow::Owned(vec![self.mime.clone()]) - } - - fn as_bytes(&self, mime_type: &str) -> Option> { - if mime_type == self.mime { - Some(Cow::Owned(self.bytes.clone())) - } else { - None - } - } -} - -#[derive(Clone, Copy)] -struct TabDragCandidate { - entity: Entity, - bounds: Rectangle, - origin: Point, -} - #[derive(Debug, Clone, Copy)] struct Focus { updated_at: Instant, @@ -2258,12 +1747,6 @@ pub struct LocalState { fingers_pressed: HashSet, /// The currently pressed item pressed_item: Option, - /// Pending tab drag candidate data - tab_drag_candidate: Option, - /// Currently dragging tab entity - dragging_tab: Option, - /// Current drop hint for drag-and-drop indicator - drop_hint: Option, } #[derive(Debug, Default, PartialEq)] @@ -2288,143 +1771,6 @@ impl LocalState { } } -#[cfg(test)] -mod tests { - use super::*; - use crate::widget::segmented_button::{self, Appearance as SegAppearance}; - use iced::Size; - use slotmap::SecondaryMap; - use std::collections::HashSet; - - #[derive(Clone, Debug)] - enum TestMessage {} - - struct TestVariant; - - impl SegmentedVariant - for SegmentedButton<'_, TestVariant, SelectionMode, Message> - where - Model: Selectable, - SelectionMode: Default, - { - const VERTICAL: bool = false; - - fn variant_appearance( - _theme: &crate::Theme, - _style: &crate::theme::SegmentedButton, - ) -> SegAppearance { - SegAppearance::default() - } - - fn variant_bounds<'b>( - &'b self, - _state: &'b LocalState, - bounds: Rectangle, - ) -> Box + 'b> { - let len = self.model.order.len(); - if len == 0 { - return Box::new(std::iter::empty()); - } - let width = bounds.width / len as f32; - Box::new( - self.model - .order - .iter() - .copied() - .enumerate() - .map(move |(idx, entity)| { - let rect = Rectangle { - x: bounds.x + (idx as f32) * width, - y: bounds.y, - width, - height: bounds.height, - }; - ItemBounds::Button(entity, rect) - }), - ) - } - - fn variant_layout( - &self, - _state: &mut LocalState, - _renderer: &crate::Renderer, - _limits: &layout::Limits, - ) -> Size { - Size::ZERO - } - } - - fn sample_model() -> ( - segmented_button::SingleSelectModel, - Vec, - ) { - let mut entities = Vec::new(); - let model = segmented_button::Model::builder() - .insert(|b| b.text("One").with_id(|id| entities.push(id))) - .insert(|b| b.text("Two").with_id(|id| entities.push(id))) - .insert(|b| b.text("Three").with_id(|id| entities.push(id))) - .build(); - (model, entities) - } - - fn test_state(dragging: segmented_button::Entity, len: usize) -> LocalState { - let mut state = LocalState { - menu_state: MenuBarState::default(), - paragraphs: SecondaryMap::new(), - text_hashes: SecondaryMap::new(), - buttons_visible: 0, - buttons_offset: 0, - collapsed: false, - focused: None, - focused_item: Item::default(), - focused_visible: false, - hovered: Item::default(), - known_length: 0, - middle_clicked: None, - internal_layout: Vec::new(), - context_cursor: Point::ORIGIN, - show_context: None, - wheel_timestamp: None, - dnd_state: crate::widget::dnd_destination::State::>::new(), - fingers_pressed: HashSet::new(), - pressed_item: None, - tab_drag_candidate: None, - dragging_tab: Some(dragging), - drop_hint: None, - }; - state.buttons_visible = len; - state.known_length = len; - state - } - - #[test] - fn drop_hint_reports_before_and_after() { - let (model, ids) = sample_model(); - let button = - SegmentedButton::::new( - &model, - ); - let state = test_state(ids[0], model.order.len()); - let bounds = Rectangle { - x: 0.0, - y: 0.0, - width: 300.0, - height: 30.0, - }; - let before = button - .drop_hint_for_position(&state, bounds, Point::new(10.0, 15.0)) - .expect("hint"); - assert_eq!(before.entity, ids[0]); - assert!(matches!(before.side, DropSide::Before)); - - let after = button - .drop_hint_for_position(&state, bounds, Point::new(290.0, 15.0)) - .expect("hint"); - assert_eq!(after.entity, ids[2]); - assert!(matches!(after.side, DropSide::After)); - } -} - impl operation::Focusable for LocalState { fn is_focused(&self) -> bool { self.focused @@ -2537,53 +1883,6 @@ fn draw_icon( ); } -fn draw_drop_indicator( - renderer: &mut Renderer, - bounds: Rectangle, - side: DropSide, - vertical: bool, - color: Color, -) { - let thickness = 4.0; - let quad_bounds = if vertical { - let y = match side { - DropSide::Before => bounds.y - thickness / 2.0, - DropSide::After => bounds.y + bounds.height - thickness / 2.0, - }; - - Rectangle { - x: bounds.x, - y, - width: bounds.width, - height: thickness, - } - } else { - let x = match side { - DropSide::Before => bounds.x - thickness / 2.0, - DropSide::After => bounds.x + bounds.width - thickness / 2.0, - }; - - Rectangle { - x, - y: bounds.y, - width: thickness, - height: bounds.height, - } - }; - - renderer.fill_quad( - renderer::Quad { - bounds: quad_bounds, - border: Border { - radius: 2.0.into(), - ..Default::default() - }, - shadow: Shadow::default(), - }, - Background::Color(color), - ); -} - fn left_button_released(event: &Event) -> bool { matches!( event,