// 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-). 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 }