Alignment: reactive or automatically calculated repositioning of UIDrawables on their parent
This commit is contained in:
parent
73230989ad
commit
4bf590749c
23 changed files with 1350 additions and 397 deletions
131
tests/unit/alignment_constructor_test.py
Normal file
131
tests/unit/alignment_constructor_test.py
Normal 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)
|
||||
214
tests/unit/alignment_test.py
Normal file
214
tests/unit/alignment_test.py
Normal 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue