From 810537b3080aedc435e8fc6e889f107ddc2b5508 Mon Sep 17 00:00:00 2001 From: Chuck <33324927+ChuckBuilds@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:08:30 -0400 Subject: [PATCH] docs(text-display): document every setting, with real renders Documentation only; no behaviour change. All 15 settings are covered, with real rendered screenshots for the ones a picture actually settles. The main thing this clears up is scroll speed. Three settings look like they control it and the old README described scroll_speed as "a multiplier, not px/s", which is not what the code does. The rate is scroll_speed / scroll_delay, scroll_speed is pixels per FRAME (clamped at 5, with a warning logged above that), and target_fps is a pacing hint passed to the core scroll helper that will not make text move faster on its own -- raising it without lowering scroll_delay does nothing to the rate. font_mode gets a four-panel comparison because it is the setting that decides whether sizing needs thinking about at all: manual overflows long text and leaves short text small, while auto shrinks long text to fit the panel and grows short text to fill it. Verified both values render distinctly rather than assuming. Also documents that font_size is ignored in auto mode and by .bdf faces, which are drawn at their own fixed pixel size. Two behaviours that were previously undocumented: enabled defaults to false, so a fresh install shows nothing; and a scroll legitimately begins with an empty panel, because the text starts fully off the right edge. That second one is also why there is no scrolling screenshot -- a single frame at the start of a pass is a blank panel, and faking one would misrepresent it. The old README's tips and use-case sections are folded in as recipes rather than dropped, including the concrete guidance that a scroll_gap_width roughly equal to the panel width gives the cleanest loop. Audits before opening: no config token dropped, all 15 schema leaves documented, no broken TOC anchors, and every old section accounted for. Co-Authored-By: Claude Opus 5 --- README.md | 2 +- docs/assets/text-display/colors.png | Bin 0 -> 13313 bytes docs/assets/text-display/font-mode.png | Bin 0 -> 14589 bytes docs/assets/text-display/font-size.png | Bin 0 -> 14367 bytes docs/assets/text-display/hero.png | Bin 0 -> 804 bytes docs/assets/text-display/panel-sizes.png | Bin 0 -> 20652 bytes docs/assets/text-display/shots.json | 304 +++++++++++++++ plugins.json | 4 +- plugins/text-display/README.md | 475 +++++++++++++++-------- plugins/text-display/manifest.json | 9 +- 10 files changed, 623 insertions(+), 171 deletions(-) create mode 100644 docs/assets/text-display/colors.png create mode 100644 docs/assets/text-display/font-mode.png create mode 100644 docs/assets/text-display/font-size.png create mode 100644 docs/assets/text-display/hero.png create mode 100644 docs/assets/text-display/panel-sizes.png create mode 100644 docs/assets/text-display/shots.json diff --git a/README.md b/README.md index 13432c47..a6404a2f 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ curl -X POST http://your-pi-ip:5000/api/v3/plugins/install \ | Plugin | Description | Preview | |--------|-------------|---------| -| [Scrolling Text](./plugins/text-display/) | Custom scrolling/static text with configurable fonts and colors | | +| [Scrolling Text](./plugins/text-display/) | Custom scrolling/static text with configurable fonts and colors | text-display on an LED panel | ### System (1) diff --git a/docs/assets/text-display/colors.png b/docs/assets/text-display/colors.png new file mode 100644 index 0000000000000000000000000000000000000000..99300270c2457420ce6f9112e24c0b8db5edb754 GIT binary patch literal 13313 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 literal 0 HcmV?d00001 diff --git a/docs/assets/text-display/font-mode.png b/docs/assets/text-display/font-mode.png new file mode 100644 index 0000000000000000000000000000000000000000..8bc470c7bfdd482e3aa8372f4a2f7ac7bec2fecc GIT binary patch literal 14589 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 g0Q$OM-zD~joiovggl@@x#Kj2qkHdr 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 y__PAje`(BRcD#J>wZ_f=:5000`, go to **Plugin Manager**, find **Scrolling Text** in +the **Plugin Store** section, and click **Install**. + +**Manually.** Copy this directory into your LEDMatrix `plugin-repos/` and +restart the display service. + +`enabled` defaults to **`false`**, so nothing appears until you switch the +plugin on. + +--- + +## Quick Start -For longer messages: ```json { - "text": "This is a long message that will scroll across the display", - "scroll": true, - "scroll_speed": 40 + "text-display": { + "enabled": true, + "text": "Welcome to the workshop" + } } ``` -### Custom Styling +A fully specified configuration: ```json { - "text": "ALERT!", - "text_color": [255, 0, 0], - "background_color": [0, 0, 0], - "font_path": "assets/fonts/PressStart2P-Regular.ttf", - "font_size": 10 + "text-display": { + "enabled": true, + "text": "Subscribe to ChuckBuilds", + "font_mode": "auto", + "font_path": "assets/fonts/PressStart2P-Regular.ttf", + "font_size": 8, + "scroll": true, + "scroll_loop": true, + "scroll_speed": 1, + "scroll_delay": 0.01, + "target_fps": 120, + "scroll_gap_width": 32, + "text_color": [255, 255, 255], + "background_color": [0, 0, 0], + "display_duration": 10, + "update_interval": 60 + } } ``` -## Font Support +--- -### TTF Fonts (TrueType) +## Text and Sizing -Most common, widely available: -```json -{ - "font_path": "assets/fonts/PressStart2P-Regular.ttf", - "font_size": 8 -} -``` +| Option | Type | Default | What it does | +|--------|------|---------|--------------| +| `text` | string | `"Subscribe to ChuckBuilds"` | The message to display | +| `font_mode` | string | `manual` | `manual` or `auto` — see below | +| `font_path` | string | `assets/fonts/PressStart2P-Regular.ttf` | Font file, relative to the project root or absolute | +| `font_size` | number | `8` | Size in pixels. **Manual mode only** | -### BDF Fonts (Bitmap) +### `font_mode` -Optimized for LED matrices: -```json -{ - "font_path": "assets/fonts/4x6.bdf", - "font_size": 6 -} +This is the setting that decides whether you have to think about sizing at all. + +![Four panels comparing manual and auto font mode on long and short +text](../../docs/assets/text-display/font-mode.png) + +- **`manual`** (the default) uses `font_size` exactly as configured. Long text + overflows the panel; short text stays small. +- **`auto`** ignores `font_size` and picks the largest crisp size that fits the + panel. Long text is shrunk to fit; short text is grown to fill. + +`auto` is the right choice for a static message you want readable across a +room, and for any text whose length you do not control. Stay on `manual` when +you want a consistent size regardless of what the message says — a ticker that +changes text should not change size with it. + +### `font_path` and `font_size` + +`font_path` accepts both TrueType (`.ttf`) and bitmap (`.bdf`) fonts: + +- **TTF** scales to any `font_size`. `PressStart2P-Regular.ttf` (the default) is + a chunky 8-bit face that stays legible at a distance; `4x6-font.ttf` fits far + more characters per line. +- **BDF** is a bitmap face drawn at one fixed pixel size. It renders crisply, + but `font_size` cannot change it — the file's own size wins. + +![Four panels showing font_size 6, 8, 12 and 16 with the same long +text](../../docs/assets/text-display/font-size.png) + +Larger sizes are more readable but fit less on the panel, which is what makes +`scroll` or `font_mode: auto` necessary for anything longer than a word or two. + +--- + +## Scrolling + +| Option | Type | Default | What it does | +|--------|------|---------|--------------| +| `scroll` | boolean | `true` | Scroll the text, or draw it statically | +| `scroll_loop` | boolean | `true` | Loop continuously, or scroll once and stop | +| `scroll_speed` | number | `1` | Pixels moved per frame | +| `scroll_delay` | number | `0.01` | Seconds per frame | +| `target_fps` | number | `120` | Target frame rate hint | +| `scroll_gap_width` | number | `32` | Blank pixels between the end and the restart | + +### How fast it moves + +Three settings look like they control speed. Only two of them actually set it: + +```text +pixels per second = scroll_speed / scroll_delay ``` -## Tips & Best Practices +So the defaults — 1 pixel per frame every 0.01s — give 100 px/s. + +- **`scroll_speed`** is pixels per *frame*, not per second. It is clamped to a + maximum of 5; above that the movement reads as jumping rather than scrolling, + and the plugin logs a warning if you set more. Values above 5 in an old config + usually mean it was written when this was pixels-per-second. +- **`scroll_delay`** is the throttle — seconds between frames. Lowering it + raises both the frame rate and the CPU cost. +- **`target_fps`** is a pacing hint passed to the core's scroll helper, clamped + to 30–200. It does not by itself change the pixels-per-second figure above; + raising it without lowering `scroll_delay` will not make text move faster. + +To make text move faster, prefer raising `scroll_speed` a little (1 → 2) over +driving `scroll_delay` very low. To make it smoother, lower `scroll_delay`. + +### Looping and the gap + +With `scroll_loop: true` the message repeats forever, and `scroll_gap_width` +sets how much blank panel passes between the last character and the first +coming round again. A gap roughly equal to your panel width gives the cleanest +loop — the message is fully gone before it returns. The default of 32 suits a +64-wide panel; on a 128-wide chain, try 128. + +With `scroll_loop: false` the text scrolls past once and stops. Pair it with +`display_duration` long enough for a full pass, or the plugin's turn will end +mid-message. + +> **A still cannot show motion.** There is no screenshot of scrolling in this +> README because a single frame captured at the start of a scroll is an empty +> panel — the text has not entered yet. That is correct behaviour, not a fault. -### For Scrolling Text +--- -1. **Adjust speed for readability**: `scroll_speed` is a multiplier, not px/s. - Values around `1`–`2` are typical; higher values scroll faster. -2. **Tune smoothness with `scroll_delay`**: lower (0.005) = smoother but - more CPU; higher (0.05) = choppier but lighter. -3. **Set appropriate gap**: a `scroll_gap_width` equal to your display width - produces clean loops. -4. **Test message length**: very long messages benefit from a higher - `target_fps` cap and lower `scroll_delay`. +## Colours -### For Static Text +| Option | Type | Default | What it does | +|--------|------|---------|--------------| +| `text_color` | array | `[255, 255, 255]` | Text colour, `[R, G, B]` | +| `background_color` | array | `[0, 0, 0]` | Panel background, `[R, G, B]` | -1. **Center short messages**: Disable scroll for text that fits -2. **Choose appropriate font size**: Match display height -3. **Use contrasting colors**: Ensure good visibility +![Four panels showing white, amber and green text, and black text on a red +background](../../docs/assets/text-display/colors.png) -### Font Selection +A non-black `background_color` lights every pixel on the panel, which draws +noticeably more power and is much brighter in a dark room. Use it for a +deliberate alert, not as a default. -1. **For LED matrices**: Pixel fonts (BDF) work best -2. **For clarity**: Use fonts designed for small sizes -3. **For style**: TrueType fonts offer more options +--- -## Common Use Cases +## Timing + +| Option | Type | Default | What it does | +|--------|------|---------|--------------| +| `display_duration` | number | `10` | Seconds the plugin holds the panel per turn | +| `update_interval` | integer | `60` | Seconds between refreshes of the text | + +For scrolling text, `display_duration` should be long enough for at least one +full pass, or viewers only ever see the middle of the message. A rough guide: + +```text +seconds for one pass ≈ (text width + panel width + scroll_gap_width) / (scroll_speed / scroll_delay) +``` + +`update_interval` matters little here because the text is static configuration +rather than fetched data — it only decides how quickly a config change is +picked up. + +--- + +## Recipes + +**A static announcement.** Large, centred, no motion: -### Announcements ```json -{ - "text": "WELCOME!", - "scroll": false, - "font_size": 12, - "text_color": [0, 255, 0] -} +{ "text": "WELCOME!", "scroll": false, "font_mode": "auto", + "text_color": [0, 255, 0] } ``` -### Ticker Messages +**A news-style ticker.** Long message, continuous loop, slightly quicker: + ```json -{ - "text": "Breaking News: LED matrices are awesome! Stay tuned for more...", - "scroll": true, - "scroll_speed": 1.5 -} +{ "text": "Breaking: LED matrices are awesome. Stay tuned for more...", + "scroll": true, "scroll_speed": 1.5, "scroll_gap_width": 128, + "display_duration": 30 } ``` -### Call to Action +**A call to action.** Coloured, looping, sized to the panel: + ```json -{ - "text": "Subscribe to ChuckBuilds on YouTube!", - "scroll": true, - "scroll_speed": 2, - "text_color": [255, 0, 0] -} +{ "text": "Subscribe to ChuckBuilds on YouTube!", "scroll": true, + "scroll_speed": 2, "text_color": [255, 0, 0] } ``` +**A one-shot message.** Scrolls past once and stops, then hands the panel back: + +```json +{ "text": "Build complete", "scroll": true, "scroll_loop": false, + "display_duration": 20 } +``` + +### Choosing a font for a panel + +Bitmap (`.bdf`) faces are drawn pixel-exact and stay crisp, which suits an LED +matrix better than a scaled outline font. TrueType gives more choice and any +size you like, at the cost of soft edges at awkward sizes. Whichever you pick, +prefer a face designed for small sizes — a display font intended for print +turns to mush below about 10 pixels. + +--- + +## Panel Sizes + +![The same message on 64x32, 128x32, 128x64 and 256x32 panels in auto +mode](../../docs/assets/text-display/panel-sizes.png) + +In `auto` mode the plugin uses whatever the panel gives it: a 64-wide panel +forces a small face, while a 256-wide chain lets the same message render large. +In `manual` mode the size is fixed, so a wider panel simply shows more of the +message before it overflows. + +--- + ## Troubleshooting -**Text not visible:** -- Check text_color is different from background_color -- Verify text string is not empty -- Check font_path points to valid font file +**Nothing appears.** +`enabled` defaults to `false`. Check it is `true`. + +**The panel is blank but I set text.** +If `scroll` is on, the message begins off the right edge — a blank panel at the +start of a pass is expected. If it stays blank, check `text_color` is not the +same as `background_color`. + +**The text is cut off at both edges.** +That is static `manual` mode with text wider than the panel. Switch to +`font_mode: auto`, lower `font_size`, or turn on `scroll`. + +**Scrolling looks jumpy.** +`scroll_speed` is pixels per *frame*. Values above 2 visibly step; above 5 the +plugin clamps and logs a warning. Lower `scroll_speed` and lower `scroll_delay` +instead. + +**I raised `target_fps` and nothing got faster.** +It is a pacing hint, not the speed control. Speed is +`scroll_speed / scroll_delay` — see [How fast it moves](#how-fast-it-moves). + +**I only ever see the middle of the message.** +`display_duration` is ending the turn before a full pass completes. Raise it, +or shorten the text. -**Scrolling too fast/slow:** -- Adjust `scroll_speed` (multiplier, default `1`). Try values between `0.5` and `3`. -- For finer control, also tune `scroll_delay` and `target_fps`. +**`font_size` has no effect.** +Either `font_mode` is `auto` (which chooses the size itself) or `font_path` +points at a `.bdf`, which is drawn at its own fixed pixel size. -**Font not loading:** -- Verify font_path is correct -- Check font file exists -- Ensure font file permissions are correct -- For BDF fonts, ensure freetype-py is installed +**The font did not change.** +`font_path` is resolved relative to the LEDMatrix project root, not to the +plugin directory. Check the log for a font-loading warning. -**Text appears cut off:** -- Reduce font_size -- For static text, ensure text fits display width -- For scrolling, text should extend beyond display +--- + +## Development + +### Project structure + +```text +text-display/ +├── manifest.json # Plugin metadata and version history +├── manager.py # TextDisplayPlugin +├── config_schema.json # Settings schema; source of truth for defaults +└── README.md +``` + +Scrolling is delegated to the core's `ScrollHelper` in frame-based mode, which +is the same mechanism the stock and leaderboard tickers use — so scrolling here +behaves consistently with those. + +### Performance + +Scrolling redraws the panel every frame, so it costs meaningfully more CPU than +static text. On a Raspberry Pi driving a large chain, prefer the default +`scroll_delay` of `0.01` over lower values, and remember that a non-black +`background_color` lights every pixel. + +### Regenerating the images in this README + +```bash +python scripts/render_docs_assets.py --plugin text-display +``` -## Performance Notes +`--check` verifies the committed images still match what the plugin renders. -- Scrolling text uses a pre-rendered cache for smooth animation -- The render loop targets `target_fps` (default 120) and sleeps - `scroll_delay` between steps -- Text cache is created once at first render and reused -- Font loading happens once at initialization +--- -## License +## Support -GPL-3.0 License - see main LEDMatrix repository for details. +- YouTube: +- Instagram: +- Discord: +- Sponsor: [GitHub Sponsors](https://github.com/sponsors/ChuckBuilds) · + [Buy Me a Coffee](https://buymeacoffee.com/chuckbuilds) · + [Ko-fi](https://ko-fi.com/chuckbuilds/) +Released under the GNU General Public License v3.0 — see [LICENSE](LICENSE). diff --git a/plugins/text-display/manifest.json b/plugins/text-display/manifest.json index fe15f87d..b1b52cb3 100644 --- a/plugins/text-display/manifest.json +++ b/plugins/text-display/manifest.json @@ -1,7 +1,7 @@ { "id": "text-display", "name": "Text Display", - "version": "1.1.5", + "version": "1.1.6", "author": "ChuckBuilds", "description": "Display custom scrolling or static text with configurable fonts, colors, and scroll speed. Perfect for announcements, messages, or custom displays.", "category": "display", @@ -24,6 +24,11 @@ } }, "versions": [ + { + "version": "1.1.6", + "released": "2026-09-02", + "ledmatrix_min": "2.0.0" + }, { "version": "1.1.5", "released": "2026-07-31", @@ -74,7 +79,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-07-31", + "last_updated": "2026-09-02", "stars": 0, "downloads": 0, "verified": true,