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.
//! Eventos de alto nivel, contexto de los manejadores y estado interactivo.

use crate::core::{Action, CursorStyle, EntityMap, FocusHandle, HitboxId, Modifiers, MouseButton, Point};
use crate::view::drag::{DragSource, DropTarget};
use crate::view::key_context::KeyContext;
use crate::view::style::Style;
use std::any::TypeId;
use std::cell::Cell;
use std::rc::Rc;

#[derive(Clone, Copy, Debug)]
pub struct ClickEvent {
    pub position: Point,
    pub button: MouseButton,
    pub modifiers: Modifiers,
    /// 1 = clic simple, 2 = doble clic, 3 = triple clic.
    pub click_count: u32,
}

#[derive(Clone, Copy, Debug)]
pub struct ScrollEvent {
    pub position: Point,
    pub delta: Point,
}

#[derive(Clone, Copy, Debug)]
pub struct DragEvent {
    pub position: Point,
    pub modifiers: Modifiers,
}

#[derive(Clone, Debug)]
pub struct KeyDownEvent {
    pub keystroke: String,
    pub text: Option<String>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TextInputEvent {
    Insert(String),
    /// Composición IME en curso; `cursor` es la posición en bytes dentro de `text`.
    Preedit {
        text: String,
        cursor: Option<usize>,
    },
}

#[derive(Clone, Copy, Debug)]
pub struct ActionEvent {
    pub action: Action,
}

/// Efectos que un manejador pide al terminar.
#[derive(Default)]
pub struct Effects {
    pub(crate) redraw: bool,
    pub(crate) actions: Vec<Action>,
    pub(crate) propagate: bool,
    /// Posición del puntero al soltar un arrastre (solo en los manejadores de `on_drop`).
    pub(crate) pointer: Option<Point>,
}

impl Effects {
    pub fn notify(&mut self) {
        self.redraw = true;
    }

    /// La acción se despacha por la ruta de foco cuando termine este manejador.
    pub fn dispatch_action(&mut self, action: Action) {
        self.actions.push(action);
    }

    /// Deja que el evento siga subiendo hacia los ancestros.
    pub fn propagate(&mut self) {
        self.propagate = true;
    }
}

pub struct EventContext<'a> {
    pub entities: &'a mut EntityMap,
    pub effects: Effects,
}

impl EventContext<'_> {
    /// Dónde estaba el puntero al soltar (solo dentro de un `on_drop`).
    pub fn pointer(&self) -> Option<Point> {
        self.effects.pointer
    }

    pub fn notify(&mut self) {
        self.effects.notify();
    }

    pub fn dispatch_action(&mut self, action: Action) {
        self.effects.dispatch_action(action);
    }

    pub fn propagate(&mut self) {
        self.effects.propagate();
    }

    pub fn focus(&mut self, handle: &FocusHandle) {
        self.entities.focus.focus(handle);
        self.effects.notify();
    }
}

pub type Handler<E> = Rc<dyn Fn(&E, &mut EventContext)>;
pub type ClickHandler = Handler<ClickEvent>;
pub type ScrollHandler = Handler<ScrollEvent>;
pub type DragHandler = Handler<DragEvent>;
pub type KeyDownHandler = Handler<KeyDownEvent>;
pub type TextInputHandler = Handler<TextInputEvent>;
pub type ActionHandler = Handler<ActionEvent>;

