use crate::reply::queue::QueueRow;
use crate::ui::{Model, queue, theme};
pub const HEADING: &str = "it is asking";
pub const HINT: &str = "a digit picks · arrows move · Enter answers";
const CHOSEN_ID: &str = "the ask's chosen row";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Choice {
Seen,
Verdict(&'static str, &'static str),
}
impl Choice {
pub fn word(self) -> String {
match self {
Self::Seen => queue::SEEN.to_owned(),
Self::Verdict(verdict, scope) if scope == crate::verbs::capability::CALL => {
verdict.to_owned()
}
Self::Verdict(verdict, scope) => queue::wider(verdict, scope),
}
}
pub(crate) fn op(self) -> &'static str {
match self {
Self::Seen => crate::verbs::SEEN.word,
Self::Verdict(..) => crate::verbs::ANSWER.word,
}
}
}
pub fn choices(row: &QueueRow) -> Vec<Choice> {
let mut out = vec![Choice::Seen];
if row.held.is_some() {
for scope in crate::verbs::SCOPES {
out.push(Choice::Verdict("pass", scope));
}
out.push(Choice::Verdict("refuse", crate::verbs::capability::CALL));
out.push(Choice::Verdict("hold", crate::verbs::capability::CALL));
}
out
}
pub fn row(model: &Model) -> Option<QueueRow> {
let id = model.conversation.as_ref()?;
model
.waiting
.iter()
.flat_map(|section| section.rows.iter())
.find(|row| row.agent == *id)
.cloned()
}
pub fn answer(model: &mut Model, row: &QueueRow, choice: Choice) {
match choice {
Choice::Seen => model.post_seen(row),
Choice::Verdict(verdict, scope) => model.post_answer(row, verdict, scope),
}
}
fn chosen(ctx: &egui::Context, len: usize) -> usize {
ctx.data(|d| d.get_temp::<usize>(egui::Id::new(CHOSEN_ID)))
.unwrap_or(0)
.min(len.saturating_sub(1))
}
pub fn moved(ctx: &egui::Context, model: &Model, step: isize) {
let Some(row) = row(model) else {
return;
};
let len = choices(&row).len();
let at = chosen(ctx, len).saturating_add_signed(step).min(len - 1);
ctx.data_mut(|d| d.insert_temp(egui::Id::new(CHOSEN_ID), at));
}
pub fn confirm(ctx: &egui::Context, model: &mut Model) {
let Some(row) = row(model) else {
return;
};
let offered = choices(&row);
if let Some(choice) = offered.get(chosen(ctx, offered.len())) {
answer(model, &row, *choice);
}
}
pub fn pick(model: &mut Model, n: usize) -> bool {
let Some(row) = row(model) else {
return false;
};
let Some(choice) = n.checked_sub(1).and_then(|i| choices(&row).get(i).copied()) else {
return false;
};
answer(model, &row, choice);
true
}
pub fn render(ui: &mut egui::Ui, model: &mut Model, row: &QueueRow) {
let accent = theme::accent(theme::State::Attention);
let offered = choices(row);
let at = chosen(ui.ctx(), offered.len());
let mut taken = None;
egui::Frame::none()
.fill(theme::SURFACE)
.stroke(egui::Stroke::new(1.0, accent))
.rounding(theme::RADIUS)
.inner_margin(egui::Margin::symmetric(theme::space::S, theme::space::XS))
.show(ui, |ui| {
ui.horizontal(|ui| {
ui.label(egui::RichText::new(HEADING).color(accent));
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
ui.label(egui::RichText::new(HINT).small().color(theme::INK_WEAK));
});
});
for (said, ink) in words(row) {
ui.add(egui::Label::new(egui::RichText::new(said).color(ink)).wrap());
}
for (i, choice) in offered.iter().enumerate() {
let seat = theme::paint::row(
ui,
&format!("{}. {}", i + 1, choice.word()),
theme::INK,
None,
i == at,
0.0,
);
crate::ui::act::tag(&seat, &[choice.op()]);
if seat.clicked() {
taken = Some(*choice);
}
}
});
if let Some(choice) = taken {
answer(model, row, choice);
}
}
pub fn words(row: &QueueRow) -> Vec<(String, egui::Color32)> {
let note = theme::accent(theme::State::Annotation);
let mut out = Vec::new();
if let Some(said) = queue::parked(row) {
out.push((said, note));
}
if let Some(said) = queue::flagged(row) {
out.push((said, note));
}
if let Some(said) = queue::signalled(row) {
out.push((said, theme::INK_WEAK));
}
if let Some(said) = &row.failure {
out.push((said.clone(), theme::accent(theme::State::Error)));
}
if !row.preview.is_empty() {
out.push((row.preview.clone(), theme::INK));
}
out
}
#[cfg(test)]
mod tests;