use macroquad::prelude::*;
use serde::Deserialize;
use crate::{gui::{alignment::{HorizontalAlignment, HorizontalAlignmentType, VerticalAlignment, VerticalAlignmentType}, gui_button::{ButtonState, GuiButton}, gui_dimension::{GuiDimension, GuiDimensionLoaderData}, resources::UiResolvedRects}, prelude::{ColorData, ComponentLoader, Context, Visible}};
#[derive(Debug, Clone)]
pub struct GuiBox {
pub width: GuiDimension,
pub height: GuiDimension,
pub color: Color,
pub screen_space: bool,
pub border_radius: f32,
}
impl Default for GuiBox {
fn default() -> Self {
Self {
width: GuiDimension::Pixels(100.0),
height: GuiDimension::Pixels(40.0),
color: Color::new(0.0, 0.0, 0.0, 1.0),
screen_space: true,
border_radius: 0.0
}
}
}
#[derive(Deserialize, Debug, Default)]
pub struct GuiBoxLoaderData {
#[serde(default)]
pub width: GuiDimensionLoaderData,
#[serde(default)]
pub height: GuiDimensionLoaderData,
pub color: ColorData,
pub screen_space: bool,
pub border_radius: f32
}
pub struct GuiBoxLoader;
impl ComponentLoader for GuiBoxLoader {
fn load(&self, ctx: &mut crate::prelude::Context, entity: hecs::Entity, data: &serde_json::Value) {
let loader_data: GuiBoxLoaderData = serde_json::from_value(data.clone())
.unwrap_or_default();
let parse_dimension = |loader_dim: GuiDimensionLoaderData| -> GuiDimension {
match loader_dim {
GuiDimensionLoaderData::Pixels(px) => GuiDimension::Pixels(px),
GuiDimensionLoaderData::Percent(s) => {
let value = s.trim_end_matches('%')
.parse::<f32>()
.unwrap_or(100.0);
GuiDimension::Percent(value / 100.0)
}
}
};
let component = GuiBox {
width: parse_dimension(loader_data.width),
height: parse_dimension(loader_data.height),
color: Color::new(
loader_data.color.r,
loader_data.color.g,
loader_data.color.b,
loader_data.color.a
),
screen_space: loader_data.screen_space,
border_radius: loader_data.border_radius
};
ctx.world.insert_one(entity, component).expect("Failed to insert GuiBox");
}
}
pub fn gui_box_render_system(ctx: &mut Context) {
let resolved_rects_map = &ctx.resource::<UiResolvedRects>().0;
let mut query = ctx.world.query::<(
&GuiBox,
Option<&GuiButton>,
Option<&Visible>,
Option<&HorizontalAlignment>,
Option<&VerticalAlignment>,
)>();
for (entity, (gui_box, button_opt, visibility, h_align, v_align)) in query.iter() {
let is_visible = visibility.map_or(true, |v| v.0);
if !is_visible || !gui_box.screen_space {
continue;
}
let (resolved_pos, resolved_size) =
if let Some(rect) = resolved_rects_map.get(&entity) {
*rect } else {
continue; };
let mut x = resolved_pos.x;
let mut y = resolved_pos.y;
let w = resolved_size.x;
let h = resolved_size.y;
if let Some(h_align) = h_align {
match h_align.0 {
HorizontalAlignmentType::Left => { }
HorizontalAlignmentType::Center => x -= w / 2.0,
HorizontalAlignmentType::Right => x -= w,
}
}
if let Some(v_align) = v_align {
match v_align.0 {
VerticalAlignmentType::Top => { }
VerticalAlignmentType::Center => y -= h / 2.0,
VerticalAlignmentType::Bottom => y -= h,
}
}
let radius = gui_box.border_radius.min(w / 2.0).min(h / 2.0);
let mut final_color = gui_box.color;
if let Some(button) = button_opt {
final_color = match button.state {
ButtonState::Hovered => button.hovered_color,
ButtonState::Pressed => button.pressed_color,
ButtonState::Idle => button.normal_color
};
}
if radius == 0.0 {
draw_rectangle(x, y, w, h, final_color);
} else {
let opaque_color = Color::new(final_color.r, final_color.g, final_color.b, 1.0);
let rt_w = w.max(1.0) as u32;
let rt_h = h.max(1.0) as u32;
let rt = render_target(rt_w, rt_h);
rt.texture.set_filter(FilterMode::Nearest);
let camera = Camera2D {
render_target: Some(rt.clone()),
zoom: vec2(2.0 / rt_w as f32, 2.0 / rt_h as f32),
target: vec2(rt_w as f32 / 2.0, rt_h as f32 / 2.0),
..Default::default()
};
set_camera(&camera);
clear_background(BLANK);
draw_rectangle(radius, 0.0, w - radius * 2.0, h, opaque_color);
draw_rectangle(0.0, radius, radius, h - radius * 2.0, opaque_color);
draw_rectangle(w - radius, radius, radius, h - radius * 2.0, opaque_color);
draw_circle(radius, radius, radius, opaque_color);
draw_circle(w - radius, radius, radius, opaque_color);
draw_circle(radius, h - radius, radius, opaque_color);
draw_circle(w - radius, h - radius, radius, opaque_color);
set_default_camera();
draw_texture_ex(
&rt.texture,
x, y, final_color, DrawTextureParams {
dest_size: Some(vec2(w, h)),
..Default::default()
},
);
}
}
}