Sections Demo 3

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