use std::rc::Rc;
use crate::{DndItem, draggable::Draggable, hooks::use_dnd_context};
use dioxus::{
core::Task,
html::geometry::{PixelsRect, PixelsVector2D},
prelude::*,
};
#[derive(Debug, Clone, Default)]
pub struct DroppableContext<T: DndItem> {
pub elements: Signal<Vec<ItemGeometry<T>>>,
pub dropable_ref: Signal<Option<Rc<MountedData>>>,
pub dropable_rect: Signal<Option<PixelsRect>>,
}
impl<T: DndItem> Copy for DroppableContext<T> {}
#[derive(Debug, Clone)]
pub struct ItemGeometry<T: DndItem> {
pub value: T,
pub rect: Option<PixelsRect>,
pub mounted: Option<Rc<MountedData>>,
}
impl<T: DndItem> DroppableContext<T> {
fn new() -> Self {
Self {
elements: Signal::new(Vec::new()),
dropable_ref: Signal::new(None),
dropable_rect: Signal::new(None),
}
}
pub fn register_element_mounted(&mut self, item: T, mounted: Rc<MountedData>) {
self.elements.with_mut(|elements| {
if let Some(index) = elements.iter().position(|e| e.value == item) {
elements[index].mounted = Some(mounted);
}
});
}
pub fn has_element(&self, item: &T) -> bool {
self.elements
.with(|elements| elements.iter().any(|e| e.value == *item))
}
pub fn update_elements_order(&mut self, items: Vec<T>) {
self.elements.with_mut(|elements| {
let mut old_map = std::mem::take(elements);
for item in items {
if let Some(pos) = old_map.iter().position(|e| e.value == item) {
elements.push(old_map.remove(pos));
} else {
elements.push(ItemGeometry {
value: item,
rect: None,
mounted: None,
});
}
}
});
}
pub async fn collect_elements_rects(&mut self) {
let items_to_measure: Vec<(usize, Rc<MountedData>)> = self
.elements
.read()
.iter()
.enumerate()
.filter_map(|(i, e)| e.mounted.as_ref().map(|m| (i, m.clone())))
.collect();
let mut tasks = Vec::new();
for (index, mounted) in items_to_measure {
tasks.push(async move {
let rect = mounted.get_client_rect().await.unwrap_or_default();
(index, rect)
});
}
let results = futures::future::join_all(tasks).await;
self.elements.with_mut(|elements| {
for (index, rect) in results {
if let Some(element) = elements.get_mut(index) {
element.rect = Some(rect);
}
}
});
}
}
#[component]
pub fn Droppable<T: DndItem>(
#[props(default)]
class: ReadSignal<String>,
#[props(default)]
draggable_class: ReadSignal<String>,
items: Signal<Vec<T>>,
) -> Element {
let context = use_dnd_context::<T>();
let mut droppable_context = use_context_provider(DroppableContext::<T>::new);
let mut reorder_task = use_signal(|| Option::<Task>::None);
use_effect(move || {
if let Some(dropable) = (droppable_context.dropable_ref)() {
spawn(async move {
let rect = dropable.get_client_rect().await.unwrap_or_default();
droppable_context.dropable_rect.set(Some(rect));
});
} else {
droppable_context.dropable_rect.set(None);
}
});
let is_active = use_memo(move || {
context
.active
.read()
.clone()
.map(|active_item| droppable_context.has_element(&active_item))
.unwrap_or(false)
});
use_effect(move || {
droppable_context.update_elements_order(items());
});
use_effect(move || {
if is_active() {
spawn(async move {
droppable_context.collect_elements_rects().await;
});
}
});
use_effect(move || {
if is_active() {
let mouse = context.mouse_pos.read();
let target_point = PixelsVector2D::new(mouse.x, mouse.y).to_point();
let active_item = context.active.read();
let elements = droppable_context.elements.peek();
if let Some(active_val) = active_item.as_ref() {
let from_idx = elements.iter().position(|e| e.value == *active_val);
let to_idx = elements.iter().position(|e| {
if let Some(rect) = e.rect
&& rect.contains(target_point)
{
let center_y = rect.min_y() + rect.height() / 2.0;
if from_idx.unwrap_or(0)
< elements
.iter()
.position(|el| el.value == e.value)
.unwrap_or(0)
{
return target_point.y > center_y;
} else {
return target_point.y < center_y;
}
}
false
});
if let (Some(to), Some(from)) = (to_idx, from_idx)
&& from != to
{
let task = spawn(async move {
let mut next_items = items.peek().clone();
let item = next_items.remove(from);
next_items.insert(to, item);
items.set(next_items);
gloo_timers::future::TimeoutFuture::new(10).await;
droppable_context.collect_elements_rects().await;
});
if let Some(old_task) = *reorder_task.peek() {
old_task.cancel();
}
reorder_task.set(Some(task));
}
}
}
});
rsx! {
div { class: "{class}", position: "relative",
onmounted: move |evt| {
droppable_context.dropable_ref.set(Some(evt.data()));
},
for (index, item) in items().iter().enumerate() {
Draggable { key: "{context.key_gen.call(item.clone())}", class: "{draggable_class}", item: item.clone(), index }
}
}
}
}