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.
> **Rendering guidance:** plugins should read the display size dynamically
> (`self.display_manager.matrix.width/height`) rather than hardcoding one
> panel. For plugins that want to *scale* their layout to any panel, the
> (`self.display_manager.width/height`) rather than hardcoding one
> 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))
> provides the shared helpers — fonts, images, and composite layouts that
> 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
any width/height and configuration (square, rectangle, 2×2, 4×4, 8×2, long
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
here.
+7 -7
View File
@@ -239,7 +239,7 @@ class StarlarkAppsPlugin(BasePlugin):
# Calculate optimal magnification based on display size
self.calculated_magnify = self._calculate_optimal_magnify()
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}")
# Load installed apps
@@ -323,8 +323,8 @@ class StarlarkAppsPlugin(BasePlugin):
Recommended magnify value (1-8)
"""
try:
display_width = self.display_manager.matrix.width
display_height = self.display_manager.matrix.height
display_width = self.display_manager.width
display_height = self.display_manager.height
# Tronbyte native resolution
NATIVE_WIDTH = 64
@@ -362,8 +362,8 @@ class StarlarkAppsPlugin(BasePlugin):
Dictionary with recommendation details
"""
try:
display_width = self.display_manager.matrix.width
display_height = self.display_manager.matrix.height
display_width = self.display_manager.width
display_height = self.display_manager.height
NATIVE_WIDTH = 64
NATIVE_HEIGHT = 32
@@ -852,8 +852,8 @@ class StarlarkAppsPlugin(BasePlugin):
# Scale frames if needed
if self.config.get("scale_output", True):
width = self.display_manager.matrix.width
height = self.display_manager.matrix.height
width = self.display_manager.width
height = self.display_manager.height
# Get scaling method from config
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.
In double-sided mode the physical panel chain shows N identical copies of a
smaller logical screen. Plugins size themselves from ``matrix.width`` /
``matrix.height`` (the documented convention, used at 30+ call sites), so
this proxy reports the logical dimensions while delegating every real
smaller logical screen. Plugins size themselves from
``display_manager.width`` / ``height`` (the documented convention), which
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``,
``Clear`` and so on — to the underlying physical matrix. The duplication
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):
"""Temporarily present a smaller logical canvas to plugins.
Plugins lay out against ``display_manager.matrix.width`` (and the
``width``/``height`` properties, which defer to it), so the only way to
Plugins lay out against the ``display_manager.width``/``height``
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
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