use crate::actions::DraftKey;
use crate::inboxview::{InboxEntry, header_line};
use crate::transcript::Tone;
use std::collections::HashSet;
mod recall;
pub use recall::{Caret, Recall, Step, prompts};
pub const SNAP_SECS: f64 = 0.3;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QueueRow {
pub key: String,
pub header: String,
pub role: crate::theme::Role,
pub preview: String,
pub body: String,
pub expanded: bool,
pub tone: Tone,
}
pub fn rows(
agent_id: &str,
pending: &[InboxEntry],
titles: &crate::nav::convs::Titles,
folds: &HashSet<String>,
) -> Vec<QueueRow> {
pending
.iter()
.map(|entry| {
let key = format!("inbox/{agent_id}/{}", entry.name);
QueueRow {
header: header_line(&entry.deposit, titles),
role: crate::theme::message_role(
entry.deposit.sender.as_deref().unwrap_or_default(),
entry.deposit.epitaph.is_some(),
),
preview: entry.deposit.body.lines().next().unwrap_or("").to_string(),
body: entry.deposit.body.clone(),
expanded: folds.contains(&key),
tone: tone_of(entry),
key,
}
})
.collect()
}
fn tone_of(entry: &InboxEntry) -> Tone {
if entry.in_memory() {
Tone::Weak
} else {
Tone::Plain
}
}
#[derive(Debug, Default)]
pub struct ComposerRam {
pub folds: HashSet<String>,
pub snap: SnapState,
pub recall: Recall,
pub caret: Caret,
}
#[derive(Debug, Default)]
pub struct SnapState {
target: Option<DraftKey>,
last_count: usize,
settled: f32,
anim: Option<(f64, f32)>,
}
impl SnapState {
pub fn observe(&mut self, target: &DraftKey, count: usize, now: f64) {
if self.target.as_ref() != Some(target) {
*self = Self {
target: Some(target.clone()),
last_count: count,
..Self::default()
};
return;
}
if count < self.last_count {
self.anim = Some((now, self.settled));
} else if self.anim.is_some_and(|(start, _)| now - start >= SNAP_SECS) {
self.anim = None;
}
self.last_count = count;
}
pub fn desired(&self, cap: f32, now: f64) -> f32 {
let base = self.settled;
let value = match self.anim {
Some((start, from)) => {
let t = ((now - start) / SNAP_SECS).clamp(0.0, 1.0);
let eased = 1.0 - (1.0 - t).powi(3);
let height = f64::from(from) + (f64::from(base) - f64::from(from)) * eased;
base.max(height as f32)
}
None => base,
};
value.min(cap)
}
pub fn settle(&mut self, painted: f32) {
self.settled = painted;
}
pub fn settled(&self) -> f32 {
self.settled
}
pub fn active(&self, now: f64) -> bool {
self.anim.is_some_and(|(start, _)| now - start < SNAP_SECS)
}
}
#[cfg(test)]
mod tests;