ui_examples/
d_containers.rs

1use ggez::{graphics::Color, *};
2use mooeye::{scene_manager, ui, ui::UiContainer, ui::UiContent};
3
4// # Containers
5// In this example, we learn about the 4 main types of containers provided with mooeye and use them to create a UI containing multiple elements.
6
7/// A very basic scene struct, once again only holding the root element of our GUI.
8pub struct DScene {
9    /// The root element of DScene's GUI.
10    gui: ui::UiElement<()>,
11}
12
13impl DScene {
14    /// Creates a new DScene.
15    /// As you can see, for scenes that do not change based on parameters, we would like to use something like ```DScene::default()``` to
16    /// communicate that this is the standard way for a ```DScene``` to come into existence.
17    /// However, we cannot derive Default as the passing of a parameter ```ctx: &Context``` is almost always neccessary,
18    /// so we have to use ```new(ctx: &Context)``` instead.
19    pub fn new(ctx: &Context) -> Result<Self, GameError> {
20        // Predefine some visuals so we don't have to do it for every element.
21
22        let vis = ui::Visuals::new(
23            Color::from_rgb(180, 120, 60),
24            Color::from_rgb(18, 12, 6),
25            1.,
26            0.,
27        );
28
29        // You can also create 'custom' visuals that allow you to set the thickness of each border & radius of each corner separately.
30
31        let vis2 = ui::Visuals::new_custom(
32            Color::from_rgb(180, 120, 60),
33            Color::from_rgb(18, 12, 6),
34            [4., 1., 4., 1.],
35            [2., 2., 2., 2.],
36        );
37
38        let hover_vis = ui::Visuals::new(
39            Color::from_rgb(160, 100, 40),
40            Color::from_rgb(18, 12, 6),
41            3.,
42            0.,
43        );
44
45        let cont_vis = ui::Visuals::new_custom(
46            Color::from_rgb(60, 120, 180),
47            Color::from_rgb(180, 180, 190),
48            [16., 8., 8., 8.],
49            [12., 2., 2., 12.],
50        );
51
52        // Note that the constructor now returns a Result.
53        // This is neccessary as the 'add' function used to add UI elements to grid containers can fail, thus failing the constructor.
54
55        // The first container we use is a vertical box simply laying out elements from top to bottom.
56        let mut ver_box = ui::containers::VerticalBox::new();
57        // We can manually change the spacing between elements in the box
58        ver_box.spacing = 10.;
59        // first, we need to add all the children to this vertical box.
60        // We'll just use TextElement for now, but these can also be images, sprites, placeholders or more containers.
61        for i in 0..8 {
62            // Create an element.
63            let element = graphics::Text::new(format!("{}", i))
64                .set_font("Bahnschrift")
65                .set_scale(28.)
66                .to_owned()
67                .to_element_builder(0, ctx)
68                .with_visuals(vis2)
69                .build();
70            // Add the element to the box. This cannot fail for non-grid containers, if ver_box were not an actual container
71            // or a container that requires a special method for adding, like e.g. GridBox, it would simply consume the element and do nothing.
72            ver_box.add(element);
73        }
74        // After adding all children, we can convert to a UiElement and style the box like we would style an other element. The usual pattern here is to shadow the variable to avoid use-after-move.
75        let ver_box = ver_box
76            .to_element_builder(0, ctx)
77            .with_visuals(cont_vis)
78            // Using larger padding to accomodate our thick borders.
79            .with_padding((24., 16., 16., 16.))
80            .build();
81
82        // Another container we can use is GridBox. A GridBox needs to be initialized with a set height and width and cannot be extended.
83        let mut grid = ui::containers::GridBox::new(4, 4);
84        // The contents of a grid box are initialized as empty elements.  We'll add buttons to the diagonal of the grid.
85        for i in 0..4 {
86            // Create an element.
87            let element = graphics::Text::new(format!("{}", i))
88                .set_font("Bahnschrift")
89                .set_scale(28.)
90                .to_owned()
91                .to_element_builder(0, ctx)
92                .with_visuals(vis)
93                // Elements can be given alignment and will align within their respecitive cell in the grid.
94                .with_alignment(ui::Alignment::Max, None)
95                .build();
96            // Add the element to the box. This can fail, if ver_box were not an actual container
97            // or a container that requires a special method for adding, like e.g. GridBox.
98            grid.add(element, i, i)?;
99        }
100
101        // We'll also create our usual back button and put it into the top right of the grid.
102
103        let back = graphics::Text::new("Back!")
104            .set_font("Bahnschrift")
105            .set_scale(28.)
106            .to_owned()
107            .to_element_builder(1, ctx)
108            .with_visuals(vis)
109            .with_hover_visuals(hover_vis)
110            .build();
111
112        // This time, we'll enhance our back button a bit by using an icon that is displayed over the top left corner.
113        // To achieve this, we'll use a StackBox.
114        let mut stack = ui::containers::StackBox::new();
115        stack.add(back);
116        // The add_top function adds something to the top of a stack box. Creating and adding an element can be done inline.
117        stack.add_top(
118            graphics::Image::from_path(ctx, "/moo.png")?
119                .to_element_builder(0, ctx)
120                // We'll align the icon to the top right
121                .with_alignment(ui::Alignment::Min, ui::Alignment::Min)
122                // and offset it slightly
123                .with_offset(-10., -10.)
124                .build(),
125        )?;
126        // to_element is a shorthand for to_element_builder().build() if we want to simply take the default builder and not change anything.
127        let stack = stack.to_element(0, ctx);
128
129        // Now, we add the stack to the grid.
130        grid.add(stack, 3, 0)?;
131
132        // And finally build the grid.
133        let grid = grid
134            .to_element_builder(0, ctx)
135            .with_visuals(cont_vis)
136            .with_padding((24., 16., 16., 16.))
137            .build();
138
139        // The horizontal box is exactly the same as the vertical box except for orientation.
140        // We will use a horizontal box to contain the boxes created so far.
141        // if you don't want to create multiple variables, adding of children can be done inline for non-grid
142        // containers by using .with_child.
143        Ok(Self {
144            gui: ui::containers::HorizontalBox::new()
145                .to_element_builder(0, ctx)
146                .with_child(ver_box)
147                .with_child(grid)
148                .build(),
149        })
150    }
151}
152
153impl scene_manager::Scene for DScene {
154    fn update(&mut self, ctx: &mut Context) -> Result<scene_manager::SceneSwitch, GameError> {
155        // Nothing much to do here, except implement the back button functionality.
156
157        let messages = self.gui.manage_messages(ctx, None);
158
159        if messages.contains(&ui::UiMessage::Triggered(1)) {
160            // If it is, we end the current scene (and return to the previous one) by popping it off the stack.
161            return Ok(scene_manager::SceneSwitch::pop(1));
162        }
163
164        Ok(scene_manager::SceneSwitch::None)
165    }
166
167    fn draw(&mut self, ctx: &mut Context, mouse_listen: bool) -> Result<(), GameError> {
168        // Once again, we first create a canvas and set a pixel sampler. Note that this time, we dont clear the background.
169
170        let mut canvas = ggez::graphics::Canvas::from_frame(ctx, None);
171        // Since we don't set the sampler to 'nearest', our corners will look more round, but the pixel-cow will look blurry.
172        //canvas.set_sampler(ggez::graphics::Sampler::nearest_clamp());
173
174        self.gui.draw_to_screen(ctx, &mut canvas, mouse_listen);
175
176        canvas.finish(ctx)?;
177
178        Ok(())
179    }
180}