diff --git a/src/ui/tile_bar.rs b/src/ui/tile_bar.rs index da7f433..42fe3ea 100644 --- a/src/ui/tile_bar.rs +++ b/src/ui/tile_bar.rs @@ -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 = 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 = 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