mirror of
https://github.com/wez/wezterm.git
synced 2024-12-24 05:42:03 +03:00
notionally allow multiple background image layers
This adds some types that will enable richer background images. * Can specify multiple layers * Each layer can select from image files or gradient definitions * Layers have additional properties to specify positioning, scaling, tiling and whether they scroll with the viewport. None of the additional properties are hooked up yet.
This commit is contained in:
parent
a9612bc613
commit
5d44ed6f85
@ -1,6 +1,185 @@
|
||||
use crate::{default_one_point_oh, Config, HsbTransform};
|
||||
use luahelper::impl_lua_conversion_dynamic;
|
||||
use wezterm_dynamic::{FromDynamic, ToDynamic};
|
||||
|
||||
#[derive(Debug, Clone, FromDynamic, ToDynamic)]
|
||||
pub enum BackgroundSource {
|
||||
Gradient(Gradient),
|
||||
File(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromDynamic, ToDynamic)]
|
||||
pub struct BackgroundLayer {
|
||||
pub source: BackgroundSource,
|
||||
|
||||
/// Where the top left corner of the background begins
|
||||
#[dynamic(default)]
|
||||
pub origin: BackgroundOrigin,
|
||||
|
||||
#[dynamic(default)]
|
||||
pub attachment: BackgroundAttachment,
|
||||
|
||||
#[dynamic(default)]
|
||||
pub repeat_x: BackgroundRepeat,
|
||||
|
||||
#[dynamic(default)]
|
||||
pub repeat_y: BackgroundRepeat,
|
||||
|
||||
#[dynamic(default)]
|
||||
pub vertical_align: BackgroundVerticalAlignment,
|
||||
|
||||
#[dynamic(default)]
|
||||
pub horizontal_align: BackgroundHorizontalAlignment,
|
||||
|
||||
/// Additional alpha modifier
|
||||
#[dynamic(default = "default_one_point_oh")]
|
||||
pub opacity: f32,
|
||||
|
||||
/// Additional hsb transform
|
||||
#[dynamic(default)]
|
||||
pub hsb: HsbTransform,
|
||||
|
||||
#[dynamic(default)]
|
||||
pub width: BackgroundSize,
|
||||
|
||||
#[dynamic(default)]
|
||||
pub height: BackgroundSize,
|
||||
}
|
||||
|
||||
impl BackgroundLayer {
|
||||
pub fn with_legacy(cfg: &Config) -> Option<Self> {
|
||||
let source = if let Some(gradient) = &cfg.window_background_gradient {
|
||||
BackgroundSource::Gradient(gradient.clone())
|
||||
} else if let Some(path) = &cfg.window_background_image {
|
||||
BackgroundSource::File(path.to_string_lossy().to_string())
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
Some(BackgroundLayer {
|
||||
source,
|
||||
opacity: cfg.window_background_opacity,
|
||||
hsb: cfg.window_background_image_hsb.unwrap_or_default(),
|
||||
origin: Default::default(),
|
||||
attachment: Default::default(),
|
||||
repeat_x: Default::default(),
|
||||
repeat_y: Default::default(),
|
||||
vertical_align: Default::default(),
|
||||
horizontal_align: Default::default(),
|
||||
width: BackgroundSize::Percent(100),
|
||||
height: BackgroundSize::Percent(100),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/background-size>
|
||||
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic)]
|
||||
pub enum BackgroundSize {
|
||||
/// Scales image as large as possible without cropping or stretching.
|
||||
/// If the container is larger than the image, tiles the image unless
|
||||
/// the correspond `repeat` is NoRepeat.
|
||||
Contain,
|
||||
/// Scale the image (preserving aspect ratio) to the smallest possible
|
||||
/// size to the fill the container leaving no empty space.
|
||||
/// If the aspect ratio differs from the background, the image is
|
||||
/// cropped.
|
||||
Cover,
|
||||
/// Scales the image so that its aspect ratio is maintained
|
||||
Auto,
|
||||
/// Stretches the image to the specified length in pixels
|
||||
Length(usize),
|
||||
/// Stretches the image to a percentage of the background size
|
||||
/// as determined by the `origin` property.
|
||||
Percent(u8),
|
||||
}
|
||||
|
||||
impl Default for BackgroundSize {
|
||||
fn default() -> Self {
|
||||
Self::Contain
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic)]
|
||||
pub enum BackgroundHorizontalAlignment {
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
}
|
||||
|
||||
impl Default for BackgroundHorizontalAlignment {
|
||||
fn default() -> Self {
|
||||
Self::Left
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic)]
|
||||
pub enum BackgroundVerticalAlignment {
|
||||
Top,
|
||||
Middle,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
impl Default for BackgroundVerticalAlignment {
|
||||
fn default() -> Self {
|
||||
Self::Top
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic)]
|
||||
pub enum BackgroundRepeat {
|
||||
/// Repeat as much as possible to cover the area.
|
||||
/// The last image will be clipped if it doesn't fit.
|
||||
Repeat,
|
||||
/// Repeat as much as possible without clipping.
|
||||
/// The first and last images are aligned with the edges,
|
||||
/// with any gaps being distributed evenly between
|
||||
/// the images.
|
||||
/// The `position` property is ignored unless only
|
||||
/// a single image an be displayed without clipping.
|
||||
/// Clipping will only occur when there isn't enough
|
||||
/// room to display a single image.
|
||||
Space,
|
||||
/// As the available space increases, the images will
|
||||
/// stretch until there is room (space >= 50% of image
|
||||
/// size) for another one to be added. When adding a
|
||||
/// new image, the current images compress to allow
|
||||
/// room.
|
||||
Round,
|
||||
/// The image is not repeated.
|
||||
/// The position of the image is defined by the
|
||||
/// `position` property
|
||||
NoRepeat,
|
||||
}
|
||||
|
||||
impl Default for BackgroundRepeat {
|
||||
fn default() -> Self {
|
||||
Self::NoRepeat
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic)]
|
||||
pub enum BackgroundAttachment {
|
||||
Fixed,
|
||||
Scroll,
|
||||
}
|
||||
|
||||
impl Default for BackgroundAttachment {
|
||||
fn default() -> Self {
|
||||
Self::Fixed
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic)]
|
||||
pub enum BackgroundOrigin {
|
||||
BorderBox,
|
||||
PaddingBox,
|
||||
}
|
||||
|
||||
impl Default for BackgroundOrigin {
|
||||
fn default() -> Self {
|
||||
Self::BorderBox
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic)]
|
||||
pub enum Interpolation {
|
||||
Linear,
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::background::Gradient;
|
||||
use crate::background::{BackgroundLayer, Gradient};
|
||||
use crate::bell::{AudibleBell, EasingFunction, VisualBell};
|
||||
use crate::color::{
|
||||
ColorSchemeFile, HsbTransform, Palette, SrgbaTuple, TabBarStyle, WindowFrameConfig,
|
||||
@ -391,6 +391,9 @@ pub struct Config {
|
||||
#[dynamic(default)]
|
||||
pub foreground_text_hsb: HsbTransform,
|
||||
|
||||
#[dynamic(default)]
|
||||
pub background: Vec<BackgroundLayer>,
|
||||
|
||||
/// Specifies the alpha value to use when rendering the background
|
||||
/// of the window. The background is taken either from the
|
||||
/// window_background_image, or if there is none, the background
|
||||
@ -962,6 +965,10 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(bg) = BackgroundLayer::with_legacy(self) {
|
||||
cfg.background.push(bg);
|
||||
}
|
||||
|
||||
cfg
|
||||
}
|
||||
|
||||
|
197
wezterm-gui/src/termwindow/background.rs
Normal file
197
wezterm-gui/src/termwindow/background.rs
Normal file
@ -0,0 +1,197 @@
|
||||
use crate::Dimensions;
|
||||
use anyhow::Context;
|
||||
use config::{
|
||||
BackgroundLayer, BackgroundSize, BackgroundSource, ConfigHandle, GradientOrientation,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use termwiz::image::{ImageData, ImageDataType};
|
||||
|
||||
pub struct LoadedBackgroundLayer {
|
||||
pub source: Arc<ImageData>,
|
||||
pub def: BackgroundLayer,
|
||||
}
|
||||
|
||||
fn load_background_layer(
|
||||
layer: &BackgroundLayer,
|
||||
dimensions: &Dimensions,
|
||||
) -> anyhow::Result<LoadedBackgroundLayer> {
|
||||
let data = match &layer.source {
|
||||
BackgroundSource::Gradient(g) => {
|
||||
let grad = g
|
||||
.build()
|
||||
.with_context(|| format!("building gradient {:?}", g))?;
|
||||
|
||||
let mut width = match layer.width {
|
||||
BackgroundSize::Percent(p) => (p as u32 * dimensions.pixel_width as u32) / 100,
|
||||
BackgroundSize::Length(u) => u as u32,
|
||||
unsup => anyhow::bail!("{:?} not yet implemented", unsup),
|
||||
};
|
||||
let mut height = match layer.height {
|
||||
BackgroundSize::Percent(p) => (p as u32 * dimensions.pixel_height as u32) / 100,
|
||||
BackgroundSize::Length(u) => u as u32,
|
||||
unsup => anyhow::bail!("{:?} not yet implemented", unsup),
|
||||
};
|
||||
|
||||
if matches!(g.orientation, GradientOrientation::Radial { .. }) {
|
||||
// To simplify the math, we compute a perfect circle
|
||||
// for the radial gradient, and let the texture sampler
|
||||
// perturb it to fill the window
|
||||
width = width.min(height);
|
||||
height = height.min(width);
|
||||
}
|
||||
|
||||
let mut imgbuf = image::RgbaImage::new(width, height);
|
||||
let fw = width as f64;
|
||||
let fh = height as f64;
|
||||
|
||||
fn to_pixel(c: colorgrad::Color) -> image::Rgba<u8> {
|
||||
let (r, g, b, a) = c.rgba_u8();
|
||||
image::Rgba([r, g, b, a])
|
||||
}
|
||||
|
||||
// Map t which is in range [a, b] to range [c, d]
|
||||
fn remap(t: f64, a: f64, b: f64, c: f64, d: f64) -> f64 {
|
||||
(t - a) * ((d - c) / (b - a)) + c
|
||||
}
|
||||
|
||||
let (dmin, dmax) = grad.domain();
|
||||
|
||||
let rng = fastrand::Rng::new();
|
||||
|
||||
// We add some randomness to the position that we use to
|
||||
// index into the color gradient, so that we can avoid
|
||||
// visible color banding. The default 64 was selected
|
||||
// because it it was the smallest value on my mac where
|
||||
// the banding wasn't obvious.
|
||||
let noise_amount = g.noise.unwrap_or_else(|| {
|
||||
if matches!(g.orientation, GradientOrientation::Radial { .. }) {
|
||||
16
|
||||
} else {
|
||||
64
|
||||
}
|
||||
});
|
||||
|
||||
fn noise(rng: &fastrand::Rng, noise_amount: usize) -> f64 {
|
||||
if noise_amount == 0 {
|
||||
0.
|
||||
} else {
|
||||
rng.usize(0..noise_amount) as f64 * -1.
|
||||
}
|
||||
}
|
||||
|
||||
match g.orientation {
|
||||
GradientOrientation::Horizontal => {
|
||||
for (x, _, pixel) in imgbuf.enumerate_pixels_mut() {
|
||||
*pixel = to_pixel(grad.at(remap(
|
||||
x as f64 + noise(&rng, noise_amount),
|
||||
0.0,
|
||||
fw,
|
||||
dmin,
|
||||
dmax,
|
||||
)));
|
||||
}
|
||||
}
|
||||
GradientOrientation::Vertical => {
|
||||
for (_, y, pixel) in imgbuf.enumerate_pixels_mut() {
|
||||
*pixel = to_pixel(grad.at(remap(
|
||||
y as f64 + noise(&rng, noise_amount),
|
||||
0.0,
|
||||
fh,
|
||||
dmin,
|
||||
dmax,
|
||||
)));
|
||||
}
|
||||
}
|
||||
GradientOrientation::Linear { angle } => {
|
||||
let angle = angle.unwrap_or(0.0).to_radians();
|
||||
for (x, y, pixel) in imgbuf.enumerate_pixels_mut() {
|
||||
let (x, y) = (x as f64, y as f64);
|
||||
let (x, y) = (x - fw / 2., y - fh / 2.);
|
||||
let t = x * f64::cos(angle) - y * f64::sin(angle);
|
||||
*pixel = to_pixel(grad.at(remap(
|
||||
t + noise(&rng, noise_amount),
|
||||
-fw / 2.,
|
||||
fw / 2.,
|
||||
dmin,
|
||||
dmax,
|
||||
)));
|
||||
}
|
||||
}
|
||||
GradientOrientation::Radial { radius, cx, cy } => {
|
||||
let radius = fw * radius.unwrap_or(0.5);
|
||||
let cx = fw * cx.unwrap_or(0.5);
|
||||
let cy = fh * cy.unwrap_or(0.5);
|
||||
|
||||
for (x, y, pixel) in imgbuf.enumerate_pixels_mut() {
|
||||
let nx = noise(&rng, noise_amount);
|
||||
let ny = noise(&rng, noise_amount);
|
||||
|
||||
let t = (nx + (x as f64 - cx).powi(2) + (ny + y as f64 - cy).powi(2))
|
||||
.sqrt()
|
||||
/ radius;
|
||||
*pixel = to_pixel(grad.at(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let data = imgbuf.into_vec();
|
||||
ImageData::with_data(ImageDataType::new_single_frame(width, height, data))
|
||||
}
|
||||
BackgroundSource::File(path) => {
|
||||
let data = std::fs::read(path)
|
||||
.with_context(|| format!("Failed to load window_background_image {}", path))?;
|
||||
log::info!("loaded {}", path);
|
||||
let data = ImageDataType::EncodedFile(data).decode();
|
||||
ImageData::with_data(data)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(LoadedBackgroundLayer {
|
||||
source: Arc::new(data),
|
||||
def: layer.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load_background_image(
|
||||
config: &ConfigHandle,
|
||||
dimensions: &Dimensions,
|
||||
) -> Vec<LoadedBackgroundLayer> {
|
||||
let mut layers = vec![];
|
||||
for layer in &config.background {
|
||||
match load_background_layer(layer, dimensions) {
|
||||
Ok(layer) => layers.push(layer),
|
||||
Err(err) => {
|
||||
log::error!("Failed to load background: {:#}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
layers
|
||||
}
|
||||
|
||||
pub fn reload_background_image(
|
||||
config: &ConfigHandle,
|
||||
existing: &[LoadedBackgroundLayer],
|
||||
dimensions: &Dimensions,
|
||||
) -> Vec<LoadedBackgroundLayer> {
|
||||
// We want to reuse the existing version of the image where possible
|
||||
// so that the textures we may have cached can be re-used and so that
|
||||
// animation state can be preserved across the reload.
|
||||
let map: HashMap<_, _> = existing
|
||||
.iter()
|
||||
.map(|layer| (layer.source.hash(), &layer.source))
|
||||
.collect();
|
||||
|
||||
load_background_image(config, dimensions)
|
||||
.into_iter()
|
||||
.map(|mut layer| {
|
||||
let hash = layer.source.hash();
|
||||
|
||||
if let Some(existing) = map.get(&hash) {
|
||||
layer.source = Arc::clone(existing);
|
||||
}
|
||||
|
||||
layer
|
||||
})
|
||||
.collect()
|
||||
}
|
@ -17,6 +17,9 @@ use crate::scrollbar::*;
|
||||
use crate::selection::Selection;
|
||||
use crate::shapecache::*;
|
||||
use crate::tabbar::{TabBarItem, TabBarState};
|
||||
use crate::termwindow::background::{
|
||||
load_background_image, reload_background_image, LoadedBackgroundLayer,
|
||||
};
|
||||
use crate::termwindow::keyevent::KeyTableState;
|
||||
use crate::termwindow::modal::Modal;
|
||||
use ::wezterm_term::input::{ClickPosition, MouseButton as TMB};
|
||||
@ -27,8 +30,8 @@ use config::keyassignment::{
|
||||
QuickSelectArguments, RotationDirection, SpawnCommand, SplitSize,
|
||||
};
|
||||
use config::{
|
||||
configuration, AudibleBell, ConfigHandle, Dimension, DimensionContext, GradientOrientation,
|
||||
TermConfig, WindowCloseConfirmation,
|
||||
configuration, AudibleBell, ConfigHandle, Dimension, DimensionContext, TermConfig,
|
||||
WindowCloseConfirmation,
|
||||
};
|
||||
use mlua::{FromLua, UserData, UserDataFields};
|
||||
use mux::pane::{CloseReason, Pane, PaneId, Pattern as MuxPattern};
|
||||
@ -50,7 +53,6 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
use termwiz::hyperlink::Hyperlink;
|
||||
use termwiz::image::{ImageData, ImageDataType};
|
||||
use termwiz::surface::SequenceNo;
|
||||
use wezterm_font::FontConfiguration;
|
||||
use wezterm_gui_subcommands::GuiPosition;
|
||||
@ -58,6 +60,7 @@ use wezterm_term::color::ColorPalette;
|
||||
use wezterm_term::input::LastMouseClick;
|
||||
use wezterm_term::{Alert, StableRowIndex, TerminalConfiguration};
|
||||
|
||||
pub mod background;
|
||||
pub mod box_model;
|
||||
pub mod clipboard;
|
||||
mod keyevent;
|
||||
@ -361,7 +364,7 @@ pub struct TermWindow {
|
||||
pane_state: RefCell<HashMap<PaneId, PaneState>>,
|
||||
semantic_zones: HashMap<PaneId, SemanticZoneCache>,
|
||||
|
||||
window_background: Option<Arc<ImageData>>,
|
||||
window_background: Vec<LoadedBackgroundLayer>,
|
||||
|
||||
current_mouse_buttons: Vec<MousePress>,
|
||||
current_mouse_capture: Option<MouseCapture>,
|
||||
@ -517,165 +520,6 @@ impl TermWindow {
|
||||
}
|
||||
}
|
||||
|
||||
fn load_background_image(config: &ConfigHandle, dimensions: &Dimensions) -> Option<Arc<ImageData>> {
|
||||
match &config.window_background_gradient {
|
||||
Some(g) => match g.build() {
|
||||
Ok(grad) => {
|
||||
let mut width = dimensions.pixel_width as u32;
|
||||
let mut height = dimensions.pixel_height as u32;
|
||||
|
||||
if matches!(g.orientation, GradientOrientation::Radial { .. }) {
|
||||
// To simplify the math, we compute a perfect circle
|
||||
// for the radial gradient, and let the texture sampler
|
||||
// perturb it to fill the window
|
||||
width = width.min(height);
|
||||
height = height.min(width);
|
||||
}
|
||||
|
||||
let mut imgbuf = image::RgbaImage::new(width, height);
|
||||
let fw = width as f64;
|
||||
let fh = height as f64;
|
||||
|
||||
fn to_pixel(c: colorgrad::Color) -> image::Rgba<u8> {
|
||||
let (r, g, b, a) = c.rgba_u8();
|
||||
image::Rgba([r, g, b, a])
|
||||
}
|
||||
|
||||
// Map t which is in range [a, b] to range [c, d]
|
||||
fn remap(t: f64, a: f64, b: f64, c: f64, d: f64) -> f64 {
|
||||
(t - a) * ((d - c) / (b - a)) + c
|
||||
}
|
||||
|
||||
let (dmin, dmax) = grad.domain();
|
||||
|
||||
let rng = fastrand::Rng::new();
|
||||
|
||||
// We add some randomness to the position that we use to
|
||||
// index into the color gradient, so that we can avoid
|
||||
// visible color banding. The default 64 was selected
|
||||
// because it it was the smallest value on my mac where
|
||||
// the banding wasn't obvious.
|
||||
let noise_amount = g.noise.unwrap_or_else(|| {
|
||||
if matches!(g.orientation, GradientOrientation::Radial { .. }) {
|
||||
16
|
||||
} else {
|
||||
64
|
||||
}
|
||||
});
|
||||
|
||||
fn noise(rng: &fastrand::Rng, noise_amount: usize) -> f64 {
|
||||
if noise_amount == 0 {
|
||||
0.
|
||||
} else {
|
||||
rng.usize(0..noise_amount) as f64 * -1.
|
||||
}
|
||||
}
|
||||
|
||||
match g.orientation {
|
||||
GradientOrientation::Horizontal => {
|
||||
for (x, _, pixel) in imgbuf.enumerate_pixels_mut() {
|
||||
*pixel = to_pixel(grad.at(remap(
|
||||
x as f64 + noise(&rng, noise_amount),
|
||||
0.0,
|
||||
fw,
|
||||
dmin,
|
||||
dmax,
|
||||
)));
|
||||
}
|
||||
}
|
||||
GradientOrientation::Vertical => {
|
||||
for (_, y, pixel) in imgbuf.enumerate_pixels_mut() {
|
||||
*pixel = to_pixel(grad.at(remap(
|
||||
y as f64 + noise(&rng, noise_amount),
|
||||
0.0,
|
||||
fh,
|
||||
dmin,
|
||||
dmax,
|
||||
)));
|
||||
}
|
||||
}
|
||||
GradientOrientation::Linear { angle } => {
|
||||
let angle = angle.unwrap_or(0.0).to_radians();
|
||||
for (x, y, pixel) in imgbuf.enumerate_pixels_mut() {
|
||||
let (x, y) = (x as f64, y as f64);
|
||||
let (x, y) = (x - fw / 2., y - fh / 2.);
|
||||
let t = x * f64::cos(angle) - y * f64::sin(angle);
|
||||
*pixel = to_pixel(grad.at(remap(
|
||||
t + noise(&rng, noise_amount),
|
||||
-fw / 2.,
|
||||
fw / 2.,
|
||||
dmin,
|
||||
dmax,
|
||||
)));
|
||||
}
|
||||
}
|
||||
GradientOrientation::Radial { radius, cx, cy } => {
|
||||
let radius = fw * radius.unwrap_or(0.5);
|
||||
let cx = fw * cx.unwrap_or(0.5);
|
||||
let cy = fh * cy.unwrap_or(0.5);
|
||||
|
||||
for (x, y, pixel) in imgbuf.enumerate_pixels_mut() {
|
||||
let nx = noise(&rng, noise_amount);
|
||||
let ny = noise(&rng, noise_amount);
|
||||
|
||||
let t = (nx + (x as f64 - cx).powi(2) + (ny + y as f64 - cy).powi(2))
|
||||
.sqrt()
|
||||
/ radius;
|
||||
*pixel = to_pixel(grad.at(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let data = imgbuf.into_vec();
|
||||
return Some(Arc::new(ImageData::with_data(
|
||||
ImageDataType::new_single_frame(width, height, data),
|
||||
)));
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"window_background_gradient: error building gradient: {:#} {:?}",
|
||||
err,
|
||||
g
|
||||
);
|
||||
return None;
|
||||
}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
match &config.window_background_image {
|
||||
Some(p) => match std::fs::read(p) {
|
||||
Ok(data) => {
|
||||
log::info!("loaded {}", p.display());
|
||||
let data = ImageDataType::EncodedFile(data).decode();
|
||||
Some(Arc::new(ImageData::with_data(data)))
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"Failed to load window_background_image {}: {}",
|
||||
p.display(),
|
||||
err
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn reload_background_image(
|
||||
config: &ConfigHandle,
|
||||
image: &Option<Arc<ImageData>>,
|
||||
dimensions: &Dimensions,
|
||||
) -> Option<Arc<ImageData>> {
|
||||
let data = load_background_image(config, dimensions)?;
|
||||
match image {
|
||||
Some(existing) if existing.hash() == data.data().compute_hash() => {
|
||||
Some(Arc::clone(existing))
|
||||
}
|
||||
_ => Some(data),
|
||||
}
|
||||
}
|
||||
|
||||
impl TermWindow {
|
||||
pub async fn new_window(mux_window_id: MuxWindowId) -> anyhow::Result<()> {
|
||||
let config = configuration();
|
||||
|
@ -898,7 +898,7 @@ impl super::TermWindow {
|
||||
));
|
||||
|
||||
let window_is_transparent =
|
||||
self.window_background.is_some() || self.config.window_background_opacity != 1.0;
|
||||
!self.window_background.is_empty() || self.config.window_background_opacity != 1.0;
|
||||
let gl_state = self.render_state.as_ref().unwrap();
|
||||
let white_space = gl_state.util_sprites.white_space.texture_coords();
|
||||
let filled_box = gl_state.util_sprites.filled_box.texture_coords();
|
||||
@ -1113,7 +1113,7 @@ impl super::TermWindow {
|
||||
let filled_box = gl_state.util_sprites.filled_box.texture_coords();
|
||||
|
||||
let window_is_transparent =
|
||||
self.window_background.is_some() || config.window_background_opacity != 1.0;
|
||||
!self.window_background.is_empty() || config.window_background_opacity != 1.0;
|
||||
|
||||
let default_bg = palette
|
||||
.resolve_bg(ColorAttribute::Default)
|
||||
@ -1126,28 +1126,29 @@ impl super::TermWindow {
|
||||
|
||||
// Render the full window background
|
||||
if pos.index == 0 {
|
||||
match (self.window_background.as_ref(), self.allow_images) {
|
||||
(Some(im), true) => {
|
||||
// Render the window background image
|
||||
let color = palette
|
||||
.background
|
||||
.to_linear()
|
||||
.mul_alpha(config.window_background_opacity);
|
||||
match (self.window_background.is_empty(), self.allow_images) {
|
||||
(false, true) => {
|
||||
// Render the window background image(s)
|
||||
for layer in &self.window_background {
|
||||
let color = palette.background.to_linear().mul_alpha(layer.def.opacity);
|
||||
|
||||
let (sprite, next_due) =
|
||||
gl_state.glyph_cache.borrow_mut().cached_image(im, None)?;
|
||||
self.update_next_frame_time(next_due);
|
||||
let mut quad = layers[0].allocate()?;
|
||||
quad.set_position(
|
||||
self.dimensions.pixel_width as f32 / -2.,
|
||||
self.dimensions.pixel_height as f32 / -2.,
|
||||
self.dimensions.pixel_width as f32 / 2.,
|
||||
self.dimensions.pixel_height as f32 / 2.,
|
||||
);
|
||||
quad.set_texture(sprite.texture_coords());
|
||||
quad.set_is_background_image();
|
||||
quad.set_hsv(config.window_background_image_hsb);
|
||||
quad.set_fg_color(color);
|
||||
let (sprite, next_due) = gl_state
|
||||
.glyph_cache
|
||||
.borrow_mut()
|
||||
.cached_image(&layer.source, None)?;
|
||||
self.update_next_frame_time(next_due);
|
||||
let mut quad = layers[0].allocate()?;
|
||||
quad.set_position(
|
||||
self.dimensions.pixel_width as f32 / -2.,
|
||||
self.dimensions.pixel_height as f32 / -2.,
|
||||
self.dimensions.pixel_width as f32 / 2.,
|
||||
self.dimensions.pixel_height as f32 / 2.,
|
||||
);
|
||||
quad.set_texture(sprite.texture_coords());
|
||||
quad.set_is_background_image();
|
||||
quad.set_hsv(Some(layer.def.hsb));
|
||||
quad.set_fg_color(color);
|
||||
}
|
||||
}
|
||||
_ if window_is_transparent && num_panes > 1 => {
|
||||
// Avoid doubling up the background color: the panes
|
||||
@ -1179,7 +1180,7 @@ impl super::TermWindow {
|
||||
}
|
||||
}
|
||||
|
||||
if num_panes > 1 && self.window_background.is_none() {
|
||||
if num_panes > 1 && self.window_background.is_empty() {
|
||||
// Per-pane, palette-specified background
|
||||
let cell_width = self.render_metrics.cell_size.width as f32;
|
||||
let cell_height = self.render_metrics.cell_size.height as f32;
|
||||
|
Loading…
Reference in New Issue
Block a user