use crate::ui::{Model, Pick, Selector, theme};
const ANCHOR: &str = "the open selector's segment";
const TOGGLED: &str = "the pass a selector was clicked on";
const LAYER: &str = "a selector's dropup over the box";
pub const NAME: &str = "a selector's dropup";
pub const NOTHING: &str = "nothing heard yet";
pub const HINT: &str = "arrows walk · Enter picks · Esc closes";
const CURRENT: &str = theme::glyph::CALL;
pub fn anchor(ui: &egui::Ui, model: &Model, selector: Selector, rect: egui::Rect) {
if model
.dropup
.as_ref()
.is_some_and(|open| open.selector == selector)
{
ui.data_mut(|d| d.insert_temp(egui::Id::new(ANCHOR), rect));
}
}
pub fn toggle(ui: &egui::Ui, model: &mut Model, selector: Selector) {
model.drop_up(selector);
let pass = ui.ctx().cumulative_pass_nr();
ui.data_mut(|d| d.insert_temp(egui::Id::new(TOGGLED), pass));
}
fn ops(pick: &Pick) -> &'static [&'static str] {
use crate::verbs;
match pick {
Pick::Wall(_) => &[verbs::CONVERSATIONS.word],
Pick::Found(_) => &[verbs::PREPARE, verbs::PROMPT],
Pick::Provider(_) => &[verbs::MODELS.word],
Pick::Model(_) => &[verbs::MODEL.word],
Pick::Effort(_) => &[verbs::EFFORT],
Pick::Priority(_) => &[verbs::PRIORITY],
}
}
pub fn keys(ctx: &egui::Context, model: &mut Model) {
if model.dropup.is_none() || model.covered() {
return;
}
let took = |key| ctx.input_mut(|i| i.consume_key(egui::Modifiers::NONE, key));
let (up, down, enter, escape) = (
took(egui::Key::ArrowUp),
took(egui::Key::ArrowDown),
took(egui::Key::Enter),
took(egui::Key::Escape),
);
if up {
model.walk_dropup(-1);
}
if down {
model.walk_dropup(1);
}
if escape {
model.close_dropup();
}
if enter && let Some(open) = &model.dropup {
model.pick(open.cursor);
}
}
pub fn render(ui: &egui::Ui, model: &mut Model, foot: f32) {
if footed(model) {
return;
}
let column = ui.max_rect();
let room = egui::Rect::from_min_max(column.left_top(), egui::pos2(column.right(), foot));
let id = egui::Id::new(LAYER);
let mut ui = egui::Ui::new(
ui.ctx().clone(),
id,
egui::UiBuilder::new()
.layer_id(egui::LayerId::new(egui::Order::Foreground, id))
.max_rect(room),
);
stand(&mut ui, model);
}
pub fn footed(model: &Model) -> bool {
model
.dropup
.as_ref()
.is_some_and(|open| open.selector == Selector::Workspace)
}
pub fn stand(ui: &mut egui::Ui, model: &mut Model) {
let Some(cursor) = model.dropup.as_ref().map(|open| open.cursor) else {
return;
};
if model.covered() || crate::ui::slash::parsed(&model.draft).is_some() {
model.close_dropup();
return;
}
let choices = model.choices();
let room = ui.max_rect();
let air = 2.0 * theme::space::S;
let words = |text: &str| super::width(ui, text) + air;
let bar = ui.spacing().scroll.bar_width + ui.spacing().scroll.bar_inner_margin;
let wide = choices
.iter()
.map(|choice| {
theme::paint::row_width(ui, &format!("{CURRENT} {}", choice.text)) + air + bar
})
.fold(words(HINT), f32::max)
.min(room.width() - 2.0 * theme::space::L);
let anchor = ui.data(|d| d.get_temp::<egui::Rect>(egui::Id::new(ANCHOR)));
let left = anchor
.map_or(room.left(), |seat| seat.left() - theme::space::S)
.min(room.right() - theme::space::L - wide)
.max(room.left() + theme::space::L);
let line = theme::ROW + ui.spacing().item_spacing.y;
let lines = std::iter::repeat_n(line, choices.len().max(1) + 1).sum::<f32>();
let mut picked = None;
let span = egui::Rangef::new(left, left + wide);
let stood = crate::ui::overlay::float_at(ui, NAME, span, lines + air, |ui| {
ui.label(egui::RichText::new(HINT).small().color(theme::INK_WEAK));
for (at, choice) in choices.iter().enumerate() {
let text = if choice.current {
format!("{CURRENT} {}", choice.text)
} else {
choice.text.clone()
};
let seat = theme::paint::row(ui, &text, theme::INK, None, at == cursor, 0.0);
crate::ui::act::tag(&seat, ops(&choice.pick));
if seat.clicked() {
picked = Some(at);
}
}
if choices.is_empty() {
theme::paint::empty(ui, NOTHING);
}
});
if let Some(at) = picked {
model.pick(at);
return;
}
let toggled = ui.data(|d| d.get_temp::<u64>(egui::Id::new(TOGGLED)));
let pass = ui.ctx().cumulative_pass_nr();
let elsewhere = ui.input(|i| {
i.pointer.any_click()
&& i.pointer
.interact_pos()
.is_some_and(|at| !stood.contains(at))
});
if elsewhere && toggled != Some(pass) {
model.close_dropup();
}
}