beuvy-runtime 0.1.0

A low-level Bevy UI kit with reusable controls and utility-class styling.
Documentation
use super::*;
use crate::select::model::{Select, SelectPanel, SelectTrigger};
use bevy::prelude::*;
use bevy::ui::Val::{Auto, Px};
use bevy::window::PrimaryWindow;

fn computed_node(width: f32, height: f32) -> ComputedNode {
    ComputedNode {
        size: Vec2::new(width, height),
        inverse_scale_factor: 1.0,
        ..default()
    }
}

fn spawn_open_select(app: &mut App, trigger_y: f32) -> Entity {
    let root = app
        .world_mut()
        .spawn((
            Node {
                overflow: Overflow::scroll_y(),
                ..default()
            },
            computed_node(300.0, 300.0),
            UiGlobalTransform::default(),
        ))
        .id();
    let select = app.world_mut().spawn_empty().id();
    let trigger = app
        .world_mut()
        .spawn((
            SelectTrigger { select },
            computed_node(160.0, 32.0),
            UiGlobalTransform::from_translation(Vec2::new(0.0, trigger_y)),
        ))
        .id();
    let panel = app
        .world_mut()
        .spawn((
            SelectPanel,
            Node {
                display: Display::Flex,
                max_height: Px(360.0),
                ..default()
            },
            computed_node(160.0, 120.0),
        ))
        .id();

    app.world_mut().entity_mut(root).add_child(select);
    app.world_mut()
        .entity_mut(select)
        .add_children(&[trigger, panel]);
    app.world_mut().entity_mut(select).insert(Select {
        name: "select".to_string(),
        value: "a".to_string(),
        initial_value: "a".to_string(),
        options: Vec::new(),
        panel,
        trigger,
        chevron_glyph: Entity::PLACEHOLDER,
        open: true,
        disabled: false,
    });

    panel
}

#[test]
fn select_panel_flips_above_when_bottom_space_is_tight() {
    let mut app = App::new();
    app.add_systems(Update, sync_select_panel_placement);
    let panel = spawn_open_select(&mut app, 130.0);

    app.update();

    let node = app.world().entity(panel).get::<Node>().expect("panel node");
    assert_eq!(node.left, Px(70.0));
    assert_eq!(node.top, Px(138.0));
    assert_eq!(node.bottom, Auto);
    assert_eq!(node.min_width, Px(160.0));
    assert_eq!(node.margin.bottom, Px(0.0));
    assert_eq!(node.max_height, Px(258.0));
}

#[test]
fn select_panel_opens_below_when_bottom_space_is_available() {
    let mut app = App::new();
    app.add_systems(Update, sync_select_panel_placement);
    let panel = spawn_open_select(&mut app, 0.0);

    app.update();

    let node = app.world().entity(panel).get::<Node>().expect("panel node");
    assert_eq!(node.left, Px(70.0));
    assert_eq!(node.top, Px(172.0));
    assert_eq!(node.bottom, Auto);
    assert_eq!(node.min_width, Px(160.0));
    assert_eq!(node.margin.top, Px(0.0));
    assert_eq!(node.max_height, Px(128.0));
}

#[test]
fn select_panel_uses_window_space_without_clip_ancestor() {
    let mut app = App::new();
    app.world_mut().spawn((
        PrimaryWindow,
        Window {
            resolution: (800, 600).into(),
            ..default()
        },
    ));
    app.add_systems(Update, sync_select_panel_placement);

    let select = app.world_mut().spawn_empty().id();
    let trigger = app
        .world_mut()
        .spawn((
            SelectTrigger { select },
            computed_node(160.0, 32.0),
            UiGlobalTransform::from_translation(Vec2::new(400.0, 100.0)),
        ))
        .id();
    let panel = app
        .world_mut()
        .spawn((
            SelectPanel,
            Node {
                display: Display::Flex,
                max_height: Px(360.0),
                ..default()
            },
            computed_node(160.0, 120.0),
        ))
        .id();

    app.world_mut()
        .entity_mut(select)
        .add_children(&[trigger, panel]);
    app.world_mut().entity_mut(select).insert(Select {
        name: "select".to_string(),
        value: "a".to_string(),
        initial_value: "a".to_string(),
        options: Vec::new(),
        panel,
        trigger,
        chevron_glyph: Entity::PLACEHOLDER,
        open: true,
        disabled: false,
    });

    app.update();

    let node = app.world().entity(panel).get::<Node>().expect("panel node");
    assert_eq!(node.left, Px(320.0));
    assert_eq!(node.top, Px(122.0));
    assert_eq!(node.bottom, Auto);
    assert_eq!(node.min_width, Px(160.0));
    assert_eq!(node.margin.top, Px(0.0));
    assert!(matches!(node.max_height, Px(value) if value > 48.0));
}