244 lines
5.5 KiB
Python
244 lines
5.5 KiB
Python
"""µFrame Intermediate Representation — node types for the IR tree.
|
||
|
||
Every .uf source parses into a tree of IRNode subclasses. The layout
|
||
engine measures, positions, and paints these nodes into a CharGrid.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from dataclasses import dataclass, field
|
||
from enum import Enum, auto
|
||
from typing import Any
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Enums
|
||
# ---------------------------------------------------------------------------
|
||
|
||
class BorderWeight(Enum):
|
||
LIGHT = auto()
|
||
HEAVY = auto()
|
||
DOUBLE = auto()
|
||
ROUNDED = auto()
|
||
|
||
|
||
class HeadingLevel(Enum):
|
||
H1 = 1
|
||
H2 = 2
|
||
H3 = 3
|
||
|
||
|
||
class DividerStyle(Enum):
|
||
LIGHT = auto()
|
||
HEAVY = auto()
|
||
DOUBLE = auto()
|
||
DASH = auto()
|
||
DOT = auto()
|
||
|
||
|
||
class ListStyle(Enum):
|
||
BULLET = auto()
|
||
DASH = auto()
|
||
NUMBER = auto()
|
||
ARROW = auto()
|
||
|
||
|
||
class Align(Enum):
|
||
LEFT = auto()
|
||
CENTER = auto()
|
||
RIGHT = auto()
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Style
|
||
# ---------------------------------------------------------------------------
|
||
|
||
@dataclass
|
||
class Style:
|
||
"""Visual style attached to any node."""
|
||
fg: str | None = None # 3-digit hex
|
||
bg: str | None = None # 3-digit hex
|
||
bold: bool = False
|
||
italic: bool = False
|
||
underline: bool = False
|
||
align: Align = Align.LEFT
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Inline text spans (parsed from @modifier{} syntax in text content)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
@dataclass
|
||
class TextSpan:
|
||
"""A run of text with optional inline styling."""
|
||
text: str
|
||
bold: bool = False
|
||
italic: bool = False
|
||
underline: bool = False
|
||
fg: str | None = None
|
||
bg: str | None = None
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Layout rect — assigned by the layout engine
|
||
# ---------------------------------------------------------------------------
|
||
|
||
@dataclass
|
||
class Rect:
|
||
x: int = 0
|
||
y: int = 0
|
||
w: int = 0
|
||
h: int = 0
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Base node
|
||
# ---------------------------------------------------------------------------
|
||
|
||
@dataclass
|
||
class IRNode:
|
||
"""Base class for all IR nodes."""
|
||
children: list[IRNode] = field(default_factory=list)
|
||
style: Style = field(default_factory=Style)
|
||
rect: Rect = field(default_factory=Rect)
|
||
source_line: int | None = None
|
||
|
||
# Set by measure pass
|
||
min_width: int = 0
|
||
min_height: int = 0
|
||
pref_width: int = 0
|
||
pref_height: int = 0
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Layout nodes
|
||
# ---------------------------------------------------------------------------
|
||
|
||
@dataclass
|
||
class Page(IRNode):
|
||
"""Root container. One per .uf file."""
|
||
title: str = ""
|
||
width: int = 64
|
||
|
||
|
||
@dataclass
|
||
class Box(IRNode):
|
||
"""Bordered panel with optional title."""
|
||
title: str = ""
|
||
weight: BorderWeight = BorderWeight.LIGHT
|
||
|
||
|
||
@dataclass
|
||
class Row(IRNode):
|
||
"""Horizontal layout — children split available width."""
|
||
gap: int = 1
|
||
|
||
|
||
@dataclass
|
||
class Col(IRNode):
|
||
"""Explicit column in a row. Width in chars or None (auto)."""
|
||
col_width: int | None = None
|
||
|
||
|
||
@dataclass
|
||
class Spacer(IRNode):
|
||
"""Vertical whitespace."""
|
||
lines: int = 1
|
||
|
||
|
||
@dataclass
|
||
class Pad(IRNode):
|
||
"""Inner margin for a container."""
|
||
top: int = 0
|
||
right: int = 0
|
||
bottom: int = 0
|
||
left: int = 0
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Content nodes
|
||
# ---------------------------------------------------------------------------
|
||
|
||
@dataclass
|
||
class Heading(IRNode):
|
||
"""Styled heading (levels 1–3)."""
|
||
level: HeadingLevel = HeadingLevel.H1
|
||
text: str = ""
|
||
|
||
|
||
@dataclass
|
||
class Text(IRNode):
|
||
"""Text content with optional @modifier{} inline styles."""
|
||
content: str = ""
|
||
spans: list[TextSpan] = field(default_factory=list)
|
||
|
||
|
||
@dataclass
|
||
class Label(IRNode):
|
||
"""Aligned key-value pair."""
|
||
key: str = ""
|
||
value: str = ""
|
||
|
||
|
||
@dataclass
|
||
class Divider(IRNode):
|
||
"""Full-width horizontal rule."""
|
||
divider_style: DividerStyle = DividerStyle.LIGHT
|
||
|
||
|
||
@dataclass
|
||
class Link(IRNode):
|
||
"""Clickable link — visual in ASCII, interactive in Micron."""
|
||
display: str = ""
|
||
dest: str = ""
|
||
|
||
|
||
@dataclass
|
||
class ListNode(IRNode):
|
||
"""Bulleted or numbered list."""
|
||
list_style: ListStyle = ListStyle.BULLET
|
||
|
||
|
||
@dataclass
|
||
class ListItem(IRNode):
|
||
"""Single entry in a ListNode."""
|
||
content: str = ""
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Placeholder nodes for future phases
|
||
# ---------------------------------------------------------------------------
|
||
|
||
@dataclass
|
||
class Gauge(IRNode):
|
||
"""Horizontal bar chart (Phase 4)."""
|
||
label: str = ""
|
||
value: float = 0
|
||
max_val: float = 100
|
||
bar_width: int = 28
|
||
warn: float | None = None
|
||
crit: float | None = None
|
||
|
||
|
||
@dataclass
|
||
class Sparkline(IRNode):
|
||
"""Braille sparkline (Phase 4)."""
|
||
label: str = ""
|
||
values: list[float] = field(default_factory=list)
|
||
spark_width: int = 20
|
||
|
||
|
||
@dataclass
|
||
class Status(IRNode):
|
||
"""Status indicator (Phase 4)."""
|
||
label: str = ""
|
||
state: str = "unknown"
|
||
|
||
|
||
@dataclass
|
||
class Table(IRNode):
|
||
"""Box-drawn table (Phase 4)."""
|
||
title: str = ""
|
||
columns: list[tuple[str, int]] = field(default_factory=list) # (name, width)
|
||
rows: list[list[str]] = field(default_factory=list)
|