docs(plugins): one display-size contract: display_manager.width/height

CLAUDE.md (#580) says to read display_manager.width/height because
matrix is None when hardware init fails; the development guide, the
safety-harness doc and two DisplayManager docstrings still recommended
matrix.width/height. The bundled starlark-apps plugin read matrix.width
unguarded, so its magnify recommendation and frame scaling raised in
fallback mode (e.g. after the Pi 5 hardware refusal).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-17 08:56:12 -04:00
co-authored by Claude Opus 5
parent 3a0d126fdd
commit df3524305f
4 changed files with 22 additions and 15 deletions
+4 -2
View File
@@ -3,8 +3,10 @@
This guide explains how to set up a development workflow for plugins that are maintained in separate Git repositories while still being able to test them within the LEDMatrix project. This guide explains how to set up a development workflow for plugins that are maintained in separate Git repositories while still being able to test them within the LEDMatrix project.
> **Rendering guidance:** plugins should read the display size dynamically > **Rendering guidance:** plugins should read the display size dynamically
> (`self.display_manager.matrix.width/height`) rather than hardcoding one > (`self.display_manager.width/height`) rather than hardcoding one
> panel. For plugins that want to *scale* their layout to any panel, the > panel. Don't read `display_manager.matrix.width/height`: `matrix` is
> `None` when hardware init fails, while the `width`/`height` properties
> fall back to the canvas size. For plugins that want to *scale* their layout to any panel, the
> opt-in adaptive layout system ([ADAPTIVE_LAYOUT.md](ADAPTIVE_LAYOUT.md)) > opt-in adaptive layout system ([ADAPTIVE_LAYOUT.md](ADAPTIVE_LAYOUT.md))
> provides the shared helpers — fonts, images, and composite layouts that > provides the shared helpers — fonts, images, and composite layouts that
> scale. Existing plugins keep their classic rendering unless they adopt > scale. Existing plugins keep their classic rendering unless they adopt
+2 -1
View File
@@ -10,7 +10,8 @@ plugin without breaking a size or screen you didn't think to test.
There is **no fixed set of supported panel sizes** — an RGB matrix build can be There is **no fixed set of supported panel sizes** — an RGB matrix build can be
any width/height and configuration (square, rectangle, 2×2, 4×4, 8×2, long any width/height and configuration (square, rectangle, 2×2, 4×4, 8×2, long
strips, tall stacks). Plugins are expected to read dimensions dynamically strips, tall stacks). Plugins are expected to read dimensions dynamically
(`self.display_manager.matrix.width/height`) and lay themselves out (`self.display_manager.width/height` — not `matrix.width/height`, since
`matrix` is `None` when hardware init fails) and lay themselves out
accordingly, so a hardcoded coordinate or unscaled font shows up as a failure accordingly, so a hardcoded coordinate or unscaled font shows up as a failure
here. here.
+7 -7
View File
@@ -239,7 +239,7 @@ class StarlarkAppsPlugin(BasePlugin):
# Calculate optimal magnification based on display size # Calculate optimal magnification based on display size
self.calculated_magnify = self._calculate_optimal_magnify() self.calculated_magnify = self._calculate_optimal_magnify()
if self.calculated_magnify > 1: if self.calculated_magnify > 1:
self.logger.info(f"Display size: {self.display_manager.matrix.width}x{self.display_manager.matrix.height}, " self.logger.info(f"Display size: {self.display_manager.width}x{self.display_manager.height}, "
f"recommended magnify: {self.calculated_magnify}") f"recommended magnify: {self.calculated_magnify}")
# Load installed apps # Load installed apps
@@ -323,8 +323,8 @@ class StarlarkAppsPlugin(BasePlugin):
Recommended magnify value (1-8) Recommended magnify value (1-8)
""" """
try: try:
display_width = self.display_manager.matrix.width display_width = self.display_manager.width
display_height = self.display_manager.matrix.height display_height = self.display_manager.height
# Tronbyte native resolution # Tronbyte native resolution
NATIVE_WIDTH = 64 NATIVE_WIDTH = 64
@@ -362,8 +362,8 @@ class StarlarkAppsPlugin(BasePlugin):
Dictionary with recommendation details Dictionary with recommendation details
""" """
try: try:
display_width = self.display_manager.matrix.width display_width = self.display_manager.width
display_height = self.display_manager.matrix.height display_height = self.display_manager.height
NATIVE_WIDTH = 64 NATIVE_WIDTH = 64
NATIVE_HEIGHT = 32 NATIVE_HEIGHT = 32
@@ -852,8 +852,8 @@ class StarlarkAppsPlugin(BasePlugin):
# Scale frames if needed # Scale frames if needed
if self.config.get("scale_output", True): if self.config.get("scale_output", True):
width = self.display_manager.matrix.width width = self.display_manager.width
height = self.display_manager.matrix.height height = self.display_manager.height
# Get scaling method from config # Get scaling method from config
scale_method_str = self.config.get("scale_method", "nearest") scale_method_str = self.config.get("scale_method", "nearest")
+9 -5
View File
@@ -91,9 +91,11 @@ class _LogicalMatrix:
"""Proxy that reports a logical (per-screen) size for a physical matrix. """Proxy that reports a logical (per-screen) size for a physical matrix.
In double-sided mode the physical panel chain shows N identical copies of a In double-sided mode the physical panel chain shows N identical copies of a
smaller logical screen. Plugins size themselves from ``matrix.width`` / smaller logical screen. Plugins size themselves from
``matrix.height`` (the documented convention, used at 30+ call sites), so ``display_manager.width`` / ``height`` (the documented convention), which
this proxy reports the logical dimensions while delegating every real defer to ``matrix.width`` / ``matrix.height`` -- and many older plugins read
``matrix.width`` directly -- so this proxy reports the logical dimensions
while delegating every real
operation — ``CreateFrameCanvas``, ``SwapOnVSync``, ``brightness``, operation — ``CreateFrameCanvas``, ``SwapOnVSync``, ``brightness``,
``Clear`` and so on — to the underlying physical matrix. The duplication ``Clear`` and so on — to the underlying physical matrix. The duplication
itself happens once per frame in :meth:`DisplayManager.update_display`. itself happens once per frame in :meth:`DisplayManager.update_display`.
@@ -682,8 +684,10 @@ class DisplayManager:
def render_size(self, width: int, height: Optional[int] = None): def render_size(self, width: int, height: Optional[int] = None):
"""Temporarily present a smaller logical canvas to plugins. """Temporarily present a smaller logical canvas to plugins.
Plugins lay out against ``display_manager.matrix.width`` (and the Plugins lay out against the ``display_manager.width``/``height``
``width``/``height`` properties, which defer to it), so the only way to properties (which defer to ``matrix.width`` when hardware is present,
and to the canvas when it is not; some older plugins read
``matrix.width`` directly), so the only way to
get a *narrower layout* rather than a cropped one is to tell the plugin get a *narrower layout* rather than a cropped one is to tell the plugin
the screen is narrower while it renders. Trimming after the fact cannot the screen is narrower while it renders. Trimming after the fact cannot
fix a forecast spread across five columns or a progress bar drawn at fix a forecast spread across five columns or a progress bar drawn at