use ratatui::text::Text;
#[derive(Clone, Debug)]
pub struct GridItem(String);
impl GridItem {
pub fn new<S>(value: S) -> Self
where
S: Into<String>,
{
GridItem(value.into())
}
}
impl AsRef<str> for GridItem {
fn as_ref(&self) -> &str {
&self.0
}
}
impl From<GridItem> for String {
fn from(val: GridItem) -> Self {
val.0.clone()
}
}
impl<'a> From<GridItem> for Text<'a> {
fn from(val: GridItem) -> Self {
Text::from(val.0)
}
}
impl From<String> for GridItem {
fn from(value: String) -> Self {
GridItem(value)
}
}
impl<'a> From<&'a str> for GridItem {
fn from(value: &'a str) -> Self {
GridItem(value.to_string())
}
}
#[derive(Debug, Clone)]
pub struct GridSelectorState {
pub items: Vec<GridItem>,
pub selected: Option<usize>,
pub hovered: Option<usize>,
pub(crate) columns: usize,
}
impl GridSelectorState {
pub fn new<I, T>(items: I) -> Self
where
I: IntoIterator<Item = T>,
T: Into<GridItem>, {
let items: Vec<GridItem> = items.into_iter().map(Into::into).collect();
Self {
items,
selected: None,
hovered: Some(0),
columns: 5,
}
}
pub fn columns(mut self, columns: usize) -> Self {
self.columns = columns;
self
}
pub fn selected(&self) -> Option<GridItem> {
self.selected.map(|i| self.items[i].clone())
}
pub fn selected_index(&self) -> Option<usize> {
self.selected
}
pub fn hovered(&self) -> Option<GridItem> {
self.hovered.map(|i| self.items[i].clone())
}
pub fn move_right(&mut self) -> bool {
self.hovered = if let Some(hovered) = self.hovered {
let next = hovered + 1;
if next < self.items.len() {
Some(next)
} else {
Some(0)
}
} else {
Some(0)
};
true
}
pub fn move_left(&mut self) -> bool {
self.hovered = if let Some(hovered) = self.hovered {
if hovered > 0 {
Some(hovered - 1)
} else {
Some(self.items.len() - 1)
}
} else {
Some(0)
};
true
}
pub fn move_down(&mut self) -> bool {
if let Some(hovered) = self.hovered {
let items_per_row = self.columns;
let num_items = self.items.len();
let current_row = hovered / items_per_row;
let next_row_start = (current_row + 1) * items_per_row;
let last_item_index = num_items - 1;
if next_row_start > last_item_index {
return false;
}
let mut next_index = std::cmp::min(hovered + items_per_row, last_item_index);
let next_row_count = std::cmp::min(items_per_row, last_item_index - next_row_start + 1);
if next_row_count % 2 != 0 && items_per_row % 2 != 0 && next_row_count < items_per_row {
let shift = (items_per_row - next_row_count) / 2;
if hovered % items_per_row >= shift
&& hovered % items_per_row < items_per_row - shift
{
next_index = hovered + items_per_row - shift;
} else {
next_index = if hovered % items_per_row < shift {
next_row_start
} else {
last_item_index
};
}
}
self.hovered = Some(std::cmp::min(next_index, last_item_index));
return true;
}
false
}
pub fn move_up(&mut self) -> bool {
if let Some(hovered) = self.hovered {
let row_number = hovered / self.columns;
if row_number == 0 {
return false;
}
let mut next_index = hovered.saturating_sub(self.columns);
let last_row_start = (self.items.len() / self.columns) * self.columns;
let is_last_row = hovered >= last_row_start;
if is_last_row {
let last_row_count = self.items.len() % self.columns;
if last_row_count % 2 != 0 && self.columns % 2 != 0 && last_row_count < self.columns
{
let shift = (self.columns - last_row_count) / 2;
next_index = hovered.saturating_sub(self.columns - shift);
}
}
self.hovered = Some(std::cmp::min(next_index, self.items.len() - 1));
return true;
}
false
}
pub fn move_to_row_start(&mut self) -> bool {
if let Some(hovered) = self.hovered {
let row_start = (hovered / self.columns) * self.columns;
self.hovered = Some(row_start);
true
} else {
false
}
}
pub fn move_to_row_end(&mut self) -> bool {
if let Some(hovered) = self.hovered {
let row_end = std::cmp::min(
(hovered / self.columns + 1) * self.columns - 1,
self.items.len() - 1,
);
self.hovered = Some(row_end);
true
} else {
false
}
}
pub fn select(&mut self) -> bool {
if let Some(hovered) = self.hovered {
self.selected = Some(hovered);
true
} else {
false
}
}
}