Skip to content

Add row_selected/row_unselected theme keys for row highlighting - #63

Open
HoneyHazard wants to merge 1 commit into
tsowell:mainfrom
HoneyHazard:theme-row-selected-text
Open

Add row_selected/row_unselected theme keys for row highlighting#63
HoneyHazard wants to merge 1 commit into
tsowell:mainfrom
HoneyHazard:theme-row-selected-text

Conversation

@HoneyHazard

@HoneyHazard HoneyHazard commented Aug 5, 2026

Copy link
Copy Markdown

⚠️ Full Disclosure: Drafted with AI assistance (Claude); reviewed by me briefly. I am neither a RUST developer nor pipewire expert. If I should stop making these PRs into your wonderful project, please let me know. ⚠️

That being said, I hope these can be helpful and useful additions that users could appreciate.


Selection state currently only reaches SelectorWidget (the 3-row gutter marker) - node_title, node_target, the volume% label, the "muted" label, and (in the Configuration tab) the device name and profile line are all styled identically whether their row is selected or not, with no way to change that from a theme.

Adds a new theme key, row_selected: Style, with these properties:

  • Applied as a single buf.set_style(area, ...) fill over the selected row's whole Rect before any of its spans are drawn. ratatui's Cell::set_style only overwrites fg/bg when the incoming style has Some(...) for that field, so unstyled spans (node_title etc., which default to { }) inherit the fill, while spans that already set their own color (meter_active, volume_filled, ...) still paint over it for their own glyphs.
  • Covers blank padding/spacer cells between spans that per-span styling could never reach, so the highlight reads as a clean full-width bar rather than patchy text-only coloring.
  • Empty ({ }) in all three built-in themes, so it's a no-op unless a theme explicitly sets it - fully backward compatible.
  • Covers both the Playback/Recording/Output/Input Devices node list (node_widget.rs) and the Configuration tab's device list (device_widget.rs), which are separate widget implementations and both needed the same treatment.

Here's a screenshot showing an example of row_selected usage:

row_selected example

Also adds two new config options, row_selected_extend_above and row_selected_extend_below (both default false), which extend the row_selected fill:

  • Let the row_selected fill bleed one row above and/or below the row itself, into the gap between items, independently in each direction, so the highlight doesn't cut off abruptly right at the row's own edges.
  • The selector marker (the left gutter column) extends to match, using the selector_middle glyph for the extra row - but only when row_selected is actually customized away from its default empty style, since drawing extra marker glyphs is real cell content rather than a style patch and can't rely on being inert by default the way the background fill can.
  • Clipped to the list's own area, so the extension can never bleed into a neighboring item or past the list into the footer/tab bar.
  • Both default to false, so there's no size or behavior change for any existing config unless explicitly turned on.

Here's a screenshot showing an example of row_selected_extend_above usage:

row_selected_extend_above example

Here's a screenshot showing an example of row_selected_extend_below usage:

row_selected_extend_below example

Here's a screenshot showing both together:

row_selected_extend_above and row_selected_extend_below together

Finally, adds a second new theme key, row_unselected: Style, that complements row_selected from the other direction:

  • Patched onto the text spans of every row that isn't selected - node_title, node_target, the volume% label, the "muted" label, and (Configuration tab) config_device/config_profile.
  • Before this PR, those spans had no selection-awareness at all - they render identically whether their row is selected or not. row_unselected is the only hook that lets them differ by selection state, independently of whatever row_selected does to the selected row. That matters for a "make the selection stand out from both directions" look: dim everything else (e.g. fg = "DarkGray") so contrast comes from receding the unselected rows as well as highlighting the selected one, not just from the selected row being brighter.
  • Deliberately per-span rather than a whole-row fill (unlike row_selected), since the goal is a faint text-only tint distinguishing item rows from the blank gaps between them, without a second block-style highlight competing with the selected row's own look.
  • Empty ({ }) by default in all three built-in themes, i.e. no change from an item's normal look - fully backward compatible.

Here's a screenshot showing an example of row_unselected usage:

row_unselected example

New config keys (all optional, all no-ops when unset):

[themes.default]
row_selected = { fg = "Black", bg = "Cyan", add_modifier = "BOLD" }
row_unselected = { fg = "DarkGray" }

row_selected_extend_above = false
row_selected_extend_below = false

