use std::time::Duration;
use crate::geometry::Rect;
use crate::widget::EventCx;
use super::super::edge_scroll::DELAY;
use super::{Flat, Tree, TreeNode};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TreeDrop {
pub keys: Vec<String>,
pub into: Option<String>,
}
type DropMessage<Msg> = Box<dyn Fn(TreeDrop) -> Msg>;
type DropFilter = Box<dyn Fn(&str) -> bool>;
pub(super) struct Dropping<Msg> {
pub(super) message: DropMessage<Msg>,
pub(super) accepts: DropFilter,
}
impl<Msg> Dropping<Msg> {
pub(super) fn new(message: impl Fn(TreeDrop) -> Msg + 'static, accepts: impl Fn(&str) -> bool + 'static) -> Self {
Self { message: Box::new(message), accepts: Box::new(accepts) }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum Aim {
Into(Option<String>),
Refused(String),
Reorder(Option<(usize, usize)>),
Nothing,
}
#[derive(Debug, Default)]
pub(super) struct Spring {
pending: Option<(String, Option<Duration>)>,
}
impl<Msg: 'static> Tree<Msg> {
fn find(&self, key: &str) -> Option<&TreeNode> {
fn walk<'a>(nodes: &'a [TreeNode], key: &str) -> Option<&'a TreeNode> {
nodes.iter().find_map(|node| if node.key == key { Some(node) } else { walk(&node.children, key) })
}
walk(&self.roots, key)
}
pub(super) fn carried(&self, key: &str) -> Vec<String> {
fn walk(nodes: &[TreeNode], chosen: &[String], out: &mut Vec<String>) {
for node in nodes {
if chosen.contains(&node.key) {
out.push(node.key.clone());
} else {
walk(&node.children, chosen, out);
}
}
}
if self.dropping.is_none() || !self.is_multi() || !self.is_chosen(key) {
return vec![key.to_owned()];
}
let mut out = Vec::new();
walk(&self.roots, &self.chosen, &mut out);
out
}
pub(super) fn reorders(&self, keys: &[String]) -> bool {
self.on_move.is_some() && keys.len() == 1
}
pub(super) fn drag_layout(&self, keys: &[String]) -> Vec<Flat<'_>> {
match keys {
[key] if self.reorders(keys) => self.resting(key),
_ => self.flatten(),
}
}
fn refuses(&self, keys: &[String], into: Option<&str>) -> bool {
fn holds(node: &TreeNode, key: &str) -> bool {
node.children.iter().any(|child| child.key == key || holds(child, key))
}
let parent = |key: &str| self.siblings(key).map(|(parent, _)| parent);
if keys.iter().all(|key| parent(key) == Some(into)) {
return true;
}
into.is_some_and(|into| {
keys.iter().any(|key| key == into || self.find(key).is_some_and(|node| holds(node, into)))
})
}
pub(super) fn aim(&self, keys: &[String], pointer: (i32, i32), area: Rect, offset: usize) -> Aim {
if let Some(dropping) = &self.dropping
&& area.contains(pointer.0, pointer.1)
{
let flat = self.drag_layout(keys);
let index = offset + usize::try_from(pointer.1 - area.y).unwrap_or(0);
match flat.get(index) {
Some(row) => {
let key = &row.node.key;
let own_slot = self.reorders(keys) && keys.first() == Some(key);
if (dropping.accepts)(key) && !own_slot {
return if self.refuses(keys, Some(key)) {
Aim::Refused(key.clone())
} else {
Aim::Into(Some(key.clone()))
};
}
}
None if !self.refuses(keys, None) => return Aim::Into(None),
None => {}
}
}
match keys {
[key] if self.reorders(keys) => Aim::Reorder(self.landing(key, pointer, area, offset)),
_ => Aim::Nothing,
}
}
pub(super) fn release(&self, cx: &mut EventCx<'_, Msg>, keys: Vec<String>, aim: Aim) {
match aim {
Aim::Into(into) => {
if let Some(dropping) = &self.dropping {
cx.emit((dropping.message)(TreeDrop { keys, into }));
}
}
Aim::Reorder(Some((from, to))) => {
let Some(key) = keys.first() else { return };
let parent = self.siblings(key).and_then(|(parent, _)| parent.map(str::to_owned));
self.move_node(cx, key, parent.as_deref(), from, to);
}
Aim::Reorder(None) | Aim::Refused(_) | Aim::Nothing => {}
}
}
pub(super) fn spring(&self, cx: &mut EventCx<'_, Msg>, aim: &Aim) -> Option<Duration> {
let now = cx.now();
let node = match aim {
Aim::Into(Some(key)) => self.find(key).filter(|node| node.expandable && !node.expanded),
_ => None,
};
let Some(node) = node else {
cx.memory::<Spring>().pending = None;
return None;
};
let pending = cx.memory::<Spring>().pending.clone();
match pending {
Some((key, due)) if key == node.key => {
let due = due?;
if now < due {
return Some(due - now);
}
cx.memory::<Spring>().pending = Some((key, None));
self.expand(cx, node, true);
None
}
_ => {
cx.memory::<Spring>().pending = Some((node.key.clone(), Some(now + DELAY)));
Some(DELAY)
}
}
}
}