Skip to content

Commit 2531d61

Browse files
committed
reworked a bit render mechanism
1 parent 76859c9 commit 2531d61

15 files changed

+156
-340
lines changed

python/layout_visualizer.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from graphviz import Digraph
22
import json
3+
import sys
34
from typing import Optional
45
import argparse
56

@@ -35,27 +36,25 @@ def add_node(node):
3536
return dot.source
3637
else:
3738
dot.render(cleanup=True)
38-
print(f"[✓] Layout tree rendered to {output_path}.{format}")
3939
return None
40-
4140

4241

4342
if __name__ == "__main__":
4443
parser = argparse.ArgumentParser(description="Visualize layout tree from a layout JSON file.")
4544
parser.add_argument("input",
46-
help="Input JSON layout log (e.g., logs/layout-log.json)")
45+
help="Input JSON layout log (e.g., logs/layout-log.json)", nargs="?", default="-")
4746
parser.add_argument("-o", "--out", default='-',
4847
help="Output file path or '-' for stdout (default: '-')")
4948
parser.add_argument("--format", default="png", choices=["png", "svg", "pdf"],
5049
help="Output format (default: png)")
5150

5251
args = parser.parse_args()
53-
try:
52+
if args.input != "-":
5453
with open(args.input, 'r', encoding='utf-8') as f:
5554
data = json.load(f)
56-
except (FileNotFoundError, json.JSONDecodeError):
57-
data = args.input
58-
55+
else:
56+
data = json.load("\n".join(sys.stdin.readlines()))
57+
5958
result = visualize_layout_tree(data, args.out, args.format)
6059
if result:
61-
print(result)
60+
print(result)

python/rlc/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,5 @@
1414
from .layout import Layout, Padding, Direction, FIT, FIXED, GROW
1515
from .text import Text
1616
from rlc.layout_logger import LayoutLogConfig, LayoutLogger
17-
from test.display_layout import display
1817

1918

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
from rlc.renderer.renderable import Renderable, register_renderer
22
from rlc.layout import Layout, Direction, FIT, Padding
3+
from dataclasses import dataclass
34

45
@register_renderer
6+
@dataclass
57
class ArrayRenderer(Renderable):
6-
def __init__(self, rlc_type_name, length, element_rendere: Renderable, style_policy):
7-
super().__init__(rlc_type_name, style_policy)
8-
self.length = length
9-
self.element_renderer = element_rendere
10-
self.style_policy = style_policy
8+
length: int
9+
element_renderer: Renderable
1110

