theme: move shared state into workspace crate
This commit is contained in:
parent
854e0474be
commit
37ec1a4a3f
11 changed files with 977 additions and 942 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
|
@ -710,6 +710,7 @@ dependencies = [
|
||||||
"jay-eventfd-cache",
|
"jay-eventfd-cache",
|
||||||
"jay-formats",
|
"jay-formats",
|
||||||
"jay-geometry",
|
"jay-geometry",
|
||||||
|
"jay-gfx-types",
|
||||||
"jay-io-uring",
|
"jay-io-uring",
|
||||||
"jay-layout-animation",
|
"jay-layout-animation",
|
||||||
"jay-libinput",
|
"jay-libinput",
|
||||||
|
|
@ -717,6 +718,7 @@ dependencies = [
|
||||||
"jay-pango",
|
"jay-pango",
|
||||||
"jay-pr-caps",
|
"jay-pr-caps",
|
||||||
"jay-sighand",
|
"jay-sighand",
|
||||||
|
"jay-theme",
|
||||||
"jay-time",
|
"jay-time",
|
||||||
"jay-toml-config",
|
"jay-toml-config",
|
||||||
"jay-tracy",
|
"jay-tracy",
|
||||||
|
|
@ -850,6 +852,10 @@ dependencies = [
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jay-gfx-types"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jay-io-uring"
|
name = "jay-io-uring"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -936,6 +942,18 @@ dependencies = [
|
||||||
"uapi",
|
"uapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jay-theme"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"jay-cmm",
|
||||||
|
"jay-config",
|
||||||
|
"jay-gfx-types",
|
||||||
|
"jay-utils",
|
||||||
|
"linearize",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jay-time"
|
name = "jay-time"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,8 @@ members = [
|
||||||
"bugs",
|
"bugs",
|
||||||
"logger",
|
"logger",
|
||||||
"video-types",
|
"video-types",
|
||||||
|
"gfx-types",
|
||||||
|
"theme",
|
||||||
"pango",
|
"pango",
|
||||||
"libinput",
|
"libinput",
|
||||||
"toml-config",
|
"toml-config",
|
||||||
|
|
@ -89,6 +91,8 @@ jay-pr-caps = { version = "0.1.0", path = "pr-caps" }
|
||||||
jay-bugs = { version = "0.1.0", path = "bugs" }
|
jay-bugs = { version = "0.1.0", path = "bugs" }
|
||||||
jay-logger = { version = "0.1.0", path = "logger" }
|
jay-logger = { version = "0.1.0", path = "logger" }
|
||||||
jay-video-types = { version = "0.1.0", path = "video-types" }
|
jay-video-types = { version = "0.1.0", path = "video-types" }
|
||||||
|
jay-gfx-types = { version = "0.1.0", path = "gfx-types" }
|
||||||
|
jay-theme = { version = "0.1.0", path = "theme" }
|
||||||
jay-pango = { version = "0.1.0", path = "pango" }
|
jay-pango = { version = "0.1.0", path = "pango" }
|
||||||
jay-libinput = { version = "0.1.0", path = "libinput" }
|
jay-libinput = { version = "0.1.0", path = "libinput" }
|
||||||
|
|
||||||
|
|
|
||||||
7
gfx-types/Cargo.toml
Normal file
7
gfx-types/Cargo.toml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
[package]
|
||||||
|
name = "jay-gfx-types"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
license = "GPL-3.0-only"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
7
gfx-types/src/lib.rs
Normal file
7
gfx-types/src/lib.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)]
|
||||||
|
pub enum AlphaMode {
|
||||||
|
#[default]
|
||||||
|
PremultipliedElectrical,
|
||||||
|
PremultipliedOptical,
|
||||||
|
Straight,
|
||||||
|
}
|
||||||
|
|
@ -47,6 +47,8 @@ use {
|
||||||
uapi::{OwnedFd, c},
|
uapi::{OwnedFd, c},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use jay_gfx_types::AlphaMode;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Linearize)]
|
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Linearize)]
|
||||||
pub enum GfxApi {
|
pub enum GfxApi {
|
||||||
OpenGl,
|
OpenGl,
|
||||||
|
|
@ -409,14 +411,6 @@ pub enum ResetStatus {
|
||||||
Other(u32),
|
Other(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)]
|
|
||||||
pub enum AlphaMode {
|
|
||||||
#[default]
|
|
||||||
PremultipliedElectrical,
|
|
||||||
PremultipliedOptical,
|
|
||||||
Straight,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait GfxBlendBuffer: Any + Debug {}
|
pub trait GfxBlendBuffer: Any + Debug {}
|
||||||
|
|
||||||
pub trait GfxFramebuffer: Debug {
|
pub trait GfxFramebuffer: Debug {
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,12 @@ pub const AM_PREMULTIPLIED_ELECTRICAL: u32 = 0;
|
||||||
pub const AM_PREMULTIPLIED_OPTICAL: u32 = 1;
|
pub const AM_PREMULTIPLIED_OPTICAL: u32 = 1;
|
||||||
pub const AM_STRAIGHT: u32 = 2;
|
pub const AM_STRAIGHT: u32 = 2;
|
||||||
|
|
||||||
impl AlphaMode {
|
pub trait AlphaModeExt {
|
||||||
pub fn to_vulkan(self) -> u32 {
|
fn to_vulkan(self) -> u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AlphaModeExt for AlphaMode {
|
||||||
|
fn to_vulkan(self) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
AlphaMode::PremultipliedElectrical => AM_PREMULTIPLIED_ELECTRICAL,
|
AlphaMode::PremultipliedElectrical => AM_PREMULTIPLIED_ELECTRICAL,
|
||||||
AlphaMode::PremultipliedOptical => AM_PREMULTIPLIED_OPTICAL,
|
AlphaMode::PremultipliedOptical => AM_PREMULTIPLIED_OPTICAL,
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ use {
|
||||||
crate::{
|
crate::{
|
||||||
gfx_api::AlphaMode,
|
gfx_api::AlphaMode,
|
||||||
gfx_apis::vulkan::{
|
gfx_apis::vulkan::{
|
||||||
VulkanError, descriptor::VulkanDescriptorSetLayout, device::VulkanDevice,
|
VulkanError, alpha_modes::AlphaModeExt, descriptor::VulkanDescriptorSetLayout,
|
||||||
shaders::VulkanShader,
|
device::VulkanDevice, shaders::VulkanShader,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
arrayvec::ArrayVec,
|
arrayvec::ArrayVec,
|
||||||
|
|
|
||||||
12
src/icons.rs
12
src/icons.rs
|
|
@ -222,18 +222,6 @@ impl PathBuilderExt for PathBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<crate::theme::Color> for Color {
|
|
||||||
fn from(v: crate::theme::Color) -> Self {
|
|
||||||
let [r, g, b, a] = v.to_array(Eotf::Gamma22);
|
|
||||||
let mut c = Self::TRANSPARENT;
|
|
||||||
c.set_red(r / a);
|
|
||||||
c.set_green(g / a);
|
|
||||||
c.set_blue(b / a);
|
|
||||||
c.set_alpha(a);
|
|
||||||
c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate_accents(srgb: crate::theme::Color) -> [Color; 2] {
|
fn calculate_accents(srgb: crate::theme::Color) -> [Color; 2] {
|
||||||
let [l, a, b, alpha] = srgb_to_lab(srgb);
|
let [l, a, b, alpha] = srgb_to_lab(srgb);
|
||||||
let l2 = if l < 0.65 { 0.9 } else { l - 0.4 };
|
let l2 = if l < 0.65 { 0.9 } else { l - 0.4 };
|
||||||
|
|
|
||||||
919
src/theme.rs
919
src/theme.rs
|
|
@ -1,918 +1 @@
|
||||||
#![expect(clippy::excessive_precision)]
|
pub use jay_theme::*;
|
||||||
|
|
||||||
use {
|
|
||||||
crate::{
|
|
||||||
cmm::cmm_eotf::{Eotf, bt1886_eotf_args, bt1886_inv_eotf_args},
|
|
||||||
gfx_api::AlphaMode,
|
|
||||||
utils::{clonecell::CloneCell, static_text::StaticText},
|
|
||||||
},
|
|
||||||
jay_config::theme::BarPosition as ConfigBarPosition,
|
|
||||||
linearize::Linearize,
|
|
||||||
num_traits::Float,
|
|
||||||
std::{
|
|
||||||
cell::Cell,
|
|
||||||
cmp::Ordering,
|
|
||||||
ops::{Add, Div, Mul},
|
|
||||||
sync::Arc,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
||||||
pub struct Color {
|
|
||||||
r: f32,
|
|
||||||
g: f32,
|
|
||||||
b: f32,
|
|
||||||
a: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for Color {}
|
|
||||||
|
|
||||||
impl Ord for Color {
|
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
|
||||||
self.r
|
|
||||||
.total_cmp(&other.r)
|
|
||||||
.then_with(|| self.g.total_cmp(&other.g))
|
|
||||||
.then_with(|| self.b.total_cmp(&other.b))
|
|
||||||
.then_with(|| self.a.total_cmp(&other.a))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mul<f32> for Color {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn mul(self, rhs: f32) -> Self::Output {
|
|
||||||
Self {
|
|
||||||
r: self.r * rhs,
|
|
||||||
g: self.g * rhs,
|
|
||||||
b: self.b * rhs,
|
|
||||||
a: self.a * rhs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for Color {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_f32(c: u8) -> f32 {
|
|
||||||
c as f32 / 255f32
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_u8(c: f32) -> u8 {
|
|
||||||
(c * 255f32).round() as u8
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Color {
|
|
||||||
pub const TRANSPARENT: Self = Self {
|
|
||||||
r: 0.0,
|
|
||||||
g: 0.0,
|
|
||||||
b: 0.0,
|
|
||||||
a: 0.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const SOLID_BLACK: Self = Self {
|
|
||||||
r: 0.0,
|
|
||||||
g: 0.0,
|
|
||||||
b: 0.0,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn new(
|
|
||||||
eotf: Eotf,
|
|
||||||
alpha_mode: AlphaMode,
|
|
||||||
mut r: f32,
|
|
||||||
mut g: f32,
|
|
||||||
mut b: f32,
|
|
||||||
a: f32,
|
|
||||||
) -> Self {
|
|
||||||
if eotf == Eotf::Linear {
|
|
||||||
if alpha_mode == AlphaMode::Straight && a < 1.0 {
|
|
||||||
for c in [&mut r, &mut g, &mut b] {
|
|
||||||
*c *= a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Self { r, g, b, a };
|
|
||||||
}
|
|
||||||
if alpha_mode == AlphaMode::PremultipliedElectrical && a < 1.0 && a > 0.0 {
|
|
||||||
for c in [&mut r, &mut g, &mut b] {
|
|
||||||
*c /= a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[inline(always)]
|
|
||||||
fn linear(c: f32) -> f32 {
|
|
||||||
c
|
|
||||||
}
|
|
||||||
fn st2084_pq(c: f32) -> f32 {
|
|
||||||
let cp = c.powf(1.0 / 78.84375);
|
|
||||||
let num = (cp - 0.8359375).max(0.0);
|
|
||||||
let den = 18.8515625 - 18.6875 * cp;
|
|
||||||
(num / den).powf(1.0 / 0.1593017578125)
|
|
||||||
}
|
|
||||||
fn st240(c: f32) -> f32 {
|
|
||||||
if c < 0.0913 {
|
|
||||||
c / 4.0
|
|
||||||
} else {
|
|
||||||
((c + 0.1115) / 1.1115).powf(1.0 / 0.45)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn log100(c: f32) -> f32 {
|
|
||||||
10.0.powf(2.0 * (c - 1.0))
|
|
||||||
}
|
|
||||||
fn log316(c: f32) -> f32 {
|
|
||||||
10.0.powf(2.5 * (c - 1.0))
|
|
||||||
}
|
|
||||||
fn st428(c: f32) -> f32 {
|
|
||||||
c.powf(2.6) * 52.37 / 48.0
|
|
||||||
}
|
|
||||||
fn gamma22(c: f32) -> f32 {
|
|
||||||
c.signum() * c.abs().powf(2.2)
|
|
||||||
}
|
|
||||||
fn gamma24(c: f32) -> f32 {
|
|
||||||
c.signum() * c.abs().powf(2.4)
|
|
||||||
}
|
|
||||||
fn gamma28(c: f32) -> f32 {
|
|
||||||
c.signum() * c.abs().powf(2.8)
|
|
||||||
}
|
|
||||||
fn compound_power_2_4(c: f32) -> f32 {
|
|
||||||
if c < 0.04045 {
|
|
||||||
c / 12.92
|
|
||||||
} else {
|
|
||||||
((c + 0.055) / 1.055).powf(2.4)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
macro_rules! convert {
|
|
||||||
($tf:ident) => {{
|
|
||||||
r = $tf(r);
|
|
||||||
g = $tf(g);
|
|
||||||
b = $tf(b);
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
match eotf {
|
|
||||||
Eotf::Linear => convert!(linear),
|
|
||||||
Eotf::St2084Pq => convert!(st2084_pq),
|
|
||||||
Eotf::Bt1886(c) => {
|
|
||||||
let [a1, a2, a3, a4] = bt1886_eotf_args(c);
|
|
||||||
let bt1886 = |c: f32| -> f32 { a1 * ((a2 * c + a3).powf(2.4) - a4) };
|
|
||||||
convert!(bt1886)
|
|
||||||
}
|
|
||||||
Eotf::Gamma22 => convert!(gamma22),
|
|
||||||
Eotf::Gamma24 => convert!(gamma24),
|
|
||||||
Eotf::Gamma28 => convert!(gamma28),
|
|
||||||
Eotf::St240 => convert!(st240),
|
|
||||||
Eotf::Log100 => convert!(log100),
|
|
||||||
Eotf::Log316 => convert!(log316),
|
|
||||||
Eotf::St428 => convert!(st428),
|
|
||||||
Eotf::Pow(n) => {
|
|
||||||
let e = n.eotf_f32();
|
|
||||||
let pow = |c: f32| -> f32 { c.signum() * c.abs().powf(e) };
|
|
||||||
convert!(pow)
|
|
||||||
}
|
|
||||||
Eotf::CompoundPower24 => convert!(compound_power_2_4),
|
|
||||||
}
|
|
||||||
if alpha_mode != AlphaMode::PremultipliedOptical && a < 1.0 {
|
|
||||||
for c in [&mut r, &mut g, &mut b] {
|
|
||||||
*c *= a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self { r, g, b, a }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_opaque(&self) -> bool {
|
|
||||||
self.a >= 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_gray_srgb(g: u8) -> Self {
|
|
||||||
Self::from_srgb(g, g, g)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_srgb(r: u8, g: u8, b: u8) -> Self {
|
|
||||||
Self::new(
|
|
||||||
Eotf::Gamma22,
|
|
||||||
AlphaMode::PremultipliedOptical,
|
|
||||||
to_f32(r),
|
|
||||||
to_f32(g),
|
|
||||||
to_f32(b),
|
|
||||||
1.0,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_srgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
|
|
||||||
Self::new(
|
|
||||||
Eotf::Gamma22,
|
|
||||||
AlphaMode::PremultipliedElectrical,
|
|
||||||
to_f32(r),
|
|
||||||
to_f32(g),
|
|
||||||
to_f32(b),
|
|
||||||
to_f32(a),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_u32(eotf: Eotf, alpha_mode: AlphaMode, r: u32, g: u32, b: u32, a: u32) -> Self {
|
|
||||||
fn to_f32(c: u32) -> f32 {
|
|
||||||
((c as f64) / (u32::MAX as f64)) as f32
|
|
||||||
}
|
|
||||||
Self::new(eotf, alpha_mode, to_f32(r), to_f32(g), to_f32(b), to_f32(a))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_srgba_straight(r: u8, g: u8, b: u8, a: u8) -> Self {
|
|
||||||
Self::new(
|
|
||||||
Eotf::Gamma22,
|
|
||||||
AlphaMode::Straight,
|
|
||||||
to_f32(r),
|
|
||||||
to_f32(g),
|
|
||||||
to_f32(b),
|
|
||||||
to_f32(a),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_srgba_premultiplied(self) -> [u8; 4] {
|
|
||||||
let [r, g, b, a] = self.to_array(Eotf::Gamma22);
|
|
||||||
[to_u8(r), to_u8(g), to_u8(b), to_u8(a)]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_array(self, eotf: Eotf) -> [f32; 4] {
|
|
||||||
self.to_array2(eotf, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_array2(self, eotf: Eotf, alpha: Option<f32>) -> [f32; 4] {
|
|
||||||
let mut res = [self.r, self.g, self.b, self.a];
|
|
||||||
fn linear(c: f32) -> f32 {
|
|
||||||
c
|
|
||||||
}
|
|
||||||
fn st2084_pq(c: f32) -> f32 {
|
|
||||||
let c = c.clamp(0.0, 1.0);
|
|
||||||
let num = 0.8359375 + 18.8515625 * c.powf(0.1593017578125);
|
|
||||||
let den = 1.0 + 18.6875 * c.powf(0.1593017578125);
|
|
||||||
(num / den).powf(78.84375)
|
|
||||||
}
|
|
||||||
fn st240(c: f32) -> f32 {
|
|
||||||
if c < 0.0228 {
|
|
||||||
4.0 * c
|
|
||||||
} else {
|
|
||||||
1.1115 * c.powf(0.45) - 0.1115
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn log100(c: f32) -> f32 {
|
|
||||||
let c = c.clamp(0.0, 1.0);
|
|
||||||
if c < 0.01 { 0.0 } else { 1.0 + c.log10() / 2.0 }
|
|
||||||
}
|
|
||||||
fn log316(c: f32) -> f32 {
|
|
||||||
let c = c.clamp(0.0, 1.0);
|
|
||||||
if c < 10.0.sqrt() / 1000.0 {
|
|
||||||
0.0
|
|
||||||
} else {
|
|
||||||
1.0 + c.log10() / 2.5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn st428(c: f32) -> f32 {
|
|
||||||
(48.0 * c / 52.37).powf(1.0 / 2.6)
|
|
||||||
}
|
|
||||||
fn gamma22(c: f32) -> f32 {
|
|
||||||
c.signum() * c.abs().powf(1.0 / 2.2)
|
|
||||||
}
|
|
||||||
fn gamma24(c: f32) -> f32 {
|
|
||||||
c.signum() * c.abs().powf(1.0 / 2.4)
|
|
||||||
}
|
|
||||||
fn gamma28(c: f32) -> f32 {
|
|
||||||
c.signum() * c.abs().powf(1.0 / 2.8)
|
|
||||||
}
|
|
||||||
fn compound_power_2_4(c: f32) -> f32 {
|
|
||||||
if c < 0.0031308 {
|
|
||||||
12.92 * c
|
|
||||||
} else {
|
|
||||||
1.055 * c.powf(1.0 / 2.4) - 0.055
|
|
||||||
}
|
|
||||||
}
|
|
||||||
macro_rules! convert {
|
|
||||||
($tf:ident) => {{
|
|
||||||
for c in &mut res[..3] {
|
|
||||||
*c = $tf(*c);
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
if eotf != Eotf::Linear {
|
|
||||||
if self.a < 1.0 && self.a > 0.0 {
|
|
||||||
for c in &mut res[..3] {
|
|
||||||
*c /= self.a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match eotf {
|
|
||||||
Eotf::Linear => convert!(linear),
|
|
||||||
Eotf::St2084Pq => convert!(st2084_pq),
|
|
||||||
Eotf::Bt1886(c) => {
|
|
||||||
let [a1, a2, a3, a4] = bt1886_inv_eotf_args(c);
|
|
||||||
let bt1886 = |c: f32| -> f32 { a1 * ((a2 * c + a3).powf(1.0 / 2.4) - a4) };
|
|
||||||
convert!(bt1886)
|
|
||||||
}
|
|
||||||
Eotf::Gamma22 => convert!(gamma22),
|
|
||||||
Eotf::Gamma24 => convert!(gamma24),
|
|
||||||
Eotf::Gamma28 => convert!(gamma28),
|
|
||||||
Eotf::St240 => convert!(st240),
|
|
||||||
Eotf::Log100 => convert!(log100),
|
|
||||||
Eotf::Log316 => convert!(log316),
|
|
||||||
Eotf::St428 => convert!(st428),
|
|
||||||
Eotf::Pow(n) => {
|
|
||||||
let e = n.inv_eotf_f32();
|
|
||||||
let pow = |c: f32| -> f32 { c.signum() * c.abs().powf(e) };
|
|
||||||
convert!(pow)
|
|
||||||
}
|
|
||||||
Eotf::CompoundPower24 => convert!(compound_power_2_4),
|
|
||||||
}
|
|
||||||
if self.a < 1.0 {
|
|
||||||
for c in &mut res[..3] {
|
|
||||||
*c *= self.a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(a) = alpha {
|
|
||||||
for c in &mut res {
|
|
||||||
*c *= a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn and_then(self, other: &Color) -> Color {
|
|
||||||
Color {
|
|
||||||
r: self.r * (1.0 - other.a) + other.r,
|
|
||||||
g: self.g * (1.0 - other.a) + other.g,
|
|
||||||
b: self.b * (1.0 - other.a) + other.b,
|
|
||||||
a: self.a * (1.0 - other.a) + other.a,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn srgb_to_oklab(self) -> Oklab {
|
|
||||||
if self.a == 0.0 {
|
|
||||||
return Oklab {
|
|
||||||
l: 0.0,
|
|
||||||
a: 0.0,
|
|
||||||
b: 0.0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let [r, g, b, _] = self.to_array2(Eotf::Linear, Some(1.0 / self.a));
|
|
||||||
|
|
||||||
let l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
|
|
||||||
let m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
|
|
||||||
let s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
|
|
||||||
|
|
||||||
let l_ = l.cbrt();
|
|
||||||
let m_ = m.cbrt();
|
|
||||||
let s_ = s.cbrt();
|
|
||||||
|
|
||||||
let l = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_;
|
|
||||||
let a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_;
|
|
||||||
let b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_;
|
|
||||||
|
|
||||||
Oklab { l, a, b }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<jay_config::theme::Color> for Color {
|
|
||||||
fn from(f: jay_config::theme::Color) -> Self {
|
|
||||||
let [r, g, b, a] = f.to_f32_premultiplied();
|
|
||||||
Self::new(
|
|
||||||
Eotf::Gamma22,
|
|
||||||
AlphaMode::PremultipliedElectrical,
|
|
||||||
r,
|
|
||||||
g,
|
|
||||||
b,
|
|
||||||
a,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! colors {
|
|
||||||
($($name:ident = $colors:tt,)*) => {
|
|
||||||
pub struct ThemeColors {
|
|
||||||
$(
|
|
||||||
pub $name: Cell<Color>,
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Linearize)]
|
|
||||||
#[expect(non_camel_case_types)]
|
|
||||||
pub enum ThemeColor {
|
|
||||||
$(
|
|
||||||
$name,
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ThemeColor {
|
|
||||||
pub fn field(self, theme: &Theme) -> &Cell<Color> {
|
|
||||||
let colors = &theme.colors;
|
|
||||||
match self {
|
|
||||||
$(
|
|
||||||
Self::$name => &colors.$name,
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ThemeColors {
|
|
||||||
pub fn reset(&self) {
|
|
||||||
let default = Self::default();
|
|
||||||
$(
|
|
||||||
self.$name.set(default.$name.get());
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ThemeColors {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
$(
|
|
||||||
$name: Cell::new(colors!(@colors $colors)),
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
(@colors ($r:expr, $g:expr, $b:expr)) => {
|
|
||||||
Color::from_srgb($r, $g, $b)
|
|
||||||
};
|
|
||||||
(@colors ($r:expr, $g:expr, $b:expr, $a:expr)) => {
|
|
||||||
Color::from_srgba_straight($r, $g, $b, $a)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
colors! {
|
|
||||||
background = (0x00, 0x10, 0x19),
|
|
||||||
unfocused_title_background = (0x22, 0x22, 0x22),
|
|
||||||
focused_title_background = (0x28, 0x55, 0x77),
|
|
||||||
captured_unfocused_title_background = (0x22, 0x03, 0x03),
|
|
||||||
captured_focused_title_background = (0x77, 0x28, 0x31),
|
|
||||||
focused_inactive_title_background = (0x5f, 0x67, 0x6a),
|
|
||||||
unfocused_title_text = (0x88, 0x88, 0x88),
|
|
||||||
focused_title_text = (0xff, 0xff, 0xff),
|
|
||||||
focused_inactive_title_text = (0xff, 0xff, 0xff),
|
|
||||||
separator = (0x33, 0x33, 0x33),
|
|
||||||
border = (0x3f, 0x47, 0x4a),
|
|
||||||
active_border = (0x28, 0x55, 0x77),
|
|
||||||
bar_background = (0x00, 0x00, 0x00),
|
|
||||||
bar_text = (0xff, 0xff, 0xff),
|
|
||||||
attention_requested_background = (0x23, 0x09, 0x2c),
|
|
||||||
highlight = (0x9d, 0x28, 0xc6, 0x7f),
|
|
||||||
tab_active_background = (0x4c, 0x78, 0x99),
|
|
||||||
tab_active_border = (0x28, 0x55, 0x77),
|
|
||||||
tab_inactive_background = (0x22, 0x22, 0x22),
|
|
||||||
tab_inactive_border = (0x33, 0x33, 0x33),
|
|
||||||
tab_active_text = (0xff, 0xff, 0xff),
|
|
||||||
tab_inactive_text = (0x88, 0x88, 0x88),
|
|
||||||
tab_bar_background = (0x00, 0x00, 0x00, 0x00),
|
|
||||||
tab_attention_background = (0x23, 0x09, 0x2c),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StaticText for ThemeColor {
|
|
||||||
fn text(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
ThemeColor::background => "Background",
|
|
||||||
ThemeColor::unfocused_title_background => "Title Background (unfocused)",
|
|
||||||
ThemeColor::focused_title_background => "Title Background (focused)",
|
|
||||||
ThemeColor::captured_unfocused_title_background => {
|
|
||||||
"Title Background (unfocused, captured)"
|
|
||||||
}
|
|
||||||
ThemeColor::captured_focused_title_background => "Title Background (focused, captured)",
|
|
||||||
ThemeColor::focused_inactive_title_background => "Title Background (focused, inactive)",
|
|
||||||
ThemeColor::unfocused_title_text => "Title Text (unfocused)",
|
|
||||||
ThemeColor::focused_title_text => "Title Text (focused)",
|
|
||||||
ThemeColor::focused_inactive_title_text => "Title Text (focused, inactive)",
|
|
||||||
ThemeColor::separator => "Separator",
|
|
||||||
ThemeColor::border => "Border",
|
|
||||||
ThemeColor::active_border => "Border (active)",
|
|
||||||
ThemeColor::bar_background => "Bar Background",
|
|
||||||
ThemeColor::bar_text => "Bar Text",
|
|
||||||
ThemeColor::attention_requested_background => "Attention Requested",
|
|
||||||
ThemeColor::highlight => "Highlight",
|
|
||||||
ThemeColor::tab_active_background => "Tab Background (active)",
|
|
||||||
ThemeColor::tab_active_border => "Tab Border (active)",
|
|
||||||
ThemeColor::tab_inactive_background => "Tab Background (inactive)",
|
|
||||||
ThemeColor::tab_inactive_border => "Tab Border (inactive)",
|
|
||||||
ThemeColor::tab_active_text => "Tab Text (active)",
|
|
||||||
ThemeColor::tab_inactive_text => "Tab Text (inactive)",
|
|
||||||
ThemeColor::tab_bar_background => "Tab Bar Background",
|
|
||||||
ThemeColor::tab_attention_background => "Tab Attention Background",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ThemeSize {
|
|
||||||
pub val: Cell<i32>,
|
|
||||||
pub set: Cell<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ThemeSize {
|
|
||||||
pub fn get(&self) -> i32 {
|
|
||||||
self.val.get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! sizes {
|
|
||||||
($($name:ident = ($min:expr, $max:expr, $def:expr),)*) => {
|
|
||||||
pub struct ThemeSizes {
|
|
||||||
$(
|
|
||||||
pub $name: ThemeSize,
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Linearize)]
|
|
||||||
#[expect(non_camel_case_types)]
|
|
||||||
pub enum ThemeSized {
|
|
||||||
$(
|
|
||||||
$name,
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ThemeSized {
|
|
||||||
pub fn min(self) -> i32 {
|
|
||||||
match self {
|
|
||||||
$(
|
|
||||||
Self::$name => $min,
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn max(self) -> i32 {
|
|
||||||
match self {
|
|
||||||
$(
|
|
||||||
Self::$name => $max,
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn field(self, theme: &Theme) -> &ThemeSize {
|
|
||||||
let sizes = &theme.sizes;
|
|
||||||
match self {
|
|
||||||
$(
|
|
||||||
Self::$name => &sizes.$name,
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn name(self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
$(
|
|
||||||
Self::$name => stringify!($name),
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ThemeSizes {
|
|
||||||
pub fn reset(&self) {
|
|
||||||
let default = Self::default();
|
|
||||||
$(
|
|
||||||
self.$name.val.set(default.$name.val.get());
|
|
||||||
self.$name.set.set(false);
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ThemeSizes {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
$(
|
|
||||||
$name: ThemeSize {
|
|
||||||
val: Cell::new($def),
|
|
||||||
set: Cell::new(false),
|
|
||||||
},
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ThemeSizes {
|
|
||||||
pub fn bar_height(&self) -> i32 {
|
|
||||||
if self.bar_height.set.get() {
|
|
||||||
self.bar_height.val.get()
|
|
||||||
} else {
|
|
||||||
self.title_height.val.get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bar_separator_width(&self) -> i32 {
|
|
||||||
self.bar_separator_width.get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sizes! {
|
|
||||||
title_height = (0, 1000, 17),
|
|
||||||
bar_height = (0, 1000, 17),
|
|
||||||
border_width = (0, 1000, 4),
|
|
||||||
bar_separator_width = (0, 1000, 1),
|
|
||||||
gap = (0, 1000, 0),
|
|
||||||
title_gap = (0, 1000, 5),
|
|
||||||
tab_bar_height = (0, 1000, 22),
|
|
||||||
tab_bar_padding = (0, 1000, 6),
|
|
||||||
tab_bar_radius = (0, 1000, 6),
|
|
||||||
tab_bar_border_width = (0, 1000, 2),
|
|
||||||
tab_bar_text_padding = (0, 1000, 4),
|
|
||||||
tab_bar_gap = (0, 1000, 4),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StaticText for ThemeSized {
|
|
||||||
fn text(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
ThemeSized::title_height => "Title Height",
|
|
||||||
ThemeSized::bar_height => "Bar Height",
|
|
||||||
ThemeSized::border_width => "Border Width",
|
|
||||||
ThemeSized::bar_separator_width => "Bar Separator Width",
|
|
||||||
ThemeSized::gap => "Gap",
|
|
||||||
ThemeSized::title_gap => "Title Gap",
|
|
||||||
ThemeSized::tab_bar_height => "Tab Bar Height",
|
|
||||||
ThemeSized::tab_bar_padding => "Tab Bar Padding",
|
|
||||||
ThemeSized::tab_bar_radius => "Tab Bar Radius",
|
|
||||||
ThemeSized::tab_bar_border_width => "Tab Bar Border Width",
|
|
||||||
ThemeSized::tab_bar_text_padding => "Tab Bar Text Padding",
|
|
||||||
ThemeSized::tab_bar_gap => "Tab Bar Gap",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const DEFAULT_FONT: &str = "monospace 8";
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default, Linearize)]
|
|
||||||
pub enum BarPosition {
|
|
||||||
#[default]
|
|
||||||
Top,
|
|
||||||
Bottom,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StaticText for BarPosition {
|
|
||||||
fn text(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
BarPosition::Top => "Top",
|
|
||||||
BarPosition::Bottom => "Bottom",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<ConfigBarPosition> for BarPosition {
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn try_from(value: ConfigBarPosition) -> Result<Self, Self::Error> {
|
|
||||||
let v = match value {
|
|
||||||
ConfigBarPosition::Top => Self::Top,
|
|
||||||
ConfigBarPosition::Bottom => Self::Bottom,
|
|
||||||
_ => return Err(()),
|
|
||||||
};
|
|
||||||
Ok(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<ConfigBarPosition> for BarPosition {
|
|
||||||
fn into(self) -> ConfigBarPosition {
|
|
||||||
match self {
|
|
||||||
BarPosition::Top => ConfigBarPosition::Top,
|
|
||||||
BarPosition::Bottom => ConfigBarPosition::Bottom,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Per-corner radius for rounded rectangles.
|
|
||||||
///
|
|
||||||
/// Each field specifies the radius (in logical pixels) for one corner.
|
|
||||||
/// A radius of 0 means a square corner.
|
|
||||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
|
||||||
pub struct CornerRadius {
|
|
||||||
pub top_left: f32,
|
|
||||||
pub top_right: f32,
|
|
||||||
pub bottom_right: f32,
|
|
||||||
pub bottom_left: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<f32> for CornerRadius {
|
|
||||||
fn from(value: f32) -> Self {
|
|
||||||
Self {
|
|
||||||
top_left: value,
|
|
||||||
top_right: value,
|
|
||||||
bottom_right: value,
|
|
||||||
bottom_left: value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<CornerRadius> for [f32; 4] {
|
|
||||||
fn from(cr: CornerRadius) -> Self {
|
|
||||||
[cr.top_left, cr.top_right, cr.bottom_right, cr.bottom_left]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CornerRadius {
|
|
||||||
/// Shrink or grow all radii by `width`. Radii that are 0 stay 0 (square
|
|
||||||
/// corners remain square). Negative `width` shrinks; the result is clamped
|
|
||||||
/// to 0.
|
|
||||||
pub fn expanded_by(mut self, width: f32) -> Self {
|
|
||||||
if self.top_left > 0.0 {
|
|
||||||
self.top_left = (self.top_left + width).max(0.0);
|
|
||||||
}
|
|
||||||
if self.top_right > 0.0 {
|
|
||||||
self.top_right = (self.top_right + width).max(0.0);
|
|
||||||
}
|
|
||||||
if self.bottom_right > 0.0 {
|
|
||||||
self.bottom_right = (self.bottom_right + width).max(0.0);
|
|
||||||
}
|
|
||||||
if self.bottom_left > 0.0 {
|
|
||||||
self.bottom_left = (self.bottom_left + width).max(0.0);
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Scale all radii by a factor (e.g. for HiDPI).
|
|
||||||
pub fn scaled_by(self, scale: f32) -> Self {
|
|
||||||
Self {
|
|
||||||
top_left: self.top_left * scale,
|
|
||||||
top_right: self.top_right * scale,
|
|
||||||
bottom_right: self.bottom_right * scale,
|
|
||||||
bottom_left: self.bottom_left * scale,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reduce all radii proportionally so that adjacent corners don't overlap,
|
|
||||||
/// following the CSS spec algorithm.
|
|
||||||
pub fn fit_to(self, width: f32, height: f32) -> Self {
|
|
||||||
let reduction = f32::min(
|
|
||||||
f32::min(
|
|
||||||
width / (self.top_left + self.top_right),
|
|
||||||
width / (self.bottom_left + self.bottom_right),
|
|
||||||
),
|
|
||||||
f32::min(
|
|
||||||
height / (self.top_left + self.bottom_left),
|
|
||||||
height / (self.top_right + self.bottom_right),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
let reduction = f32::min(1.0, reduction);
|
|
||||||
Self {
|
|
||||||
top_left: self.top_left * reduction,
|
|
||||||
top_right: self.top_right * reduction,
|
|
||||||
bottom_right: self.bottom_right * reduction,
|
|
||||||
bottom_left: self.bottom_left * reduction,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_zero(&self) -> bool {
|
|
||||||
self.top_left == 0.0
|
|
||||||
&& self.top_right == 0.0
|
|
||||||
&& self.bottom_right == 0.0
|
|
||||||
&& self.bottom_left == 0.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Horizontal alignment of title text inside tab buttons.
|
|
||||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
||||||
pub enum TabTitleAlign {
|
|
||||||
#[default]
|
|
||||||
Start,
|
|
||||||
Center,
|
|
||||||
End,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Theme {
|
|
||||||
pub colors: ThemeColors,
|
|
||||||
pub sizes: ThemeSizes,
|
|
||||||
pub font: CloneCell<Arc<String>>,
|
|
||||||
pub bar_font: CloneCell<Option<Arc<String>>>,
|
|
||||||
pub title_font: CloneCell<Option<Arc<String>>>,
|
|
||||||
pub default_font: Arc<String>,
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub show_titles: Cell<bool>,
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub floating_titles: Cell<bool>,
|
|
||||||
pub bar_position: Cell<BarPosition>,
|
|
||||||
pub corner_radius: Cell<CornerRadius>,
|
|
||||||
pub autotile_enabled: Cell<bool>,
|
|
||||||
pub tab_title_align: Cell<TabTitleAlign>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Theme {
|
|
||||||
fn default() -> Self {
|
|
||||||
let default_font = Arc::new(DEFAULT_FONT.to_string());
|
|
||||||
Self {
|
|
||||||
colors: Default::default(),
|
|
||||||
sizes: Default::default(),
|
|
||||||
font: CloneCell::new(default_font.clone()),
|
|
||||||
bar_font: Default::default(),
|
|
||||||
title_font: Default::default(),
|
|
||||||
default_font,
|
|
||||||
show_titles: Cell::new(true),
|
|
||||||
floating_titles: Cell::new(false),
|
|
||||||
bar_position: Default::default(),
|
|
||||||
corner_radius: Cell::new(CornerRadius::default()),
|
|
||||||
autotile_enabled: Cell::new(false),
|
|
||||||
tab_title_align: Cell::new(TabTitleAlign::default()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Theme {
|
|
||||||
pub fn bar_font(&self) -> Arc<String> {
|
|
||||||
self.bar_font.get().unwrap_or_else(|| self.font.get())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn title_font(&self) -> Arc<String> {
|
|
||||||
self.title_font.get().unwrap_or_else(|| self.font.get())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn title_height(&self) -> i32 {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn title_plus_underline_height(&self) -> i32 {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub struct Oklch {
|
|
||||||
pub l: f32,
|
|
||||||
pub c: f32,
|
|
||||||
pub h: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub struct Oklab {
|
|
||||||
pub l: f32,
|
|
||||||
pub a: f32,
|
|
||||||
pub b: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Oklab {
|
|
||||||
pub fn to_srgb(self) -> Color {
|
|
||||||
let l_ = self.l + 0.3963377774 * self.a + 0.2158037573 * self.b;
|
|
||||||
let m_ = self.l - 0.1055613458 * self.a - 0.0638541728 * self.b;
|
|
||||||
let s_ = self.l - 0.0894841775 * self.a - 1.2914855480 * self.b;
|
|
||||||
|
|
||||||
let l = l_ * l_ * l_;
|
|
||||||
let m = m_ * m_ * m_;
|
|
||||||
let s = s_ * s_ * s_;
|
|
||||||
|
|
||||||
let r = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
|
|
||||||
let g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
|
|
||||||
let b = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s;
|
|
||||||
|
|
||||||
Color::new(
|
|
||||||
Eotf::Linear,
|
|
||||||
AlphaMode::PremultipliedElectrical,
|
|
||||||
r,
|
|
||||||
g,
|
|
||||||
b,
|
|
||||||
1.0,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_oklch(self) -> Oklch {
|
|
||||||
let c = (self.a * self.a + self.b * self.b).sqrt();
|
|
||||||
let h = self.b.atan2(self.a);
|
|
||||||
|
|
||||||
Oklch { l: self.l, c, h }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Oklch {
|
|
||||||
pub fn to_oklab(self) -> Oklab {
|
|
||||||
let a = self.c * self.h.cos();
|
|
||||||
let b = self.c * self.h.sin();
|
|
||||||
|
|
||||||
Oklab { l: self.l, a, b }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Add for Oklab {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn add(self, rhs: Self) -> Self::Output {
|
|
||||||
Self {
|
|
||||||
l: self.l + rhs.l,
|
|
||||||
a: self.a + rhs.a,
|
|
||||||
b: self.b + rhs.b,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mul<f32> for Oklab {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn mul(self, rhs: f32) -> Self::Output {
|
|
||||||
Self {
|
|
||||||
l: self.l * rhs,
|
|
||||||
a: self.a * rhs,
|
|
||||||
b: self.b * rhs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Div<f32> for Oklab {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn div(self, rhs: f32) -> Self::Output {
|
|
||||||
Self {
|
|
||||||
l: self.l / rhs,
|
|
||||||
a: self.a / rhs,
|
|
||||||
b: self.b / rhs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
14
theme/Cargo.toml
Normal file
14
theme/Cargo.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "jay-theme"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
license = "GPL-3.0-only"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
jay-cmm = { version = "0.1.0", path = "../cmm" }
|
||||||
|
jay-config = { version = "1.10.0", path = "../jay-config" }
|
||||||
|
jay-gfx-types = { version = "0.1.0", path = "../gfx-types" }
|
||||||
|
jay-utils = { version = "0.1.0", path = "../utils" }
|
||||||
|
|
||||||
|
linearize = { version = "0.1.3", features = ["derive"] }
|
||||||
|
num-traits = "0.2.17"
|
||||||
916
theme/src/lib.rs
Normal file
916
theme/src/lib.rs
Normal file
|
|
@ -0,0 +1,916 @@
|
||||||
|
#![expect(clippy::excessive_precision)]
|
||||||
|
|
||||||
|
use {
|
||||||
|
jay_cmm::cmm_eotf::{Eotf, bt1886_eotf_args, bt1886_inv_eotf_args},
|
||||||
|
jay_config::theme::BarPosition as ConfigBarPosition,
|
||||||
|
jay_gfx_types::AlphaMode,
|
||||||
|
jay_utils::{clonecell::CloneCell, static_text::StaticText},
|
||||||
|
linearize::Linearize,
|
||||||
|
num_traits::Float,
|
||||||
|
std::{
|
||||||
|
cell::Cell,
|
||||||
|
cmp::Ordering,
|
||||||
|
ops::{Add, Div, Mul},
|
||||||
|
sync::Arc,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
pub struct Color {
|
||||||
|
r: f32,
|
||||||
|
g: f32,
|
||||||
|
b: f32,
|
||||||
|
a: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Color {}
|
||||||
|
|
||||||
|
impl Ord for Color {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
self.r
|
||||||
|
.total_cmp(&other.r)
|
||||||
|
.then_with(|| self.g.total_cmp(&other.g))
|
||||||
|
.then_with(|| self.b.total_cmp(&other.b))
|
||||||
|
.then_with(|| self.a.total_cmp(&other.a))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul<f32> for Color {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn mul(self, rhs: f32) -> Self::Output {
|
||||||
|
Self {
|
||||||
|
r: self.r * rhs,
|
||||||
|
g: self.g * rhs,
|
||||||
|
b: self.b * rhs,
|
||||||
|
a: self.a * rhs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Color {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_f32(c: u8) -> f32 {
|
||||||
|
c as f32 / 255f32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_u8(c: f32) -> u8 {
|
||||||
|
(c * 255f32).round() as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Color {
|
||||||
|
pub const TRANSPARENT: Self = Self {
|
||||||
|
r: 0.0,
|
||||||
|
g: 0.0,
|
||||||
|
b: 0.0,
|
||||||
|
a: 0.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SOLID_BLACK: Self = Self {
|
||||||
|
r: 0.0,
|
||||||
|
g: 0.0,
|
||||||
|
b: 0.0,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
eotf: Eotf,
|
||||||
|
alpha_mode: AlphaMode,
|
||||||
|
mut r: f32,
|
||||||
|
mut g: f32,
|
||||||
|
mut b: f32,
|
||||||
|
a: f32,
|
||||||
|
) -> Self {
|
||||||
|
if eotf == Eotf::Linear {
|
||||||
|
if alpha_mode == AlphaMode::Straight && a < 1.0 {
|
||||||
|
for c in [&mut r, &mut g, &mut b] {
|
||||||
|
*c *= a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Self { r, g, b, a };
|
||||||
|
}
|
||||||
|
if alpha_mode == AlphaMode::PremultipliedElectrical && a < 1.0 && a > 0.0 {
|
||||||
|
for c in [&mut r, &mut g, &mut b] {
|
||||||
|
*c /= a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
fn linear(c: f32) -> f32 {
|
||||||
|
c
|
||||||
|
}
|
||||||
|
fn st2084_pq(c: f32) -> f32 {
|
||||||
|
let cp = c.powf(1.0 / 78.84375);
|
||||||
|
let num = (cp - 0.8359375).max(0.0);
|
||||||
|
let den = 18.8515625 - 18.6875 * cp;
|
||||||
|
(num / den).powf(1.0 / 0.1593017578125)
|
||||||
|
}
|
||||||
|
fn st240(c: f32) -> f32 {
|
||||||
|
if c < 0.0913 {
|
||||||
|
c / 4.0
|
||||||
|
} else {
|
||||||
|
((c + 0.1115) / 1.1115).powf(1.0 / 0.45)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn log100(c: f32) -> f32 {
|
||||||
|
10.0.powf(2.0 * (c - 1.0))
|
||||||
|
}
|
||||||
|
fn log316(c: f32) -> f32 {
|
||||||
|
10.0.powf(2.5 * (c - 1.0))
|
||||||
|
}
|
||||||
|
fn st428(c: f32) -> f32 {
|
||||||
|
c.powf(2.6) * 52.37 / 48.0
|
||||||
|
}
|
||||||
|
fn gamma22(c: f32) -> f32 {
|
||||||
|
c.signum() * c.abs().powf(2.2)
|
||||||
|
}
|
||||||
|
fn gamma24(c: f32) -> f32 {
|
||||||
|
c.signum() * c.abs().powf(2.4)
|
||||||
|
}
|
||||||
|
fn gamma28(c: f32) -> f32 {
|
||||||
|
c.signum() * c.abs().powf(2.8)
|
||||||
|
}
|
||||||
|
fn compound_power_2_4(c: f32) -> f32 {
|
||||||
|
if c < 0.04045 {
|
||||||
|
c / 12.92
|
||||||
|
} else {
|
||||||
|
((c + 0.055) / 1.055).powf(2.4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
macro_rules! convert {
|
||||||
|
($tf:ident) => {{
|
||||||
|
r = $tf(r);
|
||||||
|
g = $tf(g);
|
||||||
|
b = $tf(b);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
match eotf {
|
||||||
|
Eotf::Linear => convert!(linear),
|
||||||
|
Eotf::St2084Pq => convert!(st2084_pq),
|
||||||
|
Eotf::Bt1886(c) => {
|
||||||
|
let [a1, a2, a3, a4] = bt1886_eotf_args(c);
|
||||||
|
let bt1886 = |c: f32| -> f32 { a1 * ((a2 * c + a3).powf(2.4) - a4) };
|
||||||
|
convert!(bt1886)
|
||||||
|
}
|
||||||
|
Eotf::Gamma22 => convert!(gamma22),
|
||||||
|
Eotf::Gamma24 => convert!(gamma24),
|
||||||
|
Eotf::Gamma28 => convert!(gamma28),
|
||||||
|
Eotf::St240 => convert!(st240),
|
||||||
|
Eotf::Log100 => convert!(log100),
|
||||||
|
Eotf::Log316 => convert!(log316),
|
||||||
|
Eotf::St428 => convert!(st428),
|
||||||
|
Eotf::Pow(n) => {
|
||||||
|
let e = n.eotf_f32();
|
||||||
|
let pow = |c: f32| -> f32 { c.signum() * c.abs().powf(e) };
|
||||||
|
convert!(pow)
|
||||||
|
}
|
||||||
|
Eotf::CompoundPower24 => convert!(compound_power_2_4),
|
||||||
|
}
|
||||||
|
if alpha_mode != AlphaMode::PremultipliedOptical && a < 1.0 {
|
||||||
|
for c in [&mut r, &mut g, &mut b] {
|
||||||
|
*c *= a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self { r, g, b, a }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_opaque(&self) -> bool {
|
||||||
|
self.a >= 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_gray_srgb(g: u8) -> Self {
|
||||||
|
Self::from_srgb(g, g, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_srgb(r: u8, g: u8, b: u8) -> Self {
|
||||||
|
Self::new(
|
||||||
|
Eotf::Gamma22,
|
||||||
|
AlphaMode::PremultipliedOptical,
|
||||||
|
to_f32(r),
|
||||||
|
to_f32(g),
|
||||||
|
to_f32(b),
|
||||||
|
1.0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_srgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||||
|
Self::new(
|
||||||
|
Eotf::Gamma22,
|
||||||
|
AlphaMode::PremultipliedElectrical,
|
||||||
|
to_f32(r),
|
||||||
|
to_f32(g),
|
||||||
|
to_f32(b),
|
||||||
|
to_f32(a),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_u32(eotf: Eotf, alpha_mode: AlphaMode, r: u32, g: u32, b: u32, a: u32) -> Self {
|
||||||
|
fn to_f32(c: u32) -> f32 {
|
||||||
|
((c as f64) / (u32::MAX as f64)) as f32
|
||||||
|
}
|
||||||
|
Self::new(eotf, alpha_mode, to_f32(r), to_f32(g), to_f32(b), to_f32(a))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_srgba_straight(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||||
|
Self::new(
|
||||||
|
Eotf::Gamma22,
|
||||||
|
AlphaMode::Straight,
|
||||||
|
to_f32(r),
|
||||||
|
to_f32(g),
|
||||||
|
to_f32(b),
|
||||||
|
to_f32(a),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_srgba_premultiplied(self) -> [u8; 4] {
|
||||||
|
let [r, g, b, a] = self.to_array(Eotf::Gamma22);
|
||||||
|
[to_u8(r), to_u8(g), to_u8(b), to_u8(a)]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_array(self, eotf: Eotf) -> [f32; 4] {
|
||||||
|
self.to_array2(eotf, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_array2(self, eotf: Eotf, alpha: Option<f32>) -> [f32; 4] {
|
||||||
|
let mut res = [self.r, self.g, self.b, self.a];
|
||||||
|
fn linear(c: f32) -> f32 {
|
||||||
|
c
|
||||||
|
}
|
||||||
|
fn st2084_pq(c: f32) -> f32 {
|
||||||
|
let c = c.clamp(0.0, 1.0);
|
||||||
|
let num = 0.8359375 + 18.8515625 * c.powf(0.1593017578125);
|
||||||
|
let den = 1.0 + 18.6875 * c.powf(0.1593017578125);
|
||||||
|
(num / den).powf(78.84375)
|
||||||
|
}
|
||||||
|
fn st240(c: f32) -> f32 {
|
||||||
|
if c < 0.0228 {
|
||||||
|
4.0 * c
|
||||||
|
} else {
|
||||||
|
1.1115 * c.powf(0.45) - 0.1115
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn log100(c: f32) -> f32 {
|
||||||
|
let c = c.clamp(0.0, 1.0);
|
||||||
|
if c < 0.01 { 0.0 } else { 1.0 + c.log10() / 2.0 }
|
||||||
|
}
|
||||||
|
fn log316(c: f32) -> f32 {
|
||||||
|
let c = c.clamp(0.0, 1.0);
|
||||||
|
if c < 10.0.sqrt() / 1000.0 {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
1.0 + c.log10() / 2.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn st428(c: f32) -> f32 {
|
||||||
|
(48.0 * c / 52.37).powf(1.0 / 2.6)
|
||||||
|
}
|
||||||
|
fn gamma22(c: f32) -> f32 {
|
||||||
|
c.signum() * c.abs().powf(1.0 / 2.2)
|
||||||
|
}
|
||||||
|
fn gamma24(c: f32) -> f32 {
|
||||||
|
c.signum() * c.abs().powf(1.0 / 2.4)
|
||||||
|
}
|
||||||
|
fn gamma28(c: f32) -> f32 {
|
||||||
|
c.signum() * c.abs().powf(1.0 / 2.8)
|
||||||
|
}
|
||||||
|
fn compound_power_2_4(c: f32) -> f32 {
|
||||||
|
if c < 0.0031308 {
|
||||||
|
12.92 * c
|
||||||
|
} else {
|
||||||
|
1.055 * c.powf(1.0 / 2.4) - 0.055
|
||||||
|
}
|
||||||
|
}
|
||||||
|
macro_rules! convert {
|
||||||
|
($tf:ident) => {{
|
||||||
|
for c in &mut res[..3] {
|
||||||
|
*c = $tf(*c);
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
if eotf != Eotf::Linear {
|
||||||
|
if self.a < 1.0 && self.a > 0.0 {
|
||||||
|
for c in &mut res[..3] {
|
||||||
|
*c /= self.a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match eotf {
|
||||||
|
Eotf::Linear => convert!(linear),
|
||||||
|
Eotf::St2084Pq => convert!(st2084_pq),
|
||||||
|
Eotf::Bt1886(c) => {
|
||||||
|
let [a1, a2, a3, a4] = bt1886_inv_eotf_args(c);
|
||||||
|
let bt1886 = |c: f32| -> f32 { a1 * ((a2 * c + a3).powf(1.0 / 2.4) - a4) };
|
||||||
|
convert!(bt1886)
|
||||||
|
}
|
||||||
|
Eotf::Gamma22 => convert!(gamma22),
|
||||||
|
Eotf::Gamma24 => convert!(gamma24),
|
||||||
|
Eotf::Gamma28 => convert!(gamma28),
|
||||||
|
Eotf::St240 => convert!(st240),
|
||||||
|
Eotf::Log100 => convert!(log100),
|
||||||
|
Eotf::Log316 => convert!(log316),
|
||||||
|
Eotf::St428 => convert!(st428),
|
||||||
|
Eotf::Pow(n) => {
|
||||||
|
let e = n.inv_eotf_f32();
|
||||||
|
let pow = |c: f32| -> f32 { c.signum() * c.abs().powf(e) };
|
||||||
|
convert!(pow)
|
||||||
|
}
|
||||||
|
Eotf::CompoundPower24 => convert!(compound_power_2_4),
|
||||||
|
}
|
||||||
|
if self.a < 1.0 {
|
||||||
|
for c in &mut res[..3] {
|
||||||
|
*c *= self.a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(a) = alpha {
|
||||||
|
for c in &mut res {
|
||||||
|
*c *= a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn and_then(self, other: &Color) -> Color {
|
||||||
|
Color {
|
||||||
|
r: self.r * (1.0 - other.a) + other.r,
|
||||||
|
g: self.g * (1.0 - other.a) + other.g,
|
||||||
|
b: self.b * (1.0 - other.a) + other.b,
|
||||||
|
a: self.a * (1.0 - other.a) + other.a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn srgb_to_oklab(self) -> Oklab {
|
||||||
|
if self.a == 0.0 {
|
||||||
|
return Oklab {
|
||||||
|
l: 0.0,
|
||||||
|
a: 0.0,
|
||||||
|
b: 0.0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let [r, g, b, _] = self.to_array2(Eotf::Linear, Some(1.0 / self.a));
|
||||||
|
|
||||||
|
let l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
|
||||||
|
let m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
|
||||||
|
let s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
|
||||||
|
|
||||||
|
let l_ = l.cbrt();
|
||||||
|
let m_ = m.cbrt();
|
||||||
|
let s_ = s.cbrt();
|
||||||
|
|
||||||
|
let l = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_;
|
||||||
|
let a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_;
|
||||||
|
let b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_;
|
||||||
|
|
||||||
|
Oklab { l, a, b }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<jay_config::theme::Color> for Color {
|
||||||
|
fn from(f: jay_config::theme::Color) -> Self {
|
||||||
|
let [r, g, b, a] = f.to_f32_premultiplied();
|
||||||
|
Self::new(
|
||||||
|
Eotf::Gamma22,
|
||||||
|
AlphaMode::PremultipliedElectrical,
|
||||||
|
r,
|
||||||
|
g,
|
||||||
|
b,
|
||||||
|
a,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! colors {
|
||||||
|
($($name:ident = $colors:tt,)*) => {
|
||||||
|
pub struct ThemeColors {
|
||||||
|
$(
|
||||||
|
pub $name: Cell<Color>,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Linearize)]
|
||||||
|
#[expect(non_camel_case_types)]
|
||||||
|
pub enum ThemeColor {
|
||||||
|
$(
|
||||||
|
$name,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThemeColor {
|
||||||
|
pub fn field(self, theme: &Theme) -> &Cell<Color> {
|
||||||
|
let colors = &theme.colors;
|
||||||
|
match self {
|
||||||
|
$(
|
||||||
|
Self::$name => &colors.$name,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThemeColors {
|
||||||
|
pub fn reset(&self) {
|
||||||
|
let default = Self::default();
|
||||||
|
$(
|
||||||
|
self.$name.set(default.$name.get());
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ThemeColors {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
$(
|
||||||
|
$name: Cell::new(colors!(@colors $colors)),
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(@colors ($r:expr, $g:expr, $b:expr)) => {
|
||||||
|
Color::from_srgb($r, $g, $b)
|
||||||
|
};
|
||||||
|
(@colors ($r:expr, $g:expr, $b:expr, $a:expr)) => {
|
||||||
|
Color::from_srgba_straight($r, $g, $b, $a)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
colors! {
|
||||||
|
background = (0x00, 0x10, 0x19),
|
||||||
|
unfocused_title_background = (0x22, 0x22, 0x22),
|
||||||
|
focused_title_background = (0x28, 0x55, 0x77),
|
||||||
|
captured_unfocused_title_background = (0x22, 0x03, 0x03),
|
||||||
|
captured_focused_title_background = (0x77, 0x28, 0x31),
|
||||||
|
focused_inactive_title_background = (0x5f, 0x67, 0x6a),
|
||||||
|
unfocused_title_text = (0x88, 0x88, 0x88),
|
||||||
|
focused_title_text = (0xff, 0xff, 0xff),
|
||||||
|
focused_inactive_title_text = (0xff, 0xff, 0xff),
|
||||||
|
separator = (0x33, 0x33, 0x33),
|
||||||
|
border = (0x3f, 0x47, 0x4a),
|
||||||
|
active_border = (0x28, 0x55, 0x77),
|
||||||
|
bar_background = (0x00, 0x00, 0x00),
|
||||||
|
bar_text = (0xff, 0xff, 0xff),
|
||||||
|
attention_requested_background = (0x23, 0x09, 0x2c),
|
||||||
|
highlight = (0x9d, 0x28, 0xc6, 0x7f),
|
||||||
|
tab_active_background = (0x4c, 0x78, 0x99),
|
||||||
|
tab_active_border = (0x28, 0x55, 0x77),
|
||||||
|
tab_inactive_background = (0x22, 0x22, 0x22),
|
||||||
|
tab_inactive_border = (0x33, 0x33, 0x33),
|
||||||
|
tab_active_text = (0xff, 0xff, 0xff),
|
||||||
|
tab_inactive_text = (0x88, 0x88, 0x88),
|
||||||
|
tab_bar_background = (0x00, 0x00, 0x00, 0x00),
|
||||||
|
tab_attention_background = (0x23, 0x09, 0x2c),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StaticText for ThemeColor {
|
||||||
|
fn text(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
ThemeColor::background => "Background",
|
||||||
|
ThemeColor::unfocused_title_background => "Title Background (unfocused)",
|
||||||
|
ThemeColor::focused_title_background => "Title Background (focused)",
|
||||||
|
ThemeColor::captured_unfocused_title_background => {
|
||||||
|
"Title Background (unfocused, captured)"
|
||||||
|
}
|
||||||
|
ThemeColor::captured_focused_title_background => "Title Background (focused, captured)",
|
||||||
|
ThemeColor::focused_inactive_title_background => "Title Background (focused, inactive)",
|
||||||
|
ThemeColor::unfocused_title_text => "Title Text (unfocused)",
|
||||||
|
ThemeColor::focused_title_text => "Title Text (focused)",
|
||||||
|
ThemeColor::focused_inactive_title_text => "Title Text (focused, inactive)",
|
||||||
|
ThemeColor::separator => "Separator",
|
||||||
|
ThemeColor::border => "Border",
|
||||||
|
ThemeColor::active_border => "Border (active)",
|
||||||
|
ThemeColor::bar_background => "Bar Background",
|
||||||
|
ThemeColor::bar_text => "Bar Text",
|
||||||
|
ThemeColor::attention_requested_background => "Attention Requested",
|
||||||
|
ThemeColor::highlight => "Highlight",
|
||||||
|
ThemeColor::tab_active_background => "Tab Background (active)",
|
||||||
|
ThemeColor::tab_active_border => "Tab Border (active)",
|
||||||
|
ThemeColor::tab_inactive_background => "Tab Background (inactive)",
|
||||||
|
ThemeColor::tab_inactive_border => "Tab Border (inactive)",
|
||||||
|
ThemeColor::tab_active_text => "Tab Text (active)",
|
||||||
|
ThemeColor::tab_inactive_text => "Tab Text (inactive)",
|
||||||
|
ThemeColor::tab_bar_background => "Tab Bar Background",
|
||||||
|
ThemeColor::tab_attention_background => "Tab Attention Background",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ThemeSize {
|
||||||
|
pub val: Cell<i32>,
|
||||||
|
pub set: Cell<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThemeSize {
|
||||||
|
pub fn get(&self) -> i32 {
|
||||||
|
self.val.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! sizes {
|
||||||
|
($($name:ident = ($min:expr, $max:expr, $def:expr),)*) => {
|
||||||
|
pub struct ThemeSizes {
|
||||||
|
$(
|
||||||
|
pub $name: ThemeSize,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Linearize)]
|
||||||
|
#[expect(non_camel_case_types)]
|
||||||
|
pub enum ThemeSized {
|
||||||
|
$(
|
||||||
|
$name,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThemeSized {
|
||||||
|
pub fn min(self) -> i32 {
|
||||||
|
match self {
|
||||||
|
$(
|
||||||
|
Self::$name => $min,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max(self) -> i32 {
|
||||||
|
match self {
|
||||||
|
$(
|
||||||
|
Self::$name => $max,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn field(self, theme: &Theme) -> &ThemeSize {
|
||||||
|
let sizes = &theme.sizes;
|
||||||
|
match self {
|
||||||
|
$(
|
||||||
|
Self::$name => &sizes.$name,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
$(
|
||||||
|
Self::$name => stringify!($name),
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThemeSizes {
|
||||||
|
pub fn reset(&self) {
|
||||||
|
let default = Self::default();
|
||||||
|
$(
|
||||||
|
self.$name.val.set(default.$name.val.get());
|
||||||
|
self.$name.set.set(false);
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ThemeSizes {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
$(
|
||||||
|
$name: ThemeSize {
|
||||||
|
val: Cell::new($def),
|
||||||
|
set: Cell::new(false),
|
||||||
|
},
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThemeSizes {
|
||||||
|
pub fn bar_height(&self) -> i32 {
|
||||||
|
if self.bar_height.set.get() {
|
||||||
|
self.bar_height.val.get()
|
||||||
|
} else {
|
||||||
|
self.title_height.val.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bar_separator_width(&self) -> i32 {
|
||||||
|
self.bar_separator_width.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sizes! {
|
||||||
|
title_height = (0, 1000, 17),
|
||||||
|
bar_height = (0, 1000, 17),
|
||||||
|
border_width = (0, 1000, 4),
|
||||||
|
bar_separator_width = (0, 1000, 1),
|
||||||
|
gap = (0, 1000, 0),
|
||||||
|
title_gap = (0, 1000, 5),
|
||||||
|
tab_bar_height = (0, 1000, 22),
|
||||||
|
tab_bar_padding = (0, 1000, 6),
|
||||||
|
tab_bar_radius = (0, 1000, 6),
|
||||||
|
tab_bar_border_width = (0, 1000, 2),
|
||||||
|
tab_bar_text_padding = (0, 1000, 4),
|
||||||
|
tab_bar_gap = (0, 1000, 4),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StaticText for ThemeSized {
|
||||||
|
fn text(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
ThemeSized::title_height => "Title Height",
|
||||||
|
ThemeSized::bar_height => "Bar Height",
|
||||||
|
ThemeSized::border_width => "Border Width",
|
||||||
|
ThemeSized::bar_separator_width => "Bar Separator Width",
|
||||||
|
ThemeSized::gap => "Gap",
|
||||||
|
ThemeSized::title_gap => "Title Gap",
|
||||||
|
ThemeSized::tab_bar_height => "Tab Bar Height",
|
||||||
|
ThemeSized::tab_bar_padding => "Tab Bar Padding",
|
||||||
|
ThemeSized::tab_bar_radius => "Tab Bar Radius",
|
||||||
|
ThemeSized::tab_bar_border_width => "Tab Bar Border Width",
|
||||||
|
ThemeSized::tab_bar_text_padding => "Tab Bar Text Padding",
|
||||||
|
ThemeSized::tab_bar_gap => "Tab Bar Gap",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const DEFAULT_FONT: &str = "monospace 8";
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default, Linearize)]
|
||||||
|
pub enum BarPosition {
|
||||||
|
#[default]
|
||||||
|
Top,
|
||||||
|
Bottom,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StaticText for BarPosition {
|
||||||
|
fn text(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
BarPosition::Top => "Top",
|
||||||
|
BarPosition::Bottom => "Bottom",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<ConfigBarPosition> for BarPosition {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: ConfigBarPosition) -> Result<Self, Self::Error> {
|
||||||
|
let v = match value {
|
||||||
|
ConfigBarPosition::Top => Self::Top,
|
||||||
|
ConfigBarPosition::Bottom => Self::Bottom,
|
||||||
|
_ => return Err(()),
|
||||||
|
};
|
||||||
|
Ok(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<ConfigBarPosition> for BarPosition {
|
||||||
|
fn into(self) -> ConfigBarPosition {
|
||||||
|
match self {
|
||||||
|
BarPosition::Top => ConfigBarPosition::Top,
|
||||||
|
BarPosition::Bottom => ConfigBarPosition::Bottom,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Per-corner radius for rounded rectangles.
|
||||||
|
///
|
||||||
|
/// Each field specifies the radius (in logical pixels) for one corner.
|
||||||
|
/// A radius of 0 means a square corner.
|
||||||
|
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||||
|
pub struct CornerRadius {
|
||||||
|
pub top_left: f32,
|
||||||
|
pub top_right: f32,
|
||||||
|
pub bottom_right: f32,
|
||||||
|
pub bottom_left: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<f32> for CornerRadius {
|
||||||
|
fn from(value: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
top_left: value,
|
||||||
|
top_right: value,
|
||||||
|
bottom_right: value,
|
||||||
|
bottom_left: value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CornerRadius> for [f32; 4] {
|
||||||
|
fn from(cr: CornerRadius) -> Self {
|
||||||
|
[cr.top_left, cr.top_right, cr.bottom_right, cr.bottom_left]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CornerRadius {
|
||||||
|
/// Shrink or grow all radii by `width`. Radii that are 0 stay 0 (square
|
||||||
|
/// corners remain square). Negative `width` shrinks; the result is clamped
|
||||||
|
/// to 0.
|
||||||
|
pub fn expanded_by(mut self, width: f32) -> Self {
|
||||||
|
if self.top_left > 0.0 {
|
||||||
|
self.top_left = (self.top_left + width).max(0.0);
|
||||||
|
}
|
||||||
|
if self.top_right > 0.0 {
|
||||||
|
self.top_right = (self.top_right + width).max(0.0);
|
||||||
|
}
|
||||||
|
if self.bottom_right > 0.0 {
|
||||||
|
self.bottom_right = (self.bottom_right + width).max(0.0);
|
||||||
|
}
|
||||||
|
if self.bottom_left > 0.0 {
|
||||||
|
self.bottom_left = (self.bottom_left + width).max(0.0);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scale all radii by a factor (e.g. for HiDPI).
|
||||||
|
pub fn scaled_by(self, scale: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
top_left: self.top_left * scale,
|
||||||
|
top_right: self.top_right * scale,
|
||||||
|
bottom_right: self.bottom_right * scale,
|
||||||
|
bottom_left: self.bottom_left * scale,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reduce all radii proportionally so that adjacent corners don't overlap,
|
||||||
|
/// following the CSS spec algorithm.
|
||||||
|
pub fn fit_to(self, width: f32, height: f32) -> Self {
|
||||||
|
let reduction = f32::min(
|
||||||
|
f32::min(
|
||||||
|
width / (self.top_left + self.top_right),
|
||||||
|
width / (self.bottom_left + self.bottom_right),
|
||||||
|
),
|
||||||
|
f32::min(
|
||||||
|
height / (self.top_left + self.bottom_left),
|
||||||
|
height / (self.top_right + self.bottom_right),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let reduction = f32::min(1.0, reduction);
|
||||||
|
Self {
|
||||||
|
top_left: self.top_left * reduction,
|
||||||
|
top_right: self.top_right * reduction,
|
||||||
|
bottom_right: self.bottom_right * reduction,
|
||||||
|
bottom_left: self.bottom_left * reduction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_zero(&self) -> bool {
|
||||||
|
self.top_left == 0.0
|
||||||
|
&& self.top_right == 0.0
|
||||||
|
&& self.bottom_right == 0.0
|
||||||
|
&& self.bottom_left == 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Horizontal alignment of title text inside tab buttons.
|
||||||
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||||
|
pub enum TabTitleAlign {
|
||||||
|
#[default]
|
||||||
|
Start,
|
||||||
|
Center,
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Theme {
|
||||||
|
pub colors: ThemeColors,
|
||||||
|
pub sizes: ThemeSizes,
|
||||||
|
pub font: CloneCell<Arc<String>>,
|
||||||
|
pub bar_font: CloneCell<Option<Arc<String>>>,
|
||||||
|
pub title_font: CloneCell<Option<Arc<String>>>,
|
||||||
|
pub default_font: Arc<String>,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub show_titles: Cell<bool>,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub floating_titles: Cell<bool>,
|
||||||
|
pub bar_position: Cell<BarPosition>,
|
||||||
|
pub corner_radius: Cell<CornerRadius>,
|
||||||
|
pub autotile_enabled: Cell<bool>,
|
||||||
|
pub tab_title_align: Cell<TabTitleAlign>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Theme {
|
||||||
|
fn default() -> Self {
|
||||||
|
let default_font = Arc::new(DEFAULT_FONT.to_string());
|
||||||
|
Self {
|
||||||
|
colors: Default::default(),
|
||||||
|
sizes: Default::default(),
|
||||||
|
font: CloneCell::new(default_font.clone()),
|
||||||
|
bar_font: Default::default(),
|
||||||
|
title_font: Default::default(),
|
||||||
|
default_font,
|
||||||
|
show_titles: Cell::new(true),
|
||||||
|
floating_titles: Cell::new(false),
|
||||||
|
bar_position: Default::default(),
|
||||||
|
corner_radius: Cell::new(CornerRadius::default()),
|
||||||
|
autotile_enabled: Cell::new(false),
|
||||||
|
tab_title_align: Cell::new(TabTitleAlign::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Theme {
|
||||||
|
pub fn bar_font(&self) -> Arc<String> {
|
||||||
|
self.bar_font.get().unwrap_or_else(|| self.font.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn title_font(&self) -> Arc<String> {
|
||||||
|
self.title_font.get().unwrap_or_else(|| self.font.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn title_height(&self) -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn title_plus_underline_height(&self) -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct Oklch {
|
||||||
|
pub l: f32,
|
||||||
|
pub c: f32,
|
||||||
|
pub h: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct Oklab {
|
||||||
|
pub l: f32,
|
||||||
|
pub a: f32,
|
||||||
|
pub b: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Oklab {
|
||||||
|
pub fn to_srgb(self) -> Color {
|
||||||
|
let l_ = self.l + 0.3963377774 * self.a + 0.2158037573 * self.b;
|
||||||
|
let m_ = self.l - 0.1055613458 * self.a - 0.0638541728 * self.b;
|
||||||
|
let s_ = self.l - 0.0894841775 * self.a - 1.2914855480 * self.b;
|
||||||
|
|
||||||
|
let l = l_ * l_ * l_;
|
||||||
|
let m = m_ * m_ * m_;
|
||||||
|
let s = s_ * s_ * s_;
|
||||||
|
|
||||||
|
let r = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
|
||||||
|
let g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
|
||||||
|
let b = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s;
|
||||||
|
|
||||||
|
Color::new(
|
||||||
|
Eotf::Linear,
|
||||||
|
AlphaMode::PremultipliedElectrical,
|
||||||
|
r,
|
||||||
|
g,
|
||||||
|
b,
|
||||||
|
1.0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_oklch(self) -> Oklch {
|
||||||
|
let c = (self.a * self.a + self.b * self.b).sqrt();
|
||||||
|
let h = self.b.atan2(self.a);
|
||||||
|
|
||||||
|
Oklch { l: self.l, c, h }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Oklch {
|
||||||
|
pub fn to_oklab(self) -> Oklab {
|
||||||
|
let a = self.c * self.h.cos();
|
||||||
|
let b = self.c * self.h.sin();
|
||||||
|
|
||||||
|
Oklab { l: self.l, a, b }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for Oklab {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
Self {
|
||||||
|
l: self.l + rhs.l,
|
||||||
|
a: self.a + rhs.a,
|
||||||
|
b: self.b + rhs.b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul<f32> for Oklab {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn mul(self, rhs: f32) -> Self::Output {
|
||||||
|
Self {
|
||||||
|
l: self.l * rhs,
|
||||||
|
a: self.a * rhs,
|
||||||
|
b: self.b * rhs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Div<f32> for Oklab {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn div(self, rhs: f32) -> Self::Output {
|
||||||
|
Self {
|
||||||
|
l: self.l / rhs,
|
||||||
|
a: self.a / rhs,
|
||||||
|
b: self.b / rhs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue