reddish_shift/
types_display.rs

1/*  types_display.rs -- Display implementation for types
2    This file is part of <https://github.com/mahor1221/reddish-shift>.
3    Copyright (C) 2024 Mahor Foruzesh <mahor1221@gmail.com>
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <https://www.gnu.org/licenses/>.
17*/
18
19use crate::{
20    config::Config,
21    types::{
22        Brightness, ColorSettings, Elevation, ElevationRange, Gamma, Location,
23        Period, PeriodInfo, Temperature, Time, TimeOffset, TimeRange,
24        TimeRanges, TransitionScheme,
25    },
26    AdjustmentMethod, DaemonMode, FadeStatus, LocationProvider,
27};
28use anstyle::{AnsiColor, Color, Style};
29use std::fmt::{self, Display, Formatter};
30use tracing::info;
31
32pub const WARN: Style = Style::new()
33    .bold()
34    .fg_color(Some(Color::Ansi(AnsiColor::Yellow)));
35pub const ERR: Style = Style::new()
36    .bold()
37    .fg_color(Some(Color::Ansi(AnsiColor::Red)));
38pub const HEADER: Style = Style::new().bold().underline();
39pub const BODY: Style = Style::new().bold();
40
41impl Display for Temperature {
42    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
43        write!(f, "{}", **self)
44    }
45}
46
47struct TemperatureDisplay<'a>(&'a Temperature);
48impl Display for TemperatureDisplay<'_> {
49    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
50        write!(f, "{}K", **self.0)
51    }
52}
53
54impl Display for Brightness {
55    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
56        write!(f, "{}", **self)
57    }
58}
59
60struct BrightnessDisplay<'a>(&'a Brightness);
61impl Display for BrightnessDisplay<'_> {
62    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
63        write!(f, "{}%", **self.0 as u8 * 100)
64    }
65}
66
67impl Display for Gamma {
68    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
69        write!(f, "{:.2}:{:.2}:{:.2}", self[0], self[1], self[2])
70    }
71}
72
73struct GammaDisplay<'a>(&'a Gamma);
74impl Display for GammaDisplay<'_> {
75    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
76        write!(f, "{:.2}, {:.2}, {:.2}", self.0[0], self.0[1], self.0[2])
77    }
78}
79
80impl Display for Time {
81    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
82        let Time { hour: h, minute: m } = self;
83        write!(f, "{h:02}:{m:02}")
84    }
85}
86
87impl Display for TimeOffset {
88    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
89        Display::fmt(&Time::from(*self), f)
90    }
91}
92
93impl Display for TimeRange {
94    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
95        let Self { start, end } = self;
96        write!(f, "from {start} to {end}")
97    }
98}
99
100impl Display for Elevation {
101    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
102        //// TRANSLATORS: Append degree symbol if possible
103        write!(f, "{:.2}°", **self)
104    }
105}
106
107impl Display for Location {
108    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
109        let a = *self.lat;
110        let b = *self.lon;
111        let ns = if a >= 0.0 { "N" } else { "S" };
112        let ew = if b >= 0.0 { "E" } else { "W" };
113        let a = a.abs();
114        let a1 = a as u8;
115        let a2 = (a.fract() * 100.0) as u8;
116        let a3 = ((a * 100.0).fract() * 100.0) as u8;
117        let b = b.abs();
118        let b1 = b as u8;
119        let b2 = (b.fract() * 100.0) as u8;
120        let b3 = ((b * 100.0).fract() * 100.0) as u8;
121        write!(f, "{a1}°{a2}′{a3}″{ns}, {b1}°{b2}′{b3}″{ew}")
122    }
123}
124
125//
126
127struct ColorSettingsDisplay<'a> {
128    temp: TemperatureDisplay<'a>,
129    gamma: GammaDisplay<'a>,
130    brght: BrightnessDisplay<'a>,
131}
132
133impl<'a> From<&'a ColorSettings> for ColorSettingsDisplay<'a> {
134    fn from(cs: &'a ColorSettings) -> Self {
135        let ColorSettings { temp, gamma, brght } = cs;
136        let temp = TemperatureDisplay(temp);
137        let gamma = GammaDisplay(gamma);
138        let brght = BrightnessDisplay(brght);
139        Self { temp, gamma, brght }
140    }
141}
142
143impl Display for ColorSettings {
144    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
145        let ColorSettingsDisplay { temp, gamma, brght } = self.into();
146        write!(
147            f,
148            "    {BODY}Temperature{BODY:#}: {temp}K
149    {BODY}Brightness{BODY:#}: {brght}%
150    {BODY}Gamma{BODY:#}: {gamma}"
151        )
152    }
153}
154
155impl Display for Period {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        match self {
158            Period::Night => write!(f, "    {BODY}Period{BODY:#}: night"),
159            Period::Daytime => write!(f, "    {BODY}Period{BODY:#}: daytime"),
160            Period::Transition { progress } => {
161                write!(
162                    f,
163                    "    {BODY}Period{BODY:#}: transition ({progress}% day)"
164                )
165            }
166        }
167    }
168}
169
170impl Display for PeriodInfo {
171    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
172        match self {
173            PeriodInfo::Time => Ok(()),
174            PeriodInfo::Elevation { elev, loc } => {
175                write!(
176                    f,
177                    "    {BODY}Solar elevation{BODY:#}: {elev}
178    {BODY}Location{BODY:#}: {loc}"
179                )
180            }
181        }
182    }
183}
184
185impl Display for AdjustmentMethod {
186    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
187        let s = match self {
188            AdjustmentMethod::Dummy(_) => "dummy",
189            #[cfg(unix_without_macos)]
190            AdjustmentMethod::Randr(_) => "randr",
191            #[cfg(unix_without_macos)]
192            AdjustmentMethod::Drm(_) => "drm",
193            #[cfg(unix_without_macos)]
194            AdjustmentMethod::Vidmode(_) => "vidmode",
195            #[cfg(windows)]
196            AdjustmentMethod::Win32Gdi(_) => "win32gdi",
197        };
198        write!(f, "{s}")
199    }
200}
201
202impl Display for LocationProvider {
203    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
204        let s = match self {
205            LocationProvider::Manual(_) => "manual",
206            LocationProvider::Geoclue2(_) => "geoclue2",
207        };
208        write!(f, "{s}")
209    }
210}
211
212impl Display for Config {
213    #[allow(clippy::too_many_lines)]
214    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
215        let Config {
216            day,
217            night,
218            scheme,
219            location,
220            method,
221            reset_ramps,
222            disable_fade,
223            sleep_duration_short,
224            sleep_duration,
225            mode: _,
226            time: _,
227        } = self;
228
229        writeln!(f, "{BODY}Adjustment method{BODY:#}: {method}")?;
230        writeln!(f, "{BODY}Location provider{BODY:#}: {location}")?;
231        writeln!(f, "{BODY}Reset ramps{BODY:#}: {reset_ramps}")?;
232        writeln!(f, "{BODY}Disable fade{BODY:#}: {disable_fade}")?;
233        let s = sleep_duration.as_millis();
234        writeln!(f, "{BODY}Sleep duration{BODY:#}: {s}ms")?;
235        let s = sleep_duration_short.as_millis();
236        writeln!(f, "{BODY}Sleep duration short{BODY:#}: {s}ms")?;
237
238        writeln!(f, "{HEADER}Daytime{HEADER:#}:")?;
239        match scheme {
240            TransitionScheme::Time(TimeRanges {
241                dawn: TimeRange { end, .. },
242                dusk: TimeRange { start, .. },
243            }) => {
244                writeln!(f, "    {BODY}Time{BODY:#}: from {end} to {start}")?;
245            }
246            TransitionScheme::Elev(ElevationRange { high, .. }) => {
247                writeln!(
248                    f,
249                    "    {BODY}Solar elevation{BODY:#}: above {high}"
250                )?;
251            }
252        }
253        writeln!(f, "{day}")?;
254
255        writeln!(f, "{HEADER}Night{HEADER:#}:")?;
256        match scheme {
257            TransitionScheme::Time(TimeRanges {
258                dawn: TimeRange { start, .. },
259                dusk: TimeRange { end, .. },
260            }) => {
261                writeln!(f, "    {BODY}Time{BODY:#}: from {end} to {start}")?;
262            }
263            TransitionScheme::Elev(ElevationRange { low, .. }) => {
264                writeln!(f, "    {BODY}Solar elevation{BODY:#}: below {low}")?;
265            }
266        }
267        write!(f, "{night}")?;
268        Ok(())
269    }
270}
271
272impl DaemonMode<'_, '_> {
273    #[allow(clippy::too_many_lines)]
274    pub fn log(&self) {
275        if Some(&self.period) != self.prev_period.as_ref() {
276            info!("{}", self.period);
277        }
278        match (&self.info, &self.prev_info) {
279            (
280                PeriodInfo::Elevation { elev: e1, .. },
281                Some(PeriodInfo::Elevation { elev: e2, .. }),
282            ) if e1 != e2 => {
283                info!("    {BODY}Solar elevation{BODY:#}: {e1}");
284            }
285            (
286                PeriodInfo::Elevation { loc: l1, .. },
287                Some(PeriodInfo::Elevation { loc: l2, .. }),
288            ) if l1 != l2 => {
289                info!("    {BODY}Location{BODY:#}: {l1}");
290            }
291            (PeriodInfo::Elevation { .. }, None) => {
292                info!("{}", self.info);
293            }
294            (
295                i1 @ PeriodInfo::Elevation { .. },
296                Some(i2 @ PeriodInfo::Elevation { .. }),
297            ) if i1 != i2 => {
298                info!("{}", self.info);
299            }
300            _ => {}
301        }
302
303        let ColorSettingsDisplay { temp, gamma, brght } =
304            (&self.interp).into();
305        if self.fade == FadeStatus::Completed || self.prev_interp.is_none() {
306            if Some(temp.0) != self.prev_interp.as_ref().map(|c| &c.temp) {
307                info!("    {BODY}Temperature{BODY:#}: {temp}K");
308            }
309            if Some(gamma.0) != self.prev_interp.as_ref().map(|c| &c.gamma) {
310                info!("    {BODY}Gamma{BODY:#}: {gamma}");
311            }
312            if Some(brght.0) != self.prev_interp.as_ref().map(|c| &c.brght) {
313                info!("    {BODY}Brightness{BODY:#}: {brght}%");
314            }
315        } else if Some(temp.0) != self.prev_interp.as_ref().map(|c| &c.temp) {
316            info!("    {BODY}Temperature{BODY:#}: {temp}K");
317        }
318    }
319}