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
|
* Don't hide mouse cursor when pressing only modifier keys. #3570
|
||||||
* [PaneSelect](config/lua/keyassignment/PaneSelect.md) will now un-zoom to show
|
* [PaneSelect](config/lua/keyassignment/PaneSelect.md) will now un-zoom to show
|
||||||
all panes, then re-zoom after performing its action. #3573
|
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
|
#### New
|
||||||
|
|
||||||
|
@ -31,6 +31,12 @@ use wezterm_font::units::*;
|
|||||||
use wezterm_font::{FontConfiguration, GlyphInfo, LoadedFont, LoadedFontId};
|
use wezterm_font::{FontConfiguration, GlyphInfo, LoadedFont, LoadedFontId};
|
||||||
use wezterm_term::Underline;
|
use wezterm_term::Underline;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum LoadState {
|
||||||
|
Loading,
|
||||||
|
Loaded,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct CellMetricKey {
|
pub struct CellMetricKey {
|
||||||
pub pixel_width: u16,
|
pub pixel_width: u16,
|
||||||
@ -383,6 +389,7 @@ struct FrameState {
|
|||||||
source: FrameSource,
|
source: FrameSource,
|
||||||
current_frame: DecodedFrame,
|
current_frame: DecodedFrame,
|
||||||
frames: Vec<DecodedFrame>,
|
frames: Vec<DecodedFrame>,
|
||||||
|
load_state: LoadState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FrameState {
|
impl FrameState {
|
||||||
@ -405,6 +412,7 @@ impl FrameState {
|
|||||||
height: BLACK_SIZE,
|
height: BLACK_SIZE,
|
||||||
duration: Duration::from_millis(0),
|
duration: Duration::from_millis(0),
|
||||||
},
|
},
|
||||||
|
load_state: LoadState::Loading,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,6 +422,7 @@ impl FrameState {
|
|||||||
Ok(frame) => {
|
Ok(frame) => {
|
||||||
self.frames.push(frame.clone());
|
self.frames.push(frame.clone());
|
||||||
self.current_frame = frame;
|
self.current_frame = frame;
|
||||||
|
self.load_state = LoadState::Loaded;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
Err(TryRecvError::Empty) => false,
|
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 {
|
fn start_frame_decoder(lease: BlobLease, image_data: &Arc<ImageData>) -> Self {
|
||||||
match FrameDecoder::start(lease.clone()) {
|
match FrameDecoder::start(lease.clone()) {
|
||||||
Ok(rx) => Self {
|
Ok(rx) => Self {
|
||||||
@ -854,7 +871,7 @@ impl GlyphCache {
|
|||||||
decoded: &DecodedImage,
|
decoded: &DecodedImage,
|
||||||
padding: Option<usize>,
|
padding: Option<usize>,
|
||||||
min_frame_duration: Duration,
|
min_frame_duration: Duration,
|
||||||
) -> anyhow::Result<(Sprite, Option<Instant>)> {
|
) -> anyhow::Result<(Sprite, Option<Instant>, LoadState)> {
|
||||||
let mut handle = DecodedImageHandle {
|
let mut handle = DecodedImageHandle {
|
||||||
h: decoded.image.data(),
|
h: decoded.image.data(),
|
||||||
current_frame: *decoded.current_frame.borrow(),
|
current_frame: *decoded.current_frame.borrow(),
|
||||||
@ -862,12 +879,12 @@ impl GlyphCache {
|
|||||||
match &*handle.h {
|
match &*handle.h {
|
||||||
ImageDataType::Rgba8 { hash, .. } => {
|
ImageDataType::Rgba8 { hash, .. } => {
|
||||||
if let Some(sprite) = frame_cache.get(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)?;
|
let sprite = atlas.allocate_with_padding(&handle, padding)?;
|
||||||
frame_cache.insert(*hash, sprite.clone());
|
frame_cache.insert(*hash, sprite.clone());
|
||||||
|
|
||||||
return Ok((sprite, None));
|
return Ok((sprite, None, decoded.load_state()));
|
||||||
}
|
}
|
||||||
ImageDataType::AnimRgba8 {
|
ImageDataType::AnimRgba8 {
|
||||||
hashes,
|
hashes,
|
||||||
@ -915,7 +932,7 @@ impl GlyphCache {
|
|||||||
let hash = hashes[*decoded_current_frame];
|
let hash = hashes[*decoded_current_frame];
|
||||||
|
|
||||||
if let Some(sprite) = frame_cache.get(&hash) {
|
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)?;
|
let sprite = atlas.allocate_with_padding(&handle, padding)?;
|
||||||
@ -928,6 +945,7 @@ impl GlyphCache {
|
|||||||
*decoded_frame_start
|
*decoded_frame_start
|
||||||
+ durations[*decoded_current_frame].max(min_frame_duration),
|
+ durations[*decoded_current_frame].max(min_frame_duration),
|
||||||
),
|
),
|
||||||
|
decoded.load_state(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
ImageDataType::EncodedLease(_) | ImageDataType::EncodedFile(_) => {
|
ImageDataType::EncodedLease(_) | ImageDataType::EncodedFile(_) => {
|
||||||
@ -967,7 +985,7 @@ impl GlyphCache {
|
|||||||
let hash = frames.frame_hash();
|
let hash = frames.frame_hash();
|
||||||
|
|
||||||
if let Some(sprite) = frame_cache.get(&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(
|
let frame = Image::from_raw(
|
||||||
@ -982,6 +1000,7 @@ impl GlyphCache {
|
|||||||
Ok((
|
Ok((
|
||||||
sprite,
|
sprite,
|
||||||
Some(*decoded_frame_start + frames.frame_duration().max(min_frame_duration)),
|
Some(*decoded_frame_start + frames.frame_duration().max(min_frame_duration)),
|
||||||
|
frames.load_state,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -991,7 +1010,7 @@ impl GlyphCache {
|
|||||||
&mut self,
|
&mut self,
|
||||||
image_data: &Arc<ImageData>,
|
image_data: &Arc<ImageData>,
|
||||||
padding: Option<usize>,
|
padding: Option<usize>,
|
||||||
) -> anyhow::Result<(Sprite, Option<Instant>)> {
|
) -> anyhow::Result<(Sprite, Option<Instant>, LoadState)> {
|
||||||
let hash = image_data.hash();
|
let hash = image_data.hash();
|
||||||
|
|
||||||
if let Some(decoded) = self.image_cache.get(&hash) {
|
if let Some(decoded) = self.image_cache.get(&hash) {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use crate::color::LinearRgba;
|
use crate::color::LinearRgba;
|
||||||
|
use crate::glyphcache::LoadState;
|
||||||
use crate::quad::{QuadAllocator, QuadTrait};
|
use crate::quad::{QuadAllocator, QuadTrait};
|
||||||
use crate::termwindow::RenderState;
|
use crate::termwindow::RenderState;
|
||||||
use crate::utilsprites::RenderMetrics;
|
use crate::utilsprites::RenderMetrics;
|
||||||
@ -397,15 +398,17 @@ impl crate::TermWindow {
|
|||||||
&self,
|
&self,
|
||||||
bg_color: LinearRgba,
|
bg_color: LinearRgba,
|
||||||
top: StableRowIndex,
|
top: StableRowIndex,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<bool> {
|
||||||
let gl_state = self.render_state.as_ref().unwrap();
|
let gl_state = self.render_state.as_ref().unwrap();
|
||||||
let mut layer_idx = -127;
|
let mut layer_idx = -127;
|
||||||
|
let mut loaded_any = false;
|
||||||
for layer in self.window_background.iter() {
|
for layer in self.window_background.iter() {
|
||||||
if self.render_background(gl_state, bg_color, layer, layer_idx, top)? {
|
if self.render_background(gl_state, bg_color, layer, layer_idx, top)? {
|
||||||
|
loaded_any = true;
|
||||||
layer_idx = layer_idx.saturating_add(1);
|
layer_idx = layer_idx.saturating_add(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(loaded_any)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_background(
|
fn render_background(
|
||||||
@ -422,12 +425,16 @@ impl crate::TermWindow {
|
|||||||
|
|
||||||
let color = bg_color.mul_alpha(layer.def.opacity);
|
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
|
.glyph_cache
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.cached_image(&layer.source, None)?;
|
.cached_image(&layer.source, None)?;
|
||||||
self.update_next_frame_time(next_due);
|
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_width = self.dimensions.pixel_width as f32;
|
||||||
let pixel_height = self.dimensions.pixel_height as f32;
|
let pixel_height = self.dimensions.pixel_height as f32;
|
||||||
let pixel_aspect = pixel_width / pixel_height;
|
let pixel_aspect = pixel_width / pixel_height;
|
||||||
|
@ -432,7 +432,7 @@ impl crate::TermWindow {
|
|||||||
padding.next_power_of_two()
|
padding.next_power_of_two()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (sprite, next_due) = gl_state
|
let (sprite, next_due, _load_state) = gl_state
|
||||||
.glyph_cache
|
.glyph_cache
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.cached_image(image.image_data(), Some(padding))?;
|
.cached_image(image.image_data(), Some(padding))?;
|
||||||
|
@ -171,6 +171,8 @@ impl crate::TermWindow {
|
|||||||
log::trace!("quad map elapsed {:?}", start.elapsed());
|
log::trace!("quad map elapsed {:?}", start.elapsed());
|
||||||
metrics::histogram!("quad.map", start.elapsed());
|
metrics::histogram!("quad.map", start.elapsed());
|
||||||
|
|
||||||
|
let mut paint_terminal_background = false;
|
||||||
|
|
||||||
// Render the full window background
|
// Render the full window background
|
||||||
match (self.window_background.is_empty(), self.allow_images) {
|
match (self.window_background.is_empty(), self.allow_images) {
|
||||||
(false, true) => {
|
(false, true) => {
|
||||||
@ -185,8 +187,16 @@ impl crate::TermWindow {
|
|||||||
})
|
})
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
self.render_backgrounds(bg_color, top)
|
let loaded_any = self
|
||||||
|
.render_backgrounds(bg_color, top)
|
||||||
.context("render_backgrounds")?;
|
.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 => {
|
_ if window_is_transparent => {
|
||||||
// Avoid doubling up the background color: the panes
|
// Avoid doubling up the background color: the panes
|
||||||
@ -194,32 +204,36 @@ impl crate::TermWindow {
|
|||||||
// should be no gaps that need filling in
|
// should be no gaps that need filling in
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Regular window background color
|
paint_terminal_background = true;
|
||||||
let background = if panes.len() == 1 {
|
|
||||||
// If we're the only pane, use the pane's palette
|
|
||||||
// to draw the padding background
|
|
||||||
panes[0].pane.palette().background
|
|
||||||
} else {
|
|
||||||
self.palette().background
|
|
||||||
}
|
|
||||||
.to_linear()
|
|
||||||
.mul_alpha(self.config.window_background_opacity);
|
|
||||||
|
|
||||||
self.filled_rectangle(
|
|
||||||
&mut layers,
|
|
||||||
0,
|
|
||||||
euclid::rect(
|
|
||||||
0.,
|
|
||||||
0.,
|
|
||||||
self.dimensions.pixel_width as f32,
|
|
||||||
self.dimensions.pixel_height as f32,
|
|
||||||
),
|
|
||||||
background,
|
|
||||||
)
|
|
||||||
.context("filled_rectangle for window background")?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
// to draw the padding background
|
||||||
|
panes[0].pane.palette().background
|
||||||
|
} else {
|
||||||
|
self.palette().background
|
||||||
|
}
|
||||||
|
.to_linear()
|
||||||
|
.mul_alpha(self.config.window_background_opacity);
|
||||||
|
|
||||||
|
self.filled_rectangle(
|
||||||
|
&mut layers,
|
||||||
|
0,
|
||||||
|
euclid::rect(
|
||||||
|
0.,
|
||||||
|
0.,
|
||||||
|
self.dimensions.pixel_width as f32,
|
||||||
|
self.dimensions.pixel_height as f32,
|
||||||
|
),
|
||||||
|
background,
|
||||||
|
)
|
||||||
|
.context("filled_rectangle for window background")?;
|
||||||
|
}
|
||||||
|
|
||||||
for pos in panes {
|
for pos in panes {
|
||||||
if pos.is_active {
|
if pos.is_active {
|
||||||
self.update_text_cursor(&pos);
|
self.update_text_cursor(&pos);
|
||||||
|
Loading…
Reference in New Issue
Block a user