Source code for simvx.editor.panels.anchor_picker

"""Inspector anchor picker: a 3x3 grid of anchors plus the wide ones, which sets ``Control.anchor``."""

from collections.abc import Callable

from simvx.core.descriptors import Property
from simvx.core.math.types import Vec2
from simvx.core.signals import Signal
from simvx.core.ui.containers import GridContainer, HBoxContainer, VBoxContainer
from simvx.core.ui.core import Control
from simvx.core.ui.enums import Anchor
from simvx.core.ui.widgets import Button

__all__ = ["AnchorPicker", "ANCHOR_LABELS"]

ANCHOR_LABELS: list[tuple[Anchor, str]] = [
    (Anchor.TOP_LEFT, "\u2196"),  # ↖
    (Anchor.CENTER_TOP, "\u2191"),  # ↑
    (Anchor.TOP_RIGHT, "\u2197"),  # ↗
    (Anchor.CENTER_LEFT, "\u2190"),  # ←
    (Anchor.CENTER, "\u25cf"),  # ●
    (Anchor.CENTER_RIGHT, "\u2192"),  # →
    (Anchor.BOTTOM_LEFT, "\u2199"),  # ↙
    (Anchor.CENTER_BOTTOM, "\u2193"),  # ↓
    (Anchor.BOTTOM_RIGHT, "\u2198"),  # ↘
]

_WIDE_ANCHORS: list[tuple[Anchor, str]] = [
    (Anchor.LEFT_WIDE, "Left"),
    (Anchor.RIGHT_WIDE, "Right"),
    (Anchor.TOP_WIDE, "Top"),
    (Anchor.BOTTOM_WIDE, "Bot"),
]


[docs] class AnchorPicker(VBoxContainer): """Compact grid of anchor selectors shown inline in the Inspector. Renders as a small header label plus a 3×3 directional grid and a row of wide/full variants. Clicking one sets ``target_control.anchor`` and emits ``anchor_changed``. Inline grid (not a modal popup) so the surrounding PropertyRow sizes correctly and so the affordance stays discoverable. """ anchor_changed = Signal() # emits Anchor separation = Property(3.0, range=(0, 100), hint="Space between children", on_change="mark_layout_dirty") size_x = Property(120.0, range=(0, 10000), hint="Control width", on_change="_on_size_changed") def __init__( self, target_control: Control | None = None, on_applied: Callable[[Anchor], None] | None = None, **kwargs ): super().__init__(**kwargs) self.target_control = target_control self._on_applied = on_applied self._anchor_buttons: dict[Anchor, Button] = {} grid = GridContainer(columns=3) grid.separation = 2 for anchor, label in ANCHOR_LABELS: btn = Button(label, on_press=lambda p=anchor: self._apply_anchor(p)) btn.size = Vec2(30, 26) self._anchor_buttons[anchor] = btn grid.add_child(btn) self.add_child(grid) wide_row = HBoxContainer() wide_row.separation = 2 for anchor, label in _WIDE_ANCHORS: btn = Button(label, on_press=lambda p=anchor: self._apply_anchor(p)) btn.size = Vec2(44, 20) self._anchor_buttons[anchor] = btn wide_row.add_child(btn) self.add_child(wide_row) full = Button("Full Rect", on_press=lambda: self._apply_anchor(Anchor.FULL_RECT)) full.size = Vec2(100, 20) self._anchor_buttons[Anchor.FULL_RECT] = full self.add_child(full) # Height = 3 rows of 3x3 (~26) + separation + wide row (~20) + sep + full (~20) self.size_y = 26 * 3 + 2 * 2 + self.separation + 20 + self.separation + 20 def _apply_anchor(self, anchor: Anchor) -> None: if self.target_control is not None: self.target_control.anchor = anchor self.anchor_changed.emit(anchor) if self._on_applied is not None: self._on_applied(anchor)
[docs] def get_minimum_size(self) -> Vec2: return Vec2(120, 26 * 3 + 2 * 2 + self.separation + 20 + self.separation + 20)