Fix text positioning by calculating visual width without ANSI codes

The motion effects were appearing with incorrect row offsets because
the text width calculation included ANSI color escape sequences in
the byte length, causing misaligned centering.

Changes:
- Added utils/ansi.rs module with strip_ansi() and visual_width()
- Updated renderer to use visual_width() for offset positioning
- Updated TerminalManager::print_centered() to use visual_width()
- All text positioning now correctly ignores ANSI escape sequences

This fixes the "shifted in the rows" issue where colored text was
not properly centered due to escape sequence byte counts.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-09 04:12:15 +01:00
parent 09665d3250
commit 24ca6f0262
4 changed files with 61 additions and 4 deletions

View File

@@ -1,6 +1,6 @@
use crate::animation::{easing::EasingFunction, effects::Effect, timeline::Timeline};
use crate::color::{apply, ColorEngine};
use crate::utils::{ascii::AsciiArt, terminal::TerminalManager};
use crate::utils::{ansi, ascii::AsciiArt, terminal::TerminalManager};
use anyhow::Result;
use tokio::time::sleep;
@@ -62,7 +62,7 @@ impl<'a> Renderer<'a> {
let (width, height) = terminal.get_size();
let lines: Vec<&str> = colored_text.lines().collect();
let text_height = lines.len() as i32;
let text_width = lines.iter().map(|l| l.len()).max().unwrap_or(0) as i32;
let text_width = lines.iter().map(|l| ansi::visual_width(l)).max().unwrap_or(0) as i32;
let base_x = (width as i32 - text_width) / 2;
let base_y = (height as i32 - text_height) / 2;