mirror of
https://github.com/wez/wezterm.git
synced 2024-12-24 22:01:47 +03:00
show terminal background while waiting for background image to load
Avoid a "flash" of a single black but likely overly stretched and awkwardly interpolated frame while we wait for big/animated/complex images to load and decode. For corrupt images, or images with an incorrect or typo'd filename in the config, this prevents us from punting and just showing a transparent background instead of something reasonable.
This commit is contained in:
parent
d0e9a03440
commit
27fbff4ae1
@ -32,6 +32,11 @@ As features stabilize some brief notes about them will accumulate here.
|
||||
* Don't hide mouse cursor when pressing only modifier keys. #3570
|
||||
* [PaneSelect](config/lua/keyassignment/PaneSelect.md) will now un-zoom to show
|
||||
all panes, then re-zoom after performing its action. #3573
|
||||
* Images, especially animated images, are now decoded in the background. When
|
||||
used as a background layer for the terminal, we now use the normal
|
||||
terminal background color as a placeholder until the first frame has been
|
||||
decoded. In other circumstances, you may observe a brief black frame while
|
||||
waiting for the image to decode.
|
||||
|
||||
#### New
|
||||
|
||||
|
@ -31,6 +31,12 @@ use wezterm_font::units::*;
|
||||
use wezterm_font::{FontConfiguration, GlyphInfo, LoadedFont, LoadedFontId};
|
||||
use wezterm_term::Underline;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LoadState {
|
||||
Loading,
|
||||
Loaded,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct CellMetricKey {
|
||||
pub pixel_width: u16,
|
||||
@ -383,6 +389,7 @@ struct FrameState {
|
||||
source: FrameSource,
|
||||
current_frame: DecodedFrame,
|
||||
frames: Vec<DecodedFrame>,
|
||||
load_state: LoadState,
|
||||
}
|
||||
|
||||
impl FrameState {
|
||||
@ -405,6 +412,7 @@ impl FrameState {
|
||||
height: BLACK_SIZE,
|
||||
duration: Duration::from_millis(0),
|
||||
},
|
||||
load_state: LoadState::Loading,
|
||||
}
|
||||
}
|
||||
|
||||
@ -414,6 +422,7 @@ impl FrameState {
|
||||
Ok(frame) => {
|
||||
self.frames.push(frame.clone());
|
||||
self.current_frame = frame;
|
||||
self.load_state = LoadState::Loaded;
|
||||
true
|
||||
}
|
||||
Err(TryRecvError::Empty) => false,
|
||||
@ -480,6 +489,14 @@ impl DecodedImage {
|
||||
}
|
||||
}
|
||||
|
||||
fn load_state(&self) -> LoadState {
|
||||
let frames = self.frames.borrow();
|
||||
match frames.as_ref() {
|
||||
Some(state) => state.load_state,
|
||||
None => LoadState::Loading,
|
||||
}
|
||||
}
|
||||
|
||||
fn start_frame_decoder(lease: BlobLease, image_data: &Arc<ImageData>) -> Self {
|
||||
match FrameDecoder::start(lease.clone()) {
|
||||
Ok(rx) => Self {
|
||||
@ -854,7 +871,7 @@ impl GlyphCache {
|
||||
decoded: &DecodedImage,
|
||||
padding: Option<usize>,
|
||||
min_frame_duration: Duration,
|
||||
) -> anyhow::Result<(Sprite, Option<Instant>)> {
|
||||
) -> anyhow::Result<(Sprite, Option<Instant>, LoadState)> {
|
||||
let mut handle = DecodedImageHandle {
|
||||
h: decoded.image.data(),
|
||||
current_frame: *decoded.current_frame.borrow(),
|
||||
@ -862,12 +879,12 @@ impl GlyphCache {
|
||||
match &*handle.h {
|
||||
ImageDataType::Rgba8 { hash, .. } => {
|
||||
if let Some(sprite) = frame_cache.get(hash) {
|
||||
return Ok((sprite.clone(), None));
|
||||
return Ok((sprite.clone(), None, decoded.load_state()));
|
||||
}
|
||||
let sprite = atlas.allocate_with_padding(&handle, padding)?;
|
||||
frame_cache.insert(*hash, sprite.clone());
|
||||
|
||||
return Ok((sprite, None));
|
||||
return Ok((sprite, None, decoded.load_state()));
|
||||
}
|
||||
ImageDataType::AnimRgba8 {
|
||||
hashes,
|
||||
@ -915,7 +932,7 @@ impl GlyphCache {
|
||||
let hash = hashes[*decoded_current_frame];
|
||||
|
||||
if let Some(sprite) = frame_cache.get(&hash) {
|
||||
return Ok((sprite.clone(), next));
|
||||
return Ok((sprite.clone(), next, decoded.load_state()));
|
||||
}
|
||||
|
||||
let sprite = atlas.allocate_with_padding(&handle, padding)?;
|
||||
@ -928,6 +945,7 @@ impl GlyphCache {
|
||||
*decoded_frame_start
|
||||
+ durations[*decoded_current_frame].max(min_frame_duration),
|
||||
),
|
||||
decoded.load_state(),
|
||||
));
|
||||
}
|
||||
ImageDataType::EncodedLease(_) | ImageDataType::EncodedFile(_) => {
|
||||
@ -967,7 +985,7 @@ impl GlyphCache {
|
||||
let hash = frames.frame_hash();
|
||||
|
||||
if let Some(sprite) = frame_cache.get(&hash) {
|
||||
return Ok((sprite.clone(), next));
|
||||
return Ok((sprite.clone(), next, frames.load_state));
|
||||
}
|
||||
|
||||
let frame = Image::from_raw(
|
||||
@ -982,6 +1000,7 @@ impl GlyphCache {
|
||||
Ok((
|
||||
sprite,
|
||||
Some(*decoded_frame_start + frames.frame_duration().max(min_frame_duration)),
|
||||
frames.load_state,
|
||||
))
|
||||
}
|
||||
}
|
||||
@ -991,7 +1010,7 @@ impl GlyphCache {
|
||||
&mut self,
|
||||
image_data: &Arc<ImageData>,
|
||||
padding: Option<usize>,
|
||||
) -> anyhow::Result<(Sprite, Option<Instant>)> {
|
||||
) -> anyhow::Result<(Sprite, Option<Instant>, LoadState)> {
|
||||
let hash = image_data.hash();
|
||||
|
||||
if let Some(decoded) = self.image_cache.get(&hash) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::color::LinearRgba;
|
||||
use crate::glyphcache::LoadState;
|
||||
use crate::quad::{QuadAllocator, QuadTrait};
|
||||
use crate::termwindow::RenderState;
|
||||
use crate::utilsprites::RenderMetrics;
|
||||
@ -397,15 +398,17 @@ impl crate::TermWindow {
|
||||
&self,
|
||||
bg_color: LinearRgba,
|
||||
top: StableRowIndex,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> anyhow::Result<bool> {
|
||||
let gl_state = self.render_state.as_ref().unwrap();
|
||||
let mut layer_idx = -127;
|
||||
let mut loaded_any = false;
|
||||
for layer in self.window_background.iter() {
|
||||
if self.render_background(gl_state, bg_color, layer, layer_idx, top)? {
|
||||
loaded_any = true;
|
||||
layer_idx = layer_idx.saturating_add(1);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
Ok(loaded_any)
|
||||
}
|
||||
|
||||
fn render_background(
|
||||
@ -422,12 +425,16 @@ impl crate::TermWindow {
|
||||
|
||||
let color = bg_color.mul_alpha(layer.def.opacity);
|
||||
|
||||
let (sprite, next_due) = gl_state
|
||||
let (sprite, next_due, load_state) = gl_state
|
||||
.glyph_cache
|
||||
.borrow_mut()
|
||||
.cached_image(&layer.source, None)?;
|
||||
self.update_next_frame_time(next_due);
|
||||
|
||||
if load_state == LoadState::Loading {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let pixel_width = self.dimensions.pixel_width as f32;
|
||||
let pixel_height = self.dimensions.pixel_height as f32;
|
||||
let pixel_aspect = pixel_width / pixel_height;
|
||||
|
@ -432,7 +432,7 @@ impl crate::TermWindow {
|
||||
padding.next_power_of_two()
|
||||
};
|
||||
|
||||
let (sprite, next_due) = gl_state
|
||||
let (sprite, next_due, _load_state) = gl_state
|
||||
.glyph_cache
|
||||
.borrow_mut()
|
||||
.cached_image(image.image_data(), Some(padding))?;
|
||||
|
@ -171,6 +171,8 @@ impl crate::TermWindow {
|
||||
log::trace!("quad map elapsed {:?}", start.elapsed());
|
||||
metrics::histogram!("quad.map", start.elapsed());
|
||||
|
||||
let mut paint_terminal_background = false;
|
||||
|
||||
// Render the full window background
|
||||
match (self.window_background.is_empty(), self.allow_images) {
|
||||
(false, true) => {
|
||||
@ -185,8 +187,16 @@ impl crate::TermWindow {
|
||||
})
|
||||
.unwrap_or(0);
|
||||
|
||||
self.render_backgrounds(bg_color, top)
|
||||
let loaded_any = self
|
||||
.render_backgrounds(bg_color, top)
|
||||
.context("render_backgrounds")?;
|
||||
|
||||
if !loaded_any {
|
||||
// Either there was a problem loading the background(s)
|
||||
// or they haven't finished loading yet.
|
||||
// Use the regular terminal background until that changes.
|
||||
paint_terminal_background = true;
|
||||
}
|
||||
}
|
||||
_ if window_is_transparent => {
|
||||
// Avoid doubling up the background color: the panes
|
||||
@ -194,6 +204,11 @@ impl crate::TermWindow {
|
||||
// should be no gaps that need filling in
|
||||
}
|
||||
_ => {
|
||||
paint_terminal_background = true;
|
||||
}
|
||||
}
|
||||
|
||||
if paint_terminal_background {
|
||||
// Regular window background color
|
||||
let background = if panes.len() == 1 {
|
||||
// If we're the only pane, use the pane's palette
|
||||
@ -218,7 +233,6 @@ impl crate::TermWindow {
|
||||
)
|
||||
.context("filled_rectangle for window background")?;
|
||||
}
|
||||
}
|
||||
|
||||
for pos in panes {
|
||||
if pos.is_active {
|
||||
|
Loading…
Reference in New Issue
Block a user