use std::sync::Arc;
use crate::color::Color;
use crate::geometry::Point;
use crate::draw_ctx::DrawCtx;
use crate::text::Font;
use super::node::{
DropPosition, DragState, FlatRow, TreeNode, is_descendant,
};
const ZONE_EDGE: f64 = 0.28;
pub fn compute_drop_target(
pos: Point,
rows: &[FlatRow],
nodes: &[TreeNode],
viewport_height: f64,
row_height: f64,
scroll_offset: f64,
drag: &DragState,
) -> Option<DropPosition> {
if rows.is_empty() {
return None;
}
let raw = (viewport_height - pos.y + scroll_offset) / row_height;
if raw < 0.0 {
return None;
}
let row_i = (raw as usize).min(rows.len() - 1);
let target_node = rows[row_i].node_idx;
if target_node == drag.node_idx
|| is_descendant(nodes, drag.node_idx, target_node)
{
return None;
}
let row_y_bottom = viewport_height - (row_i as f64 + 1.0) * row_height + scroll_offset;
let frac = (pos.y - row_y_bottom) / row_height;
let pos = if frac < ZONE_EDGE {
DropPosition::After(target_node)
} else if frac > 1.0 - ZONE_EDGE {
DropPosition::Before(target_node)
} else {
if rows[row_i].has_children || matches!(
nodes[target_node].icon,
crate::widgets::tree_view::node::NodeIcon::Folder
| crate::widgets::tree_view::node::NodeIcon::Package,
) {
DropPosition::AsChild(target_node)
} else {
DropPosition::After(target_node)
}
};
Some(pos)
}
pub fn apply_drop(nodes: &mut Vec<TreeNode>, drag_node_idx: usize, target: DropPosition) {
match target {
DropPosition::AsChild(parent_idx) => {
let max_order = nodes
.iter()
.enumerate()
.filter(|(i, n)| n.parent == Some(parent_idx) && *i != drag_node_idx)
.map(|(_, n)| n.order)
.max()
.map(|o| o + 1)
.unwrap_or(0);
nodes[drag_node_idx].parent = Some(parent_idx);
nodes[drag_node_idx].order = max_order;
nodes[parent_idx].is_expanded = true;
}
DropPosition::Before(ref_node_idx) | DropPosition::After(ref_node_idx) => {
let new_parent = nodes[ref_node_idx].parent;
nodes[drag_node_idx].parent = new_parent;
let mut sibs: Vec<usize> = nodes
.iter()
.enumerate()
.filter(|(i, n)| n.parent == new_parent && *i != drag_node_idx)
.map(|(i, _)| i)
.collect();
sibs.sort_by_key(|&i| nodes[i].order);
let ref_pos = sibs.iter().position(|&i| i == ref_node_idx).unwrap_or(0);
let insert_at = match target {
DropPosition::Before(_) => ref_pos,
_ => ref_pos + 1,
};
sibs.insert(insert_at, drag_node_idx);
for (new_order, &idx) in sibs.iter().enumerate() {
nodes[idx].order = new_order as u32;
}
}
}
}
pub fn paint_drop_line(ctx: &mut dyn DrawCtx, x: f64, y: f64, width: f64) {
ctx.set_stroke_color(Color::rgb(0.22, 0.45, 0.88));
ctx.set_line_width(2.0);
ctx.begin_path();
ctx.circle(x + 4.0, y, 3.0);
ctx.fill();
ctx.set_fill_color(Color::rgb(0.22, 0.45, 0.88));
ctx.begin_path();
ctx.move_to(x + 4.0, y);
ctx.line_to(x + width, y);
ctx.stroke();
}
pub fn paint_drop_child_highlight(ctx: &mut dyn DrawCtx, y_bottom: f64, width: f64, height: f64) {
ctx.set_stroke_color(Color::rgba(0.22, 0.45, 0.88, 0.7));
ctx.set_line_width(1.5);
ctx.begin_path();
ctx.rounded_rect(2.0, y_bottom, width - 4.0, height, 3.0);
ctx.stroke();
}
pub fn paint_ghost(
ctx: &mut dyn DrawCtx,
label: &str,
pos: Point,
width: f64,
row_height: f64,
font: &Arc<Font>,
font_size: f64,
icon_color: Color,
) {
let gx = (pos.x - 12.0).max(0.0);
let gy = pos.y - row_height * 0.5;
ctx.set_fill_color(Color::rgba(0.0, 0.0, 0.0, 0.18));
ctx.begin_path();
ctx.rounded_rect(gx + 2.0, gy - 2.0, width.min(200.0), row_height, 4.0);
ctx.fill();
ctx.set_global_alpha(0.82);
ctx.set_fill_color(Color::rgb(0.97, 0.97, 1.0));
ctx.begin_path();
ctx.rounded_rect(gx, gy, width.min(200.0), row_height, 4.0);
ctx.fill();
ctx.set_fill_color(icon_color);
ctx.begin_path();
ctx.rounded_rect(gx + 6.0, gy + (row_height - 12.0) * 0.5, 12.0, 12.0, 2.0);
ctx.fill();
ctx.set_font(Arc::clone(font));
ctx.set_font_size(font_size);
ctx.set_fill_color(Color::rgba(0.05, 0.05, 0.1, 0.87));
if let Some(m) = ctx.measure_text(label) {
let ty = gy + (row_height - m.ascent - m.descent) * 0.5 + m.descent;
ctx.fill_text(label, gx + 24.0, ty);
}
ctx.set_global_alpha(1.0);
}