Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,8 @@ mod tests {
tabs: vec![TabKind::Playback],
lazy_capture: Default::default(),
filters: Default::default(),
row_selected_extend_above: Default::default(),
row_selected_extend_below: Default::default(),
};

let mut app = App::new(wirehose, event_rx, config);
Expand Down Expand Up @@ -983,6 +985,8 @@ mod tests {
],
lazy_capture: Default::default(),
filters: Default::default(),
row_selected_extend_above: Default::default(),
row_selected_extend_below: Default::default(),
};
let mut app = App::new(&wirehose, event_rx, config);

Expand Down
18 changes: 18 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub struct Config {
pub peaks: Peaks,
pub char_set: CharSet,
pub theme: Theme,
pub row_selected_extend_above: bool,
pub row_selected_extend_below: bool,
pub max_volume_percent: f32,
pub enforce_max_volume: bool,
pub keybindings: HashMap<KeyEvent, Action>,
Expand Down Expand Up @@ -64,6 +66,10 @@ struct ConfigFile {
char_set: String,
#[serde(default = "default_theme_name")]
theme: String,
#[serde(default = "default_row_selected_extend")]
row_selected_extend_above: bool,
#[serde(default = "default_row_selected_extend")]
row_selected_extend_below: bool,
#[serde(default = "default_max_volume_percent")]
max_volume_percent: Option<f32>,
#[serde(default = "default_enforce_max_volume")]
Expand Down Expand Up @@ -193,6 +199,8 @@ pub struct Theme {
pub meter_center_active: Style,
pub config_device: Style,
pub config_profile: Style,
pub row_selected: Style,
pub row_unselected: Style,
pub dropdown_icon: Style,
pub dropdown_border: Style,
pub dropdown_item: Style,
Expand Down Expand Up @@ -270,6 +278,10 @@ fn default_lazy_capture() -> bool {
false
}

fn default_row_selected_extend() -> bool {
false
}

impl ConfigFile {
/// Override configuration with command-line arguments.
pub fn apply_opt(&mut self, opt: &Opt) {
Expand Down Expand Up @@ -390,6 +402,8 @@ impl TryFrom<ConfigFile> for Config {
enforce_max_volume: config_file.enforce_max_volume,
char_set,
theme,
row_selected_extend_above: config_file.row_selected_extend_above,
row_selected_extend_below: config_file.row_selected_extend_below,
keybindings: config_file.keybindings,
help,
names: config_file.names,
Expand Down Expand Up @@ -469,6 +483,8 @@ pub mod strict {
peaks: Option<Peaks>,
char_set: String,
theme: String,
row_selected_extend_above: bool,
row_selected_extend_below: bool,
max_volume_percent: Option<f32>,
enforce_max_volume: bool,
#[serde(deserialize_with = "keybindings")]
Expand All @@ -493,6 +509,8 @@ pub mod strict {
peaks: strict.peaks,
char_set: strict.char_set,
theme: strict.theme,
row_selected_extend_above: strict.row_selected_extend_above,
row_selected_extend_below: strict.row_selected_extend_below,
max_volume_percent: strict.max_volume_percent,
enforce_max_volume: strict.enforce_max_volume,
keybindings: strict.keybindings,
Expand Down
12 changes: 12 additions & 0 deletions src/config/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ pub struct ThemeOverlay {
meter_center_active: Option<StyleDef>,
config_device: Option<StyleDef>,
config_profile: Option<StyleDef>,
// Whole-row overlays: span everything above, from node_title/config_device
// through config_profile, in both the node list and the Configuration tab.
row_selected: Option<StyleDef>,
row_unselected: Option<StyleDef>,
dropdown_icon: Option<StyleDef>,
dropdown_border: Option<StyleDef>,
dropdown_item: Option<StyleDef>,
Expand Down Expand Up @@ -108,6 +112,8 @@ impl TryFrom<ThemeOverlay> for Theme {
set!(meter_center_active);
set!(config_device);
set!(config_profile);
set!(row_selected);
set!(row_unselected);
set!(dropdown_icon);
set!(dropdown_border);
set!(dropdown_item);
Expand Down Expand Up @@ -143,6 +149,8 @@ impl Default for Theme {
meter_center_active: Style::default().fg(Color::LightGreen),
config_device: Style::default(),
config_profile: Style::default(),
row_selected: Style::default(),
row_unselected: Style::default(),
dropdown_icon: Style::default(),
dropdown_border: Style::default(),
dropdown_item: Style::default(),
Expand Down Expand Up @@ -187,6 +195,8 @@ impl Theme {
meter_center_active: Style::default().add_modifier(Modifier::BOLD),
config_device: Style::default(),
config_profile: Style::default(),
row_selected: Style::default(),
row_unselected: Style::default(),
dropdown_icon: Style::default(),
dropdown_border: Style::default(),
dropdown_item: Style::default(),
Expand Down Expand Up @@ -220,6 +230,8 @@ impl Theme {
meter_center_active: Style::default(),
config_device: Style::default(),
config_profile: Style::default(),
row_selected: Style::default(),
row_unselected: Style::default(),
dropdown_icon: Style::default(),
dropdown_border: Style::default(),
dropdown_item: Style::default(),
Expand Down
22 changes: 20 additions & 2 deletions src/device_widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use ratatui::{
layout::Flex,
prelude::{Buffer, Constraint, Direction, Layout, Rect},
style::Style,
text::{Line, Span},
widgets::{StatefulWidget, Widget},
};
Expand All @@ -12,6 +13,7 @@ use smallvec::smallvec;

use crate::app::{Action, MouseArea};
use crate::config::Config;
use crate::node_widget::row_text_style;
use crate::object_list::ObjectList;
use crate::view;

Expand Down Expand Up @@ -70,12 +72,25 @@ impl<'a> DeviceWidget<'a> {

Rect::new(x, y, width, height)
}

/// See `node_widget::row_text_style`.
fn text_style(&self, style: Style) -> Style {
row_text_style(self.selected, style, self.config)
}
}

impl StatefulWidget for DeviceWidget<'_> {
type State = Vec<MouseArea>;

fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
// See node_widget::NodeWidget::render - same whole-row background
// fill so the Configuration tab gets the same selected-row
// highlight as the other tabs, covering blank space too rather
// than just the text glyphs.
if self.selected {
buf.set_style(area, self.config.theme.row_selected);
}

let mouse_areas = state;

mouse_areas.push((
Expand Down Expand Up @@ -134,7 +149,10 @@ impl StatefulWidget for DeviceWidget<'_> {

Line::from(vec![
Span::from(" "),
Span::styled(&self.device.title, self.config.theme.config_device),
Span::styled(
&self.device.title,
self.text_style(self.config.theme.config_device),
),
])
.render(title_area, buf);

Expand All @@ -147,7 +165,7 @@ impl StatefulWidget for DeviceWidget<'_> {
Span::from(" "),
Span::styled(
&self.device.target_title,
self.config.theme.config_profile,
self.text_style(self.config.theme.config_profile),
),
])
.render(target_area, buf);
Expand Down
87 changes: 73 additions & 14 deletions src/node_widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::sync::atomic::Ordering;
use ratatui::{
layout::Flex,
prelude::{Alignment, Buffer, Constraint, Direction, Layout, Rect},
style::Style,
text::{Line, Span},
widgets::{StatefulWidget, Widget},
};
Expand All @@ -27,6 +28,25 @@ fn is_default(node: &view::Node, device_kind: Option<DeviceKind>) -> bool {
}
}

/// Patches `row_unselected` on top of `style` whenever `selected` is
/// false. Unlike `row_selected` (a whole-row background fill applied
/// unconditionally by the row's own top-level widget), this only ever
/// touches text spans, so it's applied per-span rather than as a single
/// area fill. Shared by every per-row widget that renders text spans
/// (`HeaderWidget`, `VolumeWidget`, `device_widget::DeviceWidget`)
/// instead of each carrying its own copy of the same two-line branch.
pub(crate) fn row_text_style(
selected: bool,
style: Style,
config: &Config,
) -> Style {
if selected {
style
} else {
style.patch(config.theme.row_unselected)
}
}

pub struct NodeWidget<'a> {
config: &'a Config,
device_kind: Option<DeviceKind>,
Expand Down Expand Up @@ -93,6 +113,18 @@ impl StatefulWidget for NodeWidget<'_> {
type State = Vec<MouseArea>;

fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
// Fill the whole row's background first (not just under the text)
// when selected. ratatui's Cell::set_style only overwrites fg/bg
// when the incoming style has Some(...) for that field - unstyled
// spans (node_title etc. default to `{ }`) leave this fill alone,
// while spans that set their own color explicitly (meter_active,
// volume_filled...) still override it for their own glyphs. So a
// single fill here covers blank padding/gaps that per-span styling
// could never reach, while every other color stays meaningful.
if self.selected {
buf.set_style(area, self.config.theme.row_selected);
}

let mouse_areas = state;

mouse_areas.extend([
Expand Down Expand Up @@ -156,14 +188,16 @@ impl StatefulWidget for NodeWidget<'_> {
let header_area = layout[0];
let bar_area = layout[1];

HeaderWidget::new(self.config, self.device_kind, self.node).render(
header_area,
buf,
mouse_areas,
);
HeaderWidget::new(
self.config,
self.device_kind,
self.node,
self.selected,
)
.render(header_area, buf, mouse_areas);

// Render volume bar and (if enabled) peak meter
let volume = VolumeWidget::new(self.config, self.node);
let volume = VolumeWidget::new(self.config, self.node, self.selected);
if self.config.peaks == Peaks::Off {
let layout = Layout::default()
.direction(Direction::Horizontal)
Expand Down Expand Up @@ -240,21 +274,29 @@ struct HeaderWidget<'a> {
config: &'a Config,
device_kind: Option<DeviceKind>,
node: &'a view::Node,
selected: bool,
}

impl<'a> HeaderWidget<'a> {
fn new(
config: &'a Config,
device_kind: Option<DeviceKind>,
node: &'a view::Node,
selected: bool,
) -> Self {
Self {
config,
device_kind,
node,
selected,
}
}

/// See `row_text_style`.
fn text_style(&self, style: Style) -> Style {
row_text_style(self.selected, style, self.config)
}

fn target_line(&self) -> Line<'_> {
match self.node.target {
Some(view::Target::Default) => {
Expand All @@ -267,13 +309,13 @@ impl<'a> HeaderWidget<'a> {
Span::from(" "),
Span::styled(
&self.node.target_title,
self.config.theme.node_target,
self.text_style(self.config.theme.node_target),
),
])
}
_ => Line::from(Span::styled(
&self.node.target_title,
self.config.theme.node_target,
self.text_style(self.config.theme.node_target),
)),
}
}
Expand All @@ -290,7 +332,10 @@ impl<'a> HeaderWidget<'a> {
Line::from(vec![
default_span,
Span::from(" "),
Span::styled(&self.node.title, self.config.theme.node_title),
Span::styled(
&self.node.title,
self.text_style(self.config.theme.node_title),
),
])
}
}
Expand Down Expand Up @@ -337,7 +382,7 @@ impl StatefulWidget for HeaderWidget<'_> {
let ellipses_area = layout[1];
target_area = layout[3];

Span::styled("...", self.config.theme.node_title)
Span::styled("...", self.text_style(self.config.theme.node_title))
.render(ellipses_area, buf);
}
let (title_area, target_area) = (title_area, target_area);
Expand All @@ -362,11 +407,21 @@ impl StatefulWidget for HeaderWidget<'_> {
struct VolumeWidget<'a> {
config: &'a Config,
node: &'a view::Node,
selected: bool,
}

impl<'a> VolumeWidget<'a> {
fn new(config: &'a Config, node: &'a view::Node) -> Self {
Self { config, node }
fn new(config: &'a Config, node: &'a view::Node, selected: bool) -> Self {
Self {
config,
node,
selected,
}
}

/// See `row_text_style`.
fn text_style(&self, style: Style) -> Style {
row_text_style(self.selected, style, self.config)
}
}

Expand Down Expand Up @@ -397,7 +452,7 @@ impl StatefulWidget for VolumeWidget<'_> {

Line::from(Span::styled(
format!("{percent}%"),
self.config.theme.volume,
self.text_style(self.config.theme.volume),
))
.alignment(Alignment::Right)
.render(volume_label, buf);
Expand All @@ -419,7 +474,11 @@ impl StatefulWidget for VolumeWidget<'_> {
.render(volume_bar, buf);
}
if self.node.mute {
Line::from("muted").render(volume_label, buf);
Line::from(Span::styled(
"muted",
self.text_style(Style::default()),
))
.render(volume_label, buf);
}

mouse_areas.push((
Expand Down
Loading
Loading