feat: theme-coloured region dim
This commit is contained in:
1 parent
2178b840ad
commit
201f43ef34
4 files changed
+83
-31
No files matched your search
+1
-1
@@ -18,7 +18,7 @@ mod selection;
|
|||||||
mod shadow;
|
mod shadow;
|
||||||
mod sound;
|
mod sound;
|
||||||
mod stack;
|
mod stack;
|
||||||
// The theme lands in pieces; Task 2 of the theme plan removes this.
|
// Parts of the theme are used from Task 4 of the theme plan on; that task removes this.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
mod theme;
|
mod theme;
|
||||||
mod thumbnail;
|
mod thumbnail;
|
||||||
|
|||||||
+11
-3
@@ -87,6 +87,8 @@ pub fn select(wl: &mut Wayland, outputs: &[Output], frames: &[Frame]) -> Result<
|
|||||||
.context("compositor does not support wp-viewporter")
|
.context("compositor does not support wp-viewporter")
|
||||||
.hint("run `valw doctor` to see what the compositor supports")?;
|
.hint("run `valw doctor` to see what the compositor supports")?;
|
||||||
|
|
||||||
|
// The theme's colour for the overlay outside the selection.
|
||||||
|
let palette = crate::theme::current();
|
||||||
let mut surfaces = Vec::new();
|
let mut surfaces = Vec::new();
|
||||||
for (output, frame) in outputs.iter().zip(frames) {
|
for (output, frame) in outputs.iter().zip(frames) {
|
||||||
let surface = s.compositor.create_surface(&qh);
|
let surface = s.compositor.create_surface(&qh);
|
||||||
@@ -102,7 +104,7 @@ pub fn select(wl: &mut Wayland, outputs: &[Output], frames: &[Frame]) -> Result<
|
|||||||
layer.set_keyboard_interactivity(KeyboardInteractivity::Exclusive);
|
layer.set_keyboard_interactivity(KeyboardInteractivity::Exclusive);
|
||||||
layer.commit();
|
layer.commit();
|
||||||
let viewport = viewporter.get_viewport(layer.wl_surface(), &qh, ());
|
let viewport = viewporter.get_viewport(layer.wl_surface(), &qh, ());
|
||||||
surfaces.push(Surface::new(layer, viewport, frame, &s.shm)?);
|
surfaces.push(Surface::new(layer, viewport, frame, &palette, &s.shm)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
@@ -126,7 +128,13 @@ pub fn select(wl: &mut Wayland, outputs: &[Output], frames: &[Frame]) -> Result<
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Surface {
|
impl Surface {
|
||||||
fn new(layer: LayerSurface, viewport: WpViewport, frame: &Frame, shm: &Shm) -> Result<Surface> {
|
fn new(
|
||||||
|
layer: LayerSurface,
|
||||||
|
viewport: WpViewport,
|
||||||
|
frame: &Frame,
|
||||||
|
palette: &crate::theme::Palette,
|
||||||
|
shm: &Shm,
|
||||||
|
) -> Result<Surface> {
|
||||||
let (width, height) = frame.pixels.dimensions();
|
let (width, height) = frame.pixels.dimensions();
|
||||||
let len = (width * height * 4) as usize;
|
let len = (width * height * 4) as usize;
|
||||||
Ok(Surface {
|
Ok(Surface {
|
||||||
@@ -135,7 +143,7 @@ impl Surface {
|
|||||||
geom: frame.output.clone(),
|
geom: frame.output.clone(),
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
dark: render::dim(frame.pixels.as_raw(), render::DIM),
|
dark: render::tint(frame.pixels.as_raw(), palette.base, render::TINT),
|
||||||
bright: Arc::clone(&frame.pixels),
|
bright: Arc::clone(&frame.pixels),
|
||||||
// Room for two buffers so one can be on screen while we draw the next.
|
// Room for two buffers so one can be on screen while we draw the next.
|
||||||
pool: SlotPool::new(len * 2, shm).context("could not create shm pool")?,
|
pool: SlotPool::new(len * 2, shm).context("could not create shm pool")?,
|
||||||
|
|||||||
+29
-20
@@ -1,19 +1,25 @@
|
|||||||
use crate::frame::PixelRect;
|
use crate::frame::PixelRect;
|
||||||
|
|
||||||
/// Overlay brightness outside the selection (60%, i.e. darkened by 40%).
|
/// How far the overlay outside the selection leans towards the theme colour.
|
||||||
pub const DIM: u8 = 153;
|
pub const TINT: f32 = 0.55;
|
||||||
|
|
||||||
/// B, G, R, X pixels with every byte scaled by about `brightness / 255`.
|
/// B, G, R, X pixels blended `amount` of the way towards `colour` (RGB 0–1).
|
||||||
///
|
/// Runs once per output before the overlay shows: a lookup table per
|
||||||
/// This runs on every output before the overlay can appear, so it is one
|
/// channel keeps it as cheap as the old integer dim.
|
||||||
/// branch-free pass the compiler can vectorise. Scaling X too is harmless:
|
pub fn tint(pixels: &[u8], colour: [f32; 3], amount: f32) -> Vec<u8> {
|
||||||
/// the overlay buffer is XRGB8888, which ignores it.
|
let keep = 1.0 - amount;
|
||||||
pub fn dim(pixels: &[u8], brightness: u8) -> Vec<u8> {
|
let table = |c: f32| -> [u8; 256] {
|
||||||
let factor = brightness as u16 + 1;
|
std::array::from_fn(|v| (v as f32 * keep + c * 255.0 * amount).round() as u8)
|
||||||
pixels
|
};
|
||||||
.iter()
|
let [r, g, b] = colour;
|
||||||
.map(|&b| ((b as u16 * factor) >> 8) as u8)
|
let tables = [table(b), table(g), table(r)];
|
||||||
.collect()
|
let mut out = pixels.to_vec();
|
||||||
|
for px in out.as_chunks_mut::<4>().0 {
|
||||||
|
for (v, t) in px.iter_mut().zip(&tables) {
|
||||||
|
*v = t[*v as usize];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws one overlay frame into `dst`: `dark` everywhere, `bright` inside
|
/// Draws one overlay frame into `dst`: `dark` everywhere, `bright` inside
|
||||||
@@ -92,18 +98,21 @@ pub fn blend(dst: &mut [u8], dst_width: u32, src: &tiny_skia::Pixmap, at: (u32,
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn dims_every_byte() {
|
|
||||||
assert_eq!(dim(&[200, 100, 50, 0], 255), vec![200, 100, 50, 0]);
|
|
||||||
assert_eq!(dim(&[200, 100, 50, 255], DIM), vec![120, 60, 30, 153]);
|
|
||||||
assert_eq!(dim(&[255, 0, 1, 2], 0), vec![0, 0, 0, 0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn px(buf: &[u8], width: u32, x: u32, y: u32) -> [u8; 4] {
|
fn px(buf: &[u8], width: u32, x: u32, y: u32) -> [u8; 4] {
|
||||||
let i = ((y * width + x) * 4) as usize;
|
let i = ((y * width + x) * 4) as usize;
|
||||||
buf[i..i + 4].try_into().unwrap()
|
buf[i..i + 4].try_into().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tint_blends_towards_the_colour() {
|
||||||
|
// B, G, R, X = 200, 100, 0, 255; colour pure red (R = 1).
|
||||||
|
let out = tint(&[200, 100, 0, 255], [1.0, 0.0, 0.0], 0.55);
|
||||||
|
assert_eq!(out[0], (200.0 * 0.45f32).round() as u8, "blue fades");
|
||||||
|
assert_eq!(out[1], (100.0 * 0.45f32).round() as u8);
|
||||||
|
assert_eq!(out[2], (255.0 * 0.55f32).round() as u8, "red rises");
|
||||||
|
assert_eq!(tint(&[10, 20, 30, 0], [0.0; 3], 0.0), vec![10, 20, 30, 0]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn compose_outline_and_inside() {
|
fn compose_outline_and_inside() {
|
||||||
let (w, h) = (6, 6);
|
let (w, h) = (6, 6);
|
||||||
|
|||||||
+42
-7
@@ -150,21 +150,41 @@ pub fn current() -> Palette {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn noctalia_palette() -> Option<Palette> {
|
fn noctalia_palette() -> Option<Palette> {
|
||||||
let scheme = noctalia(&["color-scheme-get"])?; // "<source> <name>"
|
// Noctalia keeps the live choice in its state settings (theme-mode-set
|
||||||
let (source, name) = scheme.split_once(' ')?;
|
// writes there); its config holds the initial one. Reading the files is
|
||||||
|
// instant, unlike `noctalia msg`, and this runs before every overlay.
|
||||||
|
let state = state_dir().join("noctalia/settings.toml");
|
||||||
|
let config = crate::config::home().join(".config/noctalia/config.toml");
|
||||||
|
let (source, name, mode) = [state, config]
|
||||||
|
.iter()
|
||||||
|
.find_map(|p| theme_choice(&std::fs::read_to_string(p).ok()?))?;
|
||||||
if source != "community" {
|
if source != "community" {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let mode = noctalia(&["theme-mode-get"])?;
|
let file = state_dir()
|
||||||
let dir = std::env::var_os("XDG_STATE_HOME")
|
|
||||||
.map(PathBuf::from)
|
|
||||||
.unwrap_or_else(|| crate::config::home().join(".local/state"));
|
|
||||||
let file = dir
|
|
||||||
.join("noctalia/community-palettes")
|
.join("noctalia/community-palettes")
|
||||||
.join(format!("{}.json", name.replace(' ', "%20")));
|
.join(format!("{}.json", name.replace(' ', "%20")));
|
||||||
from_noctalia_file(&std::fs::read_to_string(file).ok()?, &mode)
|
from_noctalia_file(&std::fs::read_to_string(file).ok()?, &mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn state_dir() -> PathBuf {
|
||||||
|
std::env::var_os("XDG_STATE_HOME")
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.unwrap_or_else(|| crate::config::home().join(".local/state"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `(source, community palette, mode)` from a Noctalia settings file.
|
||||||
|
fn theme_choice(text: &str) -> Option<(String, String, String)> {
|
||||||
|
let doc: toml::Table = text.parse().ok()?;
|
||||||
|
let theme = doc.get("theme")?.as_table()?;
|
||||||
|
let get = |k: &str| theme.get(k)?.as_str().map(str::to_string);
|
||||||
|
Some((
|
||||||
|
get("source")?,
|
||||||
|
get("community_palette").unwrap_or_default(),
|
||||||
|
get("mode").unwrap_or_else(|| "dark".into()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/// The wallpaper Noctalia shows on `connector`.
|
/// The wallpaper Noctalia shows on `connector`.
|
||||||
pub fn wallpaper(connector: &str) -> Option<PathBuf> {
|
pub fn wallpaper(connector: &str) -> Option<PathBuf> {
|
||||||
noctalia(&["wallpaper-get", connector])
|
noctalia(&["wallpaper-get", connector])
|
||||||
@@ -236,6 +256,21 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn theme_choice_from_settings() {
|
||||||
|
let text = "[bar]\nx = 1\n\n[theme]\ncommunity_palette = \"Kemuri Susu\"\nmode = \"dark\"\nsource = \"community\"\n\n[wallpaper]\n";
|
||||||
|
assert_eq!(
|
||||||
|
theme_choice(text),
|
||||||
|
Some(("community".into(), "Kemuri Susu".into(), "dark".into()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
theme_choice("[theme]\nmode = \"dark\"\n"),
|
||||||
|
None,
|
||||||
|
"no source"
|
||||||
|
);
|
||||||
|
assert_eq!(theme_choice("not toml ["), None);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lerp_eases() {
|
fn lerp_eases() {
|
||||||
let a = Palette::DEFAULT;
|
let a = Palette::DEFAULT;
|
||||||
|
|||||||
Reference in new issue
Block a user