use compact_str::CompactString;
use crate::events::HitMap;
use crate::layout::layoutbox::geometry::{
SelectRow, overlay_options, select_overlay_rect, select_rows,
};
use crate::script::ScriptMsg;
use crate::tdom::node::{NodeArena, NodeId, NodeType};
use super::InputState;
use super::cmd::RenderCmd;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OverlayKind {
Select,
Datalist,
}
#[derive(Debug, Clone)]
pub struct SelectOpenState {
pub node_id: NodeId,
pub kind: OverlayKind,
pub rect: crate::layout::Rect,
pub options: Vec<crate::layout::layoutbox::geometry::SelectOption>,
pub rows: Vec<crate::layout::layoutbox::geometry::SelectRow>,
pub highlight: usize,
}
pub(super) fn hit_select_option(open: &SelectOpenState, x: u32, y: u32) -> Option<usize> {
open.rect
.contains(x, y)
.then(|| (y - open.rect.y as u32) as usize)
}
pub(super) fn row_option(rows: &[SelectRow], row: usize) -> Option<usize> {
match rows.get(row)? {
SelectRow::Item(i) => Some(*i),
SelectRow::Group(_) => rows[row..].iter().find_map(|r| match r {
SelectRow::Item(i) => Some(*i),
SelectRow::Group(_) => None,
}),
}
}
fn open_overlay(
state: &mut InputState,
hit_map: &HitMap,
arena: &NodeArena,
node_id: NodeId,
kind: OverlayKind,
filter: Option<&str>,
cmds: &mut Vec<RenderCmd>,
) {
let Some(region) = hit_map.region_for(node_id) else {
return;
};
let options = overlay_options(arena, node_id, filter);
let rows = select_rows(&options);
let selected = match kind {
OverlayKind::Select => {
let value = arena
.get(node_id)
.and_then(|n| n.attributes.get("value"))
.cloned()
.unwrap_or_default();
options.iter().position(|o| o.value == value).unwrap_or(0)
}
OverlayKind::Datalist => 0,
};
let rect = select_overlay_rect(&options, &rows, region.rect);
state.open_select = Some(SelectOpenState {
node_id,
kind,
rect,
options,
rows,
highlight: selected,
});
cmds.push(RenderCmd::SelectOpen {
node_id,
filter: filter.map(CompactString::from),
});
}
pub(super) fn open_select(
state: &mut InputState,
hit_map: &HitMap,
arena: &NodeArena,
node_id: NodeId,
cmds: &mut Vec<RenderCmd>,
) {
open_overlay(
state,
hit_map,
arena,
node_id,
OverlayKind::Select,
None,
cmds,
);
}
pub(super) fn refresh_datalist(
state: &mut InputState,
hit_map: Option<&HitMap>,
arena: Option<&NodeArena>,
cmds: &mut Vec<RenderCmd>,
) {
let Some(focused) = state.focused_input else {
return;
};
let (Some(hit_map), Some(arena)) = (hit_map, arena) else {
return;
};
let has_list = arena
.get(focused)
.is_some_and(|n| n.node_type == NodeType::Input && n.attributes.contains_key("list"));
if !has_list {
if let Some(open) = state.open_select.take() {
cmds.push(RenderCmd::SelectClose {
node_id: open.node_id,
});
}
return;
}
let filter = state.input_value.clone();
open_overlay(
state,
hit_map,
arena,
focused,
OverlayKind::Datalist,
Some(&filter),
cmds,
);
}
pub(super) fn close_select(
state: &mut InputState,
arena: &NodeArena,
pick: Option<usize>,
msgs: &mut Vec<ScriptMsg>,
cmds: &mut Vec<RenderCmd>,
) {
let Some(open) = state.open_select.take() else {
return;
};
cmds.push(RenderCmd::SelectClose {
node_id: open.node_id,
});
if let Some(idx) = pick
&& idx < open.options.len()
{
let value = open.options[idx].value.clone();
let old = arena
.get(open.node_id)
.and_then(|n| n.attributes.get("value"))
.cloned()
.unwrap_or_default();
if value != old {
msgs.push(ScriptMsg::SelectPick {
node_id: open.node_id,
value,
});
}
}
}