use std::path::Path;
use std::time::{Duration, Instant};
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
use crate::config::LayoutOverlay;
use crate::layout::setup::{ContentMarqueeState, MainMode, PanelFocus, TuiRow, ViewData};
const MARQUEE_STEP: Duration = Duration::from_millis(110);
const MARQUEE_PAD: &str = " ";
pub struct MarqueeTickCtx<'a> {
pub focus: PanelFocus,
pub main_mode: MainMode,
pub viewer_fullscreen: bool,
pub view: &'a ViewData,
pub layout: &'a LayoutOverlay,
pub term_width: u16,
pub now: Instant,
}
pub struct ContentMarqueeTick<'a> {
pub all_rows: Option<&'a [TuiRow]>,
pub dir_to_ublx: Option<&'a Path>,
pub content_selected: Option<usize>,
}
#[must_use]
pub fn left_pane_inner_width_cols(term_width: u16, layout: &LayoutOverlay) -> usize {
let w = (u32::from(term_width) * u32::from(layout.left_pct) / 100).max(1);
w.saturating_sub(2) as usize
}
#[must_use]
pub fn middle_pane_inner_width_cols(term_width: u16, layout: &LayoutOverlay) -> usize {
let w = (u32::from(term_width) * u32::from(layout.middle_pct) / 100).max(1);
w.saturating_sub(2) as usize
}
#[must_use]
pub fn row_label_for_middle(
main_mode: MainMode,
view: &ViewData,
all_rows: Option<&[TuiRow]>,
dir_to_ublx: Option<&Path>,
idx: usize,
) -> Option<String> {
let row = view.row_at(idx, all_rows)?;
let (path, cat, _) = row;
match main_mode {
MainMode::Snapshot => Some(crate::render::panes::snapshot_mode::contents_display_label(
path.as_str(),
cat.as_str(),
dir_to_ublx,
)),
_ => Some(path.clone()),
}
}
#[must_use]
pub fn visible_line(text: &str, max_cols: usize, char_offset: usize) -> String {
if max_cols == 0 {
return String::new();
}
if UnicodeWidthStr::width(text) <= max_cols {
return text.to_string();
}
let cycle: String = text.chars().chain(MARQUEE_PAD.chars()).collect();
let cyc_len = cycle.chars().count();
if cyc_len == 0 {
return String::new();
}
let chars: Vec<char> = cycle.chars().collect();
let mut off = char_offset % cyc_len;
let mut out = String::new();
let mut w = 0usize;
let mut steps = 0usize;
while w < max_cols && steps < cyc_len * 3 {
let ch = chars[off % cyc_len];
let cw = UnicodeWidthChar::width(ch).unwrap_or(0);
if cw > 0 && w + cw > max_cols {
break;
}
out.push(ch);
w += cw;
off += 1;
steps += 1;
}
out
}
fn cycle_char_len(text: &str) -> usize {
text.chars().count() + MARQUEE_PAD.chars().count()
}
pub fn tick_content_marquee(
marquee: &mut ContentMarqueeState,
ctx: &MarqueeTickCtx<'_>,
content: &ContentMarqueeTick<'_>,
) {
if ctx.viewer_fullscreen
|| ctx.main_mode == MainMode::Settings
|| ctx.view.content_len == 0
|| !matches!(
ctx.main_mode,
MainMode::Snapshot | MainMode::Delta | MainMode::Duplicates | MainMode::Lenses
)
{
marquee.reset();
return;
}
if !matches!(ctx.focus, PanelFocus::Contents) {
marquee.reset();
return;
}
let Some(global_idx) = content.content_selected else {
marquee.reset();
return;
};
let global_idx = global_idx.min(ctx.view.content_len.saturating_sub(1));
let max_cols = middle_pane_inner_width_cols(ctx.term_width, ctx.layout);
let Some(label) = row_label_for_middle(
ctx.main_mode,
ctx.view,
content.all_rows,
content.dir_to_ublx,
global_idx,
) else {
marquee.reset();
return;
};
if UnicodeWidthStr::width(label.as_str()) <= max_cols {
marquee.reset();
return;
}
let key = (global_idx, label.clone());
if marquee.anchor.as_ref() != Some(&key) {
marquee.anchor = Some(key);
marquee.offset = 0;
marquee.last_advance = Some(ctx.now);
return;
}
let cyc = cycle_char_len(label.as_str()).max(1);
let last = marquee.last_advance.get_or_insert(ctx.now);
if ctx.now.duration_since(*last) >= MARQUEE_STEP {
marquee.offset = (marquee.offset + 1) % cyc;
*last = ctx.now;
}
}
pub fn tick_category_marquee_dup_lens(
marquee: &mut ContentMarqueeState,
ctx: &MarqueeTickCtx<'_>,
category_selected: Option<usize>,
) {
if ctx.viewer_fullscreen || !matches!(ctx.main_mode, MainMode::Duplicates | MainMode::Lenses) {
marquee.reset();
return;
}
if ctx.view.filtered_categories.is_empty() {
marquee.reset();
return;
}
if !matches!(ctx.focus, PanelFocus::Categories) {
marquee.reset();
return;
}
let Some(global_idx) = category_selected else {
marquee.reset();
return;
};
let global_idx = global_idx.min(ctx.view.filtered_categories.len().saturating_sub(1));
let max_cols = left_pane_inner_width_cols(ctx.term_width, ctx.layout);
let label = ctx.view.filtered_categories[global_idx].as_str();
if UnicodeWidthStr::width(label) <= max_cols {
marquee.reset();
return;
}
let key = (global_idx, label.to_string());
if marquee.anchor.as_ref() != Some(&key) {
marquee.anchor = Some(key);
marquee.offset = 0;
marquee.last_advance = Some(ctx.now);
return;
}
let cyc = cycle_char_len(label).max(1);
let last = marquee.last_advance.get_or_insert(ctx.now);
if ctx.now.duration_since(*last) >= MARQUEE_STEP {
marquee.offset = (marquee.offset + 1) % cyc;
*last = ctx.now;
}
}