1use crate::component::{Component, EventCx, MeasureCx};
2use crate::event::Event;
3use crate::geom::{Rect, Size};
4use crate::layout::Constraint;
5use crate::render::RenderCx;
6use crate::style::Style;
7
8pub struct Select {
13 options: Vec<String>,
15 selected: usize,
17 expanded: bool,
19 focused: bool,
21 rect: Rect,
23 style: Style,
25 selected_style: Style,
27}
28
29impl Select {
30 pub fn new() -> Self {
32 Self {
33 options: Vec::new(),
34 selected: 0,
35 expanded: false,
36 focused: false,
37 rect: Rect::default(),
38 style: Style::default(),
39 selected_style: Style::default().bg(crate::style::Color::White).fg(crate::style::Color::Black),
40 }
41 }
42
43 pub fn options(mut self, options: Vec<String>) -> Self {
45 self.options = options;
46 self
47 }
48
49 pub fn style(mut self, style: Style) -> Self {
51 self.style = style;
52 self
53 }
54
55 pub fn selected_style(mut self, style: Style) -> Self {
57 self.selected_style = style;
58 self
59 }
60
61 pub fn selected(&self) -> usize {
63 self.selected
64 }
65
66 pub fn selected_text(&self) -> &str {
68 self.options.get(self.selected).map(|s| s.as_str()).unwrap_or("")
69 }
70
71 pub fn set_selected(&mut self, index: usize, cx: &mut EventCx) {
73 if index < self.options.len() {
74 self.selected = index;
75 cx.invalidate_paint();
76 }
77 }
78}
79
80impl Component for Select {
81 fn render(&self, cx: &mut RenderCx) {
82 if self.options.is_empty() {
83 return;
84 }
85
86 let indicator = if self.expanded { "▲" } else { "▼" };
88 let display = format!("{} {}", self.selected_text(), indicator);
89 if self.focused {
90 cx.set_style(self.selected_style.clone());
91 } else {
92 cx.set_style(self.style.clone());
93 }
94 cx.line(&display);
95
96 if self.expanded {
98 for (i, opt) in self.options.iter().enumerate() {
99 if i == self.selected {
100 cx.set_style(self.selected_style.clone());
101 cx.text("❯ ");
102 } else {
103 cx.set_style(self.style.clone());
104 cx.text(" ");
105 }
106 cx.line(opt);
107 }
108 }
109 }
110
111 fn measure(&self, _constraint: Constraint, _cx: &mut MeasureCx) -> Size {
112 if self.options.is_empty() {
113 return Size { width: 0, height: 0 };
114 }
115
116 let max_w: u16 = self.options.iter()
117 .map(|o| o.chars().map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0) as u16).sum::<u16>())
118 .max()
119 .unwrap_or(0)
120 + 2; let height = if self.expanded {
123 1u16.saturating_add(self.options.len() as u16)
124 } else {
125 1
126 };
127
128 Size { width: max_w, height }
129 }
130
131 fn event(&mut self, event: &Event, cx: &mut EventCx) {
132 match event {
133 Event::Focus => {
134 self.focused = true;
135 cx.invalidate_paint();
136 return;
137 }
138 Event::Blur => {
139 self.focused = false;
140 self.expanded = false;
141 cx.invalidate_layout();
142 return;
143 }
144 _ => {}
145 }
146
147 if self.options.is_empty() { return; }
148
149 if cx.phase() != crate::event::EventPhase::Target { return; }
151
152 if let Event::Key(key_event) = event {
153 match &key_event.key {
154 crate::event::Key::Enter | crate::event::Key::Char(' ') => {
155 if self.expanded {
156 self.expanded = false;
157 } else {
158 self.expanded = true;
159 }
160 cx.invalidate_layout();
161 return;
162 }
163 crate::event::Key::Esc => {
164 if self.expanded {
165 self.expanded = false;
166 cx.invalidate_layout();
167 }
168 return;
169 }
170 crate::event::Key::Up => {
171 if self.expanded {
172 if self.selected > 0 {
173 self.selected -= 1;
174 } else {
175 self.selected = self.options.len() - 1;
176 }
177 cx.invalidate_paint();
178 }
179 return;
180 }
181 crate::event::Key::Down => {
182 if self.expanded {
183 if self.selected + 1 < self.options.len() {
184 self.selected += 1;
185 } else {
186 self.selected = 0;
187 }
188 cx.invalidate_paint();
189 }
190 return;
191 }
192 _ => {}
193 }
194 }
195 }
196
197 fn layout(&mut self, rect: Rect, _cx: &mut crate::component::LayoutCx) {
198 self.rect = rect;
199 }
200
201 fn focusable(&self) -> bool {
202 true
203 }
204
205 fn style(&self) -> Style {
206 self.style.clone()
207 }
208}