aura-gui 0.3.5

UI nativa acelerada por GPU para escritorio, Android e iOS, en un solo crate: estilo tipo Tailwind, componentes, sombras, animaciones y agentes de IA. Reposo a cero. Licencia AGPL-3.0 o comercial.
//! Arrastrar y soltar con carga tipada, como `on_drag`/`on_drop` de GPUI.
//!
//! El origen guarda un valor cualquiera (`Rc<dyn Any>`) y una vista previa que
//! sigue al puntero; los destinos se eligen por `TypeId`, así una pestaña solo
//! puede soltarse donde alguien acepte pestañas.

use crate::core::{Bounds, Point, Size, px};
use crate::view::any_element::AnyElement;
use crate::view::interactivity::{EventContext, Handler};
use crate::view::window::Window;
use std::any::{Any, TypeId};
use std::rc::Rc;

/// Distancia mínima antes de que un clic se convierta en arrastre.
const DRAG_THRESHOLD: f32 = 4.0;

#[derive(Clone)]
pub struct DragSource {
    pub(crate) value: Rc<dyn Any>,
    pub(crate) preview: Rc<dyn Fn() -> AnyElement>,
}

impl DragSource {
    pub fn new<T: Any>(value: T, preview: impl Fn(&T) -> AnyElement + 'static) -> Self {
        let value = Rc::new(value);
        let shared = value.clone();
        Self { value, preview: Rc::new(move || preview(&shared)) }
    }

    pub(crate) fn type_id(&self) -> TypeId {
        (*self.value).type_id()
    }
}

/// Manejador de soltado para un tipo concreto de carga.
#[derive(Clone)]
pub struct DropTarget {
    pub(crate) type_id: TypeId,
    pub(crate) handler: Handler<Rc<dyn Any>>,
}

impl DropTarget {
    pub fn new<T: Any>(handler: impl Fn(&T, &mut EventContext) + 'static) -> Self {
        let handler = move |value: &Rc<dyn Any>, cx: &mut EventContext| {
            if let Some(value) = value.downcast_ref::<T>() {
                handler(value, cx);
            }
        };
        Self { type_id: TypeId::of::<T>(), handler: Rc::new(handler) }
    }
}

/// Arrastre en curso; empieza «pendiente» hasta superar el umbral de distancia.
pub(crate) struct ActiveDrag {
    source: DragSource,
    origin: Point,
    pub(crate) started: bool,
}

impl Window {
    /// Al presionar sobre un origen de arrastre se prepara (aún no empieza).
    pub(crate) fn prepare_drag(&mut self, position: Point) {
        let pressed = self.mouse.pressed;
        let source =
            self.frame.handlers.iter().find(|entry| Some(entry.hitbox) == pressed).and_then(|e| e.drag.clone());
        self.drag = source.map(|source| ActiveDrag { source, origin: position, started: false });
    }

    /// Movimiento con el botón presionado; `true` si hay que redibujar la vista previa.
    pub(crate) fn update_drag(&mut self, position: Point) -> bool {
        let Some(drag) = self.drag.as_mut() else { return false };
        if !drag.started {
            let (dx, dy) = (position.x.0 - drag.origin.x.0, position.y.0 - drag.origin.y.0);
            drag.started = dx.abs() > DRAG_THRESHOLD || dy.abs() > DRAG_THRESHOLD;
        }
        drag.started
    }

    /// Al soltar: entrega la carga al destino más alto que acepte su tipo.
    /// Devuelve `None` si no había arrastre (y el clic debe seguir su curso normal).
    pub(crate) fn finish_drag(&mut self, position: Point) -> Option<bool> {
        let drag = self.drag.take().filter(|drag| drag.started)?;
        let type_id = drag.source.type_id();
        let target = self.frame.hitboxes.hits_at(position).find_map(|hit| {
            let entry = self.frame.handlers.iter().find(|entry| entry.hitbox == hit)?;
            entry.drops.iter().find(|target| target.type_id == type_id).map(|target| target.handler.clone())
        });
        let mut context = EventContext { entities: &mut self.entities, effects: Default::default() };
        context.effects.notify();
        context.effects.pointer = Some(position);
        if let Some(handler) = target {
            handler(&drag.source.value, &mut context);
        }
        let effects = context.effects;
        Some(self.finish(effects))
    }

    /// Tipo de la carga que se está arrastrando, para los estilos `drag_over`.
    pub fn active_drag_type(&self) -> Option<TypeId> {
        self.drag.as_ref().filter(|drag| drag.started).map(|drag| drag.source.type_id())
    }

    /// Vista previa que sigue al puntero mientras dura el arrastre.
    pub(crate) fn drag_preview(&self) -> Option<AnyElement> {
        use crate::view::element::{IntoElement, ParentElement};
        let drag = self.drag.as_ref().filter(|drag| drag.started)?;
        let at = Bounds::new(self.mouse.position + Point::new(px(8.0), px(8.0)), Size::default());
        Some(crate::view::overlay::anchored().anchor_to(at).child((drag.source.preview)()).into_element())
    }
}