use crate::ui::slash::Row;
use crate::ui::{Model, theme};
pub const LEAD: &str = theme::glyph::LEAD;
pub const SEP: &str = theme::glyph::SEP;
pub const READY: &str = "ready";
pub const ENGINES: &str = "engines";
pub const NO_MODEL: &str = "no model";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Run {
pub text: String,
pub ink: egui::Color32,
pub door: Option<Row>,
}
fn run(text: impl Into<String>, ink: egui::Color32) -> Run {
Run {
text: text.into(),
ink,
door: None,
}
}
pub fn door(model: &Model, word: &str) -> Option<Row> {
crate::ui::slash::rows(model)
.into_iter()
.find(|row| row.word == word)
}
pub fn state(model: &Model) -> Run {
if let Some(notice) = &model.notice {
let ink = match notice {
crate::ui::Notice::Said(_) => theme::accent(theme::State::Annotation),
_ => theme::accent(theme::State::Error),
};
return run(notice.line(), ink);
}
match selected(model) {
Some(row) => run(
format!(
"{} · {}",
row.state.label(),
crate::ui::convs::age(row.age_secs)
),
theme::state_ink(&row.state),
),
None => run(READY, theme::INK_WEAK),
}
}
fn selected(model: &Model) -> Option<crate::reply::convs::ConvRow> {
let id = model.conversation.as_ref()?;
model.rows().into_iter().find(|row| row.root_id == *id)
}
fn answered_by(model: &Model) -> Option<String> {
model
.transcript
.entries
.iter()
.rev()
.find_map(|entry| match &entry.kind {
crate::reply::transcript::EntryKind::Model { model_id, .. } => Some(model_id.clone()),
_ => None,
})
}
pub fn waiting(model: &Model) -> u64 {
model
.roster
.iter()
.flat_map(|chunk| chunk.walls.iter())
.map(|wall| wall.attention)
.sum()
}
pub fn facts(model: &Model) -> Vec<Run> {
let mut out = Vec::new();
let opens = door(model, crate::ui::tuning::MODEL);
match answered_by(model) {
Some(model_id) => out.push(Run {
door: opens,
..run(model_id, theme::INK_WEAK)
}),
None if opens.is_some() => out.push(Run {
door: opens,
..run(NO_MODEL, theme::INK_WEAK)
}),
None => {}
}
if let Some(agent) = model.agent() {
out.push(run(
crate::ui::records::header::costing(agent),
theme::INK_WEAK,
));
}
let asking = waiting(model);
if asking > 0 {
out.push(run(
format!("{asking} waiting"),
theme::accent(theme::State::Attention),
));
}
let engines = model.roster.len();
out.push(run(
if engines == 1 {
"1 engine".to_owned()
} else {
format!("{engines} engines")
},
theme::INK_WEAK,
));
out
}
pub fn name(model: &Model) -> Run {
let text = match (selected(model), &model.aim) {
(Some(row), _) => row.display,
(None, Some(aim)) => aim.address.clone(),
(None, None) => ENGINES.to_owned(),
};
Run {
door: door(model, crate::ui::switcher::WORD),
..run(text, theme::INK)
}
}
fn width(ui: &egui::Ui, text: &str) -> f32 {
let font = egui::TextStyle::Small.resolve(ui.style());
ui.fonts(|fonts| {
fonts
.layout_no_wrap(text.to_owned(), font, theme::INK)
.size()
.x
})
}
pub fn fitting(ui: &egui::Ui, state: &Run, facts: &[Run], name: &str, room: f32) -> usize {
let gap = ui.spacing().item_spacing.x;
let sep = width(ui, SEP) + 2.0 * gap;
let fixed = width(ui, LEAD) * 2.0 + width(ui, &state.text) + width(ui, name) + 6.0 * gap;
let mut used = fixed;
let mut shown = 0;
for fact in facts {
let more = sep + width(ui, &fact.text);
if used + more > room {
break;
}
used += more;
shown += 1;
}
shown
}
fn paint(ui: &mut egui::Ui, run: &Run) -> Option<&'static str> {
let Some(row) = run.door else {
ui.label(egui::RichText::new(&run.text).small().color(run.ink));
return None;
};
let seat = theme::paint::link(ui, &run.text, run.ink);
crate::ui::act::tag(&seat, row.ops);
seat.clicked().then_some(row.word)
}
pub fn render(ui: &mut egui::Ui, model: &mut Model) {
let state = state(model);
let facts = facts(model);
let name = name(model);
let shown = fitting(ui, &state, &facts, &name.text, ui.available_width());
let faint = |ui: &mut egui::Ui, text: &str| {
ui.label(egui::RichText::new(text).small().color(theme::INK_FAINT));
};
let mut fired = None;
ui.horizontal(|ui| {
faint(ui, LEAD);
paint(ui, &state);
for fact in facts.iter().take(shown) {
faint(ui, SEP);
fired = paint(ui, fact).or(fired);
}
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
fired = paint(ui, &name).or(fired);
faint(ui, LEAD);
});
});
if let Some(word) = fired {
crate::ui::slash::fire(model, word, "");
}
}
#[cfg(test)]
mod tests;