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 a thing in the scene *is*, as far as the tools are concerned.
//!
//! The renderer knows an entity has a mesh and a transform. That is enough to
//! draw it and nothing like enough to list it: an outliner row needs a name,
//! an icon and a category to tint it by, and none of those can be worked out
//! from a vertex buffer. So a scene says what its objects are by implementing
//! [`SceneObject`] once per *kind* and hanging a [`SceneItem`] on each entity.
//!
//! One implementation per kind, not per entity: thirty-two chess pieces are
//! six kinds, and the kind is what carries the icon.
//!
//! Reference: `substrate/src/sceneobjects/mod.rs` in the robot2 tree.
use bevy_ecs::prelude::*;

use super::category::Category;
use crate::AppState;

/// A kind of thing a scene can contain.
pub trait SceneObject: Send + Sync + 'static {
    /// What this kind is called, in the singular: "Pawn", "Spot Light".
    fn label(&self) -> &'static str;

    /// The icon shown beside that label, as a [`crate::ui::icons::path`]
    /// constant. It has to be one of the ones compiled in -- see
    /// `ui::icons::embedded` -- or the row draws a blank.
    fn icon(&self) -> &'static str;

    /// Which group this kind belongs to. The outliner tints a row by it, so
    /// a light and a mesh are told apart without reading either name.
    fn category(&self) -> Category;

    /// Whether this kind is *content*: something a person makes, keeps, and
    /// would expect to find again. Almost everything is, hence the default.
    ///
    /// The one that is not is the scene itself, which is the thing the others
    /// are in rather than a thing in it.
    fn authored(&self) -> bool {
        true
    }

    /// Puts one of these in the scene, with whatever settings a fresh one
    /// has, and answers with the entity it made.
    ///
    /// The kind is what knows how to build one -- which mesh, which
    /// components, what to call it -- so this is where "add one of those"
    /// lives, and an "Add" menu can be a list of kinds rather than a match
    /// arm per kind somewhere else.
    ///
    /// A default one, not a placed one: the caller decides where it goes.
    /// For kinds that need more than a default to be meaningful -- a pawn is
    /// a side and a square, not just a pawn -- this is the white one at the
    /// origin, and the scene moves it from there.
    fn spawn(&self, app: &mut AppState) -> Entity;
}

/// What an entity is, and what to call this particular one.
///
/// The `kind` is shared -- every pawn points at the same [`SceneObject`] --
/// and the name is not, because "Pawn" is what a pawn is and "White Pawn e2"
/// is which one.
#[derive(Component)]
pub struct SceneItem {
    pub kind: &'static dyn SceneObject,
    pub name: String,
}

impl SceneItem {
    pub fn new(kind: &'static dyn SceneObject, name: impl Into<String>) -> Self {
        Self {
            kind,
            name: name.into(),
        }
    }

    pub fn icon(&self) -> &'static str {
        self.kind.icon()
    }

    pub fn category(&self) -> Category {
        self.kind.category()
    }
}

impl std::fmt::Debug for SceneItem {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("SceneItem")
            .field("kind", &self.kind.label())
            .field("name", &self.name)
            .finish()
    }
}