use std::collections::HashSet;
use super::{AutoExpand, Entry, Fold, Row, Tone, Transcript, rows, spine};
use crate::jsonview::{GLYPH_COLLAPSED, GLYPH_EXPANDED, toggle_path};
use crate::rail::Rail;
use crate::theme;
const NO_FOLD_MARK: &str = "·";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Reading {
pub speaker: String,
pub raw: bool,
pub auto: AutoExpand,
}
pub fn render(
ui: &mut egui::Ui,
transcript: &Transcript,
reading: &Reading,
folds: &mut HashSet<String>,
rail: &Rail,
selected: &mut Option<usize>,
) -> Option<String> {
let mut follow = None;
crate::tail::scroll(ui, true, |ui| {
if transcript.entries.is_empty() {
ui.label("(no messages yet)");
return;
}
if reading.raw {
for entry in &transcript.entries {
render_raw(ui, entry);
}
return;
}
let rules = rail.rules();
let cohorts = crate::rail::cohorts(rail);
for row in rows(transcript, &reading.speaker, reading.auto, folds) {
if let Some(&index) = rules.get(&row.key) {
if let Some(notch) = rail.notches.get(index).filter(|n| n.commit.is_some()) {
spine::rule(ui, index, notch, selected);
}
for born_here in cohorts.iter().filter(|c| c.notch == index) {
if let Some(id) = spine::cohort(ui, born_here) {
follow = Some(id);
}
}
}
render_row(ui, &row, folds);
}
});
follow
}
fn render_raw(ui: &mut egui::Ui, entry: &Entry) {
whole(ui, |ui| {
ui.monospace(&entry.name);
ui.monospace(String::from_utf8_lossy(&entry.raw).to_string());
});
}
fn whole<R>(ui: &mut egui::Ui, body: impl FnOnce(&mut egui::Ui) -> R) -> R {
ui.scope(|ui| {
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Wrap);
body(ui)
})
.inner
}
fn render_row(ui: &mut egui::Ui, row: &Row, folds: &mut HashSet<String>) {
let inline = row.fold == Fold::Steps || row.body.is_empty() || !row.expanded;
let abridged = inline && !row.body.is_empty();
ui.horizontal(|ui| {
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate);
theme::role_stripe(ui, row.role);
toggle(ui, row, folds);
let label = paint(ui, row.tone, &row.prefix);
if !row.hover.is_empty() {
label.on_hover_text(&row.hover);
}
if inline && !row.preview.is_empty() {
preview(ui, &row.preview, abridged);
}
});
if !inline {
whole(ui, |ui| ui.label(&row.body));
}
}
fn preview(ui: &mut egui::Ui, text: &str, abridged: bool) {
if !abridged {
whole(ui, |ui| ui.label(text));
return;
}
ui.scope(|ui| {
ui.set_opacity(theme::tone_solidity(Tone::Weak));
ui.label(text);
});
}
fn toggle(ui: &mut egui::Ui, row: &Row, folds: &mut HashSet<String>) {
if row.fold == Fold::Payload && row.body.is_empty() {
ui.monospace(NO_FOLD_MARK);
return;
}
let glyph = if row.expanded {
GLYPH_EXPANDED
} else {
GLYPH_COLLAPSED
};
let hit = ui
.add(egui::Label::new(egui::RichText::new(glyph).monospace()).sense(egui::Sense::click()))
.on_hover_text(
"Fold this row open or shut — open shows its whole text, shut shows the \
first line. The two checkboxes above set which kinds open by default. No key \
of its own: Tab reaches it, Space presses it.",
);
if hit.clicked() {
toggle_path(folds, &row.key);
}
}
fn paint(ui: &mut egui::Ui, tone: Tone, text: &str) -> egui::Response {
match tone {
Tone::Plain => ui.label(text),
Tone::Weak => ui.weak(text),
Tone::Good => ui.colored_label(theme::HYDRA, text),
Tone::Bad => ui.colored_label(theme::ICHOR, text),
Tone::Live => ui.colored_label(theme::SPECTRE, text),
Tone::InFlight => {
let time = ui.ctx().input(|i| i.time);
ui.ctx().request_repaint_after(theme::PULSE_REPAINT_DELAY);
ui.colored_label(theme::pulse(theme::SPECTRE, time), text)
}
}
}