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}