1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
//! Input handling methods for Combobox
use super::super::Combobox;
impl Combobox {
// ─────────────────────────────────────────────────────────────────────────
// Input handling
// ─────────────────────────────────────────────────────────────────────────
/// Insert character at cursor
pub fn insert_char(&mut self, c: char) {
let byte_idx = self.char_to_byte_index(self.cursor);
self.input.insert(byte_idx, c);
self.cursor += 1;
self.update_filter();
self.open = true;
}
/// Delete character before cursor (backspace)
pub fn delete_backward(&mut self) {
if self.cursor > 0 {
self.cursor -= 1;
let byte_idx = self.char_to_byte_index(self.cursor);
if let Some((_, ch)) = self.input.char_indices().nth(self.cursor) {
self.input.drain(byte_idx..byte_idx + ch.len_utf8());
}
self.update_filter();
}
}
/// Delete character at cursor (delete)
pub fn delete_forward(&mut self) {
let char_count = self.input.chars().count();
if self.cursor < char_count {
let byte_idx = self.char_to_byte_index(self.cursor);
if let Some((_, ch)) = self.input.char_indices().nth(self.cursor) {
self.input.drain(byte_idx..byte_idx + ch.len_utf8());
}
self.update_filter();
}
}
/// Move cursor left
pub fn move_left(&mut self) {
if self.cursor > 0 {
self.cursor -= 1;
}
}
/// Move cursor right
pub fn move_right(&mut self) {
let char_count = self.input.chars().count();
if self.cursor < char_count {
self.cursor += 1;
}
}
/// Move cursor to start
pub fn move_to_start(&mut self) {
self.cursor = 0;
}
/// Move cursor to end
pub fn move_to_end(&mut self) {
self.cursor = self.input.chars().count();
}
/// Handle key event
pub fn handle_key(&mut self, key: &crate::event::Key) -> bool {
use crate::event::Key;
match key {
Key::Char(c) => {
self.insert_char(*c);
true
}
Key::Backspace => {
self.delete_backward();
true
}
Key::Delete => {
self.delete_forward();
true
}
Key::Left => {
self.move_left();
false
}
Key::Right => {
self.move_right();
false
}
Key::Home => {
self.move_to_start();
false
}
Key::End => {
self.move_to_end();
false
}
Key::Up if self.open => {
self.select_prev();
true
}
Key::Down if self.open => {
self.select_next();
true
}
Key::Down if !self.open => {
self.open_dropdown();
true
}
Key::Enter if self.open => {
self.select_current();
true
}
Key::Enter if !self.open && self.allow_custom => {
// Accept custom value
true
}
Key::Escape if self.open => {
self.close_dropdown();
true
}
Key::Tab if self.open && !self.filtered.is_empty() => {
// Tab completion: fill with highlighted option and close dropdown
if let Some(&option_idx) = self.filtered.get(self.selected_idx) {
self.input = self.options[option_idx].label.clone();
self.cursor = self.input.chars().count();
self.update_filter();
}
self.close_dropdown();
true
}
_ => false,
}
}
}
impl Combobox {
// ─────────────────────────────────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────────────────────────────────
/// Convert character index to byte index
pub(super) fn char_to_byte_index(&self, char_idx: usize) -> usize {
self.input
.char_indices()
.nth(char_idx)
.map(|(i, _)| i)
.unwrap_or(self.input.len())
}
/// Calculate display width
pub(super) fn display_width(&self, max_width: u16) -> u16 {
if let Some(w) = self.width {
return w.min(max_width);
}
let max_option_len = self
.options
.iter()
.map(|o| crate::utils::display_width(&o.label))
.max()
.unwrap_or(crate::utils::display_width(&self.placeholder));
// +4 for padding and borders
((max_option_len.max(20) + 4) as u16).min(max_width)
}
}