aura-gui 0.1.0

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.
//! Tooltips: aparecen tras `TOOLTIP_DELAY` con el puntero quieto sobre el elemento.
//!
//! El temporizador solo pide un frame cuando vence, así que no hay despertares
//! mientras nadie pasa el ratón por un elemento con tooltip.

use crate::view::any_element::AnyElement;
use crate::view::context::ElementContext;
use crate::view::div::div;
use crate::view::element::{IntoElement, ParentElement};
use crate::view::overlay::{Placement, anchored};
use crate::view::styled::Styled;
use crate::view::theme::Token;
use crate::view::window::Window;
use crate::core::{Bounds, px};
use std::rc::Rc;
use std::time::{Duration, Instant};

pub const TOOLTIP_DELAY: Duration = Duration::from_millis(600);

#[derive(Clone)]
pub struct TooltipHover {
    pub text: Rc<str>,
    pub anchor: Bounds,
    pub since: Instant,
}

impl Window {
    /// Tras mover el ratón: si cambió el elemento con tooltip bajo el puntero, reinicia la espera.
    pub(crate) fn track_tooltip(&mut self) -> bool {
        let position = self.mouse.position;
        let found = self.frame.hitboxes.hits_at(position).find_map(|hit| {
            let text = self.frame.handlers.iter().find(|entry| entry.hitbox == hit)?.tooltip.clone()?;
            Some((text, self.frame.hitboxes.bounds(hit)?))
        });
        let changed = match (&self.tooltip, &found) {
            (Some(current), Some((text, anchor))) => current.text != *text || current.anchor != *anchor,
            (None, None) => false,
            _ => true,
        };
        if !changed {
            return false;
        }
        let was_visible = self.tooltip.as_ref().is_some_and(|current| current.since.elapsed() >= TOOLTIP_DELAY);
        self.tooltip = found.map(|(text, anchor)| TooltipHover { text, anchor, since: Instant::now() });
        if self.tooltip.is_some() {
            self.entities.schedule(TOOLTIP_DELAY, |entities| entities.timers.request_animation_frame());
        }
        was_visible
    }

    /// El tooltip listo para mostrarse, como overlay anclado debajo del elemento.
    pub(crate) fn tooltip_element(&self) -> Option<AnyElement> {
        let hover = self.tooltip.as_ref().filter(|hover| hover.since.elapsed() >= TOOLTIP_DELAY)?;
        let bubble = div()
            .px_2()
            .py_1()
            .mt(px(4.0))
            .rounded_md()
            .border_1()
            .border_color(Token::Border)
            .bg(Token::ElevatedSurface)
            .shadow_md()
            .text_xs()
            .text_color(Token::Text)
            .whitespace_nowrap()
            .child(hover.text.to_string());
        Some(anchored().anchor_to(hover.anchor).placement(Placement::Below).child(bubble).into_element())
    }
}

/// Registra el tooltip en la capa de overlays del frame actual.
pub(crate) fn prepaint_tooltip(element: &mut AnyElement, cx: &mut ElementContext) {
    element.request_layout(cx);
    element.prepaint(cx);
}