Alignment: reactive or automatically calculated repositioning of UIDrawables on their parent

This commit is contained in:
John McCardle 2026-01-13 20:40:34 -05:00
commit 4bf590749c
23 changed files with 1350 additions and 397 deletions

View file

@ -0,0 +1,131 @@
#!/usr/bin/env python3
"""Test alignment constructor arguments work correctly."""
import mcrfpy
import sys
# Test that alignment args work in constructors
print("Test 1: Frame with align constructor arg...")
parent = mcrfpy.Frame(pos=(0, 0), size=(400, 300))
child = mcrfpy.Frame(size=(100, 50), align=mcrfpy.Alignment.CENTER)
parent.children.append(child)
# Expected: (400-100)/2=150, (300-50)/2=125
if abs(child.x - 150) < 0.1 and abs(child.y - 125) < 0.1:
print(" PASS: Frame align constructor arg works")
else:
print(f" FAIL: Expected (150, 125), got ({child.x}, {child.y})")
sys.exit(1)
print("Test 2: Frame with align and margin constructor args...")
parent2 = mcrfpy.Frame(pos=(0, 0), size=(400, 300))
child2 = mcrfpy.Frame(size=(50, 50), align=mcrfpy.Alignment.TOP_LEFT, margin=10)
parent2.children.append(child2)
if abs(child2.x - 10) < 0.1 and abs(child2.y - 10) < 0.1:
print(" PASS: Frame margin constructor arg works")
else:
print(f" FAIL: Expected (10, 10), got ({child2.x}, {child2.y})")
sys.exit(1)
print("Test 3: Caption with align constructor arg...")
parent3 = mcrfpy.Frame(pos=(0, 0), size=(400, 300))
cap = mcrfpy.Caption(text="Test", align=mcrfpy.Alignment.TOP_CENTER, margin=20)
parent3.children.append(cap)
# Should be centered horizontally, 20px from top
if abs(cap.y - 20) < 0.1:
print(" PASS: Caption align constructor arg works")
else:
print(f" FAIL: Expected y=20, got y={cap.y}")
sys.exit(1)
print("Test 4: Sprite with align constructor arg...")
parent4 = mcrfpy.Frame(pos=(0, 0), size=(400, 300))
spr = mcrfpy.Sprite(align=mcrfpy.Alignment.BOTTOM_LEFT, margin=5)
parent4.children.append(spr)
if abs(spr.x - 5) < 0.1:
print(" PASS: Sprite align constructor arg works")
else:
print(f" FAIL: Expected x=5, got x={spr.x}")
sys.exit(1)
print("Test 5: Grid with align constructor arg...")
parent5 = mcrfpy.Frame(pos=(0, 0), size=(400, 300))
grid = mcrfpy.Grid(grid_size=(10, 10), size=(200, 200), align=mcrfpy.Alignment.CENTER_RIGHT, margin=15)
parent5.children.append(grid)
# Expected x: 400-200-15=185
if abs(grid.x - 185) < 0.1:
print(" PASS: Grid align constructor arg works")
else:
print(f" FAIL: Expected x=185, got x={grid.x}")
sys.exit(1)
print("Test 6: Line with align constructor arg...")
parent6 = mcrfpy.Frame(pos=(0, 0), size=(400, 300))
line = mcrfpy.Line(start=(0, 0), end=(50, 0), align=mcrfpy.Alignment.TOP_LEFT, margin=25)
parent6.children.append(line)
# Line's position (pos) should be at margin
if abs(line.pos.x - 25) < 0.1 and abs(line.pos.y - 25) < 0.1:
print(" PASS: Line align constructor arg works")
else:
print(f" FAIL: Expected pos at (25, 25), got ({line.pos.x}, {line.pos.y})")
sys.exit(1)
print("Test 7: Circle with align constructor arg...")
parent7 = mcrfpy.Frame(pos=(0, 0), size=(400, 300))
circ = mcrfpy.Circle(radius=30, align=mcrfpy.Alignment.CENTER)
parent7.children.append(circ)
# Circle is centered, center.x should be at parent center (400/2=200), center.y at (300/2=150)
if abs(circ.center.x - 200) < 0.1 and abs(circ.center.y - 150) < 0.1:
print(" PASS: Circle align constructor arg works")
else:
print(f" FAIL: Expected center at (200, 150), got ({circ.center.x}, {circ.center.y})")
sys.exit(1)
print("Test 8: Arc with align constructor arg...")
parent8 = mcrfpy.Frame(pos=(0, 0), size=(400, 300))
arc = mcrfpy.Arc(radius=40, align=mcrfpy.Alignment.BOTTOM_CENTER, vert_margin=10)
parent8.children.append(arc)
# Arc is BOTTOM_CENTER aligned with 10px vert_margin
# Arc bounds: width=2*radius=80, height=2*radius=80
# center.x should be 400/2=200 (centered)
# For bottom alignment: bottom of arc = 300-10 = 290, so center.y = 290 - 40 = 250
if abs(arc.center.x - 200) < 1.0 and abs(arc.center.y - 250) < 1.0:
print(" PASS: Arc align constructor arg works")
else:
print(f" FAIL: Expected center at (200, 250), got ({arc.center.x}, {arc.center.y})")
sys.exit(1)
print("Test 9: Testing horiz_margin and vert_margin separately...")
parent9 = mcrfpy.Frame(pos=(0, 0), size=(400, 300))
frame9 = mcrfpy.Frame(size=(100, 50), align=mcrfpy.Alignment.TOP_RIGHT, horiz_margin=30, vert_margin=20)
parent9.children.append(frame9)
# Expected: x = 400-100-30=270, y = 20
if abs(frame9.x - 270) < 0.1 and abs(frame9.y - 20) < 0.1:
print(" PASS: horiz_margin and vert_margin constructor args work")
else:
print(f" FAIL: Expected (270, 20), got ({frame9.x}, {frame9.y})")
sys.exit(1)
print("Test 10: Nested children with alignment in constructor list...")
outer = mcrfpy.Frame(
pos=(100, 100),
size=(400, 300),
children=[
mcrfpy.Frame(size=(200, 100), align=mcrfpy.Alignment.CENTER),
mcrfpy.Caption(text="Title", align=mcrfpy.Alignment.TOP_CENTER, margin=10),
]
)
# Check inner frame is centered
inner = outer.children[0]
# (400-200)/2=100, (300-100)/2=100
if abs(inner.x - 100) < 0.1 and abs(inner.y - 100) < 0.1:
print(" PASS: Nested children alignment works in constructor list")
else:
print(f" FAIL: Expected inner at (100, 100), got ({inner.x}, {inner.y})")
sys.exit(1)
print()
print("=" * 50)
print("All alignment constructor tests PASSED!")
print("=" * 50)
sys.exit(0)

