Skip to main content

fantasy_craft/gui/
gui_layout.rs

1use std::collections::HashSet;
2
3use hecs::Entity;
4use macroquad::prelude::*;
5use serde::Deserialize;
6use crate::gui::gui_box::GuiBox;
7use crate::gui::gui_draggable::GuiDraggable;
8use crate::gui::gui_element::GuiElement;
9use crate::gui::gui_local_offset::GuiLocalOffset;
10use crate::gui::resources::UiResolvedRects;
11use crate::prelude::{ComponentLoader, Context, Parent, Transform};
12use crate::gui::gui_dimension::{GuiDimension, GuiDimensionLoaderData};
13
14#[derive(Debug, Clone, Copy)]
15pub struct GuiLayout {
16    pub x: GuiDimension,
17    pub y: GuiDimension
18}
19
20impl Default for GuiLayout {
21    fn default() -> Self {
22        Self {
23            x: GuiDimension::Pixels(0.0),
24            y: GuiDimension::Pixels(0.0)
25        }
26    }
27}
28
29#[derive(Deserialize, Debug, Default)]
30pub struct GuiLayoutLoaderData {
31    #[serde(default)]
32    pub x: GuiDimensionLoaderData,
33    #[serde(default)]
34    pub y: GuiDimensionLoaderData,
35}
36
37pub struct GuiLayoutLoader;
38
39impl ComponentLoader for GuiLayoutLoader {
40    fn load(&self, ctx: &mut crate::prelude::Context, entity: hecs::Entity, data: &serde_json::Value) {
41        let loader_data: GuiLayoutLoaderData = serde_json::from_value(data.clone())
42            .unwrap_or_default();
43
44        let parse_dimension = |loader_dim: GuiDimensionLoaderData| -> GuiDimension {
45            match loader_dim {
46                GuiDimensionLoaderData::Pixels(px) => GuiDimension::Pixels(px),
47                GuiDimensionLoaderData::Percent(s) => {
48                    let value = s.trim_end_matches('%')
49                                 .parse::<f32>()
50                                 .unwrap_or(0.0); // 0% par défaut
51                    GuiDimension::Percent(value / 100.0) 
52                }
53            }
54        };
55
56        let component = GuiLayout {
57            x: parse_dimension(loader_data.x),
58            y: parse_dimension(loader_data.y),
59        };
60
61        ctx.world.insert_one(entity, component).expect("Failed to insert GuiLayout");
62    }
63}
64
65pub fn gui_resolve_layout_system(ctx: &mut Context) {
66    let (screen_w, screen_h) = (screen_width(), screen_height());
67    
68    ctx.resource_mut::<UiResolvedRects>().0.clear();
69
70    // ... (Collecte des 'entities' ... c'est correct)
71    let mut entities: HashSet<Entity> = ctx.world.query::<(&Parent, &GuiElement)>()
72        .iter()
73        .map(|(e, _)| e)
74        .collect();
75
76    entities.extend(
77        ctx.world.query::<&GuiBox>().without::<&Parent>()
78            .iter()
79            .map(|(e, _)| e)
80    );
81    let mut entities_to_process: Vec<Entity> = entities.into_iter().collect();
82    
83    let mut iterations = 0;
84    
85    while !entities_to_process.is_empty() && iterations < 10 {
86        // --- PHASE 1: COLLECTER LES MODIFICATIONS ---
87        let mut results_to_apply = Vec::new();
88        let mut processed_this_iteration = Vec::new();
89        
90        // Emprunt immuable de la map pour cette phase
91        let resolved_rects_map = &ctx.resource::<UiResolvedRects>().0;
92
93        entities_to_process.retain(|&entity| {
94            let parent_opt = ctx.world.get::<&Parent>(entity).ok();
95
96            let (parent_w, parent_h, parent_pos) = 
97                if let Some(parent) = parent_opt.as_ref() { 
98                    // Lecture depuis la map immuable
99                    if let Some((pos, size)) = resolved_rects_map.get(&parent.0) {
100                        (size.x, size.y, *pos)
101                    } else {
102                        return true; // Garder (parent pas encore traité)
103                    }
104                } else {
105                    // ... (logique de la racine, inchangée)
106                    if let Ok(layout) = ctx.world.get::<&GuiLayout>(entity) {
107                        let root_x = layout.x.resolve(screen_w);
108                        let root_y = layout.y.resolve(screen_h);
109                        (screen_w, screen_h, vec2(root_x, root_y))
110                    } else {
111                        (screen_w, screen_h, Vec2::ZERO)
112                    }
113                };
114
115            // 1. Résoudre Taille (lecture seule)
116            let resolved_size = if let Ok(gui_box) = ctx.world.get::<&GuiBox>(entity) {
117                vec2(gui_box.width.resolve(parent_w), gui_box.height.resolve(parent_h))
118            } else {
119                Vec2::ZERO
120            };
121
122            // 2. Résoudre Position (lecture seule)
123            let mut resolved_pos;
124            if parent_opt.is_some() {
125                resolved_pos = parent_pos;
126                if let Ok(local_offset) = ctx.world.get::<&GuiLocalOffset>(entity) {
127                    resolved_pos.x += local_offset.x.resolve(parent_w);
128                    resolved_pos.y += local_offset.y.resolve(parent_h);
129                }
130            } else {
131                 if let Ok(layout) = ctx.world.get::<&GuiLayout>(entity) {
132                    resolved_pos = vec2(layout.x.resolve(screen_w), layout.y.resolve(screen_h));
133                } else if let Ok(transform) = ctx.world.get::<&Transform>(entity) {
134                    resolved_pos = transform.position;
135                } else {
136                    resolved_pos = Vec2::ZERO;
137                }
138            }
139            
140            // 3. Stocker le résultat au lieu de l'insérer
141            results_to_apply.push((entity, resolved_pos, resolved_size));
142            
143            processed_this_iteration.push(entity);
144            false // Retirer de entities_to_process
145        });
146
147        // --- PHASE 2: APPLIQUER LES MODIFICATIONS ---
148        
149        // Failsafe
150        if processed_this_iteration.is_empty() && !entities_to_process.is_empty() {
151            eprintln!("Erreur de layout GUI : impossible de résoudre certaines entités.");
152            break; 
153        }
154
155        let (_world, resources) = (&mut ctx.world, &mut ctx.resources);
156
157        // 2. Obtenez l'accès mutable à la map DEPUIS 'resources'
158        let rect_map_mut = &mut resources.get_mut::<UiResolvedRects>()
159            .expect("Ressource UiResolvedRects manquante")
160            .0;
161
162        for (entity, pos, size) in results_to_apply {
163            // 1. Insérer dans la map des ressources
164            rect_map_mut.insert(entity, (pos, size));
165
166            // 2. Mettre à jour le Transform
167            let is_dragging = ctx.world.get::<&GuiDraggable>(entity)
168                .map_or(false, |d| d.is_dragging);
169
170            if !is_dragging {
171                if let Ok(mut transform) = ctx.world.get::<&mut Transform>(entity) {
172                    transform.position = pos;
173                }
174            }
175        }
176        
177        iterations += 1;
178    }
179}