1use macroquad::prelude::*;
2use serde::Deserialize;
3
4use crate::{core::event::EventBus, gui::{alignment::{HorizontalAlignment, HorizontalAlignmentType, VerticalAlignment, VerticalAlignmentType}, event::UiClickEvent, gui_action::GuiAction, gui_box::GuiBox, resources::UiResolvedRects}, prelude::{ColorData, ComponentLoader, Context, Visible}};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum ButtonState {
8 Idle,
9 Hovered,
10 Pressed
11}
12
13impl ButtonState {
14 pub fn to_str(&self) -> &'static str {
15 match self {
16 ButtonState::Idle => "idle",
17 ButtonState::Hovered => "hovered",
18 ButtonState::Pressed => "pressed"
19 }
20 }
21
22 pub fn from_str(value: &str) -> ButtonState {
23 match value {
24 "idle" => ButtonState::Idle,
25 "hovered" => ButtonState::Hovered,
26 "pressed" => ButtonState::Pressed,
27 _ => ButtonState::Idle
28 }
29 }
30}
31
32#[derive(Debug, Clone, Copy)]
33pub struct GuiButton {
34 pub state: ButtonState,
35 pub just_clicked: bool,
36 pub hovered_color: Color,
37 pub pressed_color: Color,
38 pub normal_color: Color
39}
40
41impl Default for GuiButton {
42 fn default() -> Self {
43 Self {
44 state: ButtonState::Idle,
45 just_clicked: false,
46 hovered_color: Color::new(0.0, 0.0, 0.0, 1.0),
47 pressed_color: Color::new(0.0, 0.0, 0.0, 1.0),
48 normal_color: Color::new(0.0, 0.0, 0.0, 1.0)
49 }
50 }
51}
52
53#[derive(Deserialize, Debug, Default)]
54pub struct GuiButtonLoaderData {
55 #[serde(default)]
56 pub state: String,
57 #[serde(default)]
58 pub just_clicked: bool,
59
60 pub hovered_color: ColorData,
61 pub pressed_color: ColorData,
62 pub normal_color: ColorData
63}
64
65pub struct GuiButtonLoader;
66
67impl ComponentLoader for GuiButtonLoader {
68 fn load(&self, ctx: &mut crate::prelude::Context, entity: hecs::Entity, data: &serde_json::Value) {
69 let loader_data: GuiButtonLoaderData = serde_json::from_value(data.clone())
70 .unwrap_or_default();
71
72 let component = GuiButton {
73 state: ButtonState::from_str(loader_data.state.as_str()),
74 just_clicked: loader_data.just_clicked,
75 hovered_color: Color::new(
76 loader_data.hovered_color.r,
77 loader_data.hovered_color.g,
78 loader_data.hovered_color.b,
79 loader_data.hovered_color.a
80 ),
81 pressed_color: Color::new(
82 loader_data.pressed_color.r,
83 loader_data.pressed_color.g,
84 loader_data.pressed_color.b,
85 loader_data.pressed_color.a
86 ),
87 normal_color: Color::new(
88 loader_data.normal_color.r,
89 loader_data.normal_color.g,
90 loader_data.normal_color.b,
91 loader_data.normal_color.a
92 )
93 };
94
95 ctx.world.insert_one(entity, component).expect("Failed to insert GuiButton");
96 }
97}
98
99pub fn button_interaction_system(ctx: &mut Context) {
100 let (mouse_x, mouse_y) = mouse_position();
101 let is_pressed = is_mouse_button_down(MouseButton::Left);
102 let just_clicked = is_mouse_button_pressed(MouseButton::Left);
103
104 let (world, resources) = (&mut ctx.world, &mut ctx.resources);
105
106 let resolved_rects_map = &resources.get::<UiResolvedRects>()
109 .expect("UiResolvedRects resource is missing")
110 .0;
111
112 let mut events_to_send: Vec<UiClickEvent> = Vec::new();
115
116 let mut query = world.query::<(
117 &mut GuiButton,
118 &GuiBox,
119 Option<&GuiAction>,
120 Option<&Visible>,
121 Option<&HorizontalAlignment>,
122 Option<&VerticalAlignment>
123 )>();
124
125 for (entity, (button, gui_box, action_opt, visibility, h_align, v_align)) in query.iter() {
126 let is_visible = visibility.map_or(true, |v| v.0);
127
128 if !is_visible {
129 continue;
130 }
131
132 button.just_clicked = false;
133
134 let (resolved_pos, resolved_size) =
136 if let Some(rect) = resolved_rects_map.get(&entity) {
137 *rect
138 } else {
139 continue;
140 };
141
142 if !gui_box.screen_space { continue; }
143
144 let mut x = resolved_pos.x;
145 let mut y = resolved_pos.y;
146 let w = resolved_size.x;
147 let h = resolved_size.y;
148
149 if let Some(h_align) = h_align {
151 match h_align.0 {
152 HorizontalAlignmentType::Left => {},
153 HorizontalAlignmentType::Center => x -= w / 2.0,
154 HorizontalAlignmentType::Right => x -= w,
155 }
156 }
157
158 if let Some(v_align) = v_align {
159 match v_align.0 {
160 VerticalAlignmentType::Top => {},
161 VerticalAlignmentType::Center => y -= h / 2.0,
162 VerticalAlignmentType::Bottom => y -= h,
163 }
164 }
165
166 let is_hovered = mouse_x >= x && mouse_x <= (x + w) && mouse_y >= y && mouse_y <= (y + h);
167
168 match button.state {
169 ButtonState::Idle => {
170 if is_hovered { button.state = ButtonState::Hovered; }
171 }
172 ButtonState::Hovered => {
173 if !is_hovered { button.state = ButtonState::Idle; }
174 else if just_clicked { button.state = ButtonState::Pressed; }
175 }
176 ButtonState::Pressed => {
177 if !is_pressed {
178 if is_hovered {
179 button.just_clicked = true;
180 button.state = ButtonState::Hovered;
181
182 if let Some(action) = action_opt {
185 events_to_send.push(UiClickEvent {
186 action_id: action.action_id.clone(),
187 entity,
188 });
189 }
190 } else {
191 button.state = ButtonState::Idle;
192 }
193 }
194 }
195 }
196 }
197
198 if !events_to_send.is_empty() {
203 let event_bus = resources.get_mut::<EventBus>()
204 .expect("EventBus resource is missing");
205
206 for event in events_to_send {
207 event_bus.send(event);
208 }
209 }
210}