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