mirror of
https://github.com/lvgl/lvgl.git
synced 2026-05-10 04:37:55 +08:00
feat(gdb): add style inspection with LVStyle wrapper and info style command
This commit is contained in:
@@ -5,7 +5,15 @@ from .lvgl import (
|
||||
LVDrawBuf,
|
||||
LVList,
|
||||
LVObject,
|
||||
ObjStyle,
|
||||
LVStyle,
|
||||
StyleEntry,
|
||||
dump_style_info,
|
||||
dump_obj_styles,
|
||||
dump_obj_info,
|
||||
style_prop_name,
|
||||
decode_selector,
|
||||
format_style_value,
|
||||
LVCache,
|
||||
LVCacheEntry,
|
||||
LVCacheLRURB,
|
||||
@@ -26,7 +34,15 @@ __all__ = [
|
||||
"LVCache",
|
||||
"LVRedBlackTree",
|
||||
"LVObject",
|
||||
"ObjStyle",
|
||||
"LVStyle",
|
||||
"StyleEntry",
|
||||
"dump_style_info",
|
||||
"dump_obj_styles",
|
||||
"dump_obj_info",
|
||||
"style_prop_name",
|
||||
"decode_selector",
|
||||
"format_style_value",
|
||||
"Value",
|
||||
"LVCacheEntry",
|
||||
"LVCacheLRURB",
|
||||
|
||||
@@ -2,12 +2,11 @@ import argparse
|
||||
import gdb
|
||||
|
||||
from lvglgdb.value import Value
|
||||
from lvglgdb.lvgl import LVObject
|
||||
from lvglgdb.lvgl import dump_style_info
|
||||
from lvglgdb.lvgl import LVStyle, dump_obj_styles
|
||||
|
||||
|
||||
class InfoStyle(gdb.Command):
|
||||
"""dump obj style value for specified obj"""
|
||||
"""Dump style properties. Default: single lv_style_t. Use --obj for object styles."""
|
||||
|
||||
def __init__(self):
|
||||
super(InfoStyle, self).__init__(
|
||||
@@ -15,11 +14,18 @@ class InfoStyle(gdb.Command):
|
||||
)
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
parser = argparse.ArgumentParser(description="Dump lvgl obj local style.")
|
||||
parser = argparse.ArgumentParser(description="Dump lvgl style properties.")
|
||||
parser.add_argument(
|
||||
"obj",
|
||||
"style",
|
||||
type=str,
|
||||
help="obj to show style.",
|
||||
nargs="?",
|
||||
help="lv_style_t variable to inspect.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--obj",
|
||||
type=str,
|
||||
default=None,
|
||||
help="lv_obj_t variable to inspect all styles.",
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -27,14 +33,17 @@ class InfoStyle(gdb.Command):
|
||||
except SystemExit:
|
||||
return
|
||||
|
||||
obj = gdb.parse_and_eval(args.obj)
|
||||
if not obj:
|
||||
print("Invalid obj: ", args.obj)
|
||||
return
|
||||
|
||||
obj = Value(obj)
|
||||
|
||||
# show all styles applied to this obj
|
||||
for style in LVObject(obj).styles:
|
||||
print(" ", end="")
|
||||
dump_style_info(style)
|
||||
if args.obj:
|
||||
obj = gdb.parse_and_eval(args.obj)
|
||||
if not obj:
|
||||
print("Invalid obj:", args.obj)
|
||||
return
|
||||
dump_obj_styles(Value(obj))
|
||||
elif args.style:
|
||||
style = gdb.parse_and_eval(args.style)
|
||||
if not style:
|
||||
print("Invalid style:", args.style)
|
||||
return
|
||||
LVStyle(Value(style)).print_entries()
|
||||
else:
|
||||
print("Usage: info style <style_var> or info style --obj <obj_var>")
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
from .core import LVObject, curr_inst, dump_obj_info
|
||||
from .core import LVObject, ObjStyle, curr_inst, dump_obj_info, dump_obj_styles
|
||||
from .display import LVDisplay
|
||||
from .draw import LVDrawBuf
|
||||
from .misc import (
|
||||
LVList,
|
||||
LVStyle,
|
||||
StyleEntry,
|
||||
dump_style_info,
|
||||
style_prop_name,
|
||||
decode_selector,
|
||||
format_style_value,
|
||||
LVRedBlackTree,
|
||||
dump_rb_info,
|
||||
LVCache,
|
||||
@@ -21,12 +26,19 @@ from .misc import (
|
||||
|
||||
__all__ = [
|
||||
"LVObject",
|
||||
"ObjStyle",
|
||||
"LVDisplay",
|
||||
"LVDrawBuf",
|
||||
"curr_inst",
|
||||
"LVList",
|
||||
"LVStyle",
|
||||
"StyleEntry",
|
||||
"dump_style_info",
|
||||
"dump_obj_styles",
|
||||
"dump_obj_info",
|
||||
"style_prop_name",
|
||||
"decode_selector",
|
||||
"format_style_value",
|
||||
"LVRedBlackTree",
|
||||
"dump_rb_info",
|
||||
"LVCache",
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
from .lv_obj import LVObject, dump_obj_info
|
||||
from .lv_obj import LVObject, ObjStyle, dump_obj_info, dump_obj_styles
|
||||
from .lv_global import curr_inst
|
||||
|
||||
__all__ = [
|
||||
"LVObject",
|
||||
"ObjStyle",
|
||||
"curr_inst",
|
||||
"dump_obj_info",
|
||||
"dump_obj_styles",
|
||||
]
|
||||
|
||||
@@ -1,4 +1,29 @@
|
||||
from lvglgdb.value import Value
|
||||
from lvglgdb.lvgl.misc.lv_style import LVStyle, StyleEntry, decode_selector
|
||||
|
||||
|
||||
class ObjStyle:
|
||||
"""A single style slot from obj->styles[], wrapping LVStyle."""
|
||||
|
||||
def __init__(self, index: int, selector: int, flags: list, style: LVStyle):
|
||||
self.index = index
|
||||
self.selector = selector
|
||||
self.flags = flags
|
||||
self.style = style
|
||||
|
||||
@property
|
||||
def selector_str(self) -> str:
|
||||
return decode_selector(self.selector)
|
||||
|
||||
@property
|
||||
def flags_str(self) -> str:
|
||||
return ",".join(self.flags) if self.flags else "-"
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.style)
|
||||
|
||||
def __len__(self):
|
||||
return len(list(self.style))
|
||||
|
||||
|
||||
class LVObject(Value):
|
||||
@@ -38,31 +63,34 @@ class LVObject(Value):
|
||||
def children(self):
|
||||
if not self.spec_attr:
|
||||
return
|
||||
|
||||
for i in range(self.child_count):
|
||||
child = self.spec_attr.children[i]
|
||||
yield LVObject(child)
|
||||
yield LVObject(self.spec_attr.children[i])
|
||||
|
||||
@property
|
||||
def obj_styles(self):
|
||||
"""Yield ObjStyle for each entry in obj->styles[]."""
|
||||
count = int(self.style_cnt)
|
||||
if count == 0:
|
||||
return
|
||||
styles_arr = self.super_value("styles")
|
||||
for i in range(count):
|
||||
raw = styles_arr[i]
|
||||
flags = []
|
||||
if int(raw.is_local):
|
||||
flags.append("local")
|
||||
if int(raw.is_trans):
|
||||
flags.append("trans")
|
||||
if int(raw.is_theme):
|
||||
flags.append("theme")
|
||||
if int(raw.is_disabled):
|
||||
flags.append("disabled")
|
||||
yield ObjStyle(i, int(raw.selector), flags, LVStyle(raw.style))
|
||||
|
||||
@property
|
||||
def styles(self):
|
||||
LV_STYLE_PROP_INV = 0
|
||||
LV_STYLE_PROP_ANY = 0xFF
|
||||
count = self.style_cnt
|
||||
if count == 0:
|
||||
return
|
||||
|
||||
styles = self.super_value("styles")
|
||||
for i in range(count):
|
||||
style = styles[i].style
|
||||
prop_cnt = style.prop_cnt
|
||||
values_and_props = style.values_and_props.cast(
|
||||
"lv_style_const_prop_t", ptr=True
|
||||
)
|
||||
for j in range(prop_cnt):
|
||||
prop = values_and_props[j].prop
|
||||
if prop == LV_STYLE_PROP_INV or prop == LV_STYLE_PROP_ANY:
|
||||
continue
|
||||
yield values_and_props[j]
|
||||
"""Yield StyleEntry for all style properties across all slots."""
|
||||
for obj_style in self.obj_styles:
|
||||
yield from obj_style
|
||||
|
||||
def get_child(self, index: int):
|
||||
return self.spec_attr.children[index] if self.spec_attr else None
|
||||
@@ -72,3 +100,17 @@ def dump_obj_info(obj: LVObject):
|
||||
clzname = obj.class_name
|
||||
coords = f"{obj.x1},{obj.y1},{obj.x2},{obj.y2}"
|
||||
print(f"{clzname}@{hex(obj)} {coords}")
|
||||
|
||||
|
||||
def dump_obj_styles(obj: Value):
|
||||
"""Print all styles of an object, reusing LVStyle.print_entries()."""
|
||||
lv_obj = LVObject(Value(obj))
|
||||
|
||||
has_any = False
|
||||
for obj_style in lv_obj.obj_styles:
|
||||
has_any = True
|
||||
print(f"[{obj_style.index}] {obj_style.selector_str} {obj_style.flags_str}")
|
||||
obj_style.style.print_entries()
|
||||
|
||||
if not has_any:
|
||||
print("No styles applied.")
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from .lv_ll import LVList
|
||||
from .lv_style import dump_style_info
|
||||
from .lv_style import (
|
||||
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
|
||||
from .lv_cache_entry import LVCacheEntry, dump_cache_entry_info
|
||||
@@ -11,7 +14,12 @@ from .lv_image_header_cache import LVImageHeaderCache
|
||||
|
||||
__all__ = [
|
||||
"LVList",
|
||||
"LVStyle",
|
||||
"StyleEntry",
|
||||
"dump_style_info",
|
||||
"style_prop_name",
|
||||
"decode_selector",
|
||||
"format_style_value",
|
||||
"LVRedBlackTree",
|
||||
"dump_rb_info",
|
||||
"LVCache",
|
||||
|
||||
@@ -1,7 +1,309 @@
|
||||
from dataclasses import dataclass
|
||||
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",
|
||||
}
|
||||
|
||||
def dump_style_info(style: Value):
|
||||
prop = int(style.prop)
|
||||
value = style.value
|
||||
print(f"{prop} = {value}")
|
||||
_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,
|
||||
}
|
||||
|
||||
|
||||
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})")
|
||||
|
||||
|
||||
def decode_selector(selector: int) -> str:
|
||||
"""Decode selector into part + state string."""
|
||||
part_val = (selector >> 16) & 0xFF
|
||||
state_val = selector & 0xFFFF
|
||||
|
||||
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]
|
||||
state_str = "|".join(flags) if flags else f"STATE({state_val:#x})"
|
||||
|
||||
return f"{part_str}|{state_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:
|
||||
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:
|
||||
ptr = int(value.ptr)
|
||||
return f"{ptr:#x}" if ptr else "NULL"
|
||||
else:
|
||||
return str(int(value.num))
|
||||
except gdb.error:
|
||||
return str(value)
|
||||
|
||||
|
||||
@dataclass
|
||||
class StyleEntry:
|
||||
"""A single resolved style property."""
|
||||
|
||||
prop_id: int
|
||||
value: Value
|
||||
|
||||
@property
|
||||
def prop_name(self) -> str:
|
||||
return style_prop_name(self.prop_id)
|
||||
|
||||
@property
|
||||
def value_str(self) -> str:
|
||||
return format_style_value(self.prop_id, self.value)
|
||||
|
||||
|
||||
class LVStyle(Value):
|
||||
"""LVGL style wrapper for lv_style_t."""
|
||||
|
||||
def __init__(self, style: Value):
|
||||
# Ensure we always hold a lv_style_t* pointer, like LVObject does
|
||||
typ = style.type.strip_typedefs()
|
||||
if typ.code != gdb.TYPE_CODE_PTR:
|
||||
style = Value(style.address)
|
||||
super().__init__(style.cast("lv_style_t", ptr=True))
|
||||
|
||||
def __iter__(self) -> Iterator[StyleEntry]:
|
||||
prop_cnt = int(self.prop_cnt)
|
||||
if prop_cnt == 0xFF:
|
||||
# Constant style: lv_style_const_prop_t array terminated by prop==0
|
||||
const_props = self.values_and_props.cast("lv_style_const_prop_t", ptr=True)
|
||||
j = 0
|
||||
while True:
|
||||
prop_id = int(const_props[j].prop)
|
||||
if prop_id == 0 or prop_id == 0xFF:
|
||||
break
|
||||
yield StyleEntry(prop_id, const_props[j].value)
|
||||
j += 1
|
||||
elif prop_cnt > 0:
|
||||
# Normal style: values[prop_cnt] then props[prop_cnt] (uint8_t)
|
||||
base = self.values_and_props
|
||||
value_t = gdb.lookup_type("lv_style_value_t")
|
||||
values_ptr = base.cast(value_t, ptr=True)
|
||||
props_offset = prop_cnt * value_t.sizeof
|
||||
props_ptr = Value(int(base) + props_offset).cast("uint8_t", ptr=True)
|
||||
|
||||
for j in range(prop_cnt):
|
||||
prop_id = int(props_ptr[j])
|
||||
if prop_id == 0:
|
||||
continue
|
||||
yield StyleEntry(prop_id, values_ptr[j])
|
||||
|
||||
def print_entries(self):
|
||||
"""Print style properties as a table."""
|
||||
entries = list(self.__iter__())
|
||||
if not entries:
|
||||
print("Empty style.")
|
||||
return
|
||||
|
||||
table = PrettyTable()
|
||||
table.field_names = ["prop", "value"]
|
||||
table.align = "l"
|
||||
for e in entries:
|
||||
table.add_row([e.prop_name, e.value_str])
|
||||
print(table)
|
||||
|
||||
|
||||
def dump_style_info(entry: StyleEntry):
|
||||
"""Print a single style property."""
|
||||
print(f"{entry.prop_name}({entry.prop_id}) = {entry.value_str}")
|
||||
|
||||
Reference in New Issue
Block a user