use std::collections::HashMap;
use std::rc::Rc;
use dioxus::html::MountedData;
use dioxus::prelude::*;
use crate::core::{transition, GestureEffect, GestureEvent, GesturePhase, Rect};
use crate::pointer::pointer_client;
use crate::sortable::{ReorderMode, SortEvent};
pub fn cell_of(index: usize, cols: usize) -> (usize, usize) {
let cols = cols.max(1);
(index / cols, index % cols)
}
pub fn index_of(row: usize, col: usize, cols: usize, len: usize) -> Option<usize> {
let cols = cols.max(1);
if col >= cols {
return None;
}
let ix = row * cols + col;
(ix < len).then_some(ix)
}
#[component]
pub fn SortableGrid(
len: usize,
cols: usize,
render: Callback<usize, Element>,
on_sort: EventHandler<SortEvent>,
#[props(default)]
mode: ReorderMode,
#[props(extends = div, extends = GlobalAttributes)] attributes: Vec<Attribute>,
) -> Element {
let mode_str = match mode {
ReorderMode::Insert => "insert",
ReorderMode::Swap => "swap",
};
let mut drag_from = use_signal(|| None::<usize>);
let mut over = use_signal(|| None::<usize>);
let rects = use_signal(HashMap::<usize, Rect>::new);
let mounteds = use_signal(HashMap::<usize, Rc<MountedData>>::new);
let mut gesture = use_signal(|| GesturePhase::Idle);
let mut step = move |event: GestureEvent| -> GestureEffect {
let (next, fx) = transition(*gesture.peek(), event, 8.0);
gesture.set(next);
fx
};
let mut feed = move |ix: usize, event: GestureEvent| match step(event) {
GestureEffect::Begin { .. } => {
drag_from.set(Some(ix));
over.set(None);
for (i, m) in mounteds.peek().clone() {
let mut rects = rects;
spawn(async move {
if let Ok(r) = m.get_client_rect().await {
rects.write().insert(
i,
Rect::new(r.origin.x, r.origin.y, r.size.width, r.size.height),
);
}
});
}
}
GestureEffect::Track { at } => {
let next = rects
.peek()
.iter()
.find(|(_, r)| r.contains(at))
.map(|(&i, _)| i)
.filter(|&i| Some(i) != *drag_from.peek())
.or(*over.peek());
if next != *over.peek() {
over.set(next);
}
}
GestureEffect::Drop { .. } => {
if let (Some(from), Some(to)) = (*drag_from.peek(), *over.peek()) {
if from != to {
on_sort.call(SortEvent { from, to });
}
}
drag_from.set(None);
over.set(None);
}
GestureEffect::Abort => {
drag_from.set(None);
over.set(None);
}
GestureEffect::Tap | GestureEffect::None => {}
};
rsx! {
div {
style: "display: grid; grid-template-columns: repeat({cols}, 1fr);",
"data-mode": mode_str,
..attributes,
for ix in 0..len {
div {
key: "{ix}",
draggable: true,
style: "touch-action: none;",
"data-dragging": drag_from() == Some(ix),
"data-drop-target": over() == Some(ix) && drag_from() != Some(ix),
onmounted: move |evt: Event<MountedData>| {
let m: Rc<MountedData> = evt.data();
let mut mounteds = mounteds;
let mut rects = rects;
mounteds.write().insert(ix, m.clone());
spawn(async move {
if let Ok(r) = m.get_client_rect().await {
rects.write().insert(
ix,
Rect::new(r.origin.x, r.origin.y, r.size.width, r.size.height),
);
}
});
},
onpointerdown: move |evt: PointerEvent| {
if evt.pointer_type() == "mouse" || !evt.is_primary() { return; }
feed(ix, GestureEvent::Down { at: pointer_client(&evt), pointer_id: evt.pointer_id() });
},
onpointermove: move |evt: PointerEvent| {
feed(ix, GestureEvent::Move { at: pointer_client(&evt), pointer_id: evt.pointer_id() });
},
onpointerup: move |evt: PointerEvent| {
feed(ix, GestureEvent::Up { at: pointer_client(&evt), pointer_id: evt.pointer_id() });
},
onpointercancel: move |_| feed(ix, GestureEvent::Cancel),
onlostpointercapture: move |_| feed(ix, GestureEvent::Cancel),
ondragstart: move |evt: DragEvent| {
evt.stop_propagation();
let _ = evt.data_transfer().set_data("text/plain", "dioxus-dnd-grid");
drag_from.set(Some(ix));
},
ondragover: move |evt: DragEvent| {
if drag_from().is_some() {
evt.prevent_default();
if over() != Some(ix) {
over.set(Some(ix));
}
}
},
ondrop: move |evt: DragEvent| {
evt.prevent_default();
evt.stop_propagation();
if let Some(from) = drag_from() {
if from != ix {
on_sort.call(SortEvent { from, to: ix });
}
}
drag_from.set(None);
over.set(None);
},
ondragend: move |_| {
drag_from.set(None);
over.set(None);
},
{render.call(ix)}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn grid_coordinates_round_trip() {
assert_eq!(cell_of(0, 4), (0, 0));
assert_eq!(cell_of(5, 4), (1, 1));
assert_eq!(index_of(1, 1, 4, 12), Some(5));
assert_eq!(index_of(0, 4, 4, 12), None); assert_eq!(index_of(3, 0, 4, 12), None); assert_eq!(cell_of(7, 0), (7, 0)); }
}