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.
//! Estado retenido con identificadores tipados, al estilo de `Entity<T>` de GPUI.
//!
//! Durante `update` la entidad se "presta": sale de su slot y vuelve al terminar.
//! Así el cierre recibe a la vez `&mut T` y el mapa completo, y puede actualizar
//! otras entidades anidadas sin conflicto de préstamos.

use crate::core::animation::AnimationClock;
use crate::core::async_context::{AsyncContext, LocalExecutor};
use crate::core::clipboard::{ClipboardProvider, MemoryClipboard};
use crate::core::entity_events::{EventQueue, Subscription};
use crate::core::focus::FocusState;
use crate::core::task::{Cancellable, Task};
use crate::core::tasks::{BackgroundExecutor, ForegroundSender, Inbox};
use crate::core::timers::{TimerId, TimerQueue};
use slotmap::{SlotMap, new_key_type};
use std::any::Any;
use std::future::Future;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};

new_key_type! {
    /// Identificador estable y con generación de una entidad.
    pub struct EntityId;
}

pub use crate::core::handle::Handle;

pub struct EntityMap {
    /// `None` mientras la entidad está prestada a un `update`.
    slots: SlotMap<EntityId, Option<Box<dyn Any>>>,
    dirty: Vec<EntityId>,
    /// Entidades que no piden redibujar (un panel fuera de la vista que sigue trabajando).
    quiet: Vec<EntityId>,
    pub(crate) events: EventQueue,
    depth: usize,
    /// Servicios de la app: temporizadores, pool de trabajo e inbox del hilo principal.
    pub timers: TimerQueue,
    pub background: BackgroundExecutor,
    pub inbox: Inbox,
    pub focus: FocusState,
    pub animations: AnimationClock,
    pub clipboard: Box<dyn ClipboardProvider>,
    /// Acciones pedidas desde el estado; la ventana las despacha por la ruta de foco.
    pub pending_actions: Vec<crate::core::action::Action>,
    /// Futuros del hilo principal (`spawn_local`, `EntityContext::spawn`).
    pub local: LocalExecutor,
    /// Tipo de la carga que se está arrastrando (la ventana lo actualiza antes de cada frame): así una vista puede
    /// mostrar zonas de soltado solo mientras dura el arrastre.
    pub drag_type: Option<std::any::TypeId>,
    /// Atajo de varias teclas a medias (`ctrl-w`, `mod-k`…) y cómo puede seguir: la ventana lo actualiza para que
    /// la app muestre una ayuda al estilo which-key.
    pub key_hint: Option<crate::core::key_hint::KeyHint>,
}

impl Default for EntityMap {
    fn default() -> Self {
        Self {
            slots: SlotMap::default(),
            dirty: Vec::new(),
            quiet: Vec::new(),
            events: EventQueue::default(),
            depth: 0,
            timers: TimerQueue::default(),
            background: BackgroundExecutor::default(),
            inbox: Inbox::default(),
            focus: FocusState::default(),
            animations: AnimationClock::default(),
            clipboard: Box::new(MemoryClipboard::default()),
            pending_actions: Vec::new(),
            local: LocalExecutor::default(),
            drag_type: None,
            key_hint: None,
        }
    }
}

impl EntityMap {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn insert<T: Any>(&mut self, value: T) -> Handle<T> {
        let id = self.slots.insert(Some(Box::new(value)));
        Handle { id, marker: PhantomData }
    }

    pub fn read<T: Any>(&self, handle: Handle<T>) -> Option<&T> {
        self.slots.get(handle.id)?.as_ref()?.downcast_ref::<T>()
    }

    /// Presta la entidad al cierre. Devuelve `None` si no existe o ya está prestada
    /// (una actualización reentrante de la misma entidad).
    pub fn update<T: Any, R>(
        &mut self,
        handle: Handle<T>,
        mutate: impl FnOnce(&mut T, &mut EntityContext<T>) -> R,
    ) -> Option<R> {
        let mut boxed = self.slots.get_mut(handle.id)?.take()?;
        self.depth += 1;
        let result = match boxed.downcast_mut::<T>() {
            Some(value) => {
                let mut cx = EntityContext { entities: self, handle };
                Some(mutate(value, &mut cx))
            }
            None => None,
        };
        self.depth -= 1;
        if let Some(slot) = self.slots.get_mut(handle.id) {
            *slot = Some(boxed);
        }
        self.mark_dirty(handle.id);
        if self.depth == 0 {
            self.flush_events();
        }
        result
    }

    pub fn mark_dirty(&mut self, id: EntityId) {
        if !self.dirty.contains(&id) && !self.quiet.contains(&id) {
            self.dirty.push(id);
        }
    }

    /// Una entidad silenciosa sigue trabajando pero su `notify` no despierta la ventana: nadie la está viendo.
    /// Al dejar de serlo se marca para repintar.
    pub fn set_quiet(&mut self, id: EntityId, quiet: bool) {
        self.quiet.retain(|other| *other != id);
        if quiet {
            self.quiet.push(id);
        } else {
            self.mark_dirty(id);
        }
    }

