""" This file is part of the SplendidBear Websites' projects. Copyright (c) 2026 @ www.splendidbear.org For the full copyright and license information, please view the LICENSE file that was distributed with this source code. """ from __future__ import annotations from collections.abc import Callable import gi gi.require_version("Gtk", "4.0") gi.require_version("Adw", "1") from gi.repository import Gtk, Adw from mineseeker.state.game_state import PlayerState from mineseeker.constants import WIN_THRESHOLD class PlayerPanel(Gtk.Box): """ Vertical sidebar panel showing one player's info: - Name + colour indicator - Mine count (e.g. "12 / 26") - Bonus points - Bomb toggle button - Resign button (only for the local player) """ def __init__( self, color: str, is_local: bool, on_bomb_toggle: Callable[[bool], None], on_resign: Callable[[], None], ) -> None: super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=8) self._color = color self._is_local = is_local self._on_bomb_toggle = on_bomb_toggle self._on_resign = on_resign self._bomb_active = False self.set_margin_top(12) self.set_margin_bottom(12) self.set_margin_start(12) self.set_margin_end(12) self.set_valign(Gtk.Align.START) # Colour dot + name name_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6) dot = Gtk.Label(label="●") dot.add_css_class("red-player" if color == "red" else "blue-player") name_box.append(dot) self._name_label = Gtk.Label(label="Waiting…") self._name_label.add_css_class("title-4") self._name_label.set_ellipsize(3) # PANGO_ELLIPSIZE_END name_box.append(self._name_label) self.append(name_box) # Mine count self._mine_label = Gtk.Label(label=f"0 / {WIN_THRESHOLD}") self._mine_label.add_css_class("title-2") self.append(self._mine_label) # Bonus points self._bonus_label = Gtk.Label(label="Bonus: 0") self._bonus_label.add_css_class("dim-label") self.append(self._bonus_label) # Bomb button — only meaningful for local player if is_local: self._bomb_btn = Gtk.ToggleButton(label="Bomb") self._bomb_btn.set_sensitive(False) self._bomb_btn.connect("toggled", self._on_bomb_toggled) self.append(self._bomb_btn) sep = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) self.append(sep) resign_btn = Gtk.Button(label="Resign") resign_btn.add_css_class("destructive-action") resign_btn.connect("clicked", lambda *_: self._on_resign()) self.append(resign_btn) # ------------------------------------------------------------------ # Update from state # ------------------------------------------------------------------ def update(self, player: PlayerState, is_turn: bool) -> None: self._name_label.set_label(player.display_name) self._mine_label.set_label(f"{player.mines} / {WIN_THRESHOLD}") self._bonus_label.set_label(f"Bonus: {player.bonus_points:.1f}") if self._is_local and hasattr(self, "_bomb_btn"): can_use = player.bomb_enabled and not player.bomb_used and is_turn self._bomb_btn.set_sensitive(can_use) if player.bomb_used: self._bomb_btn.set_label("Bomb Used") def set_bomb_enabled(self, enabled: bool) -> None: if self._is_local and hasattr(self, "_bomb_btn"): self._bomb_btn.set_sensitive(enabled) def reset_bomb_toggle(self) -> None: """Deactivate the bomb toggle (after a bomb move is sent).""" if self._is_local and hasattr(self, "_bomb_btn"): self._bomb_btn.set_active(False) def _on_bomb_toggled(self, btn: Gtk.ToggleButton) -> None: self._bomb_active = btn.get_active() self._on_bomb_toggle(self._bomb_active)