fantasy_craft/gui/
gui_layout.rs1use 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); 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 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 let mut results_to_apply = Vec::new();
88 let mut processed_this_iteration = Vec::new();
89
90 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 if let Some((pos, size)) = resolved_rects_map.get(&parent.0) {
100 (size.x, size.y, *pos)
101 } else {
102 return true; }
104 } else {
105 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 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 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 results_to_apply.push((entity, resolved_pos, resolved_size));
142
143 processed_this_iteration.push(entity);
144 false });
146
147 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 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 rect_map_mut.insert(entity, (pos, size));
165
166 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}