"""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)