"""Package for game screens.""" from __future__ import annotations from typing import Protocol, TypeAlias import attrs from tcod.console import Console from tcod.event import Event, Quit from tcod.event import wait as wait_for_event import tcod.event import g class Screen(Protocol): """An abstract game screen.""" __slots__ = () def on_event(self, event: Event) -> ScreenResult: """Called on events.""" def on_draw(self, console: Console) -> None: """Called when the screen is being drawn.""" @attrs.define() class Push: """Push a new screen on top of the stack.""" screen: Screen @attrs.define() class Pop: """Remove the current screen from the stack.""" @attrs.define() class Reset: """Replace the entire stack with a new screen.""" screen: Screen ScreenResult: TypeAlias = "Push | Pop | Reset | None" """Union of screen results.""" def main_draw() -> None: """Render and present the active screen.""" if not g.screens: return g.background.clear() g.foreground.rgba["bg"][:] = 0 g.foreground.rgba["fg"][:] = 0 g.screens[-1].on_draw(g.background) # g.context.present(g.background) # g.sdl_renderer.copy(g.console_render.render(g.background)) # g.foreground.blit(g.background, fg_alpha=1.0, bg_alpha=0.0) bg_tex = g.console_render.render(g.background) bg_tex.blend_mode = 1 with g.sdl_renderer.set_render_target(bg_tex): fg_tex = g.console_render.render(g.foreground) fg_tex.blend_mode = 1 g.sdl_renderer.copy(fg_tex) g.sdl_renderer.copy(bg_tex) g.sdl_renderer.present() def _apply_screen_result(result: ScreenResult) -> None: """Apply a ScreenResult to `g.screens`.""" match result: case Push(screen=screen): g.screens.append(screen) case Pop(): g.screens.pop() case Reset(screen=screen): while g.screens: _apply_screen_result(Pop()) _apply_screen_result(Push(screen)) case None: pass case _: raise TypeError(result) def convert_event(event: tcod.event.Event): match event: case tcod.event.MouseState(position=pos): width, height = g.tileset.tile_width, g.tileset.tile_height event.position = (pos[0]//width, pos[1]//height) return event def main_loop() -> None: """Run the active screen forever.""" while g.screens: main_draw() for event in wait_for_event(): match event: case Quit(): raise SystemExit() tile_event = convert_event(event) if g.screens: _apply_screen_result(g.screens[-1].on_event(tile_event)) def get_previous_screen(screen: Screen) -> Screen | None: """Return the screen before `screen` in the stack if it exists.""" current_index = next(index for index, value in enumerate(g.screens) if value is screen) return g.screens[current_index - 1] if current_index > 0 else None def draw_previous_screens(screen: Screen, console: tcod.console.Console, dim: bool = True) -> None: """Draw previous screens, optionally dimming all but the active screen.""" prev_screen = get_previous_screen(screen) if prev_screen is None: return prev_screen.on_draw(console) if dim and screen is g.screens[-1]: g.foreground.rgb["fg"] //= 4 g.foreground.rgb["bg"] //= 4 g.background.rgb["fg"] //= 4 g.background.rgb["bg"] //= 4