mcgooey/
lib.rs

1pub extern crate macroquad;
2use macroquad::prelude::*;
3pub mod button;
4pub mod column;
5pub mod row;
6pub mod text;
7
8use auto_impl::auto_impl;
9
10use self::row::Row;
11
12pub trait App {
13    fn tick(&mut self);
14}
15
16///Root of all other widgets. Represents the game window geometry.
17pub struct View {
18    ///Represents geometry of the window
19    pub geometry: Geometry,
20    ///Top most widget in the current view
21    child: Box<dyn Widget>,
22}
23
24impl Default for View {
25    fn default() -> Self {
26        View {
27            geometry: Geometry::new(Vector2::from(0, 0)),
28            child: Box::new(Row::new()),
29        }
30    }
31}
32
33// High level abstraction representing a single UI view
34impl View {
35    #[cfg(feature = "debug_draw")]
36    pub fn debug_draw(&self) {
37        draw_circle(0f32, 0f32, 20f32, RED);
38        draw_circle(screen_width(), 0f32, 20f32, RED);
39        draw_circle(0f32, screen_height(), 20f32, RED);
40        draw_circle(screen_width(), screen_height(), 20f32, RED);
41    }
42    pub fn draw(&self) {
43        #[cfg(feature = "debug_draw")]
44        self.debug_draw();
45        self.child.draw();
46    }
47
48    pub fn tick(&mut self) {
49        // redraw if any child in the widget tree requests a rebuild or window is resized
50        if self.child.get_build() || self.resized() {
51            self.build();
52        }
53        self.child.tick();
54    }
55
56    pub fn new<T: Widget + 'static>(child: T) -> View {
57        View {
58            geometry: Geometry {
59                top_left: Vector2 { x: 0f32, y: 0f32 },
60                top_left_curr: Vector2 { x: 0f32, y: 0f32 },
61                sides: Vector2 { x: 0f32, y: 0f32 },
62                abs_sides: Vector2 { x: 0f32, y: 0f32 },
63                margins: Directions2D {
64                    top: 0f32,
65                    bottom: 0f32,
66                    left: 0f32,
67                    right: 0f32,
68                },
69                abs_margins: Directions2D {
70                    top: 0f32,
71                    bottom: 0f32,
72                    left: 0f32,
73                    right: 0f32,
74                },
75            },
76            child: Box::new(child),
77        }
78    }
79
80    pub fn build(&mut self) {
81        self.geometry.abs_sides.x = screen_width();
82        self.geometry.abs_sides.y = screen_height();
83        self.geometry.top_left_curr = self.child.build(&self.geometry, None);
84    }
85
86    pub fn resized(&self) -> bool {
87        if screen_height() == self.geometry.abs_sides.y
88            && screen_width() == self.geometry.abs_sides.x
89        {
90            false
91        } else {
92            true
93        }
94    }
95}
96
97/// Represents a 2D vector value
98#[derive(Copy, Clone)]
99pub struct Vector2 {
100    pub x: f32,
101    pub y: f32,
102}
103
104impl Vector2 {
105    pub fn from(x: i32, y: i32) -> Self {
106        Vector2 {
107            x: x as f32,
108            y: y as f32,
109        }
110    }
111    pub fn new(x: f32, y: f32) -> Self {
112        Vector2 { x, y }
113    }
114}
115
116/// Represents the four 2D directions
117#[derive(Copy, Clone)]
118pub struct Directions2D {
119    pub top: f32,
120    pub bottom: f32,
121    pub left: f32,
122    pub right: f32,
123}
124
125impl Directions2D {
126    pub fn new(top: f32, bottom: f32, left: f32, right: f32) -> Self {
127        Directions2D {
128            top,
129            bottom,
130            left,
131            right,
132        }
133    }
134}
135
136///Describes rectangle geometry assigned to a widget
137pub struct Geometry {
138    //Only "public" data member
139    ///length of sides in % of available space
140    pub sides: Vector2,
141
142    //Private stuff (not intended to be set by the user
143    ///Coordinates of top left point
144    pub top_left: Vector2,
145
146    ///Coordinates of top left point from which to start drawing current widget
147    pub top_left_curr: Vector2,
148
149    ///absolute side length as set by the build algorithm
150    pub abs_sides: Vector2,
151
152    ///outer padding in widget, described vertically and horizontally, used optionally during draw. Represented as percentages.
153    pub margins: Directions2D,
154
155    ///the absolute margin dimensions, set by the build algorithm
156    pub abs_margins: Directions2D,
157}
158
159impl Geometry {
160    pub fn new(sides: Vector2) -> Self {
161        if sides.x < 0f32 || sides.y < 0f32 {
162            panic!("Widget has no geometry!");
163        } else {
164            Geometry {
165                top_left: Vector2::new(0f32, 0f32),
166                top_left_curr: Vector2::new(0f32, 0f32),
167                abs_sides: Vector2::new(0f32, 0f32),
168                sides,
169                margins: Directions2D::new(
170                    (100f32 - sides.y) / 2f32,
171                    (100f32 - sides.y) / 2f32,
172                    (100f32 - sides.x) / 2f32,
173                    (100f32 - sides.x) / 2f32,
174                ),
175                abs_margins: Directions2D::new(
176                    (100f32 - sides.y) / 2f32,
177                    (100f32 - sides.y) / 2f32,
178                    (100f32 - sides.x) / 2f32,
179                    (100f32 - sides.x) / 2f32,
180                ),
181            }
182        }
183    }
184}
185
186#[auto_impl(&mut)]
187pub trait Widget {
188    fn draw(&self);
189
190    /// Duties of build:
191    /// 1. Set top left. The widget is drawn from the top left onwards.
192    /// 2. Set abs size. The absolute size of the widget after accounting for the screen width by converting percentags
193    /// 3. Set abs margins. Used for debug drawing. This is the calculated amount of margin from percentages or inherited from parent.
194    /// 4. Call build on all children.
195    /// 5. Return top left offset, ie, the top left starting point for the next widget to be drawn by the parent widget (it's next child).
196    /// Assume the parent widget draws diagonally. This is so that the returing child widget is agnostic of what it's parent is (the parent may use horizontal, vertical, or both offsets.)
197    fn build(&mut self, geometry: &Geometry, margin: Option<Directions2D>) -> Vector2;
198    fn tick(&mut self);
199
200    /// Get widget's absolute dimensions
201    fn get_side(&self) -> Vector2;
202
203    /// Get the widget's ID. This has to be set manually by the user using the id method of the widget,
204    ///and can be used to uniquely identify the widget when communicating with external functions like in the button callbacks.
205    fn get_id(&self) -> u16;
206
207    /// A widget may need an explicit rebuild even when the window isn't resized because
208    /// for example it just got a new child that needs building
209    fn get_build(&self) -> bool;
210}