use crate::reply::roster::WsRow;
use crate::ui::{Aim, Chunk, Model, theme};
pub const NOT_ANSWERED: &str = "waiting to hear from this channel";
pub const NO_WALLS: &str = "this engine holds no workspace";
pub const NO_NAME_HERE: &str = "no entry here names it, so nothing typed here can address it";
pub const HEADING: &str = "channels";
pub fn aimable(model: &Model) -> Vec<Aim> {
let mut rows = Vec::new();
for chunk in &model.roster {
for row in ordered(&chunk.walls) {
if let Some(address) = chunk.channel.address(&row) {
rows.push(Aim {
channel: chunk.channel.name.clone(),
address,
});
}
}
}
rows
}
pub fn render(ui: &mut egui::Ui, model: &mut Model) {
ui.heading(crate::ui::keys::heading(
HEADING,
model.focus == crate::ui::Pane::Roster,
));
let reveal = model.revealing(crate::ui::keys::Pane::Roster);
egui::ScrollArea::vertical()
.id_salt(HEADING)
.auto_shrink(false)
.show(ui, |ui| {
for chunk in model.roster.clone() {
section(ui, model, &chunk, reveal);
}
});
}
fn section(ui: &mut egui::Ui, model: &mut Model, chunk: &Chunk, reveal: bool) {
ui.separator();
ui.label(header(chunk));
if let crate::ui::Held::Unheld(why) = &chunk.held {
ui.colored_label(theme::NOTICE, why);
}
for note in [chunk.stale.as_ref(), chunk.growth.as_ref()]
.into_iter()
.flatten()
{
ui.colored_label(theme::tone_ink(&crate::reply::convs::Tone::Weak), note);
}
if chunk.walls.is_empty() {
match &chunk.held {
crate::ui::Held::Unheard => {
ui.label(NOT_ANSWERED);
}
crate::ui::Held::Heard => {
ui.label(NO_WALLS);
}
crate::ui::Held::Unheld(_) => {}
}
return;
}
for row in ordered(&chunk.walls) {
wall(ui, model, chunk, &row, reveal);
}
}
pub fn header(chunk: &Chunk) -> String {
let named = match &chunk.channel.named_there {
Some(there) if *there != chunk.channel.name => {
format!("{} (named {:?} on its host)", chunk.channel.name, there)
}
_ => chunk.channel.name.clone(),
};
match &chunk.channel.dials {
Some(at) => format!("{named} — {at}"),
None => named,
}
}
fn wall(ui: &mut egui::Ui, model: &mut Model, chunk: &Chunk, row: &WsRow, reveal: bool) {
let Some(address) = chunk.channel.address(row) else {
ui.label(format!("{} — {NO_NAME_HERE}", line(row)));
return;
};
let aimed = model.aimed_at(&chunk.channel.name, Some(&address));
let seat = ui.selectable_label(aimed, line(row));
crate::ui::act::tag(&seat, &[crate::verbs::CONVERSATIONS.word]);
if aimed && reveal {
seat.scroll_to_me(None);
}
if seat.clicked() {
model.aim_at(&chunk.channel.name.clone(), &address);
}
if !aimed || model.enroll.is_some() || model.tuning.is_some() {
return;
}
if ui.button(crate::ui::enroll::OPEN).clicked() {
model.begin_enrollment();
}
let tune = ui.button(crate::ui::tuning::OPEN);
crate::ui::act::tag(&tune, &[crate::verbs::ROLES.word]);
if tune.clicked() {
model.begin_tuning();
}
}
pub fn ordered(walls: &[WsRow]) -> Vec<WsRow> {
let mut rows = walls.to_vec();
rows.sort_by(|a, b| {
(a.pinned.unwrap_or(u64::MAX), &a.workspace)
.cmp(&(b.pinned.unwrap_or(u64::MAX), &b.workspace))
});
rows
}
pub fn line(row: &WsRow) -> String {
let mut said = vec![format!(
"{} ({}) {} conversations",
row.workspace,
row.kind.label(),
row.agents
)];
if row.attention > 0 {
said.push(format!("{} waiting", row.attention));
}
if row.running {
said.push("running".to_owned());
}
said.join(" ")
}
#[cfg(test)]
mod tests;