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}