View file

@ -0,0 +1,214 @@
"""Test the alignment system for UIDrawable elements."""
import mcrfpy
import sys
# Test 1: Check Alignment enum exists and has expected values
print("Test 1: Checking Alignment enum...")
try:
assert hasattr(mcrfpy, 'Alignment'), "Alignment enum not found"
# Check all alignment values exist
expected_alignments = [
'TOP_LEFT', 'TOP_CENTER', 'TOP_RIGHT',
'CENTER_LEFT', 'CENTER', 'CENTER_RIGHT',
'BOTTOM_LEFT', 'BOTTOM_CENTER', 'BOTTOM_RIGHT'
]
for name in expected_alignments:
assert hasattr(mcrfpy.Alignment, name), f"Alignment.{name} not found"
print(" PASS: Alignment enum has all expected values")
except Exception as e:
print(f" FAIL: {e}")
sys.exit(1)
# Test 2: Check that align property exists on Frame
print("Test 2: Checking align property on Frame...")
try:
frame = mcrfpy.Frame(pos=(100, 100), size=(200, 200))
# Default alignment should be None
assert frame.align is None, f"Expected align=None, got {frame.align}"
# Set alignment
frame.align = mcrfpy.Alignment.CENTER
assert frame.align == mcrfpy.Alignment.CENTER, f"Expected CENTER, got {frame.align}"
# Set back to None
frame.align = None
assert frame.align is None, f"Expected None, got {frame.align}"
print(" PASS: align property works on Frame")
except Exception as e:
print(f" FAIL: {e}")
sys.exit(1)
# Test 3: Check margin properties exist
print("Test 3: Checking margin properties...")
try:
frame = mcrfpy.Frame(pos=(0, 0), size=(100, 100))
# Check default margins are 0
assert frame.margin == 0, f"Expected margin=0, got {frame.margin}"
assert frame.horiz_margin == 0, f"Expected horiz_margin=0, got {frame.horiz_margin}"
assert frame.vert_margin == 0, f"Expected vert_margin=0, got {frame.vert_margin}"
# Set margins when no alignment
frame.margin = 10.0
assert frame.margin == 10.0, f"Expected margin=10, got {frame.margin}"
print(" PASS: margin properties exist and can be set")
except Exception as e:
print(f" FAIL: {e}")
sys.exit(1)
# Test 4: Check alignment auto-positioning
print("Test 4: Checking alignment auto-positioning...")
try:
# Create parent frame
parent = mcrfpy.Frame(pos=(0, 0), size=(200, 200))
# Create child with CENTER alignment
child = mcrfpy.Frame(pos=(0, 0), size=(50, 50))
child.align = mcrfpy.Alignment.CENTER
# Add to parent - should trigger alignment
parent.children.append(child)
# Child should be centered: (200-50)/2 = 75
expected_x = 75.0
expected_y = 75.0
assert abs(child.x - expected_x) < 0.1, f"Expected x={expected_x}, got {child.x}"
assert abs(child.y - expected_y) < 0.1, f"Expected y={expected_y}, got {child.y}"
print(" PASS: CENTER alignment positions child correctly")
except Exception as e:
print(f" FAIL: {e}")
sys.exit(1)
# Test 5: Check TOP_LEFT with margin
print("Test 5: Checking TOP_LEFT alignment with margin...")
try:
parent = mcrfpy.Frame(pos=(0, 0), size=(200, 200))
child = mcrfpy.Frame(pos=(999, 999), size=(50, 50)) # Start at wrong position
child.align = mcrfpy.Alignment.TOP_LEFT
child.margin = 10.0
parent.children.append(child)
# Child should be at (10, 10)
assert abs(child.x - 10.0) < 0.1, f"Expected x=10, got {child.x}"
assert abs(child.y - 10.0) < 0.1, f"Expected y=10, got {child.y}"
print(" PASS: TOP_LEFT with margin positions correctly")
except Exception as e:
print(f" FAIL: {e}")
sys.exit(1)
# Test 6: Check BOTTOM_RIGHT alignment
print("Test 6: Checking BOTTOM_RIGHT alignment...")
try:
parent = mcrfpy.Frame(pos=(0, 0), size=(200, 200))
child = mcrfpy.Frame(pos=(0, 0), size=(50, 50))
child.align = mcrfpy.Alignment.BOTTOM_RIGHT
child.margin = 5.0
parent.children.append(child)
# Child should be at (200-50-5, 200-50-5) = (145, 145)
expected_x = 145.0
expected_y = 145.0
assert abs(child.x - expected_x) < 0.1, f"Expected x={expected_x}, got {child.x}"
assert abs(child.y - expected_y) < 0.1, f"Expected y={expected_y}, got {child.y}"
print(" PASS: BOTTOM_RIGHT with margin positions correctly")
except Exception as e:
print(f" FAIL: {e}")
sys.exit(1)
# Test 7: Check resize propagation
print("Test 7: Checking resize propagation to children...")
try:
parent = mcrfpy.Frame(pos=(0, 0), size=(200, 200))
child = mcrfpy.Frame(pos=(0, 0), size=(50, 50))
child.align = mcrfpy.Alignment.CENTER
parent.children.append(child)
# Initial position check
assert abs(child.x - 75.0) < 0.1, f"Initial x should be 75, got {child.x}"
# Resize parent
parent.w = 300
parent.h = 300
# Child should be re-centered: (300-50)/2 = 125
expected_x = 125.0
expected_y = 125.0
assert abs(child.x - expected_x) < 0.1, f"After resize, expected x={expected_x}, got {child.x}"
assert abs(child.y - expected_y) < 0.1, f"After resize, expected y={expected_y}, got {child.y}"
print(" PASS: Resize propagates to aligned children")
except Exception as e:
print(f" FAIL: {e}")
sys.exit(1)
# Test 8: Check that align=None freezes position
print("Test 8: Checking that align=None freezes position...")
try:
parent = mcrfpy.Frame(pos=(0, 0), size=(200, 200))
child = mcrfpy.Frame(pos=(0, 0), size=(50, 50))
child.align = mcrfpy.Alignment.CENTER
parent.children.append(child)
centered_x = child.x
centered_y = child.y
# Disable alignment
child.align = None
# Resize parent
parent.w = 400
parent.h = 400
# Position should NOT change
assert abs(child.x - centered_x) < 0.1, f"Position should be frozen at {centered_x}, got {child.x}"
assert abs(child.y - centered_y) < 0.1, f"Position should be frozen at {centered_y}, got {child.y}"
print(" PASS: align=None freezes position")
except Exception as e:
print(f" FAIL: {e}")
sys.exit(1)
# Test 9: Check CENTER alignment rejects margins
print("Test 9: Checking CENTER alignment rejects margins...")
try:
frame = mcrfpy.Frame(pos=(0, 0), size=(50, 50))
frame.align = mcrfpy.Alignment.CENTER
# Setting margin on CENTER should raise ValueError
try:
frame.margin = 10.0
print(" FAIL: Expected ValueError for margin with CENTER alignment")
sys.exit(1)
except ValueError as e:
pass # Expected
print(" PASS: CENTER alignment correctly rejects margin")
except Exception as e:
print(f" FAIL: {e}")
sys.exit(1)
# Test 10: Check alignment on other drawable types
print("Test 10: Checking alignment on Caption...")
try:
parent = mcrfpy.Frame(pos=(0, 0), size=(200, 100))
caption = mcrfpy.Caption(text="Test", pos=(0, 0))
caption.align = mcrfpy.Alignment.CENTER
parent.children.append(caption)
# Caption should be roughly centered (exact position depends on text size)
# Just verify it was moved from (0,0)
assert caption.x > 0 or caption.y > 0, "Caption should have been repositioned"
print(" PASS: Caption supports alignment")
except Exception as e:
print(f" FAIL: {e}")
sys.exit(1)
print("\n" + "=" * 40)
print("All alignment tests PASSED!")
print("=" * 40)
sys.exit(0)