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 => todo!(),
122 },
123
124 Event::Character(c) => {
126 state.event_log.push_back(format!("Key: '{}'", c));
127 }
128 Event::Paste(text) => {
129 let preview: String = text.chars().take(60).collect();
131 if text.chars().count() > 60 {
132 state.event_log.push_back(format!(
133 "Paste: \"{}…\" ({} chars)",
134 preview,
135 text.chars().count()
136 ));
137 } else {
138 state.event_log.push_back(format!("Paste: \"{}\"", preview));
139 }
140 }
141 Event::KeyUp => {
142 state.event_log.push_back("Key: ↑ Up".to_string());
143 }
144 Event::KeyDown => {
145 state.event_log.push_back("Key: ↓ Down".to_string());
146 }
147 Event::KeyLeft => {
148 state.event_log.push_back("Key: ← Left".to_string());
149 }
150 Event::KeyRight => {
151 state.event_log.push_back("Key: → Right".to_string());
152 }
153 Event::Enter => {
154 state.event_log.push_back("Key: ⏎ Enter".to_string());
155 }
156 Event::Escape => {
157 state.event_log.push_back("Key: Escape".to_string());
158 }
159 Event::Backspace => {
160 state.event_log.push_back("Key: ⌫ Backspace".to_string());
161 }
162 Event::Delete => {
163 state.event_log.push_back("Key: ⌦ Delete".to_string());
164 }
165 Event::FunctionKey(n) => {
166 state.event_log.push_back(format!("Key: F{}", n));
167 }
168
169 Event::MouseMove { x, y } => {
171 state.mouse_pos = (x, y);
172 if x % 3 == 0 && y % 3 == 0 {
174 state
175 .event_log
176 .push_back(format!("Mouse: Moved to ({}, {})", x, y));
177 }
178 }
179 Event::MouseClick { x, y, button } => {
180 state.mouse_pos = (x, y);
181 let button_name = match button {
182 MouseButton::Left => "Left",
183 MouseButton::Right => "Right",
184 MouseButton::Middle => "Middle",
185 MouseButton::Other(_) => "Other",
186 };
187
188 if state.click_tracker.is_double_click(x, y) {
190 state.event_log.push_back(format!(
191 "Mouse: DOUBLE-CLICK! {} button at ({}, {})",
192 button_name, x, y
193 ));
194 } else {
195 state
196 .event_log
197 .push_back(format!("Mouse: {} click at ({}, {})", button_name, x, y));
198 }
199 }
200 Event::MouseDrag { x, y, button } => {
201 state.mouse_pos = (x, y);
202 let button_name = match button {
203 MouseButton::Left => "Left",
204 MouseButton::Right => "Right",
205 MouseButton::Middle => "Middle",
206 MouseButton::Other(_) => "Other",
207 };
208 state
209 .event_log
210 .push_back(format!("Mouse: {} drag to ({}, {})", button_name, x, y));
211 }
212 Event::MouseScroll { delta } => {
213 let direction = if delta > 0 { "up" } else { "down" };
214 state
215 .event_log
216 .push_back(format!("Mouse: Scroll {} ({})", direction, delta));
217 }
218 Event::MouseScrollHorizontal { delta } => {
219 let direction = if delta > 0 { "right" } else { "left" };
220 state
221 .event_log
222 .push_back(format!("Mouse: Scroll {} ({})", direction, delta));
223 }
224 Event::MouseRelease { x, y, button } => {
225 state.mouse_pos = (x, y);
226 let button_name = match button {
227 MouseButton::Left => "Left",
228 MouseButton::Right => "Right",
229 MouseButton::Middle => "Middle",
230 MouseButton::Other(_) => "Other",
231 };
232 state
233 .event_log
234 .push_back(format!("Mouse: {} release at ({}, {})", button_name, x, y));
235 }
236
237 Event::Resize { width, height } => {
238 state
239 .event_log
240 .push_back(format!("Terminal: Resized to {}x{}", width, height));
241 }
242 _ => {}
243 }
244
245 if state.event_log.len() > MAX_EVENTS {
247 state.event_log.pop_front();
248 }
249
250 true
251 },
252 |state, window| {
253 let (term_width, term_height) = window.get_size();
254
255 use minui::widgets::ContainerPadding;
262
263 let panel_x: u16 = 2u16;
264 let panel_y: u16 = 1u16;
265 let panel_w: u16 = term_width.saturating_sub(4u16);
266 let panel_h: u16 = term_height.saturating_sub(4u16);
267
268 let mut log_container = Container::vertical().with_row_gap(Gap::Pixels(0u16));
271 if state.event_log.is_empty() {
272 log_container = log_container.add_child(Label::new("No events yet..."));
273 } else {
274 for line in state.event_log.iter().rev().take(MAX_EVENTS) {
275 log_container = log_container.add_child(Label::new(line.clone()));
276 }
277 }
278
279 let panel = Container::new()
280 .with_position_and_size(panel_x, panel_y, panel_w, panel_h)
281 .with_border()
282 .with_border_chars(BorderChars::double_line())
283 .with_border_color(ColorPair::new(Color::Cyan, Color::Black))
284 .with_title("MinUI Input Demo")
285 .with_title_alignment(TitleAlignment::Center)
286 .with_padding(ContainerPadding::uniform(1u16))
287 .add_child(log_container);
288
289 panel.draw(window)?;
290
291 let mouse_info = format!("Mouse: ({}, {})", state.mouse_pos.0, state.mouse_pos.1);
293 let info_y = term_height.saturating_sub(2);
294 window.write_str_colored(
295 info_y,
296 2,
297 &mouse_info,
298 ColorPair::new(Color::Cyan, Color::Transparent),
299 )?;
300
301 let help_text = "Press 'q' to quit | Try typing, clicking, scrolling!";
303 let help_x = (term_width.saturating_sub(help_text.len() as u16)) / 2;
304 let help_y = term_height.saturating_sub(1);
305 window.write_str_colored(
306 help_y,
307 help_x,
308 help_text,
309 ColorPair::new(Color::DarkGray, Color::Transparent),
310 )?;
311
312 window.flush()?;
313 Ok(())
314 },
315 )?;
316
317 Ok(())
318}