1use bevy::render::render_resource::AsBindGroup;
2use bevy::shader::ShaderRef;
3use bevy::{prelude::*, ui_widgets::observe};
4
5use crate::{consts::*, events::*, utils::*, on_mouse_out};
6use super::*;
7
8#[derive(AsBindGroup, Asset, TypePath, Debug, Clone)]
10pub struct CircularMaterial {
11 #[uniform(0)]
12 u_color: Vec4,
13 #[uniform(1)]
14 u_time: Vec4,
15 #[uniform(2)]
16 u_percent: f32,
17 #[uniform(3)]
18 u_type: f32,
19 #[uniform(4)]
20 u_background_color: Vec4
21}
22
23impl UiMaterial for CircularMaterial {
24 fn fragment_shader() -> ShaderRef {
25 get_embedded_asset_path("embedded_assets/circular.wgsl").into()
26 }
27}
28
29#[derive(Component)]
31pub struct MakaraCircular;
32
33#[derive(Component)]
35pub struct CircularBackgroundColor(pub Color);
36
37#[derive(Component)]
39pub enum CircularType {
40 Indeterminate,
42
43 Percent(f32)
45}
46
47#[derive(Component)]
49pub struct CircularColor(pub Color);
50
51pub struct CircularWidget<'a, 'w, 's> {
53 pub entity: Entity,
54 pub class: &'a mut Class,
55 pub style: WidgetStyle<'a>,
56 pub spin_color: &'a mut CircularColor,
57 pub arc_color: &'a mut CircularBackgroundColor,
58 pub commands: &'a mut Commands<'w, 's>
59}
60
61impl<'a, 'w, 's> CircularWidget<'a, 'w, 's> {
62 pub fn set_indeterminate(&mut self) {
64 self.commands.trigger(SetCircularValue {
65 entity: self.entity,
66 circular_type: "indeterminate".to_string(),
67 value: 0.0
68 });
69 }
70
71 pub fn set_percentage(&mut self, percent: f32) {
73 self.commands.trigger(SetCircularValue {
74 entity: self.entity,
75 circular_type: "percentage".to_string(),
76 value: percent
77 });
78 }
79}
80
81type IsCircularOnly = (
82 (
83 With<MakaraCircular>,
84 Without<MakaraCheckbox>,
85 Without<MakaraCheckboxButton>,
86 Without<MakaraColumn>,
87 Without<MakaraRow>,
88 Without<MakaraRoot>,
89 Without<MakaraButton>,
90 Without<MakaraDropdown>,
91 Without<MakaraDropdownOverlay>,
92 Without<MakaraImage>,
93 Without<MakaraLink>,
94 Without<MakaraModal>,
95 Without<MakaraModalBackdrop>,
96 ),
97 (
98 Without<MakaraProgressBar>,
99 Without<MakaraRadio>,
100 Without<MakaraRadioGroup>,
101 Without<MakaraScroll>,
102 Without<MakaraScrollbar>,
103 Without<MakaraTextInput>,
104 Without<MakaraTextInputCursor>,
105 Without<MakaraSlider>,
106 Without<MakaraSliderThumb>,
107 Without<MakaraSelect>,
108 Without<MakaraSelectOverlay>,
109 )
110);
111
112#[derive(SystemParam)]
114pub struct CircularQuery<'w, 's> {
115 pub id: Query<'w, 's, (Entity, &'static Id), With<MakaraCircular>>,
116 pub class: Query<'w, 's, (Entity, &'static mut Class), IsCircularOnly>,
117 pub style: StyleQuery<'w, 's, IsCircularOnly>,
118 pub custom_style: Query<
119 'w, 's,
120 (&'static mut CircularColor, &'static mut CircularBackgroundColor)
121 >,
122 pub commands: Commands<'w, 's>
123}
124
125impl<'w, 's> WidgetQuery<'w, 's> for CircularQuery<'w, 's> {
126 type WidgetView<'a> = CircularWidget<'a, 'w, 's> where Self: 'a;
127
128 fn get_components<'a>(&'a mut self, entity: Entity) -> Option<Self::WidgetView<'a>> {
129 let CircularQuery { id: _, class, style, custom_style, commands } = self;
130
131 let style_bundle = style.query.get_mut(entity).ok()?;
132 let (node, bg, border_color, shadow, z_index) = style_bundle;
133
134 let custom_style = custom_style.get_mut(entity).ok()?;
135 let (cir_color, cir_bg_color) = custom_style;
136
137 return Some(CircularWidget {
138 entity,
139 class: class.get_mut(entity).ok()?.1.into_inner(),
140 style: WidgetStyle {
141 node: node.into_inner(),
142 background_color: bg.into_inner(),
143 border_color: border_color.into_inner(),
144 shadow: shadow.into_inner(),
145 z_index: z_index.into_inner(),
146 },
147 spin_color: cir_color.into_inner(),
148 arc_color: cir_bg_color.into_inner(),
149 commands: commands
150 });
151 }
152
153 fn find_by_id<'a>(&'a mut self, target_id: &str) -> Option<Self::WidgetView<'a>> {
154 let entity = self.id.iter()
155 .find(|(_, id)| id.0 == target_id)
156 .map(|(e, _)| e)?;
157
158 self.get_components(entity)
159 }
160
161 fn find_by_entity<'a>(&'a mut self, entity: Entity) -> Option<Self::WidgetView<'a>> {
162 self.get_components(entity)
163 }
164
165 fn find_by_class(&self, target_class: &str) -> Vec<Entity> {
166 self.class.iter()
167 .filter(|(_, class)| class.0.split(" ").any(|word| word == target_class))
168 .map(|(e, _)| e)
169 .collect()
170 }
171}
172
173#[derive(Bundle)]
175pub struct CircularBundle {
176 pub id_class: IdAndClass,
177 pub style: ContainerStyle,
178 pub circular_type: CircularType,
179 pub circular_color: CircularColor,
180 pub tooltip_bundle: TooltipBundle
181}
182
183impl Default for CircularBundle {
184 fn default() -> Self {
185 let style = ContainerStyle {
186 node: Node {
187 width: px(50),
188 height: px(50),
189 justify_content: JustifyContent::Center,
190 align_items: AlignItems::Center,
191 border: UiRect::all(Val::Px(0.0)),
192 ..default()
193 },
194 shadow: BoxShadow::default(),
195 ..default()
196 };
197
198 let circular_type = CircularType::Indeterminate;
199 let circular_color = CircularColor(DEFAULT_CIRCULAR_VALUE_COLOR);
200 let tooltip_bundle = TooltipBundle::default();
201 let id_class = IdAndClass::default();
202
203 Self { style, circular_type, circular_color, tooltip_bundle, id_class }
204 }
205}
206
207impl CircularBundle {
208 pub fn percent(mut self, percent: f32) -> Self {
212 self.circular_type = CircularType::Percent(percent);
213 self
214 }
215
216 pub fn mode(mut self, mode: &str) -> Self {
219 if mode.trim() == "indeterminate" {
220 self.circular_type = CircularType::Indeterminate
221 }
222 else {
223 self.circular_type = CircularType::Percent(0.0);
224 }
225 self
226 }
227
228 pub fn color(mut self, color: impl IntoColor) -> Self {
230 self.circular_color.0 = color.into_color();
231 self
232 }
233}
234
235impl Widget for CircularBundle {
236 fn build(mut self) -> impl Bundle {
238 process_built_in_spacing_class(&self.id_class.class, &mut self.style.node);
239 process_built_in_color(&self.id_class.class, &mut self.circular_color.0);
240
241 (
242 self.id_class,
243 self.style,
244 self.circular_color,
245 self.circular_type,
246 MakaraCircular,
247 MakaraWidget,
248 CircularBackgroundColor(LIGHT_CIRCULAR_BG_COLOR),
249 observe(on_circular_value_set),
250 observe(on_circular_mouse_over),
251 observe(on_mouse_out),
252 children![
253 self.tooltip_bundle.build()
254 ]
255 )
256 }
257}
258
259impl SetContainerStyle for CircularBundle {
260 fn container_style(&mut self) -> &mut ContainerStyle {
261 &mut self.style
262 }
263}
264
265impl SetToolTip for CircularBundle {
266 fn set_tooltip(&mut self) -> &mut TooltipBundle {
267 &mut self.tooltip_bundle
268 }
269}
270
271impl SetIdAndClass for CircularBundle {
272 fn id_and_class(&mut self) -> &mut IdAndClass {
273 &mut self.id_class
274 }
275}
276
277pub fn circular() -> CircularBundle {
279 let bundle = CircularBundle::default();
280 bundle
281}
282
283pub(crate) fn detect_circular_class_change_for_built_in_color(
284 mut circulars: Query<(&Class, &mut Node, &mut CircularColor), IsCircularOnly>
285) {
286 for (class, mut node, mut cir_color) in circulars.iter_mut() {
287 process_built_in_spacing_class(class, &mut node);
288 process_built_in_color(class, &mut cir_color.0);
289 }
290}
291
292fn on_circular_value_set(
293 value_set: On<SetCircularValue>,
294 mut circulars: Query<&mut CircularType>,
295 mut commands: Commands
296) {
297 if let Ok(mut cir_type) = circulars.get_mut(value_set.entity) {
298 match value_set.circular_type.as_str() {
299 "indeterminate" => *cir_type = CircularType::Indeterminate,
300 "percentage" => {
301 *cir_type = CircularType::Percent(value_set.value.clamp(0.0, 100.0));
302 commands.trigger(Change {
303 entity: value_set.entity,
304 data: value_set.value.clamp(0.0, 100.0)
305 });
306 }
307 _ => {}
308 }
309 }
310}
311
312fn on_circular_mouse_over(
313 mut over: On<Pointer<Over>>,
314 mut commands: Commands,
315 mut tooltips: Query<
316 (&mut Node, &ComputedNode, &TooltipPosition, &UseTooltip),
317 With<MakaraTooltip>
318 >,
319 circulars: Query<
320 (&Children, &UiTransform, &ComputedNode),
321 With<MakaraCircular>
322 >,
323) {
324 if let Ok((children, transform, computed)) = circulars.get(over.entity) {
325 show_or_hide_tooltip(true, &mut tooltips, Some(computed), Some(transform), children);
326 }
327 commands.trigger(MouseOver {
328 entity: over.entity,
329 });
330 over.propagate(false);
331}
332
333pub(crate) fn detect_circular_added(
334 mut commands: Commands,
335 mut circular_materials: ResMut<Assets<CircularMaterial>>,
336 circulars: Query<
337 (Entity, &CircularType, &CircularColor, &CircularBackgroundColor),
338 Or<(Added<MakaraCircular>, Changed<ComputedNode>)>
339 >
340) {
341 for (entity, circular_type, color, bg_color) in circulars.iter() {
342 if let Color::Srgba(value) = color.0 {
343 let (u_type, u_percent) = match circular_type {
344 CircularType::Indeterminate => (0.0, 0.0),
345 CircularType::Percent(percent) => (1.0, *percent),
346 };
347
348 if let Color::Srgba(bg_value) = bg_color.0 {
349 commands
350 .entity(entity)
351 .insert(
352 MaterialNode(circular_materials.add(CircularMaterial {
353 u_time: Vec4::ZERO,
354 u_color: Vec4::new(value.red, value.green, value.blue, 1.0),
355 u_background_color: Vec4::new(
356 bg_value.red,
357 bg_value.green,
358 bg_value.blue,
359 bg_value.alpha
360 ),
361 u_type,
362 u_percent
363 }))
364 );
365 }
366 }
367 }
368}
369
370pub(crate) fn update_circular_material_u_time(
371 time: Res<Time>,
372 mut materials: ResMut<Assets<CircularMaterial>>,
373 query: Query<(&MaterialNode<CircularMaterial>, &CircularType, &CircularColor, &CircularBackgroundColor)>
374) {
375 query.iter().for_each(|(handle, circular_type, color, bg_color)| {
376 if let Some(material) = materials.get_mut(handle) {
377 match circular_type {
378 CircularType::Indeterminate => {
379 material.u_time.x = -time.elapsed_secs();
380 material.u_type = 0.0;
381 }
382 CircularType::Percent(value) => {
383 material.u_time.x = 0.0;
384 material.u_percent = *value;
385 material.u_type = 1.0;
386 }
387 }
388 if let Color::Srgba(value) = bg_color.0 {
389 material.u_background_color = Vec4::new(
390 value.red, value.green, value.blue, value.alpha
391 );
392 }
393
394 if let Color::Srgba(value) = color.0 {
395 material.u_color = Vec4::new(
396 value.red, value.green, value.blue, value.alpha
397 );
398 }
399 }
400 });
401}
402
403pub(crate) fn update_circular_style_on_theme_change_system(
404 makara_theme: Res<MakaraTheme>,
405 mut circulars: Query<(&mut CircularBackgroundColor, &CircularType), With<MakaraCircular>>,
406) {
407 if !makara_theme.is_changed() {
408 return;
409 }
410
411 let new_bg_color = match makara_theme.theme {
412 Theme::Light => LIGHT_CIRCULAR_BG_COLOR,
413 Theme::Dark => DARK_CIRCULAR_BG_COLOR,
414 };
415
416 for (mut bg_color, cir_type) in circulars.iter_mut() {
417 match cir_type {
418 CircularType::Percent(_) => {
419 if bg_color.0 == LIGHT_CIRCULAR_BG_COLOR ||
421 bg_color.0 == DARK_CIRCULAR_BG_COLOR
422 {
423 bg_color.0 = new_bg_color;
424 }
425 },
426 _ => {}
427 }
428
429 }
430}
431
432pub(crate) fn can_run_circular_systems(q: Query<&MakaraCircular>) -> bool {
433 q.count() > 0
434}