uzor_core/widgets/popup/input.rs
1//! Popup input adapter - Contract/Connector for popup event handling
2//!
3//! **PopupInputHandler is a CONTRACT/CONNECTOR trait** that connects:
4//! - Factory event handling logic (`factory/mod.rs`)
5//! - External input systems (event loops, input managers, etc.)
6
7use crate::types::Rect;
8
9/// Input handler adapter for popup events
10///
11/// This trait defines the contract for converting raw input events into popup actions.
12/// External projects implement this trait to customize input behavior (rare).
13pub trait PopupInputHandler {
14 // =========================================================================
15 // Hit Testing
16 // =========================================================================
17
18 /// Test if mouse position is inside popup rect
19 fn hit_test(&self, popup_rect: &Rect, mouse_pos: (f64, f64)) -> bool {
20 let (mouse_x, mouse_y) = mouse_pos;
21 mouse_x >= popup_rect.x
22 && mouse_x <= popup_rect.x + popup_rect.width
23 && mouse_y >= popup_rect.y
24 && mouse_y <= popup_rect.y + popup_rect.height
25 }
26
27 /// Test if click was outside popup (for auto-dismiss)
28 fn is_outside_click(&self, mouse_pos: (f64, f64), popup_rect: &Rect) -> bool {
29 !self.hit_test(popup_rect, mouse_pos)
30 }
31
32 // =========================================================================
33 // ContextMenu - Item Selection
34 // =========================================================================
35
36 /// Convert mouse Y coordinate to menu item index
37 fn mouse_to_item_index(
38 &self,
39 mouse_y: f64,
40 popup_y: f64,
41 item_height: f64,
42 item_count: usize,
43 padding_vertical: f64,
44 ) -> Option<usize> {
45 let relative_y = mouse_y - popup_y - padding_vertical;
46 if relative_y < 0.0 {
47 return None;
48 }
49
50 let index = (relative_y / item_height) as usize;
51 if index < item_count {
52 Some(index)
53 } else {
54 None
55 }
56 }
57
58 // =========================================================================
59 // ColorPicker - Color Selection
60 // =========================================================================
61
62 /// Convert mouse position to color grid index
63 fn mouse_to_color_index(
64 &self,
65 mouse_pos: (f64, f64),
66 popup_rect: &Rect,
67 grid_cols: usize,
68 swatch_size: f64,
69 grid_spacing: f64,
70 padding: f64,
71 ) -> Option<usize> {
72 let (mouse_x, mouse_y) = mouse_pos;
73
74 // Calculate position relative to grid start
75 let grid_x = mouse_x - popup_rect.x - padding;
76 let grid_y = mouse_y - popup_rect.y - padding;
77
78 if grid_x < 0.0 || grid_y < 0.0 {
79 return None;
80 }
81
82 // Calculate cell size (swatch + spacing)
83 let cell_size = swatch_size + grid_spacing;
84
85 // Calculate column and row
86 let col = (grid_x / cell_size) as usize;
87 let row = (grid_y / cell_size) as usize;
88
89 // Check if within actual swatch (not in spacing)
90 let local_x = grid_x - (col as f64 * cell_size);
91 let local_y = grid_y - (row as f64 * cell_size);
92
93 if local_x > swatch_size || local_y > swatch_size {
94 return None; // In spacing between swatches
95 }
96
97 if col >= grid_cols {
98 return None;
99 }
100
101 // Calculate linear index
102 Some(row * grid_cols + col)
103 }
104
105 // =========================================================================
106 // Position Adjustment
107 // =========================================================================
108
109 /// Adjust popup position to keep it on screen
110 fn adjust_position_to_screen(
111 &self,
112 pos: (f64, f64),
113 size: (f64, f64),
114 screen: (f64, f64),
115 ) -> (f64, f64) {
116 let (mut x, mut y) = pos;
117 let (width, height) = size;
118 let (screen_width, screen_height) = screen;
119
120 // Adjust X - keep on screen
121 if x + width > screen_width {
122 x = (x - width).max(0.0);
123 }
124 if x < 0.0 {
125 x = 0.0;
126 }
127
128 // Adjust Y - keep on screen
129 if y + height > screen_height {
130 y = (y - height).max(0.0);
131 }
132 if y < 0.0 {
133 y = 0.0;
134 }
135
136 (x, y)
137 }
138}
139
140// =============================================================================
141// Default Input Handler Implementation
142// =============================================================================
143
144/// Default implementation of PopupInputHandler
145#[derive(Clone, Copy, Debug, Default)]
146pub struct DefaultPopupInputHandler;
147
148impl PopupInputHandler for DefaultPopupInputHandler {
149 // All methods use trait defaults (no overrides needed)
150}