use super::super::ShellState;
use super::super::menus::{BallRef, Target};
use super::super::start_rows::{ball_id, continue_row, ready_row};
use crate::AppModel;
use crate::board::Column;
use crate::cli_outbound::Cli;
use crate::nav::menu::Seat;
use crate::start::StartInputs;
use std::collections::HashMap;
pub(super) struct Affordances {
starts: HashMap<String, StartInputs>,
resumes: HashMap<String, StartInputs>,
}
impl Affordances {
pub(super) fn of(model: &AppModel) -> Self {
Self {
starts: keyed(model.startable()),
resumes: keyed(model.resumable()),
}
}
}
fn keyed(inputs: Vec<StartInputs>) -> HashMap<String, StartInputs> {
inputs
.into_iter()
.filter_map(|i| Some((ball_id(&i.payload)?, i)))
.collect()
}
pub(super) fn board_row(
ui: &mut egui::Ui,
model: &mut AppModel,
state: &mut ShellState,
lernie: &Cli,
row: &crate::board::BoardRow,
affordances: &Affordances,
) {
match (
row.column,
affordances.starts.get(&row.id),
affordances.resumes.get(&row.id),
) {
(Column::Claimed, _, Some(inputs)) => {
continue_row(ui, model, state, lernie, inputs.clone());
}
(Column::Ready | Column::Gated, Some(inputs), _) => {
ready_row(ui, model, state, lernie, inputs.clone());
}
_ => read_row(ui, model, state, lernie, row),
}
ui.indent(("board-facts", row.id.as_str()), |ui| {
for gate in &row.gates {
ui.weak(format!("⊣ gate {}: {}", gate.id, gate.title))
.on_hover_text(
"This ball cannot close until that one does. Closing it is what mints \
the gate — nothing here releases it.",
);
}
for drone in &row.drones {
ui.weak(format!("↳ {}", drone.name)).on_hover_text(
"The conversation working this ball — the same object the conversation list \
above shows. Its goal names this ball.",
);
}
if let Some(figure) = &row.spend {
ui.horizontal(|ui| {
ui.weak("spend:");
crate::spend::render(ui, figure);
});
}
if let Some(figure) = &row.rollup {
ui.horizontal(|ui| {
ui.weak("epic:");
crate::spend::render(ui, figure);
})
.response
.on_hover_text(
"This ball plus its live subtree, summed across every workspace those balls \
are claimed in. A closed child leaves the live set, and its spend leaves \
this figure with it.",
);
}
});
}
fn read_row(
ui: &mut egui::Ui,
model: &mut AppModel,
state: &mut ShellState,
lernie: &Cli,
row: &crate::board::BoardRow,
) {
let label = ui
.add(
egui::Label::new(egui::RichText::new(format!("{}: {}", row.id, row.title)).weak())
.sense(egui::Sense::click()),
)
.on_hover_text(
"Right-click for this ball's actions — Close and Release. Each is also \
a key or a line: (c), (r), `/close [id]`, `/release [id]`.",
);
let seat = Seat::BallRow {
state: row.state,
assign_to: model.focused_ws_name(),
};
let target = Target::Ball(BallRef {
project: row.project.clone(),
id: row.id.clone(),
owner: row.claimant.clone().unwrap_or_default(),
});
super::super::menus::attach(&label, seat, &target, model, state, lernie);
}