Tested:

  • cargo test --release: 144/144 passing
  • cargo fmt --check / cargo clippy -- -D warnings / cargo doc (matching this repo's CI): all clean
  • Manual verification via raw ANSI capture and buffer-cell inspection in tmux that: the row_selected fill spans the full row width (including blank spacer lines and trailing padding); row_selected_extend_above/_below independently control whether the fill (and matching marker) extend one row above/below; row_unselected tints only the text spans of unselected rows, leaving blank padding/gaps and the selected row untouched; and meter/volume foreground colors still render correctly on top of both - in both the node list and the Configuration tab
  • Verified leaving row_selected, row_unselected, and the two extend options unset reproduces stock behavior exactly

@HoneyHazard
HoneyHazard force-pushed the theme-row-selected-text branch from 7854ed3 to 0417ed0 Compare August 5, 2026 11:41
@HoneyHazard HoneyHazard changed the title Add row_selected theme key for highlighting the selected row's text Add row_selected theme key for highlighting the selected row Aug 5, 2026
@HoneyHazard
HoneyHazard force-pushed the theme-row-selected-text branch 3 times, most recently from 9abd526 to 3b07cef Compare August 5, 2026 22:44
@HoneyHazard HoneyHazard changed the title Add row_selected theme key for highlighting the selected row Add row_selected/row_unselected theme keys for row highlighting Aug 5, 2026
@HoneyHazard
HoneyHazard force-pushed the theme-row-selected-text branch 2 times, most recently from 77d60e6 to 44f66ab Compare August 6, 2026 11:38
@HoneyHazard
HoneyHazard marked this pull request as ready for review August 6, 2026 12:18
Selection state currently only reaches SelectorWidget (the 3-row gutter
marker) - node_title, node_target, the volume% label, the "muted" label,
and (in the Configuration tab) the device name and profile line are all
styled identically whether their row is selected or not, with no way to
change that from a theme.

Adds a new theme key, row_selected: Style, applied as a single
buf.set_style(area, ...) fill over the selected row's whole Rect before
any of its spans are drawn. ratatui's Cell::set_style only overwrites
fg/bg when the incoming style has Some(...) for that field, so unstyled
spans (node_title etc., which default to {}) inherit the fill, while
spans that already set their own color (meter_active, volume_filled,
...) still paint over it for their own glyphs. This also covers blank
padding/spacer cells between spans that per-span styling could never
reach. Empty ({}) in all three built-in themes, so this is a no-op
unless a theme explicitly sets it - fully backward compatible.

Covers both the Playback/Recording/Output/Input Devices node list
(node_widget.rs) and the Configuration tab's device list
(device_widget.rs), which have separate widget implementations and both
needed the same treatment.

Two new config options, row_selected_extend_above and
row_selected_extend_below (both default false), let the row_selected
fill bleed one row above and/or below the row itself, into the gap
between items, so the highlight doesn't cut off abruptly right at the
row's own edges. The selector marker (the left gutter column) extends
to match, using the selector_middle glyph for the extra row - but only
when row_selected is actually customized away from its default empty
style, since drawing extra marker glyphs is real cell content rather
than a style patch and can't rely on being inert by default the way the
background fill can. The extension is clipped to the list's own area so
it can never bleed into a neighboring item - except for the true first/
last item in the whole list, where it's allowed to extend into the
header/footer area specifically when no scroll indicator is being drawn
there (i.e. there's genuinely nothing above/below to scroll to), so the
highlight doesn't stop short right at the very top/bottom of the list.

A second new theme key, row_unselected: Style, complements row_selected
from the other direction: it's patched (via Style::patch, same
only-fields-you-set-override semantics) onto the text spans of every
row that ISN'T selected - node_title, node_target, the volume% label,
the "muted" label, and (in the Configuration tab) config_device/
config_profile. Unlike row_selected this is deliberately per-span, not
a whole-row fill, since the goal is a faint text-only tint that
distinguishes item rows from the blank gaps between them without
producing a second, competing block-style highlight. Empty ({}) by
default in all three built-in themes, so also fully backward
compatible.

Tested: cargo test --release (146/146 passing), cargo fmt --check,
cargo clippy -- -D warnings, and cargo doc (all matching wiremix's CI)
clean. Manually verified via raw ANSI capture and buffer-cell inspection
in tmux that: the row_selected fill spans the full row width including
blank spacer lines and trailing padding; row_selected_extend_above/
_below independently control whether the fill (and matching marker)
extend one row above/below the row, including correctly reaching the
header/footer area for the true first/last item while leaving actual
scroll-up/-down indicators alone when they're needed; row_unselected
tints only the text spans of unselected rows and leaves blank padding/
gaps and the selected row untouched; and meter/volume foreground colors
still render correctly on top of both - in both the node list and the
Configuration tab. Also verified that leaving row_selected,
row_unselected, and the two extend options unset reproduces stock
behavior exactly.
@HoneyHazard
HoneyHazard force-pushed the theme-row-selected-text branch from 44f66ab to 015cc90 Compare August 9, 2026 04:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant