aura-gui 0.2.2

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.
//! Puntero: forma del cursor, `group_hover` y arrastrar y soltar con carga tipada.

use aura_ui::view::prelude::*;
use aura_ui::view::test_context::TestWindow;

/// Carga de ejemplo: una pestaña identificada por índice.
#[derive(Clone, Copy, Debug, PartialEq)]
struct DraggedTab(usize);

#[derive(Default)]
struct Board {
    dropped: Vec<usize>,
    clicks: usize,
}

impl View for Board {
    fn view(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
        let tab = div()
            .id("tab")
            .w(px(100.0))
            .h(px(40.0))
            .cursor_pointer()
            .group("tab")
            .on_click(cx.listener(|this: &mut Board, _: &ClickEvent, cx| {
                this.clicks += 1;
                cx.notify();
            }))
            .draggable(DraggedTab(3), |tab| div().child(format!("pestaña {}", tab.0)).into_element())
            .child(div().w(px(10.0)).h(px(10.0)).group_hover("tab", |style| style.bg(Token::Accent)));
        let target = div()
            .w(px(200.0))
            .h(px(200.0))
            .cursor_text()
            .drag_over::<DraggedTab>(|style| style.bg(Token::Accent))
            .on_drop(cx.listener(|this: &mut Board, tab: &DraggedTab, cx| {
                this.dropped.push(tab.0);
                cx.notify();
            }));
        div().size_full().flex_col().child(tab).child(target).into_element()
    }
}

#[test]
fn cursor_follows_the_element_under_the_pointer() {
    let (mut test, _) = TestWindow::new(|_| Board::default());
    test.simulate_mouse_move(50.0, 20.0);
    assert_eq!(test.window.cursor_style(), CursorStyle::PointingHand);
    test.simulate_mouse_move(50.0, 100.0);
    assert_eq!(test.window.cursor_style(), CursorStyle::IBeam);
    test.simulate_mouse_move(500.0, 500.0);
    assert_eq!(test.window.cursor_style(), CursorStyle::Arrow);
}

#[test]
fn dropping_delivers_the_typed_payload_instead_of_clicking() {
    let (mut test, board) = TestWindow::new(|_| Board::default());
    test.simulate_mouse_move(50.0, 20.0);
    test.simulate_mouse_down(50.0, 20.0);
    test.simulate_mouse_move(60.0, 100.0);
    assert_eq!(test.window.cursor_style(), CursorStyle::Grabbing, "el arrastre ya empezó");
    test.simulate_mouse_up(60.0, 100.0);
    test.read(board, |board| {
        assert_eq!(board.dropped, [3]);
        assert_eq!(board.clicks, 0, "soltar no cuenta como clic");
    });
    test.simulate_click(50.0, 20.0);
    test.read(board, |board| assert_eq!(board.clicks, 1, "un clic sin moverse sigue siendo clic"));
}

#[test]
fn dropping_outside_any_target_does_nothing() {
    let (mut test, board) = TestWindow::new(|_| Board::default());
    test.simulate_drag((50.0, 20.0), (600.0, 500.0));
    test.read(board, |board| assert!(board.dropped.is_empty() && board.clicks == 0));
}

#[test]
fn group_hover_styles_a_child_when_the_group_is_hovered() {
    let (mut test, _) = TestWindow::new(|_| Board::default());
    let accent = test.window.theme.color(Token::Accent);
    let child_painted_accent =
        |test: &TestWindow| test.window.scene().quads.iter().any(|quad| quad.background == accent);
    assert!(!child_painted_accent(&test));
    // Sobre la pestaña pero fuera del cuadrito hijo: el grupo basta para resaltarlo.
    test.simulate_mouse_move(80.0, 30.0);
    assert!(child_painted_accent(&test));
}