1use crate::Bounds;
2
3#[derive(Debug, Clone)]
6pub struct ContentEvent {
7 pub event_type: EventType,
9 pub position: Option<(usize, usize)>,
11 pub zone_id: Option<String>,
13 pub data: EventData,
15 pub timestamp: std::time::SystemTime,
17}
18
19#[derive(Debug, Clone, PartialEq)]
21pub enum EventType {
22 Click,
24 DoubleClick,
26 Hover,
28 MouseMove,
30 KeyPress,
32 Focus,
34 Blur,
36 Scroll,
38 Resize,
40 BoxResize,
42 TitleChange,
44 Custom(String),
46}
47
48#[derive(Debug, Clone, Default)]
50pub struct EventData {
51 pub mouse_button: Option<MouseButton>,
53 pub key: Option<KeyInfo>,
55 pub scroll: Option<ScrollInfo>,
57 pub size: Option<(usize, usize)>,
59 pub mouse_move: Option<MouseMoveInfo>,
61 pub hover: Option<HoverInfo>,
63 pub box_resize: Option<BoxResizeInfo>,
65 pub title_change: Option<TitleChangeInfo>,
67 pub custom_data: Option<String>,
69}
70
71#[derive(Debug, Clone, PartialEq)]
73pub enum MouseButton {
74 Left,
75 Right,
76 Middle,
77 WheelUp,
78 WheelDown,
79}
80
81#[derive(Debug, Clone)]
83pub struct KeyInfo {
84 pub key: String,
86 pub modifiers: Vec<KeyModifier>,
88}
89
90#[derive(Debug, Clone, PartialEq)]
92pub enum KeyModifier {
93 Ctrl,
94 Alt,
95 Shift,
96 Meta,
97}
98
99#[derive(Debug, Clone)]
101pub struct ScrollInfo {
102 pub direction: ScrollDirection,
104 pub amount: i32,
106}
107
108#[derive(Debug, Clone, PartialEq)]
110pub enum ScrollDirection {
111 Up,
112 Down,
113 Left,
114 Right,
115}
116
117#[derive(Debug, Clone)]
119pub struct MouseMoveInfo {
120 pub from_position: Option<(usize, usize)>,
122 pub to_position: (usize, usize),
124 pub delta: (i32, i32),
126 pub is_dragging: bool,
128 pub drag_button: Option<MouseButton>,
130}
131
132#[derive(Debug, Clone)]
134pub struct HoverInfo {
135 pub state: HoverState,
137 pub previous_zone: Option<String>,
139 pub current_zone: Option<String>,
141 pub hover_duration: Option<std::time::Duration>,
143}
144
145#[derive(Debug, Clone, PartialEq)]
147pub enum HoverState {
148 Enter,
150 Leave,
152 Move,
154}
155
156#[derive(Debug, Clone)]
158pub struct BoxResizeInfo {
159 pub resize_type: BoxResizeType,
161 pub original_bounds: (usize, usize, usize, usize), pub new_bounds: (usize, usize, usize, usize), pub anchor: ResizeAnchor,
167 pub state: ResizeState,
169}
170
171#[derive(Debug, Clone, PartialEq)]
173pub enum BoxResizeType {
174 Interactive,
176 Programmatic,
178 Terminal,
180}
181
182#[derive(Debug, Clone, PartialEq)]
184pub enum ResizeAnchor {
185 TopLeft,
187 TopRight,
189 BottomLeft,
191 BottomRight,
193 Top,
195 Bottom,
197 Left,
199 Right,
201}
202
203#[derive(Debug, Clone, PartialEq)]
205pub enum ResizeState {
206 Started,
208 InProgress,
210 Completed,
212 Cancelled,
214}
215
216#[derive(Debug, Clone)]
218pub struct TitleChangeInfo {
219 pub old_title: Option<String>,
221 pub new_title: String,
223 pub source: TitleChangeSource,
225 pub persist: bool,
227}
228
229#[derive(Debug, Clone, PartialEq)]
231pub enum TitleChangeSource {
232 User,
234 PTY,
236 Script,
238 API,
240 System,
242}
243
244#[derive(Debug, Clone, PartialEq)]
246pub enum EventResult {
247 Handled,
249 NotHandled,
251 HandledContinue,
253 StateChanged,
255}
256
257impl ContentEvent {
258 pub fn new(
260 event_type: EventType,
261 position: Option<(usize, usize)>,
262 zone_id: Option<String>,
263 ) -> Self {
264 Self {
265 event_type,
266 position,
267 zone_id,
268 data: EventData::default(),
269 timestamp: std::time::SystemTime::now(),
270 }
271 }
272
273 pub fn new_click(position: Option<(usize, usize)>, zone_id: Option<String>) -> Self {
275 let mut event = Self::new(EventType::Click, position, zone_id);
276 event.data.mouse_button = Some(MouseButton::Left);
277 event
278 }
279
280 pub fn new_click_with_button(
282 position: Option<(usize, usize)>,
283 zone_id: Option<String>,
284 button: MouseButton,
285 ) -> Self {
286 let mut event = Self::new(EventType::Click, position, zone_id);
287 event.data.mouse_button = Some(button);
288 event
289 }
290
291 pub fn new_hover(position: (usize, usize), zone_id: Option<String>) -> Self {
293 Self::new(EventType::Hover, Some(position), zone_id)
294 }
295
296 pub fn new_keypress(key: String, modifiers: Vec<KeyModifier>, zone_id: Option<String>) -> Self {
298 let mut event = Self::new(EventType::KeyPress, None, zone_id);
299 event.data.key = Some(KeyInfo { key, modifiers });
300 event
301 }
302
303 pub fn new_scroll(direction: ScrollDirection, amount: i32, zone_id: Option<String>) -> Self {
305 let mut event = Self::new(EventType::Scroll, None, zone_id);
306 event.data.scroll = Some(ScrollInfo { direction, amount });
307 event
308 }
309
310 pub fn new_focus(zone_id: Option<String>) -> Self {
312 Self::new(EventType::Focus, None, zone_id)
313 }
314
315 pub fn new_blur(zone_id: Option<String>) -> Self {
317 Self::new(EventType::Blur, None, zone_id)
318 }
319
320 pub fn new_resize(new_size: (usize, usize)) -> Self {
322 let mut event = Self::new(EventType::Resize, None, None);
323 event.data.size = Some(new_size);
324 event
325 }
326
327 pub fn new_custom(name: String, data: Option<String>, zone_id: Option<String>) -> Self {
329 let mut event = Self::new(EventType::Custom(name), None, zone_id);
330 event.data.custom_data = data;
331 event
332 }
333
334 pub fn new_mouse_move(
336 from_pos: Option<(usize, usize)>,
337 to_pos: (usize, usize),
338 zone_id: Option<String>,
339 ) -> Self {
340 let mut event = Self::new(EventType::MouseMove, Some(to_pos), zone_id);
341 let delta = if let Some(from) = from_pos {
342 (
343 to_pos.0 as i32 - from.0 as i32,
344 to_pos.1 as i32 - from.1 as i32,
345 )
346 } else {
347 (0, 0)
348 };
349 event.data.mouse_move = Some(MouseMoveInfo {
350 from_position: from_pos,
351 to_position: to_pos,
352 delta,
353 is_dragging: false,
354 drag_button: None,
355 });
356 event
357 }
358
359 pub fn new_mouse_drag(
361 from_pos: (usize, usize),
362 to_pos: (usize, usize),
363 button: MouseButton,
364 zone_id: Option<String>,
365 ) -> Self {
366 let mut event = Self::new(EventType::MouseMove, Some(to_pos), zone_id);
367 let delta = (
368 to_pos.0 as i32 - from_pos.0 as i32,
369 to_pos.1 as i32 - from_pos.1 as i32,
370 );
371 event.data.mouse_move = Some(MouseMoveInfo {
372 from_position: Some(from_pos),
373 to_position: to_pos,
374 delta,
375 is_dragging: true,
376 drag_button: Some(button),
377 });
378 event
379 }
380
381 pub fn new_hover_enter(
383 position: (usize, usize),
384 zone_id: String,
385 previous_zone: Option<String>,
386 ) -> Self {
387 let mut event = Self::new(EventType::Hover, Some(position), Some(zone_id.clone()));
388 event.data.hover = Some(HoverInfo {
389 state: HoverState::Enter,
390 previous_zone,
391 current_zone: Some(zone_id),
392 hover_duration: None,
393 });
394 event
395 }
396
397 pub fn new_hover_leave(
399 position: (usize, usize),
400 zone_id: String,
401 new_zone: Option<String>,
402 ) -> Self {
403 let mut event = Self::new(EventType::Hover, Some(position), Some(zone_id.clone()));
404 event.data.hover = Some(HoverInfo {
405 state: HoverState::Leave,
406 previous_zone: Some(zone_id),
407 current_zone: new_zone,
408 hover_duration: None,
409 });
410 event
411 }
412
413 pub fn new_hover_move(
415 position: (usize, usize),
416 zone_id: String,
417 duration: std::time::Duration,
418 ) -> Self {
419 let mut event = Self::new(EventType::Hover, Some(position), Some(zone_id.clone()));
420 event.data.hover = Some(HoverInfo {
421 state: HoverState::Move,
422 previous_zone: None,
423 current_zone: Some(zone_id),
424 hover_duration: Some(duration),
425 });
426 event
427 }
428
429 pub fn new_box_resize(
431 resize_type: BoxResizeType,
432 original_bounds: (usize, usize, usize, usize),
433 new_bounds: (usize, usize, usize, usize),
434 anchor: ResizeAnchor,
435 state: ResizeState,
436 ) -> Self {
437 let mut event = Self::new(EventType::BoxResize, None, None);
438 event.data.box_resize = Some(BoxResizeInfo {
439 resize_type,
440 original_bounds,
441 new_bounds,
442 anchor,
443 state,
444 });
445 event
446 }
447
448 pub fn new_title_change(
450 old_title: Option<String>,
451 new_title: String,
452 source: TitleChangeSource,
453 persist: bool,
454 ) -> Self {
455 let mut event = Self::new(EventType::TitleChange, None, None);
456 event.data.title_change = Some(TitleChangeInfo {
457 old_title,
458 new_title,
459 source,
460 persist,
461 });
462 event
463 }
464
465 pub fn mouse_button(&self) -> Option<&MouseButton> {
467 self.data.mouse_button.as_ref()
468 }
469
470 pub fn key_info(&self) -> Option<&KeyInfo> {
472 self.data.key.as_ref()
473 }
474
475 pub fn scroll_info(&self) -> Option<&ScrollInfo> {
477 self.data.scroll.as_ref()
478 }
479
480 pub fn is_click(&self) -> bool {
482 self.event_type == EventType::Click
483 }
484
485 pub fn is_double_click(&self) -> bool {
487 self.event_type == EventType::DoubleClick
488 }
489
490 pub fn is_keyboard(&self) -> bool {
492 self.event_type == EventType::KeyPress
493 }
494
495 pub fn is_mouse_move(&self) -> bool {
497 self.event_type == EventType::MouseMove
498 }
499
500 pub fn is_box_resize(&self) -> bool {
502 self.event_type == EventType::BoxResize
503 }
504
505 pub fn is_title_change(&self) -> bool {
507 self.event_type == EventType::TitleChange
508 }
509
510 pub fn mouse_move_info(&self) -> Option<&MouseMoveInfo> {
512 self.data.mouse_move.as_ref()
513 }
514
515 pub fn hover_info(&self) -> Option<&HoverInfo> {
517 self.data.hover.as_ref()
518 }
519
520 pub fn box_resize_info(&self) -> Option<&BoxResizeInfo> {
522 self.data.box_resize.as_ref()
523 }
524
525 pub fn title_change_info(&self) -> Option<&TitleChangeInfo> {
527 self.data.title_change.as_ref()
528 }
529
530 pub fn is_drag(&self) -> bool {
532 if let Some(mouse_move) = &self.data.mouse_move {
533 mouse_move.is_dragging
534 } else {
535 false
536 }
537 }
538
539 pub fn is_hover_enter(&self) -> bool {
541 if let Some(hover) = &self.data.hover {
542 hover.state == HoverState::Enter
543 } else {
544 false
545 }
546 }
547
548 pub fn is_hover_leave(&self) -> bool {
550 if let Some(hover) = &self.data.hover {
551 hover.state == HoverState::Leave
552 } else {
553 false
554 }
555 }
556
557 pub fn movement_delta(&self) -> Option<(i32, i32)> {
559 self.data.mouse_move.as_ref().map(|m| m.delta)
560 }
561}
562
563pub trait RenderableContent {
566 fn get_dimensions(&self) -> (usize, usize);
569
570 fn get_raw_content(&self) -> String;
572
573 fn get_box_relative_sensitive_zones(&self) -> Vec<SensitiveZone>;
576
577 fn handle_event(&mut self, event: &ContentEvent) -> EventResult;
581}
582
583#[derive(Debug, Clone)]
585pub struct SensitiveZone {
586 pub bounds: Bounds,
588 pub content_id: String,
590 pub content_type: ContentType,
592 pub metadata: SensitiveMetadata,
594}
595
596#[derive(Debug, Clone, PartialEq)]
598pub enum ContentType {
599 Choice,
601 Link,
603 Button,
605 ChartElement,
607 Tab,
609 Interactive,
611}
612
613#[derive(Debug, Clone, Default)]
615pub struct SensitiveMetadata {
616 pub display_text: Option<String>,
618 pub tooltip: Option<String>,
620 pub selected: bool,
622 pub enabled: bool,
624 pub original_line: Option<usize>,
626 pub char_range: Option<(usize, usize)>,
628}
629
630impl SensitiveZone {
631 pub fn new(bounds: Bounds, content_id: String, content_type: ContentType) -> Self {
633 Self {
634 bounds,
635 content_id,
636 content_type,
637 metadata: SensitiveMetadata::default(),
638 }
639 }
640
641 pub fn with_metadata(
643 bounds: Bounds,
644 content_id: String,
645 content_type: ContentType,
646 metadata: SensitiveMetadata,
647 ) -> Self {
648 Self {
649 bounds,
650 content_id,
651 content_type,
652 metadata,
653 }
654 }
655
656 pub fn contains(&self, x: usize, y: usize) -> bool {
659 self.bounds.contains_point(x, y)
660 }
661
662 pub fn display_text(&self) -> String {
664 self.metadata
665 .display_text
666 .clone()
667 .unwrap_or_else(|| self.content_id.clone())
668 }
669}
670
671#[derive(Debug, Clone)]
673pub struct ContentDimensions {
674 pub width: usize,
676 pub height: usize,
678 pub needs_horizontal_scroll: bool,
680 pub needs_vertical_scroll: bool,
682}
683
684impl ContentDimensions {
685 pub fn new(width: usize, height: usize, viewport_width: usize, viewport_height: usize) -> Self {
687 Self {
688 width,
689 height,
690 needs_horizontal_scroll: width > viewport_width,
691 needs_vertical_scroll: height > viewport_height,
692 }
693 }
694
695 pub fn needs_scrolling(&self) -> bool {
697 self.needs_horizontal_scroll || self.needs_vertical_scroll
698 }
699}