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