Ui¶
ui ¶
UI-layer exports for screens, slots, cards, and controls.
BlankCard ¶
Bases: Card
A card that renders nothing, letting the touchstrip background show through.
Used as the default placeholder in :class:~deckui.ui.touch_strip.TouchStrip
before the user assigns real cards. Returns None from :meth:render
so the compositor skips the slot and the
:attr:~deckui.ui.touch_strip.TouchStrip.background_color is visible.
Source code in src/deckui/ui/cards/blank.py
Card ¶
Bases: ABC
Abstract base for a single touch-strip zone under an encoder.
The touchscreen is divided into zones aligned with the device's
encoders. Cards are tiled edge-to-edge — the library imposes no
margins or gaps. Each zone is touchscreen_width // panel_count
wide and touchscreen_height tall (e.g. 200x100 per zone on
Stream Deck+).
Subclass this to build custom widgets. At minimum, implement
:meth:render. Override the handle_encoder_* and
check_selection_timeout hooks to react to encoder events.
Examples:
::
class MyCard(Card):
def render(self) -> Image.Image:
img = Image.new("RGB", (panel_width, panel_height), "black")
# ... draw custom content ...
return img
Event handlers are registered with decorators::
@card.on_tap
async def handle():
print("Card tapped!")
Source code in src/deckui/ui/cards/base.py
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 | |
on_tap ¶
Decorator to register a handler for short tap events in this zone.
Examples:
::
@widget.on_tap
async def handle():
...
Source code in src/deckui/ui/cards/base.py
on_long_press ¶
Decorator to register a handler for long press events in this zone.
Examples:
::
@widget.on_long_press
async def handle():
...
Source code in src/deckui/ui/cards/base.py
on_drag ¶
Decorator to register a handler for drag/swipe events in this zone.
The handler receives x, y, x_out, y_out arguments
describing the start and end coordinates of the drag gesture.
Examples:
::
@widget.on_drag
async def handle(x: int, y: int, x_out: int, y_out: int):
...
Source code in src/deckui/ui/cards/base.py
on_encoder_turn ¶
Decorator to register a handler for encoder turn events on this widget.
The handler receives a single direction argument:
positive = clockwise, negative = counter-clockwise.
Examples:
::
@widget.on_encoder_turn
async def handle(direction: int):
...
Source code in src/deckui/ui/cards/base.py
on_encoder_press ¶
Decorator to register a handler for encoder press events on this widget.
Examples:
::
@widget.on_encoder_press
async def handle():
...
Source code in src/deckui/ui/cards/base.py
on_encoder_release ¶
Decorator to register a handler for encoder release events on this widget.
Examples:
::
@widget.on_encoder_release
async def handle():
...
Source code in src/deckui/ui/cards/base.py
set_refresh_callback ¶
Register an async callback the card can invoke to request a refresh.
This is set automatically by :class:~deckui.runtime.deck.Deck
when dispatching events so that cards with internal timers (e.g.
long-press detection) can trigger a re-render without a direct
reference to the deck.
Source code in src/deckui/ui/cards/base.py
request_refresh
async
¶
Ask the deck to re-render this card.
No-op if no refresh callback has been registered.
queue_pending_callback ¶
Enqueue a callback for deferred async invocation.
Called by child elements (e.g. sliders) when their value changes
synchronously. The queued callbacks are drained and awaited by
:class:~deckui.runtime.deck.Deck during event dispatch or refresh.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
handler
|
AsyncHandler
|
The async callback to invoke. |
required |
args
|
tuple[object, ...]
|
Positional arguments to pass to the callback. |
required |
Source code in src/deckui/ui/cards/base.py
drain_pending_callbacks ¶
Remove and return all pending callbacks.
Returns:
| Type | Description |
|---|---|
list[tuple[AsyncHandler, tuple[object, ...]]]
|
A list of |
Source code in src/deckui/ui/cards/base.py
mark_clean ¶
mark_dirty ¶
set_rendered ¶
Store the rendered image and clear the dirty flag.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
img
|
Image | None
|
The rendered PIL image, or |
required |
Source code in src/deckui/ui/cards/base.py
prepare_assets
async
¶
dispatch_encoder_turn
async
¶
Dispatch an encoder-turn event to the card.
dispatch_encoder_press
async
¶
Dispatch an encoder-press event to the card.
dispatch_encoder_release
async
¶
Dispatch an encoder-release event to the card.
dispatch_touch
async
¶
Dispatch a touch gesture to the card.
Source code in src/deckui/ui/cards/base.py
render
abstractmethod
¶
Render this card as a panel-sized PIL Image.
Return None to let the touchstrip background colour show
through (used by :class:~deckui.ui.cards.blank.BlankCard).
Returns:
| Type | Description |
|---|---|
Image or None
|
A panel-sized RGB :class: |
Source code in src/deckui/ui/cards/base.py
handle_encoder_turn ¶
Called when the encoder above this widget is turned.
Override to handle encoder rotation. The default is a no-op.
handle_encoder_press ¶
Called when the encoder above this widget is pressed.
Override to handle encoder presses. The default is a no-op.
handle_encoder_release ¶
Called when the encoder above this widget is released.
Override to handle encoder releases. The default is a no-op.
check_selection_timeout ¶
Check whether an internal selection timeout has elapsed.
Override to implement timeout logic (e.g. for slider cycling).
The default always returns False.
Returns:
| Type | Description |
|---|---|
bool
|
|
Source code in src/deckui/ui/cards/base.py
CardController ¶
Base class for service-backed touch-strip card controllers.
Subclasses are expected to:
- Load a
.duipackage and assign the resulting :class:~deckui.DuiCardto :attr:cardin__init__. - Wire service-event subscriptions and DUI-event forwards (typically
via :meth:
~deckui.DuiCard.bind, :meth:~deckui.DuiCard.bind_range, :meth:~deckui.DuiCard.bind_many, :meth:~deckui.DuiCard.forward). - Optionally override :meth:
on_attach/ :meth:on_detachfor deck-linked subscriptions or background task lifecycle.
The base class itself stores no state and performs no wiring. It exists purely to give every controller a uniform lifecycle the application can drive in a loop on connect/disconnect.
Attributes:
| Name | Type | Description |
|---|---|---|
card |
DuiCard
|
The card the controller drives. Subclasses must assign this. |
Source code in src/deckui/ui/controller.py
on_attach
async
¶
Hook invoked from the app's on_connect callback.
Default implementation is a no-op. Override to subscribe to
deck-owned events (e.g. deck.on_brightness_changed), replay
last-known values to the freshly-connected hardware, or start
background tasks that depend on a refresh callback being wired
up.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
deck
|
Deck
|
The freshly-connected :class: |
required |
Source code in src/deckui/ui/controller.py
on_detach
async
¶
Hook invoked from the app's on_disconnect callback.
Default implementation is a no-op. Override to cancel background tasks or release deck-linked resources.
KeyController ¶
Base class for service-backed key controllers.
Mirror of :class:CardController for :class:~deckui.DuiKey.
Subclasses construct the key and wire bindings in __init__;
override :meth:on_attach / :meth:on_detach when deck access
or background tasks are required.
Attributes:
| Name | Type | Description |
|---|---|---|
key |
DuiKey
|
The key the controller drives. Subclasses must assign this. |
Source code in src/deckui/ui/controller.py
on_attach
async
¶
Hook invoked from the app's on_connect callback.
Default implementation is a no-op. See
:meth:CardController.on_attach for guidance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
deck
|
Deck
|
The freshly-connected :class: |
required |
Source code in src/deckui/ui/controller.py
on_detach
async
¶
DialAccumulator ¶
Debounce rapid dial/encoder ticks and flush them with a single callback.
callback - async def callback(steps: int) called once per flush
with the net accumulated tick count (signed).
delay - seconds to wait after the last tick before flushing.
max_steps - cap on how many ticks can accumulate (positive number).
Use max_steps=1 to collapse any number of ticks into
a single +1 / -1 event (useful for next/previous).
Examples:
::
acc = DialAccumulator(my_handler, delay=0.2, max_steps=5)
@encoder.on_turn
async def on_turn(direction: int):
acc.tick(direction)
Source code in src/deckui/ui/controls/dial_accumulator.py
tick ¶
Add direction (+1 or -1). Clamps to ±max_steps.
Source code in src/deckui/ui/controls/dial_accumulator.py
cancel ¶
Cancel any pending flush and reset the accumulated count.
Source code in src/deckui/ui/controls/dial_accumulator.py
EncoderSlot ¶
Represents a single physical rotary encoder on the Stream Deck+.
Examples:
Use decorators to register event handlers::
@encoder.on_turn
async def handle(direction: int):
print(f"Turned by {direction}")
Source code in src/deckui/ui/controls/encoder_slot.py
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | |
on_turn ¶
Decorator to register a handler for encoder turn events.
The handler receives a single direction argument:
positive = clockwise, negative = counter-clockwise.
When a press_turn handler is also registered, on_turn only
fires for turns while the encoder is not pressed.
Examples:
::
@encoder.on_turn
async def handle(direction: int):
...
Source code in src/deckui/ui/controls/encoder_slot.py
on_turn_accumulated ¶
on_turn_accumulated(callback: Callable[[int], Awaitable[None]] | None = None, *, delay: float = 0.25, max_steps: int = 10) -> DialAccumulator | Callable[[Callable[[int], Awaitable[None]]], DialAccumulator]
Register an accumulated turn handler backed by a :class:DialAccumulator.
Can be used as a plain decorator or called with keyword arguments::
# Plain decorator — default delay/max_steps
@encoder.on_turn_accumulated
async def handle(steps: int):
...
# With options
@encoder.on_turn_accumulated(delay=0.1, max_steps=5)
async def handle(steps: int):
...
Returns the :class:DialAccumulator instance (which replaces the
decorated function reference).
Source code in src/deckui/ui/controls/encoder_slot.py
on_press_turn ¶
Decorator to register a handler for turning while pressed.
The handler receives a single direction argument, just like
:meth:on_turn. When registered, on_turn will not fire
for turns that happen while the encoder is held down.
Examples:
::
@encoder.on_press_turn
async def handle(direction: int):
...
Source code in src/deckui/ui/controls/encoder_slot.py
on_press_turn_accumulated ¶
on_press_turn_accumulated(callback: Callable[[int], Awaitable[None]] | None = None, *, delay: float = 0.25, max_steps: int = 10) -> DialAccumulator | Callable[[Callable[[int], Awaitable[None]]], DialAccumulator]
Register an accumulated press-turn handler backed by a :class:DialAccumulator.
Same API as :meth:on_turn_accumulated but only fires while the
encoder is held down.
Examples:
::
@encoder.on_press_turn_accumulated(delay=0.1, max_steps=5)
async def handle(steps: int):
...
Source code in src/deckui/ui/controls/encoder_slot.py
on_press ¶
Decorator to register a handler for encoder press events.
Examples:
::
@encoder.on_press
async def handle():
...
Source code in src/deckui/ui/controls/encoder_slot.py
on_release ¶
Decorator to register a handler for encoder release events.
Examples:
::
@encoder.on_release
async def handle():
...
Source code in src/deckui/ui/controls/encoder_slot.py
dispatch_turn
async
¶
Dispatch an encoder turn event through the registered handler.
When a press_turn handler is registered, turns while pressed
are routed to it instead of the regular turn handler (matching
the priority logic of :class:~deckui.dui.event_map.EventMap).
Source code in src/deckui/ui/controls/encoder_slot.py
dispatch_press
async
¶
Dispatch an encoder press or release event.
Source code in src/deckui/ui/controls/encoder_slot.py
KeySlot ¶
Represents a single physical key on the Stream Deck.
Examples:
Use decorators to register event handlers::
@key.on_press
async def handle():
print("pressed!")
Source code in src/deckui/ui/controls/key_slot.py
image_bytes
property
¶
The pre-rendered image bytes, or None if not yet rendered.
set_refresh_callback ¶
Register an async callback the key can invoke to request a refresh.
This is set automatically by :class:~deckui.runtime.deck.Deck
when a screen is activated, so any code path (key handler,
background task, external state change) can call
:meth:request_refresh to trigger a re-render.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
callback
|
AsyncHandler
|
Async callable that triggers a deck refresh. |
required |
Source code in src/deckui/ui/controls/key_slot.py
request_refresh
async
¶
Ask the deck to re-render the active screen.
No-op if no refresh callback has been registered.
on_press ¶
Decorator to register a handler for key press events.
Examples:
::
@key.on_press
async def handle():
...
Source code in src/deckui/ui/controls/key_slot.py
on_release ¶
Decorator to register a handler for key release events.
Examples:
::
@key.on_release
async def handle():
...
Source code in src/deckui/ui/controls/key_slot.py
dispatch
async
¶
Dispatch a press or release event through the registered handlers.
mark_clean ¶
set_rendered_image ¶
InfoScreen ¶
Manage content on a non-touch info screen (e.g. Stream Deck Neo 248x58).
The info screen is a small display that shows status information. It does not support touch events or interactive cards — it is a simple image buffer with dirty tracking.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
width
|
int
|
Screen width in pixels. |
required |
height
|
int
|
Screen height in pixels. |
required |
image_format
|
str
|
Image format for encoding ( |
'JPEG'
|
Source code in src/deckui/ui/info_screen.py
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | |
set_image ¶
Set the info screen image and mark dirty.
The image is resized to fit the screen dimensions if needed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
image
|
Image
|
A PIL Image to display. |
required |
Source code in src/deckui/ui/info_screen.py
clear ¶
mark_clean ¶
mark_dirty ¶
render_bytes ¶
Encode the current image to bytes in the device's format.
Returns:
| Type | Description |
|---|---|
bytes
|
Encoded image bytes. If no image has been set, returns a blank black image. |
Source code in src/deckui/ui/info_screen.py
Screen ¶
A named layout containing keys, encoders, and touch-strip cards.
Screens allow you to define multiple layouts and switch between them. When a screen is activated, all key images, touch-strip cards, and event handlers swap atomically.
The number of available keys, encoders, and card zones is determined
by the device capabilities. For devices without encoders or a
touchscreen, those features are unavailable and accessing them raises
:class:~deckui.runtime.deck.DeckError.
Examples:
::
main = deck.screen("main")
@main.key(0).on_press
async def handle():
await deck.set_screen("settings")
Source code in src/deckui/ui/screen.py
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | |
capabilities
property
¶
The device capabilities this screen is configured for.
keys
property
¶
Mapping of key index to :class:KeySlot for all configured keys.
encoders
property
¶
Mapping of encoder index to :class:EncoderSlot for all configured encoders.
touch_strip
property
¶
The touch strip, or None if the device has no touchscreen.
info_screen
property
¶
The info screen, or None if the device has no info display.
touchstrip_background
property
writable
¶
The fill colour for the touchscreen canvas behind cards.
cards
property
¶
All touch-strip cards, or an empty list if the device has no touchscreen.
key ¶
Get or create a key slot by index.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
Key index (0 to key_count-1). |
required |
Returns:
| Type | Description |
|---|---|
KeySlot
|
The KeySlot instance for this key on this screen. |
Source code in src/deckui/ui/screen.py
set_key ¶
Replace the key slot at index with a custom key.
The same KeySlot (or :class:~deckui.dui.DuiKey) instance may
be installed on multiple screens at different slot indices — the
screen's slot map is the single source of truth for routing, so
no state is mutated on key itself.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
Key index. |
required |
key
|
KeySlot
|
The key slot to install. |
required |
Source code in src/deckui/ui/screen.py
encoder ¶
Get or create an encoder slot by index.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
Encoder index (0 to dial_count-1). |
required |
Returns:
| Type | Description |
|---|---|
EncoderSlot
|
The EncoderSlot instance for this encoder on this screen. |
Raises:
| Type | Description |
|---|---|
IndexError
|
If index is out of range or device has no encoders. |
Source code in src/deckui/ui/screen.py
card ¶
set_card ¶
Replace the card at index with a custom card.
TouchStrip ¶
Manage card zones on the Stream Deck touch strip.
The number of card zones is determined by the device's dial count (typically 4 for Stream Deck+).
The background_color fills the entire touchscreen canvas — including
the margin and gap areas outside card panels. Each :class:Screen
owns its own TouchStrip, so different screens can use different
background colours.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
panel_count
|
int
|
Number of card zones. |
4
|
background_color
|
str
|
Initial background colour. |
'black'
|
Source code in src/deckui/ui/touch_strip.py
background_color
property
writable
¶
The fill colour for the touchscreen canvas (margins and gaps).
card ¶
Get a card zone by index.
set_card ¶
Replace the card at index with a custom card.
The same Card instance may be installed on multiple screens
(and, on a single screen, in different slots across screens) —
the strip's slot list is the single source of truth for routing,
so no state is mutated on card itself.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
Card zone index. |
required |
card
|
Card
|
A :class: |
required |
Raises:
| Type | Description |
|---|---|
IndexError
|
If index is out of range. |
TypeError
|
If card is not a :class: |