use serde::{Deserialize, Serialize};
use crate::extras::icon::Icon;
use crate::node::{ActionId, Common, IntoNode, Key, Node};
use crate::style::Style;
use crate::text::{Line, Text};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ListDirection {
#[default]
TopToBottom,
BottomToTop,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum HighlightSpacing {
#[default]
Always,
WhenSelected,
Never,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ListState {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub selected: Option<Selection>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub offset: Option<u32>,
}
impl ListState {
#[must_use]
pub fn with_selected(key: impl Into<Key>) -> Self {
Self {
selected: Some(Selection::One(key.into())),
offset: None,
}
}
#[must_use]
pub fn with_selected_many(keys: Vec<Key>) -> Self {
Self {
selected: Some(Selection::Many(keys)),
offset: None,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Selection {
One(Key),
Many(Vec<Key>),
}
#[allow(
clippy::large_enum_variant,
reason = "list items are built once per list; boxing the content adds noise"
)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ListItem {
Text(Text),
Full {
#[serde(default, skip_serializing_if = "Option::is_none")]
key: Option<Key>,
content: Text,
#[serde(default, skip_serializing_if = "Option::is_none")]
style: Option<Style>,
#[serde(default, skip_serializing_if = "Option::is_none")]
detail: Option<Line>,
#[serde(default, skip_serializing_if = "Option::is_none")]
badge: Option<Line>,
#[serde(default, skip_serializing_if = "Option::is_none")]
icon: Option<Icon>,
#[serde(default, skip_serializing_if = "Option::is_none")]
disabled: Option<bool>,
},
}
impl ListItem {
#[must_use]
pub fn new(content: impl Into<Text>) -> Self {
Self::Full {
key: None,
content: content.into(),
style: None,
detail: None,
badge: None,
icon: None,
disabled: None,
}
}
#[must_use]
pub fn key(mut self, key: impl AsRef<str>) -> Self {
if let Self::Full { key: slot, .. } = &mut self {
*slot = Some(key.as_ref().to_owned());
}
self
}
#[must_use]
pub fn detail(mut self, detail: impl Into<Line>) -> Self {
if let Self::Full { detail: slot, .. } = &mut self {
*slot = Some(detail.into());
}
self
}
#[must_use]
pub fn badge(mut self, badge: impl Into<Line>) -> Self {
if let Self::Full { badge: slot, .. } = &mut self {
*slot = Some(badge.into());
}
self
}
#[must_use]
pub fn icon(mut self, icon: Icon) -> Self {
if let Self::Full { icon: slot, .. } = &mut self {
*slot = Some(icon);
}
self
}
#[must_use]
pub fn disabled(mut self, disabled: bool) -> Self {
if let Self::Full { disabled: slot, .. } = &mut self {
*slot = Some(disabled);
}
self
}
#[must_use]
pub fn style(mut self, style: Style) -> Self {
if let Self::Full { style: slot, .. } = &mut self {
*slot = Some(style);
}
self
}
#[must_use]
pub fn content(&self) -> &Text {
match self {
Self::Text(text) => text,
Self::Full { content, .. } => content,
}
}
}
impl From<Text> for ListItem {
fn from(text: Text) -> Self {
Self::Text(text)
}
}
impl From<&str> for ListItem {
fn from(s: &str) -> Self {
Self::Text(Text::from(s))
}
}
impl From<String> for ListItem {
fn from(s: String) -> Self {
Self::Text(Text::from(s))
}
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct List {
#[serde(flatten)]
pub common: Common,
pub items: Vec<ListItem>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<ListState>,
#[serde(skip_serializing_if = "Option::is_none")]
pub highlight_style: Option<Style>,
#[serde(skip_serializing_if = "Option::is_none")]
pub highlight_symbol: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub repeat_highlight_symbol: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub highlight_spacing: Option<HighlightSpacing>,
#[serde(skip_serializing_if = "Option::is_none")]
pub direction: Option<ListDirection>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scroll_padding: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub multi: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scrollbar: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub on_select: Option<ActionId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub on_activate: Option<ActionId>,
}
impl List {
#[must_use]
pub fn new(items: impl IntoIterator<Item = impl Into<ListItem>>) -> Self {
Self {
items: items.into_iter().map(Into::into).collect(),
..Self::default()
}
}
#[must_use]
pub fn items(mut self, items: impl IntoIterator<Item = impl Into<ListItem>>) -> Self {
self.items = items.into_iter().map(Into::into).collect();
self
}
#[must_use]
pub fn state(mut self, state: ListState) -> Self {
self.state = Some(state);
self
}
#[must_use]
pub fn highlight_style(mut self, style: Style) -> Self {
self.highlight_style = Some(style);
self
}
#[must_use]
pub fn highlight_symbol(mut self, symbol: impl Into<String>) -> Self {
self.highlight_symbol = Some(symbol.into());
self
}
#[must_use]
pub fn repeat_highlight_symbol(mut self, repeat: bool) -> Self {
self.repeat_highlight_symbol = Some(repeat);
self
}
#[must_use]
pub fn highlight_spacing(mut self, spacing: HighlightSpacing) -> Self {
self.highlight_spacing = Some(spacing);
self
}
#[must_use]
pub fn direction(mut self, direction: ListDirection) -> Self {
self.direction = Some(direction);
self
}
#[must_use]
pub fn scroll_padding(mut self, padding: u16) -> Self {
self.scroll_padding = Some(padding);
self
}
#[must_use]
pub fn multi(mut self, multi: bool) -> Self {
self.multi = Some(multi);
self
}
#[must_use]
pub fn scrollbar(mut self, scrollbar: bool) -> Self {
self.scrollbar = Some(scrollbar);
self
}
#[must_use]
pub fn on_select(mut self, action: impl Into<ActionId>) -> Self {
self.on_select = Some(action.into());
self
}
#[must_use]
pub fn on_activate(mut self, action: impl Into<ActionId>) -> Self {
self.on_activate = Some(action.into());
self
}
}
impl IntoNode for List {}
impl From<List> for Node {
fn from(l: List) -> Self {
Node::List(l)
}
}
pub type TableState = ListState;