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"""
24from __future__ import annotations
25
26from typing import Optional
27from math import sqrt
28
29import arcade
30from arcade import Section
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(self.left, self.right, self.bottom,
72 self.top, arcade.color.GRAY)
73 arcade.draw_lrbt_rectangle_outline(self.left, self.right, self.bottom,
74 self.top, arcade.color.WHITE)
75 self.draw_button()
76
77 def draw_button(self):
78 # draws the button and button text
79 self.button.draw()
80 arcade.draw_text('Close Modal', self.button.left + 5,
81 self.button.bottom + self.button.height / 2,
82 arcade.color.WHITE)
83
84 def on_resize(self, width: int, height: int):
85 """ set position on screen resize """
86 self.left = width // 3
87 self.bottom = (height // 2) - self.height // 2
88 pos = self.left + self.width / 2, self.bottom + self.height / 2
89 self.button.position = pos
90
91 def on_mouse_press(self, x: float, y: float, button: int, modifiers: int):
92 """ Check if the button is pressed """
93 if self.button.collides_with_point((x, y)):
94 self.enabled = False
95
96
97class InfoBar(Section):
98 """ This is the top bar of the screen where info is showed """
99
100 @property
101 def ball(self):
102 return self.view.map.ball
103
104 def on_draw(self):
105 # draw game info
106 arcade.draw_lrbt_rectangle_filled(self.left, self.right, self.bottom,
107 self.top, COLOR_DARK)
108 arcade.draw_lrbt_rectangle_outline(self.left, self.right, self.bottom,
109 self.top, COLOR_LIGHT)
110 arcade.draw_text(f'Ball bounce count: {self.ball.bounce_count}',
111 self.left + 20, self.top - self.height / 1.6,
112 COLOR_LIGHT)
113
114 ball_change_axis = self.ball.change_x, self.ball.change_y
115 arcade.draw_text(f'Ball change in axis: {ball_change_axis}',
116 self.left + 220, self.top - self.height / 1.6,
117 COLOR_LIGHT)
118 arcade.draw_text(f'Ball speed: {self.ball.speed} pixels/second',
119 self.left + 480, self.top - self.height / 1.6,
120 COLOR_LIGHT)
121
122 def on_resize(self, width: int, height: int):
123 # stick to the top
124 self.width = width
125 self.bottom = height - self.view.info_bar.height
126
127
128class Panel(Section):
129 """This is the Panel to the right where buttons and info is showed """
130
131 def __init__(self, left: int, bottom: int, width: int, height: int,
132 **kwargs):
133 super().__init__(left, bottom, width, height, **kwargs)
134
135 # create buttons
136 self.button_stop = self.new_button(arcade.color.ARSENIC)
137 self.button_toggle_info_bar = self.new_button(COLOR_1)
138
139 self.button_show_modal = self.new_button(COLOR_2)
140 # to show the key that's actually pressed
141 self.pressed_key: Optional[int] = None
142
143 @staticmethod
144 def new_button(color):
145 # helper to create new buttons
146 return arcade.SpriteSolidColor(100, 50, color=color)
147
148 def draw_button_stop(self):
149 arcade.draw_text('Press button to stop the ball', self.left + 10,
150 self.top - 40, COLOR_LIGHT, 10)
151 self.button_stop.draw()
152
153 def draw_button_toggle_info_bar(self):
154 arcade.draw_text('Press to toggle info_bar', self.left + 10,
155 self.top - 140, COLOR_LIGHT, 10)
156 self.button_toggle_info_bar.draw()
157
158 def draw_button_show_modal(self):
159 self.button_show_modal.draw()
160 arcade.draw_text('Show Modal', self.left - 37 + self.width / 2,
161 self.bottom + 95, COLOR_DARK, 10)
162
163 def on_draw(self):
164 arcade.draw_lrbt_rectangle_filled(self.left, self.right, self.bottom,
165 self.top, COLOR_DARK)
166 arcade.draw_lrbt_rectangle_outline(self.left, self.right, self.bottom,
167 self.top, COLOR_LIGHT)
168 self.draw_button_stop()
169 self.draw_button_toggle_info_bar()
170
171 if self.pressed_key:
172 arcade.draw_text(f'Pressed key code: {self.pressed_key}',
173 self.left + 10, self.top - 240, COLOR_LIGHT, 9)
174
175 self.draw_button_show_modal()
176
177 def on_mouse_press(self, x: float, y: float, button: int, modifiers: int):
178 if self.button_stop.collides_with_point((x, y)):
179 self.view.map.ball.stop()
180 elif self.button_toggle_info_bar.collides_with_point((x, y)):
181 self.view.info_bar.enabled = not self.view.info_bar.enabled
182 elif self.button_show_modal.collides_with_point((x, y)):
183 self.view.modal_section.enabled = True
184
185 def on_resize(self, width: int, height: int):
186 # stick to the right
187 self.left = width - self.width
188 self.height = height - self.view.info_bar.height
189 self.button_stop.position = self.left + self.width / 2, self.top - 80
190
191 pos = self.left + self.width / 2, self.top - 180
192 self.button_toggle_info_bar.position = pos
193
194 pos = self.left + self.width / 2, self.bottom + 100
195 self.button_show_modal.position = pos
196
197 def on_key_press(self, symbol: int, modifiers: int):
198 self.pressed_key = symbol
199
200 def on_key_release(self, _symbol: int, _modifiers: int):
201 self.pressed_key = None
202
203
204class Map(Section):
205 """ This represents the place where the game takes place """
206
207 def __init__(self, left: int, bottom: int, width: int, height: int,
208 **kwargs):
209 super().__init__(left, bottom, width, height, **kwargs)
210
211 self.ball = Ball(20, COLOR_3)
212 self.ball.position = 60, 60
213 self.sprite_list: arcade.SpriteList = arcade.SpriteList()
214 self.sprite_list.append(self.ball)
215
216 self.pressed_key: Optional[int] = None
217
218 def on_update(self, delta_time: float):
219
220 if self.pressed_key:
221 if self.pressed_key == arcade.key.UP:
222 self.ball.change_y += SPRITE_SPEED
223 elif self.pressed_key == arcade.key.RIGHT:
224 self.ball.change_x += SPRITE_SPEED
225 elif self.pressed_key == arcade.key.DOWN:
226 self.ball.change_y -= SPRITE_SPEED
227 elif self.pressed_key == arcade.key.LEFT:
228 self.ball.change_x -= SPRITE_SPEED
229
230 self.sprite_list.update()
231
232 if self.ball.top >= self.top or self.ball.bottom <= self.bottom:
233 self.ball.change_y *= -1
234 self.ball.bounce_count += 1
235 if self.ball.left <= self.left or self.ball.right >= self.right:
236 self.ball.change_x *= -1
237 self.ball.bounce_count += 1
238
239 def on_draw(self):
240 arcade.draw_lrbt_rectangle_filled(self.left, self.right, self.bottom,
241 self.top, COLOR_DARK)
242 arcade.draw_lrbt_rectangle_outline(self.left, self.right, self.bottom,
243 self.top, COLOR_LIGHT)
244 self.sprite_list.draw()
245
246 def on_key_press(self, symbol: int, modifiers: int):
247 self.pressed_key = symbol
248
249 def on_key_release(self, _symbol: int, _modifiers: int):
250 self.pressed_key = None
251
252 def on_resize(self, width: int, height: int):
253 self.width = width - self.view.panel.width
254 self.height = height - self.view.info_bar.height
255
256
257class GameView(arcade.View):
258 """ The game itself """
259
260 def __init__(self):
261 super().__init__()
262
263 # create and store the modal, so we can set
264 # self.modal_section.enabled = True to show it
265 self.modal_section = ModalSection((self.window.width / 2) - 150,
266 (self.window.height / 2) - 100,
267 300, 200)
268
269 # we set accept_keyboard_events to False (default to True)
270 self.info_bar = InfoBar(0, self.window.height - INFO_BAR_HEIGHT, self.window.width, INFO_BAR_HEIGHT,
271 accept_keyboard_keys=False)
272
273 # as prevent_dispatch is on by default, we let pass the events to the
274 # following Section: the map
275 self.panel = Panel(self.window.width - PANEL_WIDTH, 0, PANEL_WIDTH,
276 self.window.height - INFO_BAR_HEIGHT,
277 prevent_dispatch={False})
278 self.map = Map(0, 0, self.window.width - PANEL_WIDTH,
279 self.window.height - INFO_BAR_HEIGHT)
280
281 # add the sections
282 self.section_manager.add_section(self.modal_section)
283 self.section_manager.add_section(self.info_bar)
284 self.section_manager.add_section(self.panel)
285 self.section_manager.add_section(self.map)
286
287 def on_draw(self):
288 arcade.start_render()
289
290
291def main():
292 window = arcade.Window(resizable=True)
293 game = GameView()
294
295 window.show_view(game)
296
297 window.run()
298
299
300if __name__ == '__main__':
301 main()