mirror of
https://github.com/lvgl/lvgl.git
synced 2026-10-02 10:23:47 +08:00
chore(gdb): fix several edge case (#10477)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
+50
-5
@@ -1,7 +1,7 @@
|
||||
# lvglgdb
|
||||
|
||||
GDB Python extension for inspecting and debugging LVGL internals.
|
||||
Works with live debugging sessions, core dumps, and other
|
||||
GDB Python extension for inspecting and debugging LVGL internals.
|
||||
Works with live debugging sessions, core dumps, and other
|
||||
GDB-compatible targets.
|
||||
|
||||
## Installation
|
||||
@@ -20,8 +20,11 @@ py import lvglgdb
|
||||
|
||||
### Dump Commands
|
||||
|
||||
Where a command takes a widget, style, class or layer, any C expression GDB can
|
||||
evaluate will do: a variable, a member chain, or a plain address.
|
||||
|
||||
```bash
|
||||
dump obj # Dump widget tree
|
||||
dump obj # Dump widget tree (same listing as `info widget`)
|
||||
dump display -f png # Dump display framebuffer as PNG
|
||||
dump cache image # Dump image cache entries
|
||||
dump cache image_header # Dump image header cache entries
|
||||
@@ -32,18 +35,28 @@ dump indev # Dump input devices
|
||||
dump group # Dump focus groups
|
||||
dump image_decoder # Dump registered image decoders
|
||||
dump fs_drv # Dump filesystem drivers
|
||||
dump draw_task <layer_expr> # Dump draw tasks for a layer
|
||||
dump draw_task lv_global->disp_default->layer_head # Draw tasks of a layer
|
||||
dump dashboard # Generate interactive HTML dashboard
|
||||
dump dashboard -o out.html # Save dashboard to file
|
||||
dump widget props lv_keyboard # Which fields a widget class has
|
||||
```
|
||||
|
||||
### Info Commands
|
||||
|
||||
```bash
|
||||
info lvgl_version # LVGL version of the target, and this plugin's version
|
||||
info widget # List every widget, indented by tree depth
|
||||
info widget my_obj # All fields of one widget, incl. its own struct
|
||||
info widget 0x50e000000820 # ...an address works as well as a variable
|
||||
info widget my_obj text # Only the named field(s)
|
||||
info widget ball_1 # Find it by name, anywhere on any screen
|
||||
info widget label3 -p screen_1/button_2 # ...or by name below a parent path
|
||||
info widget screen_1/button_2/label3 # ...or by a full name path
|
||||
info style my_style # Inspect a single lv_style_t
|
||||
info style --obj my_obj # Inspect all styles of an lv_obj_t
|
||||
# sizes as 100%/content, enums and fonts by name
|
||||
info draw_unit # Show draw unit information
|
||||
info obj_class obj->class_p # Show object class hierarchy
|
||||
info obj_class &lv_button_class # Show object class hierarchy
|
||||
info subject &my_subject # Show subject and its observers
|
||||
```
|
||||
|
||||
@@ -53,6 +66,38 @@ info subject &my_subject # Show subject and its observers
|
||||
layer view, widget tree, style inspector, cache stats, animation list, and
|
||||
draw buffer previews (RGB565 / RGB888 / ARGB8888 / XRGB8888).
|
||||
|
||||
## Regenerating the constant tables
|
||||
|
||||
The style, flag, state, event and widget tables are generated from the LVGL
|
||||
headers, not written by hand. After changing an enum or adding a widget field,
|
||||
regenerate them from this directory:
|
||||
|
||||
```bash
|
||||
python3 scripts/generate_all.py
|
||||
```
|
||||
|
||||
The headers are taken from the nearest LVGL checkout above this one. Set
|
||||
`LVGL_ROOT` to read a different one — either a checkout or an installed tree
|
||||
that only has the public headers under `include/lvgl/`:
|
||||
|
||||
```bash
|
||||
LVGL_ROOT=~/lvgl python3 scripts/generate_all.py
|
||||
```
|
||||
|
||||
A generator that cannot find LVGL stops with an error rather than writing an
|
||||
empty table over a good one. Regenerating rewrites every table, so check the
|
||||
full diff: a parsing change that looks harmless for one table can quietly empty
|
||||
another.
|
||||
|
||||
## Tests
|
||||
|
||||
```bash
|
||||
python3 -m unittest discover tests
|
||||
```
|
||||
|
||||
The decoders and the generator helpers are pure Python, so these run without a
|
||||
target and without GDB.
|
||||
|
||||
## Structure
|
||||
|
||||
```mermaid
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import gdb
|
||||
|
||||
from .core import DumpObj, DumpIndev, DumpGroup, InfoObjClass, InfoSubject
|
||||
from .core import (
|
||||
DumpObj,
|
||||
DumpIndev,
|
||||
DumpGroup,
|
||||
InfoObjClass,
|
||||
InfoSubject,
|
||||
InfoWidget,
|
||||
DumpWidget,
|
||||
DumpWidgetProps,
|
||||
)
|
||||
from .display import DumpDisplayBuf
|
||||
from .draw import InfoDrawUnit, DumpDrawTask
|
||||
from .misc import (
|
||||
@@ -12,6 +21,7 @@ from .misc import (
|
||||
DumpTimer,
|
||||
DumpImageDecoder,
|
||||
DumpFsDrv,
|
||||
InfoVersion,
|
||||
)
|
||||
from .dashboard import DumpDashboard
|
||||
from .debugger import Debugger
|
||||
@@ -42,12 +52,16 @@ DumpFsDrv()
|
||||
DumpIndev()
|
||||
DumpGroup()
|
||||
DumpDrawTask()
|
||||
DumpWidget() # prefix for 'dump widget props'
|
||||
DumpWidgetProps()
|
||||
|
||||
# Infos
|
||||
InfoStyle()
|
||||
InfoDrawUnit()
|
||||
InfoObjClass()
|
||||
InfoSubject()
|
||||
InfoVersion()
|
||||
InfoWidget()
|
||||
|
||||
# Drivers
|
||||
Lvglobal()
|
||||
|
||||
@@ -3,6 +3,7 @@ from .lv_indev import DumpIndev
|
||||
from .lv_group import DumpGroup
|
||||
from .lv_obj_class import InfoObjClass
|
||||
from .lv_observer import InfoSubject
|
||||
from .lv_widget import InfoWidget, DumpWidget, DumpWidgetProps
|
||||
|
||||
__all__ = [
|
||||
"DumpObj",
|
||||
@@ -10,4 +11,7 @@ __all__ = [
|
||||
"DumpGroup",
|
||||
"InfoObjClass",
|
||||
"InfoSubject",
|
||||
"InfoWidget",
|
||||
"DumpWidget",
|
||||
"DumpWidgetProps",
|
||||
]
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import argparse
|
||||
|
||||
import gdb
|
||||
|
||||
from lvglgdb.lvgl import curr_inst
|
||||
from lvglgdb.lvgl import LVObject, dump_obj_info
|
||||
from lvglgdb.value import CorruptedError
|
||||
from .lv_widget import print_all_widgets, print_tree, resolve_widget
|
||||
|
||||
|
||||
class DumpObj(gdb.Command):
|
||||
@@ -14,24 +13,6 @@ class DumpObj(gdb.Command):
|
||||
"dump obj", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION
|
||||
)
|
||||
|
||||
def dump_obj(self, obj: LVObject, depth=0, limit=None):
|
||||
if not obj:
|
||||
return
|
||||
|
||||
# dump self
|
||||
print(" " * depth, end="")
|
||||
dump_obj_info(obj)
|
||||
|
||||
if limit is not None and depth >= limit:
|
||||
return
|
||||
|
||||
# dump children
|
||||
try:
|
||||
for child in obj.children:
|
||||
self.dump_obj(child, depth + 1, limit=limit)
|
||||
except CorruptedError:
|
||||
print(" " * (depth + 1) + "(corrupted children)")
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
parser = argparse.ArgumentParser(description="Dump lvgl obj tree.")
|
||||
parser.add_argument(
|
||||
@@ -46,25 +27,20 @@ class DumpObj(gdb.Command):
|
||||
type=str,
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="Optional root obj to dump.",
|
||||
help="Optional root widget: a name, a name path, or an expression.",
|
||||
)
|
||||
try:
|
||||
args = parser.parse_args(gdb.string_to_argv(args))
|
||||
except SystemExit:
|
||||
return
|
||||
|
||||
# The listing is `info widget`'s, so both commands print the same tree
|
||||
# and there is only one renderer to keep current with the widget data.
|
||||
if args.root:
|
||||
root = gdb.parse_and_eval(args.root)
|
||||
root = LVObject(root)
|
||||
self.dump_obj(root, limit=args.level)
|
||||
root, error = resolve_widget(args.root)
|
||||
if root is None:
|
||||
print(f"Error: {error}")
|
||||
return
|
||||
print_tree(root, limit=args.level)
|
||||
else:
|
||||
# dump all displays
|
||||
depth = 0
|
||||
for disp in curr_inst().displays():
|
||||
print(f"Display {hex(disp)}")
|
||||
try:
|
||||
for screen in disp.screens:
|
||||
print(f"{' ' * (depth + 1)}Screen@{hex(screen)}")
|
||||
self.dump_obj(screen, depth=depth + 1, limit=args.level)
|
||||
except CorruptedError:
|
||||
print(f"{' ' * (depth + 1)}(corrupted screens)")
|
||||
print_all_widgets(limit=args.level)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@ from .lv_anim import DumpAnim
|
||||
from .lv_timer import DumpTimer
|
||||
from .lv_image_decoder import DumpImageDecoder
|
||||
from .lv_fs import DumpFsDrv
|
||||
from .lv_version import InfoVersion
|
||||
|
||||
__all__ = [
|
||||
"InfoStyle",
|
||||
@@ -14,4 +15,5 @@ __all__ = [
|
||||
"DumpTimer",
|
||||
"DumpImageDecoder",
|
||||
"DumpFsDrv",
|
||||
"InfoVersion",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
import gdb
|
||||
|
||||
import lvglgdb
|
||||
|
||||
|
||||
def _call(expression):
|
||||
try:
|
||||
return gdb.parse_and_eval(expression)
|
||||
except gdb.error:
|
||||
return None
|
||||
|
||||
|
||||
_VERSION_MACRO = re.compile(
|
||||
r"#define\s+LVGL_VERSION_(MAJOR|MINOR|PATCH|INFO)\s+(\S+)")
|
||||
|
||||
|
||||
def version_from_sources():
|
||||
"""The version read from the lv_version.h the target was built against.
|
||||
|
||||
Neither way of asking the binary is dependable. lv_version_major() is
|
||||
`static inline`, so it is dropped unless something calls it, and the macros
|
||||
need -g3 *and* a stop inside a translation unit that included lv_version.h -
|
||||
lv_timer.c, where a breakpoint on lv_timer_handler lands, does not.
|
||||
|
||||
DWARF does record the path of every source file, so the LVGL tree can be
|
||||
found through any of its symbols and the version header read from disk.
|
||||
Returns (major, minor, patch, info) or None.
|
||||
"""
|
||||
for symbol in ("lv_obj_create", "lv_timer_handler", "lv_init"):
|
||||
try:
|
||||
found = gdb.lookup_global_symbol(symbol)
|
||||
if found is None or found.symtab is None:
|
||||
continue
|
||||
# DWARF records whatever separator the compiler used, which is not
|
||||
# always the host's: a MinGW build writes forward slashes.
|
||||
path = found.symtab.fullname().replace("\\", "/")
|
||||
marker = "/src/"
|
||||
if marker not in path:
|
||||
continue
|
||||
root = path[:path.rindex(marker)]
|
||||
# Since 9.6 the public headers live in include/lvgl/ and the
|
||||
# top-level lv_version.h is a deprecated wrapper that only includes
|
||||
# the real one, so the defines are not in it. Read that one first
|
||||
# and keep the top-level file for the releases that still have it.
|
||||
for header in (os.path.join(root, "include", "lvgl", "lv_version.h"),
|
||||
os.path.join(root, "lv_version.h")):
|
||||
if not os.path.exists(header):
|
||||
continue
|
||||
with open(header) as f:
|
||||
macros = dict(_VERSION_MACRO.findall(f.read()))
|
||||
if not {"MAJOR", "MINOR", "PATCH"} <= set(macros):
|
||||
continue
|
||||
return (int(macros["MAJOR"]), int(macros["MINOR"]),
|
||||
int(macros["PATCH"]), macros.get("INFO", '""').strip('"'))
|
||||
except (gdb.error, OSError, ValueError):
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def lvgl_version():
|
||||
"""The LVGL version of the target, as (major, minor, patch, info).
|
||||
|
||||
lv_version_major() and friends in lvgl.h are the intended way to ask, with
|
||||
the version macros as a second try. Both depend on how the target was
|
||||
built, so lv_version.h itself is the third and most dependable one.
|
||||
Fields that cannot be read come back as None.
|
||||
"""
|
||||
parts = []
|
||||
for name in ("major", "minor", "patch"):
|
||||
value = _call(f"lv_version_{name}()")
|
||||
if value is None:
|
||||
value = _call(f"LVGL_VERSION_{name.upper()}")
|
||||
parts.append(int(value) if value is not None else None)
|
||||
|
||||
info = _call("lv_version_info()") or _call("LVGL_VERSION_INFO")
|
||||
try:
|
||||
info = info.string() if info is not None else None
|
||||
except gdb.error:
|
||||
info = None
|
||||
|
||||
# `info` counts as missing too: a build that hides it loses the "-dev" that
|
||||
# tells a development version from a release.
|
||||
if any(p is None for p in parts) or info is None:
|
||||
from_sources = version_from_sources()
|
||||
if from_sources is not None:
|
||||
return from_sources
|
||||
|
||||
return (*parts, info)
|
||||
|
||||
|
||||
def version_string():
|
||||
""""9.6.0-dev", or as much of it as the target makes readable."""
|
||||
major, minor, patch, info = lvgl_version()
|
||||
if major is None:
|
||||
return ("unknown: lv_version_major() was inlined away, the version "
|
||||
"macros need a -g3 build, and LVGL's sources are not on disk")
|
||||
numbers = ".".join("?" if p is None else str(p) for p in (major, minor, patch))
|
||||
return numbers + (f"-{info}" if info else "")
|
||||
|
||||
|
||||
class InfoVersion(gdb.Command):
|
||||
"""print the LVGL version of the target, and this plugin's version"""
|
||||
|
||||
def __init__(self):
|
||||
super(InfoVersion, self).__init__(
|
||||
"info lvgl_version", gdb.COMMAND_USER, gdb.COMPLETE_NONE
|
||||
)
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
print(f"LVGL: {version_string()}")
|
||||
print(f"lvglgdb: {lvglgdb.__version__}")
|
||||
@@ -31,7 +31,11 @@ class LVGL:
|
||||
|
||||
def displays(self) -> "Iterator[LVDisplay]":
|
||||
ll = self.lv_global.disp_ll
|
||||
if not ll:
|
||||
# disp_ll is an inline lv_ll_t, not a pointer. Some GDB builds cannot
|
||||
# convert an aggregate to a boolean, and Value.__bool__ answers False
|
||||
# when that conversion fails - which would report no displays at all.
|
||||
# The list is empty exactly when it has no head.
|
||||
if not ll.head:
|
||||
return
|
||||
|
||||
from ..display.lv_display import LVDisplay
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import gdb
|
||||
|
||||
from lvglgdb.value import Value, ValueInput
|
||||
from ..misc.lv_ll import LVList
|
||||
|
||||
@@ -59,10 +61,23 @@ class LVGroup(Value):
|
||||
def obj_count(self) -> int:
|
||||
return LVList(self.obj_ll, "lv_obj_t").len
|
||||
|
||||
def _members(self):
|
||||
"""The objects in the group.
|
||||
|
||||
lv_group_init() sizes obj_ll for `lv_obj_t *`, so each node holds a
|
||||
pointer to the object rather than the object itself. Reading the node as
|
||||
an lv_obj_t would report the list's own memory as a widget.
|
||||
"""
|
||||
obj_ptr_t = gdb.lookup_type("lv_obj_t").pointer()
|
||||
for node in LVList(self.obj_ll):
|
||||
# The node's payload is the pointer, so read through it the same way
|
||||
# LVList reads its own next/prev links.
|
||||
yield Value(int(node)).cast(obj_ptr_t, ptr=True).dereference()
|
||||
|
||||
def __iter__(self):
|
||||
from .lv_obj import LVObject
|
||||
|
||||
for obj_ptr in LVList(self.obj_ll, "lv_obj_t"):
|
||||
for obj_ptr in self._members():
|
||||
yield LVObject(obj_ptr)
|
||||
|
||||
def snapshot(self):
|
||||
@@ -80,7 +95,7 @@ class LVGroup(Value):
|
||||
focused_addr = None
|
||||
|
||||
member_addrs = []
|
||||
for obj_ptr in LVList(self.obj_ll, "lv_obj_t"):
|
||||
for obj_ptr in self._members():
|
||||
addr = int(obj_ptr)
|
||||
if addr:
|
||||
member_addrs.append(hex(addr))
|
||||
|
||||
@@ -91,9 +91,49 @@ class LVObject(Value):
|
||||
|
||||
@property
|
||||
def flags_raw(self) -> int:
|
||||
"""Return raw flags bitmask, 0 if corrupted."""
|
||||
raw = self.safe_field("flags", 0)
|
||||
return int(raw)
|
||||
"""Return raw flags bitmask, 0 if corrupted.
|
||||
|
||||
Up to LVGL 9.5 this was a single `flags` bitmask. Since 9.6 each flag
|
||||
is its own bitfield named after it, so the mask is rebuilt from them.
|
||||
Without this every object reports no flags at all, because safe_field()
|
||||
cannot tell a missing field from a clear one.
|
||||
"""
|
||||
from .lv_obj_flag_consts import OBJ_FLAG_NAMES
|
||||
|
||||
raw = self.safe_field("flags")
|
||||
if raw is not None:
|
||||
return int(raw)
|
||||
|
||||
value = 0
|
||||
for bit, name in OBJ_FLAG_NAMES.items():
|
||||
# Composite names (SCROLL_CHAIN) have no field of their own.
|
||||
field = self.safe_field(name.lower())
|
||||
if field is not None and int(field):
|
||||
value |= bit
|
||||
return value | self._user_flags_mask()
|
||||
|
||||
def _user_flags_mask(self) -> int:
|
||||
"""The eight flags 9.6 keeps in spec_attr->user_flags, as a flag mask.
|
||||
|
||||
These have no bitfield of their own, so the rebuild above misses all of
|
||||
them - and LAYOUT_1 is LV_OBJ_FLAG_FLEX_IN_NEW_TRACK, so losing it costs
|
||||
a flex layout its wrapping.
|
||||
"""
|
||||
from .lv_obj_flag_consts import OBJ_FLAG_NAMES
|
||||
|
||||
raw = self._get_spec_int("user_flags")
|
||||
if not raw:
|
||||
return 0
|
||||
|
||||
# The order lv_obj_add_flag() writes them in, from bit 0 up.
|
||||
names = ("LAYOUT_1", "LAYOUT_2", "WIDGET_1", "WIDGET_2",
|
||||
"USER_1", "USER_2", "USER_3", "USER_4")
|
||||
bit_of = {name: bit for bit, name in OBJ_FLAG_NAMES.items()}
|
||||
value = 0
|
||||
for index, name in enumerate(names):
|
||||
if raw & (1 << index) and name in bit_of:
|
||||
value |= bit_of[name]
|
||||
return value
|
||||
|
||||
@property
|
||||
def flags_list(self) -> list[str]:
|
||||
|
||||
@@ -37,7 +37,7 @@ def safe_collect(
|
||||
def collect_xxx(): ...
|
||||
|
||||
In decorator mode, exceptions cause a gdb warning and return [].
|
||||
In iteration mode, per-item exceptions are skipped silently.
|
||||
In iteration mode, a failing item is warned about and left out.
|
||||
"""
|
||||
if isinstance(items_or_label, str):
|
||||
label = items_or_label
|
||||
@@ -60,8 +60,11 @@ def safe_collect(
|
||||
for item in items_or_label:
|
||||
try:
|
||||
result.append(transform(item))
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
# Dropping an object drops everything below it too, so say so
|
||||
# rather than letting a whole subtree vanish from a dump.
|
||||
gdb.write(f"Warning: skipped an item: {type(e).__name__}: {e}\n")
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
gdb.write(f"Warning: stopped collecting early: {type(e).__name__}: {e}\n")
|
||||
return result
|
||||
|
||||
@@ -1,12 +1,40 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
import gdb
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
from lvglgdb.value import Value, ValueInput
|
||||
|
||||
# numpy and Pillow are needed to turn pixels into an image, and nothing else in
|
||||
# the plugin needs them. Importing them here would make every command - and
|
||||
# `import lvglgdb` itself - fail on a GDB whose Python does not have them, so
|
||||
# they are imported where they are used. `from __future__ import annotations`
|
||||
# above keeps the type hints from needing them at definition time.
|
||||
np = None
|
||||
Image = None
|
||||
|
||||
|
||||
def _require_imaging():
|
||||
"""Import numpy and Pillow, with a message that says where they must go."""
|
||||
global np, Image
|
||||
if np is not None and Image is not None:
|
||||
return
|
||||
try:
|
||||
import numpy
|
||||
from PIL import Image as pil_image
|
||||
except ImportError as e:
|
||||
import sys
|
||||
|
||||
raise gdb.GdbError(
|
||||
f"{e.name} is needed to read images, and has to be installed for "
|
||||
f"the Python that GDB embeds "
|
||||
f"({sys.version_info.major}.{sys.version_info.minor}), not for the "
|
||||
f"one on your PATH. On Debian and Ubuntu: sudo apt install "
|
||||
f"python3-{'numpy' if e.name == 'numpy' else 'pil'}"
|
||||
) from e
|
||||
np, Image = numpy, pil_image
|
||||
|
||||
|
||||
class LVDrawBuf(Value):
|
||||
"""LVGL draw buffer"""
|
||||
@@ -84,13 +112,20 @@ class LVDrawBuf(Value):
|
||||
Returns:
|
||||
PIL.Image or None on failure.
|
||||
"""
|
||||
_require_imaging()
|
||||
header = self.super_value("header")
|
||||
stride = int(header["stride"])
|
||||
height = int(header["h"])
|
||||
cf_info = self.color_format_info()
|
||||
data_ptr = self.super_value("data")
|
||||
data_size = int(self.super_value("data_size"))
|
||||
width = (stride * 8) // cf_info["bpp"] if cf_info["bpp"] else 0
|
||||
# The width is in the header. Deriving it from the stride instead turns
|
||||
# any row padding into extra pixels, which shears the whole image.
|
||||
width = int(header["w"])
|
||||
bpp = cf_info["bpp"]
|
||||
row_bytes = (width * bpp + 7) // 8 if bpp else 0
|
||||
if not stride:
|
||||
stride = row_bytes
|
||||
expected_data_size = stride * height
|
||||
|
||||
if not data_ptr or width <= 0 or height <= 0:
|
||||
@@ -115,6 +150,13 @@ class LVDrawBuf(Value):
|
||||
.read_memory(int(data_ptr), expected_data_size)
|
||||
.tobytes()
|
||||
)
|
||||
if stride > row_bytes:
|
||||
# _convert_to_image expects rows packed back to back, so drop the
|
||||
# padding at the end of each row.
|
||||
pixel_data = b"".join(
|
||||
pixel_data[y * stride:y * stride + row_bytes]
|
||||
for y in range(height)
|
||||
)
|
||||
return self._convert_to_image(pixel_data, width, height, cf_info["value"])
|
||||
|
||||
def data_dump(self, filename: str, format: str = None) -> bool:
|
||||
@@ -185,6 +227,7 @@ class LVDrawBuf(Value):
|
||||
Returns:
|
||||
PIL.Image or None if conversion fails
|
||||
"""
|
||||
_require_imaging()
|
||||
try:
|
||||
if color_format == self._color_formats["RGB565"]:
|
||||
# Convert RGB565 to RGB888
|
||||
|
||||
@@ -5,6 +5,7 @@ from .lv_style import (
|
||||
style_prop_name,
|
||||
decode_selector,
|
||||
format_style_value,
|
||||
read_symbol,
|
||||
)
|
||||
from .lv_rb import LVRedBlackTree
|
||||
from .lv_cache import LVCache
|
||||
@@ -26,7 +27,7 @@ from .lv_anim import LVAnim
|
||||
from .lv_timer import LVTimer
|
||||
from .lv_image_decoder import LVImageDecoder
|
||||
from .lv_fs import LVFsDrv
|
||||
from .lv_utils import format_coord
|
||||
from .lv_utils import format_coord, format_enum
|
||||
|
||||
__all__ = [
|
||||
"LVList",
|
||||
@@ -55,4 +56,6 @@ __all__ = [
|
||||
"LVImageDecoder",
|
||||
"LVFsDrv",
|
||||
"format_coord",
|
||||
"format_enum",
|
||||
"read_symbol",
|
||||
]
|
||||
|
||||
@@ -68,7 +68,12 @@ class LVCache(Value):
|
||||
|
||||
def __iter__(self):
|
||||
"""Create appropriate iterator based on cache class"""
|
||||
return create_cache_iterator(self)
|
||||
iterator = create_cache_iterator(self)
|
||||
if iterator is None:
|
||||
# Returning None here would make `for entry in cache` fail with
|
||||
# TypeError about NoneType, which says nothing about the cache.
|
||||
raise gdb.GdbError(f"unsupported cache type: {self.name}")
|
||||
return iterator
|
||||
|
||||
def items(self):
|
||||
"""Get all cache entries as a list"""
|
||||
@@ -81,9 +86,11 @@ class LVCache(Value):
|
||||
"""Run sanity check and print results as a table"""
|
||||
from lvglgdb.lvgl.formatter import print_table
|
||||
|
||||
iterator = iter(self)
|
||||
if iterator is None:
|
||||
errors = [f"unsupported cache type: {self.name}"]
|
||||
try:
|
||||
iterator = iter(self)
|
||||
except gdb.GdbError as unsupported:
|
||||
iterator = ()
|
||||
errors = [str(unsupported)]
|
||||
else:
|
||||
errors = iterator.sanity_check(entry_checker)
|
||||
|
||||
|
||||
@@ -30,3 +30,8 @@ def create_cache_iterator(cache) -> LVCacheIteratorBase | None:
|
||||
|
||||
except Exception:
|
||||
raise TypeError(f"Unsupported cache type {cache}")
|
||||
|
||||
# An unknown or null class: None is the documented answer, and callers check
|
||||
# for it. LVCache.__iter__ turns it into an error that names the cache, so
|
||||
# `for entry in cache` cannot fail with a bare TypeError about NoneType.
|
||||
return None
|
||||
|
||||
@@ -30,7 +30,13 @@ class LVCacheLRURBIterator(LVCacheIteratorBase):
|
||||
"""
|
||||
for ll_node in LVList(lru_cache.ll):
|
||||
ll_addr = int(ll_node)
|
||||
# The node holds a pointer to the rb node, so the cast is to
|
||||
# lv_rb_node_t ** and the pointer has to be read through before any
|
||||
# field of it can be.
|
||||
rb_node = Value(ll_node).cast(rb_node_pp_t)
|
||||
if not rb_node:
|
||||
continue
|
||||
rb_node = rb_node.dereference()
|
||||
if not rb_node:
|
||||
continue
|
||||
data = rb_node.data
|
||||
|
||||
@@ -9,8 +9,25 @@ from .lv_style_consts import (
|
||||
STATE_FLAGS,
|
||||
COLOR_PROPS,
|
||||
POINTER_PROPS,
|
||||
SRC_PROPS,
|
||||
COORD_PROPS,
|
||||
BOOL_PROPS,
|
||||
ENUM_PROP_VALUES,
|
||||
COORD_TYPE_SHIFT,
|
||||
)
|
||||
|
||||
_COORD_TYPE_MASK = 3 << COORD_TYPE_SHIFT
|
||||
_COORD_TYPE_SPEC = 1 << COORD_TYPE_SHIFT
|
||||
_COORD_MAX = (1 << COORD_TYPE_SHIFT) - 1
|
||||
_PCT_STORED_MAX = _COORD_MAX - 1
|
||||
_PCT_POS_MAX = _PCT_STORED_MAX // 2
|
||||
|
||||
# lv_image_src_get_type(): the first byte of an image source says what it is.
|
||||
_IMAGE_HEADER_MAGIC = 0x19
|
||||
_IMAGE_HEADER_LEGACY = 0x00
|
||||
|
||||
_symbol_cache: dict[int, "str | None"] = {}
|
||||
|
||||
|
||||
def style_prop_name(prop_id: int) -> str:
|
||||
"""Resolve style property ID to human-readable name."""
|
||||
@@ -35,29 +52,104 @@ def decode_selector(selector: int) -> str:
|
||||
return f"{part_str}|{state_str}"
|
||||
|
||||
|
||||
def format_style_value(prop_id: int, value: Value) -> str:
|
||||
"""Format a style value based on property type (with ANSI color block)."""
|
||||
def decode_coord(raw: int) -> str:
|
||||
"""Render an int32 coordinate the way the source wrote it.
|
||||
|
||||
LV_PCT() and LV_SIZE_CONTENT survive in the stored value as a type tag in
|
||||
the top bits, so a percentage never has to be inferred from geometry.
|
||||
"""
|
||||
bits = raw & 0xFFFFFFFF
|
||||
# Only LV_COORD_TYPE_SPEC carries an encoding. LV_COORD_TYPE_PX_NEG is not a
|
||||
# second one: there is no LV_COORD_SET_PX_NEG, and in two's complement every
|
||||
# negative int32 already has those top bits, so a "PX_NEG" value is just a
|
||||
# negative number and prints as itself.
|
||||
if bits & _COORD_TYPE_MASK != _COORD_TYPE_SPEC:
|
||||
return str(raw)
|
||||
plain = bits & ~_COORD_TYPE_MASK
|
||||
if plain == _COORD_MAX:
|
||||
return "content"
|
||||
# Not unreachable: _COORD_TYPE_MASK covers bits 29-30 only, so a negative
|
||||
# raw keeps bit 31 in `plain`. LVGL's LV_COORD_IS_PCT() guards the same way.
|
||||
if plain > _PCT_STORED_MAX:
|
||||
return str(raw)
|
||||
pct = plain if plain <= _PCT_POS_MAX else _PCT_POS_MAX - plain
|
||||
return f"{pct}%"
|
||||
|
||||
|
||||
def decode_enum(prop_id: int, raw: int) -> str:
|
||||
"""Name an enum value, decomposing bitmasks such as BORDER_SIDE.
|
||||
|
||||
Only single-bit members are combined, and only when they account for every
|
||||
set bit. Otherwise a sequential enum like FLEX_FLOW would render an
|
||||
out-of-range value as a meaningless list of its low members.
|
||||
"""
|
||||
names = ENUM_PROP_VALUES[prop_id]
|
||||
if raw in names:
|
||||
return names[raw]
|
||||
parts, covered = [], 0
|
||||
for bit in sorted(b for b in names if b and not b & (b - 1)):
|
||||
if raw & bit:
|
||||
parts.append(names[bit])
|
||||
covered |= bit
|
||||
return "|".join(parts) if parts and covered == raw else str(raw)
|
||||
|
||||
|
||||
def read_symbol(addr: int) -> "str | None":
|
||||
"""Resolve an address to its C symbol name, or None."""
|
||||
if addr not in _symbol_cache:
|
||||
try:
|
||||
out = gdb.execute(f"info symbol {addr:#x}", to_string=True).strip()
|
||||
except gdb.error:
|
||||
out = ""
|
||||
name = out.split(" in section ")[0] if " in section " in out else None
|
||||
_symbol_cache[addr] = name
|
||||
return _symbol_cache[addr]
|
||||
|
||||
|
||||
def _forget_symbols(_event=None):
|
||||
"""Addresses move when the program is re-run, so the cache must not outlive it."""
|
||||
_symbol_cache.clear()
|
||||
|
||||
|
||||
try:
|
||||
gdb.events.exited.connect(_forget_symbols)
|
||||
gdb.events.new_objfile.connect(_forget_symbols)
|
||||
except AttributeError:
|
||||
pass # older GDB, or imported outside a debug session
|
||||
|
||||
|
||||
def _read_image_src(addr: int) -> "str | None":
|
||||
"""Read an image source pointer the way lv_image_src_get_type() does."""
|
||||
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 CorruptedError:
|
||||
return str(value)
|
||||
# bytes(): GDB returns a memoryview whose element type differs
|
||||
# between versions - b'\xf3' on some, 243 on others.
|
||||
first = bytes(gdb.selected_inferior().read_memory(addr, 1))[0]
|
||||
if first == _IMAGE_HEADER_MAGIC or first == _IMAGE_HEADER_LEGACY:
|
||||
return None # an lv_image_dsc_t, so the symbol name is the useful part
|
||||
return gdb.Value(addr).cast(gdb.lookup_type("char").pointer()).string()
|
||||
except (gdb.error, gdb.MemoryError, UnicodeDecodeError):
|
||||
return None
|
||||
|
||||
|
||||
def _format_pointer(prop_id: int, addr: int) -> str:
|
||||
"""Name a pointer property: its symbol, the string it points at, or its address."""
|
||||
if not addr:
|
||||
return "NULL"
|
||||
symbol = read_symbol(addr)
|
||||
if symbol:
|
||||
return symbol
|
||||
if prop_id in SRC_PROPS:
|
||||
text = _read_image_src(addr)
|
||||
if text:
|
||||
return text
|
||||
return f"{addr:#x}"
|
||||
|
||||
|
||||
def _style_value_data(prop_id: int, value: Value) -> dict:
|
||||
"""Extract style value as pure data dict (no ANSI codes).
|
||||
|
||||
Returns dict with 'value_str' and optional 'color_rgb'.
|
||||
Returns dict with 'value_str', optional 'color_rgb' and, for pointers,
|
||||
'ptr' so the raw address is never lost behind a resolved name.
|
||||
"""
|
||||
try:
|
||||
if prop_id in COLOR_PROPS:
|
||||
@@ -71,13 +163,32 @@ def _style_value_data(prop_id: int, value: Value) -> dict:
|
||||
}
|
||||
elif prop_id in POINTER_PROPS:
|
||||
ptr = int(value.ptr)
|
||||
return {"value_str": f"{ptr:#x}" if ptr else "NULL"}
|
||||
data = {"value_str": _format_pointer(prop_id, ptr)}
|
||||
if ptr:
|
||||
data["ptr"] = f"{ptr:#x}"
|
||||
return data
|
||||
elif prop_id in ENUM_PROP_VALUES:
|
||||
return {"value_str": decode_enum(prop_id, int(value.num))}
|
||||
elif prop_id in BOOL_PROPS:
|
||||
return {"value_str": "true" if int(value.num) else "false"}
|
||||
elif prop_id in COORD_PROPS:
|
||||
return {"value_str": decode_coord(int(value.num))}
|
||||
else:
|
||||
return {"value_str": str(int(value.num))}
|
||||
except CorruptedError:
|
||||
return {"value_str": str(value)}
|
||||
|
||||
|
||||
def format_style_value(prop_id: int, value: Value) -> str:
|
||||
"""Format a style value based on property type (with ANSI color block)."""
|
||||
data = _style_value_data(prop_id, value)
|
||||
rgb = data.get("color_rgb")
|
||||
if rgb:
|
||||
block = f"\033[48;2;{rgb['r']};{rgb['g']};{rgb['b']}m \033[0m"
|
||||
return f"{data['value_str']} {block}"
|
||||
return data["value_str"]
|
||||
|
||||
|
||||
@dataclass
|
||||
class StyleEntry:
|
||||
"""A single resolved style property."""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -96,7 +96,69 @@ def format_coord(val):
|
||||
if plain <= _PCT_POS_MAX:
|
||||
return f"{plain}%"
|
||||
return f"{_PCT_POS_MAX - plain}%"
|
||||
if coord_type == _COORD_TYPE_PX_NEG:
|
||||
plain = val & ~_COORD_TYPE_MASK
|
||||
return str(-plain) if plain else "0"
|
||||
# LV_COORD_TYPE_PX_NEG needs no branch of its own. There is no
|
||||
# LV_COORD_SET_PX_NEG: the tag exists so LV_COORD_IS_PX() accepts negatives,
|
||||
# and in two's complement every negative int32 already has those top bits,
|
||||
# so such a value is an ordinary negative pixel count. Taking
|
||||
# LV_COORD_PLAIN() of it and negating turned -5 into -536870907.
|
||||
return str(val)
|
||||
|
||||
|
||||
_enum_cache = {}
|
||||
|
||||
|
||||
def _enum_members(type_name):
|
||||
"""Members of a C enum as {value: name}, or None if the target has no such type."""
|
||||
if type_name not in _enum_cache:
|
||||
try:
|
||||
members = {
|
||||
int(f.enumval): f.name for f in gdb.lookup_type(type_name).fields()
|
||||
}
|
||||
except (gdb.error, AttributeError, TypeError):
|
||||
members = None
|
||||
_enum_cache[type_name] = members
|
||||
return _enum_cache[type_name]
|
||||
|
||||
|
||||
def format_enum(type_name, value, prefix="", bitmask=False):
|
||||
"""Name a value of a C enum, reading the names from the target's debug info.
|
||||
|
||||
The fields holding these values are plain unsigned bitfields, so GDB prints
|
||||
them as bare numbers and the enum has to be looked up by name. Reading it
|
||||
from the target rather than from a generated table means the names always
|
||||
match the binary in front of you - and when the enum was optimised out,
|
||||
the raw number is still printed.
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
value = int(value)
|
||||
members = _enum_members(type_name)
|
||||
if not members:
|
||||
return str(value)
|
||||
|
||||
def _short(name):
|
||||
return name[len(prefix):] if prefix and name.startswith(prefix) else name
|
||||
|
||||
if value in members:
|
||||
return _short(members[value])
|
||||
if not bitmask:
|
||||
return str(value)
|
||||
|
||||
parts, covered = [], 0
|
||||
for bit in sorted(b for b in members if b and not b & (b - 1)):
|
||||
if value & bit:
|
||||
parts.append(_short(members[bit]))
|
||||
covered |= bit
|
||||
return "|".join(parts) if parts and covered == value else str(value)
|
||||
|
||||
|
||||
def _forget_enums(_event=None):
|
||||
"""Enum layouts belong to the objfile they were read from."""
|
||||
_enum_cache.clear()
|
||||
|
||||
|
||||
try:
|
||||
gdb.events.exited.connect(_forget_enums)
|
||||
gdb.events.new_objfile.connect(_forget_enums)
|
||||
except AttributeError:
|
||||
pass # older GDB, or imported outside a debug session
|
||||
|
||||
@@ -57,12 +57,29 @@ def safe_point(obj, field_name):
|
||||
|
||||
|
||||
def safe_wrapper(obj, field_name, module_path, class_name):
|
||||
"""Read a struct field using its known Value wrapper, return snapshot dict."""
|
||||
"""Read a struct field using its known Value wrapper, return snapshot dict.
|
||||
|
||||
Not every wrapper has a snapshot(); LVArray does not. Value forwards an
|
||||
unknown attribute to gdb.Value.__getitem__, so asking for one raises
|
||||
gdb.error rather than AttributeError, and the caller would otherwise lose
|
||||
the whole widget - and its subtree - over a single field.
|
||||
"""
|
||||
import gdb
|
||||
|
||||
val = obj.safe_field(field_name)
|
||||
if val is None or not getattr(val, 'is_ok', True):
|
||||
return None
|
||||
import importlib
|
||||
mod = importlib.import_module(module_path)
|
||||
cls = getattr(mod, class_name)
|
||||
wrapper = cls(val)
|
||||
return wrapper.snapshot().as_dict()
|
||||
try:
|
||||
mod = importlib.import_module(module_path)
|
||||
cls = getattr(mod, class_name)
|
||||
wrapper = cls(val)
|
||||
if getattr(type(wrapper), "snapshot", None) is None:
|
||||
# An embedded struct - lv_scale_t.needles - is not convertible to an
|
||||
# int, so the address has to come from the field itself.
|
||||
if val.type.strip_typedefs().code != gdb.TYPE_CODE_PTR:
|
||||
val = val.address
|
||||
return {"addr": hex(int(val)), "type": class_name}
|
||||
return wrapper.snapshot().as_dict()
|
||||
except (gdb.error, gdb.MemoryError, ImportError, AttributeError, TypeError):
|
||||
return None
|
||||
|
||||
@@ -13,19 +13,20 @@ class LV3dtexture(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_3dtexture_t", ptr=True) or self
|
||||
self._wv_lv_3dtexture_t = self.cast("lv_3dtexture_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_3dtexture_t
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return int(self._wv.safe_field("id", 0))
|
||||
return int(self._wv_lv_3dtexture_t.safe_field("id", 0))
|
||||
|
||||
@property
|
||||
def h_flip(self):
|
||||
return int(self._wv.safe_field("h_flip", 0))
|
||||
return int(self._wv_lv_3dtexture_t.safe_field("h_flip", 0))
|
||||
|
||||
@property
|
||||
def v_flip(self):
|
||||
return int(self._wv.safe_field("v_flip", 0))
|
||||
return int(self._wv_lv_3dtexture_t.safe_field("v_flip", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,19 +14,20 @@ class LVAnimimg(LVImage):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_animimg_t", ptr=True) or self
|
||||
self._wv_lv_animimg_t = self.cast("lv_animimg_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_animimg_t
|
||||
|
||||
@property
|
||||
def anim(self):
|
||||
return safe_wrapper(self._wv, "anim", "lvglgdb.lvgl.misc.lv_anim", "LVAnim")
|
||||
return safe_wrapper(self._wv_lv_animimg_t, "anim", "lvglgdb.lvgl.misc.lv_anim", "LVAnim")
|
||||
|
||||
@property
|
||||
def dsc(self):
|
||||
return ptr_or_none(self._wv.safe_field("dsc"))
|
||||
return ptr_or_none(self._wv_lv_animimg_t.safe_field("dsc"))
|
||||
|
||||
@property
|
||||
def pic_count(self):
|
||||
return int(self._wv.safe_field("pic_count", 0))
|
||||
return int(self._wv_lv_animimg_t.safe_field("pic_count", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -13,80 +13,81 @@ class LVArc(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_arc_t", ptr=True) or self
|
||||
self._wv_lv_arc_t = self.cast("lv_arc_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_arc_t
|
||||
|
||||
@property
|
||||
def rotation(self):
|
||||
return int(self._wv.safe_field("rotation", 0))
|
||||
return int(self._wv_lv_arc_t.safe_field("rotation", 0))
|
||||
|
||||
@property
|
||||
def indic_angle_start(self):
|
||||
return int(self._wv.safe_field("indic_angle_start", 0))
|
||||
return int(self._wv_lv_arc_t.safe_field("indic_angle_start", 0))
|
||||
|
||||
@property
|
||||
def indic_angle_end(self):
|
||||
return int(self._wv.safe_field("indic_angle_end", 0))
|
||||
return int(self._wv_lv_arc_t.safe_field("indic_angle_end", 0))
|
||||
|
||||
@property
|
||||
def bg_angle_start(self):
|
||||
return int(self._wv.safe_field("bg_angle_start", 0))
|
||||
return int(self._wv_lv_arc_t.safe_field("bg_angle_start", 0))
|
||||
|
||||
@property
|
||||
def bg_angle_end(self):
|
||||
return int(self._wv.safe_field("bg_angle_end", 0))
|
||||
return int(self._wv_lv_arc_t.safe_field("bg_angle_end", 0))
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""Current value of the arc"""
|
||||
return int(self._wv.safe_field("value", 0))
|
||||
return int(self._wv_lv_arc_t.safe_field("value", 0))
|
||||
|
||||
@property
|
||||
def min_value(self):
|
||||
"""Minimum value of the arc"""
|
||||
return int(self._wv.safe_field("min_value", 0))
|
||||
return int(self._wv_lv_arc_t.safe_field("min_value", 0))
|
||||
|
||||
@property
|
||||
def max_value(self):
|
||||
"""Maximum value of the arc"""
|
||||
return int(self._wv.safe_field("max_value", 0))
|
||||
return int(self._wv_lv_arc_t.safe_field("max_value", 0))
|
||||
|
||||
@property
|
||||
def dragging(self):
|
||||
return int(self._wv.safe_field("dragging", 0))
|
||||
return int(self._wv_lv_arc_t.safe_field("dragging", 0))
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return int(self._wv.safe_field("type", 0))
|
||||
return int(self._wv_lv_arc_t.safe_field("type", 0))
|
||||
|
||||
@property
|
||||
def min_close(self):
|
||||
"""1: the last pressed angle was closer to minimum end"""
|
||||
return int(self._wv.safe_field("min_close", 0))
|
||||
return int(self._wv_lv_arc_t.safe_field("min_close", 0))
|
||||
|
||||
@property
|
||||
def in_out(self):
|
||||
"""1: The click was within the background arc angles. 0: Click outside"""
|
||||
return int(self._wv.safe_field("in_out", 0))
|
||||
return int(self._wv_lv_arc_t.safe_field("in_out", 0))
|
||||
|
||||
@property
|
||||
def chg_rate(self):
|
||||
"""Drag angle rate of change of the arc (degrees/sec)"""
|
||||
return int(self._wv.safe_field("chg_rate", 0))
|
||||
return int(self._wv_lv_arc_t.safe_field("chg_rate", 0))
|
||||
|
||||
@property
|
||||
def last_tick(self):
|
||||
"""Last dragging event timestamp of the arc"""
|
||||
return int(self._wv.safe_field("last_tick", 0))
|
||||
return int(self._wv_lv_arc_t.safe_field("last_tick", 0))
|
||||
|
||||
@property
|
||||
def last_angle(self):
|
||||
"""Last dragging angle of the arc"""
|
||||
return int(self._wv.safe_field("last_angle", 0))
|
||||
return int(self._wv_lv_arc_t.safe_field("last_angle", 0))
|
||||
|
||||
@property
|
||||
def knob_offset(self):
|
||||
"""knob offset from the main arc"""
|
||||
return int(self._wv.safe_field("knob_offset", 0))
|
||||
return int(self._wv_lv_arc_t.safe_field("knob_offset", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,70 +14,71 @@ class LVArclabel(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_arclabel_t", ptr=True) or self
|
||||
self._wv_lv_arclabel_t = self.cast("lv_arclabel_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_arclabel_t
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return safe_string(self._wv, "text")
|
||||
return safe_string(self._wv_lv_arclabel_t, "text")
|
||||
|
||||
@property
|
||||
def dot_begin(self):
|
||||
"""Offset where bytes have been replaced with dots"""
|
||||
return int(self._wv.safe_field("dot_begin", 0))
|
||||
return int(self._wv_lv_arclabel_t.safe_field("dot_begin", 0))
|
||||
|
||||
@property
|
||||
def angle_start(self):
|
||||
return int(self._wv.safe_field("angle_start", 0))
|
||||
return int(self._wv_lv_arclabel_t.safe_field("angle_start", 0))
|
||||
|
||||
@property
|
||||
def angle_size(self):
|
||||
return int(self._wv.safe_field("angle_size", 0))
|
||||
return int(self._wv_lv_arclabel_t.safe_field("angle_size", 0))
|
||||
|
||||
@property
|
||||
def offset(self):
|
||||
return int(self._wv.safe_field("offset", 0))
|
||||
return int(self._wv_lv_arclabel_t.safe_field("offset", 0))
|
||||
|
||||
@property
|
||||
def radius(self):
|
||||
return int(self._wv.safe_field("radius", 0))
|
||||
return int(self._wv_lv_arclabel_t.safe_field("radius", 0))
|
||||
|
||||
@property
|
||||
def center_offset(self):
|
||||
return safe_point(self._wv, "center_offset")
|
||||
return safe_point(self._wv_lv_arclabel_t, "center_offset")
|
||||
|
||||
@property
|
||||
def dir(self):
|
||||
return int(self._wv.safe_field("dir", 0))
|
||||
return int(self._wv_lv_arclabel_t.safe_field("dir", 0))
|
||||
|
||||
@property
|
||||
def text_align_v(self):
|
||||
"""Vertical text alignment"""
|
||||
return int(self._wv.safe_field("text_align_v", 0))
|
||||
return int(self._wv_lv_arclabel_t.safe_field("text_align_v", 0))
|
||||
|
||||
@property
|
||||
def text_align_h(self):
|
||||
"""Horizontal text alignment"""
|
||||
return int(self._wv.safe_field("text_align_h", 0))
|
||||
return int(self._wv_lv_arclabel_t.safe_field("text_align_h", 0))
|
||||
|
||||
@property
|
||||
def static_txt(self):
|
||||
"""Flag to indicate the text is static"""
|
||||
return int(self._wv.safe_field("static_txt", 0))
|
||||
return int(self._wv_lv_arclabel_t.safe_field("static_txt", 0))
|
||||
|
||||
@property
|
||||
def recolor(self):
|
||||
"""Enable in-line letter re-coloring"""
|
||||
return int(self._wv.safe_field("recolor", 0))
|
||||
return int(self._wv_lv_arclabel_t.safe_field("recolor", 0))
|
||||
|
||||
@property
|
||||
def overflow(self):
|
||||
"""Overflow mode: 0=visible, 1=ellipsis, 2=clip"""
|
||||
return int(self._wv.safe_field("overflow", 0))
|
||||
return int(self._wv_lv_arclabel_t.safe_field("overflow", 0))
|
||||
|
||||
@property
|
||||
def end_overlap(self):
|
||||
"""End overlap flag, false if prevent end overlap"""
|
||||
return int(self._wv.safe_field("end_overlap", 0))
|
||||
return int(self._wv_lv_arclabel_t.safe_field("end_overlap", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,47 +14,48 @@ class LVBar(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_bar_t", ptr=True) or self
|
||||
self._wv_lv_bar_t = self.cast("lv_bar_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_bar_t
|
||||
|
||||
@property
|
||||
def cur_value(self):
|
||||
"""Current value of the bar"""
|
||||
return int(self._wv.safe_field("cur_value", 0))
|
||||
return int(self._wv_lv_bar_t.safe_field("cur_value", 0))
|
||||
|
||||
@property
|
||||
def min_value(self):
|
||||
"""Minimum value of the bar"""
|
||||
return int(self._wv.safe_field("min_value", 0))
|
||||
return int(self._wv_lv_bar_t.safe_field("min_value", 0))
|
||||
|
||||
@property
|
||||
def max_value(self):
|
||||
"""Maximum value of the bar"""
|
||||
return int(self._wv.safe_field("max_value", 0))
|
||||
return int(self._wv_lv_bar_t.safe_field("max_value", 0))
|
||||
|
||||
@property
|
||||
def start_value(self):
|
||||
"""Start value of the bar"""
|
||||
return int(self._wv.safe_field("start_value", 0))
|
||||
return int(self._wv_lv_bar_t.safe_field("start_value", 0))
|
||||
|
||||
@property
|
||||
def indic_area(self):
|
||||
"""Save the indicator area. Might be used by derived types"""
|
||||
return safe_area(self._wv, "indic_area")
|
||||
return safe_area(self._wv_lv_bar_t, "indic_area")
|
||||
|
||||
@property
|
||||
def val_reversed(self):
|
||||
"""Whether value been reversed"""
|
||||
return int(self._wv.safe_field("val_reversed", 0))
|
||||
return int(self._wv_lv_bar_t.safe_field("val_reversed", 0))
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
"""Type of bar"""
|
||||
return int(self._wv.safe_field("mode", 0))
|
||||
return int(self._wv_lv_bar_t.safe_field("mode", 0))
|
||||
|
||||
@property
|
||||
def orientation(self):
|
||||
"""Orientation of bar"""
|
||||
return int(self._wv.safe_field("orientation", 0))
|
||||
return int(self._wv_lv_bar_t.safe_field("orientation", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,31 +14,32 @@ class LVBarcode(LVCanvas):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_barcode_t", ptr=True) or self
|
||||
self._wv_lv_barcode_t = self.cast("lv_barcode_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_barcode_t
|
||||
|
||||
@property
|
||||
def dark_color(self):
|
||||
return safe_color(self._wv, "dark_color")
|
||||
return safe_color(self._wv_lv_barcode_t, "dark_color")
|
||||
|
||||
@property
|
||||
def light_color(self):
|
||||
return safe_color(self._wv, "light_color")
|
||||
return safe_color(self._wv_lv_barcode_t, "light_color")
|
||||
|
||||
@property
|
||||
def scale(self):
|
||||
return int(self._wv.safe_field("scale", 0))
|
||||
return int(self._wv_lv_barcode_t.safe_field("scale", 0))
|
||||
|
||||
@property
|
||||
def direction(self):
|
||||
return int(self._wv.safe_field("direction", 0))
|
||||
return int(self._wv_lv_barcode_t.safe_field("direction", 0))
|
||||
|
||||
@property
|
||||
def tiled(self):
|
||||
return int(self._wv.safe_field("tiled", 0))
|
||||
return int(self._wv_lv_barcode_t.safe_field("tiled", 0))
|
||||
|
||||
@property
|
||||
def encoding(self):
|
||||
return int(self._wv.safe_field("encoding", 0))
|
||||
return int(self._wv_lv_barcode_t.safe_field("encoding", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -13,7 +13,8 @@ class LVButton(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_button_t", ptr=True) or self
|
||||
self._wv_lv_button_t = self.cast("lv_button_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_button_t
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,47 +14,48 @@ class LVButtonmatrix(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_buttonmatrix_t", ptr=True) or self
|
||||
self._wv_lv_buttonmatrix_t = self.cast("lv_buttonmatrix_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_buttonmatrix_t
|
||||
|
||||
@property
|
||||
def map_p(self):
|
||||
"""Pointer to the current map"""
|
||||
return ptr_or_none(self._wv.safe_field("map_p"))
|
||||
return ptr_or_none(self._wv_lv_buttonmatrix_t.safe_field("map_p"))
|
||||
|
||||
@property
|
||||
def button_areas(self):
|
||||
"""Array of areas of buttons"""
|
||||
return ptr_or_none(self._wv.safe_field("button_areas"))
|
||||
return ptr_or_none(self._wv_lv_buttonmatrix_t.safe_field("button_areas"))
|
||||
|
||||
@property
|
||||
def ctrl_bits(self):
|
||||
"""Array of control bytes"""
|
||||
return ptr_or_none(self._wv.safe_field("ctrl_bits"))
|
||||
return ptr_or_none(self._wv_lv_buttonmatrix_t.safe_field("ctrl_bits"))
|
||||
|
||||
@property
|
||||
def btn_cnt(self):
|
||||
"""Number of button in 'map_p'(Handled by the library)"""
|
||||
return int(self._wv.safe_field("btn_cnt", 0))
|
||||
return int(self._wv_lv_buttonmatrix_t.safe_field("btn_cnt", 0))
|
||||
|
||||
@property
|
||||
def row_cnt(self):
|
||||
"""Number of rows in 'map_p'(Handled by the library)"""
|
||||
return int(self._wv.safe_field("row_cnt", 0))
|
||||
return int(self._wv_lv_buttonmatrix_t.safe_field("row_cnt", 0))
|
||||
|
||||
@property
|
||||
def btn_id_sel(self):
|
||||
"""Index of the active button (being pressed/released etc) or LV_BUTTONMATRIX_BUTTON_NONE"""
|
||||
return int(self._wv.safe_field("btn_id_sel", 0))
|
||||
return int(self._wv_lv_buttonmatrix_t.safe_field("btn_id_sel", 0))
|
||||
|
||||
@property
|
||||
def one_check(self):
|
||||
"""1: Single button toggled at once"""
|
||||
return int(self._wv.safe_field("one_check", 0))
|
||||
return int(self._wv_lv_buttonmatrix_t.safe_field("one_check", 0))
|
||||
|
||||
@property
|
||||
def auto_free_map(self):
|
||||
"""1: Automatically free the map when the widget is deleted"""
|
||||
return int(self._wv.safe_field("auto_free_map", 0))
|
||||
return int(self._wv_lv_buttonmatrix_t.safe_field("auto_free_map", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,25 +14,26 @@ class LVCalendar(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_calendar_t", ptr=True) or self
|
||||
self._wv_lv_calendar_t = self.cast("lv_calendar_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_calendar_t
|
||||
|
||||
@property
|
||||
def btnm(self):
|
||||
return ptr_or_none(self._wv.safe_field("btnm"))
|
||||
return ptr_or_none(self._wv_lv_calendar_t.safe_field("btnm"))
|
||||
|
||||
@property
|
||||
def highlighted_dates(self):
|
||||
"""Apply different style on these days (pointer to user-defined array)"""
|
||||
return ptr_or_none(self._wv.safe_field("highlighted_dates"))
|
||||
return ptr_or_none(self._wv_lv_calendar_t.safe_field("highlighted_dates"))
|
||||
|
||||
@property
|
||||
def highlighted_dates_num(self):
|
||||
"""Number of elements in `highlighted_days`"""
|
||||
return int(self._wv.safe_field("highlighted_dates_num", 0))
|
||||
return int(self._wv_lv_calendar_t.safe_field("highlighted_dates_num", 0))
|
||||
|
||||
@property
|
||||
def use_chinese_calendar(self):
|
||||
return int(self._wv.safe_field("use_chinese_calendar", 0))
|
||||
return int(self._wv_lv_calendar_t.safe_field("use_chinese_calendar", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,15 +14,16 @@ class LVCanvas(LVImage):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_canvas_t", ptr=True) or self
|
||||
self._wv_lv_canvas_t = self.cast("lv_canvas_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_canvas_t
|
||||
|
||||
@property
|
||||
def draw_buf(self):
|
||||
return ptr_or_none(self._wv.safe_field("draw_buf"))
|
||||
return ptr_or_none(self._wv_lv_canvas_t.safe_field("draw_buf"))
|
||||
|
||||
@property
|
||||
def static_buf(self):
|
||||
return safe_wrapper(self._wv, "static_buf", "lvglgdb.lvgl.draw.lv_draw_buf", "LVDrawBuf")
|
||||
return safe_wrapper(self._wv_lv_canvas_t, "static_buf", "lvglgdb.lvgl.draw.lv_draw_buf", "LVDrawBuf")
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,45 +14,46 @@ class LVChart(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_chart_t", ptr=True) or self
|
||||
self._wv_lv_chart_t = self.cast("lv_chart_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_chart_t
|
||||
|
||||
@property
|
||||
def series_ll(self):
|
||||
"""Linked list for series (stores lv_chart_series_t)"""
|
||||
return safe_wrapper(self._wv, "series_ll", "lvglgdb.lvgl.misc.lv_ll", "LVList")
|
||||
return safe_wrapper(self._wv_lv_chart_t, "series_ll", "lvglgdb.lvgl.misc.lv_ll", "LVList")
|
||||
|
||||
@property
|
||||
def cursor_ll(self):
|
||||
"""Linked list for cursors (stores lv_chart_cursor_t)"""
|
||||
return safe_wrapper(self._wv, "cursor_ll", "lvglgdb.lvgl.misc.lv_ll", "LVList")
|
||||
return safe_wrapper(self._wv_lv_chart_t, "cursor_ll", "lvglgdb.lvgl.misc.lv_ll", "LVList")
|
||||
|
||||
@property
|
||||
def pressed_point_id(self):
|
||||
return int(self._wv.safe_field("pressed_point_id", 0))
|
||||
return int(self._wv_lv_chart_t.safe_field("pressed_point_id", 0))
|
||||
|
||||
@property
|
||||
def hdiv_cnt(self):
|
||||
"""Number of horizontal division lines"""
|
||||
return int(self._wv.safe_field("hdiv_cnt", 0))
|
||||
return int(self._wv_lv_chart_t.safe_field("hdiv_cnt", 0))
|
||||
|
||||
@property
|
||||
def vdiv_cnt(self):
|
||||
"""Number of vertical division lines"""
|
||||
return int(self._wv.safe_field("vdiv_cnt", 0))
|
||||
return int(self._wv_lv_chart_t.safe_field("vdiv_cnt", 0))
|
||||
|
||||
@property
|
||||
def point_cnt(self):
|
||||
"""Number of points in all series"""
|
||||
return int(self._wv.safe_field("point_cnt", 0))
|
||||
return int(self._wv_lv_chart_t.safe_field("point_cnt", 0))
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""Chart type"""
|
||||
return int(self._wv.safe_field("type", 0))
|
||||
return int(self._wv_lv_chart_t.safe_field("type", 0))
|
||||
|
||||
@property
|
||||
def update_mode(self):
|
||||
return int(self._wv.safe_field("update_mode", 0))
|
||||
return int(self._wv_lv_chart_t.safe_field("update_mode", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,15 +14,16 @@ class LVCheckbox(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_checkbox_t", ptr=True) or self
|
||||
self._wv_lv_checkbox_t = self.cast("lv_checkbox_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_checkbox_t
|
||||
|
||||
@property
|
||||
def txt(self):
|
||||
return safe_string(self._wv, "txt")
|
||||
return safe_string(self._wv_lv_checkbox_t, "txt")
|
||||
|
||||
@property
|
||||
def static_txt(self):
|
||||
return int(self._wv.safe_field("static_txt", 0))
|
||||
return int(self._wv_lv_checkbox_t.safe_field("static_txt", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,67 +14,68 @@ class LVDropdown(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_dropdown_t", ptr=True) or self
|
||||
self._wv_lv_dropdown_t = self.cast("lv_dropdown_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_dropdown_t
|
||||
|
||||
@property
|
||||
def list(self):
|
||||
"""The dropped down list"""
|
||||
return ptr_or_none(self._wv.safe_field("list"))
|
||||
return ptr_or_none(self._wv_lv_dropdown_t.safe_field("list"))
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
"""Text to display on the dropdown's button"""
|
||||
return safe_string(self._wv, "text")
|
||||
return safe_string(self._wv_lv_dropdown_t, "text")
|
||||
|
||||
@property
|
||||
def symbol(self):
|
||||
"""Arrow or other icon when the drop-down list is closed"""
|
||||
return ptr_or_none(self._wv.safe_field("symbol"))
|
||||
return ptr_or_none(self._wv_lv_dropdown_t.safe_field("symbol"))
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
"""Options in a '\n' separated list"""
|
||||
return safe_string(self._wv, "options")
|
||||
return safe_string(self._wv_lv_dropdown_t, "options")
|
||||
|
||||
@property
|
||||
def option_cnt(self):
|
||||
"""Number of options"""
|
||||
return int(self._wv.safe_field("option_cnt", 0))
|
||||
return int(self._wv_lv_dropdown_t.safe_field("option_cnt", 0))
|
||||
|
||||
@property
|
||||
def sel_opt_id(self):
|
||||
"""Index of the currently selected option"""
|
||||
return int(self._wv.safe_field("sel_opt_id", 0))
|
||||
return int(self._wv_lv_dropdown_t.safe_field("sel_opt_id", 0))
|
||||
|
||||
@property
|
||||
def sel_opt_id_orig(self):
|
||||
"""Store the original index on focus"""
|
||||
return int(self._wv.safe_field("sel_opt_id_orig", 0))
|
||||
return int(self._wv_lv_dropdown_t.safe_field("sel_opt_id_orig", 0))
|
||||
|
||||
@property
|
||||
def pr_opt_id(self):
|
||||
"""Index of the currently pressed option"""
|
||||
return int(self._wv.safe_field("pr_opt_id", 0))
|
||||
return int(self._wv_lv_dropdown_t.safe_field("pr_opt_id", 0))
|
||||
|
||||
@property
|
||||
def dir(self):
|
||||
"""Direction in which the list should open"""
|
||||
return int(self._wv.safe_field("dir", 0))
|
||||
return int(self._wv_lv_dropdown_t.safe_field("dir", 0))
|
||||
|
||||
@property
|
||||
def static_options(self):
|
||||
"""1: Only a pointer is saved in `options`"""
|
||||
return int(self._wv.safe_field("static_options", 0))
|
||||
return int(self._wv_lv_dropdown_t.safe_field("static_options", 0))
|
||||
|
||||
@property
|
||||
def selected_highlight(self):
|
||||
"""1: Make the selected option highlighted in the list"""
|
||||
return int(self._wv.safe_field("selected_highlight", 0))
|
||||
return int(self._wv_lv_dropdown_t.safe_field("selected_highlight", 0))
|
||||
|
||||
@property
|
||||
def static_text(self):
|
||||
"""1: Only a pointer is saved in `text`"""
|
||||
return int(self._wv.safe_field("static_text", 0))
|
||||
return int(self._wv_lv_dropdown_t.safe_field("static_text", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,11 +14,12 @@ class LVDropdownList(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_dropdown_list_t", ptr=True) or self
|
||||
self._wv_lv_dropdown_list_t = self.cast("lv_dropdown_list_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_dropdown_list_t
|
||||
|
||||
@property
|
||||
def dropdown(self):
|
||||
return ptr_or_none(self._wv.safe_field("dropdown"))
|
||||
return ptr_or_none(self._wv_lv_dropdown_list_t.safe_field("dropdown"))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,19 +14,20 @@ class LVFfmpegPlayer(LVImage):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_ffmpeg_player_t", ptr=True) or self
|
||||
self._wv_lv_ffmpeg_player_t = self.cast("lv_ffmpeg_player_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_ffmpeg_player_t
|
||||
|
||||
@property
|
||||
def timer(self):
|
||||
return ptr_or_none(self._wv.safe_field("timer"))
|
||||
return ptr_or_none(self._wv_lv_ffmpeg_player_t.safe_field("timer"))
|
||||
|
||||
@property
|
||||
def auto_restart(self):
|
||||
return int(self._wv.safe_field("auto_restart", 0))
|
||||
return int(self._wv_lv_ffmpeg_player_t.safe_field("auto_restart", 0))
|
||||
|
||||
@property
|
||||
def decoder_name(self):
|
||||
return safe_string(self._wv, "decoder_name")
|
||||
return safe_string(self._wv_lv_ffmpeg_player_t, "decoder_name")
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,43 +14,44 @@ class LVGstreamer(LVImage):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_gstreamer_t", ptr=True) or self
|
||||
self._wv_lv_gstreamer_t = self.cast("lv_gstreamer_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_gstreamer_t
|
||||
|
||||
@property
|
||||
def last_buffer(self):
|
||||
return ptr_or_none(self._wv.safe_field("last_buffer"))
|
||||
return ptr_or_none(self._wv_lv_gstreamer_t.safe_field("last_buffer"))
|
||||
|
||||
@property
|
||||
def last_sample(self):
|
||||
return ptr_or_none(self._wv.safe_field("last_sample"))
|
||||
return ptr_or_none(self._wv_lv_gstreamer_t.safe_field("last_sample"))
|
||||
|
||||
@property
|
||||
def pipeline(self):
|
||||
return ptr_or_none(self._wv.safe_field("pipeline"))
|
||||
return ptr_or_none(self._wv_lv_gstreamer_t.safe_field("pipeline"))
|
||||
|
||||
@property
|
||||
def audio_convert(self):
|
||||
return ptr_or_none(self._wv.safe_field("audio_convert"))
|
||||
return ptr_or_none(self._wv_lv_gstreamer_t.safe_field("audio_convert"))
|
||||
|
||||
@property
|
||||
def video_convert(self):
|
||||
return ptr_or_none(self._wv.safe_field("video_convert"))
|
||||
return ptr_or_none(self._wv_lv_gstreamer_t.safe_field("video_convert"))
|
||||
|
||||
@property
|
||||
def audio_volume(self):
|
||||
return ptr_or_none(self._wv.safe_field("audio_volume"))
|
||||
return ptr_or_none(self._wv_lv_gstreamer_t.safe_field("audio_volume"))
|
||||
|
||||
@property
|
||||
def gstreamer_timer(self):
|
||||
return ptr_or_none(self._wv.safe_field("gstreamer_timer"))
|
||||
return ptr_or_none(self._wv_lv_gstreamer_t.safe_field("gstreamer_timer"))
|
||||
|
||||
@property
|
||||
def frame_queue(self):
|
||||
return ptr_or_none(self._wv.safe_field("frame_queue"))
|
||||
return ptr_or_none(self._wv_lv_gstreamer_t.safe_field("frame_queue"))
|
||||
|
||||
@property
|
||||
def is_video_info_valid(self):
|
||||
return int(self._wv.safe_field("is_video_info_valid", 0))
|
||||
return int(self._wv_lv_gstreamer_t.safe_field("is_video_info_valid", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,76 +14,77 @@ class LVImage(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_image_t", ptr=True) or self
|
||||
self._wv_lv_image_t = self.cast("lv_image_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_image_t
|
||||
|
||||
@property
|
||||
def src(self):
|
||||
"""Image source: Pointer to an array or a file or a symbol"""
|
||||
return ptr_or_none(self._wv.safe_field("src"))
|
||||
return ptr_or_none(self._wv_lv_image_t.safe_field("src"))
|
||||
|
||||
@property
|
||||
def bitmap_mask_src(self):
|
||||
"""Pointer to an A8 bitmap mask"""
|
||||
return ptr_or_none(self._wv.safe_field("bitmap_mask_src"))
|
||||
return ptr_or_none(self._wv_lv_image_t.safe_field("bitmap_mask_src"))
|
||||
|
||||
@property
|
||||
def offset(self):
|
||||
return safe_point(self._wv, "offset")
|
||||
return safe_point(self._wv_lv_image_t, "offset")
|
||||
|
||||
@property
|
||||
def w(self):
|
||||
"""Width of the image (Handled by the library)"""
|
||||
return int(self._wv.safe_field("w", 0))
|
||||
return int(self._wv_lv_image_t.safe_field("w", 0))
|
||||
|
||||
@property
|
||||
def h(self):
|
||||
"""Height of the image (Handled by the library)"""
|
||||
return int(self._wv.safe_field("h", 0))
|
||||
return int(self._wv_lv_image_t.safe_field("h", 0))
|
||||
|
||||
@property
|
||||
def rotation(self):
|
||||
"""Rotation angle of the image"""
|
||||
return int(self._wv.safe_field("rotation", 0))
|
||||
return int(self._wv_lv_image_t.safe_field("rotation", 0))
|
||||
|
||||
@property
|
||||
def scale_x(self):
|
||||
"""256 means no zoom, 512 double size, 128 half size"""
|
||||
return int(self._wv.safe_field("scale_x", 0))
|
||||
return int(self._wv_lv_image_t.safe_field("scale_x", 0))
|
||||
|
||||
@property
|
||||
def scale_y(self):
|
||||
"""256 means no zoom, 512 double size, 128 half size"""
|
||||
return int(self._wv.safe_field("scale_y", 0))
|
||||
return int(self._wv_lv_image_t.safe_field("scale_y", 0))
|
||||
|
||||
@property
|
||||
def pivot(self):
|
||||
"""Rotation center of the image"""
|
||||
return safe_point(self._wv, "pivot")
|
||||
return safe_point(self._wv_lv_image_t, "pivot")
|
||||
|
||||
@property
|
||||
def src_type(self):
|
||||
"""See: lv_image_src_t"""
|
||||
return int(self._wv.safe_field("src_type", 0))
|
||||
return int(self._wv_lv_image_t.safe_field("src_type", 0))
|
||||
|
||||
@property
|
||||
def cf(self):
|
||||
"""Color format from `lv_color_format_t`"""
|
||||
return int(self._wv.safe_field("cf", 0))
|
||||
return int(self._wv_lv_image_t.safe_field("cf", 0))
|
||||
|
||||
@property
|
||||
def antialias(self):
|
||||
"""Apply anti-aliasing in transformations (rotate, zoom)"""
|
||||
return int(self._wv.safe_field("antialias", 0))
|
||||
return int(self._wv_lv_image_t.safe_field("antialias", 0))
|
||||
|
||||
@property
|
||||
def align(self):
|
||||
"""Image size mode when image size and object size is different. See lv_image_align_t"""
|
||||
return int(self._wv.safe_field("align", 0))
|
||||
return int(self._wv_lv_image_t.safe_field("align", 0))
|
||||
|
||||
@property
|
||||
def blend_mode(self):
|
||||
"""Element of `lv_blend_mode_t`"""
|
||||
return int(self._wv.safe_field("blend_mode", 0))
|
||||
return int(self._wv_lv_image_t.safe_field("blend_mode", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -13,7 +13,8 @@ class LVImagebutton(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_imagebutton_t", ptr=True) or self
|
||||
self._wv_lv_imagebutton_t = self.cast("lv_imagebutton_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_imagebutton_t
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,63 +14,64 @@ class LVImePinyin(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_ime_pinyin_t", ptr=True) or self
|
||||
self._wv_lv_ime_pinyin_t = self.cast("lv_ime_pinyin_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_ime_pinyin_t
|
||||
|
||||
@property
|
||||
def kb(self):
|
||||
return ptr_or_none(self._wv.safe_field("kb"))
|
||||
return ptr_or_none(self._wv_lv_ime_pinyin_t.safe_field("kb"))
|
||||
|
||||
@property
|
||||
def cand_panel(self):
|
||||
return ptr_or_none(self._wv.safe_field("cand_panel"))
|
||||
return ptr_or_none(self._wv_lv_ime_pinyin_t.safe_field("cand_panel"))
|
||||
|
||||
@property
|
||||
def dict(self):
|
||||
return ptr_or_none(self._wv.safe_field("dict"))
|
||||
return ptr_or_none(self._wv_lv_ime_pinyin_t.safe_field("dict"))
|
||||
|
||||
@property
|
||||
def k9_legal_py_ll(self):
|
||||
return safe_wrapper(self._wv, "k9_legal_py_ll", "lvglgdb.lvgl.misc.lv_ll", "LVList")
|
||||
return safe_wrapper(self._wv_lv_ime_pinyin_t, "k9_legal_py_ll", "lvglgdb.lvgl.misc.lv_ll", "LVList")
|
||||
|
||||
@property
|
||||
def cand_str(self):
|
||||
"""Candidate string"""
|
||||
return safe_string(self._wv, "cand_str")
|
||||
return safe_string(self._wv_lv_ime_pinyin_t, "cand_str")
|
||||
|
||||
@property
|
||||
def k9_py_ll_pos(self):
|
||||
"""Current pinyin map pages(k9)"""
|
||||
return int(self._wv.safe_field("k9_py_ll_pos", 0))
|
||||
return int(self._wv_lv_ime_pinyin_t.safe_field("k9_py_ll_pos", 0))
|
||||
|
||||
@property
|
||||
def k9_legal_py_count(self):
|
||||
"""Count of legal Pinyin numbers(k9)"""
|
||||
return int(self._wv.safe_field("k9_legal_py_count", 0))
|
||||
return int(self._wv_lv_ime_pinyin_t.safe_field("k9_legal_py_count", 0))
|
||||
|
||||
@property
|
||||
def k9_input_str_len(self):
|
||||
"""9-key input(k9) mode input string max len"""
|
||||
return int(self._wv.safe_field("k9_input_str_len", 0))
|
||||
return int(self._wv_lv_ime_pinyin_t.safe_field("k9_input_str_len", 0))
|
||||
|
||||
@property
|
||||
def ta_count(self):
|
||||
"""The number of characters entered in the text box this time"""
|
||||
return int(self._wv.safe_field("ta_count", 0))
|
||||
return int(self._wv_lv_ime_pinyin_t.safe_field("ta_count", 0))
|
||||
|
||||
@property
|
||||
def cand_num(self):
|
||||
"""Number of candidates"""
|
||||
return int(self._wv.safe_field("cand_num", 0))
|
||||
return int(self._wv_lv_ime_pinyin_t.safe_field("cand_num", 0))
|
||||
|
||||
@property
|
||||
def py_page(self):
|
||||
"""Current pinyin map pages(k26)"""
|
||||
return int(self._wv.safe_field("py_page", 0))
|
||||
return int(self._wv_lv_ime_pinyin_t.safe_field("py_page", 0))
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
"""Set mode, 1: 26-key input(k26), 0: 9-key input(k9). Default: 1."""
|
||||
return int(self._wv.safe_field("mode", 0))
|
||||
return int(self._wv_lv_ime_pinyin_t.safe_field("mode", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,22 +14,23 @@ class LVKeyboard(LVButtonmatrix):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_keyboard_t", ptr=True) or self
|
||||
self._wv_lv_keyboard_t = self.cast("lv_keyboard_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_keyboard_t
|
||||
|
||||
@property
|
||||
def ta(self):
|
||||
"""Pointer to the assigned text area"""
|
||||
return ptr_or_none(self._wv.safe_field("ta"))
|
||||
return ptr_or_none(self._wv_lv_keyboard_t.safe_field("ta"))
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
"""Key map type"""
|
||||
return int(self._wv.safe_field("mode", 0))
|
||||
return int(self._wv_lv_keyboard_t.safe_field("mode", 0))
|
||||
|
||||
@property
|
||||
def popovers(self):
|
||||
"""Show button titles in popovers on press"""
|
||||
return int(self._wv.safe_field("popovers", 0))
|
||||
return int(self._wv_lv_keyboard_t.safe_field("popovers", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,76 +14,77 @@ class LVLabel(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_label_t", ptr=True) or self
|
||||
self._wv_lv_label_t = self.cast("lv_label_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_label_t
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return safe_string(self._wv, "text")
|
||||
return safe_string(self._wv_lv_label_t, "text")
|
||||
|
||||
@property
|
||||
def translation_tag(self):
|
||||
return safe_string(self._wv, "translation_tag")
|
||||
return safe_string(self._wv_lv_label_t, "translation_tag")
|
||||
|
||||
@property
|
||||
def dot_begin(self):
|
||||
"""Offset where bytes have been replaced with dots"""
|
||||
return int(self._wv.safe_field("dot_begin", 0))
|
||||
return int(self._wv_lv_label_t.safe_field("dot_begin", 0))
|
||||
|
||||
@property
|
||||
def max_lines(self):
|
||||
return int(self._wv.safe_field("max_lines", 0))
|
||||
return int(self._wv_lv_label_t.safe_field("max_lines", 0))
|
||||
|
||||
@property
|
||||
def sel_start(self):
|
||||
return int(self._wv.safe_field("sel_start", 0))
|
||||
return int(self._wv_lv_label_t.safe_field("sel_start", 0))
|
||||
|
||||
@property
|
||||
def sel_end(self):
|
||||
return int(self._wv.safe_field("sel_end", 0))
|
||||
return int(self._wv_lv_label_t.safe_field("sel_end", 0))
|
||||
|
||||
@property
|
||||
def size_cache(self):
|
||||
"""Text size cache"""
|
||||
return safe_point(self._wv, "size_cache")
|
||||
return safe_point(self._wv_lv_label_t, "size_cache")
|
||||
|
||||
@property
|
||||
def offset(self):
|
||||
"""Text draw position offset"""
|
||||
return safe_point(self._wv, "offset")
|
||||
return safe_point(self._wv_lv_label_t, "offset")
|
||||
|
||||
@property
|
||||
def long_mode(self):
|
||||
"""Determine what to do with the long texts"""
|
||||
return int(self._wv.safe_field("long_mode", 0))
|
||||
return int(self._wv_lv_label_t.safe_field("long_mode", 0))
|
||||
|
||||
@property
|
||||
def static_txt(self):
|
||||
"""Flag to indicate the text is static"""
|
||||
return int(self._wv.safe_field("static_txt", 0))
|
||||
return int(self._wv_lv_label_t.safe_field("static_txt", 0))
|
||||
|
||||
@property
|
||||
def recolor(self):
|
||||
"""Enable in-line letter re-coloring"""
|
||||
return int(self._wv.safe_field("recolor", 0))
|
||||
return int(self._wv_lv_label_t.safe_field("recolor", 0))
|
||||
|
||||
@property
|
||||
def expand(self):
|
||||
"""Ignore real width (used by the library with LV_LABEL_LONG_MODE_SCROLL)"""
|
||||
return int(self._wv.safe_field("expand", 0))
|
||||
return int(self._wv_lv_label_t.safe_field("expand", 0))
|
||||
|
||||
@property
|
||||
def invalid_size_cache(self):
|
||||
"""1: Recalculate size and update cache"""
|
||||
return int(self._wv.safe_field("invalid_size_cache", 0))
|
||||
return int(self._wv_lv_label_t.safe_field("invalid_size_cache", 0))
|
||||
|
||||
@property
|
||||
def need_refr_text(self):
|
||||
"""1: Refresh text after layout update completion"""
|
||||
return int(self._wv.safe_field("need_refr_text", 0))
|
||||
return int(self._wv_lv_label_t.safe_field("need_refr_text", 0))
|
||||
|
||||
@property
|
||||
def text_size(self):
|
||||
return safe_point(self._wv, "text_size")
|
||||
return safe_point(self._wv_lv_label_t, "text_size")
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,16 +14,17 @@ class LVLed(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_led_t", ptr=True) or self
|
||||
self._wv_lv_led_t = self.cast("lv_led_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_led_t
|
||||
|
||||
@property
|
||||
def color(self):
|
||||
return safe_color(self._wv, "color")
|
||||
return safe_color(self._wv_lv_led_t, "color")
|
||||
|
||||
@property
|
||||
def bright(self):
|
||||
"""Current brightness of the LED (0..255)"""
|
||||
return int(self._wv.safe_field("bright", 0))
|
||||
return int(self._wv_lv_led_t.safe_field("bright", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -13,22 +13,23 @@ class LVLine(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_line_t", ptr=True) or self
|
||||
self._wv_lv_line_t = self.cast("lv_line_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_line_t
|
||||
|
||||
@property
|
||||
def point_num(self):
|
||||
"""Number of points in 'point_array'"""
|
||||
return int(self._wv.safe_field("point_num", 0))
|
||||
return int(self._wv_lv_line_t.safe_field("point_num", 0))
|
||||
|
||||
@property
|
||||
def y_inv(self):
|
||||
"""1: y == 0 will be on the bottom"""
|
||||
return int(self._wv.safe_field("y_inv", 0))
|
||||
return int(self._wv_lv_line_t.safe_field("y_inv", 0))
|
||||
|
||||
@property
|
||||
def point_array_is_mutable(self):
|
||||
"""whether the point array is const or mutable"""
|
||||
return int(self._wv.safe_field("point_array_is_mutable", 0))
|
||||
return int(self._wv_lv_line_t.safe_field("point_array_is_mutable", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,72 +14,73 @@ class LVMenu(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_menu_t", ptr=True) or self
|
||||
self._wv_lv_menu_t = self.cast("lv_menu_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_menu_t
|
||||
|
||||
@property
|
||||
def storage(self):
|
||||
"""a pointer to obj that is the parent of all pages not displayed"""
|
||||
return ptr_or_none(self._wv.safe_field("storage"))
|
||||
return ptr_or_none(self._wv_lv_menu_t.safe_field("storage"))
|
||||
|
||||
@property
|
||||
def main(self):
|
||||
return ptr_or_none(self._wv.safe_field("main"))
|
||||
return ptr_or_none(self._wv_lv_menu_t.safe_field("main"))
|
||||
|
||||
@property
|
||||
def main_page(self):
|
||||
return ptr_or_none(self._wv.safe_field("main_page"))
|
||||
return ptr_or_none(self._wv_lv_menu_t.safe_field("main_page"))
|
||||
|
||||
@property
|
||||
def main_header(self):
|
||||
return ptr_or_none(self._wv.safe_field("main_header"))
|
||||
return ptr_or_none(self._wv_lv_menu_t.safe_field("main_header"))
|
||||
|
||||
@property
|
||||
def main_header_title(self):
|
||||
return ptr_or_none(self._wv.safe_field("main_header_title"))
|
||||
return ptr_or_none(self._wv_lv_menu_t.safe_field("main_header_title"))
|
||||
|
||||
@property
|
||||
def sidebar(self):
|
||||
return ptr_or_none(self._wv.safe_field("sidebar"))
|
||||
return ptr_or_none(self._wv_lv_menu_t.safe_field("sidebar"))
|
||||
|
||||
@property
|
||||
def sidebar_page(self):
|
||||
return ptr_or_none(self._wv.safe_field("sidebar_page"))
|
||||
return ptr_or_none(self._wv_lv_menu_t.safe_field("sidebar_page"))
|
||||
|
||||
@property
|
||||
def sidebar_header(self):
|
||||
return ptr_or_none(self._wv.safe_field("sidebar_header"))
|
||||
return ptr_or_none(self._wv_lv_menu_t.safe_field("sidebar_header"))
|
||||
|
||||
@property
|
||||
def sidebar_header_title(self):
|
||||
return ptr_or_none(self._wv.safe_field("sidebar_header_title"))
|
||||
return ptr_or_none(self._wv_lv_menu_t.safe_field("sidebar_header_title"))
|
||||
|
||||
@property
|
||||
def selected_tab(self):
|
||||
return ptr_or_none(self._wv.safe_field("selected_tab"))
|
||||
return ptr_or_none(self._wv_lv_menu_t.safe_field("selected_tab"))
|
||||
|
||||
@property
|
||||
def history_ll(self):
|
||||
return safe_wrapper(self._wv, "history_ll", "lvglgdb.lvgl.misc.lv_ll", "LVList")
|
||||
return safe_wrapper(self._wv_lv_menu_t, "history_ll", "lvglgdb.lvgl.misc.lv_ll", "LVList")
|
||||
|
||||
@property
|
||||
def cur_depth(self):
|
||||
return int(self._wv.safe_field("cur_depth", 0))
|
||||
return int(self._wv_lv_menu_t.safe_field("cur_depth", 0))
|
||||
|
||||
@property
|
||||
def prev_depth(self):
|
||||
return int(self._wv.safe_field("prev_depth", 0))
|
||||
return int(self._wv_lv_menu_t.safe_field("prev_depth", 0))
|
||||
|
||||
@property
|
||||
def sidebar_generated(self):
|
||||
return int(self._wv.safe_field("sidebar_generated", 0))
|
||||
return int(self._wv_lv_menu_t.safe_field("sidebar_generated", 0))
|
||||
|
||||
@property
|
||||
def mode_header(self):
|
||||
return int(self._wv.safe_field("mode_header", 0))
|
||||
return int(self._wv_lv_menu_t.safe_field("mode_header", 0))
|
||||
|
||||
@property
|
||||
def mode_root_back_btn(self):
|
||||
return int(self._wv.safe_field("mode_root_back_btn", 0))
|
||||
return int(self._wv_lv_menu_t.safe_field("mode_root_back_btn", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,15 +14,16 @@ class LVMenuPage(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_menu_page_t", ptr=True) or self
|
||||
self._wv_lv_menu_page_t = self.cast("lv_menu_page_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_menu_page_t
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
return safe_string(self._wv, "title")
|
||||
return safe_string(self._wv_lv_menu_page_t, "title")
|
||||
|
||||
@property
|
||||
def static_title(self):
|
||||
return int(self._wv.safe_field("static_title", 0))
|
||||
return int(self._wv_lv_menu_page_t.safe_field("static_title", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,28 +14,29 @@ class LVMsgbox(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_msgbox_t", ptr=True) or self
|
||||
self._wv_lv_msgbox_t = self.cast("lv_msgbox_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_msgbox_t
|
||||
|
||||
@property
|
||||
def header(self):
|
||||
return ptr_or_none(self._wv.safe_field("header"))
|
||||
return ptr_or_none(self._wv_lv_msgbox_t.safe_field("header"))
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
return ptr_or_none(self._wv.safe_field("content"))
|
||||
return ptr_or_none(self._wv_lv_msgbox_t.safe_field("content"))
|
||||
|
||||
@property
|
||||
def footer(self):
|
||||
return ptr_or_none(self._wv.safe_field("footer"))
|
||||
return ptr_or_none(self._wv_lv_msgbox_t.safe_field("footer"))
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
return ptr_or_none(self._wv.safe_field("title"))
|
||||
return ptr_or_none(self._wv_lv_msgbox_t.safe_field("title"))
|
||||
|
||||
@property
|
||||
def auto_parent(self):
|
||||
"""Mark that the parent was automatically created"""
|
||||
return int(self._wv.safe_field("auto_parent", 0))
|
||||
return int(self._wv_lv_msgbox_t.safe_field("auto_parent", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,45 +14,46 @@ class LVQrcode(LVCanvas):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_qrcode_t", ptr=True) or self
|
||||
self._wv_lv_qrcode_t = self.cast("lv_qrcode_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_qrcode_t
|
||||
|
||||
@property
|
||||
def dark_color(self):
|
||||
return safe_color(self._wv, "dark_color")
|
||||
return safe_color(self._wv_lv_qrcode_t, "dark_color")
|
||||
|
||||
@property
|
||||
def light_color(self):
|
||||
return safe_color(self._wv, "light_color")
|
||||
return safe_color(self._wv_lv_qrcode_t, "light_color")
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
"""Copy of the payload, kept so the bitmap can be re-encoded on a property change"""
|
||||
return ptr_or_none(self._wv.safe_field("data"))
|
||||
return ptr_or_none(self._wv_lv_qrcode_t.safe_field("data"))
|
||||
|
||||
@property
|
||||
def data_len(self):
|
||||
"""Stored payload length in bytes"""
|
||||
return int(self._wv.safe_field("data_len", 0))
|
||||
return int(self._wv_lv_qrcode_t.safe_field("data_len", 0))
|
||||
|
||||
@property
|
||||
def quiet_zone(self):
|
||||
"""Add the QR spec's blank margin around the code (boolean toggle)"""
|
||||
return int(self._wv.safe_field("quiet_zone", 0))
|
||||
return int(self._wv_lv_qrcode_t.safe_field("quiet_zone", 0))
|
||||
|
||||
@property
|
||||
def update_mode(self):
|
||||
"""lv_qrcode_update_mode_t: when a property change is re-encoded"""
|
||||
return int(self._wv.safe_field("update_mode", 0))
|
||||
return int(self._wv_lv_qrcode_t.safe_field("update_mode", 0))
|
||||
|
||||
@property
|
||||
def needs_update(self):
|
||||
"""The bitmap is out of date; re-encoded on the next redraw (deferred mode)"""
|
||||
return int(self._wv.safe_field("needs_update", 0))
|
||||
return int(self._wv_lv_qrcode_t.safe_field("needs_update", 0))
|
||||
|
||||
@property
|
||||
def render_failed(self):
|
||||
"""The last encode attempt failed (or none has run yet)"""
|
||||
return int(self._wv.safe_field("render_failed", 0))
|
||||
return int(self._wv_lv_qrcode_t.safe_field("render_failed", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -13,35 +13,36 @@ class LVRoller(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_roller_t", ptr=True) or self
|
||||
self._wv_lv_roller_t = self.cast("lv_roller_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_roller_t
|
||||
|
||||
@property
|
||||
def option_cnt(self):
|
||||
"""Number of options"""
|
||||
return int(self._wv.safe_field("option_cnt", 0))
|
||||
return int(self._wv_lv_roller_t.safe_field("option_cnt", 0))
|
||||
|
||||
@property
|
||||
def sel_opt_id(self):
|
||||
"""Index of the current option"""
|
||||
return int(self._wv.safe_field("sel_opt_id", 0))
|
||||
return int(self._wv_lv_roller_t.safe_field("sel_opt_id", 0))
|
||||
|
||||
@property
|
||||
def sel_opt_id_ori(self):
|
||||
"""Store the original index on focus"""
|
||||
return int(self._wv.safe_field("sel_opt_id_ori", 0))
|
||||
return int(self._wv_lv_roller_t.safe_field("sel_opt_id_ori", 0))
|
||||
|
||||
@property
|
||||
def inf_page_cnt(self):
|
||||
"""Number of extra pages added to make the roller look infinite"""
|
||||
return int(self._wv.safe_field("inf_page_cnt", 0))
|
||||
return int(self._wv_lv_roller_t.safe_field("inf_page_cnt", 0))
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
return int(self._wv.safe_field("mode", 0))
|
||||
return int(self._wv_lv_roller_t.safe_field("mode", 0))
|
||||
|
||||
@property
|
||||
def moved(self):
|
||||
return int(self._wv.safe_field("moved", 0))
|
||||
return int(self._wv_lv_roller_t.safe_field("moved", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,85 +14,86 @@ class LVScale(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_scale_t", ptr=True) or self
|
||||
self._wv_lv_scale_t = self.cast("lv_scale_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_scale_t
|
||||
|
||||
@property
|
||||
def section_ll(self):
|
||||
"""Linked list for the sections (stores lv_scale_section_t)"""
|
||||
return safe_wrapper(self._wv, "section_ll", "lvglgdb.lvgl.misc.lv_ll", "LVList")
|
||||
return safe_wrapper(self._wv_lv_scale_t, "section_ll", "lvglgdb.lvgl.misc.lv_ll", "LVList")
|
||||
|
||||
@property
|
||||
def txt_src(self):
|
||||
return ptr_or_none(self._wv.safe_field("txt_src"))
|
||||
return ptr_or_none(self._wv_lv_scale_t.safe_field("txt_src"))
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
"""Orientation and layout of scale."""
|
||||
return int(self._wv.safe_field("mode", 0))
|
||||
return int(self._wv_lv_scale_t.safe_field("mode", 0))
|
||||
|
||||
@property
|
||||
def range_min(self):
|
||||
"""Scale's minimum value"""
|
||||
return int(self._wv.safe_field("range_min", 0))
|
||||
return int(self._wv_lv_scale_t.safe_field("range_min", 0))
|
||||
|
||||
@property
|
||||
def range_max(self):
|
||||
"""Scale's maximum value"""
|
||||
return int(self._wv.safe_field("range_max", 0))
|
||||
return int(self._wv_lv_scale_t.safe_field("range_max", 0))
|
||||
|
||||
@property
|
||||
def total_tick_count(self):
|
||||
"""Total number of ticks (major and minor)"""
|
||||
return int(self._wv.safe_field("total_tick_count", 0))
|
||||
return int(self._wv_lv_scale_t.safe_field("total_tick_count", 0))
|
||||
|
||||
@property
|
||||
def major_tick_every(self):
|
||||
"""Frequency of major ticks to minor ticks"""
|
||||
return int(self._wv.safe_field("major_tick_every", 0))
|
||||
return int(self._wv_lv_scale_t.safe_field("major_tick_every", 0))
|
||||
|
||||
@property
|
||||
def label_enabled(self):
|
||||
"""Draw labels for major ticks?"""
|
||||
return int(self._wv.safe_field("label_enabled", 0))
|
||||
return int(self._wv_lv_scale_t.safe_field("label_enabled", 0))
|
||||
|
||||
@property
|
||||
def post_draw(self):
|
||||
return int(self._wv.safe_field("post_draw", 0))
|
||||
return int(self._wv_lv_scale_t.safe_field("post_draw", 0))
|
||||
|
||||
@property
|
||||
def draw_ticks_on_top(self):
|
||||
"""Draw ticks on top of main line?"""
|
||||
return int(self._wv.safe_field("draw_ticks_on_top", 0))
|
||||
return int(self._wv_lv_scale_t.safe_field("draw_ticks_on_top", 0))
|
||||
|
||||
@property
|
||||
def angle_range(self):
|
||||
"""Degrees between low end and high end of scale"""
|
||||
return int(self._wv.safe_field("angle_range", 0))
|
||||
return int(self._wv_lv_scale_t.safe_field("angle_range", 0))
|
||||
|
||||
@property
|
||||
def rotation(self):
|
||||
"""Clockwise angular offset from 3-o'clock position of low end of scale"""
|
||||
return int(self._wv.safe_field("rotation", 0))
|
||||
return int(self._wv_lv_scale_t.safe_field("rotation", 0))
|
||||
|
||||
@property
|
||||
def custom_label_cnt(self):
|
||||
"""Number of custom labels provided in `txt_src`"""
|
||||
return int(self._wv.safe_field("custom_label_cnt", 0))
|
||||
return int(self._wv_lv_scale_t.safe_field("custom_label_cnt", 0))
|
||||
|
||||
@property
|
||||
def last_tick_width(self):
|
||||
"""Width of last tick in pixels"""
|
||||
return int(self._wv.safe_field("last_tick_width", 0))
|
||||
return int(self._wv_lv_scale_t.safe_field("last_tick_width", 0))
|
||||
|
||||
@property
|
||||
def first_tick_width(self):
|
||||
"""Width of first tick in pixels"""
|
||||
return int(self._wv.safe_field("first_tick_width", 0))
|
||||
return int(self._wv_lv_scale_t.safe_field("first_tick_width", 0))
|
||||
|
||||
@property
|
||||
def needles(self):
|
||||
"""Needle list of this scale"""
|
||||
return safe_wrapper(self._wv, "needles", "lvglgdb.lvgl.misc.lv_array", "LVArray")
|
||||
return safe_wrapper(self._wv_lv_scale_t, "needles", "lvglgdb.lvgl.misc.lv_array", "LVArray")
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,34 +14,35 @@ class LVSlider(LVBar):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_slider_t", ptr=True) or self
|
||||
self._wv_lv_slider_t = self.cast("lv_slider_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_slider_t
|
||||
|
||||
@property
|
||||
def left_knob_area(self):
|
||||
return safe_area(self._wv, "left_knob_area")
|
||||
return safe_area(self._wv_lv_slider_t, "left_knob_area")
|
||||
|
||||
@property
|
||||
def right_knob_area(self):
|
||||
return safe_area(self._wv, "right_knob_area")
|
||||
return safe_area(self._wv_lv_slider_t, "right_knob_area")
|
||||
|
||||
@property
|
||||
def pressed_point(self):
|
||||
return safe_point(self._wv, "pressed_point")
|
||||
return safe_point(self._wv_lv_slider_t, "pressed_point")
|
||||
|
||||
@property
|
||||
def value_to_set(self):
|
||||
"""Which bar value to set"""
|
||||
return ptr_or_none(self._wv.safe_field("value_to_set"))
|
||||
return ptr_or_none(self._wv_lv_slider_t.safe_field("value_to_set"))
|
||||
|
||||
@property
|
||||
def dragging(self):
|
||||
"""1: the slider is being dragged"""
|
||||
return int(self._wv.safe_field("dragging", 0))
|
||||
return int(self._wv_lv_slider_t.safe_field("dragging", 0))
|
||||
|
||||
@property
|
||||
def left_knob_focus(self):
|
||||
"""1: with encoder now the right knob can be adjusted"""
|
||||
return int(self._wv.safe_field("left_knob_focus", 0))
|
||||
return int(self._wv_lv_slider_t.safe_field("left_knob_focus", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,40 +14,41 @@ class LVSpangroup(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_spangroup_t", ptr=True) or self
|
||||
self._wv_lv_spangroup_t = self.cast("lv_spangroup_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_spangroup_t
|
||||
|
||||
@property
|
||||
def lines(self):
|
||||
return int(self._wv.safe_field("lines", 0))
|
||||
return int(self._wv_lv_spangroup_t.safe_field("lines", 0))
|
||||
|
||||
@property
|
||||
def indent(self):
|
||||
"""first line indent"""
|
||||
return int(self._wv.safe_field("indent", 0))
|
||||
return int(self._wv_lv_spangroup_t.safe_field("indent", 0))
|
||||
|
||||
@property
|
||||
def cache_w(self):
|
||||
"""the cache automatically calculates the width"""
|
||||
return int(self._wv.safe_field("cache_w", 0))
|
||||
return int(self._wv_lv_spangroup_t.safe_field("cache_w", 0))
|
||||
|
||||
@property
|
||||
def cache_h(self):
|
||||
"""similar cache_w"""
|
||||
return int(self._wv.safe_field("cache_h", 0))
|
||||
return int(self._wv_lv_spangroup_t.safe_field("cache_h", 0))
|
||||
|
||||
@property
|
||||
def child_ll(self):
|
||||
return safe_wrapper(self._wv, "child_ll", "lvglgdb.lvgl.misc.lv_ll", "LVList")
|
||||
return safe_wrapper(self._wv_lv_spangroup_t, "child_ll", "lvglgdb.lvgl.misc.lv_ll", "LVList")
|
||||
|
||||
@property
|
||||
def overflow(self):
|
||||
"""details see lv_span_overflow_t"""
|
||||
return int(self._wv.safe_field("overflow", 0))
|
||||
return int(self._wv_lv_spangroup_t.safe_field("overflow", 0))
|
||||
|
||||
@property
|
||||
def refresh(self):
|
||||
"""the spangroup need refresh cache_w and cache_h"""
|
||||
return int(self._wv.safe_field("refresh", 0))
|
||||
return int(self._wv_lv_spangroup_t.safe_field("refresh", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -13,42 +13,43 @@ class LVSpinbox(LVTextarea):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_spinbox_t", ptr=True) or self
|
||||
self._wv_lv_spinbox_t = self.cast("lv_spinbox_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_spinbox_t
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return int(self._wv.safe_field("value", 0))
|
||||
return int(self._wv_lv_spinbox_t.safe_field("value", 0))
|
||||
|
||||
@property
|
||||
def range_max(self):
|
||||
return int(self._wv.safe_field("range_max", 0))
|
||||
return int(self._wv_lv_spinbox_t.safe_field("range_max", 0))
|
||||
|
||||
@property
|
||||
def range_min(self):
|
||||
return int(self._wv.safe_field("range_min", 0))
|
||||
return int(self._wv_lv_spinbox_t.safe_field("range_min", 0))
|
||||
|
||||
@property
|
||||
def step(self):
|
||||
return int(self._wv.safe_field("step", 0))
|
||||
return int(self._wv_lv_spinbox_t.safe_field("step", 0))
|
||||
|
||||
@property
|
||||
def digit_count(self):
|
||||
return int(self._wv.safe_field("digit_count", 0))
|
||||
return int(self._wv_lv_spinbox_t.safe_field("digit_count", 0))
|
||||
|
||||
@property
|
||||
def dec_point_pos(self):
|
||||
"""if 0, there is no separator and the number is an integer"""
|
||||
return int(self._wv.safe_field("dec_point_pos", 0))
|
||||
return int(self._wv_lv_spinbox_t.safe_field("dec_point_pos", 0))
|
||||
|
||||
@property
|
||||
def rollover(self):
|
||||
"""Set to true for rollover functionality"""
|
||||
return int(self._wv.safe_field("rollover", 0))
|
||||
return int(self._wv_lv_spinbox_t.safe_field("rollover", 0))
|
||||
|
||||
@property
|
||||
def digit_step_dir(self):
|
||||
"""the direction the digit will step on encoder button press when editing"""
|
||||
return int(self._wv.safe_field("digit_step_dir", 0))
|
||||
return int(self._wv_lv_spinbox_t.safe_field("digit_step_dir", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -13,17 +13,18 @@ class LVSpinner(LVArc):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_spinner_t", ptr=True) or self
|
||||
self._wv_lv_spinner_t = self.cast("lv_spinner_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_spinner_t
|
||||
|
||||
@property
|
||||
def duration(self):
|
||||
"""Anim duration in ms"""
|
||||
return int(self._wv.safe_field("duration", 0))
|
||||
return int(self._wv_lv_spinner_t.safe_field("duration", 0))
|
||||
|
||||
@property
|
||||
def angle(self):
|
||||
"""Anim angle in degrees"""
|
||||
return int(self._wv.safe_field("angle", 0))
|
||||
return int(self._wv_lv_spinner_t.safe_field("angle", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -13,16 +13,17 @@ class LVSwitch(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_switch_t", ptr=True) or self
|
||||
self._wv_lv_switch_t = self.cast("lv_switch_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_switch_t
|
||||
|
||||
@property
|
||||
def anim_state(self):
|
||||
return int(self._wv.safe_field("anim_state", 0))
|
||||
return int(self._wv_lv_switch_t.safe_field("anim_state", 0))
|
||||
|
||||
@property
|
||||
def orientation(self):
|
||||
"""Orientation of switch"""
|
||||
return int(self._wv.safe_field("orientation", 0))
|
||||
return int(self._wv_lv_switch_t.safe_field("orientation", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,35 +14,36 @@ class LVTable(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_table_t", ptr=True) or self
|
||||
self._wv_lv_table_t = self.cast("lv_table_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_table_t
|
||||
|
||||
@property
|
||||
def col_cnt(self):
|
||||
return int(self._wv.safe_field("col_cnt", 0))
|
||||
return int(self._wv_lv_table_t.safe_field("col_cnt", 0))
|
||||
|
||||
@property
|
||||
def row_cnt(self):
|
||||
return int(self._wv.safe_field("row_cnt", 0))
|
||||
return int(self._wv_lv_table_t.safe_field("row_cnt", 0))
|
||||
|
||||
@property
|
||||
def cell_data(self):
|
||||
return ptr_or_none(self._wv.safe_field("cell_data"))
|
||||
return ptr_or_none(self._wv_lv_table_t.safe_field("cell_data"))
|
||||
|
||||
@property
|
||||
def row_h(self):
|
||||
return ptr_or_none(self._wv.safe_field("row_h"))
|
||||
return ptr_or_none(self._wv_lv_table_t.safe_field("row_h"))
|
||||
|
||||
@property
|
||||
def col_w(self):
|
||||
return ptr_or_none(self._wv.safe_field("col_w"))
|
||||
return ptr_or_none(self._wv_lv_table_t.safe_field("col_w"))
|
||||
|
||||
@property
|
||||
def col_act(self):
|
||||
return int(self._wv.safe_field("col_act", 0))
|
||||
return int(self._wv_lv_table_t.safe_field("col_act", 0))
|
||||
|
||||
@property
|
||||
def row_act(self):
|
||||
return int(self._wv.safe_field("row_act", 0))
|
||||
return int(self._wv_lv_table_t.safe_field("row_act", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -13,19 +13,20 @@ class LVTabview(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_tabview_t", ptr=True) or self
|
||||
self._wv_lv_tabview_t = self.cast("lv_tabview_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_tabview_t
|
||||
|
||||
@property
|
||||
def tab_cur(self):
|
||||
return int(self._wv.safe_field("tab_cur", 0))
|
||||
return int(self._wv_lv_tabview_t.safe_field("tab_cur", 0))
|
||||
|
||||
@property
|
||||
def tab_pos(self):
|
||||
return int(self._wv.safe_field("tab_pos", 0))
|
||||
return int(self._wv_lv_tabview_t.safe_field("tab_pos", 0))
|
||||
|
||||
@property
|
||||
def tab_bar_size(self):
|
||||
return int(self._wv.safe_field("tab_bar_size", 0))
|
||||
return int(self._wv_lv_tabview_t.safe_field("tab_bar_size", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,81 +14,82 @@ class LVTextarea(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_textarea_t", ptr=True) or self
|
||||
self._wv_lv_textarea_t = self.cast("lv_textarea_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_textarea_t
|
||||
|
||||
@property
|
||||
def label(self):
|
||||
"""Label of the text area"""
|
||||
return ptr_or_none(self._wv.safe_field("label"))
|
||||
return ptr_or_none(self._wv_lv_textarea_t.safe_field("label"))
|
||||
|
||||
@property
|
||||
def placeholder_txt(self):
|
||||
"""Place holder label. only visible if text is an empty string"""
|
||||
return safe_string(self._wv, "placeholder_txt")
|
||||
return safe_string(self._wv_lv_textarea_t, "placeholder_txt")
|
||||
|
||||
@property
|
||||
def placeholder_txt_size(self):
|
||||
"""Size of the placeholder text"""
|
||||
return safe_point(self._wv, "placeholder_txt_size")
|
||||
return safe_point(self._wv_lv_textarea_t, "placeholder_txt_size")
|
||||
|
||||
@property
|
||||
def pwd_tmp(self):
|
||||
"""Used to store the original text in password mode"""
|
||||
return safe_string(self._wv, "pwd_tmp")
|
||||
return safe_string(self._wv_lv_textarea_t, "pwd_tmp")
|
||||
|
||||
@property
|
||||
def pwd_bullet(self):
|
||||
"""Replacement characters displayed in password mode"""
|
||||
return safe_string(self._wv, "pwd_bullet")
|
||||
return safe_string(self._wv_lv_textarea_t, "pwd_bullet")
|
||||
|
||||
@property
|
||||
def accepted_chars(self):
|
||||
"""Only these characters will be accepted. NULL: accept all"""
|
||||
return safe_string(self._wv, "accepted_chars")
|
||||
return safe_string(self._wv_lv_textarea_t, "accepted_chars")
|
||||
|
||||
@property
|
||||
def max_length(self):
|
||||
"""The max. number of characters. 0: no limit"""
|
||||
return int(self._wv.safe_field("max_length", 0))
|
||||
return int(self._wv_lv_textarea_t.safe_field("max_length", 0))
|
||||
|
||||
@property
|
||||
def pwd_show_time(self):
|
||||
"""Time to show characters in password mode before change them to '*'"""
|
||||
return int(self._wv.safe_field("pwd_show_time", 0))
|
||||
return int(self._wv_lv_textarea_t.safe_field("pwd_show_time", 0))
|
||||
|
||||
@property
|
||||
def sel_start(self):
|
||||
"""Temporary values for text selection"""
|
||||
return int(self._wv.safe_field("sel_start", 0))
|
||||
return int(self._wv_lv_textarea_t.safe_field("sel_start", 0))
|
||||
|
||||
@property
|
||||
def sel_end(self):
|
||||
return int(self._wv.safe_field("sel_end", 0))
|
||||
return int(self._wv_lv_textarea_t.safe_field("sel_end", 0))
|
||||
|
||||
@property
|
||||
def text_sel_in_prog(self):
|
||||
"""User is in process of selecting"""
|
||||
return int(self._wv.safe_field("text_sel_in_prog", 0))
|
||||
return int(self._wv_lv_textarea_t.safe_field("text_sel_in_prog", 0))
|
||||
|
||||
@property
|
||||
def text_sel_en(self):
|
||||
"""Text can be selected on this text area"""
|
||||
return int(self._wv.safe_field("text_sel_en", 0))
|
||||
return int(self._wv_lv_textarea_t.safe_field("text_sel_en", 0))
|
||||
|
||||
@property
|
||||
def pwd_mode(self):
|
||||
"""Replace characters with '*'"""
|
||||
return int(self._wv.safe_field("pwd_mode", 0))
|
||||
return int(self._wv_lv_textarea_t.safe_field("pwd_mode", 0))
|
||||
|
||||
@property
|
||||
def one_line(self):
|
||||
"""One line mode (ignore line breaks)"""
|
||||
return int(self._wv.safe_field("one_line", 0))
|
||||
return int(self._wv_lv_textarea_t.safe_field("one_line", 0))
|
||||
|
||||
@property
|
||||
def static_accepted_chars(self):
|
||||
"""1: Only a pointer is saved in `accepted_chars`"""
|
||||
return int(self._wv.safe_field("static_accepted_chars", 0))
|
||||
return int(self._wv_lv_textarea_t.safe_field("static_accepted_chars", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -14,11 +14,12 @@ class LVTileview(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_tileview_t", ptr=True) or self
|
||||
self._wv_lv_tileview_t = self.cast("lv_tileview_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_tileview_t
|
||||
|
||||
@property
|
||||
def tile_act(self):
|
||||
return ptr_or_none(self._wv.safe_field("tile_act"))
|
||||
return ptr_or_none(self._wv_lv_tileview_t.safe_field("tile_act"))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -13,11 +13,12 @@ class LVTileviewTile(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_tileview_tile_t", ptr=True) or self
|
||||
self._wv_lv_tileview_tile_t = self.cast("lv_tileview_tile_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_tileview_tile_t
|
||||
|
||||
@property
|
||||
def dir(self):
|
||||
return int(self._wv.safe_field("dir", 0))
|
||||
return int(self._wv_lv_tileview_tile_t.safe_field("dir", 0))
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -13,7 +13,8 @@ class LVWin(LVObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._wv = self.cast("lv_win_t", ptr=True) or self
|
||||
self._wv_lv_win_t = self.cast("lv_win_t", ptr=True) or self
|
||||
self._wv = self._wv_lv_win_t
|
||||
|
||||
def snapshot(self, include_children=False, include_styles=False):
|
||||
"""Snapshot with widget-specific fields in widget_data."""
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
"""Runtime access to the field metadata behind the generated wrappers.
|
||||
|
||||
The wrappers carry the values; what the values *mean* - which fields matter
|
||||
first, what an enum's numbers are called, how a widget summarises itself - lives
|
||||
in the generator's widget_specs.json. The dashboard already ships that file to
|
||||
the browser, so reading the same file here keeps the terminal output and the
|
||||
HTML inspector telling one story instead of two that drift apart.
|
||||
|
||||
Everything degrades to the raw value when the file is missing, which is the case
|
||||
when the plugin is vendored without its scripts/ directory.
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
_SPECS_PATH = (
|
||||
Path(__file__).resolve().parents[3] / "scripts" / "generators" / "widget_specs.json"
|
||||
)
|
||||
|
||||
_specs = None
|
||||
|
||||
|
||||
def _load() -> dict:
|
||||
global _specs
|
||||
if _specs is None:
|
||||
try:
|
||||
_specs = json.loads(_SPECS_PATH.read_text(encoding="utf-8"))
|
||||
except (OSError, ValueError):
|
||||
_specs = {}
|
||||
return _specs
|
||||
|
||||
|
||||
def widget_spec(class_name: str) -> dict:
|
||||
"""The spec for a class name, with or without the 'lv_' prefix."""
|
||||
specs = _load()
|
||||
return specs.get(class_name) or specs.get("lv_" + class_name) or {}
|
||||
|
||||
|
||||
def field_types(class_name: str) -> dict:
|
||||
"""Map of field name -> generator field type ('int', 'bool', 'enum:4', ...)."""
|
||||
return widget_spec(class_name).get("_auto", {}).get("fields", {})
|
||||
|
||||
|
||||
def order_fields(class_name: str, keys) -> list:
|
||||
"""Order widget_data keys: the spec's primary fields first, rest after."""
|
||||
primary = widget_spec(class_name).get("primary", [])
|
||||
keys = list(keys)
|
||||
head = [k for k in primary if k in keys]
|
||||
return head + [k for k in keys if k not in head]
|
||||
|
||||
|
||||
def format_value(class_name: str, field: str, value) -> str:
|
||||
"""Render one widget_data value: enums by name, points and areas inline."""
|
||||
if value is None:
|
||||
return "-"
|
||||
|
||||
names = widget_spec(class_name).get("enums", {}).get(field)
|
||||
if names is not None and isinstance(value, int) and not isinstance(value, bool):
|
||||
if 0 <= value < len(names):
|
||||
return f"{names[value]} ({value})"
|
||||
return str(value)
|
||||
|
||||
if isinstance(value, dict):
|
||||
if {"x1", "y1", "x2", "y2"} <= value.keys():
|
||||
return (
|
||||
f"({value['x1']},{value['y1']})-({value['x2']},{value['y2']})"
|
||||
)
|
||||
if {"x", "y"} <= value.keys():
|
||||
return f"({value['x']},{value['y']})"
|
||||
|
||||
if field_types(class_name).get(field) == "bool":
|
||||
return str(bool(value))
|
||||
|
||||
if isinstance(value, str):
|
||||
return f'"{escape(value)}"'
|
||||
|
||||
return str(value)
|
||||
|
||||
|
||||
def escape(text: str) -> str:
|
||||
"""Keep a value on one line: a label's text may well be several."""
|
||||
return text.replace("\\", "\\\\").replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t")
|
||||
|
||||
|
||||
def summary(class_name: str, widget_data: dict, limit: int = 32) -> str:
|
||||
"""One-line gist of a widget, from the spec's summary template."""
|
||||
tpl = widget_spec(class_name).get("summary_tpl")
|
||||
if not tpl or not widget_data:
|
||||
return ""
|
||||
|
||||
import re
|
||||
|
||||
def _sub(m):
|
||||
value = widget_data.get(m.group(1))
|
||||
if value is None:
|
||||
return ""
|
||||
text = escape(str(value))
|
||||
return text[:limit] + "…" if len(text) > limit else text
|
||||
|
||||
return re.sub(r"\{(\w+)\}", _sub, tpl).strip()
|
||||
@@ -33,6 +33,9 @@ def parse_enum(path: Path, enum_type: str, prefix: str,
|
||||
raise RuntimeError(f"Cannot find {enum_type} enum in {path}")
|
||||
|
||||
entries = {}
|
||||
# #defines of the header, plus the members already seen: an enum member is
|
||||
# commonly defined in terms of earlier ones.
|
||||
known = _defines(text)
|
||||
current_val = 0
|
||||
for line in m.group(1).splitlines():
|
||||
line = line.strip().rstrip(",")
|
||||
@@ -45,18 +48,38 @@ def parse_enum(path: Path, enum_type: str, prefix: str,
|
||||
):
|
||||
continue
|
||||
|
||||
match = re.match(
|
||||
rf"({re.escape(prefix)}\w+)\s*=\s*(0x[\da-fA-F]+|\d+)", line
|
||||
)
|
||||
match = re.match(rf"({re.escape(prefix)}\w+)\s*=\s*(.+)$", line)
|
||||
if match:
|
||||
name = match.group(1)
|
||||
current_val = int(match.group(2), 0)
|
||||
# A comment can start on the same line as the value, and a Doxygen
|
||||
# one often runs on to the next; only the expression before it is
|
||||
# the value. LV_EVENT_PREPROCESS = 0x8000 is written that way. A
|
||||
# comment that closes again can also sit between two operands, so
|
||||
# drop those first rather than truncating the expression at one.
|
||||
expression = re.sub(r"/\*.*?\*/", " ", match.group(2))
|
||||
expression = re.split(r"//|/\*", expression, maxsplit=1)[0]
|
||||
value = eval_c_expr(expression.rstrip().rstrip(","), known)
|
||||
if value is None:
|
||||
# Falling back to the running counter would give this member a
|
||||
# plausible wrong number and shift every implicit member after
|
||||
# it, which is impossible to notice in the generated table.
|
||||
raise ValueError(
|
||||
f"{name} is assigned `{expression.strip()}`, which this "
|
||||
f"parser cannot evaluate. Teach eval_c_expr about it rather "
|
||||
f"than letting the value be guessed."
|
||||
)
|
||||
current_val = value
|
||||
else:
|
||||
match = re.match(rf"({re.escape(prefix)}\w+)", line)
|
||||
if not match:
|
||||
continue
|
||||
name = match.group(1)
|
||||
|
||||
# A skipped member is still a member: a later one may be written in
|
||||
# terms of it, so it has to be resolvable even though it is left out
|
||||
# of the table.
|
||||
known[name] = current_val
|
||||
|
||||
if name in skip:
|
||||
current_val += 1
|
||||
continue
|
||||
@@ -68,6 +91,54 @@ def parse_enum(path: Path, enum_type: str, prefix: str,
|
||||
return entries
|
||||
|
||||
|
||||
_DEFINE = re.compile(r"^#define\s+(_?LV_[A-Z0-9_]+)\s+(.+)$", re.M)
|
||||
|
||||
|
||||
def _defines(text: str) -> dict:
|
||||
"""The #define constants of a header, so enum values can reference them."""
|
||||
return {name: value.split("/*")[0].strip()
|
||||
for name, value in _DEFINE.findall(text)}
|
||||
|
||||
|
||||
def eval_c_expr(expr: str, known: dict) -> "int | None":
|
||||
"""Evaluate a C constant expression, or None if it cannot be resolved.
|
||||
|
||||
Enum members are regularly defined in terms of others -
|
||||
`LV_FLEX_FLOW_ROW_WRAP = LV_FLEX_FLOW_ROW | LV_FLEX_WRAP` - and a parser
|
||||
that only accepts literals silently numbers them sequentially instead,
|
||||
which turns a bitmask into consecutive integers.
|
||||
"""
|
||||
expr = expr.strip().rstrip(",")
|
||||
if not expr:
|
||||
return None
|
||||
for _ in range(8):
|
||||
replaced = re.sub(
|
||||
r"_?LV_[A-Z0-9_]+",
|
||||
lambda m: f"({known[m.group(0)]})" if m.group(0) in known else m.group(0),
|
||||
expr,
|
||||
)
|
||||
if replaced == expr:
|
||||
break
|
||||
expr = replaced
|
||||
# Integer suffixes, then blank out hex literals so their letters are not
|
||||
# mistaken for an unresolved identifier.
|
||||
expr = re.sub(r"\b(0[xX][0-9a-fA-F]+|\d+)[uUlL]+\b", r"\1", expr)
|
||||
if re.search(r"[A-Za-z_]", re.sub(r"0[xX][0-9a-fA-F]+", "0", expr)):
|
||||
return None
|
||||
if not re.fullmatch(r"[\d\sxXa-fA-F|&^~<>+\-*/()]+", expr):
|
||||
return None
|
||||
try:
|
||||
value = int(eval(expr, {"__builtins__": {}}, {}))
|
||||
except Exception:
|
||||
return None
|
||||
# Python's ints are unbounded, so ~0x0F is -16 where C's 32-bit unsigned
|
||||
# gives 0xFFFFFFF0. Only complement can produce that difference here: the
|
||||
# other operators agree for the values LVGL's headers hold.
|
||||
if "~" in expr and value < 0:
|
||||
value &= 0xFFFFFFFF
|
||||
return value
|
||||
|
||||
|
||||
def parse_bitmask_enum(path: Path, enum_type: str, prefix: str,
|
||||
skip: set[str] | None = None) -> dict[int, str]:
|
||||
"""Parse a C typedef enum with bitmask values (1u << N) from a header.
|
||||
@@ -105,9 +176,14 @@ def parse_bitmask_enum(path: Path, enum_type: str, prefix: str,
|
||||
):
|
||||
continue
|
||||
|
||||
# Match: NAME = (1u << N) or NAME = 1 << N (parens optional)
|
||||
# Match: NAME = (1u << N) or NAME = 1 << N (parens optional), and only
|
||||
# that. Without anchoring the end, `NAME = (1 << 5) | (1 << 6)` would
|
||||
# match its first shift and be recorded as a single-bit flag, which is
|
||||
# worse than leaving a combination out of a bit table.
|
||||
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+)\)?"
|
||||
r"\s*,?\s*(?:/[/*].*)?$",
|
||||
line,
|
||||
)
|
||||
if not match:
|
||||
continue
|
||||
|
||||
@@ -6,10 +6,12 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from lvgl_paths import include_dir, lvgl_root
|
||||
from enum_parser import parse_enum, generate_dict_module
|
||||
|
||||
LVGL_SRC = Path(__file__).parent.parent.parent.parent.parent / "src"
|
||||
LVGL_INC = Path(__file__).parent.parent.parent.parent.parent / "include" / "lvgl"
|
||||
LVGL_ROOT = lvgl_root(__file__)
|
||||
LVGL_SRC = LVGL_ROOT / "src"
|
||||
LVGL_INC = include_dir(LVGL_ROOT)
|
||||
OUTPUT = Path(__file__).parent.parent.parent / "lvglgdb" / "lvgl" / "draw" / "lv_draw_consts.py"
|
||||
|
||||
DRAW_H = LVGL_INC / "draw" / "lv_draw.h"
|
||||
|
||||
@@ -5,9 +5,10 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from lvgl_paths import include_dir, lvgl_root
|
||||
from enum_parser import parse_enum, generate_dict_module
|
||||
|
||||
LVGL_INC = Path(__file__).parent.parent.parent.parent.parent / "include" / "lvgl"
|
||||
LVGL_INC = include_dir(lvgl_root(__file__))
|
||||
OUTPUT = Path(__file__).parent.parent.parent / "lvglgdb" / "lvgl" / "misc" / "lv_event_consts.py"
|
||||
|
||||
SKIP_EVENTS = {"LV_EVENT_LAST", "LV_EVENT_PREPROCESS", "LV_EVENT_MARKED_DELETING"}
|
||||
|
||||
@@ -5,9 +5,10 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from lvgl_paths import include_dir, lvgl_root
|
||||
from enum_parser import parse_enum, generate_dict_module
|
||||
|
||||
LVGL_INC = Path(__file__).parent.parent.parent.parent.parent / "include" / "lvgl"
|
||||
LVGL_INC = include_dir(lvgl_root(__file__))
|
||||
OUTPUT = Path(__file__).parent.parent.parent / "lvglgdb" / "lvgl" / "core" / "lv_indev_consts.py"
|
||||
|
||||
|
||||
|
||||
@@ -5,9 +5,10 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from lvgl_paths import include_dir, lvgl_root
|
||||
from enum_parser import parse_bitmask_enum, generate_dict_module
|
||||
|
||||
LVGL_INC = Path(__file__).parent.parent.parent.parent.parent / "include" / "lvgl"
|
||||
LVGL_INC = include_dir(lvgl_root(__file__))
|
||||
OUTPUT = Path(__file__).parent.parent.parent / "lvglgdb" / "lvgl" / "core" / "lv_obj_flag_consts.py"
|
||||
|
||||
|
||||
|
||||
@@ -5,9 +5,10 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from lvgl_paths import include_dir, lvgl_root
|
||||
from enum_parser import parse_bitmask_enum, generate_dict_module
|
||||
|
||||
LVGL_INC = Path(__file__).parent.parent.parent.parent.parent / "include" / "lvgl"
|
||||
LVGL_INC = include_dir(lvgl_root(__file__))
|
||||
OUTPUT = Path(__file__).parent.parent.parent / "lvglgdb" / "lvgl" / "core" / "lv_obj_state_consts.py"
|
||||
|
||||
SKIP = {"LV_STATE_ANY"}
|
||||
|
||||
@@ -2,16 +2,23 @@
|
||||
"""Generate style constant tables from LVGL headers."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
LVGL_INC = Path(__file__).parent.parent.parent.parent.parent / "include" / "lvgl"
|
||||
LVGL_SRC = Path(__file__).parent.parent.parent.parent.parent / "src"
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from lvgl_paths import include_dir, lvgl_root
|
||||
from enum_parser import parse_enum
|
||||
|
||||
LVGL_ROOT = lvgl_root(__file__)
|
||||
LVGL_INC = include_dir(LVGL_ROOT)
|
||||
LVGL_SRC = LVGL_ROOT / "src"
|
||||
GDB_ROOT = Path(__file__).parent.parent.parent
|
||||
OUTPUT = GDB_ROOT / "lvglgdb" / "lvgl" / "misc" / "lv_style_consts.py"
|
||||
|
||||
STYLE_H = LVGL_INC / "core" / "lv_style.h"
|
||||
OBJ_STYLE_H = LVGL_INC / "core" / "lv_obj_style.h"
|
||||
STYLE_GEN_H = LVGL_INC / "core" / "lv_style_gen.h"
|
||||
AREA_H = LVGL_INC / "core" / "lv_area.h"
|
||||
|
||||
SKIP_PROPS = {
|
||||
"LV_STYLE_PROP_INV",
|
||||
@@ -113,22 +120,101 @@ def parse_color_props(style_gen_h: Path, prop_map: dict[int, str]) -> set[int]:
|
||||
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."""
|
||||
def _props_by_value_type(
|
||||
style_gen_h: Path, prop_map: dict[int, str], type_pattern: str
|
||||
) -> set[int]:
|
||||
"""Identify properties whose lv_style_gen.h setter takes the given value type."""
|
||||
text = style_gen_h.read_text()
|
||||
name_to_id = {v: k for k, v in prop_map.items()}
|
||||
ptr_ids = set()
|
||||
ids = set()
|
||||
for match in re.finditer(
|
||||
r"void\s+lv_style_set_(\w+)\s*\([^,]+,\s*(?:const\s+)?(?:void|lv_\w+)\s*\*",
|
||||
text,
|
||||
rf"void\s+lv_style_set_(\w+)\s*\([^,]+,\s*{type_pattern}\s+value\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
|
||||
ids.add(name_to_id[prop_name])
|
||||
return ids
|
||||
|
||||
|
||||
def generate(props, parts, states, color_ids, pointer_ids) -> str:
|
||||
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.
|
||||
|
||||
Matches any pointer type, so `const int32_t *` (the grid templates) is
|
||||
classified as a pointer rather than read as a number.
|
||||
"""
|
||||
return _props_by_value_type(style_gen_h, prop_map, r"(?:const\s+)?\w+\s*\*")
|
||||
|
||||
|
||||
def parse_src_props(style_gen_h: Path, prop_map: dict[int, str]) -> set[int]:
|
||||
"""Identify `const void *` properties: the image sources.
|
||||
|
||||
Their value may be an `lv_image_dsc_t *`, a file path or an `LV_SYMBOL_*`
|
||||
string, so the formatter is allowed to try reading them as C strings.
|
||||
"""
|
||||
return _props_by_value_type(style_gen_h, prop_map, r"const\s+void\s*\*")
|
||||
|
||||
|
||||
def parse_coord_props(style_gen_h: Path, prop_map: dict[int, str]) -> set[int]:
|
||||
"""Identify `int32_t` properties: the ones that may hold LV_PCT()/LV_SIZE_CONTENT."""
|
||||
return _props_by_value_type(style_gen_h, prop_map, r"int32_t")
|
||||
|
||||
|
||||
def parse_bool_props(style_gen_h: Path, prop_map: dict[int, str]) -> set[int]:
|
||||
"""Identify `bool` properties."""
|
||||
return _props_by_value_type(style_gen_h, prop_map, r"bool")
|
||||
|
||||
|
||||
def parse_enum_props(
|
||||
style_gen_h: Path, prop_map: dict[int, str]
|
||||
) -> dict[int, dict[int, str]]:
|
||||
"""Map each enum-valued property to its {value: name} table.
|
||||
|
||||
The setter signature names the enum type; the type's own header is found by
|
||||
searching the include tree for its typedef.
|
||||
"""
|
||||
text = style_gen_h.read_text()
|
||||
name_to_id = {v: k for k, v in prop_map.items()}
|
||||
headers = list(LVGL_INC.rglob("*.h"))
|
||||
enum_tables: dict[str, dict[int, str]] = {}
|
||||
result = {}
|
||||
|
||||
for match in re.finditer(
|
||||
r"void\s+lv_style_set_(\w+)\s*\([^,]+,\s*(lv_\w+_t)\s+value\s*\)", text
|
||||
):
|
||||
prop_name, enum_type = match.group(1).upper(), match.group(2)
|
||||
if prop_name not in name_to_id or enum_type in ("lv_color_t", "lv_opa_t"):
|
||||
continue
|
||||
if enum_type not in enum_tables:
|
||||
enum_tables[enum_type] = _parse_named_enum(headers, enum_type)
|
||||
if enum_tables[enum_type]:
|
||||
result[name_to_id[prop_name]] = enum_tables[enum_type]
|
||||
return result
|
||||
|
||||
|
||||
def _parse_named_enum(headers: list[Path], enum_type: str) -> dict[int, str]:
|
||||
"""Find and parse an enum typedef by name, {} if it cannot be found."""
|
||||
needle = f"}} {enum_type};"
|
||||
prefix = enum_type.removesuffix("_t").upper() + "_"
|
||||
for header in headers:
|
||||
if needle not in header.read_text():
|
||||
continue
|
||||
try:
|
||||
return parse_enum(header, enum_type, prefix)
|
||||
except RuntimeError:
|
||||
return {}
|
||||
return {}
|
||||
|
||||
|
||||
def parse_coord_shift(area_h: Path) -> int:
|
||||
"""Read LV_COORD_TYPE_SHIFT, which defines the LV_PCT()/content encoding."""
|
||||
m = re.search(r"#define\s+LV_COORD_TYPE_SHIFT\s+\((\d+)U?\)", area_h.read_text())
|
||||
if not m:
|
||||
raise RuntimeError("Cannot find LV_COORD_TYPE_SHIFT")
|
||||
return int(m.group(1))
|
||||
|
||||
|
||||
def generate(props, parts, states, color_ids, pointer_ids, src_ids, coord_ids,
|
||||
bool_ids, enum_values, coord_shift) -> str:
|
||||
"""Generate Python source for the style constants module."""
|
||||
lines = [
|
||||
'"""',
|
||||
@@ -166,13 +252,32 @@ def generate(props, parts, states, color_ids, pointer_ids) -> str:
|
||||
lines.append("COLOR_PROPS = set()")
|
||||
lines.append("")
|
||||
|
||||
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()")
|
||||
for name, ids in (
|
||||
("POINTER_PROPS", pointer_ids),
|
||||
("SRC_PROPS", src_ids),
|
||||
("COORD_PROPS", coord_ids),
|
||||
("BOOL_PROPS", bool_ids),
|
||||
):
|
||||
if ids:
|
||||
lines.append(f"{name} = {{")
|
||||
for v in sorted(ids):
|
||||
lines.append(f" {v}, # {props.get(v, '?')}")
|
||||
lines.append("}")
|
||||
else:
|
||||
lines.append(f"{name} = set()")
|
||||
lines.append("")
|
||||
|
||||
lines.append("ENUM_PROP_VALUES = {")
|
||||
for prop_id in sorted(enum_values):
|
||||
lines.append(f" {prop_id}: {{ # {props.get(prop_id, '?')}")
|
||||
for val in sorted(enum_values[prop_id]):
|
||||
lines.append(f' {val}: "{enum_values[prop_id][val]}",')
|
||||
lines.append(" },")
|
||||
lines.append("}")
|
||||
lines.append("")
|
||||
|
||||
lines.append("# From LV_COORD_TYPE_SHIFT in lv_area.h")
|
||||
lines.append(f"COORD_TYPE_SHIFT = {coord_shift}")
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
@@ -184,12 +289,19 @@ def main():
|
||||
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_ids = parse_src_props(STYLE_GEN_H, props)
|
||||
coord_ids = parse_coord_props(STYLE_GEN_H, props)
|
||||
bool_ids = parse_bool_props(STYLE_GEN_H, props)
|
||||
enum_values = parse_enum_props(STYLE_GEN_H, props)
|
||||
coord_shift = parse_coord_shift(AREA_H)
|
||||
|
||||
src = generate(props, parts, states, color_ids, pointer_ids)
|
||||
src = generate(props, parts, states, color_ids, pointer_ids, src_ids,
|
||||
coord_ids, bool_ids, enum_values, coord_shift)
|
||||
OUTPUT.write_text(src)
|
||||
print(
|
||||
f"Generated {OUTPUT.name} ({len(props)} props, {len(parts)} parts, "
|
||||
f"{len(states)} states, {len(color_ids)} color, {len(pointer_ids)} pointer)"
|
||||
f"{len(states)} states, {len(color_ids)} color, {len(pointer_ids)} pointer, "
|
||||
f"{len(coord_ids)} coord, {len(bool_ids)} bool, {len(enum_values)} enum)"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -5,9 +5,10 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from lvgl_paths import include_dir, lvgl_root
|
||||
from enum_parser import parse_enum, generate_dict_module
|
||||
|
||||
LVGL_INC = Path(__file__).parent.parent.parent.parent.parent / "include" / "lvgl"
|
||||
LVGL_INC = include_dir(lvgl_root(__file__))
|
||||
OUTPUT = Path(__file__).parent.parent.parent / "lvglgdb" / "lvgl" / "core" / "lv_observer_consts.py"
|
||||
|
||||
|
||||
|
||||
@@ -24,9 +24,11 @@ from pathlib import Path
|
||||
from dataclasses import dataclass, field as dc_field
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from lvgl_paths import include_dir, lvgl_root # noqa: E402
|
||||
|
||||
LVGL_SRC = Path(__file__).parent.parent.parent.parent.parent / "src"
|
||||
LVGL_INC = Path(__file__).parent.parent.parent.parent.parent / "include" / "lvgl"
|
||||
LVGL_ROOT = lvgl_root(__file__)
|
||||
LVGL_SRC = LVGL_ROOT / "src"
|
||||
LVGL_INC = include_dir(LVGL_ROOT)
|
||||
WIDGETS_DIR = LVGL_SRC / "widgets"
|
||||
OUTPUT_DIR = Path(__file__).parent.parent.parent / "lvglgdb" / "lvgl" / "widgets"
|
||||
SPECS_JSON = Path(__file__).parent / "widget_specs.json"
|
||||
@@ -42,7 +44,9 @@ SIMPLE_INT_TYPES = {
|
||||
def _scan_enum_types() -> set[str]:
|
||||
"""Scan LVGL headers to find all typedef enum and int-like alias type names."""
|
||||
result = set()
|
||||
headers = list(LVGL_SRC.rglob("*.h")) + list(LVGL_INC.rglob("*.h"))
|
||||
# In a checkout LVGL_INC *is* LVGL_SRC, so the two trees have to be merged
|
||||
# rather than concatenated or every header is read twice.
|
||||
headers = sorted({*LVGL_SRC.rglob("*.h"), *LVGL_INC.rglob("*.h")})
|
||||
for h in headers:
|
||||
text = h.read_text(errors="ignore")
|
||||
# typedef enum { ... } lv_xxx_t;
|
||||
@@ -235,22 +239,28 @@ def parse_widgets() -> dict[str, WidgetDef]:
|
||||
return widgets
|
||||
|
||||
|
||||
def _field_expr(f: StructField) -> str | None:
|
||||
"""Return snapshot expression for a field, or None to skip."""
|
||||
def _field_expr(f: StructField, attr: str = "_wv") -> str | None:
|
||||
"""Return snapshot expression for a field, or None to skip.
|
||||
|
||||
`attr` is the class's own cast of the object. Each class needs its own,
|
||||
because a widget struct embeds its ancestor as the first member: casting
|
||||
an lv_slider_t to lv_bar_t is valid, but reading lv_bar_t's fields off an
|
||||
lv_slider_t cast is not, and safe_field() would quietly return the default.
|
||||
"""
|
||||
if f.is_array:
|
||||
return None
|
||||
if f.is_obj_pointer:
|
||||
return f'ptr_or_none(self._wv.safe_field("{f.name}"))'
|
||||
return f'ptr_or_none(self.{attr}.safe_field("{f.name}"))'
|
||||
if f.is_string:
|
||||
return f'safe_string(self._wv, "{f.name}")'
|
||||
return f'safe_string(self.{attr}, "{f.name}")'
|
||||
if f.is_pointer:
|
||||
return f'ptr_or_none(self._wv.safe_field("{f.name}"))'
|
||||
return f'ptr_or_none(self.{attr}.safe_field("{f.name}"))'
|
||||
if f.c_type == "lv_color_t":
|
||||
return f'safe_color(self._wv, "{f.name}")'
|
||||
return f'safe_color(self.{attr}, "{f.name}")'
|
||||
if f.c_type == "lv_area_t":
|
||||
return f'safe_area(self._wv, "{f.name}")'
|
||||
return f'safe_area(self.{attr}, "{f.name}")'
|
||||
if f.c_type == "lv_point_t":
|
||||
return f'safe_point(self._wv, "{f.name}")'
|
||||
return f'safe_point(self.{attr}, "{f.name}")'
|
||||
# Known wrapper types — use snapshot() for rich output
|
||||
_WRAPPER_TYPES = {
|
||||
"lv_draw_buf_t": ("lvglgdb.lvgl.draw.lv_draw_buf", "LVDrawBuf"),
|
||||
@@ -260,13 +270,13 @@ def _field_expr(f: StructField) -> str | None:
|
||||
}
|
||||
if f.c_type in _WRAPPER_TYPES:
|
||||
mod, cls = _WRAPPER_TYPES[f.c_type]
|
||||
return f'safe_wrapper(self._wv, "{f.name}", "{mod}", "{cls}")'
|
||||
return f'safe_wrapper(self.{attr}, "{f.name}", "{mod}", "{cls}")'
|
||||
if f.is_bitfield or f.c_type in SIMPLE_INT_TYPES or f.c_type.startswith(("uint", "int")):
|
||||
return f'int(self._wv.safe_field("{f.name}", 0))'
|
||||
return f'int(self.{attr}.safe_field("{f.name}", 0))'
|
||||
# TODO: implement generic struct expansion (Value.to_dict) for
|
||||
# non-enum lv_*_t types like lv_calendar_date_t.
|
||||
if f.c_type in _INT_SAFE_TYPES:
|
||||
return f'int(self._wv.safe_field("{f.name}", 0))'
|
||||
return f'int(self.{attr}.safe_field("{f.name}", 0))'
|
||||
return None
|
||||
|
||||
|
||||
@@ -372,20 +382,38 @@ def safe_point(obj, field_name):
|
||||
|
||||
|
||||
def safe_wrapper(obj, field_name, module_path, class_name):
|
||||
"""Read a struct field using its known Value wrapper, return snapshot dict."""
|
||||
"""Read a struct field using its known Value wrapper, return snapshot dict.
|
||||
|
||||
Not every wrapper has a snapshot(); LVArray does not. Value forwards an
|
||||
unknown attribute to gdb.Value.__getitem__, so asking for one raises
|
||||
gdb.error rather than AttributeError, and the caller would otherwise lose
|
||||
the whole widget - and its subtree - over a single field.
|
||||
"""
|
||||
import gdb
|
||||
|
||||
val = obj.safe_field(field_name)
|
||||
if val is None or not getattr(val, 'is_ok', True):
|
||||
return None
|
||||
import importlib
|
||||
mod = importlib.import_module(module_path)
|
||||
cls = getattr(mod, class_name)
|
||||
wrapper = cls(val)
|
||||
return wrapper.snapshot().as_dict()
|
||||
try:
|
||||
mod = importlib.import_module(module_path)
|
||||
cls = getattr(mod, class_name)
|
||||
wrapper = cls(val)
|
||||
if getattr(type(wrapper), "snapshot", None) is None:
|
||||
# An embedded struct - lv_scale_t.needles - is not convertible to an
|
||||
# int, so the address has to come from the field itself.
|
||||
if val.type.strip_typedefs().code != gdb.TYPE_CODE_PTR:
|
||||
val = val.address
|
||||
return {"addr": hex(int(val)), "type": class_name}
|
||||
return wrapper.snapshot().as_dict()
|
||||
except (gdb.error, gdb.MemoryError, ImportError, AttributeError, TypeError):
|
||||
return None
|
||||
'''
|
||||
|
||||
|
||||
def gen_widget_file(wdef: WidgetDef, widgets: dict[str, WidgetDef]) -> str:
|
||||
"""Generate a single widget module file."""
|
||||
attr = f"_wv_{wdef.c_type}"
|
||||
lines = [
|
||||
'"""',
|
||||
f"Auto-generated wrapper for {wdef.c_type}.",
|
||||
@@ -406,7 +434,7 @@ def gen_widget_file(wdef: WidgetDef, widgets: dict[str, WidgetDef]) -> str:
|
||||
# Import helpers only if needed
|
||||
needs = set()
|
||||
for f in wdef.fields:
|
||||
expr = _field_expr(f)
|
||||
expr = _field_expr(f, attr)
|
||||
if expr is None:
|
||||
continue
|
||||
if "ptr_or_none" in expr:
|
||||
@@ -435,13 +463,14 @@ def gen_widget_file(wdef: WidgetDef, widgets: dict[str, WidgetDef]) -> str:
|
||||
lines.append("")
|
||||
lines.append(f" def __init__(self, obj):")
|
||||
lines.append(f" super().__init__(obj)")
|
||||
lines.append(f' self._wv = self.cast("{wdef.c_type}", ptr=True) or self')
|
||||
lines.append(f' self.{attr} = self.cast("{wdef.c_type}", ptr=True) or self')
|
||||
lines.append(f" self._wv = self.{attr}")
|
||||
lines.append("")
|
||||
|
||||
# Properties
|
||||
snapshot_fields = []
|
||||
for f in wdef.fields:
|
||||
expr = _field_expr(f)
|
||||
expr = _field_expr(f, attr)
|
||||
if expr is None:
|
||||
continue
|
||||
lines.append(f" @property")
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
"""Where LVGL is, for the generators that read its headers.
|
||||
|
||||
Walking a fixed number of parents up from the generator's own file only works
|
||||
while the plugin sits in the layout it ships in. The plugin is also vendored
|
||||
into other projects, and an installed LVGL keeps its headers somewhere else
|
||||
again, so the checkout is searched for instead of assumed - and a generator that
|
||||
cannot find one stops rather than writing an empty table over a good one.
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _is_lvgl(path):
|
||||
"""A checkout, or an installed tree that only has the public headers."""
|
||||
if (path / "lv_version.h").is_file():
|
||||
return (path / "src").is_dir() or (path / "include" / "lvgl").is_dir()
|
||||
# An installed tree has no top-level lv_version.h, only the copy that was
|
||||
# installed with the rest of the public headers.
|
||||
return (path / "include" / "lvgl" / "lv_version.h").is_file()
|
||||
|
||||
|
||||
def lvgl_root(start=None):
|
||||
"""The LVGL checkout to read: $LVGL_ROOT, or the nearest one above `start`."""
|
||||
from_env = os.environ.get("LVGL_ROOT")
|
||||
if from_env:
|
||||
root = Path(from_env).expanduser().resolve()
|
||||
if not _is_lvgl(root):
|
||||
raise SystemExit(
|
||||
f"LVGL_ROOT is {root}, which has no lv_version.h beside a src/ "
|
||||
f"or include/lvgl/, and none inside include/lvgl/ either"
|
||||
)
|
||||
return root
|
||||
|
||||
here = Path(start or __file__).resolve()
|
||||
for parent in here.parents:
|
||||
if _is_lvgl(parent):
|
||||
return parent
|
||||
raise SystemExit(
|
||||
f"no LVGL checkout above {here}. Set LVGL_ROOT to one, for example\n"
|
||||
f" LVGL_ROOT=~/lvgl python3 {Path(here).name}"
|
||||
)
|
||||
|
||||
|
||||
def include_dir(root):
|
||||
"""Where the public headers are: include/lvgl if installed, else src/."""
|
||||
installed = root / "include" / "lvgl"
|
||||
return installed if installed.is_dir() else root / "src"
|
||||
@@ -0,0 +1,101 @@
|
||||
"""Tests for the generator helpers under scripts/.
|
||||
|
||||
These read C headers and the filesystem, so they need no target and no GDB -
|
||||
unlike test_style_decode.py, nothing here imports lvglgdb.
|
||||
|
||||
Run from the GDB script root with:
|
||||
python3 -m unittest discover tests
|
||||
"""
|
||||
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(ROOT / "scripts"))
|
||||
|
||||
from enum_parser import parse_enum # noqa: E402
|
||||
from lvgl_paths import _is_lvgl, include_dir # noqa: E402
|
||||
|
||||
|
||||
def write_enum(directory: Path, body: str) -> Path:
|
||||
"""A header holding one typedef enum, as parse_enum expects to find it."""
|
||||
path = directory / "lv_test.h"
|
||||
path.write_text("typedef enum {\n" + body + "\n} lv_test_t;\n")
|
||||
return path
|
||||
|
||||
|
||||
class TestParseEnum(unittest.TestCase):
|
||||
def test_a_trailing_comment_is_not_part_of_the_value(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path = write_enum(Path(tmp), """
|
||||
LV_TEST_A = 0x10, /** the first one, which runs
|
||||
* on to the next line */
|
||||
LV_TEST_B = 0x20,
|
||||
""")
|
||||
self.assertEqual(
|
||||
parse_enum(path, "lv_test_t", "LV_TEST_"),
|
||||
{0x10: "A", 0x20: "B"},
|
||||
)
|
||||
|
||||
def test_a_comment_between_operands_does_not_truncate_the_value(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path = write_enum(Path(tmp), """
|
||||
LV_TEST_A = 0x10,
|
||||
LV_TEST_B = LV_TEST_A /* keep going */ | 0x01,
|
||||
""")
|
||||
self.assertEqual(
|
||||
parse_enum(path, "lv_test_t", "LV_TEST_"),
|
||||
{0x10: "A", 0x11: "B"},
|
||||
)
|
||||
|
||||
def test_a_skipped_member_can_still_be_referenced(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path = write_enum(Path(tmp), """
|
||||
LV_TEST_A = 0x10,
|
||||
LV_TEST_HIDDEN = 0x20,
|
||||
LV_TEST_B = LV_TEST_HIDDEN | 0x01,
|
||||
""")
|
||||
entries = parse_enum(path, "lv_test_t", "LV_TEST_",
|
||||
skip={"LV_TEST_HIDDEN"})
|
||||
self.assertEqual(entries, {0x10: "A", 0x21: "B"})
|
||||
|
||||
def test_an_unevaluable_value_is_an_error_not_a_guess(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path = write_enum(Path(tmp), """
|
||||
LV_TEST_A = some_call(3),
|
||||
""")
|
||||
with self.assertRaises(ValueError):
|
||||
parse_enum(path, "lv_test_t", "LV_TEST_")
|
||||
|
||||
|
||||
class TestLvglPaths(unittest.TestCase):
|
||||
def make_tree(self, tmp: str, *files: str) -> Path:
|
||||
root = Path(tmp)
|
||||
for name in files:
|
||||
path = root / name
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text("")
|
||||
return root
|
||||
|
||||
def test_a_checkout_is_found(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = self.make_tree(tmp, "lv_version.h", "src/lv_init.c")
|
||||
self.assertTrue(_is_lvgl(root))
|
||||
self.assertEqual(include_dir(root), root / "src")
|
||||
|
||||
def test_an_installed_tree_is_found(self):
|
||||
"""No top-level lv_version.h, only the installed public headers."""
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = self.make_tree(tmp, "include/lvgl/lv_version.h")
|
||||
self.assertTrue(_is_lvgl(root))
|
||||
self.assertEqual(include_dir(root), root / "include" / "lvgl")
|
||||
|
||||
def test_an_unrelated_directory_is_not_lvgl(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
self.assertFalse(_is_lvgl(self.make_tree(tmp, "src/main.c")))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,152 @@
|
||||
"""Tests for the style value decoders.
|
||||
|
||||
The decoders are pure functions, so they are tested without a live target. GDB
|
||||
is stubbed because importing lvglgdb.value needs a subclassable gdb.Value.
|
||||
|
||||
Run from the GDB script root with:
|
||||
python3 -m unittest discover tests
|
||||
"""
|
||||
|
||||
import sys
|
||||
import types
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def _install_stubs():
|
||||
"""Make lvglgdb.lvgl.misc.lv_style importable outside GDB."""
|
||||
if "gdb" not in sys.modules:
|
||||
gdb = types.ModuleType("gdb")
|
||||
|
||||
class _Error(Exception):
|
||||
pass
|
||||
|
||||
class _Value:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
class _Type:
|
||||
pass
|
||||
|
||||
gdb.Value = _Value
|
||||
gdb.Type = _Type
|
||||
gdb.error = _Error
|
||||
gdb.MemoryError = _Error
|
||||
for code, name in enumerate(
|
||||
("PTR", "STRUCT", "UNION", "INT", "ENUM", "ARRAY", "TYPEDEF")
|
||||
):
|
||||
setattr(gdb, f"TYPE_CODE_{name}", code)
|
||||
sys.modules["gdb"] = gdb
|
||||
|
||||
# Bind the packages to their directories without running their __init__,
|
||||
# which pulls in the GDB command layer.
|
||||
for name in ("lvglgdb", "lvglgdb.lvgl", "lvglgdb.lvgl.misc"):
|
||||
if name not in sys.modules:
|
||||
module = types.ModuleType(name)
|
||||
module.__path__ = [str(ROOT.joinpath(*name.split(".")))]
|
||||
sys.modules[name] = module
|
||||
|
||||
|
||||
sys.path.insert(0, str(ROOT))
|
||||
_install_stubs()
|
||||
|
||||
from lvglgdb.lvgl.misc.lv_style import decode_coord, decode_enum # noqa: E402
|
||||
from lvglgdb.lvgl.misc.lv_style_consts import ( # noqa: E402
|
||||
COORD_PROPS,
|
||||
COORD_TYPE_SHIFT,
|
||||
ENUM_PROP_VALUES,
|
||||
POINTER_PROPS,
|
||||
STYLE_PROP_NAMES,
|
||||
)
|
||||
|
||||
SPEC = 1 << COORD_TYPE_SHIFT
|
||||
COORD_MAX = SPEC - 1
|
||||
PCT_POS_MAX = (COORD_MAX - 1) // 2
|
||||
|
||||
|
||||
def lv_pct(value: int) -> int:
|
||||
"""The LV_PCT() macro, so the tests encode the way LVGL does."""
|
||||
stored = PCT_POS_MAX - value if value < 0 else value
|
||||
return SPEC | stored
|
||||
|
||||
|
||||
def prop_id(name: str) -> int:
|
||||
return next(k for k, v in STYLE_PROP_NAMES.items() if v == name)
|
||||
|
||||
|
||||
class TestDecodeCoord(unittest.TestCase):
|
||||
def test_plain_pixels(self):
|
||||
self.assertEqual(decode_coord(0), "0")
|
||||
self.assertEqual(decode_coord(100), "100")
|
||||
self.assertEqual(decode_coord(32767), "32767")
|
||||
|
||||
def test_negative_pixels(self):
|
||||
self.assertEqual(decode_coord(-5), "-5")
|
||||
self.assertEqual(decode_coord(-1), "-1")
|
||||
|
||||
def test_percent(self):
|
||||
self.assertEqual(decode_coord(lv_pct(100)), "100%")
|
||||
self.assertEqual(decode_coord(lv_pct(50)), "50%")
|
||||
self.assertEqual(decode_coord(lv_pct(0)), "0%")
|
||||
|
||||
def test_negative_percent(self):
|
||||
self.assertEqual(decode_coord(lv_pct(-20)), "-20%")
|
||||
|
||||
def test_size_content(self):
|
||||
self.assertEqual(decode_coord(SPEC | COORD_MAX), "content")
|
||||
|
||||
def test_measured_value_from_a_real_target(self):
|
||||
"""536871012 is what a widgets-demo screen actually reports for HEIGHT."""
|
||||
self.assertEqual(decode_coord(536871012), "100%")
|
||||
|
||||
def test_a_negative_value_whose_type_bits_read_as_spec(self):
|
||||
"""LV_COORD_PLAIN() keeps bit 31, so `plain` can exceed the pct range.
|
||||
|
||||
LVGL guards the same way - LV_COORD_IS_PCT() also requires
|
||||
LV_COORD_PLAIN(x) <= LV_PCT_STORED_MAX - and without the guard this
|
||||
comes out as a nonsense percentage rather than the number it is.
|
||||
"""
|
||||
raw = ((1 << 31) | SPEC | 100) - (1 << 32)
|
||||
self.assertEqual(decode_coord(raw), str(raw))
|
||||
|
||||
|
||||
class TestDecodeEnum(unittest.TestCase):
|
||||
def test_plain_enum(self):
|
||||
flex_flow = prop_id("FLEX_FLOW")
|
||||
self.assertEqual(decode_enum(flex_flow, 0), "ROW")
|
||||
self.assertEqual(decode_enum(flex_flow, 1), "COLUMN")
|
||||
|
||||
def test_bitmask_is_decomposed(self):
|
||||
border_side = prop_id("BORDER_SIDE")
|
||||
names = ENUM_PROP_VALUES[border_side]
|
||||
bottom = next(bit for bit, n in names.items() if n == "BOTTOM")
|
||||
top = next(bit for bit, n in names.items() if n == "TOP")
|
||||
self.assertEqual(decode_enum(border_side, bottom | top), "BOTTOM|TOP")
|
||||
|
||||
def test_exact_match_wins_over_decomposition(self):
|
||||
border_side = prop_id("BORDER_SIDE")
|
||||
full = next(bit for bit, n in ENUM_PROP_VALUES[border_side].items()
|
||||
if n == "FULL")
|
||||
self.assertEqual(decode_enum(border_side, full), "FULL")
|
||||
|
||||
def test_unknown_value_falls_back_to_the_number(self):
|
||||
self.assertEqual(decode_enum(prop_id("FLEX_FLOW"), 99), "99")
|
||||
|
||||
|
||||
class TestGeneratedTables(unittest.TestCase):
|
||||
def test_sizes_are_classified_as_coordinates(self):
|
||||
for name in ("WIDTH", "HEIGHT", "MIN_WIDTH", "MAX_HEIGHT", "PAD_LEFT"):
|
||||
self.assertIn(prop_id(name), COORD_PROPS, name)
|
||||
|
||||
def test_grid_templates_are_pointers_not_numbers(self):
|
||||
for name in ("GRID_COLUMN_DSC_ARRAY", "GRID_ROW_DSC_ARRAY"):
|
||||
self.assertIn(prop_id(name), POINTER_PROPS, name)
|
||||
|
||||
def test_a_property_is_never_both_a_coordinate_and_an_enum(self):
|
||||
self.assertEqual(COORD_PROPS & set(ENUM_PROP_VALUES), set())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,100 @@
|
||||
"""Tests for the widget field metadata used by `info widget`.
|
||||
|
||||
The module only reads widget_specs.json and formats values, so it needs no
|
||||
target and no GDB - it is imported straight from its file to keep the lvglgdb
|
||||
package (and its `import gdb`) out of the way.
|
||||
|
||||
Run from the GDB script root with:
|
||||
python3 -m unittest discover tests
|
||||
"""
|
||||
|
||||
import importlib.util
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
_spec = importlib.util.spec_from_file_location(
|
||||
"widget_specs_under_test", ROOT / "lvglgdb" / "lvgl" / "widgets" / "specs.py"
|
||||
)
|
||||
specs = importlib.util.module_from_spec(_spec)
|
||||
_spec.loader.exec_module(specs)
|
||||
|
||||
|
||||
class TestSpecLookup(unittest.TestCase):
|
||||
def test_a_class_name_without_the_prefix_still_finds_its_spec(self):
|
||||
# Before LVGL 6d999331d the class names carried no 'lv_'.
|
||||
self.assertEqual(
|
||||
specs.widget_spec("label"), specs.widget_spec("lv_label")
|
||||
)
|
||||
|
||||
def test_an_unknown_class_has_an_empty_spec_rather_than_raising(self):
|
||||
self.assertEqual(specs.widget_spec("lv_nope"), {})
|
||||
|
||||
|
||||
class TestFormatValue(unittest.TestCase):
|
||||
def test_an_enum_is_named(self):
|
||||
self.assertEqual(
|
||||
specs.format_value("lv_label", "long_mode", 2), "SCROLL (2)"
|
||||
)
|
||||
|
||||
def test_an_out_of_range_enum_stays_a_number(self):
|
||||
self.assertEqual(specs.format_value("lv_label", "long_mode", 99), "99")
|
||||
|
||||
def test_a_bool_field_reads_as_a_bool_not_as_1(self):
|
||||
self.assertEqual(specs.format_value("lv_label", "recolor", 1), "True")
|
||||
|
||||
def test_points_and_areas_are_rendered_inline(self):
|
||||
self.assertEqual(
|
||||
specs.format_value("lv_label", "offset", {"x": 1, "y": 2}), "(1,2)"
|
||||
)
|
||||
self.assertEqual(
|
||||
specs.format_value(
|
||||
"lv_slider", "left_knob_area",
|
||||
{"x1": 1, "y1": 2, "x2": 3, "y2": 4},
|
||||
),
|
||||
"(1,2)-(3,4)",
|
||||
)
|
||||
|
||||
def test_a_missing_value_is_a_dash(self):
|
||||
self.assertEqual(specs.format_value("lv_label", "text", None), "-")
|
||||
|
||||
def test_a_multiline_text_stays_on_one_line(self):
|
||||
self.assertEqual(
|
||||
specs.format_value("lv_label", "text", "a\nb"), '"a\\nb"'
|
||||
)
|
||||
|
||||
|
||||
class TestSummary(unittest.TestCase):
|
||||
def test_the_template_is_filled_from_widget_data(self):
|
||||
self.assertEqual(
|
||||
specs.summary("lv_arc", {"value": 50, "min_value": 0, "max_value": 100}),
|
||||
"50 [0..100]",
|
||||
)
|
||||
|
||||
def test_a_missing_field_leaves_a_gap_rather_than_a_none(self):
|
||||
self.assertEqual(specs.summary("lv_label", {"text": None}), "")
|
||||
|
||||
def test_a_long_value_is_cut(self):
|
||||
self.assertEqual(
|
||||
specs.summary("lv_label", {"text": "x" * 40}, limit=8), "xxxxxxxx…"
|
||||
)
|
||||
|
||||
def test_a_widget_without_a_template_has_no_summary(self):
|
||||
self.assertEqual(specs.summary("lv_obj", {"whatever": 1}), "")
|
||||
|
||||
|
||||
class TestOrderFields(unittest.TestCase):
|
||||
def test_primary_fields_come_first_and_nothing_is_lost(self):
|
||||
keys = ["rotation", "value", "dragging", "max_value"]
|
||||
ordered = specs.order_fields("lv_arc", keys)
|
||||
self.assertEqual(ordered[0], "value")
|
||||
self.assertEqual(sorted(ordered), sorted(keys))
|
||||
|
||||
def test_an_unknown_class_keeps_the_given_order(self):
|
||||
keys = ["b", "a"]
|
||||
self.assertEqual(specs.order_fields("lv_nope", keys), keys)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user