use crate::item::{Item, ItemResponse};
use crate::state::DragDetectionState;
use crate::{DragDropUi, ItemState};
use egui::{Id, Layout, Pos2, Rect, Ui, UiBuilder, Vec2};
pub struct ItemIterator<'a> {
state: &'a mut DragDropUi,
dragged_item_rect: Option<Rect>,
hovering_item: Option<(Id, Pos2)>,
layout: Layout,
set_next_item_as_hovering_above: bool,
pub(crate) hovering_last_item: bool,
pub(crate) last_item: Option<(usize, Id, Pos2)>,
pub(crate) mark_next_as_closest_item: Option<(f32, Pos2)>,
pub(crate) is_after_dragged_item: bool,
pub(crate) is_after_hovered_item: bool,
pub(crate) hovering_over_any_handle: bool,
pub(crate) source_item: Option<(usize, Id)>,
#[allow(clippy::type_complexity)]
pub(crate) closest_item: Option<(f32, Option<(usize, Id, Pos2)>)>,
}
impl<'a> ItemIterator<'a> {
pub(crate) fn new(
state: &'a mut DragDropUi,
dragged_item_rect: Option<Rect>,
layout: Layout,
) -> Self {
let hovering_item = match state.detection_state {
DragDetectionState::Dragging {
closest_item: item, ..
} => Some(item),
_ => None,
};
let hovering_last_item = match state.detection_state {
DragDetectionState::Dragging {
hovering_last_item, ..
} => hovering_last_item,
_ => false,
};
Self {
state,
dragged_item_rect,
layout,
set_next_item_as_hovering_above: false,
closest_item: None,
hovering_item,
mark_next_as_closest_item: None,
hovering_last_item,
last_item: None,
is_after_dragged_item: false,
is_after_hovered_item: false,
hovering_over_any_handle: false,
source_item: None,
}
}
pub fn next(
&mut self,
ui: &mut Ui,
id: Id,
idx: usize,
add_surrounding_space_automatically: bool,
content: impl FnOnce(&mut Ui, Item) -> ItemResponse,
) {
let is_dragged_item = self.state.detection_state.is_dragging_item(id);
if let Some((distance, pos)) = self.mark_next_as_closest_item {
self.mark_next_as_closest_item = None;
self.closest_item = Some((distance, Some((idx, id, pos))));
}
if is_dragged_item {
self.is_after_dragged_item = true;
}
if let Some((hovering_id, _pos)) = self.hovering_item {
if hovering_id == id {
self.is_after_hovered_item = true;
}
}
if add_surrounding_space_automatically {
self.space_before(ui, id, |_ui, _space| {});
}
let dragging = self.state.detection_state.is_dragging();
let item = Item::new(
id,
ui.unique_id(),
ItemState {
dragged: is_dragged_item,
index: idx,
},
self.state,
&mut self.hovering_over_any_handle,
);
let rect = if is_dragged_item {
if let Some((_id, pos)) = self.hovering_item {
let mut child =
ui.new_child(UiBuilder::new().max_rect(ui.available_rect_before_wrap()));
let start = ui.next_widget_position();
let rect = child
.scope_builder(
UiBuilder::new().max_rect(Rect::from_min_size(pos, child.available_size())),
|ui| content(ui, item),
)
.inner
.0;
Rect::from_min_size(start, rect.size())
} else {
content(ui, item).0
}
} else {
content(ui, item).0
};
if dragging != self.state.detection_state.is_dragging() {
self.set_next_item_as_hovering_above = true;
}
if add_surrounding_space_automatically {
self.space_after(ui, id, |_ui, _space| {});
}
if let Some(dragged_item_rect) = self.dragged_item_rect {
if self.layout.main_wrap {
if rect.contains(dragged_item_rect.center()) {
if self.is_after_hovered_item {
self.mark_next_as_closest_item = Some((0.0, rect.min));
} else {
self.closest_item = Some((0.0, Some((idx, id, rect.min))));
}
}
} else {
let (distance, mark_next) = self.get_distance(dragged_item_rect, rect);
self.check_closest_item(distance, rect.min, Some((idx, id)), mark_next);
}
}
if self.state.detection_state.is_dragging_item(id) {
self.source_item = Some((idx, id));
}
self.last_item = Some((idx, id, rect.min));
}
fn get_distance(&mut self, dragged_item_rect: Rect, rect: Rect) -> (f32, bool) {
let size_difference = dragged_item_rect.size() - rect.size();
let (distance, mark_next) = if self.layout.is_horizontal() {
let distance = dragged_item_rect.center().x - rect.center().x;
let mark_next = rect.center().x < dragged_item_rect.center().x;
(distance, mark_next)
} else {
let distance = dragged_item_rect.center().y - rect.center().y;
let mark_next = if size_difference.y.abs() > 0.0 {
rect.center().y < dragged_item_rect.center().y
} else {
self.is_after_hovered_item
};
(distance, mark_next)
};
let distance = distance.abs();
(distance, mark_next)
}
pub fn space_before(&mut self, ui: &mut Ui, id: Id, content: impl FnOnce(&mut Ui, Vec2)) {
if !self.hovering_last_item {
self.add_space_and_check_closest(ui, id, content);
}
}
pub fn space_after(&mut self, ui: &mut Ui, id: Id, content: impl FnOnce(&mut Ui, Vec2)) {
if self.hovering_last_item {
self.add_space_and_check_closest(ui, id, content);
}
}
fn add_space_and_check_closest(
&mut self,
ui: &mut Ui,
id: Id,
content: impl FnOnce(&mut Ui, Vec2),
) {
if let Some((hovering_id, _pos)) = self.hovering_item {
if hovering_id == id {
if let Some(dragged_item_rect) = self.dragged_item_rect {
let rect = ui
.allocate_ui(dragged_item_rect.size(), |ui| {
ui.set_min_size(dragged_item_rect.size());
content(ui, dragged_item_rect.size());
})
.response
.rect;
let (distance, _mark_next) = self.get_distance(dragged_item_rect, rect);
self.check_closest_item(distance, rect.min, None, false);
}
}
}
}
fn check_closest_item(
&mut self,
distance: f32,
pos: Pos2,
item: Option<(usize, Id)>,
mark_next: bool,
) {
if self.closest_item.is_none() || distance < self.closest_item.unwrap().0 {
if mark_next {
self.mark_next_as_closest_item = Some((distance, pos));
} else {
self.closest_item = Some((distance, item.map(|(idx, id)| (idx, id, pos))));
}
}
}
}