fix: image loading

This commit is contained in:
2026-07-04 18:24:49 +02:00
parent c31166c087
commit 96b85f0159
4 changed files with 56 additions and 33 deletions
+37 -19
View File
@@ -336,6 +336,7 @@ impl GraphicsCommand {
}
// Decode base64 payload
log::debug!("Parsing payload: len={}, content={:?}", payload_part.len(), std::str::from_utf8(payload_part).ok());
if !payload_part.is_empty() {
if let Ok(payload_str) = std::str::from_utf8(payload_part) {
if let Ok(decoded) = base64_decode(payload_str) {
@@ -449,7 +450,7 @@ pub fn decode_gif(
looping: true,
total_duration_ms,
state: AnimationState::Running,
loops_remaining: None,
loops_remaining: DEFAULT_ANIMATION_LOOPS,
})
} else {
None
@@ -628,7 +629,7 @@ pub fn decode_webm(
looping: true,
total_duration_ms,
state: AnimationState::Running,
loops_remaining: None,
loops_remaining: DEFAULT_ANIMATION_LOOPS,
})
} else {
None
@@ -704,6 +705,8 @@ impl ImageData {
}
}
pub const DEFAULT_ANIMATION_LOOPS: Option<u32> = None; // None = infinite
/// Animation state for playback control.
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub enum AnimationState {
@@ -1162,7 +1165,7 @@ impl ImageStorage {
looping: true,
total_duration_ms: 100,
state: AnimationState::Loading,
loops_remaining: None,
loops_remaining: DEFAULT_ANIMATION_LOOPS,
});
}
@@ -1359,6 +1362,7 @@ impl ImageStorage {
log::debug!("Animation {} running ({} frames)", id, anim.frames.len());
// Reset frame start when starting animation
anim.frame_start = None;
anim.looping = true;
AnimationState::Running
}
_ => anim.state.clone(),
@@ -1754,21 +1758,31 @@ impl ImageStorage {
if anim.looping {
// Check loop count
if let Some(ref mut loops) = anim.loops_remaining {
if *loops > 0 {
*loops -= 1;
anim.current_frame = 0;
} else {
// No more loops, stop
anim.state = AnimationState::Stopped;
continue;
}
} else {
// Infinite looping
anim.current_frame = 0;
}
}
// else: stay on last frame
} else {
if *loops > 0 {
log::debug!("Animation {} looping, {} loops remaining", id, *loops - 1);
*loops -= 1;
anim.current_frame = 0;
} else {
log::debug!("Animation {} stopped: no more loops", id);
// No more loops, stop
anim.state = AnimationState::Stopped;
continue;
}
} else {
log::debug!("Animation {} looping indefinitely", id);
// Infinite looping
anim.current_frame = 0;
}
}
log::debug!("Animation {} reached end, looping={}", id, anim.looping);
if !anim.looping {
log::debug!("Animation {} stopping (looping=false)", id);
}
// else: stay on last frame
} else {
anim.current_frame = next_frame;
}
@@ -1811,8 +1825,12 @@ impl ImageStorage {
/// when using the STANDARD_NO_PAD engine with lenient decoding.
fn base64_decode(input: &str) -> Result<Vec<u8>, GraphicsError> {
// Use standard base64 with lenient decoding (ignores whitespace, handles missing padding)
let mut input = input.to_string();
while input.len() % 4 != 0 {
input.push('=');
}
base64::engine::general_purpose::STANDARD
.decode(input.as_bytes())
.decode(&input)
.map_err(|_| GraphicsError::Base64DecodeFailed)
}
+3
View File
@@ -103,6 +103,7 @@ impl ImageRenderer {
/// Upload an image to the GPU, creating or updating its texture.
pub fn upload_image(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, image: &ImageData) {
log::debug!("upload_image: id={}, width={}, height={}, data_len={}", image.id, image.width, image.height, image.data.len());
// Get current frame data (handles animation frames automatically)
let data = image.current_frame_data();
@@ -231,6 +232,7 @@ impl ImageRenderer {
pub fn sync_images(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, storage: &mut ImageStorage) {
// Update animations and get list of changed image IDs
let changed_ids = storage.update_animations();
log::debug!("Sync images: changed_ids={:?}, dirty={}", changed_ids, storage.dirty);
// Re-upload frames that changed due to animation
for id in &changed_ids {
@@ -294,6 +296,7 @@ impl ImageRenderer {
// Image spans from visible_row to visible_row + placement.rows
let image_bottom = visible_row + placement.rows as isize;
if image_bottom < 0 || visible_row >= visible_rows as isize {
log::debug!("Image {} culled: visible_row={}, image_bottom={}, visible_rows={}", placement.image_id, visible_row, image_bottom, visible_rows);
continue; // Image is completely off-screen
}
+9 -6
View File
@@ -2023,14 +2023,17 @@ impl App {
});
let has_selection = tab.panes.values().any(|p| p.selection.is_some());
if !has_dirty_content && !self.needs_redraw && self.edge_glows.is_empty() && !fade_in_progress && !has_selection && !has_pending_redraw {
let image_animation_in_progress = tab.panes.values().any(|p| {
p.terminal.image_storage.has_animations()
});
needs_another_frame = image_animation_in_progress;
return needs_another_frame;
// Check if any images have running animations
let image_animation_in_progress = tab.panes.values().any(|p| {
p.terminal.image_storage.has_animations()
});
if !has_dirty_content && !self.needs_redraw && self.edge_glows.is_empty() && !fade_in_progress && !has_selection && !has_pending_redraw && !image_animation_in_progress {
return false;
}
needs_another_frame = image_animation_in_progress;
// First pass: sync images and calculate dim factors (needs mutable access)
let mut dim_factors: Vec<(PaneId, f32)> = Vec::new();
for (pane_id, _) in &geometries {
+7 -8
View File
@@ -2747,10 +2747,9 @@ impl Terminal {
if !placement.suppress_cursor_move
&& !placement.virtual_placement
{
// Move cursor down by (rows - 1) since we're already on the first row
// Then set cursor to the column after the image
let new_row =
self.cursor_row + placement.rows.saturating_sub(1);
// Move cursor to the right and down by the image dimensions
self.cursor_col += placement.cols;
let new_row = self.cursor_row + placement.rows;
if new_row >= self.rows {
// Need to scroll
let scroll_amount = new_row - self.rows + 1;
@@ -2759,11 +2758,11 @@ impl Terminal {
} else {
self.cursor_row = new_row;
}
// Move cursor to after the image (or stay at column 0 of next line)
// Per protocol, cursor ends at the last row of the image
// If cursor is now beyond the right edge, it will be handled by the normal
// cursor movement logic (wrapping/scrolling) if applicable.
log::debug!(
"Cursor moved after image placement: row={} (moved {} rows)",
self.cursor_row, placement.rows.saturating_sub(1)
"Cursor moved after image placement: col={}, row={} (moved {}x{} cells)",
self.cursor_col, self.cursor_row, placement.cols, placement.rows
);
}
}