返回 CodeWhale
input.rs
1 //! Gallery entries for package Input: the text input in each of its states,
2 //! and a form that has just refused a submit.
3 //!
4 //! Each entry builds its state the way a host would, from keys, so the
5 //! gallery doubles as a usage example.
6
7 use std::borrow::Cow;
8
9 use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
10 use ratatui::{buffer::Buffer, layout::Rect};
11
12 use super::Entry;
13 use crate::{Form, FormField, FormState, Paint, TextInput, TextInputState, Theme};
14
15 pub(crate) fn entries() -> Vec<Entry> {
16 vec![
17 Entry {
18 name: "text-input-empty",
19 width: 40,
20 height: 2,
21 draw: empty,
22 },
23 Entry {
24 name: "text-input-typed",
25 width: 40,
26 height: 2,
27 draw: typed,
28 },
29 Entry {
30 name: "text-input-unfocused",
31 width: 40,
32 height: 2,
33 draw: unfocused,
34 },
35 Entry {
36 name: "text-input-invalid",
37 width: 40,
38 height: 3,
39 draw: invalid,
40 },
41 Entry {
42 name: "text-input-disabled",
43 width: 40,
44 height: 3,
45 draw: disabled,
46 },
47 Entry {
48 name: "text-input-secret",
49 width: 40,
50 height: 2,
51 draw: secret,
52 },
53 Entry {
54 name: "text-input-long",
55 width: 40,
56 height: 2,
57 draw: long,
58 },
59 Entry {
60 name: "text-input-wide-text",
61 width: 40,
62 height: 2,
63 draw: wide_text,
64 },
65 Entry {
66 name: "form",
67 width: 60,
68 height: 14,
69 draw: form,
70 },
71 ]
72 }
73
74 fn key(code: KeyCode) -> KeyEvent {
75 KeyEvent::new(code, KeyModifiers::NONE)
76 }
77
78 fn type_text(state: &mut TextInputState, text: &str) {
79 for c in text.chars() {
80 state.handle_key(key(KeyCode::Char(c)));
81 }
82 }
83
84 fn empty(area: Rect, buf: &mut Buffer, theme: &Theme) {
85 let state = TextInputState::new();
86 TextInput::new(&state)
87 .label("Port")
88 .placeholder("e.g. 8080")
89 .focused(true)
90 .paint(area, buf, theme);
91 }
92
93 fn typed(area: Rect, buf: &mut Buffer, theme: &Theme) {
94 let mut state = TextInputState::new();
95 type_text(&mut state, "nightly-report");
96 TextInput::new(&state)
97 .label("Name")
98 .focused(true)
99 .paint(area, buf, theme);
100 }
101
102 fn unfocused(area: Rect, buf: &mut Buffer, theme: &Theme) {
103 let state = TextInputState::with_text("nightly-report");
104 TextInput::new(&state).label("Name").paint(area, buf, theme);
105 }
106
107 fn invalid(area: Rect, buf: &mut Buffer, theme: &Theme) {
108 let state = TextInputState::with_text("70000");
109 TextInput::new(&state)
110 .label("Port")
111 .focused(true)
112 .error("Enter a port from 1 to 65535.")
113 .paint(area, buf, theme);
114 }
115
116 fn disabled(area: Rect, buf: &mut Buffer, theme: &Theme) {
117 let state = TextInputState::with_text("shannon-labs");
118 TextInput::new(&state)
119 .label("Organization")
120 .disabled("Set by your administrator")
121 .paint(area, buf, theme);
122 }
123
124 fn secret(area: Rect, buf: &mut Buffer, theme: &Theme) {
125 let mut state = TextInputState::secret();
126 type_text(&mut state, "hunter2 hunter2");
127 TextInput::new(&state)
128 .label("API key")
129 .focused(true)
130 .paint(area, buf, theme);
131 }
132
133 /// Longer than the field: clipped on both sides, the cursor kept in view.
134 fn long(area: Rect, buf: &mut Buffer, theme: &Theme) {
135 let mut state = TextInputState::with_text(
136 "/Volumes/work/shannon-labs/codewhale/crates/tui/src/tui/views/automations/editor.rs",
137 );
138 for _ in 0.."editor.rs".len() + 22 {
139 state.handle_key(key(KeyCode::Left));
140 }
141 TextInput::new(&state)
142 .label("Path")
143 .focused(true)
144 .paint(area, buf, theme);
145 }
146
147 /// Double-width characters, a combining mark and an emoji ZWJ sequence: each
148 /// is one step for the cursor.
149 fn wide_text(area: Rect, buf: &mut Buffer, theme: &Theme) {
150 // ASCII-safe terminals get ASCII sample text: the frame rules want
151 // ASCII-only output there. The unit tests cover wide text in every mode.
152 let text = if theme.ascii() {
153 "wide, combining and emoji text goes here"
154 } else {
155 "鲸鱼日报 cafe\u{301} 👩\u{200d}💻 夜间"
156 };
157 let mut state = TextInputState::with_text(text);
158 state.handle_key(key(KeyCode::Left));
159 TextInput::new(&state)
160 .label("Title")
161 .focused(true)
162 .paint(area, buf, theme);
163 }
164
165 fn project(text: &str) -> Result<(), Cow<'static, str>> {
166 if text.trim().is_empty() {
167 Err("Choose a project.".into())
168 } else {
169 Ok(())
170 }
171 }
172
173 fn name(text: &str) -> Result<(), Cow<'static, str>> {
174 if text.trim().is_empty() {
175 Err("Give it a name.".into())
176 } else if text.contains(char::is_whitespace) {
177 Err("Use dashes instead of spaces.".into())
178 } else {
179 Ok(())
180 }
181 }
182
183 /// A form after Enter on a form with an empty project: the error shows under
184 /// its field, focus is there, and nothing typed was lost.
185 fn form(area: Rect, buf: &mut Buffer, theme: &Theme) {
186 let mut state = FormState::new(vec![
187 FormField::text("Name").validate(name),
188 FormField::text("Project")
189 .placeholder("Choose a project")
190 .validate(project),
191 FormField::read_only("Schedule", "every day at 7"),
192 FormField::check("Notify me", true).help("Sends one message when it finishes."),
193 ]);
194 type_text_into(&mut state, "nightly-report");
195 state.handle_key(key(KeyCode::Enter));
196 Form::new(&state).paint(area, buf, theme);
197 }
198
199 fn type_text_into(state: &mut FormState, text: &str) {
200 for c in text.chars() {
201 state.handle_key(key(KeyCode::Char(c)));
202 }
203 }
204
204 lines RUST