#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Room {
pub width: u16,
pub lead: u16,
pub padding: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OnScreen {
pub first: usize,
pub last: usize,
pub cursor: usize,
pub total: usize,
}
impl OnScreen {
pub fn label(&self, compact: bool) -> String {
if compact {
format!("col {}/{}", self.cursor, self.total)
} else {
format!("col {} of {}", self.cursor, self.total)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColumnMove {
#[cfg(test)]
StepLeft,
#[cfg(test)]
StepRight,
First,
PageLeft,
PageRight,
Last,
Reveal(usize),
Keep(usize),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CursorMove {
Left,
Right,
First,
Last,
PageLeft,
PageRight,
}
pub fn whole_from(
start: usize,
count: usize,
room: Room,
width: &mut impl FnMut(usize) -> Option<u16>,
) -> Option<usize> {
let mut used = room.lead;
let mut whole = 0;
for i in start..count {
let w = width(i)?;
if used.saturating_add(w) > room.width {
break;
}
used = used.saturating_add(w).saturating_add(room.padding);
whole += 1;
}
Some(whole)
}
pub fn start_ending_at(
end: usize,
room: Room,
width: &mut impl FnMut(usize) -> Option<u16>,
) -> Option<usize> {
let mut used = room.lead.saturating_add(width(end)?);
let mut start = end;
while start > 0 {
let next = used
.saturating_add(room.padding)
.saturating_add(width(start - 1)?);
if next > room.width {
break;
}
used = next;
start -= 1;
}
Some(start)
}
pub fn plan(
mv: ColumnMove,
current: usize,
count: usize,
room: Room,
mut width: impl FnMut(usize) -> Option<u16>,
) -> Option<usize> {
let Some(last) = count.checked_sub(1) else {
return Some(0);
};
let current = current.min(last);
match mv {
#[cfg(test)]
ColumnMove::StepLeft => Some(current.saturating_sub(1)),
#[cfg(test)]
ColumnMove::StepRight => Some((current + 1).min(last)),
ColumnMove::First => Some(0),
ColumnMove::PageLeft if current == 0 => Some(0),
ColumnMove::PageLeft => start_ending_at(current - 1, room, &mut width),
ColumnMove::PageRight if current == last => Some(current),
ColumnMove::PageRight => {
let whole = whole_from(current, count, room, &mut width)?;
if current + whole >= count {
return Some(current);
}
let next = current + whole.max(1);
let last_page = start_ending_at(last, room, &mut width)?;
Some(next.min(last_page).max(current + 1))
}
ColumnMove::Last => start_ending_at(last, room, &mut width),
ColumnMove::Reveal(column) => {
let column = column.min(last);
if column >= current && column - current < whole_from(current, count, room, &mut width)?
{
return Some(current);
}
Some(column.min(start_ending_at(last, room, &mut width)?))
}
ColumnMove::Keep(column) => {
let column = column.min(last);
if column <= current {
return Some(column);
}
if column - current < whole_from(current, column + 1, room, &mut width)? {
return Some(current);
}
start_ending_at(column, room, &mut width)
}
}
}
#[cfg(test)]
mod tests;