use crate::base::{Point, Rect, Rgba};
use crate::render::{Attrs, Style};
use crate::theme::TokenSet;
use crate::ui::StyledCanvas;
pub(crate) const IND_ZONE: i32 = 2;
const GAP: i32 = 1;
pub(crate) struct BarItem {
pub title: String,
pub badge: Option<String>,
}
pub(crate) struct BarModel {
pub items: Vec<BarItem>,
pub active: usize,
}
#[derive(Clone)]
pub(crate) struct BarSeg {
pub index: usize,
pub x: i32,
pub w: i32,
pub title_w: i32,
}
#[derive(Clone)]
pub(crate) struct BarPlan {
pub segs: Vec<BarSeg>,
pub first: usize,
pub overflow: bool,
pub left_more: bool,
pub right_more: bool,
}
impl BarPlan {
fn empty() -> BarPlan {
BarPlan {
segs: Vec::new(),
first: 0,
overflow: false,
left_more: false,
right_more: false,
}
}
}
fn seg_w(title_w: i32, badge_w: i32) -> i32 {
1 + title_w + if badge_w > 0 { 1 + badge_w } else { 0 } + 1
}
pub(crate) fn plan_bar(m: &BarModel, prev_first: usize, avail: i32) -> BarPlan {
let n = m.items.len();
if n == 0 || avail <= 0 {
return BarPlan::empty();
}
let tw: Vec<i32> = m
.items
.iter()
.map(|i| crate::text::width(&i.title))
.collect();
let bw: Vec<i32> = m
.items
.iter()
.map(|i| i.badge.as_deref().map(crate::text::width).unwrap_or(0))
.collect();
let total: i32 = (0..n).map(|i| seg_w(tw[i], bw[i])).sum::<i32>() + GAP * (n as i32 - 1);
if total <= avail {
let mut segs = Vec::with_capacity(n);
let mut x = 0;
for i in 0..n {
let w = seg_w(tw[i], bw[i]);
segs.push(BarSeg {
index: i,
x,
w,
title_w: tw[i],
});
x += w + GAP;
}
return BarPlan {
segs,
first: 0,
overflow: false,
left_more: false,
right_more: false,
};
}
let budget = (avail - 2 * IND_ZONE).max(4);
let title_w: Vec<i32> = (0..n)
.map(|i| {
if seg_w(tw[i], bw[i]) > budget {
(budget - seg_w(0, bw[i])).max(1)
} else {
tw[i]
}
})
.collect();
let active = m.active.min(n - 1);
let run_w = |a: usize, b: usize| -> i32 {
(a..=b).map(|i| seg_w(title_w[i], bw[i])).sum::<i32>() + GAP * (b - a) as i32
};
let mut first = prev_first.min(active);
while first < active && run_w(first, active) > budget {
first += 1;
}
let mut last = active;
while last + 1 < n && run_w(first, last + 1) <= budget {
last += 1;
}
let mut segs = Vec::with_capacity(last - first + 1);
let mut x = IND_ZONE;
for i in first..=last {
let w = seg_w(title_w[i], bw[i]);
segs.push(BarSeg {
index: i,
x,
w,
title_w: title_w[i],
});
x += w + GAP;
}
BarPlan {
segs,
first,
overflow: true,
left_more: first > 0,
right_more: last < n - 1,
}
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum BarHit {
Prev,
Next,
Tab(usize),
Miss,
}
pub(crate) fn hit_bar(plan: &BarPlan, avail: i32, x: i32) -> BarHit {
if plan.overflow {
if x < IND_ZONE {
return if plan.left_more {
BarHit::Prev
} else {
BarHit::Miss
};
}
if x >= avail - IND_ZONE {
return if plan.right_more {
BarHit::Next
} else {
BarHit::Miss
};
}
}
for s in &plan.segs {
if x >= s.x && x < s.x + s.w {
return BarHit::Tab(s.index);
}
}
BarHit::Miss
}
pub(crate) struct BarInk {
pub active: Rgba,
pub idle: Rgba,
pub badge: Rgba,
pub strip: Rgba,
pub indicator: Rgba,
pub ground: Rgba,
}
pub(crate) fn ink_from(t: &TokenSet) -> BarInk {
BarInk {
active: t.text,
idle: t.text_muted,
badge: t.info,
strip: t.border_focus,
indicator: t.text_muted,
ground: t.surface,
}
}
pub(crate) fn draw_bar(
canvas: &mut dyn StyledCanvas,
rect: Rect,
m: &BarModel,
plan: &BarPlan,
ink: &BarInk,
) {
if rect.is_empty() {
return;
}
canvas.fill_styled(rect, ' ', &Style::new().fg(ink.idle).bg(ink.ground));
let limit = if plan.overflow {
rect.w - IND_ZONE
} else {
rect.w
};
if plan.overflow {
let ind = Style::new().fg(ink.indicator).bg(ink.ground);
if plan.left_more {
canvas.print_styled(Point::new(rect.x, rect.y), "‹", &ind);
}
if plan.right_more && rect.w >= 1 {
canvas.print_styled(Point::new(rect.x + rect.w - 1, rect.y), "›", &ind);
}
}
for s in &plan.segs {
if s.x >= limit {
break;
}
let item = &m.items[s.index];
let active = s.index == m.active;
let title_style = if active {
Style::new()
.fg(ink.active)
.bg(ink.ground)
.attrs(Attrs::BOLD)
} else {
Style::new().fg(ink.idle).bg(ink.ground)
};
let title_cols = s.title_w.min(limit - s.x - 1);
let title = crate::text::truncate_ellipsis(&item.title, title_cols);
canvas.print_styled(Point::new(rect.x + s.x + 1, rect.y), &title, &title_style);
if let Some(b) = &item.badge {
let bx = s.x + 1 + s.title_w + 1;
if bx < limit {
let badge = crate::text::truncate_ellipsis(b, limit - bx);
canvas.print_styled(
Point::new(rect.x + bx, rect.y),
&badge,
&Style::new().fg(ink.badge).bg(ink.ground),
);
}
}
if active && rect.h > 1 {
let strip_w = s.w.min(limit - s.x).max(0);
let strip = "▔".repeat(strip_w as usize);
canvas.print_styled(
Point::new(rect.x + s.x, rect.y + 1),
&strip,
&Style::new().fg(ink.strip).bg(ink.ground),
);
}
}
}