/// Estado interactivo de un elemento: identificador, estilos de estado y manejadores.
#[derive(Default)]
pub struct Interactivity {
    pub id: Option<String>,
    pub hover_style: Option<Style>,
    pub active_style: Option<Style>,
    pub focus_style: Option<Style>,
    /// Estilos condicionales (`md:`, `dark:`), de menor a mayor punto de corte.
    pub variants: Vec<(crate::view::variants::Variant, Style)>,
    pub on_click: Option<ClickHandler>,
    pub on_mouse_down: Option<ClickHandler>,
    pub on_right_click: Option<ClickHandler>,
    pub on_scroll: Option<ScrollHandler>,
    pub on_drag: Option<DragHandler>,
    pub on_mouse_move: Option<DragHandler>,
    pub blocks_mouse: bool,
    pub record_bounds: Option<Rc<Cell<crate::core::Bounds>>>,
    pub tooltip: Option<Rc<str>>,
    pub focus: Option<FocusHandle>,
    pub tab_index: Option<i32>,
    pub key_context: Option<KeyContext>,
    pub actions: Vec<(Action, ActionHandler)>,
    pub on_key_down: Option<KeyDownHandler>,
    pub on_text_input: Option<TextInputHandler>,
    pub cursor: Option<CursorStyle>,
    /// Nombre de grupo: los descendientes con `group_hover` reaccionan al hover de este elemento.
    pub group: Option<Rc<str>>,
    pub group_hover: Option<(Rc<str>, Style)>,
    pub drag: Option<DragSource>,
    pub drops: Vec<DropTarget>,
    /// Estilos mientras se arrastra encima una carga del tipo indicado.
    pub drag_over: Vec<(TypeId, Style)>,
    /// Rol y etiqueta para lectores de pantalla.
    pub role: Option<accesskit::Role>,
    pub label: Option<Rc<str>>,
}

impl Interactivity {
    /// Solo los elementos interactivos necesitan un hitbox.
    pub fn needs_hitbox(&self, scrollable: bool) -> bool {
        self.hover_style.is_some()
            || self.active_style.is_some()
            || self.on_click.is_some()
            || self.on_mouse_down.is_some()
            || self.on_right_click.is_some()
            || self.on_scroll.is_some()
            || self.on_drag.is_some()
            || self.on_mouse_move.is_some()
            || self.focus.is_some()
            || self.blocks_mouse
            || self.tooltip.is_some()
            || self.cursor.is_some()
            || self.group.is_some()
            || self.drag.is_some()
            || !self.drops.is_empty()
            || !self.drag_over.is_empty()
            || scrollable
    }

    /// Solo los elementos con contexto, foco o manejadores de teclado entran al árbol de despacho.
    pub fn needs_dispatch_node(&self) -> bool {
        self.focus.is_some()
            || self.key_context.is_some()
            || !self.actions.is_empty()
            || self.on_key_down.is_some()
            || self.on_text_input.is_some()
    }
}

/// Manejadores de ratón que sobreviven al árbol de elementos hasta el siguiente frame.
#[derive(Clone)]
pub struct HandlerEntry {
    pub hitbox: HitboxId,
    pub on_click: Option<ClickHandler>,
    pub on_mouse_down: Option<ClickHandler>,
    pub on_right_click: Option<ClickHandler>,
    pub on_scroll: Option<ScrollHandler>,
    pub on_drag: Option<DragHandler>,
    pub on_mouse_move: Option<DragHandler>,
    pub scroll: Option<SharedScroll>,
    pub focus: Option<FocusHandle>,
    pub tooltip: Option<Rc<str>>,
    pub cursor: Option<CursorStyle>,
    pub drag: Option<DragSource>,
    pub drops: Vec<DropTarget>,
}

impl HandlerEntry {
    /// Entrada sin manejadores; se completa con la sintaxis `..HandlerEntry::new(hitbox)`.
    pub fn new(hitbox: HitboxId) -> Self {
        Self {
            hitbox,
            on_click: None,
            on_mouse_down: None,
            on_right_click: None,
            on_scroll: None,
            on_drag: None,
            on_mouse_move: None,
            scroll: None,
            focus: None,
            tooltip: None,
            cursor: None,
            drag: None,
            drops: Vec::new(),
        }
    }
}

/// Desplazamiento persistente de un contenedor con scroll.
#[derive(Clone, Copy, Debug, Default)]
pub struct ScrollState {
    pub offset_y: f32,
    pub max_y: f32,
    pub viewport_y: f32,
    /// Distancia agarrada del pulgar mientras se arrastra la barra de scroll.
    pub grab: Option<f32>,
}

/// Compartido entre el elemento, la ventana y los manejadores de la barra.
pub type SharedScroll = Rc<Cell<ScrollState>>;

impl ScrollState {
    pub fn scroll_by(shared: &SharedScroll, delta_y: f32) -> bool {
        let mut state = shared.get();
        let next = (state.offset_y - delta_y).clamp(0.0, state.max_y);
        let changed = next != state.offset_y;
        state.offset_y = next;
        shared.set(state);
        changed
    }
}