mirror of
https://github.com/lvgl/lvgl.git
synced 2026-05-09 20:27:41 +08:00
chore(gdb): add obj state/scroll/layout fields to snapshot
- Add gen_obj_state_consts.py generator for lv_state_t - Fix parse_bitmask_enum to support parens-optional shift expressions - Add state, scroll, ext_click_pad, ext_draw_size, scrollbar_mode, scroll_snap, scroll_dir, layer_type, name, user_data, layout_inv, w/h_layout, is_deleting, rendered, skip_trans to LVObject.snapshot() - Update dashboard frontend with full detail panel rendering
This commit is contained in:
@@ -46,6 +46,8 @@ from .lvgl import (
|
||||
LVObjClass,
|
||||
OBJ_FLAG_NAMES,
|
||||
decode_obj_flags,
|
||||
OBJ_STATE_NAMES,
|
||||
decode_obj_states,
|
||||
LVSubject,
|
||||
LVObserver,
|
||||
SUBJECT_TYPE_NAMES,
|
||||
@@ -99,6 +101,8 @@ __all__ = [
|
||||
"LVObjClass",
|
||||
"OBJ_FLAG_NAMES",
|
||||
"decode_obj_flags",
|
||||
"OBJ_STATE_NAMES",
|
||||
"decode_obj_states",
|
||||
"LVSubject",
|
||||
"LVObserver",
|
||||
"SUBJECT_TYPE_NAMES",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
--color-blue: var(--blue);
|
||||
--color-sapphire: var(--sapphire);
|
||||
--color-green: var(--green);
|
||||
--color-yellow: var(--yellow);
|
||||
--color-peach: var(--peach);
|
||||
--color-mauve: var(--mauve);
|
||||
--color-teal: var(--teal);
|
||||
@@ -1237,6 +1238,17 @@ ui-topbar .topbar-search::placeholder {
|
||||
color: var(--color-overlay1);
|
||||
border: 1px solid var(--surface0);
|
||||
}
|
||||
.detail-state-active {
|
||||
border-color: var(--color-yellow);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
border-color: color-mix(in oklab, var(--color-yellow) 30%, transparent);
|
||||
}
|
||||
background-color: var(--color-yellow);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-yellow) 10%, transparent);
|
||||
}
|
||||
color: var(--color-yellow);
|
||||
}
|
||||
.scene-controls {
|
||||
margin-bottom: calc(var(--spacing) * 1);
|
||||
display: flex;
|
||||
@@ -2075,7 +2087,7 @@ function renderObjTree(obj, depth = 0) {
|
||||
if (det.className = "obj-node", obj.addr)
|
||||
det.id = "obj-" + obj.addr;
|
||||
let sum = document.createElement("summary");
|
||||
if (sum.style.setProperty("--depth-color", DEPTH_COLORS[depth % DEPTH_COLORS.length]), sum.textContent = obj.class_name || "obj", obj.flags_list?.includes("HIDDEN"))
|
||||
if (sum.style.setProperty("--depth-color", DEPTH_COLORS[depth % DEPTH_COLORS.length]), sum.textContent = (obj.name ? obj.name + " " : "") + (obj.class_name || "obj"), obj.flags_list?.includes("HIDDEN"))
|
||||
sum.textContent += " \uD83D\uDC41\uD83D\uDDE8";
|
||||
if (det.appendChild(sum), obj.addr)
|
||||
objDataMap[obj.addr] = obj, registerHL(obj.addr, det), sum.addEventListener("mouseenter", () => highlightObj(obj.addr)), sum.addEventListener("mouseleave", () => clearHighlight()), sum.addEventListener("click", (e) => {
|
||||
@@ -2131,6 +2143,12 @@ function renderObjDetail(addr, panel) {
|
||||
let wrap = el("div", "detail-flags-wrap");
|
||||
obj.flags_list.forEach((f) => wrap.appendChild(el("span", "detail-flag-badge", f))), flagSec.appendChild(wrap), panel.appendChild(flagSec);
|
||||
}
|
||||
if (obj.state_list?.length) {
|
||||
let stateSec = el("div", "detail-section");
|
||||
stateSec.appendChild(el("div", "detail-section-title", "State"));
|
||||
let wrap = el("div", "detail-flags-wrap");
|
||||
obj.state_list.forEach((s) => wrap.appendChild(el("span", "detail-flag-badge" + (s === "DEFAULT" ? "" : " detail-state-active"), s))), stateSec.appendChild(wrap), panel.appendChild(stateSec);
|
||||
}
|
||||
let refSec = el("div", "detail-section");
|
||||
if (refSec.appendChild(el("div", "detail-section-title", "References")), obj.parent_addr) {
|
||||
let row = el("div", "kv-row");
|
||||
@@ -2140,7 +2158,49 @@ function renderObjDetail(addr, panel) {
|
||||
let row = el("div", "kv-row");
|
||||
row.appendChild(el("span", "kv-label", "group")), row.appendChild(xref(obj.group_addr, "group")), refSec.appendChild(row);
|
||||
}
|
||||
if (refSec.appendChild(kvPair("children", String(obj.child_count || 0))), refSec.appendChild(kvPair("styles", String(obj.style_count || 0))), panel.appendChild(refSec), obj.styles?.length) {
|
||||
if (obj.user_data)
|
||||
refSec.appendChild(kvPair("user_data", obj.user_data));
|
||||
if (obj.name)
|
||||
refSec.appendChild(kvPair("name", obj.name));
|
||||
if (refSec.appendChild(kvPair("children", String(obj.child_count || 0))), refSec.appendChild(kvPair("styles", String(obj.style_count || 0))), panel.appendChild(refSec), obj.scroll || obj.ext_click_pad || obj.ext_draw_size || obj.scrollbar_mode || obj.layer_type || obj.w_layout || obj.h_layout) {
|
||||
let layoutSec = el("div", "detail-section");
|
||||
if (layoutSec.appendChild(el("div", "detail-section-title", "Layout & Scroll")), obj.scroll)
|
||||
layoutSec.appendChild(kvPair("scroll", `${obj.scroll.x}, ${obj.scroll.y}`));
|
||||
if (obj.ext_click_pad)
|
||||
layoutSec.appendChild(kvPair("ext_click_pad", String(obj.ext_click_pad)));
|
||||
if (obj.ext_draw_size)
|
||||
layoutSec.appendChild(kvPair("ext_draw_size", String(obj.ext_draw_size)));
|
||||
if (obj.scrollbar_mode != null)
|
||||
layoutSec.appendChild(kvPair("scrollbar_mode", String(obj.scrollbar_mode)));
|
||||
if (obj.scroll_dir != null)
|
||||
layoutSec.appendChild(kvPair("scroll_dir", String(obj.scroll_dir)));
|
||||
if (obj.scroll_snap_x)
|
||||
layoutSec.appendChild(kvPair("scroll_snap_x", String(obj.scroll_snap_x)));
|
||||
if (obj.scroll_snap_y)
|
||||
layoutSec.appendChild(kvPair("scroll_snap_y", String(obj.scroll_snap_y)));
|
||||
if (obj.layer_type)
|
||||
layoutSec.appendChild(kvPair("layer_type", String(obj.layer_type)));
|
||||
if (obj.w_layout)
|
||||
layoutSec.appendChild(kvPair("w_layout", "true"));
|
||||
if (obj.h_layout)
|
||||
layoutSec.appendChild(kvPair("h_layout", "true"));
|
||||
panel.appendChild(layoutSec);
|
||||
}
|
||||
if (obj.layout_inv || obj.is_deleting || obj.rendered || obj.skip_trans) {
|
||||
let intSec = el("div", "detail-section");
|
||||
intSec.appendChild(el("div", "detail-section-title", "Internal"));
|
||||
let wrap = el("div", "detail-flags-wrap");
|
||||
if (obj.layout_inv)
|
||||
wrap.appendChild(el("span", "detail-flag-badge detail-state-active", "layout_inv"));
|
||||
if (obj.is_deleting)
|
||||
wrap.appendChild(el("span", "detail-flag-badge detail-state-active", "is_deleting"));
|
||||
if (obj.rendered)
|
||||
wrap.appendChild(el("span", "detail-flag-badge", "rendered"));
|
||||
if (obj.skip_trans)
|
||||
wrap.appendChild(el("span", "detail-flag-badge", "skip_trans"));
|
||||
intSec.appendChild(wrap), panel.appendChild(intSec);
|
||||
}
|
||||
if (obj.styles?.length) {
|
||||
let styleSec = el("div", "detail-section");
|
||||
styleSec.appendChild(el("div", "detail-section-title", "Styles (" + obj.styles.length + ")")), obj.styles.forEach((s) => {
|
||||
let card = el("div", "detail-style-card");
|
||||
@@ -3113,7 +3173,7 @@ class UiDashboard extends BaseComponent {
|
||||
customElements.define("ui-dashboard", UiDashboard);
|
||||
|
||||
// src/app.ts
|
||||
document.getElementById("about").innerHTML = `<span>uinspy v${"0.4.6"}</span><span>·</span><span>Built ${"2026-04-01 13:27 GMT+8"}</span><span>·</span><span>${"8f35af9"}</span><span>·</span><span>${"Canvas2D"}</span><span>·</span><a href="https://github.com/W-Mai/uinspy" target="_blank" rel="noopener">GitHub</a><span>·</span><a href="https://lvgl.io" target="_blank" rel="noopener">LVGL</a><span>·</span><span>MIT</span>`;
|
||||
document.getElementById("about").innerHTML = `<span>uinspy v${"0.4.6"}</span><span>·</span><span>Built ${"2026-04-02 03:55 GMT+8"}</span><span>·</span><span>${"be336ae"}</span><span>·</span><span>${"Canvas2D"}</span><span>·</span><a href="https://github.com/W-Mai/uinspy" target="_blank" rel="noopener">GitHub</a><span>·</span><a href="https://lvgl.io" target="_blank" rel="noopener">LVGL</a><span>·</span><span>MIT</span>`;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -10,6 +10,8 @@ from .core import (
|
||||
LVObjClass,
|
||||
OBJ_FLAG_NAMES,
|
||||
decode_obj_flags,
|
||||
OBJ_STATE_NAMES,
|
||||
decode_obj_states,
|
||||
LVSubject,
|
||||
LVObserver,
|
||||
SUBJECT_TYPE_NAMES,
|
||||
@@ -100,6 +102,8 @@ __all__ = [
|
||||
"LVObjClass",
|
||||
"OBJ_FLAG_NAMES",
|
||||
"decode_obj_flags",
|
||||
"OBJ_STATE_NAMES",
|
||||
"decode_obj_states",
|
||||
"LVSubject",
|
||||
"LVObserver",
|
||||
"SUBJECT_TYPE_NAMES",
|
||||
|
||||
@@ -4,6 +4,7 @@ from .lv_indev import LVIndev, INDEV_TYPE_NAMES
|
||||
from .lv_group import LVGroup
|
||||
from .lv_obj_class import LVObjClass
|
||||
from .lv_obj_flag_consts import OBJ_FLAG_NAMES, decode_obj_flags
|
||||
from .lv_obj_state_consts import OBJ_STATE_NAMES, decode_obj_states
|
||||
from .lv_observer import LVSubject, LVObserver
|
||||
from .lv_observer_consts import SUBJECT_TYPE_NAMES
|
||||
|
||||
@@ -19,6 +20,8 @@ __all__ = [
|
||||
"LVObjClass",
|
||||
"OBJ_FLAG_NAMES",
|
||||
"decode_obj_flags",
|
||||
"OBJ_STATE_NAMES",
|
||||
"decode_obj_states",
|
||||
"LVSubject",
|
||||
"LVObserver",
|
||||
"SUBJECT_TYPE_NAMES",
|
||||
|
||||
@@ -101,6 +101,15 @@ class LVObject(Value):
|
||||
from .lv_obj_flag_consts import decode_obj_flags
|
||||
return decode_obj_flags(self.flags_raw)
|
||||
|
||||
@property
|
||||
def state_raw(self) -> int:
|
||||
return int(self.safe_field("state", 0))
|
||||
|
||||
@property
|
||||
def state_list(self) -> list[str]:
|
||||
from .lv_obj_state_consts import decode_obj_states
|
||||
return decode_obj_states(self.state_raw)
|
||||
|
||||
@property
|
||||
def child_cnt(self) -> int:
|
||||
"""Return child count, 0 if corrupted."""
|
||||
@@ -167,8 +176,26 @@ class LVObject(Value):
|
||||
("style_count", lambda s: int(s.style_cnt), 0),
|
||||
("flags", lambda s: s.flags_raw, 0),
|
||||
("flags_list", lambda s: s.flags_list, []),
|
||||
("state", lambda s: s.state_raw, 0),
|
||||
("state_list", lambda s: s.state_list, []),
|
||||
("parent_addr", lambda s: ptr_or_none(s.super_value("parent"))),
|
||||
("group_addr", lambda s: s._get_group_addr()),
|
||||
("user_data", lambda s: ptr_or_none(s.safe_field("user_data"))),
|
||||
("layout_inv", lambda s: bool(s.safe_field("layout_inv", 0, int)), False),
|
||||
("w_layout", lambda s: bool(s.safe_field("w_layout", 0, int)), False),
|
||||
("h_layout", lambda s: bool(s.safe_field("h_layout", 0, int)), False),
|
||||
("is_deleting", lambda s: bool(s.safe_field("is_deleting", 0, int)), False),
|
||||
("rendered", lambda s: bool(s.safe_field("rendered", 0, int)), False),
|
||||
("skip_trans", lambda s: bool(s.safe_field("skip_trans", 0, int)), False),
|
||||
("scroll", lambda s: s._get_scroll()),
|
||||
("ext_click_pad", lambda s: s._get_spec_int("ext_click_pad")),
|
||||
("ext_draw_size", lambda s: s._get_spec_int("ext_draw_size")),
|
||||
("scrollbar_mode", lambda s: s._get_spec_int("scrollbar_mode")),
|
||||
("scroll_snap_x", lambda s: s._get_spec_int("scroll_snap_x")),
|
||||
("scroll_snap_y", lambda s: s._get_spec_int("scroll_snap_y")),
|
||||
("scroll_dir", lambda s: s._get_spec_int("scroll_dir")),
|
||||
("layer_type", lambda s: s._get_spec_int("layer_type")),
|
||||
("name", lambda s: s._get_name()),
|
||||
])
|
||||
if include_children:
|
||||
d["children"] = self._collect_children(include_styles)
|
||||
@@ -211,6 +238,33 @@ class LVObject(Value):
|
||||
addr = int(grp)
|
||||
return hex(addr) if addr else None
|
||||
|
||||
def _get_spec_int(self, field_name):
|
||||
"""Get an int field from spec_attr, or None."""
|
||||
spec = self.spec_attr
|
||||
if not spec or not int(spec):
|
||||
return None
|
||||
return int(spec.safe_field(field_name, 0))
|
||||
|
||||
def _get_scroll(self):
|
||||
"""Get scroll offset from spec_attr, or None."""
|
||||
spec = self.spec_attr
|
||||
if not spec or not int(spec):
|
||||
return None
|
||||
scroll = spec.safe_field("scroll")
|
||||
if scroll is None:
|
||||
return None
|
||||
return {"x": int(scroll["x"]), "y": int(scroll["y"])}
|
||||
|
||||
def _get_name(self):
|
||||
"""Get object name from spec_attr, or None."""
|
||||
spec = self.spec_attr
|
||||
if not spec or not int(spec):
|
||||
return None
|
||||
name = spec.safe_field("name")
|
||||
if name is None or not int(name):
|
||||
return None
|
||||
return name.string()
|
||||
|
||||
|
||||
def dump_obj_info(obj: LVObject):
|
||||
from lvglgdb.lvgl.formatter import print_info
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
"""
|
||||
Auto-generated object state constants from LVGL headers.
|
||||
|
||||
Do not edit manually. Regenerate from the GDB script root with:
|
||||
python3 scripts/generate_all.py
|
||||
"""
|
||||
|
||||
OBJ_STATE_NAMES = {
|
||||
0: "DEFAULT",
|
||||
1: "ALT",
|
||||
4: "CHECKED",
|
||||
8: "FOCUSED",
|
||||
16: "FOCUS_KEY",
|
||||
32: "EDITED",
|
||||
64: "HOVERED",
|
||||
128: "PRESSED",
|
||||
256: "SCROLLED",
|
||||
512: "DISABLED",
|
||||
4096: "USER_1",
|
||||
8192: "USER_2",
|
||||
16384: "USER_3",
|
||||
32768: "USER_4",
|
||||
}
|
||||
|
||||
def decode_obj_states(raw: int) -> list[str]:
|
||||
"""Decode a bitmask of lv_state_t into a list of state names."""
|
||||
if raw == 0:
|
||||
return ["DEFAULT"]
|
||||
return [name for bit, name in OBJ_STATE_NAMES.items() if bit and raw & bit]
|
||||
@@ -105,9 +105,9 @@ def parse_bitmask_enum(path: Path, enum_type: str, prefix: str,
|
||||
):
|
||||
continue
|
||||
|
||||
# Match: NAME = (1u << N) or NAME = (1 << N)
|
||||
# Match: NAME = (1u << N) or NAME = 1 << N (parens optional)
|
||||
match = re.match(
|
||||
rf"({re.escape(prefix)}\w+)\s*=\s*\(1u?\s*<<\s*(\d+)\)", line
|
||||
rf"({re.escape(prefix)}\w+)\s*=\s*\(?1u?\s*<<\s*(\d+)\)?", line
|
||||
)
|
||||
if not match:
|
||||
continue
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate object state constant tables from LVGL headers."""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from enum_parser import parse_bitmask_enum, generate_dict_module
|
||||
|
||||
LVGL_SRC = Path(__file__).parent.parent.parent.parent.parent / "src"
|
||||
OUTPUT = Path(__file__).parent.parent.parent / "lvglgdb" / "lvgl" / "core" / "lv_obj_state_consts.py"
|
||||
|
||||
SKIP = {"LV_STATE_ANY"}
|
||||
|
||||
|
||||
def main():
|
||||
obj_states = parse_bitmask_enum(
|
||||
LVGL_SRC / "core" / "lv_obj_style.h",
|
||||
"lv_state_t",
|
||||
"LV_STATE_",
|
||||
skip=SKIP,
|
||||
)
|
||||
# DEFAULT = 0 is not a bitmask entry, add manually
|
||||
obj_states[0] = "DEFAULT"
|
||||
|
||||
src = generate_dict_module(
|
||||
"object state constants from LVGL headers",
|
||||
{"OBJ_STATE_NAMES": obj_states},
|
||||
)
|
||||
src += (
|
||||
"\ndef decode_obj_states(raw: int) -> list[str]:\n"
|
||||
' """Decode a bitmask of lv_state_t into a list of state names."""\n'
|
||||
" if raw == 0:\n"
|
||||
' return ["DEFAULT"]\n'
|
||||
" return [name for bit, name in OBJ_STATE_NAMES.items() if bit and raw & bit]\n"
|
||||
)
|
||||
OUTPUT.write_text(src)
|
||||
print(f"Generated {OUTPUT.name} ({len(obj_states)} obj states)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user