1use bevy::{ecs::system::EntityCommand, prelude::*, text::TextLayoutInfo, ui::widget::TextFlags};
2
3use crate::{flux_interaction::FluxInteraction, theme::icons::IconData};
4
5use super::{
6 generated::*, LockableStyleAttribute, LockedStyleAttributes, UiStyle, UiStyleUnchecked,
7};
8
9macro_rules! check_lock {
11 ($world:expr, $entity:expr, $prop:literal, $lock_attr:path) => {
12 if let Some(locked_attrs) = $world.get::<LockedStyleAttributes>($entity) {
13 if locked_attrs.contains($lock_attr) {
14 warn!(
15 "Failed to style {} property on entity {:?}: Attribute locked!",
16 $prop, $entity
17 );
18 return;
19 }
20 }
21 };
22}
23
24impl EntityCommand for SetZIndex {
25 fn apply(self, entity: Entity, world: &mut World) {
26 if self.check_lock {
27 check_lock!(world, entity, "z index", LockableStyleAttribute::ZIndex);
28 }
29
30 let Some(mut z_index) = world.get_mut::<ZIndex>(entity) else {
31 warn!(
32 "Failed to set z index on entity {}: No ZIndex component found!",
33 entity
34 );
35 return;
36 };
37
38 if let (ZIndex::Local(level), ZIndex::Local(target)) = (*z_index, self.z_index) {
40 if level != target {
41 *z_index = self.z_index;
42 }
43 } else if let (ZIndex::Global(level), ZIndex::Global(target)) = (*z_index, self.z_index) {
44 if level != target {
45 *z_index = self.z_index;
46 }
47 } else {
48 *z_index = self.z_index;
49 }
50 }
51}
52
53#[derive(Clone, Debug)]
54pub enum ImageSource {
55 Path(String),
56 Lookup(String, fn(String, Entity, &mut World) -> Handle<Image>),
57 Handle(Handle<Image>),
58 Atlas(String, TextureAtlasLayout),
59}
60
61impl Default for ImageSource {
62 fn default() -> Self {
63 Self::Handle(Handle::default())
64 }
65}
66
67impl From<&str> for ImageSource {
68 fn from(path: &str) -> Self {
69 Self::Path(path.to_string())
70 }
71}
72
73impl From<String> for ImageSource {
74 fn from(path: String) -> Self {
75 Self::Path(path)
76 }
77}
78
79pub struct SetImage {
80 source: ImageSource,
81 check_lock: bool,
82}
83
84impl EntityCommand for SetImage {
85 fn apply(self, entity: Entity, world: &mut World) {
86 if self.check_lock {
87 check_lock!(world, entity, "image", LockableStyleAttribute::Image);
88 }
89
90 let handle = match self.source.clone() {
91 ImageSource::Path(path) => {
92 if path == "" {
93 Handle::default()
94 } else {
95 world.resource::<AssetServer>().load(path)
96 }
97 }
98 ImageSource::Lookup(path, callback) => callback(path, entity, world),
99 ImageSource::Handle(handle) => handle,
100 ImageSource::Atlas(path, _) => {
101 if path == "" {
102 Handle::default()
103 } else {
104 world.resource::<AssetServer>().load(path)
105 }
106 }
107 };
108
109 let Some(mut image) = world.get_mut::<UiImage>(entity) else {
110 warn!(
111 "Failed to set image on entity {}: No UiImage component found!",
112 entity
113 );
114 return;
115 };
116
117 if image.texture != handle {
118 image.texture = handle;
119 }
120
121 if let ImageSource::Atlas(_, layout) = self.source {
122 let layout_handle = world
123 .resource_mut::<Assets<TextureAtlasLayout>>()
124 .add(layout.clone());
125
126 if let Some(mut atlas) = world.get_mut::<TextureAtlas>(entity) {
127 if atlas.layout != layout_handle {
128 atlas.layout = layout_handle;
129 atlas.index = 0;
130 }
131 } else {
132 world
133 .entity_mut(entity)
134 .insert(TextureAtlas::from(layout_handle));
135 }
136 }
137 }
138}
139
140pub trait SetImageExt {
141 fn image(&mut self, source: ImageSource) -> &mut Self;
142}
143
144impl SetImageExt for UiStyle<'_> {
145 fn image(&mut self, source: ImageSource) -> &mut Self {
146 self.commands.add(SetImage {
147 source,
148 check_lock: true,
149 });
150 self
151 }
152}
153
154pub trait SetImageUncheckedExt {
155 fn image(&mut self, source: ImageSource) -> &mut Self;
156}
157
158impl SetImageUncheckedExt for UiStyleUnchecked<'_> {
159 fn image(&mut self, source: ImageSource) -> &mut Self {
160 self.commands.add(SetImage {
161 source,
162 check_lock: false,
163 });
164 self
165 }
166}
167
168impl EntityCommand for SetImageTint {
169 fn apply(self, entity: Entity, world: &mut World) {
170 if self.check_lock {
171 check_lock!(
172 world,
173 entity,
174 "image tint",
175 LockableStyleAttribute::ImageTint
176 );
177 }
178
179 let Some(mut image) = world.get_mut::<UiImage>(entity) else {
180 warn!(
181 "Failed to set image tint on entity {}: No UiImage component found!",
182 entity
183 );
184 return;
185 };
186
187 if image.color != self.image_tint {
188 image.color = self.image_tint;
189 }
190 }
191}
192
193impl EntityCommand for SetImageFlip {
194 fn apply(self, entity: Entity, world: &mut World) {
195 if self.check_lock {
196 check_lock!(
197 world,
198 entity,
199 "image flip",
200 LockableStyleAttribute::ImageFlip
201 );
202 }
203
204 let Some(mut image) = world.get_mut::<UiImage>(entity) else {
205 warn!(
206 "Failed to set image flip on entity {}: No UiImage component found!",
207 entity
208 );
209 return;
210 };
211
212 if image.flip_x != self.image_flip.x {
213 image.flip_x = self.image_flip.x;
214 }
215
216 if image.flip_y != self.image_flip.y {
217 image.flip_y = self.image_flip.y;
218 }
219 }
220}
221
222impl EntityCommand for SetImageScaleMode {
223 fn apply(self, entity: Entity, world: &mut World) {
224 if self.check_lock {
225 check_lock!(
226 world,
227 entity,
228 "image scale mode",
229 LockableStyleAttribute::ImageScaleMode
230 );
231 }
232
233 if let Some(image_scale_mode) = self.image_scale_mode {
234 if let Some(mut scale_mode) = world.get_mut::<ImageScaleMode>(entity) {
235 *scale_mode = image_scale_mode;
236 } else {
237 world.entity_mut(entity).insert(image_scale_mode);
238 }
239 } else if let Some(_) = world.get::<ImageScaleMode>(entity) {
240 world.entity_mut(entity).remove::<ImageScaleMode>();
241 }
242 }
243}
244
245pub struct SetFluxInteractionEnabled {
246 enabled: bool,
247 check_lock: bool,
248}
249
250impl EntityCommand for SetFluxInteractionEnabled {
251 fn apply(self, entity: Entity, world: &mut World) {
252 if self.check_lock {
253 check_lock!(
254 world,
255 entity,
256 "flux interaction",
257 LockableStyleAttribute::FluxInteraction
258 );
259 }
260
261 let Some(mut flux_interaction) = world.get_mut::<FluxInteraction>(entity) else {
262 warn!(
263 "Failed to set flux interaction on entity {}: No FluxInteraction component found!",
264 entity
265 );
266 return;
267 };
268
269 if self.enabled {
270 if *flux_interaction == FluxInteraction::Disabled {
271 *flux_interaction = FluxInteraction::None;
272 }
273 } else {
274 if *flux_interaction != FluxInteraction::Disabled {
275 *flux_interaction = FluxInteraction::Disabled;
276 }
277 }
278 }
279}
280
281pub trait SetFluxInteractionExt {
282 fn disable_flux_interaction(&mut self) -> &mut Self;
283 fn enable_flux_interaction(&mut self) -> &mut Self;
284 fn flux_interaction_enabled(&mut self, enabled: bool) -> &mut Self;
285}
286
287impl SetFluxInteractionExt for UiStyle<'_> {
288 fn disable_flux_interaction(&mut self) -> &mut Self {
289 self.commands.add(SetFluxInteractionEnabled {
290 enabled: false,
291 check_lock: true,
292 });
293 self
294 }
295
296 fn enable_flux_interaction(&mut self) -> &mut Self {
297 self.commands.add(SetFluxInteractionEnabled {
298 enabled: true,
299 check_lock: true,
300 });
301 self
302 }
303
304 fn flux_interaction_enabled(&mut self, enabled: bool) -> &mut Self {
305 self.commands.add(SetFluxInteractionEnabled {
306 enabled,
307 check_lock: true,
308 });
309 self
310 }
311}
312
313pub trait SetFluxInteractionUncheckedExt {
314 fn disable_flux_interaction(&mut self) -> &mut Self;
315 fn enable_flux_interaction(&mut self) -> &mut Self;
316 fn flux_interaction_enabled(&mut self, enabled: bool) -> &mut Self;
317}
318
319impl SetFluxInteractionUncheckedExt for UiStyleUnchecked<'_> {
320 fn disable_flux_interaction(&mut self) -> &mut Self {
321 self.commands.add(SetFluxInteractionEnabled {
322 enabled: false,
323 check_lock: false,
324 });
325 self
326 }
327
328 fn enable_flux_interaction(&mut self) -> &mut Self {
329 self.commands.add(SetFluxInteractionEnabled {
330 enabled: true,
331 check_lock: false,
332 });
333 self
334 }
335
336 fn flux_interaction_enabled(&mut self, enabled: bool) -> &mut Self {
337 self.commands.add(SetFluxInteractionEnabled {
338 enabled,
339 check_lock: false,
340 });
341 self
342 }
343}
344
345pub trait SetNodeShowHideExt {
346 fn show(&mut self) -> &mut Self;
347 fn hide(&mut self) -> &mut Self;
348 fn render(&mut self, render: bool) -> &mut Self;
349}
350
351impl SetNodeShowHideExt for UiStyle<'_> {
352 fn show(&mut self) -> &mut Self {
353 self.commands
354 .add(SetVisibility {
355 visibility: Visibility::Inherited,
356 check_lock: true,
357 })
358 .add(SetDisplay {
359 display: Display::Flex,
360 check_lock: true,
361 });
362 self
363 }
364
365 fn hide(&mut self) -> &mut Self {
366 self.commands
367 .add(SetVisibility {
368 visibility: Visibility::Hidden,
369 check_lock: true,
370 })
371 .add(SetDisplay {
372 display: Display::None,
373 check_lock: true,
374 });
375 self
376 }
377
378 fn render(&mut self, render: bool) -> &mut Self {
379 if render {
380 self.commands
381 .add(SetVisibility {
382 visibility: Visibility::Inherited,
383 check_lock: true,
384 })
385 .add(SetDisplay {
386 display: Display::Flex,
387 check_lock: true,
388 });
389 } else {
390 self.commands
391 .add(SetVisibility {
392 visibility: Visibility::Hidden,
393 check_lock: true,
394 })
395 .add(SetDisplay {
396 display: Display::None,
397 check_lock: true,
398 });
399 }
400
401 self
402 }
403}
404
405pub trait SetNodeShowHideUncheckedExt {
406 fn show(&mut self) -> &mut Self;
407 fn hide(&mut self) -> &mut Self;
408 fn render(&mut self, render: bool) -> &mut Self;
409}
410
411impl SetNodeShowHideUncheckedExt for UiStyleUnchecked<'_> {
412 fn show(&mut self) -> &mut Self {
413 self.commands
414 .add(SetVisibility {
415 visibility: Visibility::Inherited,
416 check_lock: false,
417 })
418 .add(SetDisplay {
419 display: Display::Flex,
420 check_lock: false,
421 });
422 self
423 }
424
425 fn hide(&mut self) -> &mut Self {
426 self.commands
427 .add(SetVisibility {
428 visibility: Visibility::Hidden,
429 check_lock: false,
430 })
431 .add(SetDisplay {
432 display: Display::None,
433
434 check_lock: false,
435 });
436 self
437 }
438
439 fn render(&mut self, render: bool) -> &mut Self {
440 if render {
441 self.commands
442 .add(SetVisibility {
443 visibility: Visibility::Inherited,
444 check_lock: false,
445 })
446 .add(SetDisplay {
447 display: Display::Flex,
448 check_lock: false,
449 });
450 } else {
451 self.commands
452 .add(SetVisibility {
453 visibility: Visibility::Hidden,
454 check_lock: false,
455 })
456 .add(SetDisplay {
457 display: Display::None,
458 check_lock: false,
459 });
460 }
461
462 self
463 }
464}
465
466pub struct SetAbsolutePosition {
467 absolute_position: Vec2,
468 check_lock: bool,
469}
470
471impl EntityCommand for SetAbsolutePosition {
472 fn apply(self, entity: Entity, world: &mut World) {
473 if self.check_lock {
474 check_lock!(world, entity, "position: top", LockableStyleAttribute::Top);
475 check_lock!(
476 world,
477 entity,
478 "position: left",
479 LockableStyleAttribute::Left
480 );
481 }
482
483 let offset = if let Some(parent) = world.get::<Parent>(entity) {
484 let Some(parent_node) = world.get::<Node>(parent.get()) else {
485 warn!(
486 "Failed to set position on entity {}: Parent has no Node component!",
487 entity
488 );
489 return;
490 };
491
492 let size = parent_node.unrounded_size();
493 let Some(parent_transform) = world.get::<GlobalTransform>(parent.get()) else {
494 warn!(
495 "Failed to set position on entity {}: Parent has no GlobalTransform component!",
496 entity
497 );
498 return;
499 };
500
501 parent_transform.translation().truncate() - (size / 2.)
502 } else {
503 Vec2::ZERO
504 };
505
506 let Some(mut style) = world.get_mut::<Style>(entity) else {
507 warn!(
508 "Failed to set position on entity {}: No Style component found!",
509 entity
510 );
511 return;
512 };
513
514 style.top = Val::Px(self.absolute_position.y - offset.y);
515 style.left = Val::Px(self.absolute_position.x - offset.x);
516 }
517}
518
519pub trait SetAbsolutePositionExt {
520 fn absolute_position(&mut self, position: Vec2) -> &mut Self;
521}
522
523impl SetAbsolutePositionExt for UiStyle<'_> {
524 fn absolute_position(&mut self, position: Vec2) -> &mut Self {
525 self.commands.add(SetAbsolutePosition {
526 absolute_position: position,
527 check_lock: true,
528 });
529 self
530 }
531}
532
533pub trait SetAbsolutePositionUncheckedExt {
534 fn absolute_position(&mut self, position: Vec2) -> &mut Self;
535}
536
537impl SetAbsolutePositionUncheckedExt for UiStyleUnchecked<'_> {
538 fn absolute_position(&mut self, position: Vec2) -> &mut Self {
539 self.commands.add(SetAbsolutePosition {
540 absolute_position: position,
541 check_lock: false,
542 });
543 self
544 }
545}
546
547impl EntityCommand for SetIcon {
548 fn apply(self, entity: Entity, world: &mut World) {
549 match self.icon {
551 IconData::None => {
552 if self.check_lock {
553 check_lock!(world, entity, "icon", LockableStyleAttribute::Image);
554 }
556
557 world.entity_mut(entity).remove::<Text>();
558 world.entity_mut(entity).remove::<UiImage>();
559 }
560 IconData::Image(path, color) => {
561 SetImage {
562 source: ImageSource::Path(path),
563 check_lock: self.check_lock,
564 }
565 .apply(entity, world);
566 SetImageTint {
567 image_tint: color,
568 check_lock: self.check_lock,
569 }
570 .apply(entity, world);
571 }
572 IconData::FontCodepoint(font, codepoint, color, font_size) => {
573 world
576 .entity_mut(entity)
577 .insert(BackgroundColor(Color::NONE));
578
579 world.entity_mut(entity).remove::<UiImage>();
580 let font = world.resource::<AssetServer>().load(font);
581
582 if let Some(mut text) = world.get_mut::<Text>(entity) {
583 text.sections = vec![TextSection::new(
584 codepoint,
585 TextStyle {
586 font,
587 font_size,
588 color,
589 },
590 )];
591 } else {
592 world.entity_mut(entity).insert((
593 Text::from_section(
594 codepoint,
595 TextStyle {
596 font,
597 font_size,
598 color,
599 },
600 )
601 .with_justify(JustifyText::Center)
602 .with_no_wrap(),
603 TextLayoutInfo::default(),
604 TextFlags::default(),
605 ));
606 }
607 }
608 }
609 }
610}
611
612#[derive(Clone, Debug)]
613pub enum FontSource {
614 Path(String),
615 Handle(Handle<Font>),
616}
617
618impl Default for FontSource {
619 fn default() -> Self {
620 Self::Handle(Handle::default())
621 }
622}
623
624impl From<&str> for FontSource {
625 fn from(path: &str) -> Self {
626 Self::Path(path.to_string())
627 }
628}
629
630impl From<String> for FontSource {
631 fn from(path: String) -> Self {
632 Self::Path(path)
633 }
634}
635
636impl EntityCommand for SetFont {
638 fn apply(self, entity: Entity, world: &mut World) {
639 let font = match self.font {
640 FontSource::Path(path) => world.resource::<AssetServer>().load(path),
641 FontSource::Handle(handle) => handle,
642 };
643
644 let Some(mut text) = world.get_mut::<Text>(entity) else {
645 warn!(
646 "Failed to set font on entity {}: No Text component found!",
647 entity
648 );
649 return;
650 };
651
652 text.sections = text
653 .sections
654 .iter_mut()
655 .map(|section| {
656 section.style.font = font.clone();
657 section.clone()
658 })
659 .collect();
660 }
661}
662
663impl EntityCommand for SetFontSize {
664 fn apply(self, entity: Entity, world: &mut World) {
665 let Some(mut text) = world.get_mut::<Text>(entity) else {
666 warn!(
667 "Failed to set font on entity {}: No Text component found!",
668 entity
669 );
670 return;
671 };
672
673 text.sections = text
674 .sections
675 .iter_mut()
676 .map(|section| {
677 section.style.font_size = self.font_size;
678 section.clone()
679 })
680 .collect();
681 }
682}
683
684impl EntityCommand for SetSizedFont {
685 fn apply(self, entity: Entity, world: &mut World) {
686 let font = world.resource::<AssetServer>().load(self.sized_font.font);
687
688 let Some(mut text) = world.get_mut::<Text>(entity) else {
689 warn!(
690 "Failed to set sized font on entity {}: No Text component found!",
691 entity
692 );
693 return;
694 };
695
696 text.sections = text
697 .sections
698 .iter_mut()
699 .map(|section| {
700 section.style.font = font.clone();
701 section.style.font_size = self.sized_font.size;
702 section.clone()
703 })
704 .collect();
705 }
706}
707
708impl EntityCommand for SetFontColor {
709 fn apply(self, entity: Entity, world: &mut World) {
710 let Some(mut text) = world.get_mut::<Text>(entity) else {
711 warn!(
712 "Failed to set font on entity {}: No Text component found!",
713 entity
714 );
715 return;
716 };
717
718 text.sections = text
719 .sections
720 .iter_mut()
721 .map(|section| {
722 section.style.color = self.font_color;
723 section.clone()
724 })
725 .collect();
726 }
727}
728
729struct SetLockedAttribute {
730 attribute: LockableStyleAttribute,
731 locked: bool,
732}
733
734impl EntityCommand for SetLockedAttribute {
735 fn apply(self, entity: Entity, world: &mut World) {
736 if let Some(mut locked_attributes) = world.get_mut::<LockedStyleAttributes>(entity) {
737 if self.locked {
738 if !locked_attributes.contains(self.attribute) {
739 locked_attributes.0.insert(self.attribute);
740 }
741 } else {
742 if locked_attributes.contains(self.attribute) {
743 locked_attributes.0.remove(&self.attribute);
744 }
745 }
746 } else if self.locked {
747 let mut locked_attributes = LockedStyleAttributes::default();
748 locked_attributes.0.insert(self.attribute);
749 world.entity_mut(entity).insert(locked_attributes);
750 }
751 }
752}
753
754pub trait SetLockedAttributeExt {
755 fn lock_attribute(&mut self, attribute: LockableStyleAttribute) -> &mut Self;
756}
757
758impl SetLockedAttributeExt for UiStyle<'_> {
759 fn lock_attribute(&mut self, attribute: LockableStyleAttribute) -> &mut Self {
760 self.commands.add(SetLockedAttribute {
761 attribute,
762 locked: true,
763 });
764 self
765 }
766}
767
768pub trait SetLockedAttributeUncheckedExt {
769 fn unlock_attribute(&mut self, attribute: LockableStyleAttribute) -> &mut Self;
770}
771
772impl SetLockedAttributeUncheckedExt for UiStyleUnchecked<'_> {
773 fn unlock_attribute(&mut self, attribute: LockableStyleAttribute) -> &mut Self {
774 self.commands.add(SetLockedAttribute {
775 attribute,
776 locked: false,
777 });
778 self
779 }
780}
781
782impl EntityCommand for SetScale {
783 fn apply(self, entity: Entity, world: &mut World) {
784 if self.check_lock {
785 check_lock!(world, entity, "scale", LockableStyleAttribute::Scale);
786 }
787
788 let Some(mut transform) = world.get_mut::<Transform>(entity) else {
789 warn!(
790 "Failed to set scale on entity {}: No Transform component found!",
791 entity
792 );
793 return;
794 };
795
796 let new_scale = Vec3::ONE * self.scale;
797 if transform.scale != new_scale {
798 transform.scale = new_scale;
799 }
800 }
801}
802
803impl EntityCommand for SetSize {
804 fn apply(self, entity: Entity, world: &mut World) {
805 if self.check_lock {
806 check_lock!(world, entity, "size: width", LockableStyleAttribute::Width);
807 check_lock!(
808 world,
809 entity,
810 "size: height",
811 LockableStyleAttribute::Height
812 );
813 }
814
815 let Some(mut style) = world.get_mut::<Style>(entity) else {
816 warn!(
817 "Failed to set size on entity {}: No Style component found!",
818 entity
819 );
820 return;
821 };
822
823 if style.width != self.size {
824 style.width = self.size;
825 }
826
827 if style.height != self.size {
828 style.height = self.size;
829 }
830 }
831}