use crate::ui::{Model, theme};
pub use candidates::{ACCEPT, GOAL_HINT, RELEASE, SPREAD, SUMMARY_HINT};
pub use rows::{attempt, changed, churn, receipt};
mod candidates;
mod rows;
pub const OPEN: &str = "run the fleet…";
pub const CLOSE: &str = "done";
pub const HEADING: &str = "the fleet";
pub const RUN: &str = "run this many at once";
pub const DISBAND: &str = "stop the fleet";
pub const WATCH: &str = "watch with this model";
pub const DISARM: &str = "stop watching";
pub const SCAN: &str = "flush the inboxes";
pub const PROJECT_HINT: &str = "project";
pub const MODEL_HINT: &str = "cheap model id";
pub const FEWER: &str = "−";
pub const MORE: &str = "+";
pub const ATTEMPTS: &str = "delivery attempts";
pub const CHANGES: &str = "what the agents changed";
pub const NOT_ANSWERED: &str = "waiting to hear what this wall's agents have done";
pub const NOTHING: &str = "this wall's agents have delivered nothing yet";
pub fn render(ui: &mut egui::Ui, model: &mut Model) -> bool {
let Some((at, said)) = model
.fleet
.as_ref()
.map(|held| (held.at.clone(), held.said.clone()))
else {
return false;
};
ui.heading(HEADING);
ui.label(format!("on {} — {}", at.address, at.channel));
ui.horizontal_wrapped(|ui| {
if ui.button(CLOSE).clicked() {
model.close_fleet();
}
});
if let Some(said) = &said {
ui.colored_label(theme::NOTICE, receipt(said));
}
ui.separator();
if let Some(envelope) = acts(ui, model, &at.address) {
model.post_fleet(envelope);
}
ui.separator();
listings(ui, model);
true
}
fn acts(ui: &mut egui::Ui, model: &mut Model, wall: &str) -> Option<serde_json::Value> {
let mut fired: Option<serde_json::Value> = None;
let fleet = model.fleet.as_mut()?;
ui.horizontal_wrapped(|ui| {
if ui.button(FEWER).clicked() {
fleet.cap = fleet.cap.saturating_sub(1).max(1);
}
ui.label(fleet.cap.to_string());
if ui.button(MORE).clicked() {
fleet.cap = fleet.cap.saturating_add(1);
}
ui.add(egui::TextEdit::singleline(&mut fleet.project).hint_text(PROJECT_HINT));
let run = ui.add_enabled(!fleet.project.trim().is_empty(), egui::Button::new(RUN));
crate::ui::act::tag(&run, &[crate::verbs::FLEET]);
if run.clicked() {
fired = Some(crate::verbs::fleet(
wall.to_owned(),
fleet.project.trim().to_owned(),
fleet.cap,
));
}
let stop = ui.button(DISBAND);
crate::ui::act::tag(&stop, &[crate::verbs::DISBAND.word]);
if stop.clicked() {
fired = Some(crate::verbs::disband(wall.to_owned()));
}
});
ui.horizontal_wrapped(|ui| {
ui.add(egui::TextEdit::singleline(&mut fleet.model).hint_text(MODEL_HINT));
let watch = ui.add_enabled(!fleet.model.trim().is_empty(), egui::Button::new(WATCH));
crate::ui::act::tag(&watch, &[crate::verbs::ARM.word]);
if watch.clicked() {
fired = Some(crate::verbs::arm(
wall.to_owned(),
fleet.model.trim().to_owned(),
));
}
let drop = ui.button(DISARM);
crate::ui::act::tag(&drop, &[crate::verbs::DISARM.word]);
if drop.clicked() {
fired = Some(crate::verbs::disarm(wall.to_owned()));
}
let flush = ui.button(SCAN);
crate::ui::act::tag(&flush, &[crate::verbs::SCAN.word]);
if flush.clicked() {
fired = Some(crate::verbs::scan(wall.to_owned()));
}
});
fired
}
fn listings(ui: &mut egui::Ui, model: &mut Model) {
let attempts = model.attempts.clone();
let work = model.work.clone();
egui::ScrollArea::vertical()
.id_salt(HEADING)
.auto_shrink(false)
.show(ui, |ui| {
ui.label(ATTEMPTS);
match attempts.as_deref() {
None => drop(ui.label(NOT_ANSWERED)),
Some([]) => drop(ui.label(NOTHING)),
Some(rows) => {
for row in rows {
ui.separator();
for said in attempt(row) {
ui.label(said);
}
}
}
}
ui.separator();
ui.label(CHANGES);
match work.as_deref() {
None => drop(ui.label(NOT_ANSWERED)),
Some([]) => drop(ui.label(NOTHING)),
Some(rows) => candidates::render(ui, model, rows),
}
});
}
#[cfg(test)]
mod tests;