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