1use macroquad::prelude::*;
2use serde::Deserialize;
3use crate::{gui::{alignment::{HorizontalAlignment, HorizontalAlignmentType, VerticalAlignment, VerticalAlignmentType}, gui_button::{ButtonState, GuiButton}, gui_dimension::{GuiDimension, GuiDimensionLoaderData}, resources::UiResolvedRects}, prelude::{ColorData, ComponentLoader, Context, Visible}};
4
5#[derive(Debug, Clone)]
6pub struct GuiBox {
7 pub width: GuiDimension,
8 pub height: GuiDimension,
9 pub color: Color,
10 pub screen_space: bool,
11 pub border_radius: f32,
12}
13
14impl Default for GuiBox {
15 fn default() -> Self {
16 Self {
17 width: GuiDimension::Pixels(100.0),
18 height: GuiDimension::Pixels(40.0),
19 color: Color::new(0.0, 0.0, 0.0, 1.0),
20 screen_space: true,
21 border_radius: 0.0
22 }
23 }
24}
25
26#[derive(Deserialize, Debug, Default)]
27pub struct GuiBoxLoaderData {
28 #[serde(default)]
29 pub width: GuiDimensionLoaderData,
30
31 #[serde(default)]
32 pub height: GuiDimensionLoaderData,
33
34 pub color: ColorData,
35 pub screen_space: bool,
36 pub border_radius: f32
37}
38
39pub struct GuiBoxLoader;
40
41impl ComponentLoader for GuiBoxLoader {
42 fn load(&self, ctx: &mut crate::prelude::Context, entity: hecs::Entity, data: &serde_json::Value) {
43 let loader_data: GuiBoxLoaderData = serde_json::from_value(data.clone())
44 .unwrap_or_default();
45
46 let parse_dimension = |loader_dim: GuiDimensionLoaderData| -> GuiDimension {
47 match loader_dim {
48 GuiDimensionLoaderData::Pixels(px) => GuiDimension::Pixels(px),
49 GuiDimensionLoaderData::Percent(s) => {
50 let value = s.trim_end_matches('%')
52 .parse::<f32>()
53 .unwrap_or(100.0); GuiDimension::Percent(value / 100.0)
57 }
58 }
59 };
60
61 let component = GuiBox {
62 width: parse_dimension(loader_data.width),
63 height: parse_dimension(loader_data.height),
64 color: Color::new(
65 loader_data.color.r,
66 loader_data.color.g,
67 loader_data.color.b,
68 loader_data.color.a
69 ),
70 screen_space: loader_data.screen_space,
71 border_radius: loader_data.border_radius
72 };
73
74 ctx.world.insert_one(entity, component).expect("Failed to insert GuiBox");
75 }
76}
77
78pub fn gui_box_render_system(ctx: &mut Context) {
79 let resolved_rects_map = &ctx.resource::<UiResolvedRects>().0;
81
82 let mut query = ctx.world.query::<(
83 &GuiBox,
84 Option<&GuiButton>,
85 Option<&Visible>,
86 Option<&HorizontalAlignment>,
87 Option<&VerticalAlignment>,
88 )>();
89
90 for (entity, (gui_box, button_opt, visibility, h_align, v_align)) in query.iter() {
91 let is_visible = visibility.map_or(true, |v| v.0);
92 if !is_visible || !gui_box.screen_space {
93 continue;
94 }
95
96 let (resolved_pos, resolved_size) =
98 if let Some(rect) = resolved_rects_map.get(&entity) {
99 *rect } else {
101 continue; };
103
104 let mut x = resolved_pos.x;
105 let mut y = resolved_pos.y;
106 let w = resolved_size.x;
107 let h = resolved_size.y;
108 if let Some(h_align) = h_align {
112 match h_align.0 {
113 HorizontalAlignmentType::Left => { }
114 HorizontalAlignmentType::Center => x -= w / 2.0,
115 HorizontalAlignmentType::Right => x -= w,
116 }
117 }
118 if let Some(v_align) = v_align {
119 match v_align.0 {
120 VerticalAlignmentType::Top => { }
121 VerticalAlignmentType::Center => y -= h / 2.0,
122 VerticalAlignmentType::Bottom => y -= h,
123 }
124 }
125
126 let radius = gui_box.border_radius.min(w / 2.0).min(h / 2.0);
127
128 let mut final_color = gui_box.color;
130 if let Some(button) = button_opt {
131 final_color = match button.state {
132 ButtonState::Hovered => button.hovered_color,
133 ButtonState::Pressed => button.pressed_color,
134 ButtonState::Idle => button.normal_color
135 };
136 }
137
138 if radius == 0.0 {
140 draw_rectangle(x, y, w, h, final_color);
141 } else {
142 let opaque_color = Color::new(final_color.r, final_color.g, final_color.b, 1.0);
144
145 let rt_w = w.max(1.0) as u32;
147 let rt_h = h.max(1.0) as u32;
148 let rt = render_target(rt_w, rt_h);
149 rt.texture.set_filter(FilterMode::Nearest);
150
151 let camera = Camera2D {
153 render_target: Some(rt.clone()),
154 zoom: vec2(2.0 / rt_w as f32, 2.0 / rt_h as f32),
155 target: vec2(rt_w as f32 / 2.0, rt_h as f32 / 2.0),
156 ..Default::default()
157 };
158 set_camera(&camera);
159
160 clear_background(BLANK);
162 draw_rectangle(radius, 0.0, w - radius * 2.0, h, opaque_color);
163 draw_rectangle(0.0, radius, radius, h - radius * 2.0, opaque_color);
164 draw_rectangle(w - radius, radius, radius, h - radius * 2.0, opaque_color);
165 draw_circle(radius, radius, radius, opaque_color);
166 draw_circle(w - radius, radius, radius, opaque_color);
167 draw_circle(radius, h - radius, radius, opaque_color);
168 draw_circle(w - radius, h - radius, radius, opaque_color);
169
170 set_default_camera();
172
173 draw_texture_ex(
175 &rt.texture,
176 x, y, final_color, DrawTextureParams {
180 dest_size: Some(vec2(w, h)),
181 ..Default::default()
182 },
183 );
184 }
185 }
186}