//! Canonical SVG assets, sizing and semantic tint rules for native Ferese UIs. use cosmic::iced::{Color, ContentFit, Length}; use cosmic::theme; use cosmic::widget::icon; pub const ARROW: &[u8] = include_bytes!("../assets/icons/arrow.svg"); pub const BATTERY: &[u8] = include_bytes!("../assets/icons/battery.svg"); pub const CHECK: &[u8] = include_bytes!("../assets/icons/check.svg"); pub const DISPLAY: &[u8] = include_bytes!("../assets/icons/display.svg"); pub const FERESE: &[u8] = include_bytes!("../assets/icons/ferese.svg"); pub const OVERVIEW: &[u8] = include_bytes!("../assets/icons/overview.svg"); pub const AIRPLANE: &[u8] = include_bytes!("../assets/icons/status/airplane.svg"); pub const BATTERY_25: &[u8] = include_bytes!("../assets/icons/status/battery-25.svg"); pub const BATTERY_50: &[u8] = include_bytes!("../assets/icons/status/battery-50.svg"); pub const BATTERY_75: &[u8] = include_bytes!("../assets/icons/status/battery-75.svg"); pub const BATTERY_CHARGING: &[u8] = include_bytes!("../assets/icons/status/battery-charging.svg"); pub const BATTERY_EMPTY: &[u8] = include_bytes!("../assets/icons/status/battery-empty.svg"); pub const BATTERY_FULL: &[u8] = include_bytes!("../assets/icons/status/battery-full.svg"); pub const BLUETOOTH_CONNECTED: &[u8] = include_bytes!("../assets/icons/status/bluetooth-connected.svg"); pub const BLUETOOTH_OFF: &[u8] = include_bytes!("../assets/icons/status/bluetooth-off.svg"); pub const BLUETOOTH_ON: &[u8] = include_bytes!("../assets/icons/status/bluetooth-on.svg"); pub const BRIGHTNESS: &[u8] = include_bytes!("../assets/icons/status/brightness.svg"); pub const CALENDAR: &[u8] = include_bytes!("../assets/icons/status/calendar.svg"); pub const CHEVRON_DOWN: &[u8] = include_bytes!("../assets/icons/status/chevron-down.svg"); pub const CHEVRON_UP: &[u8] = include_bytes!("../assets/icons/status/chevron-up.svg"); pub const CLOSE: &[u8] = include_bytes!("../assets/icons/status/close.svg"); pub const CONTROL_CENTER: &[u8] = include_bytes!("../assets/icons/status/control-center.svg"); pub const DND: &[u8] = include_bytes!("../assets/icons/status/dnd.svg"); pub const ETHERNET: &[u8] = include_bytes!("../assets/icons/status/ethernet.svg"); pub const LOCK: &[u8] = include_bytes!("../assets/icons/status/lock.svg"); pub const MIC_MUTE: &[u8] = include_bytes!("../assets/icons/status/mic-mute.svg"); pub const MIC: &[u8] = include_bytes!("../assets/icons/status/mic.svg"); pub const MOON: &[u8] = include_bytes!("../assets/icons/status/moon.svg"); pub const NOTIFICATIONS_OFF: &[u8] = include_bytes!("../assets/icons/status/notifications-off.svg"); pub const NOTIFICATIONS: &[u8] = include_bytes!("../assets/icons/status/notifications.svg"); pub const POWER_BALANCED: &[u8] = include_bytes!("../assets/icons/status/power-balanced.svg"); pub const POWER_OFF: &[u8] = include_bytes!("../assets/icons/status/power-off.svg"); pub const POWER_PERFORMANCE: &[u8] = include_bytes!("../assets/icons/status/power-performance.svg"); pub const POWER_SAVER: &[u8] = include_bytes!("../assets/icons/status/power-saver.svg"); pub const POWER: &[u8] = include_bytes!("../assets/icons/status/power.svg"); pub const RECORD_CANCEL: &[u8] = include_bytes!("../assets/icons/status/record-cancel.svg"); pub const RECORD_SAVING: &[u8] = include_bytes!("../assets/icons/status/record-saving.svg"); pub const RECORD_STOP: &[u8] = include_bytes!("../assets/icons/status/record-stop.svg"); pub const RECORD: &[u8] = include_bytes!("../assets/icons/status/record.svg"); pub const RESTART: &[u8] = include_bytes!("../assets/icons/status/restart.svg"); pub const SETTINGS: &[u8] = include_bytes!("../assets/icons/status/settings.svg"); pub const SUSPEND: &[u8] = include_bytes!("../assets/icons/status/suspend.svg"); pub const TRASH: &[u8] = include_bytes!("../assets/icons/status/trash.svg"); pub const VOLUME_HIGH: &[u8] = include_bytes!("../assets/icons/status/volume-high.svg"); pub const VOLUME_LOW: &[u8] = include_bytes!("../assets/icons/status/volume-low.svg"); pub const VOLUME_MEDIUM: &[u8] = include_bytes!("../assets/icons/status/volume-medium.svg"); pub const VOLUME_MUTE: &[u8] = include_bytes!("../assets/icons/status/volume-mute.svg"); pub const WIFI_FULL: &[u8] = include_bytes!("../assets/icons/status/wifi-full.svg"); pub const WIFI_LOW: &[u8] = include_bytes!("../assets/icons/status/wifi-low.svg"); pub const WIFI_MEDIUM: &[u8] = include_bytes!("../assets/icons/status/wifi-medium.svg"); pub const WIFI_OFF: &[u8] = include_bytes!("../assets/icons/status/wifi-off.svg"); pub const VOLUME: &[u8] = include_bytes!("../assets/icons/volume.svg"); pub const WIFI: &[u8] = include_bytes!("../assets/icons/wifi.svg"); pub const APPLICATION: &[u8] = include_bytes!("../../../packaging/icons/ferese.svg"); pub fn tinted(source: &'static [u8], size: u16, foreground: Color) -> icon::Icon { accented(source, size, foreground, foreground) } pub fn symbolic(source: &'static [u8], size: u16) -> icon::Icon { icon::from_svg_bytes(source).symbolic(true).icon().size(size) } pub fn accented(source: &'static [u8], size: u16, foreground: Color, accent: Color) -> icon::Icon { let svg = std::str::from_utf8(source).expect("embedded SVG must be UTF-8"); // Battery canvases are wider; reserve that space instead of stretching // or clipping them into the square slot used by the other status icons. let aspect = if svg.contains("viewBox=\"0 0 32 24\"") { 4.0 / 3.0 } else { 1.0 }; icon::from_svg_bytes(tint_svg(source, foreground, accent)) .symbolic(false) .icon() .size(size) .width(Length::Fixed(f32::from(size) * aspect)) .content_fit(ContentFit::Contain) .class(theme::Svg::custom(move |_| cosmic::iced::widget::svg::Style { color: None, })) } pub fn tint_svg(source: &[u8], foreground: Color, accent: Color) -> Vec { let rgba = |foreground: Color| { format!( "rgba({},{},{},{})", (foreground.r * 255.0).round() as u8, (foreground.g * 255.0).round() as u8, (foreground.b * 255.0).round() as u8, foreground.a, ) }; std::str::from_utf8(source) .expect("embedded icons must be UTF-8 SVG") .replace("currentColor", &rgba(foreground)) // The source artwork's blue swatch is the semantic theme accent. // Battery warning/success swatches remain status colors. .replace("#3d7be6", &rgba(accent)) .into_bytes() } pub fn outline(path: &str, tint: Color, size: u16) -> icon::Icon { let svg = format!( r##" "##, ); icon::from_svg_bytes(tint_svg(svg.as_bytes(), tint, tint)) .symbolic(false) .icon() .size(size) } #[cfg(test)] mod tests { use cosmic::iced::Color; #[test] fn icons_use_theme_accent_and_preserve_battery_status_colors() { for (source, accent) in [ (super::BATTERY_FULL, "#63c168"), (super::BATTERY_50, "#3d7be6"), (super::BATTERY_25, "#e0654f"), (super::WIFI_FULL, "#3d7be6"), (super::CONTROL_CENTER, "#3d7be6"), ] { let svg = String::from_utf8(super::tint_svg( source, Color::from_rgb8(205, 214, 244), Color::from_rgb8(203, 166, 247), )) .unwrap(); assert!(!svg.contains("currentColor")); assert!(svg.contains("rgba(205,214,244,1)")); assert!(!svg.contains("#3d7be6")); assert!(svg.contains(if accent == "#3d7be6" { "rgba(203,166,247,1)" } else { accent })); } } }