use crate::breath::{Phase, PhaseState};
const RAMP: [char; 8] = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
pub fn fullness(state: PhaseState) -> f32 {
match state.phase {
Phase::Inhale => state.progress,
Phase::HoldIn => 1.0,
Phase::Exhale => 1.0 - state.progress,
Phase::HoldOut => 0.0,
Phase::Still => 0.5,
}
}
pub fn block_for(fullness: f32) -> char {
let last = RAMP.len() - 1;
let index = (fullness.clamp(0.0, 1.0) * last as f32).round() as usize;
RAMP[index.min(last)]
}
pub fn breath_title(state: PhaseState) -> String {
format!(
"{} meditate · {}",
block_for(fullness(state)),
state.phase.label()
)
}
pub fn set_sequence(title: &str) -> String {
format!("\x1b]0;{title}\x07")
}
pub const PUSH_TITLE: &str = "\x1b[22;0t";
pub const POP_TITLE: &str = "\x1b[23;0t";
pub fn notification(message: &str) -> String {
format!("\x1b]9;{message}\x07")
}
#[cfg(test)]
mod tests {
use super::*;
fn state(phase: Phase, progress: f32) -> PhaseState {
PhaseState {
phase,
progress,
breath_count: 0,
}
}
#[test]
fn fullness_rises_on_inhale_and_falls_on_exhale() {
assert_eq!(fullness(state(Phase::Inhale, 0.0)), 0.0);
assert_eq!(fullness(state(Phase::Inhale, 1.0)), 1.0);
assert_eq!(fullness(state(Phase::HoldIn, 0.5)), 1.0);
assert_eq!(fullness(state(Phase::Exhale, 0.0)), 1.0);
assert_eq!(fullness(state(Phase::Exhale, 1.0)), 0.0);
assert_eq!(fullness(state(Phase::HoldOut, 0.5)), 0.0);
assert_eq!(fullness(state(Phase::Still, 0.0)), 0.5);
}
#[test]
fn block_spans_the_ramp_and_clamps() {
assert_eq!(block_for(0.0), '▁');
assert_eq!(block_for(1.0), '█');
assert_eq!(block_for(-1.0), '▁');
assert_eq!(block_for(2.0), '█');
assert_eq!(block_for(0.5), '▅');
}
#[test]
fn title_reads_block_then_phase() {
assert_eq!(
breath_title(state(Phase::Inhale, 0.0)),
"▁ meditate · inhale"
);
assert_eq!(breath_title(state(Phase::HoldIn, 0.0)), "█ meditate · hold");
}
#[test]
fn set_sequence_wraps_in_osc() {
assert_eq!(set_sequence("hi"), "\x1b]0;hi\x07");
}
}