nightshade-api 0.46.0

Procedural high level API for the nightshade game engine
Documentation
//! UI that lives in the 3D world rather than on the screen: panels you can hang
//! on a wall, stand in space, or attach to an object. A panel is a group you
//! orient with [`set_rotation`](crate::prelude::set_rotation); buttons and
//! labels are placed in its local plane. Buttons are real meshes, so the regular
//! [`clicked_entity`](crate::prelude::clicked_entity) picking reports clicks on
//! them, and the camera can move around them like any other geometry.
//!
//! This composes world primitives (quads, 3D text, picking) rather than
//! projecting the screen-space widget tree, so the look is flat panels and
//! labels, not the retained-UI theme.

use nightshade::prelude::*;

use crate::appearance::{set_color, set_unlit};
use crate::picking::{clicked_entity, entity_under_cursor};
use crate::placement::{set_position, set_rotation, set_scale};
use crate::scene::{set_parent, spawn_group, spawn_plane};
use crate::text::spawn_label;

const FACE_FORWARD: f32 = -std::f32::consts::FRAC_PI_2;

fn oriented_quad(
    world: &mut World,
    parent: Entity,
    x: f32,
    y: f32,
    z: f32,
    size: Vec2,
    color: [f32; 4],
) -> Entity {
    let quad = spawn_plane(world, Vec3::zeros());
    set_parent(world, quad, Some(parent));
    set_position(world, quad, vec3(x, y, z));
    set_rotation(world, quad, Vec3::x(), FACE_FORWARD);
    set_scale(world, quad, vec3(size.x, 1.0, size.y));
    set_color(world, quad, color);
    set_unlit(world, quad, true);
    quad
}

/// Spawns a flat panel of `width` by `height` centered at `position`, standing
/// upright and facing +Z. Returns the panel group. Orient it with
/// [`set_rotation`](crate::prelude::set_rotation) and fill it with
/// [`world_panel_button`] and [`world_panel_label`], whose offsets are in the
/// panel's local plane (x right, y up).
pub fn spawn_world_panel(
    world: &mut World,
    position: Vec3,
    width: f32,
    height: f32,
    color: [f32; 4],
) -> Entity {
    let panel = spawn_group(world, position);
    oriented_quad(world, panel, 0.0, 0.0, 0.0, vec2(width, height), color);
    panel
}

/// Adds a button to a panel at local offset (`x`, `y`) sized `width` by
/// `height`, returning the button entity. Poll it with [`world_button_clicked`].
pub fn world_panel_button(
    world: &mut World,
    panel: Entity,
    x: f32,
    y: f32,
    width: f32,
    height: f32,
    color: [f32; 4],
) -> Entity {
    oriented_quad(world, panel, x, y, 0.01, vec2(width, height), color)
}

/// Adds a text label to a panel at local offset (`x`, `y`), returning the label
/// entity. Restyle it with [`set_text_color`](crate::prelude::set_text_color)
/// and [`set_text_size`](crate::prelude::set_text_size), update it with
/// [`set_text`](crate::prelude::set_text).
pub fn world_panel_label(world: &mut World, panel: Entity, text: &str, x: f32, y: f32) -> Entity {
    let label = spawn_label(world, text, Vec3::zeros());
    set_parent(world, label, Some(panel));
    set_position(world, label, vec3(x, y, 0.02));
    label
}

/// Whether the button mesh was clicked this frame, via the same world picking as
/// [`clicked_entity`](crate::prelude::clicked_entity).
pub fn world_button_clicked(world: &World, button: Entity) -> bool {
    clicked_entity(world) == Some(button)
}

/// Whether the cursor is currently over the button mesh.
pub fn world_button_hovered(world: &World, button: Entity) -> bool {
    entity_under_cursor(world) == Some(button)
}