i3status_rs/formatting/formatter/
str.rs1use std::iter::repeat;
2use std::time::Instant;
3
4use crate::escape::CollectEscaped;
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.val.parse().error("Width must be a positive integer")?;
40 }
41 "max_width" | "max_w" => {
42 max_width = arg.val.parse().error("Width must be a positive integer")?;
43 }
44 "width" | "w" => {
45 min_width = arg.val.parse().error("Width must be a positive integer")?;
46 max_width = min_width;
47 }
48 "rot_interval" => {
49 rot_interval = Some(
50 arg.val
51 .parse()
52 .error("Interval must be a positive number")?,
53 );
54 }
55 "rot_separator" => {
56 rot_separator = Some(arg.val.to_string());
57 }
58 other => {
59 return Err(Error::new(format!("Unknown argument for 'str': '{other}'")));
60 }
61 }
62 }
63 if max_width < min_width {
64 return Err(Error::new(
65 "Max width must be greater of equal to min width",
66 ));
67 }
68 if let Some(rot_interval) = rot_interval {
69 if rot_interval < 0.1 {
70 return Err(Error::new("Interval must be greater than 0.1"));
71 }
72 }
73 Ok(StrFormatter {
74 min_width,
75 max_width,
76 rot_interval_ms: rot_interval.map(|x| (x * 1e3) as u64),
77 init_time: Some(Instant::now()),
78 rot_separator,
79 })
80 }
81}
82
83impl Formatter for StrFormatter {
84 fn format(&self, val: &Value, config: &SharedConfig) -> Result<String, FormatError> {
85 match val {
86 Value::Text(text) => {
87 let text: Vec<&str> = text.graphemes(true).collect();
88 let width = text.len();
89 Ok(match (self.rot_interval_ms, self.init_time) {
90 (Some(rot_interval_ms), Some(init_time)) if width > self.max_width => {
91 let rot_separator: Vec<&str> = self
92 .rot_separator
93 .as_deref()
94 .unwrap_or("|")
95 .graphemes(true)
96 .collect();
97 let width = width + rot_separator.len(); let step = (init_time.elapsed().as_millis() as u64 / rot_interval_ms)
99 as usize
100 % width;
101 let w1 = self.max_width.min(width - step);
102 text.iter()
103 .chain(rot_separator.iter())
104 .skip(step)
105 .take(w1)
106 .chain(text.iter())
107 .take(self.max_width)
108 .collect_pango_escaped()
109 }
110 _ => text
111 .iter()
112 .chain(repeat(&" ").take(self.min_width.saturating_sub(width)))
113 .take(self.max_width)
114 .collect_pango_escaped(),
115 })
116 }
117 Value::Icon(icon, value) => config.get_icon(icon, *value).map_err(Into::into),
118 other => Err(FormatError::IncompatibleFormatter {
119 ty: other.type_name(),
120 fmt: "str",
121 }),
122 }
123 }
124
125 fn interval(&self) -> Option<Duration> {
126 self.rot_interval_ms.map(Duration::from_millis)
127 }
128}