Skip to content
led-ticker

Custom color provider

This is a how-to for plugin authors: write a custom color provider — the object that decides what color text is, frame by frame. You’ll build a pulse that makes a message breathe brighter and dimmer.

What you’ll need:

  • A scaffolded plugin — see the authoring guide; you only import led_ticker.plugin.
  • No hardware needed: you’ll preview with make render-demo (run from the repo after make dev).
  • Your plugin installed (pip install -e . from its directory) — make render-demo only picks up installed plugins.
Built-in color providers animating text — your pulse will breathe similarly.
Built-in color providers animating text — your pulse will breathe similarly.

A color provider is a class with two flags and one method:

class Pulse(ColorProviderBase):
per_char = False
frame_invariant = False
def color_for(self, frame, char_index, total_chars): ...
  • color_for(frame, char_index, total_chars) returns a Color for the current frame. char_index / total_chars describe the character being drawn (for per-character effects).
  • per_charFalse returns one color for the whole string; True is called per character, so you can color each letter differently (using char_index).
  • frame_invariant — does your color depend on frame? True means no (a constant or gradient — led-ticker paints it once and sleeps, a performance fast-path); False means yes (it animates — led-ticker re-renders every tick).

The pulse keeps one color but scales its brightness up and down with a sine wave driven by frame:

def color_for(self, frame, char_index, total_chars):
level = 0.65 + 0.35 * math.sin(frame * self.speed * 0.05)
r, g, b = self.color
return make_color(int(r * level), int(g * level), int(b * level))

make_color(r, g, b) builds a Color (channels 0–255). The 0.65 is the midpoint brightness and 0.35 the swing, so brightness oscillates between about 30% and 100% — it never goes fully dark; the 0.05 just scales frame into the sine, so tune speed from TOML and leave the 0.05 alone. Because the output depends on frame, frame_invariant = False. Add a base color and a speed as constructor arguments and they’ll come from TOML.

  1. Register the class in your plugin’s register(api):

    @api.color_provider("pulse")
    class Pulse(ColorProviderBase): ...

    It’s namespaced — in the example_colorprovider plugin it becomes example_colorprovider.pulse.

  2. Point a text widget’s font_color at it in your config/config.toml (optionally with fields):

    [[playlist.section.widget]]
    type = "message"
    text = "breathing"
    font_color = { style = "example_colorprovider.pulse", color = [0, 200, 255], speed = 6 }
  3. Install your plugin so led-ticker can find it. make render-demo loads only installed plugins (see Package & install), then preview — no hardware needed:

    Terminal window
    pip install -e . # run from your plugin's directory
    make render-demo CONFIG=config/config.toml OUT=preview.gif
    open preview.gif # macOS; xdg-open on Linux

The full plugin — examples/plugins/example_colorprovider/__init__.py:

"""Example led-ticker plugin: a custom 'pulse' color provider (the 'Custom color provider' how-to).
Drop `example_colorprovider/` into your `config/plugins/` (local use), or package it
with an `[project.entry-points."led_ticker.plugins"] example_colorprovider = "example_colorprovider:register"`
entry, then use it as `font_color = {style = "example_colorprovider.pulse"}`.
Imports only `led_ticker.plugin` (the public surface) plus stdlib.
"""
import math
from led_ticker.plugin import ColorProviderBase, make_color
def register(api):
@api.color_provider("pulse")
class Pulse(ColorProviderBase):
# One color for the whole string (not per-character).
per_char = False
# `color_for` depends on `frame`, so the widget must re-render each tick.
# Declaring this True would freeze the pulse — ColorProviderBase forces
# you to set it explicitly.
frame_invariant = False
# Config fields come from TOML, e.g.
# font_color = {style = "example_colorprovider.pulse", color = [0, 200, 255], speed = 6}
def __init__(self, color=(0, 200, 255), speed=6):
self.color = color
self.speed = speed
def color_for(self, frame, char_index, total_chars):
# Brightness breathes between ~0.30 and ~1.00 as the frame advances.
level = 0.65 + 0.35 * math.sin(frame * self.speed * 0.05)
r, g, b = self.color
return make_color(int(r * level), int(g * level), int(b * level))
  • The color is frozen / doesn’t animate — you declared frame_invariant = True but color_for uses frame. Set frame_invariant = False.
  • TypeError: Pulse must define 'frame_invariant' — declare it as a class attribute (that’s ColorProviderBase doing its job).
  • “unknown font_color style” / not found — check the namespaced style (example_colorprovider.pulse, not pulse) and that the plugin is installed/loaded (see Installing a plugin).