diff --git a/scripts/gdb/lvglgdb/lvgl/misc/__init__.py b/scripts/gdb/lvglgdb/lvgl/misc/__init__.py index 673d614ae4..b1b4ddae15 100644 --- a/scripts/gdb/lvglgdb/lvgl/misc/__init__.py +++ b/scripts/gdb/lvglgdb/lvgl/misc/__init__.py @@ -1,7 +1,11 @@ from .lv_ll import LVList from .lv_style import ( - LVStyle, StyleEntry, - dump_style_info, style_prop_name, decode_selector, format_style_value, + LVStyle, + StyleEntry, + dump_style_info, + style_prop_name, + decode_selector, + format_style_value, ) from .lv_rb import LVRedBlackTree, dump_rb_info from .lv_cache import LVCache, dump_cache_info diff --git a/scripts/gdb/lvglgdb/lvgl/misc/lv_style.py b/scripts/gdb/lvglgdb/lvgl/misc/lv_style.py index 756cab72b0..a4dc9e800b 100644 --- a/scripts/gdb/lvglgdb/lvgl/misc/lv_style.py +++ b/scripts/gdb/lvglgdb/lvgl/misc/lv_style.py @@ -4,200 +4,18 @@ from typing import Iterator import gdb from prettytable import PrettyTable from lvglgdb.value import Value - -# Style property ID to name mapping (from lv_style.h enum) -_STYLE_PROP_NAMES = { - 1: "WIDTH", - 2: "HEIGHT", - 3: "LENGTH", - 4: "TRANSFORM_WIDTH", - 5: "TRANSFORM_HEIGHT", - 8: "MIN_WIDTH", - 9: "MAX_WIDTH", - 10: "MIN_HEIGHT", - 11: "MAX_HEIGHT", - 12: "TRANSLATE_X", - 13: "TRANSLATE_Y", - 14: "RADIAL_OFFSET", - 16: "X", - 17: "Y", - 18: "ALIGN", - 24: "PAD_TOP", - 25: "PAD_BOTTOM", - 26: "PAD_LEFT", - 27: "PAD_RIGHT", - 28: "PAD_RADIAL", - 29: "PAD_ROW", - 30: "PAD_COLUMN", - 32: "MARGIN_TOP", - 33: "MARGIN_BOTTOM", - 34: "MARGIN_LEFT", - 35: "MARGIN_RIGHT", - 40: "BG_GRAD", - 41: "BG_GRAD_DIR", - 42: "BG_MAIN_OPA", - 43: "BG_GRAD_OPA", - 44: "BG_GRAD_COLOR", - 45: "BG_MAIN_STOP", - 46: "BG_GRAD_STOP", - 48: "BG_IMAGE_SRC", - 49: "BG_IMAGE_OPA", - 50: "BG_IMAGE_RECOLOR_OPA", - 51: "BG_IMAGE_TILED", - 52: "BG_IMAGE_RECOLOR", - 56: "BORDER_WIDTH", - 57: "BORDER_COLOR", - 58: "BORDER_OPA", - 59: "BORDER_POST", - 60: "BORDER_SIDE", - 64: "OUTLINE_WIDTH", - 65: "OUTLINE_COLOR", - 66: "OUTLINE_OPA", - 67: "OUTLINE_PAD", - 72: "BG_OPA", - 73: "BG_COLOR", - 74: "SHADOW_WIDTH", - 75: "LINE_WIDTH", - 76: "ARC_WIDTH", - 77: "TEXT_FONT", - 78: "IMAGE_RECOLOR_OPA", - 80: "IMAGE_OPA", - 81: "SHADOW_OPA", - 82: "LINE_OPA", - 83: "ARC_OPA", - 84: "TEXT_OPA", - 88: "SHADOW_COLOR", - 89: "IMAGE_RECOLOR", - 90: "LINE_COLOR", - 91: "ARC_COLOR", - 92: "TEXT_COLOR", - 96: "ARC_IMAGE_SRC", - 97: "SHADOW_OFFSET_X", - 98: "SHADOW_OFFSET_Y", - 99: "SHADOW_SPREAD", - 100: "LINE_DASH_WIDTH", - 101: "TEXT_ALIGN", - 102: "TEXT_LETTER_SPACE", - 103: "TEXT_LINE_SPACE", - 104: "LINE_DASH_GAP", - 105: "LINE_ROUNDED", - 106: "IMAGE_COLORKEY", - 107: "TEXT_OUTLINE_STROKE_WIDTH", - 108: "TEXT_OUTLINE_STROKE_OPA", - 109: "TEXT_OUTLINE_STROKE_COLOR", - 110: "TEXT_DECOR", - 111: "ARC_ROUNDED", - 112: "OPA", - 113: "OPA_LAYERED", - 114: "COLOR_FILTER_DSC", - 115: "COLOR_FILTER_OPA", - 116: "ANIM", - 117: "ANIM_DURATION", - 118: "TRANSITION", - 120: "RADIUS", - 121: "BITMAP_MASK_SRC", - 122: "BLEND_MODE", - 123: "ROTARY_SENSITIVITY", - 124: "TRANSLATE_RADIAL", - 128: "CLIP_CORNER", - 129: "BASE_DIR", - 130: "RECOLOR", - 131: "RECOLOR_OPA", - 132: "LAYOUT", - 136: "BLUR_RADIUS", - 137: "BLUR_BACKDROP", - 138: "BLUR_QUALITY", - 144: "DROP_SHADOW_RADIUS", - 145: "DROP_SHADOW_OFFSET_X", - 146: "DROP_SHADOW_OFFSET_Y", - 147: "DROP_SHADOW_COLOR", - 148: "DROP_SHADOW_OPA", - 149: "DROP_SHADOW_QUALITY", - 152: "TRANSFORM_SCALE_X", - 153: "TRANSFORM_SCALE_Y", - 154: "TRANSFORM_PIVOT_X", - 155: "TRANSFORM_PIVOT_Y", - 156: "TRANSFORM_ROTATION", - 157: "TRANSFORM_SKEW_X", - 158: "TRANSFORM_SKEW_Y", - 160: "FLEX_FLOW", - 161: "FLEX_MAIN_PLACE", - 162: "FLEX_CROSS_PLACE", - 163: "FLEX_TRACK_PLACE", - 164: "FLEX_GROW", - 165: "GRID_COLUMN_DSC_ARRAY", - 166: "GRID_ROW_DSC_ARRAY", - 168: "GRID_COLUMN_ALIGN", - 169: "GRID_ROW_ALIGN", - 170: "GRID_CELL_COLUMN_POS", - 171: "GRID_CELL_COLUMN_SPAN", - 172: "GRID_CELL_X_ALIGN", - 173: "GRID_CELL_ROW_POS", - 174: "GRID_CELL_ROW_SPAN", - 175: "GRID_CELL_Y_ALIGN", -} - -_PART_NAMES = { - 0x00: "MAIN", - 0x01: "SCROLLBAR", - 0x02: "INDICATOR", - 0x03: "KNOB", - 0x04: "SELECTED", - 0x05: "ITEMS", - 0x06: "CURSOR", - 0x08: "CUSTOM_FIRST", - 0x0F: "ANY", -} - -_STATE_FLAGS = { - 0x0001: "ALT", - 0x0004: "CHECKED", - 0x0008: "FOCUSED", - 0x0010: "FOCUS_KEY", - 0x0020: "EDITED", - 0x0040: "HOVERED", - 0x0080: "PRESSED", - 0x0100: "SCROLLED", - 0x0200: "DISABLED", - 0x1000: "USER_1", - 0x2000: "USER_2", - 0x4000: "USER_3", - 0x8000: "USER_4", -} - -_COLOR_PROPS = { - 44, - 52, - 57, - 65, - 73, - 88, - 89, - 90, - 91, - 92, - 109, - 130, - 147, -} - -_POINTER_PROPS = { - 40, - 48, - 77, - 96, - 114, - 116, - 118, - 121, - 165, - 166, -} +from .lv_style_consts import ( + STYLE_PROP_NAMES, + PART_NAMES, + STATE_FLAGS, + COLOR_PROPS, + POINTER_PROPS, +) def style_prop_name(prop_id: int) -> str: """Resolve style property ID to human-readable name.""" - return _STYLE_PROP_NAMES.get(prop_id, f"UNKNOWN({prop_id})") + return STYLE_PROP_NAMES.get(prop_id, f"UNKNOWN({prop_id})") def decode_selector(selector: int) -> str: @@ -205,14 +23,14 @@ def decode_selector(selector: int) -> str: part_val = (selector >> 16) & 0xFF state_val = selector & 0xFFFF - part_str = _PART_NAMES.get(part_val, f"PART({part_val:#x})") + part_str = PART_NAMES.get(part_val, f"PART({part_val:#x})") if state_val == 0: state_str = "DEFAULT" elif state_val == 0xFFFF: state_str = "ANY" else: - flags = [name for bit, name in _STATE_FLAGS.items() if state_val & bit] + flags = [name for bit, name in STATE_FLAGS.items() if state_val & bit] state_str = "|".join(flags) if flags else f"STATE({state_val:#x})" return f"{part_str}|{state_str}" @@ -221,14 +39,14 @@ def decode_selector(selector: int) -> str: def format_style_value(prop_id: int, value: Value) -> str: """Format a style value based on property type.""" try: - if prop_id in _COLOR_PROPS: + if prop_id in COLOR_PROPS: color = value.color r = int(color.red) & 0xFF g = int(color.green) & 0xFF b = int(color.blue) & 0xFF block = f"\033[48;2;{r};{g};{b}m \033[0m" return f"#{r:02x}{g:02x}{b:02x} {block}" - elif prop_id in _POINTER_PROPS: + elif prop_id in POINTER_PROPS: ptr = int(value.ptr) return f"{ptr:#x}" if ptr else "NULL" else: diff --git a/scripts/gdb/lvglgdb/lvgl/misc/lv_style_consts.py b/scripts/gdb/lvglgdb/lvgl/misc/lv_style_consts.py new file mode 100644 index 0000000000..79b986d888 --- /dev/null +++ b/scripts/gdb/lvglgdb/lvgl/misc/lv_style_consts.py @@ -0,0 +1,193 @@ +""" +Auto-generated style constants from LVGL headers. + +Do not edit manually. Regenerate with: + python3 scripts/gen_style_consts.py +""" + +STYLE_PROP_NAMES = { + 1: "WIDTH", + 2: "HEIGHT", + 3: "LENGTH", + 4: "TRANSFORM_WIDTH", + 5: "TRANSFORM_HEIGHT", + 8: "MIN_WIDTH", + 9: "MAX_WIDTH", + 10: "MIN_HEIGHT", + 11: "MAX_HEIGHT", + 12: "TRANSLATE_X", + 13: "TRANSLATE_Y", + 14: "RADIAL_OFFSET", + 16: "X", + 17: "Y", + 18: "ALIGN", + 24: "PAD_TOP", + 25: "PAD_BOTTOM", + 26: "PAD_LEFT", + 27: "PAD_RIGHT", + 28: "PAD_RADIAL", + 29: "PAD_ROW", + 30: "PAD_COLUMN", + 32: "MARGIN_TOP", + 33: "MARGIN_BOTTOM", + 34: "MARGIN_LEFT", + 35: "MARGIN_RIGHT", + 40: "BG_GRAD", + 41: "BG_GRAD_DIR", + 42: "BG_MAIN_OPA", + 43: "BG_GRAD_OPA", + 44: "BG_GRAD_COLOR", + 45: "BG_MAIN_STOP", + 46: "BG_GRAD_STOP", + 48: "BG_IMAGE_SRC", + 49: "BG_IMAGE_OPA", + 50: "BG_IMAGE_RECOLOR_OPA", + 51: "BG_IMAGE_TILED", + 52: "BG_IMAGE_RECOLOR", + 56: "BORDER_WIDTH", + 57: "BORDER_COLOR", + 58: "BORDER_OPA", + 59: "BORDER_POST", + 60: "BORDER_SIDE", + 64: "OUTLINE_WIDTH", + 65: "OUTLINE_COLOR", + 66: "OUTLINE_OPA", + 67: "OUTLINE_PAD", + 72: "BG_OPA", + 73: "BG_COLOR", + 74: "SHADOW_WIDTH", + 75: "LINE_WIDTH", + 76: "ARC_WIDTH", + 77: "TEXT_FONT", + 78: "IMAGE_RECOLOR_OPA", + 80: "IMAGE_OPA", + 81: "SHADOW_OPA", + 82: "LINE_OPA", + 83: "ARC_OPA", + 84: "TEXT_OPA", + 88: "SHADOW_COLOR", + 89: "IMAGE_RECOLOR", + 90: "LINE_COLOR", + 91: "ARC_COLOR", + 92: "TEXT_COLOR", + 96: "ARC_IMAGE_SRC", + 97: "SHADOW_OFFSET_X", + 98: "SHADOW_OFFSET_Y", + 99: "SHADOW_SPREAD", + 100: "LINE_DASH_WIDTH", + 101: "TEXT_ALIGN", + 102: "TEXT_LETTER_SPACE", + 103: "TEXT_LINE_SPACE", + 104: "LINE_DASH_GAP", + 105: "LINE_ROUNDED", + 106: "IMAGE_COLORKEY", + 107: "TEXT_OUTLINE_STROKE_WIDTH", + 108: "TEXT_OUTLINE_STROKE_OPA", + 109: "TEXT_OUTLINE_STROKE_COLOR", + 110: "TEXT_DECOR", + 111: "ARC_ROUNDED", + 112: "OPA", + 113: "OPA_LAYERED", + 114: "COLOR_FILTER_DSC", + 115: "COLOR_FILTER_OPA", + 116: "ANIM", + 117: "ANIM_DURATION", + 118: "TRANSITION", + 120: "RADIUS", + 121: "BITMAP_MASK_SRC", + 122: "BLEND_MODE", + 123: "ROTARY_SENSITIVITY", + 124: "TRANSLATE_RADIAL", + 128: "CLIP_CORNER", + 129: "BASE_DIR", + 130: "RECOLOR", + 131: "RECOLOR_OPA", + 132: "LAYOUT", + 136: "BLUR_RADIUS", + 137: "BLUR_BACKDROP", + 138: "BLUR_QUALITY", + 144: "DROP_SHADOW_RADIUS", + 145: "DROP_SHADOW_OFFSET_X", + 146: "DROP_SHADOW_OFFSET_Y", + 147: "DROP_SHADOW_COLOR", + 148: "DROP_SHADOW_OPA", + 149: "DROP_SHADOW_QUALITY", + 152: "TRANSFORM_SCALE_X", + 153: "TRANSFORM_SCALE_Y", + 154: "TRANSFORM_PIVOT_X", + 155: "TRANSFORM_PIVOT_Y", + 156: "TRANSFORM_ROTATION", + 157: "TRANSFORM_SKEW_X", + 158: "TRANSFORM_SKEW_Y", + 160: "FLEX_FLOW", + 161: "FLEX_MAIN_PLACE", + 162: "FLEX_CROSS_PLACE", + 163: "FLEX_TRACK_PLACE", + 164: "FLEX_GROW", + 165: "GRID_COLUMN_DSC_ARRAY", + 166: "GRID_ROW_DSC_ARRAY", + 168: "GRID_COLUMN_ALIGN", + 169: "GRID_ROW_ALIGN", + 170: "GRID_CELL_COLUMN_POS", + 171: "GRID_CELL_COLUMN_SPAN", + 172: "GRID_CELL_X_ALIGN", + 173: "GRID_CELL_ROW_POS", + 174: "GRID_CELL_ROW_SPAN", + 175: "GRID_CELL_Y_ALIGN", +} + +PART_NAMES = { + 0x00: "MAIN", + 0x01: "SCROLLBAR", + 0x02: "INDICATOR", + 0x03: "KNOB", + 0x04: "SELECTED", + 0x05: "ITEMS", + 0x06: "CURSOR", + 0x08: "CUSTOM_FIRST", + 0x0F: "ANY", +} + +STATE_FLAGS = { + 0x0001: "ALT", + 0x0004: "CHECKED", + 0x0008: "FOCUSED", + 0x0010: "FOCUS_KEY", + 0x0020: "EDITED", + 0x0040: "HOVERED", + 0x0080: "PRESSED", + 0x0100: "SCROLLED", + 0x0200: "DISABLED", + 0x1000: "USER_1", + 0x2000: "USER_2", + 0x4000: "USER_3", + 0x8000: "USER_4", +} + +COLOR_PROPS = { + 44, # BG_GRAD_COLOR + 52, # BG_IMAGE_RECOLOR + 57, # BORDER_COLOR + 65, # OUTLINE_COLOR + 73, # BG_COLOR + 88, # SHADOW_COLOR + 89, # IMAGE_RECOLOR + 90, # LINE_COLOR + 91, # ARC_COLOR + 92, # TEXT_COLOR + 109, # TEXT_OUTLINE_STROKE_COLOR + 130, # RECOLOR + 147, # DROP_SHADOW_COLOR +} + +POINTER_PROPS = { + 40, # BG_GRAD + 48, # BG_IMAGE_SRC + 77, # TEXT_FONT + 96, # ARC_IMAGE_SRC + 106, # IMAGE_COLORKEY + 114, # COLOR_FILTER_DSC + 116, # ANIM + 118, # TRANSITION + 121, # BITMAP_MASK_SRC +} diff --git a/scripts/gdb/scripts/gen_style_consts.py b/scripts/gdb/scripts/gen_style_consts.py new file mode 100644 index 0000000000..5422162697 --- /dev/null +++ b/scripts/gdb/scripts/gen_style_consts.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python3 +""" +Generate style constant tables from LVGL header files. + +Parses lv_style.h, lv_obj_style.h, and lv_style_gen.h to produce +lv_style_consts.py used by the lvglgdb GDB plugin. + +Usage: + python3 scripts/gen_style_consts.py +""" + +import re +from pathlib import Path + +SCRIPT_DIR = Path(__file__).parent +GDB_ROOT = SCRIPT_DIR.parent +LVGL_SRC = GDB_ROOT.parent.parent / "src" +OUTPUT = GDB_ROOT / "lvglgdb" / "lvgl" / "misc" / "lv_style_consts.py" + +STYLE_H = LVGL_SRC / "misc" / "lv_style.h" +OBJ_STYLE_H = LVGL_SRC / "core" / "lv_obj_style.h" +STYLE_GEN_H = LVGL_SRC / "misc" / "lv_style_gen.h" + + +def parse_style_props(path: Path) -> dict[int, str]: + """Parse _lv_style_id_t enum from lv_style.h.""" + text = path.read_text() + + # Extract enum block + m = re.search(r"enum\s+_lv_style_id_t\s*\{(.*?)\}", text, re.DOTALL) + if not m: + raise RuntimeError("Cannot find _lv_style_id_t enum") + + props = {} + current_val = 0 + for line in m.group(1).splitlines(): + line = line.strip().rstrip(",") + if ( + not line + or line.startswith("/*") + or line.startswith("//") + or line.startswith("*") + ): + continue + + # Match: LV_STYLE_XXX = value or LV_STYLE_XXX (auto-increment) + match = re.match(r"(LV_STYLE_\w+)\s*=\s*(0x[\da-fA-F]+|\d+)", line) + if match: + name = match.group(1) + val_str = match.group(2) + current_val = int(val_str, 0) + else: + match = re.match(r"(LV_STYLE_\w+)", line) + if not match: + continue + name = match.group(1) + + # Skip meta entries + if name in ( + "LV_STYLE_PROP_INV", + "LV_STYLE_PROP_ANY", + "LV_STYLE_PROP_CONST", + "LV_STYLE_LAST_BUILT_IN_PROP", + "LV_STYLE_NUM_BUILT_IN_PROPS", + ): + current_val += 1 + continue + + short = name.removeprefix("LV_STYLE_") + props[current_val] = short + current_val += 1 + + return props + + +def parse_parts(path: Path) -> dict[int, str]: + """Parse lv_part_t enum from lv_obj_style.h.""" + text = path.read_text() + m = re.search(r"typedef\s+enum\s*\{(.*?)\}\s*lv_part_t", text, re.DOTALL) + if not m: + raise RuntimeError("Cannot find lv_part_t enum") + + parts = {} + for line in m.group(1).splitlines(): + match = re.match(r"\s*(LV_PART_\w+)\s*=\s*(0x[\da-fA-F]+|\d+)", line) + if not match: + continue + name = match.group(1) + val = int(match.group(2), 0) + short = name.removeprefix("LV_PART_") + # Selector uses bits [23:16], shift down to get the key + parts[val >> 16] = short + + return parts + + +def _parse_int_expr(expr: str) -> int | None: + """Parse a C integer expression: decimal, hex, or '1 << N'.""" + expr = expr.strip() + m = re.match(r"^(0x[\da-fA-F]+|\d+)$", expr) + if m: + return int(m.group(1), 0) + m = re.match(r"^(\d+)\s*<<\s*(\d+)$", expr) + if m: + return int(m.group(1)) << int(m.group(2)) + return None + + +def parse_states(path: Path) -> dict[int, str]: + """Parse lv_state_t enum from lv_obj_style.h.""" + text = path.read_text() + m = re.search(r"typedef\s+enum\s*\{(.*?)\}\s*lv_state_t", text, re.DOTALL) + if not m: + raise RuntimeError("Cannot find lv_state_t enum") + + states = {} + for line in m.group(1).splitlines(): + match = re.match(r"\s*(LV_STATE_\w+)\s*=\s*(.+?)(?:,|/)", line) + if not match: + continue + name = match.group(1) + expr = match.group(2).strip() + + # Skip DEFAULT (0) and ANY (0xFFFF) + if name in ("LV_STATE_DEFAULT", "LV_STATE_ANY"): + continue + + val = _parse_int_expr(expr) + if val is None: + continue + short = name.removeprefix("LV_STATE_") + states[val] = short + + return states + + +def parse_color_props(style_gen_h: Path, prop_map: dict[int, str]) -> set[int]: + """Identify color properties from lv_style_gen.h setter signatures.""" + text = style_gen_h.read_text() + name_to_id = {v: k for k, v in prop_map.items()} + + color_ids = set() + for match in re.finditer( + r"void\s+lv_style_set_(\w+)\s*\([^,]+,\s*lv_color_t", text + ): + prop_name = match.group(1).upper() + if prop_name in name_to_id: + color_ids.add(name_to_id[prop_name]) + + return color_ids + + +def parse_pointer_props(style_gen_h: Path, prop_map: dict[int, str]) -> set[int]: + """Identify pointer properties from lv_style_gen.h setter signatures.""" + text = style_gen_h.read_text() + name_to_id = {v: k for k, v in prop_map.items()} + + ptr_ids = set() + for match in re.finditer( + r"void\s+lv_style_set_(\w+)\s*\([^,]+,\s*(?:const\s+)?(?:void|lv_\w+)\s*\*", + text, + ): + prop_name = match.group(1).upper() + if prop_name in name_to_id: + ptr_ids.add(name_to_id[prop_name]) + + return ptr_ids + + +def generate( + props: dict[int, str], + parts: dict[int, str], + states: dict[int, str], + color_ids: set[int], + pointer_ids: set[int], +) -> str: + """Generate Python source for the constants module.""" + lines = [ + '"""', + "Auto-generated style constants from LVGL headers.", + "", + "Do not edit manually. Regenerate with:", + " python3 scripts/gen_style_consts.py", + '"""', + "", + ] + + # STYLE_PROP_NAMES + lines.append("STYLE_PROP_NAMES = {") + for k in sorted(props): + lines.append(f' {k}: "{props[k]}",') + lines.append("}") + lines.append("") + + # PART_NAMES + lines.append("PART_NAMES = {") + for k in sorted(parts): + lines.append(f' 0x{k:02X}: "{parts[k]}",') + lines.append("}") + lines.append("") + + # STATE_FLAGS + lines.append("STATE_FLAGS = {") + for k in sorted(states): + lines.append(f' 0x{k:04X}: "{states[k]}",') + lines.append("}") + lines.append("") + + # COLOR_PROPS + if color_ids: + lines.append("COLOR_PROPS = {") + for v in sorted(color_ids): + lines.append(f" {v}, # {props.get(v, '?')}") + lines.append("}") + else: + lines.append("COLOR_PROPS = set()") + lines.append("") + + # POINTER_PROPS + if pointer_ids: + lines.append("POINTER_PROPS = {") + for v in sorted(pointer_ids): + lines.append(f" {v}, # {props.get(v, '?')}") + lines.append("}") + else: + lines.append("POINTER_PROPS = set()") + lines.append("") + + return "\n".join(lines) + + +def main(): + props = parse_style_props(STYLE_H) + parts = parse_parts(OBJ_STYLE_H) + states = parse_states(OBJ_STYLE_H) + color_ids = parse_color_props(STYLE_GEN_H, props) + pointer_ids = parse_pointer_props(STYLE_GEN_H, props) + + src = generate(props, parts, states, color_ids, pointer_ids) + OUTPUT.write_text(src) + print( + f"Generated {OUTPUT} ({len(props)} props, {len(parts)} parts, " + f"{len(states)} states, {len(color_ids)} color, {len(pointer_ids)} pointer)" + ) + + +if __name__ == "__main__": + main()