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>, }
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)
}
}
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)
}
}