mirror of
https://github.com/wez/wezterm.git
synced 2024-11-22 22:42:48 +03:00
add wezterm.color module for working with colors
wezterm.color.parse() returns a color object that can be assigned in the wezterm color config, and that can be used to adjust hue, saturation and lightness, as well as calculate harmonizing colors (complements, triads, squares) from the RGB/HSL color wheel.
This commit is contained in:
parent
4161e67f60
commit
463ca2fa29
1
.gitignore
vendored
1
.gitignore
vendored
@ -20,6 +20,7 @@
|
||||
/docs/config/lua/mux-events/index.md
|
||||
/docs/config/lua/mux-window/index.md
|
||||
/docs/config/lua/pane/index.md
|
||||
/docs/config/lua/wezterm.color/index.md
|
||||
/docs/config/lua/wezterm.gui/index.md
|
||||
/docs/config/lua/wezterm.mux/index.md
|
||||
/docs/config/lua/wezterm/index.md
|
||||
|
18
Cargo.lock
generated
18
Cargo.lock
generated
@ -607,6 +607,16 @@ dependencies = [
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color-funcs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"config",
|
||||
"csscolorparser",
|
||||
"luahelper",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
@ -851,6 +861,7 @@ version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841475b11553e394b89eebb5cdfe8ca0b960005ad464bca369ade02a62da053e"
|
||||
dependencies = [
|
||||
"lab",
|
||||
"phf 0.10.1",
|
||||
]
|
||||
|
||||
@ -1092,6 +1103,7 @@ dependencies = [
|
||||
"battery",
|
||||
"chrono",
|
||||
"cocoa",
|
||||
"color-funcs",
|
||||
"config",
|
||||
"dirs-next",
|
||||
"env_logger",
|
||||
@ -1898,6 +1910,12 @@ dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lab"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
|
@ -316,6 +316,10 @@ TOC = [
|
||||
"module: wezterm",
|
||||
"config/lua/wezterm",
|
||||
),
|
||||
Gen(
|
||||
"module: wezterm.color",
|
||||
"config/lua/wezterm.color",
|
||||
),
|
||||
Gen(
|
||||
"module: wezterm.gui",
|
||||
"config/lua/wezterm.gui",
|
||||
|
@ -333,6 +333,129 @@ impl SrgbaTuple {
|
||||
(self.2 * 65535.) as u16
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_hsla(self) -> (f64, f64, f64, f64) {
|
||||
csscolorparser::Color::from_rgba(self.0.into(), self.1.into(), self.2.into(), self.3.into())
|
||||
.to_hsla()
|
||||
}
|
||||
|
||||
pub fn from_hsla(h: f64, s: f64, l: f64, a: f64) -> Self {
|
||||
let (r, g, b, a) = csscolorparser::Color::from_hsla(h, s, l, a).rgba();
|
||||
Self(r as f32, g as f32, b as f32, a as f32)
|
||||
}
|
||||
|
||||
/// Scale the color towards the maximum saturation by factor, a value ranging from 0.0 to 1.0.
|
||||
pub fn saturate(&self, factor: f64) -> Self {
|
||||
let (h, s, l, a) = self.to_hsla();
|
||||
let s = apply_scale(s, factor);
|
||||
Self::from_hsla(h, s, l, a)
|
||||
}
|
||||
|
||||
/// Increase the saturation by amount, a value ranging from 0.0 to 1.0.
|
||||
pub fn saturate_fixed(&self, amount: f64) -> Self {
|
||||
let (h, s, l, a) = self.to_hsla();
|
||||
let s = apply_fixed(s, amount);
|
||||
Self::from_hsla(h, s, l, a)
|
||||
}
|
||||
|
||||
/// Scale the color towards the maximum lightness by factor, a value ranging from 0.0 to 1.0
|
||||
pub fn lighten(&self, factor: f64) -> Self {
|
||||
let (h, s, l, a) = self.to_hsla();
|
||||
let l = apply_scale(l, factor);
|
||||
Self::from_hsla(h, s, l, a)
|
||||
}
|
||||
|
||||
/// Lighten the color by amount, a value ranging from 0.0 to 1.0
|
||||
pub fn lighten_fixed(&self, amount: f64) -> Self {
|
||||
let (h, s, l, a) = self.to_hsla();
|
||||
let l = apply_fixed(l, amount);
|
||||
Self::from_hsla(h, s, l, a)
|
||||
}
|
||||
|
||||
/// Find the complementary color: the one opposite it on the color wheel
|
||||
pub fn adjust_hue_fixed(&self, amount: f64) -> Self {
|
||||
let (h, s, l, a) = self.to_hsla();
|
||||
let h = normalize_angle(h + amount);
|
||||
Self::from_hsla(h, s, l, a)
|
||||
}
|
||||
|
||||
pub fn complement(&self) -> Self {
|
||||
self.adjust_hue_fixed(180.)
|
||||
}
|
||||
|
||||
pub fn triad(&self) -> (Self, Self) {
|
||||
(self.adjust_hue_fixed(120.), self.adjust_hue_fixed(-120.))
|
||||
}
|
||||
|
||||
pub fn square(&self) -> (Self, Self, Self) {
|
||||
(
|
||||
self.adjust_hue_fixed(90.),
|
||||
self.adjust_hue_fixed(270.),
|
||||
self.adjust_hue_fixed(180.),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// From "Paint Inspired Color Compositing" by Gosset and Chen
|
||||
/// <https://stackoverflow.com/a/14116553/149111 >has a python
|
||||
/// implementation
|
||||
/// <https://bahamas10.github.io/ryb/> has a copy of the paper
|
||||
/// itself at <https://bahamas10.github.io/ryb/assets/ryb.pdf>
|
||||
pub fn ryb_to_rgb(r: f32, y: f32, b: f32) -> (f32, f32, f32) {
|
||||
fn cubic(t: f32, a: f32, b: f32) -> f32 {
|
||||
let weight = t * t * (3. - 2. * t);
|
||||
a + weight * (b - a)
|
||||
}
|
||||
|
||||
let red = {
|
||||
let x0 = cubic(b, 1.0, 0.163);
|
||||
let x1 = cubic(b, 1.0, 0.0);
|
||||
let x2 = cubic(b, 1.0, 0.5);
|
||||
let x3 = cubic(b, 1.0, 0.2);
|
||||
let y0 = cubic(y, x0, x1);
|
||||
let y1 = cubic(y, x2, x3);
|
||||
cubic(r, y0, y1)
|
||||
};
|
||||
|
||||
let green = {
|
||||
let x0 = cubic(b, 1.0, 0.373);
|
||||
let x1 = cubic(b, 1.0, 0.66);
|
||||
let x2 = cubic(b, 0.0, 0.0);
|
||||
let x3 = cubic(b, 0.5, 0.094);
|
||||
let y0 = cubic(y, x0, x1);
|
||||
let y1 = cubic(y, x2, x3);
|
||||
cubic(r, y0, y1)
|
||||
};
|
||||
|
||||
let blue = {
|
||||
let x0 = cubic(b, 1.0, 0.6);
|
||||
let x1 = cubic(b, 0.0, 0.2);
|
||||
let x2 = cubic(b, 0.0, 0.5);
|
||||
let x3 = cubic(b, 0.0, 0.0);
|
||||
let y0 = cubic(y, x0, x1);
|
||||
let y1 = cubic(y, x2, x3);
|
||||
cubic(r, y0, y1)
|
||||
};
|
||||
|
||||
(red, green, blue)
|
||||
}
|
||||
|
||||
fn normalize_angle(t: f64) -> f64 {
|
||||
let mut t = t % 360.0;
|
||||
if t < 0.0 {
|
||||
t += 360.0;
|
||||
}
|
||||
t
|
||||
}
|
||||
|
||||
fn apply_scale(current: f64, factor: f64) -> f64 {
|
||||
let difference = if factor >= 0. { 1.0 - current } else { current };
|
||||
let delta = difference.max(0.) * factor;
|
||||
(current + delta).max(0.)
|
||||
}
|
||||
|
||||
fn apply_fixed(current: f64, amount: f64) -> f64 {
|
||||
(current + amount).max(0.)
|
||||
}
|
||||
|
||||
impl Hash for SrgbaTuple {
|
||||
|
@ -18,6 +18,7 @@ As features stabilize some brief notes about them will accumulate here.
|
||||
* [window:set_position](config/lua/window/set_position.md) method for controlling window position.
|
||||
* [window:maximize](config/lua/window/maximize.md) and [window:restore](config/lua/window/restore.md) methods for controlling window maximization state.
|
||||
* [window:get_selection_escapes_for_pane](config/lua/window/get_selection_escapes_for_pane.md) method for getting the current selection including escape sequences. [#2223](https://github.com/wez/wezterm/issues/2223)
|
||||
* New [wezterm.color](config/lua/wezterm.color/index.md) module for working with colors.
|
||||
* New [wezterm.gui](config/lua/wezterm.gui/index.md) module and [mux_window:gui_window](config/lua/mux-window/gui_window.md) method.
|
||||
* New [wezterm.gui.screens()](config/lua/wezterm.gui/screens.md) function for getting information about the available screens/monitors/displays
|
||||
* You may now use [wezterm.format](config/lua/wezterm/format.md) (or otherwise use strings with escape sequences) in the labels of the [Launcher Menu](config/launch.md#the-launcher-menu).
|
||||
|
6
docs/config/lua/wezterm.color/index.markdown
Normal file
6
docs/config/lua/wezterm.color/index.markdown
Normal file
@ -0,0 +1,6 @@
|
||||
*Since: nightly builds only*
|
||||
|
||||
The `wezterm.color` module exposes functions that work with colors.
|
||||
|
||||
## Available functions, constants
|
||||
|
117
docs/config/lua/wezterm.color/parse.md
Normal file
117
docs/config/lua/wezterm.color/parse.md
Normal file
@ -0,0 +1,117 @@
|
||||
# wezterm.color.parse(string)
|
||||
|
||||
*Since: nightly builds only*
|
||||
|
||||
Parses the passed color and returns an `RgbaColor` object.
|
||||
`RgbaColor` objects evaluate as strings but have a number of methods
|
||||
that allow transforming colors.
|
||||
|
||||
```
|
||||
> wezterm.color.parse("black")
|
||||
#000000
|
||||
```
|
||||
|
||||
This example picks a foreground color, computes its complement
|
||||
and darkens it to use it as a background color:
|
||||
|
||||
```lua
|
||||
local wezterm = require 'wezterm'
|
||||
|
||||
local fg = wezterm.color.parse("yellow")
|
||||
local bg = fg:complement():darken(0.2)
|
||||
|
||||
return {
|
||||
colors = {
|
||||
foreground = fg,
|
||||
background = bg,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## `color:complement()`
|
||||
|
||||
*Since: nightly builds only*
|
||||
|
||||
Returns the complement of the color. The complement is computed
|
||||
by converting to HSL, rotating by 180 degrees and converting back
|
||||
to RGBA.
|
||||
|
||||
## `color:triad()`
|
||||
|
||||
*Since: nightly builds only*
|
||||
|
||||
Returns the other two colors that form a triad. The other colors
|
||||
are at +/- 120 degrees in the HSL color wheel.
|
||||
|
||||
```lua
|
||||
local a, b = wezterm:color.parse("yellow"):triad()
|
||||
```
|
||||
|
||||
## `color:square()`
|
||||
|
||||
*Since: nightly builds only*
|
||||
|
||||
Returns the other three colors that form a square. The other colors
|
||||
are 90 degrees apart on the HSL color wheel.
|
||||
|
||||
```lua
|
||||
local a, b, = wezterm:color.parse("yellow"):square()
|
||||
```
|
||||
|
||||
## `color:saturate(factor)`
|
||||
|
||||
*Since: nightly builds only*
|
||||
|
||||
Scales the color towards the maximum saturation by the provided factor, which
|
||||
should be in the range `0.0` through `1.0`.
|
||||
|
||||
## `color:saturate_fixed(amount)`
|
||||
|
||||
*Since: nightly builds only*
|
||||
|
||||
Increase the saturation by amount, a value ranging from `0.0` to `1.0`.
|
||||
|
||||
## `color:desaturate(factor)`
|
||||
|
||||
*Since: nightly builds only*
|
||||
|
||||
Scales the color towards the minimum saturation by the provided factor, which
|
||||
should be in the range `0.0` through `1.0`.
|
||||
|
||||
## `color:desaturate_fixed(amount)`
|
||||
|
||||
*Since: nightly builds only*
|
||||
|
||||
Decrease the saturation by amount, a value ranging from `0.0` to `1.0`.
|
||||
|
||||
## `color:lighten(factor)`
|
||||
|
||||
*Since: nightly builds only*
|
||||
|
||||
Scales the color towards the maximum lightness by the provided factor, which
|
||||
should be in the range `0.0` through `1.0`.
|
||||
|
||||
## `color:lighten_fixed(amount)`
|
||||
|
||||
*Since: nightly builds only*
|
||||
|
||||
Increase the lightness by amount, a value ranging from `0.0` to `1.0`.
|
||||
|
||||
## `color:darken(factor)`
|
||||
|
||||
*Since: nightly builds only*
|
||||
|
||||
Scales the color towards the minimum lightness by the provided factor, which
|
||||
should be in the range `0.0` through `1.0`.
|
||||
|
||||
## `color:darken_fixed(amount)`
|
||||
|
||||
*Since: nightly builds only*
|
||||
|
||||
Decrease the lightness by amount, a value ranging from `0.0` to `1.0`.
|
||||
|
||||
## `color:adjust_hue_fixed(degrees)`
|
||||
|
||||
*Since: nightly builds only*
|
||||
|
||||
Adjust the hue angle by the specified number of degrees.
|
@ -17,6 +17,7 @@ log = "0.4"
|
||||
env_logger = "0.9"
|
||||
termwiz = { path = "../termwiz" }
|
||||
battery = { path = "../lua-api-crates/battery" }
|
||||
color-funcs = { path = "../lua-api-crates/color-funcs" }
|
||||
termwiz-funcs = { path = "../lua-api-crates/termwiz-funcs" }
|
||||
logging = { path = "../lua-api-crates/logging" }
|
||||
mux-lua = { path = "../lua-api-crates/mux" }
|
||||
|
@ -161,6 +161,7 @@ fn register_panic_hook() {
|
||||
fn register_lua_modules() {
|
||||
for func in [
|
||||
battery::register,
|
||||
color_funcs::register,
|
||||
termwiz_funcs::register,
|
||||
logging::register,
|
||||
mux_lua::register,
|
||||
|
12
lua-api-crates/color-funcs/Cargo.toml
Normal file
12
lua-api-crates/color-funcs/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "color-funcs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
config = { path = "../../config" }
|
||||
csscolorparser = {version="0.6", features=["lab"]}
|
||||
luahelper = { path = "../../luahelper" }
|
89
lua-api-crates/color-funcs/src/lib.rs
Normal file
89
lua-api-crates/color-funcs/src/lib.rs
Normal file
@ -0,0 +1,89 @@
|
||||
use config::lua::get_or_create_sub_module;
|
||||
use config::lua::mlua::{self, Lua, MetaMethod, UserData, UserDataMethods};
|
||||
use config::RgbaColor;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ColorWrap(RgbaColor);
|
||||
|
||||
impl ColorWrap {
|
||||
pub fn complement(&self) -> Self {
|
||||
Self(self.0.complement().into())
|
||||
}
|
||||
pub fn triad(&self) -> (Self, Self) {
|
||||
let (a, b) = self.0.triad();
|
||||
(Self(a.into()), Self(b.into()))
|
||||
}
|
||||
pub fn square(&self) -> (Self, Self, Self) {
|
||||
let (a, b, c) = self.0.square();
|
||||
(Self(a.into()), Self(b.into()), Self(c.into()))
|
||||
}
|
||||
pub fn saturate(&self, factor: f64) -> Self {
|
||||
Self(self.0.saturate(factor).into())
|
||||
}
|
||||
pub fn saturate_fixed(&self, amount: f64) -> Self {
|
||||
Self(self.0.saturate_fixed(amount).into())
|
||||
}
|
||||
pub fn lighten(&self, factor: f64) -> Self {
|
||||
Self(self.0.lighten(factor).into())
|
||||
}
|
||||
pub fn lighten_fixed(&self, amount: f64) -> Self {
|
||||
Self(self.0.lighten_fixed(amount).into())
|
||||
}
|
||||
pub fn adjust_hue_fixed(&self, amount: f64) -> Self {
|
||||
Self(self.0.adjust_hue_fixed(amount).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for ColorWrap {
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(MetaMethod::ToString, |_, this, _: ()| {
|
||||
let s: String = this.0.into();
|
||||
Ok(s)
|
||||
});
|
||||
methods.add_meta_method(MetaMethod::Eq, |_, this, other: ColorWrap| {
|
||||
Ok(this.0 == other.0)
|
||||
});
|
||||
methods.add_method("complement", |_, this, _: ()| Ok(this.complement()));
|
||||
methods.add_method("triad", |_, this, _: ()| Ok(this.triad()));
|
||||
methods.add_method("square", |_, this, _: ()| Ok(this.square()));
|
||||
methods.add_method("saturate", |_, this, factor: f64| Ok(this.saturate(factor)));
|
||||
|
||||
methods.add_method("desaturate", |_, this, factor: f64| {
|
||||
Ok(this.saturate(-factor))
|
||||
});
|
||||
|
||||
methods.add_method("saturate_fixed", |_, this, amount: f64| {
|
||||
Ok(this.saturate_fixed(amount))
|
||||
});
|
||||
methods.add_method("desaturate_fixed", |_, this, amount: f64| {
|
||||
Ok(this.saturate_fixed(-amount))
|
||||
});
|
||||
|
||||
methods.add_method("lighten", |_, this, factor: f64| Ok(this.lighten(factor)));
|
||||
|
||||
methods.add_method("darken", |_, this, factor: f64| Ok(this.lighten(-factor)));
|
||||
|
||||
methods.add_method("lighten_fixed", |_, this, amount: f64| {
|
||||
Ok(this.lighten_fixed(amount))
|
||||
});
|
||||
methods.add_method("darken_fixed", |_, this, amount: f64| {
|
||||
Ok(this.lighten_fixed(-amount))
|
||||
});
|
||||
|
||||
methods.add_method("adjust_hue_fixed", |_, this, amount: f64| {
|
||||
Ok(this.adjust_hue_fixed(amount))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(lua: &Lua) -> anyhow::Result<()> {
|
||||
let color = get_or_create_sub_module(lua, "color")?;
|
||||
color.set("parse", lua.create_function(parse_color)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_color<'lua>(_: &'lua Lua, spec: String) -> mlua::Result<ColorWrap> {
|
||||
let color =
|
||||
RgbaColor::try_from(spec).map_err(|err| mlua::Error::external(format!("{err:#}")))?;
|
||||
Ok(ColorWrap(color))
|
||||
}
|
Loading…
Reference in New Issue
Block a user