Add row_selected/row_unselected theme keys for row highlighting - #63
Open
HoneyHazard wants to merge 1 commit into
Open
Add row_selected/row_unselected theme keys for row highlighting#63HoneyHazard wants to merge 1 commit into
HoneyHazard wants to merge 1 commit into
Conversation
HoneyHazard
force-pushed
the
theme-row-selected-text
branch
from
August 5, 2026 11:41
7854ed3 to
0417ed0
Compare
HoneyHazard
force-pushed
the
theme-row-selected-text
branch
3 times, most recently
from
August 5, 2026 22:44
9abd526 to
3b07cef
Compare
HoneyHazard
force-pushed
the
theme-row-selected-text
branch
2 times, most recently
from
August 6, 2026 11:38
77d60e6 to
44f66ab
Compare
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
force-pushed
the
theme-row-selected-text
branch
from
August 9, 2026 04:29
44f66ab to
015cc90
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:buf.set_style(area, ...)fill over the selected row's wholeRectbefore any of its spans are drawn. ratatui'sCell::set_styleonly overwritesfg/bgwhen the incoming style hasSome(...)for that field, so unstyled spans (node_titleetc., 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.{ }) in all three built-in themes, so it's a no-op unless a theme explicitly sets it - fully backward compatible.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_selectedusage:Also adds two new config options,
row_selected_extend_aboveandrow_selected_extend_below(both defaultfalse), which extend therow_selectedfill:row_selectedfill 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.selector_middleglyph for the extra row - but only whenrow_selectedis 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.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_aboveusage:Here's a screenshot showing an example of
row_selected_extend_belowusage:Here's a screenshot showing both together:
Finally, adds a second new theme key,
row_unselected: Style, that complementsrow_selectedfrom the other direction:node_title,node_target, the volume% label, the "muted" label, and (Configuration tab)config_device/config_profile.row_unselectedis the only hook that lets them differ by selection state, independently of whateverrow_selecteddoes 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.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.{ }) 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_unselectedusage:New config keys (all optional, all no-ops when unset):
Tested:
cargo test --release: 144/144 passingcargo fmt --check/cargo clippy -- -D warnings/cargo doc(matching this repo's CI): all cleanrow_selectedfill spans the full row width (including blank spacer lines and trailing padding);row_selected_extend_above/_belowindependently control whether the fill (and matching marker) extend one row above/below;row_unselectedtints 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 tabrow_selected,row_unselected, and the two extend options unset reproduces stock behavior exactly