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::font;
use super::widget::Widget;
use crate::scene::SceneEntity;

/// A line of text pinned to a pixel position, drawn left-aligned from its
/// top-left corner.
///
/// The difference from [`super::HeadingText`] is what happens when the text
/// changes: a heading is centred, so a readout that grows a word jumps
/// sideways. Anything being watched while it updates — a debug overlay, a
/// clock — wants to stay where it was put.
#[derive(Component, Clone, Debug)]
pub struct TextLine {
    pub text: String,
    pub x: f32,
    pub y: f32,
    pub pixel_size: f32,
    pub color: Color,
}

impl TextLine {
    /// How wide this line is as drawn, for sizing whatever sits behind it.
    pub fn width(&self) -> f32 {
        font::text_width(&self.text, self.pixel_size)
    }
}

/// Builder for a [`TextLine`].
///
/// ```no_run
/// # use codecraft::{AppState, ui::Text};
/// # fn demo(app: &mut AppState) {
/// app.spawn(Text::at(16.0, 16.0, "FPS 60"));
/// # }
/// ```
pub struct Text {
    line: TextLine,
}

impl Text {
    pub const DEFAULT_PIXEL_SIZE: f32 = 2.0;

    pub fn at(x: f32, y: f32, text: impl Into<String>) -> Self {
        Self {
            line: TextLine {
                text: text.into(),
                x,
                y,
                pixel_size: Self::DEFAULT_PIXEL_SIZE,
                color: Color::WHITE,
            },
        }
    }

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

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

impl Widget for Text {
    type Output = Entity;

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