Source code for simvx.core.ui.bottom_strip

"""BottomControlsStrip: light-grey hint bar pinned to the bottom of the screen.

Solitaire, Casual Crusade, Claustrowordia, and Tiny Yurts all hand-rolled the
same widget: a 32–40 px light-grey rectangle pinned to the bottom edge, listing
"DRAG X · PRESS Y · …" hint segments separated by middle-dots, with the active
hint optionally bolded. This ships that widget as a first-class Control.

Example::

    strip = BottomControlsStrip(hints=["DRAG cards onto piles", "DOUBLE-CLICK to auto-play", "ESC menu"])
    strip.place_bottom_strip(height=32)  # see Control.place_bottom_strip
    root.add_child(strip)
"""

from __future__ import annotations

from ..descriptors import Property
from ..math.types import Vec2
from .core import Control

__all__ = ["BottomControlsStrip"]


[docs] class BottomControlsStrip(Control): """Pinned hint strip rendering "SEG · SEG · …" centred over a light-grey bg. Properties ---------- hints Tuple of short hint strings. Each segment is rendered separated by a middle-dot glyph. Re-assignment redraws. font_size Text size in px (default 13). text_colour RGBA tuple for the hint text (default near-black). bg_colour RGBA tuple for the strip background (default light grey ``#E6E6E6``). Layout ------ The strip is a normal :class:`Control`: pair it with :meth:`Control.place_bottom_strip` (or its top-strip companion) for the canonical anchored placement. """ _draw_caching = True font_size = Property(13.0, range=(8, 32), hint="Hint text size") separator = Property(" \u00b7 ", hint="Inter-segment glyph (middle-dot by default)") def __init__( self, hints: list[str] | tuple[str, ...] = (), *, bg_colour: tuple[float, float, float, float] = (0.90, 0.90, 0.92, 1.0), text_colour: tuple[float, float, float, float] = (0.10, 0.10, 0.12, 1.0), **kwargs, ): super().__init__(**kwargs) self._hints: tuple[str, ...] = tuple(hints) self._bg_colour = tuple(bg_colour) self._text_colour = tuple(text_colour) self.size = Vec2(self.size_x, 32.0) # --- properties -------------------------------------------------------- @property def hints(self) -> tuple[str, ...]: return self._hints
[docs] @hints.setter def hints(self, value): self._hints = tuple(value) self.queue_redraw()
@property def bg_colour(self): return self._bg_colour
[docs] @bg_colour.setter def bg_colour(self, value): from ..properties import Colour coerced = Colour.coerce(value, name="BottomControlsStrip.bg_colour") if coerced is None: # The strip renders directly to ``draw_rect`` and has no theme key # to fall back on, so ``None`` cannot be resolved. Fail loudly per # the engine-wide "raw draw callsites resolve or fail loudly" rule. raise TypeError( "BottomControlsStrip.bg_colour does not support None: it has no theme fallback; " "assign an RGBA tuple instead." ) self._bg_colour = coerced self.queue_redraw()
@property def text_colour(self): return self._text_colour
[docs] @text_colour.setter def text_colour(self, value): from ..properties import Colour coerced = Colour.coerce(value, name="BottomControlsStrip.text_colour") if coerced is None: # See ``bg_colour`` setter: no theme fallback, fail loudly. raise TypeError( "BottomControlsStrip.text_colour does not support None: it has no theme fallback; " "assign an RGBA tuple instead." ) self._text_colour = coerced self.queue_redraw()
# --- rendering ---------------------------------------------------------
[docs] def on_draw(self, renderer): x, y, w, h = self.get_global_rect() renderer.draw_rect((x, y), (w, h), colour=self._bg_colour, filled=True) if not self._hints: return text = self.separator.join(self._hints) scale = self.font_size / 14.0 text_w = renderer.text_width(text, scale) text_x = x + (w - text_w) / 2 text_y = y + (h - self.font_size) / 2 renderer.draw_text(text, (text_x, text_y), colour=self._text_colour, scale=scale)
[docs] def get_minimum_size(self) -> Vec2: return Vec2(max(self.min_size.x, 80.0), max(self.min_size.y, 24.0))