codecraft 0.1.1

A minimalist 3D game engine built on parts of Bevy (ECS, color) with wgpu and winit: OpenPBR materials, clustered lighting, an immediate-mode UI, audio and gamepad haptics
Documentation
//! What the dev UI is made of: the badge in the corner and the menu it
//! opens.
//!
//! Apart from [`super`] because this is the *shape* of the thing -- which
//! entries there are, what they are called, what they open -- and that is what
//! changes most often. The switch that turns it on and the systems that put it
//! up and take it down do not change when a menu grows an entry.
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};

use crate::ecs::World;
use crate::ui::icons::path;
use crate::ui::{Anchor, Button, MenuLayout, Panel};

/// The dev UI is drawn at the menu metrics' own size rather than scaled down
/// from a game menu's: an application menu already has a size, and it is this
/// one. See [`crate::ui::layout::Metrics::menu`].
pub const SCALE: f32 = 1.0;

/// The badge: a button now rather than a label, because it opens the menu.
///
/// A spanner for what it is and a caret for what it does. The caret is turned
/// over when the menu is up -- see [`dev_menu_system`] -- because an arrow
/// that says "up" while the thing it opened is already up says nothing.
pub fn badge(world: &mut World, open: Arc<AtomicBool>, width: f32, height: f32) -> MenuLayout {
    Panel::new("DevBadge")
        .anchor(Anchor::BottomRight)
        .scale(SCALE)
        .menu()
        .add(
            Button::new("DEV", move || {
                open.fetch_xor(true, Ordering::Relaxed);
            })
            .icon(path::WRENCH)
            .trailing(path::CARET_UP),
        )
        .spawn(world, width, height)
}

/// The menu itself, sitting directly above the badge.
///
/// The shape is the one `substrate`'s app menu has -- App, File, Create,
/// View -- because a person who knows one should find their way around the
/// other. What is wired up is another matter: the entries that do nothing yet
/// are here so the shape is visible and so adding the behaviour later is a
/// closure rather than a layout.
///
/// Create is not here. It builds itself from what a scene registers, and this
/// renderer has no registry yet -- an empty Create menu is worse than none.
pub fn menu(world: &mut World, open: Arc<AtomicBool>, width: f32, height: f32) -> MenuLayout {
    // Clear of the badge: one row and the panel padding it sits in.
    let metrics = crate::ui::layout::Metrics::menu(SCALE);
    let above = -(metrics.button_height + crate::ui::layout::PANEL_PADDING * 2.0);

    let outliner = open.clone();
    Panel::new("DevMenu")
        .anchor(Anchor::BottomRight)
        .scale(SCALE)
        .menu()
        .offset(0.0, above)
        .add(Button::submenu("APP", |menu| {
            menu.add(Button::label("PLAY"))
                .add(Button::label("EDIT"))
                .add(Button::label("CONNECTIONS"))
                .add(Button::label("SETTINGS"))
                .add(Button::label("ABOUT"))
                .add(Button::new("EXIT", || std::process::exit(0)))
        }))
        .add(Button::submenu("FILE", |menu| {
            menu.add(Button::label("IMPORT MODEL"))
                .add(Button::label("OPEN SCENE"))
                .add(Button::label("SAVE SCENE AS"))
        }))
        .add(Button::submenu("VIEW", move |menu| {
            let outliner = outliner.clone();
            menu.add(Button::new("OUTLINER", move || {
                outliner.store(true, Ordering::Relaxed);
            }))
            .add(Button::label("INSTANCES"))
            .add(Button::label("PROPERTIES"))
            .add(Button::label("TIMELINE"))
            .add(Button::label("ACTIONS"))
            .add(Button::label("SCREENSHOT"))
            .add(Button::label("RECENTER VIEW"))
        }))
        .spawn(world, width, height)
}


/// The badge is a panel and one button.
pub const BADGE_ENTITIES: usize = 2;