206 lines
7.4 KiB
Python
206 lines
7.4 KiB
Python
"""Zeichenroutinen – alles, was Pixel auf den Bildschirm bringt."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import pygame
|
||
|
||
from . import config
|
||
from .case import Case
|
||
from .sequences import CaseSpec
|
||
|
||
|
||
# --------------------------------------------------------------------------
|
||
# Hintergrund-Bänder
|
||
# --------------------------------------------------------------------------
|
||
def draw_top_bar(surface: pygame.Surface, assets, percent: float) -> None:
|
||
surface.fill(
|
||
config.COLOR_TOP_BAR, (0, 0, config.SCREEN_WIDTH, config.TOP_BAR_HEIGHT)
|
||
)
|
||
font = assets.font
|
||
title = font.render(config.TITLE_TEXT, True, config.COLOR_WHITE)
|
||
surface.blit(title, (40, config.TOP_BAR_HEIGHT // 2 - title.get_height() // 2))
|
||
_draw_progress_bar(surface, assets, percent)
|
||
|
||
|
||
def _draw_progress_bar(surface: pygame.Surface, assets, percent: float) -> None:
|
||
bar_width = config.PROGRESS_BAR_WIDTH
|
||
bar_height = config.PROGRESS_BAR_HEIGHT
|
||
x = config.SCREEN_WIDTH - bar_width - config.PROGRESS_BAR_RIGHT_MARGIN
|
||
y = config.TOP_BAR_HEIGHT // 2 - bar_height // 2
|
||
gap = config.PROGRESS_BAR_GAP
|
||
segment_width = (bar_width - (config.PROGRESS_BAR_SEGMENTS - 1) * gap) / config.PROGRESS_BAR_SEGMENTS
|
||
|
||
red = min(255, int(percent * 2.55))
|
||
green = max(0, 255 - int(percent * 2.55))
|
||
color = (red, green, 0)
|
||
|
||
for index in range(config.PROGRESS_BAR_SEGMENTS):
|
||
segment_x = x + index * (segment_width + gap)
|
||
rect = (segment_x, y, segment_width, bar_height)
|
||
pygame.draw.rect(surface, config.COLOR_PROGRESS_TRACK, rect, border_radius=config.PROGRESS_BAR_RADIUS)
|
||
if percent >= (index + 1) * 20:
|
||
pygame.draw.rect(surface, color, rect, border_radius=config.PROGRESS_BAR_RADIUS)
|
||
|
||
text = assets.font.render(f"{int(percent)}%", True, config.COLOR_WHITE)
|
||
surface.blit(text, (x + bar_width + 10, y))
|
||
|
||
|
||
def draw_background(surface: pygame.Surface, assets) -> None:
|
||
if assets.background is not None:
|
||
surface.blit(assets.background, (0, config.TOP_BAR_HEIGHT))
|
||
else:
|
||
pygame.draw.rect(
|
||
surface,
|
||
config.COLOR_BACKGROUND,
|
||
(0, config.TOP_BAR_HEIGHT, config.SCREEN_WIDTH, config.GAME_AREA_HEIGHT),
|
||
)
|
||
|
||
|
||
def draw_bottom_bar(surface: pygame.Surface, assets, upcoming: list[CaseSpec]) -> None:
|
||
surface.fill(
|
||
config.COLOR_BOTTOM_BAR,
|
||
(0, config.TOP_BAR_HEIGHT + config.GAME_AREA_HEIGHT, config.SCREEN_WIDTH, config.BOTTOM_BAR_HEIGHT),
|
||
)
|
||
top = config.SCREEN_HEIGHT - config.BOTTOM_BAR_HEIGHT
|
||
label = (assets.font_small or assets.font).render(config.NEXT_LABEL, True, config.COLOR_WHITE)
|
||
surface.blit(label, (40, top + 12))
|
||
|
||
preview_x = 40
|
||
for index, spec in enumerate(upcoming[: config.NEXT_PREVIEW_COUNT]):
|
||
image = _preview_surface(assets, spec)
|
||
height = image.get_height() if image is not None else 0
|
||
y = config.SCREEN_HEIGHT - config.NEXT_PREVIEW_MARGIN - height
|
||
if image is None:
|
||
width, height = _spec_size(spec)
|
||
y = config.SCREEN_HEIGHT - config.NEXT_PREVIEW_MARGIN - height
|
||
pygame.draw.rect(
|
||
surface,
|
||
config.CASE_COLORS[index % len(config.CASE_COLORS)],
|
||
(preview_x, y, width, height),
|
||
)
|
||
else:
|
||
width = image.get_width()
|
||
surface.blit(image, (preview_x, y))
|
||
preview_x += max(width, 60) + 20
|
||
|
||
|
||
_PREVIEW_CACHE: dict[tuple[int, int], pygame.Surface | None] = {}
|
||
|
||
|
||
def _preview_surface(assets, spec: CaseSpec) -> pygame.Surface | None:
|
||
"""Vorschau-Grafik in verkleinertem Massstab (gecacht)."""
|
||
key = (spec.units_w, spec.units_h)
|
||
if key in _PREVIEW_CACHE:
|
||
return _PREVIEW_CACHE[key]
|
||
image = assets.case_surface(spec.units_w, spec.units_h) if assets is not None else None
|
||
if image is not None:
|
||
size = (
|
||
max(1, int(image.get_width() * config.NEXT_PREVIEW_SCALE)),
|
||
max(1, int(image.get_height() * config.NEXT_PREVIEW_SCALE)),
|
||
)
|
||
image = pygame.transform.smoothscale(image, size)
|
||
_PREVIEW_CACHE[key] = image
|
||
return image
|
||
|
||
|
||
# --------------------------------------------------------------------------
|
||
# Auflieger
|
||
# --------------------------------------------------------------------------
|
||
def draw_trailer(surface: pygame.Surface, offset: tuple[int, int] = (0, 0)) -> None:
|
||
pygame.draw.rect(
|
||
surface,
|
||
config.COLOR_TRAILER,
|
||
(
|
||
config.TRAILER_X + offset[0],
|
||
config.TRAILER_Y + offset[1],
|
||
config.TRAILER_WIDTH,
|
||
config.TRAILER_HEIGHT,
|
||
),
|
||
)
|
||
|
||
|
||
def draw_wall(surface: pygame.Surface, wall_x: float, offset: tuple[int, int] = (0, 0)) -> None:
|
||
pygame.draw.line(
|
||
surface,
|
||
config.COLOR_WALL,
|
||
(wall_x + offset[0], config.TRAILER_Y + offset[1]),
|
||
(wall_x + offset[0], config.TRAILER_BOTTOM + offset[1]),
|
||
3,
|
||
)
|
||
|
||
|
||
# --------------------------------------------------------------------------
|
||
# Koffer
|
||
# --------------------------------------------------------------------------
|
||
def _spec_size(spec: CaseSpec) -> tuple[int, int]:
|
||
return (
|
||
int(spec.units_w * config.UNIT_LENGTH),
|
||
int(spec.units_h * config.UNIT_HEIGHT),
|
||
)
|
||
|
||
|
||
def draw_case(surface: pygame.Surface, assets, case: Case, angle: float = 0.0) -> None:
|
||
x, y = int(case.x), int(case.y)
|
||
width, height = int(case.width), int(case.height)
|
||
image = assets.case_surface(case.units_w, case.units_h) if assets is not None else None
|
||
if image is None:
|
||
pygame.draw.rect(surface, case.color, (x, y, width, height))
|
||
return
|
||
if angle:
|
||
rotated = pygame.transform.rotate(image, angle)
|
||
surface.blit(rotated, rotated.get_rect(bottomleft=(x, y + height)))
|
||
else:
|
||
surface.blit(image, (x, y))
|
||
|
||
|
||
# --------------------------------------------------------------------------
|
||
# Hinweise und Overlays
|
||
# --------------------------------------------------------------------------
|
||
def draw_hint(surface: pygame.Surface, assets, stack_possible: bool) -> None:
|
||
if stack_possible:
|
||
text, color = config.HINT_STACK, config.COLOR_HINT_STACK
|
||
else:
|
||
text, color = config.HINT_SNAP, config.COLOR_HINT_SNAP
|
||
rendered = assets.font.render(text, True, color)
|
||
surface.blit(
|
||
rendered,
|
||
(
|
||
config.SCREEN_WIDTH - config.SCREEN_WIDTH // 3 - rendered.get_width() // 2,
|
||
config.SCREEN_HEIGHT - config.BOTTOM_BAR_HEIGHT // 2 - rendered.get_height() // 2,
|
||
),
|
||
)
|
||
|
||
|
||
def draw_overlay_lines(
|
||
surface: pygame.Surface,
|
||
assets,
|
||
lines: list[str],
|
||
*,
|
||
status: str = "",
|
||
status_color: tuple[int, int, int] = config.COLOR_ERROR,
|
||
) -> None:
|
||
overlay = pygame.Surface((config.SCREEN_WIDTH, config.SCREEN_HEIGHT))
|
||
overlay.set_alpha(220)
|
||
overlay.fill((0, 0, 0))
|
||
surface.blit(overlay, (0, 0))
|
||
|
||
center_y = config.SCREEN_HEIGHT // 2
|
||
line_gap = 16
|
||
total_height = 0
|
||
rendered: list[pygame.Surface] = []
|
||
for line in lines:
|
||
image = assets.font.render(line, True, config.COLOR_WHITE)
|
||
rendered.append(image)
|
||
total_height += image.get_height() + line_gap
|
||
y = center_y - total_height // 2
|
||
for image in rendered:
|
||
surface.blit(image, (config.SCREEN_WIDTH // 2 - image.get_width() // 2, y))
|
||
y += image.get_height() + line_gap
|
||
|
||
if status:
|
||
small = assets.font_small.render(status, True, status_color)
|
||
surface.blit(
|
||
small,
|
||
(config.SCREEN_WIDTH // 2 - small.get_width() // 2, y + 20),
|
||
)
|