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