use crate::AppModel;
use crate::cli_outbound::Cli;
use crate::nav::convs::{ConvRow, age_label};
use crate::nav::menu::Seat;
use crate::theme;
use std::path::PathBuf;
use super::ShellState;
use super::menus::Target;
pub(super) struct RowCtx {
ws: PathBuf,
selected: Option<String>,
named: bool,
}
impl RowCtx {
pub(super) fn of(model: &mut AppModel, ws: PathBuf) -> Self {
let named =
crate::nav::tabs::is_named(&super::chrome::ws_rows(model), &model.snap.ws_name(&ws));
Self {
selected: model.focused_agent_id(),
named,
ws,
}
}
}
fn flight_hue(ui: &egui::Ui, row: &ConvRow) -> Option<egui::Color32> {
let (_, hue, _) = theme::flight_badge(row.flight?);
let time = ui.ctx().input(|i| i.time);
ui.ctx().request_repaint_after(theme::PULSE_REPAINT_DELAY);
Some(theme::pulse(hue, time))
}
pub(super) fn conversation_row(
ui: &mut egui::Ui,
model: &mut AppModel,
state: &mut ShellState,
lernie: &Cli,
row: &ConvRow,
ctx: &RowCtx,
) {
ui.scope(|ui| {
ui.set_opacity(theme::tone_solidity(row.tone));
row_body(ui, model, state, lernie, row, ctx);
});
}
fn row_body(
ui: &mut egui::Ui,
model: &mut AppModel,
state: &mut ShellState,
lernie: &Cli,
row: &ConvRow,
ctx: &RowCtx,
) {
let ws = ctx.ws.as_path();
ui.horizontal(|ui| {
elbow(ui, row.depth);
state_cell(ui, row);
let pulsing = flight_hue(ui, row);
let selected = ctx.selected.as_deref() == Some(row.root_id.as_str());
let title = match pulsing {
Some(hue) => egui::RichText::new(row.display_name()).color(hue),
None => egui::RichText::new(row.display_name()),
};
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if row.attention > 0 {
ui.colored_label(theme::BRAZEN, "⚑")
.on_hover_text(theme::ROW_ATTENTION);
}
if let Some(ball) = &row.ball {
super::conv_ball::row_badge(ui, ball);
}
if row.uncertain {
ui.colored_label(theme::SIGIL, "?")
.on_hover_text(theme::STATE_UNCERTAIN);
}
if let Some(check) = &row.verdict {
let (glyph, hue, says) = theme::verdict_badge(check.verdict);
ui.colored_label(hue, glyph).on_hover_text(format!(
"{says}\n{}\nchecked at {} by {}",
check.reason, check.sha, check.model
));
}
subagent_field(ui, model, state, ws, row);
ui.weak(age_label(row.age_secs));
if let (Some(hue), Some(class)) = (pulsing, row.flight) {
let (mark, _, says) = theme::flight_badge(class);
ui.colored_label(hue, mark).on_hover_text(says);
}
ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| {
let mut label = ui.selectable_label(selected, title).on_hover_text(format!(
"{} — open this conversation: the centre shows its transcript and \
the composer below aims at it. Right-click for Stop / Flush \
without leaving the one you are reading. ↑ / ↓ walks the list \
onto it (Ctrl+↑ / Ctrl+↓ from inside the box); → unfolds its \
subagents, ← folds them away and, on a child, pages up to its \
parent.",
row.root_id
));
if row.name_display_only {
label = label.on_hover_text(theme::NAME_DISPLAY_ONLY);
}
if label.clicked() {
super::focus::conversation(model, state, ws, &row.root_id);
}
let seat = Seat::ConversationRow {
stoppable: row.stoppable,
has_children: row.stop_children,
named: ctx.named,
};
let target = Target::Conversation {
ws: ctx.ws.clone(),
agent: row.root_id.clone(),
};
super::menus::attach(&label, seat, &target, model, state, lernie);
let subtitle = row.subtitle();
if !subtitle.is_empty() {
ui.weak(subtitle);
}
});
});
});
}
mod cells;
use cells::{elbow, state_cell, subagent_field};