1use crate::errors::*;
2use crate::util;
3use serde::Deserialize;
4use std::collections::HashMap;
5
6#[derive(Deserialize, Debug, Clone)]
7#[serde(try_from = "IconsConfigRaw")]
8pub struct Icons(pub HashMap<String, Icon>);
9
10#[derive(Deserialize, Debug, Clone)]
11#[serde(untagged)]
12pub enum Icon {
13 Single(String),
14 Progression(Vec<String>),
15}
16
17impl From<&'static str> for Icon {
18 fn from(value: &'static str) -> Self {
19 Self::Single(value.into())
20 }
21}
22
23impl<const N: usize> From<[&str; N]> for Icon {
24 fn from(value: [&str; N]) -> Self {
25 Self::Progression(value.iter().map(|s| s.to_string()).collect())
26 }
27}
28
29impl Default for Icons {
30 fn default() -> Self {
31 Self(map! {
33 "backlight" => "BRIGHT",
34 "bat" => "BAT",
35 "bat_charging" => "CHG",
36 "bat_not_available" => "BAT N/A",
37 "bell" => "ON",
38 "bell-slash" => "OFF",
39 "bluetooth" => "BT",
40 "calendar" => "CAL",
41 "cogs" => "LOAD",
42 "cpu" => "CPU",
43 "cpu_boost_on" => "BOOST ON",
44 "cpu_boost_off" => "BOOST OFF",
45 "disk_drive" => "DISK",
46 "docker" => "DOCKER",
47 "github" => "GITHUB",
48 "gpu" => "GPU",
49 "headphones" => "HEAD",
50 "hueshift" => "HUE",
51 "joystick" => "JOY",
52 "keyboard" => "KBD",
53 "mail" => "MAIL",
54 "memory_mem" => "MEM",
55 "memory_swap" => "SWAP",
56 "mouse" => "MOUSE",
57 "music" => "MUSIC",
58 "music_next" => ">",
59 "music_pause" => "||",
60 "music_play" => ">",
61 "music_prev" => "<",
62 "net_bridge" => "BRIDGE",
63 "net_cellular" => [
64 "NO SIGNAL",
65 "0 BARS",
66 "1 BAR",
67 "2 BARS",
68 "3 BARS",
69 "4 BARS",
70 ],
71 "net_down" => "DOWN",
72 "net_loopback" => "LO",
73 "net_modem" => "MODEM",
74 "net_up" => "UP ",
75 "net_vpn" => "VPN",
76 "net_wired" => "ETH",
77 "net_wireless" => "WLAN",
78 "notification" => "NOTIF",
79 "phone" => "PHONE",
80 "phone_disconnected" => "PHONE",
81 "ping" => "PING",
82 "pomodoro" => "POMODORO",
83 "pomodoro_break" => "BREAK",
84 "pomodoro_paused" => "PAUSED",
85 "pomodoro_started" => "STARTED",
86 "pomodoro_stopped" => "STOPPED",
87 "refresh" => "REFRESH",
88 "resolution" => "RES",
89 "scratchpad" => "[]",
90 "tasks" => "TSK",
91 "tea" => "TEA",
92 "thermometer" => "TEMP",
93 "time" => "TIME",
94 "toggle_off" => "OFF",
95 "toggle_on" => "ON",
96 "unknown" => "??",
97 "update" => "UPD",
98 "uptime" => "UP",
99 "volume" => "VOL",
100 "volume_muted" => "VOL MUTED",
101 "microphone" => "MIC",
102 "microphone_muted" => "MIC MUTED",
103 "weather_clouds_night" => "CLOUDY",
104 "weather_clouds" => "CLOUDY",
105 "weather_default" => "WEATHER",
106 "weather_fog_night" => "FOG",
107 "weather_fog" => "FOG",
108 "weather_moon" => "MOONY",
109 "weather_rain_night" => "RAIN",
110 "weather_rain" => "RAIN",
111 "weather_snow" => "SNOW",
112 "weather_sun" => "SUNNY",
113 "weather_thunder_night" => "STORM",
114 "weather_thunder" => "STORM",
115 "webcam" => "CAM",
116 "xrandr" => "SCREEN"
117 })
118 }
119}
120
121impl Icons {
122 pub fn from_file(file: &str) -> Result<Self> {
123 if file == "none" {
124 Ok(Icons::default())
125 } else {
126 let file = util::find_file(file, Some("icons"), Some("toml"))?
127 .or_error(|| format!("Icon set '{file}' not found"))?;
128 Ok(Icons(util::deserialize_toml_file(file)?))
129 }
130 }
131
132 pub fn apply_overrides(&mut self, overrides: HashMap<String, Icon>) {
133 self.0.extend(overrides);
134 }
135
136 pub fn get(&self, icon: &'_ str, value: Option<f64>) -> Option<&str> {
137 match (self.0.get(icon)?, value) {
138 (Icon::Single(icon), _) => Some(icon),
139 (Icon::Progression(prog), _) if prog.is_empty() => None,
140 (Icon::Progression(prog), None) => Some(prog.last().unwrap()),
141 (Icon::Progression(prog), Some(value)) => {
142 let index = ((value * prog.len() as f64) as usize).clamp(0, prog.len() - 1);
143 Some(prog[index].as_str())
144 }
145 }
146 }
147}
148
149#[derive(Deserialize, Default)]
150#[serde(deny_unknown_fields, default)]
151struct IconsConfigRaw {
152 icons: Option<String>,
153 overrides: Option<HashMap<String, Icon>>,
154}
155
156impl TryFrom<IconsConfigRaw> for Icons {
157 type Error = Error;
158
159 fn try_from(raw: IconsConfigRaw) -> Result<Self, Self::Error> {
160 let mut icons = Self::from_file(raw.icons.as_deref().unwrap_or("none"))?;
161 if let Some(overrides) = raw.overrides {
162 for icon in overrides {
163 icons.0.insert(icon.0, icon.1);
164 }
165 }
166 Ok(icons)
167 }
168}