Skip to main content

tui_kit/wizard/
types.rs

1//! All data types for the wizard widget.
2
3use std::collections::HashMap;
4
5// ── Step definitions ──────────────────────────────────────────────────────────
6
7/// The kind of a wizard step, controlling how it is rendered and how keys are handled.
8pub enum WizardStepKind {
9    /// Free-text input.  Tab/Enter advances to the next step.
10    Leaf,
11    /// Free-text input that can be skipped by pressing Enter on an empty buffer.
12    Optional,
13    /// Cycle through a fixed list of options with Left/Right.
14    Select(&'static [&'static str]),
15    /// Visual grouping that expands its children in place.  Sections are not
16    /// counted as leaves themselves — their children are counted instead.
17    Section(&'static [WizardStep]),
18    /// Variable-length list of items, each with the given sub-steps.
19    /// Counts as **one leaf** in the DFS order (the array header).
20    Array(&'static [WizardStep]),
21    /// One or more action buttons rendered inline.
22    ///
23    /// Left/Right cycles the highlighted button.
24    /// Enter on the **first** button (index 0) fires [`WizardEvent::Done`].
25    /// Enter on any other button fires [`WizardEvent::Cancelled`].
26    /// BackTab retreats to the previous step.  Esc fires `Cancelled`.
27    Buttons(&'static [&'static str]),
28}
29
30/// One node in the wizard step tree.
31pub struct WizardStep {
32    pub label: &'static str,
33    pub kind: WizardStepKind,
34}
35
36impl WizardStep {
37    pub const fn leaf(label: &'static str) -> Self {
38        Self { label, kind: WizardStepKind::Leaf }
39    }
40    pub const fn optional(label: &'static str) -> Self {
41        Self { label, kind: WizardStepKind::Optional }
42    }
43    pub const fn select(label: &'static str, opts: &'static [&'static str]) -> Self {
44        Self { label, kind: WizardStepKind::Select(opts) }
45    }
46    pub const fn section(label: &'static str, children: &'static [WizardStep]) -> Self {
47        Self { label, kind: WizardStepKind::Section(children) }
48    }
49    pub const fn array(label: &'static str, sub_steps: &'static [WizardStep]) -> Self {
50        Self { label, kind: WizardStepKind::Array(sub_steps) }
51    }
52    pub const fn buttons(labels: &'static [&'static str]) -> Self {
53        Self { label: "", kind: WizardStepKind::Buttons(labels) }
54    }
55    pub fn is_section(&self) -> bool { matches!(self.kind, WizardStepKind::Section(_)) }
56    pub fn is_array(&self)   -> bool { matches!(self.kind, WizardStepKind::Array(_)) }
57    /// Children for Section or Array steps; empty for all others.
58    pub fn children(&self) -> &'static [WizardStep] {
59        match &self.kind {
60            WizardStepKind::Section(c) | WizardStepKind::Array(c) => c,
61            _ => &[],
62        }
63    }
64}
65
66// ── Array state ───────────────────────────────────────────────────────────────
67
68/// State for an in-progress edit of one array item.
69pub struct ArrayEditSession {
70    /// Which item (0-based index into `ArrayState::items`).
71    pub item_idx: usize,
72    /// Which sub-step is currently active.
73    pub sub_step: usize,
74    /// `true` if this item was just created — Esc will delete it.
75    pub is_new: bool,
76    /// Original sub-step values for Esc-restore on existing items.
77    pub original_values: Vec<String>,
78    /// Text buffer for Leaf sub-steps.
79    pub buffer: String,
80    /// Byte-cursor within `buffer`.
81    pub buf_cursor: usize,
82    /// Selected index for Select sub-steps.
83    pub select_idx: usize,
84}
85
86/// Mutable state for one Array step.
87pub struct ArrayState {
88    /// Completed items; `items[i][j]` is the value for sub-step `j` of item `i`.
89    pub items: Vec<Vec<String>>,
90    /// Browse cursor when expanded — direct item index (0-based).
91    pub selected: usize,
92    /// Active edit session, if any.
93    pub editing: Option<ArrayEditSession>,
94    /// Whether the array is expanded (showing items) or collapsed (showing badge only).
95    pub expanded: bool,
96    /// Which header button is focused when collapsed: 0 = [+ add], 1 = [n] badge.
97    pub header_sel: usize,
98    /// Which inline button is focused on the selected item: 0 = item itself, 1 = [remove].
99    pub item_btn_sel: usize,
100}
101
102impl ArrayState {
103    pub fn new() -> Self {
104        Self {
105            items: vec![],
106            selected: 0,
107            editing: None,
108            expanded: false,
109            header_sel: 0,
110            item_btn_sel: 0,
111        }
112    }
113}
114
115// ── Wizard state ──────────────────────────────────────────────────────────────
116
117/// All mutable wizard state.
118pub struct WizardState {
119    /// The full step tree (static, defined by the caller).
120    pub steps: &'static [WizardStep],
121    /// How many leading DFS-leaf steps are wizard-managed.
122    /// When `current >= input_count`, the wizard is complete.
123    pub input_count: usize,
124    /// DFS-leaf index of the currently active step.
125    pub current: usize,
126    /// Confirmed string values for non-array leaf steps.
127    /// Indexed by step leaf index; may be sparse if the user cycled backward.
128    pub values: Vec<String>,
129    /// Text buffer for the currently active Leaf/Optional input step.
130    pub buffer: String,
131    /// Byte-cursor within `buffer`.
132    pub cursor: usize,
133    /// State for each Array step, keyed by its DFS-leaf index.
134    pub array_states: HashMap<usize, ArrayState>,
135    /// Which button is highlighted when the current step is a Buttons step.
136    pub button_selected: usize,
137}
138
139// ── Events ────────────────────────────────────────────────────────────────────
140
141/// Event returned by [`WizardState::handle_key`].
142///
143/// `StepCompleted` doubles as the "field blurred / onChange" hook: validate
144/// the value here and show an error toast if needed.
145#[derive(Debug)]
146pub enum WizardEvent {
147    None,
148    /// A non-array leaf step was confirmed (also the validate / onChange hook).
149    StepCompleted { index: usize, value: String },
150    /// All `input_count` steps have been confirmed.
151    Done(Vec<String>),
152    /// The user cancelled (Esc at any top-level position).
153    Cancelled,
154    /// An item was added to an array step (editing session started).
155    ArrayItemAdded { array_step_idx: usize, item_idx: usize },
156    /// An item was deleted from an array step.
157    ArrayItemDeleted { array_step_idx: usize, item_idx: usize },
158    /// An item's edit session was confirmed with all sub-step values.
159    ArrayItemCompleted { array_step_idx: usize, item_idx: usize, values: Vec<String> },
160}
161
162// ── Internal copy-friendly kind reference ─────────────────────────────────────
163
164/// Lightweight `Copy` view of a step kind, carrying only `'static` refs.
165/// Used to avoid borrow conflicts when dispatching `handle_key`.
166#[derive(Clone, Copy)]
167pub(super) enum StepKindRef {
168    Leaf,
169    Optional,
170    Select(&'static [&'static str]),
171    Array(&'static [WizardStep]),
172    Buttons(&'static [&'static str]),
173}