feat(gdb): extract style constants to auto-generated lv_style_consts.py

This commit is contained in:
Benign X
2026-02-28 14:48:42 +08:00
committed by Neo Xu
parent da24713e9e
commit 2cf818f1ed
4 changed files with 459 additions and 196 deletions

View File

@@ -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

View File

@@ -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:

View File

@@ -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
}

View File

@@ -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()