use std::collections::HashMap;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(
feature = "serialization",
derive(serde::Serialize, serde::Deserialize)
)]
#[cfg_attr(feature = "serialization", serde(rename_all = "snake_case"))]
pub enum WidgetType {
Container,
Dialog,
Button,
Input,
TextArea,
Checkbox,
Radio,
Select,
List,
Table,
TabBar,
Tab,
Menu,
MenuItem,
Label,
Header,
Footer,
Sidebar,
Toolbar,
StatusBar,
Progress,
Scroll,
Tree,
Spinner,
Toast,
Tooltip,
Accordion,
Breadcrumb,
LoadingList,
KeyHints,
MultiProgress,
StatusLog,
TitleCard,
LineInput,
Dropdown,
ScrollableText,
Form,
SplitPanel,
SearchableList,
RadioGroup,
FileBrowser,
ConfirmDialog,
StepIndicator,
StyledText,
PaneLayout,
Sparkline,
Divider,
Canvas,
Paginator,
HelpPanel,
Switch,
CommandPalette,
BigText,
SpanTree,
DiffViewer,
TerminalOutput,
Custom(String),
}
impl WidgetType {
pub fn is_interactive(&self) -> bool {
matches!(
self,
WidgetType::Button
| WidgetType::Input
| WidgetType::TextArea
| WidgetType::Checkbox
| WidgetType::Radio
| WidgetType::Select
| WidgetType::List
| WidgetType::Table
| WidgetType::Tab
| WidgetType::MenuItem
| WidgetType::Tree
| WidgetType::LineInput
| WidgetType::Dropdown
| WidgetType::LoadingList
| WidgetType::Accordion
| WidgetType::RadioGroup
| WidgetType::SearchableList
| WidgetType::FileBrowser
| WidgetType::StepIndicator
| WidgetType::Paginator
| WidgetType::Switch
| WidgetType::CommandPalette
| WidgetType::SpanTree
)
}
pub fn is_container(&self) -> bool {
matches!(
self,
WidgetType::Container
| WidgetType::Dialog
| WidgetType::ConfirmDialog
| WidgetType::Scroll
| WidgetType::Sidebar
| WidgetType::Form
| WidgetType::SplitPanel
)
}
}
impl std::fmt::Display for WidgetType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WidgetType::Custom(name) => write!(f, "{}", name),
other => write!(f, "{:?}", other),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "serialization",
derive(serde::Serialize, serde::Deserialize)
)]
pub struct Annotation {
pub widget_type: WidgetType,
pub label: Option<String>,
pub id: Option<String>,
pub focused: bool,
pub disabled: bool,
pub selected: bool,
pub expanded: Option<bool>,
pub value: Option<String>,
#[cfg_attr(
feature = "serialization",
serde(default, skip_serializing_if = "HashMap::is_empty")
)]
pub metadata: HashMap<String, String>,
}
impl Annotation {
pub fn new(widget_type: WidgetType) -> Self {
Self {
widget_type,
label: None,
id: None,
focused: false,
disabled: false,
selected: false,
expanded: None,
value: None,
metadata: HashMap::new(),
}
}
pub fn container(id: impl Into<String>) -> Self {
Self::new(WidgetType::Container).with_id(id)
}
pub fn dialog(title: impl Into<String>) -> Self {
Self::new(WidgetType::Dialog).with_label(title)
}
pub fn button(id: impl Into<String>) -> Self {
Self::new(WidgetType::Button).with_id(id)
}
pub fn input(id: impl Into<String>) -> Self {
Self::new(WidgetType::Input).with_id(id)
}
pub fn text_area(id: impl Into<String>) -> Self {
Self::new(WidgetType::TextArea).with_id(id)
}
pub fn checkbox(id: impl Into<String>) -> Self {
Self::new(WidgetType::Checkbox).with_id(id)
}
pub fn list(id: impl Into<String>) -> Self {
Self::new(WidgetType::List).with_id(id)
}
pub fn table(id: impl Into<String>) -> Self {
Self::new(WidgetType::Table).with_id(id)
}
pub fn tab(id: impl Into<String>) -> Self {
Self::new(WidgetType::Tab).with_id(id)
}
pub fn menu_item(id: impl Into<String>) -> Self {
Self::new(WidgetType::MenuItem).with_id(id)
}
pub fn label(text: impl Into<String>) -> Self {
Self::new(WidgetType::Label).with_label(text)
}
pub fn header(text: impl Into<String>) -> Self {
Self::new(WidgetType::Header).with_label(text)
}
pub fn spinner(id: impl Into<String>) -> Self {
Self::new(WidgetType::Spinner).with_id(id)
}
pub fn toast(id: impl Into<String>) -> Self {
Self::new(WidgetType::Toast).with_id(id)
}
pub fn tooltip(id: impl Into<String>) -> Self {
Self::new(WidgetType::Tooltip).with_id(id)
}
pub fn accordion(id: impl Into<String>) -> Self {
Self::new(WidgetType::Accordion).with_id(id)
}
pub fn breadcrumb(id: impl Into<String>) -> Self {
Self::new(WidgetType::Breadcrumb).with_id(id)
}
pub fn loading_list(id: impl Into<String>) -> Self {
Self::new(WidgetType::LoadingList).with_id(id)
}
pub fn key_hints(id: impl Into<String>) -> Self {
Self::new(WidgetType::KeyHints).with_id(id)
}
pub fn multi_progress(id: impl Into<String>) -> Self {
Self::new(WidgetType::MultiProgress).with_id(id)
}
pub fn status_log(id: impl Into<String>) -> Self {
Self::new(WidgetType::StatusLog).with_id(id)
}
pub fn title_card(id: impl Into<String>) -> Self {
Self::new(WidgetType::TitleCard).with_id(id)
}
pub fn line_input(id: impl Into<String>) -> Self {
Self::new(WidgetType::LineInput).with_id(id)
}
pub fn dropdown(id: impl Into<String>) -> Self {
Self::new(WidgetType::Dropdown).with_id(id)
}
pub fn scrollable_text(id: impl Into<String>) -> Self {
Self::new(WidgetType::ScrollableText).with_id(id)
}
pub fn form(id: impl Into<String>) -> Self {
Self::new(WidgetType::Form).with_id(id)
}
pub fn split_panel(id: impl Into<String>) -> Self {
Self::new(WidgetType::SplitPanel).with_id(id)
}
pub fn searchable_list(id: impl Into<String>) -> Self {
Self::new(WidgetType::SearchableList).with_id(id)
}
pub fn radio_group(id: impl Into<String>) -> Self {
Self::new(WidgetType::RadioGroup).with_id(id)
}
pub fn file_browser(id: impl Into<String>) -> Self {
Self::new(WidgetType::FileBrowser).with_id(id)
}
pub fn confirm_dialog(title: impl Into<String>) -> Self {
Self::new(WidgetType::ConfirmDialog).with_label(title)
}
pub fn step_indicator(id: impl Into<String>) -> Self {
Self::new(WidgetType::StepIndicator).with_id(id)
}
pub fn styled_text(id: impl Into<String>) -> Self {
Self::new(WidgetType::StyledText).with_id(id)
}
pub fn pane_layout(id: impl Into<String>) -> Self {
Self::new(WidgetType::PaneLayout).with_id(id)
}
pub fn sparkline(id: impl Into<String>) -> Self {
Self::new(WidgetType::Sparkline).with_id(id)
}
pub fn divider(id: impl Into<String>) -> Self {
Self::new(WidgetType::Divider).with_id(id)
}
pub fn canvas(id: impl Into<String>) -> Self {
Self::new(WidgetType::Canvas).with_id(id)
}
pub fn paginator(id: impl Into<String>) -> Self {
Self::new(WidgetType::Paginator).with_id(id)
}
pub fn help_panel(id: impl Into<String>) -> Self {
Self::new(WidgetType::HelpPanel).with_id(id)
}
pub fn switch(id: impl Into<String>) -> Self {
Self::new(WidgetType::Switch).with_id(id)
}
pub fn command_palette(id: impl Into<String>) -> Self {
Self::new(WidgetType::CommandPalette).with_id(id)
}
pub fn big_text(id: impl Into<String>) -> Self {
Self::new(WidgetType::BigText).with_id(id)
}
pub fn span_tree(id: impl Into<String>) -> Self {
Self::new(WidgetType::SpanTree).with_id(id)
}
pub fn diff_viewer(id: impl Into<String>) -> Self {
Self::new(WidgetType::DiffViewer).with_id(id)
}
pub fn terminal_output(id: impl Into<String>) -> Self {
Self::new(WidgetType::TerminalOutput).with_id(id)
}
pub fn custom(type_name: impl Into<String>, id: impl Into<String>) -> Self {
Self::new(WidgetType::Custom(type_name.into())).with_id(id)
}
pub fn with_label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub fn with_id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
}
pub fn with_focus(mut self, focused: bool) -> Self {
self.focused = focused;
self
}
pub fn with_disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
pub fn with_selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
pub fn with_expanded(mut self, expanded: bool) -> Self {
self.expanded = Some(expanded);
self
}
pub fn with_value(mut self, value: impl Into<String>) -> Self {
self.value = Some(value.into());
self
}
pub fn with_meta(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
pub fn has_id(&self, id: &str) -> bool {
self.id.as_deref() == Some(id)
}
pub fn is_type(&self, widget_type: &WidgetType) -> bool {
&self.widget_type == widget_type
}
pub fn is_interactive(&self) -> bool {
self.widget_type.is_interactive() && !self.disabled
}
pub fn description(&self) -> String {
let mut parts = Vec::new();
parts.push(format!("{:?}", self.widget_type));
if let Some(label) = &self.label {
parts.push(format!("\"{}\"", label));
}
if let Some(id) = &self.id {
parts.push(format!("#{}", id));
}
if self.focused {
parts.push("(focused)".to_string());
}
if self.disabled {
parts.push("(disabled)".to_string());
}
if self.selected {
parts.push("(selected)".to_string());
}
parts.join(" ")
}
}
impl Default for Annotation {
fn default() -> Self {
Self::new(WidgetType::Container)
}
}
#[cfg(test)]
mod tests;