    pub fn remove(&mut self, id: EntityId) -> bool {
        self.quiet.retain(|other| *other != id);
        self.events.forget(id);
        self.slots.remove(id).is_some()
    }

    pub fn contains(&self, id: EntityId) -> bool {
        self.slots.contains_key(id)
    }

    pub fn take_dirty(&mut self) -> Vec<EntityId> {
        std::mem::take(&mut self.dirty)
    }

    pub fn is_dirty(&self) -> bool {
        !self.dirty.is_empty()
    }

    pub fn foreground(&self) -> ForegroundSender {
        self.inbox.sender()
    }

    pub fn schedule(&mut self, delay: std::time::Duration, job: impl FnOnce(&mut EntityMap) + 'static) -> TimerId {
        self.timers.schedule(delay, job)
    }

    /// Ejecuta temporizadores vencidos y trabajos llegados de otros hilos.
    /// Devuelve `true` si se ejecutó algo.
    pub fn run_pending(&mut self, now: std::time::Instant) -> bool {
        let mut ran = false;
        for job in self.timers.take_due(now) {
            job(self);
            ran = true;
        }
        // Los futuros encolan trabajo en el inbox y el inbox despierta futuros:
        // se alterna hasta que ninguno de los dos tenga nada pendiente.
        loop {
            let sender = self.inbox.sender();
            let mut progressed = self.local.poll_ready(&sender);
            while let Some(job) = self.inbox.try_next() {
                job(self);
                progressed = true;
            }
            if !progressed {
                return ran;
            }
            ran = true;
        }
    }

    /// Ejecuta un futuro en el hilo principal; se sondea cuando algo lo despierta.
    pub fn spawn_local(&mut self, future: impl Future<Output = ()> + 'static) {
        let sender = self.inbox.sender();
        self.local.spawn(future, &sender);
    }

    pub fn async_context(&self) -> AsyncContext {
        AsyncContext { sender: self.inbox.sender(), background: self.background.clone() }
    }

    /// Suscribe `subscriber` a los eventos `E` que emita `emitter`.
    pub fn subscribe<T: Any, S: Any, E: Any>(
        &mut self,
        emitter: Handle<T>,
        subscriber: Handle<S>,
        callback: impl Fn(&mut S, &E, &mut EntityContext<S>) + 'static,
    ) -> Subscription {
        self.events.subscribe(emitter.id, subscriber.id, move |entities, event| {
            let Some(event) = event.downcast_ref::<E>() else { return };
            entities.update(subscriber, |state, cx| callback(state, event, cx));
        })
    }

    pub fn unsubscribe(&mut self, subscription: Subscription) {
        self.events.unsubscribe(subscription);
    }

    /// Entrega los eventos pendientes; un suscriptor puede emitir nuevos eventos.
    fn flush_events(&mut self) {
        while let Some((emitter, event)) = self.events.pop() {
            for callback in self.events.listeners(emitter) {
                callback(self, event.as_ref());
            }
        }
    }
}

/// Contexto de una actualización: da acceso al mapa (para otras entidades) y a
/// operaciones propias de la entidad prestada.
pub struct EntityContext<'a, T> {
    entities: &'a mut EntityMap,
    handle: Handle<T>,
}

impl<T: Any> EntityContext<'_, T> {
    /// Como `cx.spawn` de GPUI: el futuro recibe el handle de esta entidad y un
    /// `AsyncContext`. Soltar la `Task` devuelta cancela el futuro en su siguiente punto de espera.
    pub fn spawn<R: 'static, F: Future<Output = R> + 'static>(
        &mut self,
        body: impl FnOnce(Handle<T>, AsyncContext) -> F,
    ) -> Task<R> {
        let (task, completer) = Task::pending();
        let cancel = task.cancel_token();
        let future = body(self.handle(), self.entities.async_context());
        self.entities.spawn_local(async move {
            if let Some(value) = (Cancellable { future: Box::pin(future), cancel }).await {
                completer.complete(value);
            }
        });
        task
    }

    pub fn handle(&self) -> Handle<T> {
        self.handle
    }

    /// Encola un evento; se entrega al terminar la actualización más externa.
    pub fn emit<E: Any>(&mut self, event: E) {
        self.entities.events.push(self.handle.id, Box::new(event));
    }

    pub fn notify(&mut self) {
        self.entities.mark_dirty(self.handle.id);
    }

    /// Pide que la ventana despache `action` cuando termine el evento actual.
    pub fn dispatch_action(&mut self, action: crate::core::action::Action) {
        self.entities.pending_actions.push(action);
    }
}

impl<T> Deref for EntityContext<'_, T> {
    type Target = EntityMap;

    fn deref(&self) -> &EntityMap {
        self.entities
    }
}

impl<T> DerefMut for EntityContext<'_, T> {
    fn deref_mut(&mut self) -> &mut EntityMap {
        self.entities
    }
}

#[cfg(test)]
#[path = "entity_tests.rs"]
mod tests;