Skip to main content

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}