1211
def build_layout(self, obj, direction=Direction.COLUMN, color="white", sizing=(FIT(), FIT()), logger=None, padding=Padding(2,2,2,2)):
1312
layout = self.make_layout(sizing=sizing, direction=direction, color=color, padding=padding, border=3, child_gap=5)
1413
layout.binding = {"type": "array"}
1514
color = 'lightgray'
1615
if self.element_renderer is not None:
17-
for i in range(self.length):
16+
for i in range(self.length):
1817
item = obj[i]
1918
# Alternate direction for the next depth
2019
next_dir = (
@@ -24,7 +23,7 @@ def build_layout(self, obj, direction=Direction.COLUMN, color="white", sizing=(F
2423
item_binding = {
2524
"type": "array_item",
2625
"index": i,
27-
"parent": layout.binding
26+
"parent": layout.binding
2827
}
2928
child = self.element_renderer(
3029
item,
@@ -38,32 +37,16 @@ def build_layout(self, obj, direction=Direction.COLUMN, color="white", sizing=(F
3837
child.binding = item_binding
3938
layout.add_child(child)
4039
return layout
41-
40+
4241
def update(self, layout, obj, elapsed_time=0.0):
4342
for i, child in enumerate(layout.children):
4443
item = obj[i]
4544
self.element_renderer.update(child, item, elapsed_time)
46-
45+
4746
def _iter_children(self):
4847
return [self.element_renderer]
4948

50-
def _describe_self(self):
51-
return f"{self.rlc_type_name + str(self.style_policy)}[len={self.length}]"
52-
53-
def _to_dict_data(self):
54-
return {
55-
"length": self.length,
56-
"element": self.element_renderer.to_dict() if self.element_renderer is not None else None,
57-
"style_policy" : self.style_policy
58-
}
59-
60-
@classmethod
61-
def _from_dict_data(cls, rlc_type_name, data):
62-
length = data["length"]
63-
element = Renderable.from_dict(data["element"]) if data["element"] is not None else None
64-
return cls(rlc_type_name, length, element, data["style_policy"])
65-
6649
def apply_interactivity(self, layout_child, index=None, parent_obj=None):
6750
"""Hook for subclasses to mark children interactive."""
6851
return None
69-
52+
Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,28 @@
11
from rlc.text import Text
22
from rlc.renderer.renderable import Renderable, register_renderer
33
from rlc.layout import Direction, FIT, Padding
4+
from dataclasses import dataclass
45

56
@register_renderer
67
class BoundedIntRenderer(Renderable):
78
"""
89
Renderer for bounded integer structs like BIntT1T10T.
910
"""
10-
def __init__(self, rlc_type_name, style_policy):
11-
super().__init__(rlc_type_name, style_policy)
12-
# Optionally parse bounds from type name: BIntT1T10T -> 1,10
13-
name = rlc_type_name
14-
self.bounds = self._parse_bounds_from_name(name)
15-
self.style_policy = style_policy
16-
17-
def _parse_bounds_from_name(self, name: str):
18-
# Extract numbers between 'T' markers if present
19-
# e.g., "BIntT1T10T" -> (1, 10)
20-
parts = [p for p in name.split("T") if p.isdigit()]
21-
if len(parts) >= 2:
22-
return (int(parts[0]), int(parts[1]))
23-
return None
24-
2511
def build_layout(self, obj, direction=Direction.COLUMN,
2612
color="white", sizing=(FIT(), FIT()), logger=None, padding=Padding(2,2,2,2)):
2713
# Extract the 'value' field (the inner c_long)
2814
value = getattr(obj, "value", None)
2915
val_str = str(value if isinstance(value, int) else getattr(value, "value", value))
3016

31-
# Include bounds if known
32-
# if self.bounds:
33-
# low, high = self.bounds
34-
# display = f"{val_str} [{low}-{high}]"
35-
# else:
36-
# display = val_str
3717
layout = self.make_text(val_str, "Arial", 16, "black")
3818
layout.binding = {
3919
"type": "bounded_int",
4020
"obj": obj
4121
}
4222
return layout
43-
23+
4424
def update(self, layout, obj, elapsed_time=0.0):
4525
if isinstance(layout, Text):
4626
value = getattr(obj, "value", None)
4727
new_val = str(value if isinstance(value, int) else getattr(value, "value", value))
4828
layout.update_text(new_val)
49-
50-
def _describe_self(self):
51-
return f"{self.rlc_type_name + str(self.style_policy)}(bounded)"
52-
53-
def _to_dict_data(self):
54-
return {
55-
"bounds": self.bounds,
56-
"style_policy" : self.style_policy
57-
}
58-
59-
@classmethod
60-
def _from_dict_data(cls, rlc_type_name, data):
61-
obj = cls(rlc_type_name, data["style_policy"])
62-
obj.bounds = tuple(data["bounds"])
63-
return obj
Lines changed: 4 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,22 @@
11
from rlc.text import Text
22
from rlc.renderer.renderable import Renderable, register_renderer
33
from rlc.layout import Direction, FIT, Padding
4+
from dataclasses import dataclass
45

56
@register_renderer
7+
@dataclass
68
class BoundedVectorRenderer(Renderable):
79
"""
810
Renderer for bounded integer structs like BIntT1T10T.
911
"""
10-
def __init__(self, rlc_type_name, vector_renderer, style_policy):
11-
super().__init__(rlc_type_name, style_policy)
12-
name = rlc_type_name
13-
self.vector_renderer = vector_renderer
14-
self.bounds = self._parse_bounds_from_name(name)
15-
self.style_policy = style_policy
16-
17-
def _parse_bounds_from_name(self, name: str):
18-
# Extract numbers between 'T' markers if present
19-
# e.g., "BoundedVectorT20T" -> (1, 10)
20-
parts = [p for p in name.split("T") if p.isdigit()]
21-
if len(parts) >= 2:
22-
return int(parts[-1])
23-
return None
12+
vector_renderer: Renderable
2413

2514
def build_layout(self, obj, direction=Direction.COLUMN,
2615
color="white", sizing=(FIT(), FIT()), logger=None, padding=Padding(2,2,2,2)):
2716
value = getattr(obj, "_data", None)
2817
value_layout = self.vector_renderer(value)
2918
return value_layout
30-
31-
19+
3220
def update(self, layout, obj, elapsed_time=0.0):
3321
value = getattr(obj, "_data")
3422
self.vector_renderer.update(layout, value, elapsed_time)
35-
36-
def _describe_self(self):
37-
return f"{self.rlc_type_name + str(self.style_policy)}(bounded)"
38-
39-
def _to_dict_data(self):
40-
return {
41-
"bounds": self.bounds,
42-
"field": self.vector_renderer.to_dict(),
43-
"style_policy" : self.style_policy
44-
}
45-
46-
@classmethod
47-
def _from_dict_data(cls, rlc_type_name, data):
48-
vector_renderer = Renderable.from_dict(data["field"])
49-
obj = cls(rlc_type_name, vector_renderer, data["style_policy"])
50-
obj.bounds = data["bounds"]
51-
52-
return obj

python/rlc/renderer/primitive_renderer.py

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,11 @@
44
from rlc.text import Text
55
from rlc.layout import Direction, FIT, Padding
66
import time
7+
from dataclasses import dataclass
78

89
@register_renderer
10+
@dataclass
911
class PrimitiveRenderer(Renderable):
10-
def __init__(self, rlc_type_name, style_policy):
11-
super().__init__(rlc_type_name, style_policy)
12-
self.style_policy = style_policy
13-
14-
def _iter_children(self):
15-
return []
16-
1712
def build_layout(self, obj, direction=Direction.COLUMN, color="white", sizing=(FIT(), FIT()), logger=None, padding=Padding(2,2,2,2)):
1813
if self.rlc_type_name == "c_bool":
1914
text = "True" if obj else "False"
@@ -29,7 +24,7 @@ def build_layout(self, obj, direction=Direction.COLUMN, color="white", sizing=(F
2924
}
3025

3126
return layout
32-
27+
3328
def update(self, layout, obj, elapsed_time=0.0):
3429
"""Update the text node if the value changed."""
3530
if isinstance(layout, Text):
@@ -44,12 +39,4 @@ def _extract_value(self, obj):
4439
else:
4540
text = str(obj)
4641
return text
47-
48-
def _to_dict_data(self):
49-
return {
50-
"style_policy" : self.style_policy
51-
}
5242

53-
@classmethod
54-
def _from_dict_data(cls, rlc_type_name, data):
55-
return cls(rlc_type_name, data["style_policy"])

python/rlc/renderer/renderable.py

Lines changed: 6 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11

22
from abc import ABC, abstractmethod
33
from rlc.layout import Layout, Direction, FIT, Padding
4+
from typing import Dict
5+
from dataclasses import dataclass, field
46
from rlc.text import Text
57

68
_renderer_registry = {} # maps class name → class
@@ -9,14 +11,14 @@ def register_renderer(cls):
911
_renderer_registry[cls.__name__] = cls
1012
return cls
1113

14+
@dataclass(kw_only=True)
1215
class Renderable(ABC):
1316
"""
1417
Base abstract renderer type.
1518
Each subclasss knows how to convert its types object into a Layout tree.
1619
"""
17-
def __init__(self, rlc_type_name, style_policy : dict):
18-
self.rlc_type_name = rlc_type_name
19-
self.style_policy = style_policy
20+
rlc_type_name: str
21+
style_policy: Dict = field(default_factory=dict)
2022

2123
def make_layout(self, direction=Direction.COLUMN, color="white", sizing=(FIT(), FIT()), logger=None, padding=Padding(2,2,2,2), border=3, child_gap=5) -> Layout:
2224
layout = Layout(sizing=sizing, direction=direction, color=color, padding=padding, border=border, child_gap=child_gap)
@@ -53,7 +55,7 @@ def update(self, layout, obj, elapsed_time: float = 0.0):
5355

5456
def __call__(self, obj, parent_binding=None, **kwds):
5557
layout = self.build_layout(obj=obj, **kwds)
56-
58+
5759
# If build_layout didn't set binding, ensure we add it
5860
if layout.binding is None:
5961
layout.binding = {
@@ -75,52 +77,7 @@ def __call__(self, obj, parent_binding=None, **kwds):
7577
child.binding["parent"] = layout.binding
7678

7779
return layout
78-
79-
80-
# ---------- PUBLIC SERIALIZATION API ----------
81-
def to_dict(self):
82-
return {
83-
"renderer_class": self.__class__.__name__,
84-
"rlc_type_name": self.rlc_type_name,
85-
"data": self._to_dict_data(),
86-
"style_policy" : self.style_policy
87-
}
88-
89-
@staticmethod
90-
def from_dict(d):
91-
cls_name = d["renderer_class"]
92-
if cls_name not in _renderer_registry:
93-
raise ValueError(f"Unknown renderer class '{cls_name}'")
94-
cls = _renderer_registry[cls_name]
95-
return cls._from_dict_data(
96-
d["rlc_type_name"],
97-
d["data"]
98-
)
99-
100-
# ---------- SUBCLASS HOOKS ----------
101-
@abstractmethod
102-
def _to_dict_data(self):
103-
"""Return subclass-specific dict data."""
104-
pass
105-
106-
@classmethod
107-
@abstractmethod
108-
def _from_dict_data(cls, rlc_type_name, data):
109-
"""Recreate subclass instance."""
110-
pass
111-
112-
def print_tree(self, indent: int = 0):
113-
prefix = " " * indent
114-
print(f"{prefix}{self.__class__.__name__}({self._describe_self()})")
115-
for child in self._iter_children():
116-
if isinstance(child, Renderable):
117-
child.print_tree(indent + 1)
118-
else:
119-
print(f"{' ' * (indent + 1)}{child}")
12080

121-
def _describe_self(self) -> str:
122-
"""Subclasses can override to show additional info."""
123-
return self.rlc_type_name + str(self.style_policy)
12481

12582
def _iter_children(self):
12683
"""Return iterable of child renderers, if any. Override per subclass."""

0 commit comments

Comments
 (0)