use crate::reply::rail::{Card, Notch, Rail};
use crate::reply::{convs::Tone, governing::Governing};
use crate::ui::{Model, theme};
pub const SPINE_HEAD: &str = "spine";
pub const GOVERNING_HEAD: &str = "governing config";
pub const NOT_ANSWERED_SPINE: &str =
"waiting to hear what this conversation's history is anchored to";
pub const NO_NOTCHES: &str = "no step of its history is anchored to a commit yet";
pub const NO_CARDS: &str = "nothing has been dispatched from this conversation";
pub const NOT_ANSWERED_GOVERNING: &str = "waiting to hear which config governs it";
pub const NO_FILES: &str = "its tree holds no file";
pub const ROLE_HINT: &str = "role";
pub const GOAL_HINT: &str = "what the fork is for";
pub const ROLE_SAID: &str =
"a role is a name the fork point's config declares, and this seat cannot list them yet";
pub fn render(ui: &mut egui::Ui, model: &mut Model) {
governing_half(ui, model.records.governing.clone().as_ref());
ui.separator();
let Some(spine) = model.records.rail.clone() else {
ui.label(egui::RichText::new(SPINE_HEAD).strong());
ui.label(NOT_ANSWERED_SPINE);
return;
};
draft(ui, model);
if spine.notches.is_empty() {
ui.label(NO_NOTCHES);
}
for row in &spine.notches {
notch(ui, model, row);
}
cards(ui, &spine);
}
fn governing_half(ui: &mut egui::Ui, answer: Option<&Governing>) {
let Some(config) = answer else {
ui.label(egui::RichText::new(GOVERNING_HEAD).strong());
ui.label(NOT_ANSWERED_GOVERNING);
return;
};
ui.horizontal_wrapped(|ui| {
ui.label(egui::RichText::new(GOVERNING_HEAD).strong());
ui.label(format!("{} — {}", config.label(), config.oid));
if config.files.is_empty() {
ui.label(NO_FILES);
return;
}
for path in &config.files {
ui.label(path.clone());
}
});
}
fn draft(ui: &mut egui::Ui, model: &mut Model) {
ui.horizontal_wrapped(|ui| {
ui.label(egui::RichText::new(SPINE_HEAD).strong());
ui.add(egui::TextEdit::singleline(&mut model.forking.role).hint_text(ROLE_HINT));
ui.add(egui::TextEdit::singleline(&mut model.forking.goal).hint_text(GOAL_HINT));
ui.colored_label(theme::tone_ink(&Tone::Weak), ROLE_SAID);
});
}
fn notch(ui: &mut egui::Ui, model: &mut Model, row: &Notch) {
ui.horizontal_wrapped(|ui| {
ui.label(headline(row));
if let Some(said) = seated(row) {
ui.colored_label(theme::tone_ink(&Tone::Weak), said);
}
let Some(commit) = row.commit.clone() else {
return;
};
let fire = ui.add_enabled(model.forking.ready(), egui::Button::new(forking(row)));
crate::ui::act::tag(&fire, &[crate::verbs::spine::FORK]);
if fire.clicked() {
model.post_fork(commit);
}
});
}
fn cards(ui: &mut egui::Ui, spine: &Rail) {
if spine.cards.is_empty() {
ui.label(NO_CARDS);
return;
}
for row in &spine.cards {
ui.horizontal_wrapped(|ui| {
ui.label(card(row));
if let Some(said) = &row.tail {
ui.colored_label(theme::tone_ink(&Tone::Weak), said.clone());
}
});
}
}
pub fn headline(row: &Notch) -> String {
format!("{} {} — {} tokens", row.seq, row.short(), row.budget)
}
pub fn seated(row: &Notch) -> Option<String> {
let seat = row.seat.as_ref()?;
Some(format!(
"above {} — it had read {} entries",
seat.row, seat.cut
))
}
pub fn forking(row: &Notch) -> String {
format!("fork from {}", row.short())
}
pub fn card(row: &Card) -> String {
format!(
"{} — {} — {} — {} tokens",
row.name,
row.fork,
row.state.label(),
row.tokens
)
}
#[cfg(test)]
mod tests;