use crate::render::Cell;
use crate::style::Color;
use crate::utils::truncate_to_width;
use crate::widget::theme::{MAX_DROPDOWN_VISIBLE, PLACEHOLDER_FG};
use crate::widget::traits::{RenderContext, View};
use super::Select;
impl View for Select {
fn render(&self, ctx: &mut RenderContext) {
let area = ctx.area;
if area.width < 3 || area.height < 1 {
return;
}
let width = self.display_width(area.width);
let text_width = (width - 2) as usize;
let fg = if self.disabled {
Some(PLACEHOLDER_FG)
} else if self.focused {
self.fg.or(Some(Color::CYAN))
} else {
self.fg
};
let bg = self.bg;
let display_text = if self.open && self.searchable && !self.query.is_empty() {
&self.query
} else {
self.value().unwrap_or(&self.placeholder)
};
let arrow = if self.open { "▲" } else { "▼" };
ctx.fill_row(0, width, fg, bg);
if self.focused && !self.disabled {
ctx.draw_focus_brackets(0, width, Color::CYAN);
}
let icon = if self.open && self.searchable {
"🔍".chars().next().unwrap_or('?')
} else {
arrow.chars().next().unwrap_or('▼')
};
let mut cell = Cell::new(icon);
cell.fg = fg;
cell.bg = bg;
ctx.set(0, 0, cell);
let truncated = truncate_to_width(display_text, text_width);
let mut cx: u16 = 2;
for ch in truncated.chars() {
let mut cell = Cell::new(ch);
cell.fg = fg;
cell.bg = bg;
ctx.set(cx, 0, cell);
cx += crate::utils::char_width(ch) as u16;
}
if self.open {
let visible_options: Vec<(usize, &String)> = if self.query.is_empty() {
self.options.iter().enumerate().collect()
} else {
self.filtered
.iter()
.filter_map(|&i| self.options.get(i).map(|opt| (i, opt)))
.collect()
};
let dropdown_height = if visible_options.is_empty() {
1u16 } else {
(visible_options.len() as u16).min(MAX_DROPDOWN_VISIBLE)
};
let (abs_x, abs_y) = ctx.absolute_position();
let buf_height = ctx.buffer.height();
let space_below = buf_height.saturating_sub(abs_y + 1);
let overlay_y = if space_below >= dropdown_height {
abs_y + 1 } else {
abs_y.saturating_sub(dropdown_height) };
let overlay_area = crate::layout::Rect::new(abs_x, overlay_y, width, dropdown_height);
let mut entry = crate::widget::traits::OverlayEntry::new(100, overlay_area);
if visible_options.is_empty() && !self.query.is_empty() {
let msg = "No results";
for (i, ch) in msg.chars().enumerate() {
let mut cell = Cell::new(ch);
cell.fg = Some(PLACEHOLDER_FG);
entry.push(2 + i as u16, 0, cell);
}
}
for (row, (option_idx, option)) in visible_options
.iter()
.enumerate()
.take(dropdown_height as usize)
{
let y = row as u16;
let is_selected = self.selection.is_selected(*option_idx);
let (fg, bg) = if is_selected {
(self.selected_fg, self.selected_bg)
} else {
(self.fg, self.bg)
};
for x in 0..width {
let mut cell = Cell::new(' ');
cell.fg = fg;
cell.bg = bg;
entry.push(x, y, cell);
}
let indicator = if is_selected { '›' } else { ' ' };
let mut cell = Cell::new(indicator);
cell.fg = fg;
cell.bg = bg;
entry.push(0, y, cell);
let match_indices: std::collections::HashSet<usize> = self
.get_match(option)
.map(|m| m.indices.into_iter().collect())
.unwrap_or_default();
let truncated = truncate_to_width(option, text_width);
let mut cx: u16 = 2;
for (j, ch) in truncated.chars().enumerate() {
let mut cell = Cell::new(ch);
cell.bg = bg;
if match_indices.contains(&j) {
cell.fg = self.highlight_fg;
} else {
cell.fg = fg;
}
entry.push(cx, y, cell);
cx += crate::utils::char_width(ch) as u16;
}
}
if !ctx.queue_overlay(entry.clone()) {
for oc in &entry.cells {
ctx.set(oc.x, oc.y + 1, oc.cell);
}
}
}
}
crate::impl_view_meta!("Select");
}