i3status_rs/formatting/formatter/
str.rs1use std::iter::repeat_n;
2use std::time::Instant;
3
4use crate::escape::CollectEscaped as _;
5
6use super::*;
7
8const DEFAULT_STR_MIN_WIDTH: usize = 0;
9const DEFAULT_STR_MAX_WIDTH: usize = usize::MAX;
10const DEFAULT_STR_ROT_INTERVAL: Option<f64> = None;
11const DEFAULT_STR_ROT_SEP: Option<String> = None;
12
13pub const DEFAULT_STRING_FORMATTER: StrFormatter = StrFormatter {
14 min_width: DEFAULT_STR_MIN_WIDTH,
15 max_width: DEFAULT_STR_MAX_WIDTH,
16 rot_interval_ms: None,
17 init_time: None,
18 rot_separator: None,
19};
20
21#[derive(Debug)]
22pub struct StrFormatter {
23 min_width: usize,
24 max_width: usize,
25 rot_interval_ms: Option<u64>,
26 init_time: Option<Instant>,
27 rot_separator: Option<String>,
28}
29
30impl StrFormatter {
31 pub(super) fn from_args(args: &[Arg]) -> Result<Self> {
32 let mut min_width = DEFAULT_STR_MIN_WIDTH;
33 let mut max_width = DEFAULT_STR_MAX_WIDTH;
34 let mut rot_interval = DEFAULT_STR_ROT_INTERVAL;
35 let mut rot_separator = DEFAULT_STR_ROT_SEP;
36 for arg in args {
37 match arg.key {
38 "min_width" | "min_w" => {
39 min_width = arg.parse_value()?;
40 }
41 "max_width" | "max_w" => {
42 max_width = arg.parse_value()?;
43 }
44 "width" | "w" => {
45 min_width = arg.parse_value()?;
46 max_width = min_width;
47 }
48 "rot_interval" => {
49 rot_interval = Some(arg.parse_value()?);
50 }
51 "rot_separator" => {
52 rot_separator = Some(arg.parse_value()?);
53 }
54 other => {
55 return Err(Error::new(format!("Unknown argument for 'str': '{other}'")));
56 }
57 }
58 }
59 if max_width < min_width {
60 return Err(Error::new(
61 "Max width must be greater of equal to min width",
62 ));
63 }
64 if let Some(rot_interval) = rot_interval {
65 if rot_interval < 0.1 {
66 return Err(Error::new("Interval must be greater than 0.1"));
67 }
68 }
69 Ok(StrFormatter {
70 min_width,
71 max_width,
72 rot_interval_ms: rot_interval.map(|x| (x * 1e3) as u64),
73 init_time: Some(Instant::now()),
74 rot_separator,
75 })
76 }
77}
78
79impl Formatter for StrFormatter {
80 fn format(&self, val: &Value, config: &SharedConfig) -> Result<String, FormatError> {
81 match val {
82 Value::Text(text) => {
83 let text: Vec<&str> = text.graphemes(true).collect();
84 let width = text.len();
85 Ok(match (self.rot_interval_ms, self.init_time) {
86 (Some(rot_interval_ms), Some(init_time)) if width > self.max_width => {
87 let rot_separator: Vec<&str> = self
88 .rot_separator
89 .as_deref()
90 .unwrap_or("|")
91 .graphemes(true)
92 .collect();
93 let width = width + rot_separator.len(); let step = (init_time.elapsed().as_millis() as u64 / rot_interval_ms)
95 as usize
96 % width;
97 let w1 = self.max_width.min(width - step);
98 text.iter()
99 .chain(rot_separator.iter())
100 .skip(step)
101 .take(w1)
102 .chain(text.iter())
103 .take(self.max_width)
104 .collect_pango_escaped()
105 }
106 _ => text
107 .iter()
108 .chain(repeat_n(&" ", self.min_width.saturating_sub(width)))
109 .take(self.max_width)
110 .collect_pango_escaped(),
111 })
112 }
113 Value::Icon(icon, value) => config.get_icon(icon, *value).map_err(Into::into),
114 other => Err(FormatError::IncompatibleFormatter {
115 ty: other.type_name(),
116 fmt: "str",
117 }),
118 }
119 }
120
121 fn interval(&self) -> Option<Duration> {
122 self.rot_interval_ms.map(Duration::from_millis)
123 }
124}