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
 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()