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
use bevy_ecs::prelude::*;

use super::color::Color;
use super::widget::Widget;
use crate::scene::SceneEntity;

/// A single line of screen-centered text, drawn without a background.
///
/// Position is recomputed from [`super::ScreenSize`] every frame, so a
/// heading stays centered across window resizes without layout bookkeeping.
#[derive(Component, Clone, Debug)]
pub struct HeadingText {
    pub text: String,
    pub pixel_size: f32,
    pub color: Color,
    /// Horizontal center as a fraction of screen width (0.5 = middle).
    pub x_ratio: f32,
    /// Vertical center as a fraction of screen height (0.5 = middle).
    pub y_ratio: f32,
    /// Pixels to shift down from `y_ratio`, for text that should sit a fixed
    /// distance under a title rather than one that stretches with the window.
    pub y_offset: f32,
}

/// Builder for a [`HeadingText`].
///
/// ```no_run
/// # use codecraft::{AppState, ui::Heading};
/// # fn demo(app: &mut AppState) {
/// app.spawn(Heading::text("CHESS RS"));
/// # }
/// ```
pub struct Heading {
    heading: HeadingText,
}

impl Heading {
    pub const DEFAULT_PIXEL_SIZE: f32 = 8.0;

    pub fn text(text: impl Into<String>) -> Self {
        Self {
            heading: HeadingText {
                text: text.into(),
                pixel_size: Self::DEFAULT_PIXEL_SIZE,
                color: Color::WHITE,
                x_ratio: 0.5,
                y_ratio: 0.5,
                y_offset: 0.0,
            },
        }
    }

    /// Size of one bitmap-font pixel; the whole heading scales with it.
    pub fn pixel_size(mut self, pixel_size: f32) -> Self {
        self.heading.pixel_size = pixel_size;
        self
    }

    pub fn color(mut self, color: Color) -> Self {
        self.heading.color = color;
        self
    }

    /// Horizontal placement as a fraction of screen width (0.5 = middle).
    pub fn x_ratio(mut self, x_ratio: f32) -> Self {
        self.heading.x_ratio = x_ratio;
        self
    }

    /// Vertical placement as a fraction of screen height (0.5 = middle).
    pub fn y_ratio(mut self, y_ratio: f32) -> Self {
        self.heading.y_ratio = y_ratio;
        self
    }

    /// Pixels to shift down from [`Heading::y_ratio`].
    pub fn y_offset(mut self, y_offset: f32) -> Self {
        self.heading.y_offset = y_offset;
        self
    }
}

impl Widget for Heading {
    type Output = Entity;

    fn spawn(self, world: &mut World, _screen_width: f32, _screen_height: f32) -> Entity {
        world.spawn((self.heading, SceneEntity)).id()
    }
}