icons: add icon infrastructure
This commit is contained in:
parent
4970749924
commit
9192446602
10 changed files with 343 additions and 3 deletions
44
Cargo.lock
generated
44
Cargo.lock
generated
|
|
@ -110,6 +110,12 @@ version = "1.0.95"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
|
|
@ -183,6 +189,12 @@ version = "3.16.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
|
|
@ -597,6 +609,7 @@ dependencies = [
|
|||
"shaderc",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"tiny-skia",
|
||||
"tracy-client-sys",
|
||||
"uapi",
|
||||
]
|
||||
|
|
@ -1245,6 +1258,12 @@ version = "1.13.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "strict-num"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
|
|
@ -1345,6 +1364,31 @@ dependencies = [
|
|||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
"bytemuck",
|
||||
"cfg-if",
|
||||
"log",
|
||||
"tiny-skia-path",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia-path"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"bytemuck",
|
||||
"strict-num",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.8.1"
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ png = "0.17.13"
|
|||
rustc-demangle = { version = "0.1.24", optional = true }
|
||||
tracy-client-sys = { version = "0.24.1", features = ["ondemand", "manual-lifetime", "debuginfod", "demangle"], optional = true }
|
||||
kbvm = "0.1.4"
|
||||
tiny-skia = { version = "0.11.4", default-features = false, features = ["std"] }
|
||||
|
||||
[build-dependencies]
|
||||
repc = "0.1.1"
|
||||
|
|
|
|||
|
|
@ -288,6 +288,7 @@ fn start_compositor2(
|
|||
color_management_enabled: Cell::new(false),
|
||||
color_manager,
|
||||
float_above_fullscreen: Cell::new(false),
|
||||
icons: Default::default(),
|
||||
});
|
||||
state.tracker.register(ClientId::from_raw(0));
|
||||
create_dummy_output(&state);
|
||||
|
|
|
|||
|
|
@ -1545,6 +1545,7 @@ impl ConfigProxyHandler {
|
|||
}
|
||||
self.state.root.clone().node_visit(&mut V);
|
||||
self.state.damage(self.state.root.extents.get());
|
||||
self.state.icons.update_sizes(&self.state);
|
||||
}
|
||||
|
||||
fn colors_changed(&self) {
|
||||
|
|
@ -1561,6 +1562,7 @@ impl ConfigProxyHandler {
|
|||
}
|
||||
self.state.root.clone().node_visit(&mut V);
|
||||
self.state.damage(self.state.root.extents.get());
|
||||
self.state.icons.clear();
|
||||
}
|
||||
|
||||
fn get_sized(&self, sized: Resizable) -> Result<ThemeSized, CphError> {
|
||||
|
|
|
|||
|
|
@ -618,6 +618,7 @@ impl dyn GfxFramebuffer {
|
|||
let (width, height) = self.logical_size(transform);
|
||||
Rect::new(0, 0, width, height).unwrap()
|
||||
},
|
||||
icons: None,
|
||||
};
|
||||
cursor.render_hardware_cursor(&mut renderer);
|
||||
self.render(
|
||||
|
|
@ -906,6 +907,7 @@ pub fn create_render_pass(
|
|||
let (width, height) = logical_size(physical_size, transform);
|
||||
Rect::new(0, 0, width, height).unwrap()
|
||||
},
|
||||
icons: state.icons.get(state, scale),
|
||||
};
|
||||
node.node_render(&mut renderer, 0, 0, None);
|
||||
if let Some(rect) = cursor_rect {
|
||||
|
|
|
|||
284
src/icons.rs
Normal file
284
src/icons.rs
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
#![allow(clippy::excessive_precision)]
|
||||
|
||||
use {
|
||||
crate::{
|
||||
cmm::cmm_transfer_function::TransferFunction,
|
||||
format::ARGB8888,
|
||||
gfx_api::{GfxContext, GfxError, GfxTexture},
|
||||
scale::Scale,
|
||||
state::State,
|
||||
theme::Theme,
|
||||
utils::{copyhashmap::CopyHashMap, windows::WindowsExt},
|
||||
},
|
||||
ahash::AHashSet,
|
||||
linearize::{Linearize, StaticMap, static_map},
|
||||
std::{cell::Cell, f32::consts::PI, mem, rc::Rc, sync::LazyLock},
|
||||
thiserror::Error,
|
||||
tiny_skia::{Color, FillRule, Paint, Path, PathBuilder, Pixmap, Transform},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Icons {
|
||||
icons: CopyHashMap<i32, Option<Rc<SizedIcons>>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Linearize)]
|
||||
pub enum IconState {
|
||||
Active,
|
||||
Passive,
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
pub struct SizedIcons {
|
||||
pub pin_unfocused_title: StaticMap<IconState, Rc<dyn GfxTexture>>,
|
||||
pub pin_focused_title: StaticMap<IconState, Rc<dyn GfxTexture>>,
|
||||
pub pin_attention_requested: StaticMap<IconState, Rc<dyn GfxTexture>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum IconsError {
|
||||
#[error("Could not create a pixmap")]
|
||||
CreatePixmap,
|
||||
#[error("The requested icons size is non-positive")]
|
||||
NonPositiveSize,
|
||||
#[error("There is no gfx context")]
|
||||
NoRenderContext,
|
||||
#[error("Could not create texture")]
|
||||
CreateTexture(#[source] GfxError),
|
||||
}
|
||||
|
||||
impl Icons {
|
||||
pub fn update_sizes(&self, state: &State) {
|
||||
let mut sizes = AHashSet::new();
|
||||
let height = state.theme.sizes.title_height.get();
|
||||
for &(scale, _) in &*state.scales.lock() {
|
||||
let [size] = scale.pixel_size([height]);
|
||||
if size > 0 {
|
||||
sizes.insert(size);
|
||||
}
|
||||
}
|
||||
self.icons.lock().retain(|size, _| sizes.contains(size));
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
self.icons.clear();
|
||||
}
|
||||
|
||||
pub fn get(&self, state: &State, scale: Scale) -> Option<Rc<SizedIcons>> {
|
||||
let [size] = scale.pixel_size([state.theme.sizes.title_height.get()]);
|
||||
if let Some(icons) = self.icons.get(&size) {
|
||||
return icons;
|
||||
}
|
||||
let icons = match self.create(state, size) {
|
||||
Ok(i) => Some(i),
|
||||
Err(e) => {
|
||||
log::error!("Could not create icons: {}", e);
|
||||
None
|
||||
}
|
||||
};
|
||||
self.icons.set(size, icons.clone());
|
||||
icons
|
||||
}
|
||||
|
||||
fn create(&self, state: &State, size: i32) -> Result<Rc<SizedIcons>, IconsError> {
|
||||
let Some(ctx) = state.render_ctx.get() else {
|
||||
return Err(IconsError::NoRenderContext);
|
||||
};
|
||||
Ok(Rc::new(create_icons(size, &state.theme, &ctx)?))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_icons(
|
||||
size: i32,
|
||||
theme: &Theme,
|
||||
ctx: &Rc<dyn GfxContext>,
|
||||
) -> Result<SizedIcons, IconsError> {
|
||||
if size <= 0 {
|
||||
return Err(IconsError::NonPositiveSize);
|
||||
}
|
||||
let size = size as u32;
|
||||
|
||||
let create_pins = |color: crate::theme::Color| {
|
||||
let create_pin = |color: Color| {
|
||||
let mut paint = Paint::default();
|
||||
paint.set_color(color);
|
||||
let s = size as f32 / 100.0;
|
||||
let transform = Transform::from_scale(s, s);
|
||||
let mut pixmap = Pixmap::new(size, size).ok_or(IconsError::CreatePixmap)?;
|
||||
pixmap.fill_path(&PIN_PATH, &paint, FillRule::EvenOdd, transform, None);
|
||||
upload_pixmap(pixmap, ctx)
|
||||
};
|
||||
let colors = calculate_accents(color);
|
||||
Ok(static_map! {
|
||||
IconState::Passive => create_pin(colors[0])?,
|
||||
IconState::Active => create_pin(colors[1])?,
|
||||
})
|
||||
};
|
||||
|
||||
Ok(SizedIcons {
|
||||
pin_unfocused_title: create_pins(theme.colors.unfocused_title_background.get())?,
|
||||
pin_focused_title: create_pins(theme.colors.focused_title_background.get())?,
|
||||
pin_attention_requested: create_pins(theme.colors.attention_requested_background.get())?,
|
||||
})
|
||||
}
|
||||
|
||||
fn upload_pixmap(
|
||||
pixmap: Pixmap,
|
||||
ctx: &Rc<dyn GfxContext>,
|
||||
) -> Result<Rc<dyn GfxTexture>, IconsError> {
|
||||
let width = pixmap.width();
|
||||
let height = pixmap.width();
|
||||
let bytes = unsafe { mem::transmute::<Vec<u8>, Vec<Cell<u8>>>(pixmap.take()) };
|
||||
for chunk in bytes.array_chunks_ext::<4>() {
|
||||
let r = chunk[0].get();
|
||||
let b = chunk[2].get();
|
||||
chunk[0].set(b);
|
||||
chunk[2].set(r);
|
||||
}
|
||||
let tex: Rc<dyn GfxTexture> = ctx
|
||||
.clone()
|
||||
.shmem_texture(
|
||||
None,
|
||||
&bytes,
|
||||
ARGB8888,
|
||||
width as _,
|
||||
height as _,
|
||||
width as i32 * 4,
|
||||
None,
|
||||
)
|
||||
.map_err(IconsError::CreateTexture)?;
|
||||
Ok(tex)
|
||||
}
|
||||
|
||||
static PIN_PATH: LazyLock<Path> = LazyLock::new(|| {
|
||||
let cx = 50.0f32;
|
||||
let cy = 40.0f32;
|
||||
let r = 30.0f32;
|
||||
let xx = cx;
|
||||
let xy = 90.0f32;
|
||||
let d = xy - cy;
|
||||
let v1 = r / d * (d * d - r * r).sqrt();
|
||||
let v2 = 1.0 / d * (d * d - r * r);
|
||||
|
||||
let mut path = PathBuilder::new();
|
||||
path.move_to(cx, cy - r);
|
||||
path.arc_cw_to(cx, cy, cx + r, cy);
|
||||
path.arc_cw_to(cx, cy, xx + v1, xy - v2);
|
||||
path.line_to(xx, xy);
|
||||
path.line_to(xx - v1, xy - v2);
|
||||
path.arc_cw_to(cx, cy, cx - r, cy);
|
||||
path.arc_cw_to(cx, cy, cx, cy - r);
|
||||
path.close();
|
||||
path.push_circle(cx, cy, r / 2.5);
|
||||
path.finish().unwrap()
|
||||
});
|
||||
|
||||
#[test]
|
||||
fn pin_path() {
|
||||
let _path = &*PIN_PATH;
|
||||
}
|
||||
|
||||
trait PathBuilderExt {
|
||||
fn arc_cw_to(&mut self, cx: f32, cy: f32, x: f32, y: f32);
|
||||
}
|
||||
|
||||
impl PathBuilderExt for PathBuilder {
|
||||
fn arc_cw_to(&mut self, cx: f32, cy: f32, x: f32, y: f32) {
|
||||
let (x0, y0) = match self.last_point() {
|
||||
None => {
|
||||
self.move_to(0.0, 0.0);
|
||||
(0.0, 0.0)
|
||||
}
|
||||
Some(p) => (p.x, p.y),
|
||||
};
|
||||
|
||||
let ux = x0 - cx;
|
||||
let uy = y0 - cy;
|
||||
let ul = (ux * ux + uy * uy).sqrt();
|
||||
let uxn = ux / ul;
|
||||
let uyn = uy / ul;
|
||||
let a1 = (uy / ux).atan();
|
||||
|
||||
let tx = x - cx;
|
||||
let ty = y - cy;
|
||||
let tl = (tx * tx + ty * ty).sqrt();
|
||||
let txn = tx / tl;
|
||||
let tyn = ty / tl;
|
||||
let a2 = (ty / tx).atan();
|
||||
|
||||
let c = 4.0 / 3.0 * ((a2 - a1 + PI) % PI / 4.0).tan();
|
||||
let uc = ul * c;
|
||||
let tc = tl * c;
|
||||
self.cubic_to(
|
||||
x0 - uyn * uc,
|
||||
y0 + uxn * uc,
|
||||
x + tyn * tc,
|
||||
y - txn * tc,
|
||||
x,
|
||||
y,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::theme::Color> for Color {
|
||||
fn from(v: crate::theme::Color) -> Self {
|
||||
let [r, g, b, a] = v.to_array(TransferFunction::Srgb);
|
||||
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] {
|
||||
let [l, a, b, alpha] = srgb_to_lab(srgb);
|
||||
let l2 = if l < 0.65 { 0.9 } else { l - 0.4 };
|
||||
let l1 = (l2 + l) / 2.0;
|
||||
[
|
||||
lab_to_color([l1, a, b, alpha]),
|
||||
lab_to_color([l2, a, b, alpha]),
|
||||
]
|
||||
}
|
||||
|
||||
fn srgb_to_lab(srgb: crate::theme::Color) -> [f32; 4] {
|
||||
let [mut r, mut g, mut b, alpha] = srgb.to_array(TransferFunction::Srgb);
|
||||
if alpha < 1.0 {
|
||||
r /= alpha;
|
||||
g /= alpha;
|
||||
b /= alpha;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
[
|
||||
0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
|
||||
1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
|
||||
0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_,
|
||||
alpha,
|
||||
]
|
||||
}
|
||||
|
||||
fn lab_to_color([l, a, b, alpha]: [f32; 4]) -> Color {
|
||||
let l_ = l + 0.3963377774 * a + 0.2158037573 * b;
|
||||
let m_ = l - 0.1055613458 * a - 0.0638541728 * b;
|
||||
let s_ = l - 0.0894841775 * a - 1.2914855480 * b;
|
||||
|
||||
let l = l_ * l_ * l_;
|
||||
let m = m_ * m_ * m_;
|
||||
let s = s_ * s_ * s_;
|
||||
|
||||
let mut c = Color::TRANSPARENT;
|
||||
c.set_red(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s);
|
||||
c.set_green(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s);
|
||||
c.set_blue(-0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s);
|
||||
c.set_alpha(alpha);
|
||||
c
|
||||
}
|
||||
|
|
@ -72,6 +72,7 @@ mod format;
|
|||
mod gfx_api;
|
||||
mod gfx_apis;
|
||||
mod globals;
|
||||
mod icons;
|
||||
mod ifs;
|
||||
mod io_uring;
|
||||
#[cfg(feature = "it")]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use {
|
||||
crate::{
|
||||
gfx_api::{AcquireSync, GfxApiOpt, ReleaseSync, SampleRect},
|
||||
icons::SizedIcons,
|
||||
ifs::wl_surface::{
|
||||
SurfaceBuffer, WlSurface,
|
||||
x_surface::xwindow::Xwindow,
|
||||
|
|
@ -27,6 +28,8 @@ pub struct Renderer<'a> {
|
|||
pub state: &'a State,
|
||||
pub logical_extents: Rect,
|
||||
pub pixel_extents: Rect,
|
||||
#[expect(dead_code)]
|
||||
pub icons: Option<Rc<SizedIcons>>,
|
||||
}
|
||||
|
||||
impl Renderer<'_> {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ use {
|
|||
},
|
||||
gfx_apis::create_gfx_context,
|
||||
globals::{Globals, GlobalsError, RemovableWaylandGlobal, WaylandGlobal},
|
||||
icons::Icons,
|
||||
ifs::{
|
||||
ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1,
|
||||
ext_idle_notification_v1::ExtIdleNotificationV1,
|
||||
|
|
@ -237,6 +238,7 @@ pub struct State {
|
|||
pub color_management_enabled: Cell<bool>,
|
||||
pub color_manager: Rc<ColorManager>,
|
||||
pub float_above_fullscreen: Cell<bool>,
|
||||
pub icons: Icons,
|
||||
}
|
||||
|
||||
// impl Drop for State {
|
||||
|
|
@ -466,6 +468,7 @@ impl State {
|
|||
UpdateTextTexturesVisitor.visit_display(&self.root);
|
||||
self.reload_cursors();
|
||||
self.update_xwayland_wire_scale();
|
||||
self.icons.update_sizes(self);
|
||||
}
|
||||
|
||||
fn cursor_sizes_changed(&self) {
|
||||
|
|
@ -501,6 +504,7 @@ impl State {
|
|||
self.render_ctx_version.fetch_add(1);
|
||||
self.cursors.set(None);
|
||||
self.drm_feedback.set(None);
|
||||
self.icons.clear();
|
||||
self.wait_for_sync_obj
|
||||
.set_ctx(ctx.as_ref().and_then(|c| c.sync_obj_ctx().cloned()));
|
||||
|
||||
|
|
@ -1044,6 +1048,7 @@ impl State {
|
|||
let (width, height) = target.logical_size(target_transform);
|
||||
Rect::new_sized(0, 0, width, height).unwrap()
|
||||
},
|
||||
icons: None,
|
||||
};
|
||||
let mut sample_rect = SampleRect::identity();
|
||||
sample_rect.buffer_transform = transform;
|
||||
|
|
|
|||
|
|
@ -182,7 +182,6 @@ impl Color {
|
|||
Self::new(TransferFunction::Srgb, to_f32(r), to_f32(g), to_f32(b))
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "it"), expect(dead_code))]
|
||||
pub fn from_srgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
Self::new_premultiplied(
|
||||
TransferFunction::Srgb,
|
||||
|
|
@ -220,7 +219,6 @@ impl Color {
|
|||
c
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "it"), expect(dead_code))]
|
||||
pub fn to_srgba_premultiplied(self) -> [u8; 4] {
|
||||
let [r, g, b, a] = self.to_array(TransferFunction::Srgb);
|
||||
[to_u8(r), to_u8(g), to_u8(b), to_u8(a)]
|
||||
|
|
@ -332,7 +330,6 @@ impl Color {
|
|||
res
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "it"), expect(dead_code))]
|
||||
pub fn and_then(self, other: &Color) -> Color {
|
||||
Color {
|
||||
r: self.r * (1.0 - other.a) + other.r,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue