1use core::fmt;
7
8use glam::Vec2;
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
13pub struct Position {
14 pub x: f32,
16 pub y: f32,
18}
19
20impl Position {
21 #[must_use]
23 pub const fn new(x: f32, y: f32) -> Self {
24 Self { x, y }
25 }
26
27 #[must_use]
29 pub const fn zero() -> Self {
30 Self { x: 0.0, y: 0.0 }
31 }
32
33 #[must_use]
35 pub const fn as_vec2(self) -> Vec2 {
36 Vec2::new(self.x, self.y)
37 }
38
39 #[must_use]
41 pub const fn from_vec2(v: Vec2) -> Self {
42 Self { x: v.x, y: v.y }
43 }
44
45 #[must_use]
47 pub fn distance_to(self, other: Self) -> f32 {
48 let dx = other.x - self.x;
49 let dy = other.y - self.y;
50 dx.hypot(dy)
51 }
52}
53
54impl Default for Position {
55 fn default() -> Self {
56 Self::zero()
57 }
58}
59
60impl fmt::Display for Position {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 write!(f, "({:.2}, {:.2})", self.x, self.y)
63 }
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
68pub struct Velocity {
69 pub x: f32,
71 pub y: f32,
73}
74
75impl Velocity {
76 #[must_use]
78 pub const fn new(x: f32, y: f32) -> Self {
79 Self { x, y }
80 }
81
82 #[must_use]
84 pub const fn zero() -> Self {
85 Self { x: 0.0, y: 0.0 }
86 }
87
88 #[must_use]
90 pub fn speed(self) -> f32 {
91 self.x.hypot(self.y)
92 }
93
94 #[must_use]
96 pub fn normalized(self) -> Self {
97 let mag = self.speed();
98 if mag < f32::EPSILON {
99 Self::zero()
100 } else {
101 Self {
102 x: self.x / mag,
103 y: self.y / mag,
104 }
105 }
106 }
107
108 #[must_use]
110 pub const fn scaled(self, factor: f32) -> Self {
111 Self {
112 x: self.x * factor,
113 y: self.y * factor,
114 }
115 }
116}
117
118impl Default for Velocity {
119 fn default() -> Self {
120 Self::zero()
121 }
122}
123
124impl fmt::Display for Velocity {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 write!(f, "vel({:.2}, {:.2})", self.x, self.y)
127 }
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
136pub enum Anchor {
137 #[default]
139 Center,
140 TopLeft,
142 TopCenter,
144 TopRight,
146 MiddleLeft,
148 MiddleRight,
150 BottomLeft,
152 BottomCenter,
154 BottomRight,
156 Stretch,
158}
159
160impl Anchor {
161 #[must_use]
163 pub const fn normalized(self) -> (f32, f32) {
164 match self {
165 Self::TopLeft => (0.0, 0.0),
166 Self::TopCenter => (0.5, 0.0),
167 Self::TopRight => (1.0, 0.0),
168 Self::MiddleLeft => (0.0, 0.5),
169 Self::Center | Self::Stretch => (0.5, 0.5),
170 Self::MiddleRight => (1.0, 0.5),
171 Self::BottomLeft => (0.0, 1.0),
172 Self::BottomCenter => (0.5, 1.0),
173 Self::BottomRight => (1.0, 1.0),
174 }
175 }
176}
177
178impl fmt::Display for Anchor {
179 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180 match self {
181 Self::Center => write!(f, "center"),
182 Self::TopLeft => write!(f, "top-left"),
183 Self::TopCenter => write!(f, "top-center"),
184 Self::TopRight => write!(f, "top-right"),
185 Self::MiddleLeft => write!(f, "middle-left"),
186 Self::MiddleRight => write!(f, "middle-right"),
187 Self::BottomLeft => write!(f, "bottom-left"),
188 Self::BottomCenter => write!(f, "bottom-center"),
189 Self::BottomRight => write!(f, "bottom-right"),
190 Self::Stretch => write!(f, "stretch"),
191 }
192 }
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
197pub enum ScaleMode {
198 #[default]
200 Adaptive,
201 PixelPerfect,
203 Fixed,
205}
206
207#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
209pub struct UiElement {
210 pub anchor: Anchor,
212 pub offset: Vec2,
214 pub size: Vec2,
216 pub scale_mode: ScaleMode,
218 pub visible: bool,
220 pub z_order: i32,
222}
223
224impl UiElement {
225 #[must_use]
227 pub const fn new(size: Vec2) -> Self {
228 Self {
229 anchor: Anchor::Center,
230 offset: Vec2::ZERO,
231 size,
232 scale_mode: ScaleMode::Adaptive,
233 visible: true,
234 z_order: 0,
235 }
236 }
237
238 #[must_use]
240 pub const fn with_anchor(mut self, anchor: Anchor) -> Self {
241 self.anchor = anchor;
242 self
243 }
244
245 #[must_use]
247 pub const fn with_offset(mut self, offset: Vec2) -> Self {
248 self.offset = offset;
249 self
250 }
251
252 #[must_use]
254 pub const fn with_scale_mode(mut self, mode: ScaleMode) -> Self {
255 self.scale_mode = mode;
256 self
257 }
258
259 #[must_use]
261 pub const fn with_z_order(mut self, z: i32) -> Self {
262 self.z_order = z;
263 self
264 }
265
266 #[must_use]
268 pub fn calculate_position(&self, container_size: Vec2) -> Vec2 {
269 let (ax, ay) = self.anchor.normalized();
270 Vec2::new(
271 container_size.x.mul_add(ax, self.offset.x),
272 container_size.y.mul_add(ay, self.offset.y),
273 )
274 }
275}
276
277impl Default for UiElement {
278 fn default() -> Self {
279 Self::new(Vec2::new(100.0, 100.0))
280 }
281}
282
283#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
285pub struct Camera {
286 pub zoom: f32,
288 pub target_resolution: Option<Vec2>,
290 pub keep_aspect: bool,
292 pub fov: f32,
294 pub position: Position,
296}
297
298impl Camera {
299 #[must_use]
301 pub const fn new() -> Self {
302 Self {
303 zoom: 1.0,
304 target_resolution: None,
305 keep_aspect: true,
306 fov: 60.0,
307 position: Position::zero(),
308 }
309 }
310
311 #[must_use]
313 pub const fn pixel_art(width: f32, height: f32) -> Self {
314 Self {
315 zoom: 1.0,
316 target_resolution: Some(Vec2::new(width, height)),
317 keep_aspect: true,
318 fov: 60.0,
319 position: Position::zero(),
320 }
321 }
322
323 #[must_use]
325 pub const fn with_zoom(mut self, zoom: f32) -> Self {
326 self.zoom = zoom;
327 self
328 }
329
330 #[must_use]
332 pub const fn with_position(mut self, pos: Position) -> Self {
333 self.position = pos;
334 self
335 }
336}
337
338impl Default for Camera {
339 fn default() -> Self {
340 Self::new()
341 }
342}
343
344#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
346pub struct Sprite {
347 pub texture_id: u32,
349 pub source: Option<Rect>,
351 pub color: [f32; 4],
353 pub flip_x: bool,
355 pub flip_y: bool,
357}
358
359impl Sprite {
360 #[must_use]
362 pub const fn new(texture_id: u32) -> Self {
363 Self {
364 texture_id,
365 source: None,
366 color: [1.0, 1.0, 1.0, 1.0],
367 flip_x: false,
368 flip_y: false,
369 }
370 }
371
372 #[must_use]
374 pub const fn with_source(mut self, rect: Rect) -> Self {
375 self.source = Some(rect);
376 self
377 }
378
379 #[must_use]
381 pub const fn with_color(mut self, r: f32, g: f32, b: f32, a: f32) -> Self {
382 self.color = [r, g, b, a];
383 self
384 }
385}
386
387impl Default for Sprite {
388 fn default() -> Self {
389 Self::new(0)
390 }
391}
392
393#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
395pub struct Rect {
396 pub x: f32,
398 pub y: f32,
400 pub width: f32,
402 pub height: f32,
404}
405
406impl Rect {
407 #[must_use]
409 pub const fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
410 Self {
411 x,
412 y,
413 width,
414 height,
415 }
416 }
417
418 #[must_use]
420 pub const fn from_size(width: f32, height: f32) -> Self {
421 Self::new(0.0, 0.0, width, height)
422 }
423
424 #[must_use]
426 pub fn contains_point(&self, x: f32, y: f32) -> bool {
427 x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height
428 }
429
430 #[must_use]
432 pub fn overlaps(&self, other: &Self) -> bool {
433 self.x < other.x + other.width
434 && self.x + self.width > other.x
435 && self.y < other.y + other.height
436 && self.y + self.height > other.y
437 }
438
439 #[must_use]
441 pub const fn center(&self) -> (f32, f32) {
442 (self.x + self.width / 2.0, self.y + self.height / 2.0)
443 }
444}
445
446impl Default for Rect {
447 fn default() -> Self {
448 Self::new(0.0, 0.0, 1.0, 1.0)
449 }
450}
451
452#[cfg(test)]
453#[allow(clippy::unwrap_used, clippy::expect_used)]
454mod tests {
455 use super::*;
456
457 #[test]
460 fn test_position_new() {
461 let pos = Position::new(10.0, 20.0);
462 assert!((pos.x - 10.0).abs() < f32::EPSILON);
463 assert!((pos.y - 20.0).abs() < f32::EPSILON);
464 }
465
466 #[test]
467 fn test_position_zero() {
468 let pos = Position::zero();
469 assert!((pos.x).abs() < f32::EPSILON);
470 assert!((pos.y).abs() < f32::EPSILON);
471 }
472
473 #[test]
474 fn test_position_default() {
475 let pos = Position::default();
476 assert!((pos.x).abs() < f32::EPSILON);
477 assert!((pos.y).abs() < f32::EPSILON);
478 }
479
480 #[test]
481 fn test_position_as_vec2() {
482 let pos = Position::new(10.0, 20.0);
483 let v = pos.as_vec2();
484 assert!((v.x - 10.0).abs() < f32::EPSILON);
485 assert!((v.y - 20.0).abs() < f32::EPSILON);
486 }
487
488 #[test]
489 fn test_position_from_vec2() {
490 let v = Vec2::new(10.0, 20.0);
491 let pos = Position::from_vec2(v);
492 assert!((pos.x - 10.0).abs() < f32::EPSILON);
493 assert!((pos.y - 20.0).abs() < f32::EPSILON);
494 }
495
496 #[test]
497 fn test_position_distance() {
498 let p1 = Position::new(0.0, 0.0);
499 let p2 = Position::new(3.0, 4.0);
500 assert!((p1.distance_to(p2) - 5.0).abs() < f32::EPSILON);
501 }
502
503 #[test]
504 fn test_position_display() {
505 let pos = Position::new(1.5, 2.5);
506 assert_eq!(format!("{pos}"), "(1.50, 2.50)");
507 }
508
509 #[test]
512 fn test_velocity_new() {
513 let vel = Velocity::new(1.0, 2.0);
514 assert!((vel.x - 1.0).abs() < f32::EPSILON);
515 assert!((vel.y - 2.0).abs() < f32::EPSILON);
516 }
517
518 #[test]
519 fn test_velocity_zero() {
520 let vel = Velocity::zero();
521 assert!((vel.x).abs() < f32::EPSILON);
522 assert!((vel.y).abs() < f32::EPSILON);
523 }
524
525 #[test]
526 fn test_velocity_default() {
527 let vel = Velocity::default();
528 assert!((vel.x).abs() < f32::EPSILON);
529 assert!((vel.y).abs() < f32::EPSILON);
530 }
531
532 #[test]
533 fn test_velocity_speed() {
534 let vel = Velocity::new(3.0, 4.0);
535 assert!((vel.speed() - 5.0).abs() < f32::EPSILON);
536 }
537
538 #[test]
539 fn test_velocity_normalized() {
540 let vel = Velocity::new(3.0, 4.0);
541 let norm = vel.normalized();
542 assert!((norm.speed() - 1.0).abs() < 0.001);
543 }
544
545 #[test]
546 fn test_velocity_normalized_zero() {
547 let vel = Velocity::zero();
548 let norm = vel.normalized();
549 assert!((norm.x).abs() < f32::EPSILON);
550 assert!((norm.y).abs() < f32::EPSILON);
551 }
552
553 #[test]
554 fn test_velocity_scaled() {
555 let vel = Velocity::new(1.0, 2.0);
556 let scaled = vel.scaled(2.0);
557 assert!((scaled.x - 2.0).abs() < f32::EPSILON);
558 assert!((scaled.y - 4.0).abs() < f32::EPSILON);
559 }
560
561 #[test]
562 fn test_velocity_display() {
563 let vel = Velocity::new(1.5, 2.5);
564 assert_eq!(format!("{vel}"), "vel(1.50, 2.50)");
565 }
566
567 #[test]
570 fn test_anchor_normalized() {
571 assert_eq!(Anchor::TopLeft.normalized(), (0.0, 0.0));
572 assert_eq!(Anchor::TopCenter.normalized(), (0.5, 0.0));
573 assert_eq!(Anchor::TopRight.normalized(), (1.0, 0.0));
574 assert_eq!(Anchor::MiddleLeft.normalized(), (0.0, 0.5));
575 assert_eq!(Anchor::Center.normalized(), (0.5, 0.5));
576 assert_eq!(Anchor::MiddleRight.normalized(), (1.0, 0.5));
577 assert_eq!(Anchor::BottomLeft.normalized(), (0.0, 1.0));
578 assert_eq!(Anchor::BottomCenter.normalized(), (0.5, 1.0));
579 assert_eq!(Anchor::BottomRight.normalized(), (1.0, 1.0));
580 assert_eq!(Anchor::Stretch.normalized(), (0.5, 0.5));
581 }
582
583 #[test]
584 fn test_anchor_default() {
585 let anchor = Anchor::default();
586 assert_eq!(anchor, Anchor::Center);
587 }
588
589 #[test]
590 fn test_anchor_display() {
591 assert_eq!(format!("{}", Anchor::TopLeft), "top-left");
592 assert_eq!(format!("{}", Anchor::TopCenter), "top-center");
593 assert_eq!(format!("{}", Anchor::TopRight), "top-right");
594 assert_eq!(format!("{}", Anchor::MiddleLeft), "middle-left");
595 assert_eq!(format!("{}", Anchor::Center), "center");
596 assert_eq!(format!("{}", Anchor::MiddleRight), "middle-right");
597 assert_eq!(format!("{}", Anchor::BottomLeft), "bottom-left");
598 assert_eq!(format!("{}", Anchor::BottomCenter), "bottom-center");
599 assert_eq!(format!("{}", Anchor::BottomRight), "bottom-right");
600 assert_eq!(format!("{}", Anchor::Stretch), "stretch");
601 }
602
603 #[test]
606 fn test_scale_mode_default() {
607 let mode = ScaleMode::default();
608 assert_eq!(mode, ScaleMode::Adaptive);
609 }
610
611 #[test]
614 fn test_ui_element_new() {
615 let elem = UiElement::new(Vec2::new(100.0, 50.0));
616 assert_eq!(elem.anchor, Anchor::Center);
617 assert_eq!(elem.offset, Vec2::ZERO);
618 assert!(elem.visible);
619 assert_eq!(elem.z_order, 0);
620 assert_eq!(elem.scale_mode, ScaleMode::Adaptive);
621 }
622
623 #[test]
624 fn test_ui_element_default() {
625 let elem = UiElement::default();
626 assert!((elem.size.x - 100.0).abs() < f32::EPSILON);
627 assert!((elem.size.y - 100.0).abs() < f32::EPSILON);
628 }
629
630 #[test]
631 fn test_ui_element_builders() {
632 let elem = UiElement::new(Vec2::new(100.0, 50.0))
633 .with_anchor(Anchor::TopLeft)
634 .with_offset(Vec2::new(10.0, 20.0))
635 .with_scale_mode(ScaleMode::PixelPerfect)
636 .with_z_order(5);
637
638 assert_eq!(elem.anchor, Anchor::TopLeft);
639 assert_eq!(elem.offset, Vec2::new(10.0, 20.0));
640 assert_eq!(elem.scale_mode, ScaleMode::PixelPerfect);
641 assert_eq!(elem.z_order, 5);
642 }
643
644 #[test]
645 fn test_ui_element_position_calculation() {
646 let elem = UiElement::new(Vec2::new(100.0, 50.0))
647 .with_anchor(Anchor::TopLeft)
648 .with_offset(Vec2::new(10.0, 20.0));
649
650 let pos = elem.calculate_position(Vec2::new(800.0, 600.0));
651 assert!((pos.x - 10.0).abs() < f32::EPSILON);
652 assert!((pos.y - 20.0).abs() < f32::EPSILON);
653 }
654
655 #[test]
656 fn test_ui_element_center_position() {
657 let elem = UiElement::new(Vec2::new(100.0, 50.0)).with_anchor(Anchor::Center);
658
659 let pos = elem.calculate_position(Vec2::new(800.0, 600.0));
660 assert!((pos.x - 400.0).abs() < f32::EPSILON);
661 assert!((pos.y - 300.0).abs() < f32::EPSILON);
662 }
663
664 #[test]
665 fn test_ui_element_bottom_right_position() {
666 let elem = UiElement::new(Vec2::new(100.0, 50.0)).with_anchor(Anchor::BottomRight);
667
668 let pos = elem.calculate_position(Vec2::new(800.0, 600.0));
669 assert!((pos.x - 800.0).abs() < f32::EPSILON);
670 assert!((pos.y - 600.0).abs() < f32::EPSILON);
671 }
672
673 #[test]
676 fn test_rect_new() {
677 let rect = Rect::new(10.0, 20.0, 100.0, 50.0);
678 assert!((rect.x - 10.0).abs() < f32::EPSILON);
679 assert!((rect.y - 20.0).abs() < f32::EPSILON);
680 assert!((rect.width - 100.0).abs() < f32::EPSILON);
681 assert!((rect.height - 50.0).abs() < f32::EPSILON);
682 }
683
684 #[test]
685 fn test_rect_from_size() {
686 let rect = Rect::from_size(100.0, 50.0);
687 assert!((rect.x).abs() < f32::EPSILON);
688 assert!((rect.y).abs() < f32::EPSILON);
689 assert!((rect.width - 100.0).abs() < f32::EPSILON);
690 assert!((rect.height - 50.0).abs() < f32::EPSILON);
691 }
692
693 #[test]
694 fn test_rect_default() {
695 let rect = Rect::default();
696 assert!((rect.x).abs() < f32::EPSILON);
697 assert!((rect.y).abs() < f32::EPSILON);
698 assert!((rect.width - 1.0).abs() < f32::EPSILON);
699 assert!((rect.height - 1.0).abs() < f32::EPSILON);
700 }
701
702 #[test]
703 fn test_rect_contains_point() {
704 let rect = Rect::new(10.0, 10.0, 100.0, 50.0);
705 assert!(rect.contains_point(50.0, 30.0));
706 assert!(!rect.contains_point(0.0, 0.0));
707 assert!(rect.contains_point(10.0, 10.0)); assert!(rect.contains_point(110.0, 60.0)); }
710
711 #[test]
712 fn test_rect_overlaps() {
713 let r1 = Rect::new(0.0, 0.0, 100.0, 100.0);
714 let r2 = Rect::new(50.0, 50.0, 100.0, 100.0);
715 let r3 = Rect::new(200.0, 200.0, 50.0, 50.0);
716
717 assert!(r1.overlaps(&r2));
718 assert!(!r1.overlaps(&r3));
719 }
720
721 #[test]
722 fn test_rect_center() {
723 let rect = Rect::new(0.0, 0.0, 100.0, 50.0);
724 let (cx, cy) = rect.center();
725 assert!((cx - 50.0).abs() < f32::EPSILON);
726 assert!((cy - 25.0).abs() < f32::EPSILON);
727 }
728
729 #[test]
732 fn test_sprite_new() {
733 let sprite = Sprite::new(42);
734 assert_eq!(sprite.texture_id, 42);
735 assert!(sprite.source.is_none());
736 assert!(!sprite.flip_x);
737 assert!(!sprite.flip_y);
738 }
739
740 #[test]
741 fn test_sprite_default() {
742 let sprite = Sprite::default();
743 assert_eq!(sprite.texture_id, 0);
744 }
745
746 #[test]
747 fn test_sprite_with_source() {
748 let sprite = Sprite::new(1).with_source(Rect::new(0.0, 0.0, 32.0, 32.0));
749 assert!(sprite.source.is_some());
750 let src = sprite.source.unwrap();
751 assert!((src.width - 32.0).abs() < f32::EPSILON);
752 }
753
754 #[test]
755 fn test_sprite_with_color() {
756 let sprite = Sprite::new(1).with_color(1.0, 0.5, 0.0, 0.8);
757 assert!((sprite.color[0] - 1.0).abs() < f32::EPSILON);
758 assert!((sprite.color[1] - 0.5).abs() < f32::EPSILON);
759 assert!((sprite.color[2] - 0.0).abs() < f32::EPSILON);
760 assert!((sprite.color[3] - 0.8).abs() < f32::EPSILON);
761 }
762
763 #[test]
766 fn test_camera_new() {
767 let cam = Camera::new();
768 assert!((cam.zoom - 1.0).abs() < f32::EPSILON);
769 assert!(cam.keep_aspect);
770 assert!(cam.target_resolution.is_none());
771 assert!((cam.fov - 60.0).abs() < f32::EPSILON);
772 }
773
774 #[test]
775 fn test_camera_default() {
776 let cam = Camera::default();
777 assert!((cam.zoom - 1.0).abs() < f32::EPSILON);
778 assert!(cam.keep_aspect);
779 }
780
781 #[test]
782 fn test_camera_pixel_art() {
783 let cam = Camera::pixel_art(320.0, 240.0);
784 assert!(cam.target_resolution.is_some());
785 let res = cam.target_resolution.unwrap();
786 assert!((res.x - 320.0).abs() < f32::EPSILON);
787 assert!((res.y - 240.0).abs() < f32::EPSILON);
788 }
789
790 #[test]
791 fn test_camera_with_zoom() {
792 let cam = Camera::new().with_zoom(2.0);
793 assert!((cam.zoom - 2.0).abs() < f32::EPSILON);
794 }
795
796 #[test]
797 fn test_camera_with_position() {
798 let cam = Camera::new().with_position(Position::new(100.0, 200.0));
799 assert!((cam.position.x - 100.0).abs() < f32::EPSILON);
800 assert!((cam.position.y - 200.0).abs() < f32::EPSILON);
801 }
802}