feat: added a twist
This commit is contained in:
134
backend/uframe/layout.py
Normal file
134
backend/uframe/layout.py
Normal file
@@ -0,0 +1,134 @@
|
||||
"""Top-down layout pass — assign (x, y, w, h) positions to every node."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from uframe.ir import (
|
||||
IRNode, Page, Box, Row, Col, Spacer, Pad, Rect,
|
||||
Heading, Text, Label, Divider, Link, ListNode, ListItem,
|
||||
Gauge, Sparkline, Status,
|
||||
)
|
||||
|
||||
|
||||
def layout(node: IRNode, x: int, y: int, w: int, h: int) -> int:
|
||||
"""Assign positions to a node and its children.
|
||||
|
||||
Args:
|
||||
node: the IR node to lay out
|
||||
x, y: top-left position in the grid
|
||||
w: available width
|
||||
h: available height (advisory, may grow)
|
||||
|
||||
Returns:
|
||||
The actual height consumed by this node.
|
||||
"""
|
||||
node.rect = Rect(x=x, y=y, w=w, h=0)
|
||||
|
||||
if isinstance(node, Page):
|
||||
cursor_y = y
|
||||
for child in node.children:
|
||||
child_h = layout(child, x, cursor_y, w, h - (cursor_y - y))
|
||||
cursor_y += child_h
|
||||
node.rect.h = cursor_y - y
|
||||
return node.rect.h
|
||||
|
||||
elif isinstance(node, Box):
|
||||
# Border takes 1 char on each side
|
||||
inner_x = x + 1
|
||||
inner_y = y + 1
|
||||
inner_w = w - 2
|
||||
cursor_y = inner_y
|
||||
for child in node.children:
|
||||
child_h = layout(child, inner_x, cursor_y, inner_w,
|
||||
h - 2 - (cursor_y - inner_y))
|
||||
cursor_y += child_h
|
||||
inner_h = cursor_y - inner_y
|
||||
node.rect.h = inner_h + 2 # +2 for top/bottom border
|
||||
return node.rect.h
|
||||
|
||||
elif isinstance(node, Row):
|
||||
n = len(node.children)
|
||||
if n == 0:
|
||||
return 0
|
||||
|
||||
gap_total = node.gap * (n - 1)
|
||||
usable = w - gap_total
|
||||
|
||||
# Distribute width
|
||||
widths: list[int] = []
|
||||
fixed_total = 0
|
||||
flex_count = 0
|
||||
for child in node.children:
|
||||
if isinstance(child, Col) and child.col_width is not None:
|
||||
widths.append(child.col_width)
|
||||
fixed_total += child.col_width
|
||||
else:
|
||||
widths.append(0)
|
||||
flex_count += 1
|
||||
|
||||
flex_each = max(1, (usable - fixed_total) // flex_count) if flex_count > 0 else 0
|
||||
remainder = (usable - fixed_total) - (flex_each * flex_count) if flex_count > 0 else 0
|
||||
|
||||
for i, child in enumerate(node.children):
|
||||
if widths[i] == 0:
|
||||
widths[i] = flex_each
|
||||
if remainder > 0:
|
||||
widths[i] += 1
|
||||
remainder -= 1
|
||||
|
||||
# Lay out each child at its column position
|
||||
max_h = 0
|
||||
col_x = x
|
||||
for i, child in enumerate(node.children):
|
||||
child_h = layout(child, col_x, y, widths[i], h)
|
||||
max_h = max(max_h, child_h)
|
||||
col_x += widths[i] + node.gap
|
||||
|
||||
node.rect.h = max_h
|
||||
return max_h
|
||||
|
||||
elif isinstance(node, Col):
|
||||
cursor_y = y
|
||||
col_w = node.col_width if node.col_width is not None else w
|
||||
col_w = min(col_w, w)
|
||||
for child in node.children:
|
||||
child_h = layout(child, x, cursor_y, col_w, h - (cursor_y - y))
|
||||
cursor_y += child_h
|
||||
node.rect.w = col_w
|
||||
node.rect.h = cursor_y - y
|
||||
return node.rect.h
|
||||
|
||||
elif isinstance(node, Spacer):
|
||||
node.rect.h = node.lines
|
||||
return node.lines
|
||||
|
||||
elif isinstance(node, Pad):
|
||||
cursor_y = y + node.top
|
||||
inner_w = w - node.left - node.right
|
||||
for child in node.children:
|
||||
child_h = layout(child, x + node.left, cursor_y, inner_w,
|
||||
h - node.top - node.bottom - (cursor_y - y - node.top))
|
||||
cursor_y += child_h
|
||||
node.rect.h = (cursor_y - y) + node.bottom
|
||||
return node.rect.h
|
||||
|
||||
elif isinstance(node, (Heading, Text, Label, Divider, Link, ListItem,
|
||||
Gauge, Sparkline, Status)):
|
||||
node.rect.h = node.pref_height
|
||||
return node.pref_height
|
||||
|
||||
elif isinstance(node, ListNode):
|
||||
cursor_y = y
|
||||
for child in node.children:
|
||||
child_h = layout(child, x + 2, cursor_y, w - 2, h - (cursor_y - y))
|
||||
cursor_y += child_h
|
||||
node.rect.h = cursor_y - y
|
||||
return node.rect.h
|
||||
|
||||
else:
|
||||
# Generic vertical stacking
|
||||
cursor_y = y
|
||||
for child in node.children:
|
||||
child_h = layout(child, x, cursor_y, w, h - (cursor_y - y))
|
||||
cursor_y += child_h
|
||||
node.rect.h = cursor_y - y
|
||||
return node.rect.h
|
||||
Reference in New Issue
Block a user