Skip to main content

fantasy_craft/gui/
gui_box.rs

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                    // Enlève le '%' et parse en f32
51                    let value = s.trim_end_matches('%')
52                                 .parse::<f32>()
53                                 .unwrap_or(100.0); // 100% par défaut en cas d'erreur
54                    
55                    // Convertit en 0.0-1.0
56                    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    // --- MODIFIED: Get map once ---
80    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        // --- MODIFIED ---
97        let (resolved_pos, resolved_size) = 
98            if let Some(rect) = resolved_rects_map.get(&entity) {
99                *rect // Dereference the tuple (pos, size)
100            } else {
101                continue; // This UI element was not processed by the layout system
102            };
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        // --- END OF CHANGE ---
109
110        // (Alignment logic is correct)
111        if let Some(h_align) = h_align {
112            match h_align.0 {
113                HorizontalAlignmentType::Left => { /* Default */ }
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 => { /* Default */ }
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        // Determine the final color
129        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        // (Drawing logic is correct)
139        if radius == 0.0 {
140            draw_rectangle(x, y, w, h, final_color);
141        } else {
142            // 1. Create an opaque version
143            let opaque_color = Color::new(final_color.r, final_color.g, final_color.b, 1.0);
144
145            // 2. Create the render target
146            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            // 3. Set up a camera
152            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            // 4. Draw the 7 shapes (OPAQUE) at (0, 0)
161            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            // 5. Restore the default camera
171            set_default_camera();
172
173            // 6. Draw the RenderTarget to the screen at its final, aligned position
174            draw_texture_ex(
175                &rt.texture,
176                x, // Use the aligned x
177                y, // Use the aligned y
178                final_color, // Use the original color (with alpha)
179                DrawTextureParams {
180                    dest_size: Some(vec2(w, h)),
181                    ..Default::default()
182                },
183            );
184        }
185    }
186}