Fix: Propagate user exit signal to break animation loop

**CRITICAL BUG FIX**: When using -l (loop), the outer loop in main.rs
was restarting the animation even after user pressed exit keys.

Changes:
- render() now returns Result<bool> instead of Result<()>
- Returns true when user presses exit key (q/ESC/Ctrl+C)
- Returns false when animation completes naturally
- main.rs checks return value and breaks loop on user exit

This fixes the infinite loop issue where pressing q/ESC/Ctrl+C
had no effect when using the -l flag.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-09 07:08:24 +01:00
parent 946289544d
commit 12793893c3
3 changed files with 15 additions and 11 deletions

View File

@@ -43,7 +43,7 @@ impl AnimationEngine {
self self
} }
pub async fn run(&self, terminal: &mut TerminalManager) -> Result<()> { pub async fn run(&self, terminal: &mut TerminalManager) -> Result<bool> {
let renderer = renderer::Renderer::new( let renderer = renderer::Renderer::new(
&self.ascii_art, &self.ascii_art,
self.duration_ms, self.duration_ms,

View File

@@ -33,7 +33,7 @@ impl<'a> Renderer<'a> {
} }
} }
pub async fn render(&self, terminal: &mut TerminalManager) -> Result<()> { pub async fn render(&self, terminal: &mut TerminalManager) -> Result<bool> {
let mut timeline = Timeline::new(self.timeline.duration_ms(), self.timeline.fps()); let mut timeline = Timeline::new(self.timeline.duration_ms(), self.timeline.fps());
timeline.start(); timeline.start();
@@ -67,7 +67,7 @@ impl<'a> Renderer<'a> {
loop { loop {
// Check for exit FIRST // Check for exit FIRST
if should_exit.load(Ordering::Relaxed) { if should_exit.load(Ordering::Relaxed) {
break; return Ok(true); // User requested exit
} }
let frame_start = std::time::Instant::now(); let frame_start = std::time::Instant::now();
@@ -78,7 +78,7 @@ impl<'a> Renderer<'a> {
// Check again before rendering // Check again before rendering
if should_exit.load(Ordering::Relaxed) { if should_exit.load(Ordering::Relaxed) {
break; return Ok(true); // User requested exit
} }
// Apply effect // Apply effect
@@ -93,7 +93,7 @@ impl<'a> Renderer<'a> {
// Check before terminal operations // Check before terminal operations
if should_exit.load(Ordering::Relaxed) { if should_exit.load(Ordering::Relaxed) {
break; return Ok(true); // User requested exit
} }
// Render to terminal // Render to terminal
@@ -129,12 +129,12 @@ impl<'a> Renderer<'a> {
// Check if user wants to exit // Check if user wants to exit
if should_exit.load(Ordering::Relaxed) { if should_exit.load(Ordering::Relaxed) {
break; return Ok(true); // User requested exit
} }
// Check if animation is complete before advancing // Check if animation is complete before advancing
if timeline.is_complete() { if timeline.is_complete() {
break; return Ok(false); // Animation completed naturally
} }
// Advance to next frame and wait // Advance to next frame and wait
@@ -150,7 +150,7 @@ impl<'a> Renderer<'a> {
while remaining > Duration::ZERO { while remaining > Duration::ZERO {
if should_exit.load(Ordering::Relaxed) { if should_exit.load(Ordering::Relaxed) {
break; return Ok(true); // User requested exit during sleep
} }
let sleep_time = remaining.min(chunk_duration); let sleep_time = remaining.min(chunk_duration);
sleep(sleep_time).await; sleep(sleep_time).await;
@@ -158,8 +158,6 @@ impl<'a> Renderer<'a> {
} }
} }
} }
Ok(())
} }
fn apply_colors(&self, text: &str, progress: f64) -> String { fn apply_colors(&self, text: &str, progress: f64) -> String {

View File

@@ -61,8 +61,14 @@ async fn run_piglet(args: PigletCli) -> Result<()> {
// Run animation // Run animation
loop { loop {
animation_engine.run(&mut terminal).await?; let user_exited = animation_engine.run(&mut terminal).await?;
// If user pressed exit key, stop looping
if user_exited {
break;
}
// If not looping, stop after one animation
if !args.loop_animation { if !args.loop_animation {
break; break;
} }