1use minui::MouseButton;
7use minui::input::ClickTracker;
8use minui::prelude::*;
9use std::collections::VecDeque;
10use std::time::Duration;
11
12const MAX_EVENTS: usize = 12;
16
17struct InputDemoState {
18 event_log: VecDeque<String>,
19 mouse_pos: (u16, u16),
20 click_tracker: ClickTracker,
21}
22
23fn main() -> minui::Result<()> {
24 let initial_state = InputDemoState {
25 event_log: {
26 let mut log = VecDeque::new();
27 log.push_back("Welcome to MinUI Input Demo!".to_string());
28 log.push_back("Try typing, moving mouse, clicking...".to_string());
29 log.push_back("Double-click quickly to see double-click detection!".to_string());
30 log.push_back("Press 'q' to quit".to_string());
31 log
32 },
33 mouse_pos: (0, 0),
34 click_tracker: ClickTracker::new(),
35 };
36
37 let mut app = App::new(initial_state)?.with_frame_rate(Duration::from_millis(16));
38
39 app.run(
40 |state, event| {
41 if let Event::KeyWithModifiers(k) = event {
43 if matches!(k.key, KeyKind::Char('q')) {
44 return false;
45 }
46 }
47 if matches!(event, Event::Character('q')) {
48 return false;
49 }
50
51 match event {
53 Event::KeyWithModifiers(k) => match k.key {
55 KeyKind::Char(c) => {
56 state.event_log.push_back(format!(
57 "Key: '{}' (mods: shift={}, ctrl={}, alt={}, super={})",
58 c, k.mods.shift, k.mods.ctrl, k.mods.alt, k.mods.super_key
59 ));
60 }
61 KeyKind::Up => {
62 state.event_log.push_back(format!(
63 "Key: ↑ Up (mods: shift={}, ctrl={}, alt={}, super={})",
64 k.mods.shift, k.mods.ctrl, k.mods.alt, k.mods.super_key
65 ));
66 }
67 KeyKind::Down => {
68 state.event_log.push_back(format!(
69 "Key: ↓ Down (mods: shift={}, ctrl={}, alt={}, super={})",
70 k.mods.shift, k.mods.ctrl, k.mods.alt, k.mods.super_key
71 ));
72 }
73 KeyKind::Left => {
74 state.event_log.push_back(format!(
75 "Key: ← Left (mods: shift={}, ctrl={}, alt={}, super={})",
76 k.mods.shift, k.mods.ctrl, k.mods.alt, k.mods.super_key
77 ));
78 }
79 KeyKind::Right => {
80 state.event_log.push_back(format!(
81 "Key: → Right (mods: shift={}, ctrl={}, alt={}, super={})",
82 k.mods.shift, k.mods.ctrl, k.mods.alt, k.mods.super_key
83 ));
84 }
85 KeyKind::Enter => {
86 state.event_log.push_back(format!(
87 "Key: ⏎ Enter (mods: shift={}, ctrl={}, alt={}, super={})",
88 k.mods.shift, k.mods.ctrl, k.mods.alt, k.mods.super_key
89 ));
90 }
91 KeyKind::Escape => {
92 state.event_log.push_back(format!(
93 "Key: Escape (mods: shift={}, ctrl={}, alt={}, super={})",
94 k.mods.shift, k.mods.ctrl, k.mods.alt, k.mods.super_key
95 ));
96 }
97 KeyKind::Backspace => {
98 state.event_log.push_back(format!(
99 "Key: ⌫ Backspace (mods: shift={}, ctrl={}, alt={}, super={})",
100 k.mods.shift, k.mods.ctrl, k.mods.alt, k.mods.super_key
101 ));
102 }
103 KeyKind::Delete => {
104 state.event_log.push_back(format!(
105 "Key: ⌦ Delete (mods: shift={}, ctrl={}, alt={}, super={})",
106 k.mods.shift, k.mods.ctrl, k.mods.alt, k.mods.super_key
107 ));
108 }
109 KeyKind::Tab => {
110 state.event_log.push_back(format!(
111 "Key: Tab (mods: shift={}, ctrl={}, alt={}, super={})",
112 k.mods.shift, k.mods.ctrl, k.mods.alt, k.mods.super_key
113 ));
114 }
115 KeyKind::Function(n) => {
116 state.event_log.push_back(format!(
117 "Key: F{} (mods: shift={}, ctrl={}, alt={}, super={})",
118 n, k.mods.shift, k.mods.ctrl, k.mods.alt, k.mods.super_key
119 ));
120 }
121 KeyKind::CapsLock => {
122 state.event_log.push_back("Key: Caps Lock".to_string());
123 }
124 },
125
126 Event::Character(c) => {
128 state.event_log.push_back(format!("Key: '{}'", c));
129 }
130 Event::Paste(text) => {
131 let preview: String = text.chars().take(60).collect();
133 if text.chars().count() > 60 {
134 state.event_log.push_back(format!(
135 "Paste: \"{}…\" ({} chars)",
136 preview,
137 text.chars().count()
138 ));
139 } else {
140 state.event_log.push_back(format!("Paste: \"{}\"", preview));
141 }
142 }
143 Event::KeyUp => {
144 state.event_log.push_back("Key: ↑ Up".to_string());
145 }
146 Event::KeyDown => {
147 state.event_log.push_back("Key: ↓ Down".to_string());
148 }
149 Event::KeyLeft => {
150 state.event_log.push_back("Key: ← Left".to_string());
151 }
152 Event::KeyRight => {
153 state.event_log.push_back("Key: → Right".to_string());
154 }
155 Event::Enter => {
156 state.event_log.push_back("Key: ⏎ Enter".to_string());
157 }
158 Event::Escape => {
159 state.event_log.push_back("Key: Escape".to_string());
160 }
161 Event::Backspace => {
162 state.event_log.push_back("Key: ⌫ Backspace".to_string());
163 }
164 Event::Delete => {
165 state.event_log.push_back("Key: ⌦ Delete".to_string());
166 }
167 Event::FunctionKey(n) => {
168 state.event_log.push_back(format!("Key: F{}", n));
169 }
170
171 Event::MouseMove { x, y } => {
173 state.mouse_pos = (x, y);
174 if x % 3 == 0 && y % 3 == 0 {
176 state
177 .event_log
178 .push_back(format!("Mouse: Moved to ({}, {})", x, y));
179 }
180 }
181 Event::MouseClick { x, y, button } => {
182 state.mouse_pos = (x, y);
183 let button_name = match button {
184 MouseButton::Left => "Left",
185 MouseButton::Right => "Right",
186 MouseButton::Middle => "Middle",
187 MouseButton::Other(_) => "Other",
188 };
189
190 if state.click_tracker.is_double_click(x, y) {
192 state.event_log.push_back(format!(
193 "Mouse: DOUBLE-CLICK! {} button at ({}, {})",
194 button_name, x, y
195 ));
196 } else {
197 state
198 .event_log
199 .push_back(format!("Mouse: {} click at ({}, {})", button_name, x, y));
200 }
201 }
202 Event::MouseDrag { x, y, button } => {
203 state.mouse_pos = (x, y);
204 let button_name = match button {
205 MouseButton::Left => "Left",
206 MouseButton::Right => "Right",
207 MouseButton::Middle => "Middle",
208 MouseButton::Other(_) => "Other",
209 };
210 state
211 .event_log
212 .push_back(format!("Mouse: {} drag to ({}, {})", button_name, x, y));
213 }
214 Event::MouseScroll { delta } => {
215 let direction = if delta > 0 { "up" } else { "down" };
216 state
217 .event_log
218 .push_back(format!("Mouse: Scroll {} ({})", direction, delta));
219 }
220 Event::MouseScrollHorizontal { delta } => {
221 let direction = if delta > 0 { "right" } else { "left" };
222 state
223 .event_log
224 .push_back(format!("Mouse: Scroll {} ({})", direction, delta));
225 }
226 Event::MouseRelease { x, y, button } => {
227 state.mouse_pos = (x, y);
228 let button_name = match button {
229 MouseButton::Left => "Left",
230 MouseButton::Right => "Right",
231 MouseButton::Middle => "Middle",
232 MouseButton::Other(_) => "Other",
233 };
234 state
235 .event_log
236 .push_back(format!("Mouse: {} release at ({}, {})", button_name, x, y));
237 }
238
239 Event::Resize { width, height } => {
240 state
241 .event_log
242 .push_back(format!("Terminal: Resized to {}x{}", width, height));
243 }
244 _ => {}
245 }
246
247 if state.event_log.len() > MAX_EVENTS {
249 state.event_log.pop_front();
250 }
251
252 true
253 },
254 |state, window| {
255 let (term_width, term_height) = window.get_size();
256
257 use minui::widgets::ContainerPadding;
264
265 let panel_x: u16 = 2u16;
266 let panel_y: u16 = 1u16;
267 let panel_w: u16 = term_width.saturating_sub(4u16);
268 let panel_h: u16 = term_height.saturating_sub(4u16);
269
270 let mut log_container = Container::vertical().with_row_gap(Gap::Pixels(0u16));
273 if state.event_log.is_empty() {
274 log_container = log_container.add_child(Label::new("No events yet..."));
275 } else {
276 for line in state.event_log.iter().rev().take(MAX_EVENTS) {
277 log_container = log_container.add_child(Label::new(line.clone()));
278 }
279 }
280
281 let panel = Container::new()
282 .with_position_and_size(panel_x, panel_y, panel_w, panel_h)
283 .with_border()
284 .with_border_chars(BorderChars::double_line())
285 .with_border_color(ColorPair::new(Color::Cyan, Color::Black))
286 .with_title("MinUI Input Demo")
287 .with_title_alignment(TitleAlignment::Center)
288 .with_padding(ContainerPadding::uniform(1u16))
289 .add_child(log_container);
290
291 panel.draw(window)?;
292
293 let mouse_info = format!("Mouse: ({}, {})", state.mouse_pos.0, state.mouse_pos.1);
295 let info_y = term_height.saturating_sub(2);
296 window.write_str_colored(
297 info_y,
298 2,
299 &mouse_info,
300 ColorPair::new(Color::Cyan, Color::Transparent),
301 )?;
302
303 let help_text = "Press 'q' to quit | Try typing, clicking, scrolling!";
305 let help_x = (term_width.saturating_sub(help_text.len() as u16)) / 2;
306 let help_y = term_height.saturating_sub(1);
307 window.write_str_colored(
308 help_y,
309 help_x,
310 help_text,
311 ColorPair::new(Color::DarkGray, Color::Transparent),
312 )?;
313
314 window.flush()?;
315 Ok(())
316 },
317 )?;
318
319 Ok(())
320}