All checks were successful
check / check (push) Successful in 1m34s
Med en grupps lista öppen stegar skroll (på gruppikonen eller över listan) markeringen mellan raderna — markerad rad får accentbakgrund och triggar samma förhandsvisning som hover (peek/fokus enligt list_hover). Funkar i både hover- och klickläge; utan öppen lista beter sig skroll som förut (cykla fönster). Watchdogen räknar stegning som aktivitet så listan inte stängs mitt i. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
215 lines
5.6 KiB
Rust
215 lines
5.6 KiB
Rust
// Config → CSS. All theming (färger, transparens, skuggor, accent,
|
|
// storlekar, animationer) genereras här och laddas i en CssProvider som
|
|
// byts ut vid live-omladdning. Skärmar med per-skärm-överridar får egna
|
|
// CSS-block scopade på dockfönstrets output-klass (o-<connector>).
|
|
|
|
use gtk4 as gtk;
|
|
use gtk4::gdk;
|
|
|
|
use crate::config::{Config, IndicatorStyle};
|
|
|
|
/// CSS-klass för en viss skärms dockfönster.
|
|
pub fn output_class(connector: &str) -> String {
|
|
let safe: String = connector
|
|
.chars()
|
|
.map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
|
|
.collect();
|
|
format!("o-{safe}")
|
|
}
|
|
|
|
/// "#rrggbb" + alpha → "rgba(r, g, b, a)". Trasig färg → grå.
|
|
fn rgba(hex: &str, alpha: f64) -> String {
|
|
let h = hex.trim_start_matches('#');
|
|
let (r, g, b) = if h.len() == 6 {
|
|
(
|
|
u8::from_str_radix(&h[0..2], 16).unwrap_or(0x88),
|
|
u8::from_str_radix(&h[2..4], 16).unwrap_or(0x88),
|
|
u8::from_str_radix(&h[4..6], 16).unwrap_or(0x88),
|
|
)
|
|
} else {
|
|
(0x88, 0x88, 0x88)
|
|
};
|
|
format!("rgba({r}, {g}, {b}, {alpha:.3})")
|
|
}
|
|
|
|
/// De utseende-styrda reglerna, med valfritt selektor-prefix
|
|
/// ("" globalt, "window.o-DP-1 " för en enskild skärm).
|
|
fn css_body(cfg: &Config, p: &str) -> String {
|
|
let a = &cfg.appearance;
|
|
let bg = rgba(&a.background, a.opacity);
|
|
let border = rgba(&a.border.color, a.border.opacity);
|
|
let accent = rgba(&a.accent, 1.0);
|
|
let accent_soft = rgba(&a.accent, 0.28);
|
|
let shadow = if a.shadow.enabled {
|
|
format!(
|
|
"box-shadow: 0 2px {}px {};",
|
|
a.shadow.blur,
|
|
rgba("#000000", a.shadow.opacity)
|
|
)
|
|
} else {
|
|
String::new()
|
|
};
|
|
let transition = if a.animations {
|
|
"transition: all 150ms ease;"
|
|
} else {
|
|
"transition: none;"
|
|
};
|
|
let hover_zoom = if a.hover_zoom { "-gtk-icon-transform: scale(1.15);" } else { "" };
|
|
let item_pad = (a.icon_spacing / 2).max(0);
|
|
let (ind_w, ind_h, ind_radius) = match a.indicator.style {
|
|
IndicatorStyle::Dot => (5, 5, 3),
|
|
IndicatorStyle::Line => (16, 3, 2),
|
|
IndicatorStyle::None => (0, 0, 0),
|
|
};
|
|
|
|
format!(
|
|
r#"
|
|
{p}.dock-panel {{
|
|
background-color: {bg};
|
|
border-radius: {radius}px;
|
|
border: {bwidth}px solid {border};
|
|
padding: {padding}px;
|
|
{shadow}
|
|
}}
|
|
|
|
{p}.dock-item {{
|
|
background: transparent;
|
|
border: none;
|
|
border-radius: {item_radius}px;
|
|
padding: 4px;
|
|
margin: 0 {item_pad}px;
|
|
min-width: {icon}px;
|
|
min-height: {icon}px;
|
|
{transition}
|
|
}}
|
|
|
|
{p}.dock-item:hover {{
|
|
background-color: {accent_soft};
|
|
}}
|
|
|
|
{p}.dock-item:hover image {{
|
|
{hover_zoom}
|
|
}}
|
|
|
|
{p}.dock-item image {{
|
|
{transition}
|
|
}}
|
|
|
|
{p}.dock-item.urgent {{
|
|
background-color: {accent_soft};
|
|
animation: waydock-pulse 900ms ease-in-out infinite alternate;
|
|
}}
|
|
|
|
{p}.indicator-box {{
|
|
min-height: {ind_h}px;
|
|
}}
|
|
|
|
{p}.indicator {{
|
|
background-color: {accent};
|
|
border-radius: {ind_radius}px;
|
|
min-width: {ind_w}px;
|
|
min-height: {ind_h}px;
|
|
margin: 0 1px;
|
|
}}
|
|
|
|
{p}.indicator.inactive {{
|
|
background-color: {accent_dim};
|
|
}}
|
|
|
|
{p}.group-badge {{
|
|
background-color: {accent};
|
|
color: #ffffff;
|
|
border-radius: 8px;
|
|
font-size: 9px;
|
|
font-weight: bold;
|
|
min-width: 14px;
|
|
min-height: 14px;
|
|
padding: 0 2px;
|
|
}}
|
|
|
|
{p}popover.waydock-menu contents {{
|
|
background-color: {menu_bg};
|
|
border-radius: 10px;
|
|
border: 1px solid {border};
|
|
}}
|
|
|
|
{p}popover.waydock-menu modelbutton,
|
|
{p}popover.waydock-menu button.flat {{
|
|
color: #eeeeee;
|
|
border-radius: 6px;
|
|
padding: 6px 10px;
|
|
}}
|
|
|
|
{p}popover.waydock-menu modelbutton:hover,
|
|
{p}popover.waydock-menu button.flat:hover,
|
|
{p}popover.waydock-menu button.flat.selected {{
|
|
background-color: {accent_soft};
|
|
}}
|
|
"#,
|
|
p = p,
|
|
bg = bg,
|
|
radius = a.corner_radius,
|
|
bwidth = a.border.width,
|
|
border = border,
|
|
padding = a.padding,
|
|
shadow = shadow,
|
|
item_radius = (a.corner_radius as i32 - 4).max(4),
|
|
item_pad = item_pad,
|
|
icon = a.icon_size + 8,
|
|
transition = transition,
|
|
hover_zoom = hover_zoom,
|
|
accent = accent,
|
|
accent_soft = accent_soft,
|
|
accent_dim = rgba(&a.accent, 0.45),
|
|
ind_h = ind_h,
|
|
ind_w = ind_w,
|
|
ind_radius = ind_radius,
|
|
menu_bg = rgba(&a.background, (a.opacity + 0.2).min(1.0)),
|
|
)
|
|
}
|
|
|
|
pub fn build_css(cfg: &Config) -> String {
|
|
let mut css = String::from(
|
|
r#"
|
|
window.waydock,
|
|
window.waydock-sensor,
|
|
window.waydock-hotspot {
|
|
background: transparent;
|
|
}
|
|
|
|
window.waydock-sensor box,
|
|
window.waydock-hotspot box {
|
|
background-color: rgba(0, 0, 0, 0.004);
|
|
}
|
|
|
|
/* utan "to" animeras tillbaka till regelns egen bakgrund (accenten) */
|
|
@keyframes waydock-pulse {
|
|
from { background-color: transparent; }
|
|
}
|
|
"#,
|
|
);
|
|
css += &css_body(cfg, "");
|
|
// skärmar med överridar får egna, mer specifika block
|
|
for name in cfg.outputs.keys() {
|
|
let eff = cfg.for_output(name);
|
|
css += &css_body(&eff, &format!("window.{} ", output_class(name)));
|
|
}
|
|
css
|
|
}
|
|
|
|
/// Ladda (eller byt ut) den globala CSS-providern.
|
|
pub fn apply(cfg: &Config, previous: Option<>k::CssProvider>) -> gtk::CssProvider {
|
|
let display = gdk::Display::default().expect("ingen display");
|
|
if let Some(old) = previous {
|
|
gtk::style_context_remove_provider_for_display(&display, old);
|
|
}
|
|
let provider = gtk::CssProvider::new();
|
|
provider.load_from_string(&build_css(cfg));
|
|
gtk::style_context_add_provider_for_display(
|
|
&display,
|
|
&provider,
|
|
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
|
);
|
|
provider
|
|
}
|