shadow_engine_2d 2.0.1

A modern, high-performance 2D game engine built in Rust with ECS, physics, particles, audio, and more
Documentation
use crate::ecs::Component;
use crate::math::{Color, Vec2};

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Anchor {
    TopLeft,
    TopCenter,
    TopRight,
    CenterLeft,
    Center,
    CenterRight,
    BottomLeft,
    BottomCenter,
    BottomRight,
}

impl Anchor {
    pub fn to_offset(&self, screen_width: f32, screen_height: f32) -> Vec2 {
        match self {
            Anchor::TopLeft => Vec2::new(0.0, 0.0),
            Anchor::TopCenter => Vec2::new(screen_width / 2.0, 0.0),
            Anchor::TopRight => Vec2::new(screen_width, 0.0),
            Anchor::CenterLeft => Vec2::new(0.0, screen_height / 2.0),
            Anchor::Center => Vec2::new(screen_width / 2.0, screen_height / 2.0),
            Anchor::CenterRight => Vec2::new(screen_width, screen_height / 2.0),
            Anchor::BottomLeft => Vec2::new(0.0, screen_height),
            Anchor::BottomCenter => Vec2::new(screen_width / 2.0, screen_height),
            Anchor::BottomRight => Vec2::new(screen_width, screen_height),
        }
    }
}

#[derive(Clone, Debug)]
pub struct UITransform {
    pub anchor: Anchor,
    pub offset: Vec2,
    pub size: Vec2,
    pub z_index: i32,
}

impl UITransform {
    pub fn new(anchor: Anchor) -> Self {
        Self {
            anchor,
            offset: Vec2::ZERO,
            size: Vec2::new(100.0, 50.0),
            z_index: 0,
        }
    }

    pub fn with_offset(mut self, offset: Vec2) -> Self {
        self.offset = offset;
        self
    }

    pub fn with_size(mut self, size: Vec2) -> Self {
        self.size = size;
        self
    }

    pub fn with_z_index(mut self, z_index: i32) -> Self {
        self.z_index = z_index;
        self
    }

    pub fn get_screen_position(&self, screen_width: f32, screen_height: f32) -> Vec2 {
        self.anchor.to_offset(screen_width, screen_height) + self.offset
    }

    pub fn contains_point(&self, point: Vec2, screen_width: f32, screen_height: f32) -> bool {
        let pos = self.get_screen_position(screen_width, screen_height);
        point.x >= pos.x
            && point.x <= pos.x + self.size.x
            && point.y >= pos.y
            && point.y <= pos.y + self.size.y
    }
}

impl Component for UITransform {}

#[derive(Clone, Debug)]
pub struct UIText {
    pub text: String,
    pub font_size: f32,
    pub color: Color,
    pub alignment: TextAlignment,
}

impl UIText {
    pub fn new(text: impl Into<String>) -> Self {
        Self {
            text: text.into(),
            font_size: 16.0,
            color: Color::WHITE,
            alignment: TextAlignment::Left,
        }
    }

    pub fn with_size(mut self, size: f32) -> Self {
        self.font_size = size;
        self
    }

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

    pub fn with_alignment(mut self, alignment: TextAlignment) -> Self {
        self.alignment = alignment;
        self
    }
}

impl Component for UIText {}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum TextAlignment {
    Left,
    Center,
    Right,
}

#[derive(Clone, Debug)]
pub struct UIPanel {
    pub background_color: Color,
    pub border_color: Option<Color>,
    pub border_width: f32,
    pub corner_radius: f32,
}

impl UIPanel {
    pub fn new() -> Self {
        Self {
            background_color: Color::rgb(0.2, 0.2, 0.2),
            border_color: None,
            border_width: 0.0,
            corner_radius: 0.0,
        }
    }

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

    pub fn with_border(mut self, color: Color, width: f32) -> Self {
        self.border_color = Some(color);
        self.border_width = width;
        self
    }

    pub fn with_corner_radius(mut self, radius: f32) -> Self {
        self.corner_radius = radius;
        self
    }
}

impl Default for UIPanel {
    fn default() -> Self {
        Self::new()
    }
}

impl Component for UIPanel {}

#[derive(Clone, Debug)]
pub struct UIButton {
    pub normal_color: Color,
    pub hover_color: Color,
    pub pressed_color: Color,
    pub disabled_color: Color,
    pub state: ButtonState,
    pub enabled: bool,
    pub on_click: Option<String>, // Event ID
}

impl UIButton {
    pub fn new() -> Self {
        Self {
            normal_color: Color::rgb(0.3, 0.3, 0.3),
            hover_color: Color::rgb(0.4, 0.4, 0.4),
            pressed_color: Color::rgb(0.2, 0.2, 0.2),
            disabled_color: Color::rgb(0.15, 0.15, 0.15),
            state: ButtonState::Normal,
            enabled: true,
            on_click: None,
        }
    }

    pub fn with_colors(mut self, normal: Color, hover: Color, pressed: Color) -> Self {
        self.normal_color = normal;
        self.hover_color = hover;
        self.pressed_color = pressed;
        self
    }

    pub fn with_on_click(mut self, event_id: impl Into<String>) -> Self {
        self.on_click = Some(event_id.into());
        self
    }

    pub fn get_current_color(&self) -> Color {
        if !self.enabled {
            return self.disabled_color;
        }
        match self.state {
            ButtonState::Normal => self.normal_color,
            ButtonState::Hovered => self.hover_color,
            ButtonState::Pressed => self.pressed_color,
        }
    }
}

impl Default for UIButton {
    fn default() -> Self {
        Self::new()
    }
}

impl Component for UIButton {}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ButtonState {
    Normal,
    Hovered,
    Pressed,
}

#[derive(Clone, Debug)]
pub struct UIImage {
    pub texture_name: String,
    pub tint: Color,
    pub preserve_aspect: bool,
}

impl UIImage {
    pub fn new(texture_name: impl Into<String>) -> Self {
        Self {
            texture_name: texture_name.into(),
            tint: Color::WHITE,
            preserve_aspect: true,
        }
    }

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

impl Component for UIImage {}

#[derive(Clone, Debug)]
pub struct UIProgressBar {
    pub value: f32,
    pub max_value: f32,
    pub fill_color: Color,
    pub background_color: Color,
    pub border_color: Option<Color>,
    pub border_width: f32,
}

impl UIProgressBar {
    pub fn new(max_value: f32) -> Self {
        Self {
            value: 0.0,
            max_value,
            fill_color: Color::GREEN,
            background_color: Color::rgb(0.2, 0.2, 0.2),
            border_color: Some(Color::WHITE),
            border_width: 2.0,
        }
    }

    pub fn with_value(mut self, value: f32) -> Self {
        self.value = value.clamp(0.0, self.max_value);
        self
    }

    pub fn with_colors(mut self, fill: Color, background: Color) -> Self {
        self.fill_color = fill;
        self.background_color = background;
        self
    }

    pub fn get_fill_percentage(&self) -> f32 {
        if self.max_value == 0.0 {
            0.0
        } else {
            (self.value / self.max_value).clamp(0.0, 1.0)
        }
    }

    pub fn set_value(&mut self, value: f32) {
        self.value = value.clamp(0.0, self.max_value);
    }
}

impl Component for UIProgressBar {}

pub struct UISystem {
    screen_width: f32,
    screen_height: f32,
    mouse_position: Vec2,
    mouse_pressed: bool,
    mouse_just_pressed: bool,
    mouse_just_released: bool,
}

impl UISystem {
    pub fn new(screen_width: f32, screen_height: f32) -> Self {
        Self {
            screen_width,
            screen_height,
            mouse_position: Vec2::ZERO,
            mouse_pressed: false,
            mouse_just_pressed: false,
            mouse_just_released: false,
        }
    }

    pub fn resize(&mut self, width: f32, height: f32) {
        self.screen_width = width;
        self.screen_height = height;
    }

    pub fn update_mouse(&mut self, position: Vec2, pressed: bool, just_pressed: bool, just_released: bool) {
        self.mouse_position = position;
        self.mouse_pressed = pressed;
        self.mouse_just_pressed = just_pressed;
        self.mouse_just_released = just_released;
    }

    pub fn screen_size(&self) -> (f32, f32) {
        (self.screen_width, self.screen_height)
    }

    pub fn mouse_position(&self) -> Vec2 {
        self.mouse_position
    }
}

impl Default for UISystem {
    fn default() -> Self {
        Self::new(1280.0, 720.0)
    }
}

// Helper builders
pub struct UIBuilder;

impl UIBuilder {
    pub fn text(text: impl Into<String>, anchor: Anchor, offset: Vec2) -> (UITransform, UIText) {
        let transform = UITransform::new(anchor).with_offset(offset);
        let text = UIText::new(text);
        (transform, text)
    }

    pub fn button(
        anchor: Anchor,
        offset: Vec2,
        size: Vec2,
        label: impl Into<String>,
    ) -> (UITransform, UIButton, UIPanel, UIText) {
        let transform = UITransform::new(anchor).with_offset(offset).with_size(size);
        let button = UIButton::new();
        let panel = UIPanel::new();
        let text = UIText::new(label).with_alignment(TextAlignment::Center);
        (transform, button, panel, text)
    }

    pub fn panel(anchor: Anchor, offset: Vec2, size: Vec2) -> (UITransform, UIPanel) {
        let transform = UITransform::new(anchor).with_offset(offset).with_size(size);
        let panel = UIPanel::new();
        (transform, panel)
    }

    pub fn progress_bar(
        anchor: Anchor,
        offset: Vec2,
        size: Vec2,
        max_value: f32,
    ) -> (UITransform, UIProgressBar) {
        let transform = UITransform::new(anchor).with_offset(offset).with_size(size);
        let bar = UIProgressBar::new(max_value);
        (transform, bar)
    }
}