ui_examples/
e_messages.rs

1use std::{
2    collections::HashMap,
3    time::Duration,
4};
5
6use mooeye::{ui, ui::UiContent, scene_manager};
7
8use ggez::{
9    context::Context,
10    graphics::{Color, Text},
11    *,
12};
13
14/// A once again basic scene containing only a GUI.
15pub struct EScene {
16    /// The root element of EScene's GUI.
17    gui: ui::UiElement<()>,
18}
19
20impl EScene {
21    /// Creates a new 'default' EScene.
22    pub fn new(ctx: &Context) -> GameResult<Self> {
23
24
25        // This title will change based on transitions whenever certain buttons are clicked.
26        let title = Text::new("Move this element with the buttons.\nYou have not yet clicked a button.")
27        // First, we style the title.
28        .set_font("Bahnschrift")
29        .set_scale(28.)
30        .to_owned()
31        .to_element_builder(0, ctx)
32        // Then, we add a message handler to the element.
33        .with_message_handler(
34
35            // The message handle receives a set of messages and a mutable transition vector.
36        |messages,_,transitions| {
37            let ids = [11,12,13,21,22,23,];
38
39            // We check if any of the buttons that interest us were clicked in the last frame.
40            for id in ids{
41                for message in messages{
42                    if *message == ui::UiMessage::<()>::Clicked(id){
43
44                        // If yes, we add a new transition to the vector.
45                        transitions.push_back(
46                            // Transitions are initalized with the duration they should take to complete and augmented via the builder pattern.
47                            ui::Transition::new(Duration::ZERO)
48                            // Here, we add a new content that will replace the old text once the transition completes.
49                            .with_new_content(Text::new(format!(
50                                "Move this element with the buttons.\nYou clicked a button with id {}.",
51                                id
52                            ))
53                            .set_font("Bahnschrift")
54                            .set_scale(24.)
55                            .to_owned())
56                        )
57                        
58                    }
59                }
60            }
61        })
62        .build();
63
64        // Define a general visual style to use for all buttons.
65        let vis = ui::Visuals::new(
66            Color::from_rgb(77, 109, 191),
67            Color::from_rgb(55, 67, 87),
68            2.,
69            4.,
70        );
71
72        // Create a grid box to hold all buttons.
73        let mut grid_box = ui::containers::GridBox::new(2, 3);
74
75        // Now, we create 6 buttons to move the element to all possible vertical and horizontal alignments and add them to the grid.
76        let vert_up = Text::new(" ^ ")
77            .set_font("Bahnschrift")
78            .to_owned()
79            .to_element_builder(11, ctx)
80            .with_visuals(vis) 
81            // We can also set a sound to be played on click/key press
82            .with_trigger_sound(ggez::audio::Source::new(ctx, "/blipSelect.wav").ok())
83            .build();
84        grid_box
85            .add(vert_up, 0, 0)?;
86
87        let vert_ce = Text::new(" . ")
88            .set_font("Bahnschrift")
89            .to_owned()
90            .to_element_builder(12, ctx)
91            .with_visuals(vis)
92            .build();
93        grid_box
94            .add(vert_ce, 0, 1)?;
95
96        let vert_do = Text::new(" v ")
97            .set_font("Bahnschrift")
98            .to_owned().to_element_builder(13, ctx)
99            .with_visuals(vis)
100            .build();
101        grid_box
102            .add(vert_do, 0, 2)?;
103
104        let hor_up = Text::new(" < ")
105            .set_font("Bahnschrift")
106            .to_owned().to_element_builder(21, ctx)
107            .with_visuals(vis)
108            .build();
109        grid_box
110            .add(hor_up, 1, 0)?;
111
112        let hor_ce = Text::new(" . ")
113            .set_font("Bahnschrift")
114            .to_owned().to_element_builder(22, ctx)
115            .with_visuals(vis)
116            .build();
117        grid_box
118            .add(hor_ce, 1, 1)?;
119
120        let hor_do = Text::new(" > ")
121            .set_font("Bahnschrift")
122            .to_owned().to_element_builder(23, ctx)
123            .with_visuals(vis)
124            .build();
125        grid_box
126            .add(hor_do, 1, 2)?;
127
128        // The well-known back button will take us back to scene select.
129        let back = Text::new("Back")
130            .set_font("Bahnschrift")
131            .to_owned()
132            .to_element_builder(1, ctx)
133            .with_visuals(vis)
134            .as_fill()
135            .build();
136
137        
138        // We create a general VBox to contain our UI
139        // We can create Vertical and Horizontal Boxes with the 'spaced' constructor to set its spacing value.
140        let gui_box = ui::containers::VerticalBox::new_spaced(6.)
141        .to_element_builder(0, ctx)
142        // We put the title, grid and back button together in a box.
143        .with_child(title)
144        .with_child(grid_box.to_element(30, ctx))
145        .with_child(back)
146        .with_size(ui::Size::Shrink(128., f32::INFINITY), ui::Size::Shrink(0., f32::INFINITY))
147        .with_visuals(ui::Visuals::new(
148            Color::from_rgb(120, 170, 200),
149            Color::from_rgb(55, 67, 87),
150            2.,
151            0.,
152        ))
153        // Another message handler. Along with the messages and transition vector as discussed above, we also receive a layout struct
154        // that represents the layout struct of the element in the moment the transition was initialized. This allows us to only change
155        // the relevant parts of the layout without having to recreate it from scratch.
156        .with_message_handler(|messages, layout, transitions| {
157            // This guards prevent spam clicking a button from locking up the element with 1.5-second transitions.
158            if !transitions.is_empty() {
159                return;
160            }
161            let vert_map = HashMap::from([
162                (11, ui::Alignment::Min),
163                (12, ui::Alignment::Center),
164                (13, ui::Alignment::Max),
165            ]);
166            let hor_map = HashMap::from([
167                (21, ui::Alignment::Min),
168                (22, ui::Alignment::Center),
169                (23, ui::Alignment::Max),
170            ]);
171            // Check if any vertical buttons were clicked.
172            for (key, val) in vert_map {
173                if messages.contains(&ui::UiMessage::Triggered(key)) {
174                    transitions.push_back(
175                        // If yes, add a transition.
176                        ui::Transition::new(Duration::from_secs_f32(1.5))
177                        // This time, we don't change the content, but the layout.
178                        // Layout, visuals and hover_visuals are not  changed on completion like content, but instead applied gradually.
179                        .with_new_layout(ui::Layout{
180                            y_alignment: val,
181                            ..layout
182                        }),
183                    );
184                }
185            }
186            // Repeat for horizontal keys.
187            for (key, val) in hor_map {
188                if messages.contains(&ui::UiMessage::Triggered(key)) {
189                    transitions.push_back(
190                        ui::Transition::new(Duration::from_secs_f32(1.5)).with_new_layout(ui::Layout{
191                            x_alignment: val,
192                            ..layout
193                        }),
194                    );
195                }
196            }
197        })
198        .build();
199
200        // Finally, we wrap our gui_box into a space-filling stack pane so we have a place to later add further elements
201
202        Ok(Self {
203            gui: ui::containers::StackBox::new()
204            .to_element_builder(100, ctx)
205            .as_fill()
206            .with_child(gui_box)
207            .build()
208        })
209    }
210}
211
212
213impl scene_manager::Scene for EScene {
214    fn update(&mut self, ctx: &mut Context) -> Result<scene_manager::SceneSwitch, GameError> {
215        // Nothing much to do here, except implement the back button functionality.
216
217        let messages = self.gui.manage_messages(ctx, None);
218
219        if messages.contains(&ui::UiMessage::Triggered(1)){
220            // If it is, we end the current scene (and return to the previous one) by popping it off the stack.
221            return Ok(scene_manager::SceneSwitch::pop(1));
222        }
223
224        if messages.contains(&ui::UiMessage::Triggered(13)){
225            // If a certain button is pressed, add a small text element to the gui.
226            self.gui.add_element(100,
227                // using a duration box as a wrapper will remove the element after a set amount of time
228                  ui::containers::DurationBox::new(
229                    Duration::from_secs_f32(1.5),
230                     graphics::Text::new("Just a small reminder that you pressed button 13.")
231                     .set_font("Bahnschrift")
232                     .set_wrap(true)
233                     .set_bounds(glam::Vec2::new(200., 500.))
234                     .set_scale(28.)
235                     .to_owned()
236                     .to_element_builder(0, ctx)
237                     .with_visuals(ui::Visuals::new(
238                        Color::from_rgb(77, 109, 191),
239                        Color::from_rgb(55, 67, 87),
240                        2.,
241                        4.,
242                    ))
243                     .build()
244                    ).to_element_builder(0, ctx)
245                    .with_alignment(ui::Alignment::Center, ui::Alignment::Min)
246                    .with_offset(0., 25.)
247                    .build()
248                    );
249        }
250
251        Ok(scene_manager::SceneSwitch::None)
252    
253    }
254
255    fn draw(&mut self, ctx: &mut Context, mouse_listen: bool) -> Result<(), GameError> {
256
257        // Once again the basic drawing function.
258
259        let mut canvas = graphics::Canvas::from_frame(ctx, None);
260        canvas.set_sampler(graphics::Sampler::nearest_clamp());
261
262        self.gui.draw_to_screen(ctx, &mut canvas, mouse_listen);
263
264        canvas.finish(ctx)?;
265
266        Ok(())
267    }
268}