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
44 changes: 24 additions & 20 deletions src/component/metrics_dashboard/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
//!
//! assert_eq!(state.widget_count(), 3);
//! assert_eq!(state.columns(), 3);
//! assert_eq!(state.selected_index(), 0);
//! assert_eq!(state.selected_index(), Some(0));
//! ```

use std::marker::PhantomData;
Expand Down Expand Up @@ -273,7 +273,7 @@ pub struct MetricsDashboardState {
/// Number of columns in the grid layout.
columns: usize,
/// Currently selected widget index.
selected: usize,
selected: Option<usize>,
/// Whether the component is focused.
focused: bool,
/// Whether the component is disabled.
Expand All @@ -287,7 +287,7 @@ impl Default for MetricsDashboardState {
Self {
widgets: Vec::new(),
columns: 3,
selected: 0,
selected: None,
focused: false,
disabled: false,
title: None,
Expand All @@ -311,10 +311,11 @@ impl MetricsDashboardState {
/// assert_eq!(state.columns(), 2);
/// ```
pub fn new(widgets: Vec<MetricWidget>, columns: usize) -> Self {
let selected = if widgets.is_empty() { None } else { Some(0) };
Self {
widgets,
columns: columns.max(1),
selected: 0,
selected,
focused: false,
disabled: false,
title: None,
Expand Down Expand Up @@ -380,18 +381,19 @@ impl MetricsDashboardState {
}

/// Returns the selected widget index.
pub fn selected_index(&self) -> usize {
pub fn selected_index(&self) -> Option<usize> {
self.selected
}

/// Returns a reference to the selected widget.
pub fn selected_widget(&self) -> Option<&MetricWidget> {
self.widgets.get(self.selected)
self.widgets.get(self.selected?)
}

/// Returns the (row, column) position of the selected widget.
pub fn selected_position(&self) -> (usize, usize) {
(self.selected / self.columns, self.selected % self.columns)
pub fn selected_position(&self) -> Option<(usize, usize)> {
let selected = self.selected?;
Some((selected / self.columns, selected % self.columns))
}

/// Returns the title.
Expand Down Expand Up @@ -497,23 +499,25 @@ impl Component for MetricsDashboard {

let len = state.widgets.len();
let cols = state.columns;
let current = state.selected;
let current = state.selected.unwrap_or(0);
let current_row = current / cols;
let current_col = current % cols;

match msg {
MetricsDashboardMessage::Left => {
if current_col > 0 {
state.selected = current - 1;
Some(MetricsDashboardOutput::SelectionChanged(state.selected))
let new_index = current - 1;
state.selected = Some(new_index);
Some(MetricsDashboardOutput::SelectionChanged(new_index))
} else {
None
}
}
MetricsDashboardMessage::Right => {
if current_col < cols - 1 && current + 1 < len {
state.selected = current + 1;
Some(MetricsDashboardOutput::SelectionChanged(state.selected))
let new_index = current + 1;
state.selected = Some(new_index);
Some(MetricsDashboardOutput::SelectionChanged(new_index))
} else {
None
}
Expand All @@ -522,8 +526,8 @@ impl Component for MetricsDashboard {
if current_row > 0 {
let new_index = (current_row - 1) * cols + current_col;
if new_index < len {
state.selected = new_index;
Some(MetricsDashboardOutput::SelectionChanged(state.selected))
state.selected = Some(new_index);
Some(MetricsDashboardOutput::SelectionChanged(new_index))
} else {
None
}
Expand All @@ -534,15 +538,15 @@ impl Component for MetricsDashboard {
MetricsDashboardMessage::Down => {
let new_index = (current_row + 1) * cols + current_col;
if new_index < len {
state.selected = new_index;
Some(MetricsDashboardOutput::SelectionChanged(state.selected))
state.selected = Some(new_index);
Some(MetricsDashboardOutput::SelectionChanged(new_index))
} else {
None
}
}
MetricsDashboardMessage::First => {
if current != 0 {
state.selected = 0;
state.selected = Some(0);
Some(MetricsDashboardOutput::SelectionChanged(0))
} else {
None
Expand All @@ -551,7 +555,7 @@ impl Component for MetricsDashboard {
MetricsDashboardMessage::Last => {
let last = len - 1;
if current != last {
state.selected = last;
state.selected = Some(last);
Some(MetricsDashboardOutput::SelectionChanged(last))
} else {
None
Expand Down Expand Up @@ -593,7 +597,7 @@ impl Component for MetricsDashboard {
for (col_idx, col_area) in col_areas.iter().enumerate() {
let widget_idx = row_idx * cols + col_idx;
if let Some(widget) = state.widgets.get(widget_idx) {
let is_selected = widget_idx == state.selected;
let is_selected = state.selected == Some(widget_idx);
render_widget(widget, is_selected, state, frame, *col_area, theme);
}
}
Expand Down
32 changes: 20 additions & 12 deletions src/component/metrics_dashboard/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ fn test_new() {
let state = MetricsDashboardState::new(sample_widgets(), 3);
assert_eq!(state.widget_count(), 6);
assert_eq!(state.columns(), 3);
assert_eq!(state.selected_index(), 0);
assert_eq!(state.selected_index(), Some(0));
assert!(!state.is_focused());
assert!(!state.is_disabled());
}
Expand Down Expand Up @@ -231,13 +231,13 @@ fn test_rows_empty() {
#[test]
fn test_selected_position() {
let mut state = focused_state();
assert_eq!(state.selected_position(), (0, 0));
assert_eq!(state.selected_position(), Some((0, 0)));

MetricsDashboard::update(&mut state, MetricsDashboardMessage::Right);
assert_eq!(state.selected_position(), (0, 1));
assert_eq!(state.selected_position(), Some((0, 1)));

MetricsDashboard::update(&mut state, MetricsDashboardMessage::Down);
assert_eq!(state.selected_position(), (1, 1));
assert_eq!(state.selected_position(), Some((1, 1)));
}

// =============================================================================
Expand All @@ -248,7 +248,7 @@ fn test_selected_position() {
fn test_right() {
let mut state = focused_state();
let output = MetricsDashboard::update(&mut state, MetricsDashboardMessage::Right);
assert_eq!(state.selected_index(), 1);
assert_eq!(state.selected_index(), Some(1));
assert_eq!(output, Some(MetricsDashboardOutput::SelectionChanged(1)));
}

Expand All @@ -257,7 +257,7 @@ fn test_left() {
let mut state = focused_state();
MetricsDashboard::update(&mut state, MetricsDashboardMessage::Right);
let output = MetricsDashboard::update(&mut state, MetricsDashboardMessage::Left);
assert_eq!(state.selected_index(), 0);
assert_eq!(state.selected_index(), Some(0));
assert_eq!(output, Some(MetricsDashboardOutput::SelectionChanged(0)));
}

Expand All @@ -267,23 +267,23 @@ fn test_right_at_row_end() {
MetricsDashboard::update(&mut state, MetricsDashboardMessage::Right);
MetricsDashboard::update(&mut state, MetricsDashboardMessage::Right);
let output = MetricsDashboard::update(&mut state, MetricsDashboardMessage::Right);
assert_eq!(state.selected_index(), 2);
assert_eq!(state.selected_index(), Some(2));
assert_eq!(output, None);
}

#[test]
fn test_left_at_row_start() {
let mut state = focused_state();
let output = MetricsDashboard::update(&mut state, MetricsDashboardMessage::Left);
assert_eq!(state.selected_index(), 0);
assert_eq!(state.selected_index(), Some(0));
assert_eq!(output, None);
}

#[test]
fn test_down() {
let mut state = focused_state();
let output = MetricsDashboard::update(&mut state, MetricsDashboardMessage::Down);
assert_eq!(state.selected_index(), 3);
assert_eq!(state.selected_index(), Some(3));
assert_eq!(output, Some(MetricsDashboardOutput::SelectionChanged(3)));
}

Expand All @@ -292,7 +292,7 @@ fn test_up() {
let mut state = focused_state();
MetricsDashboard::update(&mut state, MetricsDashboardMessage::Down);
let output = MetricsDashboard::update(&mut state, MetricsDashboardMessage::Up);
assert_eq!(state.selected_index(), 0);
assert_eq!(state.selected_index(), Some(0));
assert_eq!(output, Some(MetricsDashboardOutput::SelectionChanged(0)));
}

Expand All @@ -316,15 +316,15 @@ fn test_first() {
let mut state = focused_state();
MetricsDashboard::update(&mut state, MetricsDashboardMessage::Last);
let output = MetricsDashboard::update(&mut state, MetricsDashboardMessage::First);
assert_eq!(state.selected_index(), 0);
assert_eq!(state.selected_index(), Some(0));
assert_eq!(output, Some(MetricsDashboardOutput::SelectionChanged(0)));
}

#[test]
fn test_last() {
let mut state = focused_state();
let output = MetricsDashboard::update(&mut state, MetricsDashboardMessage::Last);
assert_eq!(state.selected_index(), 5);
assert_eq!(state.selected_index(), Some(5));
assert_eq!(output, Some(MetricsDashboardOutput::SelectionChanged(5)));
}

Expand Down Expand Up @@ -604,6 +604,14 @@ fn test_partial_eq() {
// Edge cases
// =============================================================================

#[test]
fn test_empty_dashboard_selected_index_is_none() {
let state = MetricsDashboardState::default();
assert_eq!(state.selected_index(), None);
assert!(state.selected_widget().is_none());
assert!(state.selected_position().is_none());
}

#[test]
fn test_empty_dashboard_ignores_navigation() {
let mut state = MetricsDashboardState::default();
Expand Down