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