use std::collections::HashSet;
use hecs::Entity;
use macroquad::prelude::*;
use serde::Deserialize;
use crate::gui::gui_box::GuiBox;
use crate::gui::gui_draggable::GuiDraggable;
use crate::gui::gui_element::GuiElement;
use crate::gui::gui_local_offset::GuiLocalOffset;
use crate::gui::resources::UiResolvedRects;
use crate::prelude::{ComponentLoader, Context, Parent, Transform};
use crate::gui::gui_dimension::{GuiDimension, GuiDimensionLoaderData};
#[derive(Debug, Clone, Copy)]
pub struct GuiLayout {
pub x: GuiDimension,
pub y: GuiDimension
}
impl Default for GuiLayout {
fn default() -> Self {
Self {
x: GuiDimension::Pixels(0.0),
y: GuiDimension::Pixels(0.0)
}
}
}
#[derive(Deserialize, Debug, Default)]
pub struct GuiLayoutLoaderData {
#[serde(default)]
pub x: GuiDimensionLoaderData,
#[serde(default)]
pub y: GuiDimensionLoaderData,
}
pub struct GuiLayoutLoader;
impl ComponentLoader for GuiLayoutLoader {
fn load(&self, ctx: &mut crate::prelude::Context, entity: hecs::Entity, data: &serde_json::Value) {
let loader_data: GuiLayoutLoaderData = 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(0.0); GuiDimension::Percent(value / 100.0)
}
}
};
let component = GuiLayout {
x: parse_dimension(loader_data.x),
y: parse_dimension(loader_data.y),
};
ctx.world.insert_one(entity, component).expect("Failed to insert GuiLayout");
}
}
pub fn gui_resolve_layout_system(ctx: &mut Context) {
let (screen_w, screen_h) = (screen_width(), screen_height());
ctx.resource_mut::<UiResolvedRects>().0.clear();
let mut entities: HashSet<Entity> = ctx.world.query::<(&Parent, &GuiElement)>()
.iter()
.map(|(e, _)| e)
.collect();
entities.extend(
ctx.world.query::<&GuiBox>().without::<&Parent>()
.iter()
.map(|(e, _)| e)
);
let mut entities_to_process: Vec<Entity> = entities.into_iter().collect();
let mut iterations = 0;
while !entities_to_process.is_empty() && iterations < 10 {
let mut results_to_apply = Vec::new();
let mut processed_this_iteration = Vec::new();
let resolved_rects_map = &ctx.resource::<UiResolvedRects>().0;
entities_to_process.retain(|&entity| {
let parent_opt = ctx.world.get::<&Parent>(entity).ok();
let (parent_w, parent_h, parent_pos) =
if let Some(parent) = parent_opt.as_ref() {
if let Some((pos, size)) = resolved_rects_map.get(&parent.0) {
(size.x, size.y, *pos)
} else {
return true; }
} else {
if let Ok(layout) = ctx.world.get::<&GuiLayout>(entity) {
let root_x = layout.x.resolve(screen_w);
let root_y = layout.y.resolve(screen_h);
(screen_w, screen_h, vec2(root_x, root_y))
} else {
(screen_w, screen_h, Vec2::ZERO)
}
};
let resolved_size = if let Ok(gui_box) = ctx.world.get::<&GuiBox>(entity) {
vec2(gui_box.width.resolve(parent_w), gui_box.height.resolve(parent_h))
} else {
Vec2::ZERO
};
let mut resolved_pos;
if parent_opt.is_some() {
resolved_pos = parent_pos;
if let Ok(local_offset) = ctx.world.get::<&GuiLocalOffset>(entity) {
resolved_pos.x += local_offset.x.resolve(parent_w);
resolved_pos.y += local_offset.y.resolve(parent_h);
}
} else {
if let Ok(layout) = ctx.world.get::<&GuiLayout>(entity) {
resolved_pos = vec2(layout.x.resolve(screen_w), layout.y.resolve(screen_h));
} else if let Ok(transform) = ctx.world.get::<&Transform>(entity) {
resolved_pos = transform.position;
} else {
resolved_pos = Vec2::ZERO;
}
}
results_to_apply.push((entity, resolved_pos, resolved_size));
processed_this_iteration.push(entity);
false });
if processed_this_iteration.is_empty() && !entities_to_process.is_empty() {
eprintln!("Erreur de layout GUI : impossible de résoudre certaines entités.");
break;
}
let (_world, resources) = (&mut ctx.world, &mut ctx.resources);
let rect_map_mut = &mut resources.get_mut::<UiResolvedRects>()
.expect("Ressource UiResolvedRects manquante")
.0;
for (entity, pos, size) in results_to_apply {
rect_map_mut.insert(entity, (pos, size));
let is_dragging = ctx.world.get::<&GuiDraggable>(entity)
.map_or(false, |d| d.is_dragging);
if !is_dragging {
if let Ok(mut transform) = ctx.world.get::<&mut Transform>(entity) {
transform.position = pos;
}
}
}
iterations += 1;
}
}