Sections Demo 3

sections_demo_3.py
1"""
2Section Example 3:
3
4This shows how sections work with a very small example
5
6What's key here is to understand how sections can isolate code that otherwise
7 goes packed together in the view.
8Also, note that events are received on each section only based on the
9 section configuration. This way you don't have to check every time if the mouse
10 position is on top of some area.
11
12Note:
13 - Event dispatching (two sections will receive on_key_press and on_key_release)
14 - Prevent dispatching to allow some events to stop propagating
15 - Event draw, update and event delivering order based on section_manager
16 sections list order
17 - Section "enable" property to show or hide sections
18 - Modal Sections: sections that draw last but capture all events and also stop
19 other sections from updating.
20
21If Python and Arcade are installed, this example can be run from the command line with:
22python -m arcade.examples.sections_demo_3
23"""
24
25from __future__ import annotations
26
27from math import sqrt
28
29import arcade
30from arcade import Section, SectionManager
31from arcade.types import Color
32
33INFO_BAR_HEIGHT = 40
34PANEL_WIDTH = 200
35SPRITE_SPEED = 1
36
37COLOR_LIGHT = Color.from_hex_string("#D9BBA0")
38COLOR_DARK = Color.from_hex_string("#0D0D0D")
39COLOR_1 = Color.from_hex_string("#2A1459")
40COLOR_2 = Color.from_hex_string("#4B89BF")
41COLOR_3 = Color.from_hex_string("#03A688")
42
43
44class Ball(arcade.SpriteCircle):
45 """The moving ball"""
46
47 def __init__(self, radius, color):
48 super().__init__(radius, color)
49
50 self.bounce_count: int = 0 # to count the number of bounces
51
52 @property
53 def speed(self):
54 # return euclidian distance * current fps (60 default)
55 return int(sqrt(pow(self.change_x, 2) + pow(self.change_y, 2)) * 60)
56
57
58class ModalSection(Section):
59 """A modal section that represents a popup that waits for user input"""
60
61 def __init__(self, left: int, bottom: int, width: int, height: int):
62 super().__init__(left, bottom, width, height, modal=True, enabled=False)
63
64 # modal button
65 self.button = arcade.SpriteSolidColor(100, 50, color=arcade.color.RED)
66 pos = self.left + self.width / 2, self.bottom + self.height / 2
67 self.button.position = pos
68
69 def on_draw(self):
70 # draw modal frame and button
71 arcade.draw_lrbt_rectangle_filled(
72 self.left, self.right, self.bottom, self.top, arcade.color.GRAY
73 )
74 arcade.draw_lrbt_rectangle_outline(
75 self.left, self.right, self.bottom, self.top, arcade.color.WHITE
76 )
77 self.draw_button()
78
79 def draw_button(self):
80 # draws the button and button text
81 arcade.draw_sprite(self.button)
82 arcade.draw_text(
83 "Close Modal",
84 self.button.left + 5,
85 self.button.bottom + self.button.height / 2,
86 arcade.color.WHITE,
87 )
88
89 def on_resize(self, width: int, height: int):
90 """set position on screen resize"""
91 self.left = width // 3
92 self.bottom = (height // 2) - self.height // 2
93 pos = self.left + self.width / 2, self.bottom + self.height / 2
94 self.button.position = pos
95
96 def on_mouse_press(self, x: float, y: float, button: int, modifiers: int):
97 """Check if the button is pressed"""
98 if self.button.collides_with_point((x, y)):
99 self.enabled = False
100
101
102class InfoBar(Section):
103 """This is the top bar of the screen where info is showed"""
104
105 @property
106 def ball(self):
107 return self.view.map.ball
108
109 def on_draw(self):
110 # draw game info
111 arcade.draw_lrbt_rectangle_filled(self.left, self.right, self.bottom, self.top, COLOR_DARK)
112 arcade.draw_lrbt_rectangle_outline(
113 self.left, self.right, self.bottom, self.top, COLOR_LIGHT
114 )
115 arcade.draw_text(
116 f"Ball bounce count: {self.ball.bounce_count}",
117 self.left + 20,
118 self.top - self.height / 1.6,
119 COLOR_LIGHT,
120 )
121
122 ball_change_axis = self.ball.change_x, self.ball.change_y
123 arcade.draw_text(
124 f"Ball change in axis: {ball_change_axis}",
125 self.left + 220,
126 self.top - self.height / 1.6,
127 COLOR_LIGHT,
128 )
129 arcade.draw_text(
130 f"Ball speed: {self.ball.speed} pixels/second",
131 self.left + 480,
132 self.top - self.height / 1.6,
133 COLOR_LIGHT,
134 )
135
136 def on_resize(self, width: int, height: int):
137 # stick to the top
138 self.width = width
139 self.bottom = height - self.view.info_bar.height
140
141
142class Panel(Section):
143 """This is the Panel to the right where buttons and info is showed"""
144
145 def __init__(self, left: int, bottom: int, width: int, height: int, **kwargs):
146 super().__init__(left, bottom, width, height, **kwargs)
147
148 # create buttons
149 self.button_stop = self.new_button(arcade.color.ARSENIC)
150 self.button_toggle_info_bar = self.new_button(COLOR_1)
151
152 self.button_show_modal = self.new_button(COLOR_2)
153 # to show the key that's actually pressed
154 self.pressed_key: int | None = None
155
156 @staticmethod
157 def new_button(color):
158 # helper to create new buttons
159 return arcade.SpriteSolidColor(100, 50, color=color)
160
161 def draw_button_stop(self):
162 arcade.draw_text(
163 "Press button to stop the ball", self.left + 10, self.top - 40, COLOR_LIGHT, 10
164 )
165 arcade.draw_sprite(self.button_stop)
166
167 def draw_button_toggle_info_bar(self):
168 arcade.draw_text(
169 "Press to toggle info_bar", self.left + 10, self.top - 140, COLOR_LIGHT, 10
170 )
171 arcade.draw_sprite(self.button_toggle_info_bar)
172
173 def draw_button_show_modal(self):
174 arcade.draw_sprite(self.button_show_modal)
175 arcade.draw_text(
176 "Show Modal", self.left - 37 + self.width / 2, self.bottom + 95, COLOR_DARK, 10
177 )
178
179 def on_draw(self):
180 arcade.draw_lrbt_rectangle_filled(self.left, self.right, self.bottom, self.top, COLOR_DARK)
181 arcade.draw_lrbt_rectangle_outline(
182 self.left, self.right, self.bottom, self.top, COLOR_LIGHT
183 )
184 self.draw_button_stop()
185 self.draw_button_toggle_info_bar()
186
187 if self.pressed_key:
188 arcade.draw_text(
189 f"Pressed key code: {self.pressed_key}",
190 self.left + 10,
191 self.top - 240,
192 COLOR_LIGHT,
193 9,
194 )
195
196 self.draw_button_show_modal()
197
198 def on_mouse_press(self, x: float, y: float, button: int, modifiers: int):
199 if self.button_stop.collides_with_point((x, y)):
200 self.view.map.ball.stop()
201 elif self.button_toggle_info_bar.collides_with_point((x, y)):
202 self.view.info_bar.enabled = not self.view.info_bar.enabled
203 elif self.button_show_modal.collides_with_point((x, y)):
204 self.view.modal_section.enabled = True
205
206 def on_resize(self, width: int, height: int):
207 # stick to the right
208 self.left = width - self.width
209 self.height = height - self.view.info_bar.height
210 self.button_stop.position = self.left + self.width / 2, self.top - 80
211
212 pos = self.left + self.width / 2, self.top - 180
213 self.button_toggle_info_bar.position = pos
214
215 pos = self.left + self.width / 2, self.bottom + 100
216 self.button_show_modal.position = pos
217
218 def on_key_press(self, symbol: int, modifiers: int):
219 self.pressed_key = symbol
220
221 def on_key_release(self, _symbol: int, _modifiers: int):
222 self.pressed_key = None
223
224
225class Map(Section):
226 """This represents the place where the game takes place"""
227
228 def __init__(self, left: int, bottom: int, width: int, height: int, **kwargs):
229 super().__init__(left, bottom, width, height, **kwargs)
230
231 self.ball = Ball(20, COLOR_3)
232 self.ball.position = 60, 60
233 self.sprite_list: arcade.SpriteList = arcade.SpriteList()
234 self.sprite_list.append(self.ball)
235
236 self.pressed_key: int | None = None
237
238 def on_update(self, delta_time: float):
239 if self.pressed_key:
240 if self.pressed_key == arcade.key.UP:
241 self.ball.change_y += SPRITE_SPEED
242 elif self.pressed_key == arcade.key.RIGHT:
243 self.ball.change_x += SPRITE_SPEED
244 elif self.pressed_key == arcade.key.DOWN:
245 self.ball.change_y -= SPRITE_SPEED
246 elif self.pressed_key == arcade.key.LEFT:
247 self.ball.change_x -= SPRITE_SPEED
248
249 self.sprite_list.update()
250
251 if self.ball.top >= self.top or self.ball.bottom <= self.bottom:
252 self.ball.change_y *= -1
253 self.ball.bounce_count += 1
254 if self.ball.left <= self.left or self.ball.right >= self.right:
255 self.ball.change_x *= -1
256 self.ball.bounce_count += 1
257
258 def on_draw(self):
259 arcade.draw_lrbt_rectangle_filled(self.left, self.right, self.bottom, self.top, COLOR_DARK)
260 arcade.draw_lrbt_rectangle_outline(
261 self.left, self.right, self.bottom, self.top, COLOR_LIGHT
262 )
263 self.sprite_list.draw()
264
265 def on_key_press(self, symbol: int, modifiers: int):
266 self.pressed_key = symbol
267
268 def on_key_release(self, _symbol: int, _modifiers: int):
269 self.pressed_key = None
270
271 def on_resize(self, width: int, height: int):
272 self.width = width - self.view.panel.width
273 self.height = height - self.view.info_bar.height
274
275
276class GameView(arcade.View):
277 """The game itself"""
278
279 def __init__(self):
280 super().__init__()
281
282 # create and store the modal, so we can set
283 # self.modal_section.enabled = True to show it
284 self.modal_section = ModalSection(
285 (self.window.width / 2) - 150,
286 (self.window.height / 2) - 100,
287 300,
288 200,
289 )
290
291 # we set accept_keyboard_events to False (default to True)
292 self.info_bar = InfoBar(
293 0,
294 self.window.height - INFO_BAR_HEIGHT,
295 self.window.width,
296 INFO_BAR_HEIGHT,
297 accept_keyboard_keys=False,
298 )
299
300 # as prevent_dispatch is on by default, we let pass the events to the
301 # following Section: the map
302 self.panel = Panel(
303 self.window.width - PANEL_WIDTH,
304 0,
305 PANEL_WIDTH,
306 self.window.height - INFO_BAR_HEIGHT,
307 prevent_dispatch={False},
308 )
309 self.map = Map(0, 0, self.window.width - PANEL_WIDTH, self.window.height - INFO_BAR_HEIGHT)
310
311 # add the sections
312 self.section_manager = SectionManager(self)
313 self.section_manager.add_section(self.modal_section)
314 self.section_manager.add_section(self.info_bar)
315 self.section_manager.add_section(self.panel)
316 self.section_manager.add_section(self.map)
317
318 def on_show_view(self) -> None:
319 self.section_manager.enable()
320
321 def on_hide_view(self) -> None:
322 self.section_manager.disable()
323
324 def on_draw(self):
325 self.clear()
326
327
328def main():
329 """ Main function """
330 # Create a window class. This is what actually shows up on screen
331 window = arcade.Window(resizable=True)
332
333 # Create the GameView
334 game = GameView()
335
336 # Show GameView on screen
337 window.show_view(game)
338
339 # Start the arcade game loop
340 arcade.run()
341
342
343if __name__ == "__main__":
344 main()