mirror of
https://github.com/wez/wezterm.git
synced 2024-12-22 12:51:31 +03:00
allow setting selection colors with alpha values
This commit allows the following configuration: ``` wezterm -n --config 'colors = { selection_fg = "clear", selection_bg = "rgba:50% 50% 50% 50%" }' ``` which sets the selection_bg to fully transparent, and selection_bg to 50% transparent gray. When selection_fg is fully transparent we'll use the normal fg color. When selection_bg is partially (or fully!) transparent, it will be alpha blended over the current cell background color. To support this, the config file will now accept rgba colors specified as 4 whitespace delimited numeric values. If a value ends with `%` it is interpreted as a number in the range 0-100. Otherwise, it is interpreted as a number in the range 0-255. The 4 values are red, green, blue, alpha. At this time, only the selection_fg and selection_bg settings accept alpha values. refs: #1615
This commit is contained in:
parent
240026de48
commit
8cb74c62d2
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -4701,6 +4701,7 @@ name = "wezterm-color-types"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -405,7 +405,7 @@ macro_rules! pdu {
|
||||
/// The overall version of the codec.
|
||||
/// This must be bumped when backwards incompatible changes
|
||||
/// are made to the types and protocol.
|
||||
pub const CODEC_VERSION: usize = 17;
|
||||
pub const CODEC_VERSION: usize = 18;
|
||||
|
||||
// Defines the Pdu enum.
|
||||
// Each struct has an explicit identifying number.
|
||||
|
@ -7,5 +7,9 @@ repository = "https://github.com/wez/wezterm"
|
||||
description = "Types for working with colors"
|
||||
license = "MIT"
|
||||
|
||||
[features]
|
||||
use_serde = ["serde"]
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "1.4"
|
||||
serde = {version="1.0", features = ["derive"], optional=true}
|
||||
|
@ -1,3 +1,5 @@
|
||||
#[cfg(feature = "use_serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
@ -209,6 +211,7 @@ impl SrgbaPixel {
|
||||
|
||||
/// A pixel value encoded as SRGBA RGBA values in f32 format (range: 0.0-1.0)
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
|
||||
pub struct SrgbaTuple(pub f32, pub f32, pub f32, pub f32);
|
||||
|
||||
impl From<(f32, f32, f32, f32)> for SrgbaTuple {
|
||||
@ -230,6 +233,11 @@ lazy_static::lazy_static! {
|
||||
fn build_colors() -> HashMap<String, SrgbaTuple> {
|
||||
let mut map = HashMap::new();
|
||||
let rgb_txt = include_str!("rgb.txt");
|
||||
|
||||
map.insert("transparent".to_string(), SrgbaTuple(0., 0., 0., 0.));
|
||||
map.insert("none".to_string(), SrgbaTuple(0., 0., 0., 0.));
|
||||
map.insert("clear".to_string(), SrgbaTuple(0., 0., 0., 0.));
|
||||
|
||||
for line in rgb_txt.lines() {
|
||||
let mut fields = line.split_ascii_whitespace();
|
||||
let red = fields.next().unwrap();
|
||||
@ -289,6 +297,16 @@ impl SrgbaTuple {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_rgba_string(self) -> String {
|
||||
format!(
|
||||
"rgba:{} {} {} {}%",
|
||||
(self.0 * 255.) as u8,
|
||||
(self.1 * 255.) as u8,
|
||||
(self.2 * 255.) as u8,
|
||||
(self.3 * 100.) as u8
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a string of the form `rgb:RRRR/GGGG/BBBB`
|
||||
pub fn to_x11_16bit_rgb_string(self) -> String {
|
||||
format!(
|
||||
@ -402,6 +420,31 @@ impl FromStr for SrgbaTuple {
|
||||
let blue = digit!();
|
||||
|
||||
Ok(Self(red, green, blue, 1.0))
|
||||
} else if s.starts_with("rgba:") {
|
||||
let fields: Vec<_> = s[5..].split_ascii_whitespace().collect();
|
||||
if fields.len() == 4 {
|
||||
fn field(s: &str) -> Result<f32, ()> {
|
||||
if s.ends_with('%') {
|
||||
let v: f32 = s[0..s.len() - 1].parse().map_err(|_| ())?;
|
||||
Ok(v / 100.)
|
||||
} else {
|
||||
let v: f32 = s.parse().map_err(|_| ())?;
|
||||
if v > 255.0 || v < 0. {
|
||||
Err(())
|
||||
} else {
|
||||
Ok(v / 255.)
|
||||
}
|
||||
}
|
||||
}
|
||||
let r: f32 = field(fields[0])?;
|
||||
let g: f32 = field(fields[1])?;
|
||||
let b: f32 = field(fields[2])?;
|
||||
let a: f32 = field(fields[3])?;
|
||||
|
||||
Ok(Self(r, g, b, a))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
} else if s.starts_with("hsl:") {
|
||||
let fields: Vec<_> = s[4..].split_ascii_whitespace().collect();
|
||||
if fields.len() == 3 {
|
||||
@ -431,7 +474,7 @@ impl FromStr for SrgbaTuple {
|
||||
Err(())
|
||||
}
|
||||
} else {
|
||||
Err(())
|
||||
Self::from_named(s).ok_or(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -470,6 +513,20 @@ impl LinearRgba {
|
||||
|
||||
pub const TRANSPARENT: Self = Self::with_components(0., 0., 0., 0.);
|
||||
|
||||
/// Returns true if this color is fully transparent
|
||||
pub fn is_fully_transparent(self) -> bool {
|
||||
self.3 == 0.0
|
||||
}
|
||||
|
||||
/// Returns self, except when self is transparent, in which case returns other
|
||||
pub fn when_fully_transparent(self, other: Self) -> Self {
|
||||
if self.is_fully_transparent() {
|
||||
other
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert to an SRGB u32 pixel
|
||||
pub fn srgba_pixel(self) -> SrgbaPixel {
|
||||
SrgbaPixel::rgba(
|
||||
@ -501,6 +558,20 @@ mod tests {
|
||||
assert_eq!(foo.to_rgb_string(), "#0015ff");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_rgba() {
|
||||
assert_eq!(
|
||||
SrgbaTuple::from_str("clear").unwrap().to_rgba_string(),
|
||||
"rgba:0 0 0 0%"
|
||||
);
|
||||
assert_eq!(
|
||||
SrgbaTuple::from_str("rgba:100% 0 0 50%")
|
||||
.unwrap()
|
||||
.to_rgba_string(),
|
||||
"rgba:255 0 0 50%"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_rgb() {
|
||||
assert!(SrgbaTuple::from_str("").is_err());
|
||||
@ -524,12 +595,4 @@ mod tests {
|
||||
let grey = SrgbaTuple::from_str("rgb:f0f0/f0f0/f0f0").unwrap();
|
||||
assert_eq!(grey.to_rgb_string(), "#f0f0f0");
|
||||
}
|
||||
|
||||
#[cfg(feature = "use_serde")]
|
||||
#[test]
|
||||
fn roundtrip_rgbcolor() {
|
||||
let data = varbincode::serialize(&SrgbaTuple::from_named("DarkGreen").unwrap()).unwrap();
|
||||
eprintln!("serialized as {:?}", data);
|
||||
let _decoded: SrgbaTuple = varbincode::deserialize(data.as_slice()).unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ serde = {version="1.0", features = ["rc", "derive"]}
|
||||
serde_json = "1.0"
|
||||
smol = "1.2"
|
||||
terminfo = "0.7"
|
||||
termwiz = { path = "../termwiz" }
|
||||
termwiz = { path = "../termwiz", features=["use_serde"] }
|
||||
toml = "0.5"
|
||||
umask = { path = "../umask" }
|
||||
unicode-segmentation = "1.8"
|
||||
|
@ -1,8 +1,9 @@
|
||||
use crate::lua::{format_as_escapes, FormatItem};
|
||||
use crate::*;
|
||||
use luahelper::impl_lua_conversion;
|
||||
use std::str::FromStr;
|
||||
use termwiz::cell::CellAttributes;
|
||||
pub use termwiz::color::{ColorSpec, RgbColor};
|
||||
pub use termwiz::color::{ColorSpec, RgbColor, SrgbaTuple};
|
||||
|
||||
#[derive(Debug, Copy, Deserialize, Serialize, Clone)]
|
||||
pub struct HsbTransform {
|
||||
@ -44,6 +45,35 @@ where
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize, Serialize, Clone, Copy)]
|
||||
#[serde(try_from = "String", into = "String")]
|
||||
pub struct RgbaColor {
|
||||
#[serde(flatten)]
|
||||
color: SrgbaTuple,
|
||||
}
|
||||
|
||||
impl Into<String> for RgbaColor {
|
||||
fn into(self) -> String {
|
||||
self.color.to_rgb_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<SrgbaTuple> for RgbaColor {
|
||||
fn into(self) -> SrgbaTuple {
|
||||
self.color
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<String> for RgbaColor {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(s: String) -> anyhow::Result<RgbaColor> {
|
||||
Ok(RgbaColor {
|
||||
color: SrgbaTuple::from_str(&s)
|
||||
.map_err(|_| anyhow::anyhow!("failed to parse {} as RgbaColor", &s))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct Palette {
|
||||
/// The text color to use when the attributes are reset to default
|
||||
@ -55,8 +85,8 @@ pub struct Palette {
|
||||
pub cursor_bg: Option<RgbColor>,
|
||||
pub cursor_border: Option<RgbColor>,
|
||||
/// The color of selected text
|
||||
pub selection_fg: Option<RgbColor>,
|
||||
pub selection_bg: Option<RgbColor>,
|
||||
pub selection_fg: Option<RgbaColor>,
|
||||
pub selection_bg: Option<RgbaColor>,
|
||||
/// A list of 8 colors corresponding to the basic ANSI palette
|
||||
pub ansi: Option<[RgbColor; 8]>,
|
||||
/// A list of 8 colors corresponding to bright versions of the
|
||||
@ -87,7 +117,7 @@ impl From<Palette> for wezterm_term::color::ColorPalette {
|
||||
macro_rules! apply_color {
|
||||
($name:ident) => {
|
||||
if let Some($name) = cfg.$name {
|
||||
p.$name = $name;
|
||||
p.$name = $name.into();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::fmt;
|
||||
use std::result::Result;
|
||||
pub use termwiz::color::{AnsiColor, ColorAttribute, RgbColor};
|
||||
pub use termwiz::color::{AnsiColor, ColorAttribute, RgbColor, SrgbaTuple};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct Palette256(pub [RgbColor; 256]);
|
||||
@ -43,7 +43,7 @@ impl std::iter::FromIterator<RgbColor> for Palette256 {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
|
||||
pub struct ColorPalette {
|
||||
pub colors: Palette256,
|
||||
@ -52,8 +52,8 @@ pub struct ColorPalette {
|
||||
pub cursor_fg: RgbColor,
|
||||
pub cursor_bg: RgbColor,
|
||||
pub cursor_border: RgbColor,
|
||||
pub selection_fg: RgbColor,
|
||||
pub selection_bg: RgbColor,
|
||||
pub selection_fg: SrgbaTuple,
|
||||
pub selection_bg: SrgbaTuple,
|
||||
pub scrollbar_thumb: RgbColor,
|
||||
pub split: RgbColor,
|
||||
}
|
||||
@ -114,8 +114,8 @@ impl ColorPalette {
|
||||
cursor_fg: grey_out(self.cursor_fg),
|
||||
cursor_bg: grey_out(self.cursor_bg),
|
||||
cursor_border: grey_out(self.cursor_border),
|
||||
selection_fg: grey_out(self.selection_fg),
|
||||
selection_bg: grey_out(self.selection_bg),
|
||||
selection_fg: grey_out(self.selection_fg.into()).into(),
|
||||
selection_bg: grey_out(self.selection_bg.into()).into(),
|
||||
scrollbar_thumb: grey_out(self.scrollbar_thumb),
|
||||
split: grey_out(self.split),
|
||||
}
|
||||
@ -206,8 +206,8 @@ impl ColorPalette {
|
||||
let cursor_border = RgbColor::new_8bpc(0x52, 0xad, 0x70);
|
||||
let cursor_fg = colors[AnsiColor::Black as usize];
|
||||
|
||||
let selection_fg = colors[AnsiColor::Black as usize];
|
||||
let selection_bg = RgbColor::new_8bpc(0xff, 0xfa, 0xcd);
|
||||
let selection_fg = colors[AnsiColor::Black as usize].into();
|
||||
let selection_bg = RgbColor::new_8bpc(0xff, 0xfa, 0xcd).into();
|
||||
|
||||
let scrollbar_thumb = RgbColor::new_8bpc(0x22, 0x22, 0x22);
|
||||
let split = RgbColor::new_8bpc(0x44, 0x44, 0x44);
|
||||
|
@ -800,13 +800,13 @@ impl<'a> Performer<'a> {
|
||||
ColorOrQuery::Query => {
|
||||
let response = OperatingSystemCommand::ChangeDynamicColors(
|
||||
which_color,
|
||||
vec![ColorOrQuery::Color(self.palette().$name)],
|
||||
vec![ColorOrQuery::Color(self.palette().$name.into())],
|
||||
);
|
||||
log::trace!("Color Query response {:?}", response);
|
||||
write!(self.writer, "{}", response).ok();
|
||||
self.writer.flush().ok();
|
||||
}
|
||||
ColorOrQuery::Color(c) => self.palette_mut().$name = c,
|
||||
ColorOrQuery::Color(c) => self.palette_mut().$name = c.into(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ wezterm-color-types = { path = "../color-types" }
|
||||
|
||||
[features]
|
||||
widgets = ["cassowary", "fnv"]
|
||||
use_serde = ["serde"]
|
||||
use_serde = ["serde", "wezterm-color-types/use_serde"]
|
||||
use_image = ["image"]
|
||||
docs = ["widgets", "use_serde"]
|
||||
|
||||
|
@ -60,6 +60,12 @@ pub struct RgbColor {
|
||||
bits: u32,
|
||||
}
|
||||
|
||||
impl Into<SrgbaTuple> for RgbColor {
|
||||
fn into(self) -> SrgbaTuple {
|
||||
self.to_tuple_rgba()
|
||||
}
|
||||
}
|
||||
|
||||
impl RgbColor {
|
||||
/// Construct a color from discrete red, green, blue values
|
||||
/// in the range 0-255.
|
||||
|
@ -1276,8 +1276,8 @@ impl super::TermWindow {
|
||||
let selrange = self.selection(pos.pane.pane_id()).range.clone();
|
||||
|
||||
let start = Instant::now();
|
||||
let selection_fg = rgbcolor_to_window_color(palette.selection_fg);
|
||||
let selection_bg = rgbcolor_to_window_color(palette.selection_bg);
|
||||
let selection_fg = palette.selection_fg.to_linear();
|
||||
let selection_bg = palette.selection_bg.to_linear();
|
||||
let cursor_fg = rgbcolor_to_window_color(palette.cursor_fg);
|
||||
let cursor_bg = rgbcolor_to_window_color(palette.cursor_bg);
|
||||
for (line_idx, line) in lines.iter().enumerate() {
|
||||
@ -2495,9 +2495,11 @@ impl super::TermWindow {
|
||||
visibility,
|
||||
) {
|
||||
// Selected text overrides colors
|
||||
(true, _, _, CursorVisibility::Hidden) => {
|
||||
(params.selection_fg, params.selection_bg, params.cursor_bg)
|
||||
}
|
||||
(true, _, _, CursorVisibility::Hidden) => (
|
||||
params.selection_fg.when_fully_transparent(params.fg_color),
|
||||
params.selection_bg,
|
||||
params.cursor_bg,
|
||||
),
|
||||
// block Cursor cell overrides colors
|
||||
(
|
||||
_,
|
||||
|
Loading…
Reference in New Issue
Block a user