fix: scroll tile bar to keep selected tile visible
When there are more tiles than fit in the available width, the tile bar now auto-scrolls to ensure the selected tile is always visible. Overflow indicators (◀ ▶) show when tiles exist beyond the visible area. Scroll offset is computed fresh each frame from tile_cat_idx. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -52,16 +52,71 @@ impl<'a> Widget for TileBar<'a> {
|
||||
None
|
||||
};
|
||||
|
||||
let mut x = area.x + 1;
|
||||
buf.set_string(area.x, area.y, " Tiles: ", Style::default().fg(Color::Gray));
|
||||
x += 8;
|
||||
let prefix = " Tiles: ";
|
||||
let prefix_w = prefix.width() as u16;
|
||||
buf.set_string(area.x, area.y, prefix, Style::default().fg(Color::Gray));
|
||||
|
||||
let cat_names: Vec<&str> = self.model.category_names();
|
||||
for (i, cat_name) in cat_names.iter().enumerate() {
|
||||
let (axis_symbol, axis_color) = TileBar::axis_display(view.axis_of(cat_name));
|
||||
let label = format!(" [{cat_name} {axis_symbol}] ");
|
||||
let is_selected = selected_cat_idx == Some(i);
|
||||
|
||||
// Compute label widths for all tiles
|
||||
let labels: Vec<String> = cat_names
|
||||
.iter()
|
||||
.map(|cat_name| {
|
||||
let (axis_symbol, _) = TileBar::axis_display(view.axis_of(cat_name));
|
||||
format!(" [{cat_name} {axis_symbol}] ")
|
||||
})
|
||||
.collect();
|
||||
let widths: Vec<u16> = labels.iter().map(|l| l.width() as u16).collect();
|
||||
|
||||
// Available space for tiles (after prefix)
|
||||
let avail = area.width.saturating_sub(prefix_w);
|
||||
|
||||
// Find the minimal starting index so the selected tile is fully visible.
|
||||
// We scroll by whole tiles: find the first tile to draw such that the
|
||||
// selected tile fits within the available width.
|
||||
let sel = selected_cat_idx.unwrap_or(0);
|
||||
let mut start = 0;
|
||||
loop {
|
||||
// Check if selected tile is visible when starting from `start`
|
||||
let mut used: u16 = 0;
|
||||
let mut sel_visible = false;
|
||||
for i in start..labels.len() {
|
||||
if used + widths[i] > avail {
|
||||
break;
|
||||
}
|
||||
used += widths[i];
|
||||
if i == sel {
|
||||
sel_visible = true;
|
||||
}
|
||||
}
|
||||
if sel_visible || start >= sel {
|
||||
break;
|
||||
}
|
||||
start += 1;
|
||||
}
|
||||
|
||||
// Draw an overflow indicator if we scrolled past the beginning
|
||||
let mut x = area.x + prefix_w + 1;
|
||||
if start > 0 {
|
||||
buf.set_string(
|
||||
area.x + prefix_w,
|
||||
area.y,
|
||||
"◀",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
);
|
||||
x += 1;
|
||||
}
|
||||
|
||||
// Render tiles from `start`
|
||||
let mut last_drawn = start;
|
||||
for i in start..labels.len() {
|
||||
let label_w = widths[i];
|
||||
if x + label_w > area.x + area.width {
|
||||
break;
|
||||
}
|
||||
|
||||
let (_, axis_color) = TileBar::axis_display(view.axis_of(cat_names[i]));
|
||||
let is_selected = selected_cat_idx == Some(i);
|
||||
let style = if is_selected {
|
||||
Style::default()
|
||||
.fg(Color::Black)
|
||||
@ -71,12 +126,15 @@ impl<'a> Widget for TileBar<'a> {
|
||||
Style::default().fg(axis_color)
|
||||
};
|
||||
|
||||
let label_w = label.width() as u16;
|
||||
if x + label_w > area.x + area.width {
|
||||
break;
|
||||
}
|
||||
buf.set_string(x, area.y, &label, style);
|
||||
buf.set_string(x, area.y, &labels[i], style);
|
||||
x += label_w;
|
||||
last_drawn = i;
|
||||
}
|
||||
|
||||
// Draw overflow indicator if tiles remain after the visible area
|
||||
if last_drawn + 1 < labels.len() && x < area.x + area.width {
|
||||
buf.set_string(x, area.y, "▶", Style::default().fg(Color::DarkGray));
|
||||
x += 1;
|
||||
}
|
||||
|
||||
// Hint
|
||||
|
||||
Reference in New Issue
Block a user