livesplit_core/component/text/
mod.rs

1//! Provides the Text Component and relevant types for using it. The Text
2//! Component simply visualizes any given text. This can either be a single
3//! centered text, or split up into a left and right text, which is suitable for
4//! a situation where you have a label and a value.
5
6use super::key_value;
7use crate::{
8    platform::prelude::*,
9    settings::{Color, Field, Gradient, SettingsDescription, Value},
10    timing::formatter,
11    util::PopulateString,
12    Timer,
13};
14use alloc::borrow::Cow;
15use core::mem;
16use serde::{Deserialize, Serialize};
17
18#[cfg(test)]
19mod tests;
20
21/// The Text Component simply visualizes any given text. This can either be a
22/// single centered text, or split up into a left and right text, which is
23/// suitable for a situation where you have a label and a value.
24#[derive(Default, Clone)]
25pub struct Component {
26    settings: Settings,
27}
28
29/// The Settings for this component.
30#[derive(Clone, Serialize, Deserialize)]
31#[serde(default)]
32pub struct Settings {
33    /// The background shown behind the component.
34    pub background: Gradient,
35    /// Specifies whether to display the left and right text is supposed to be
36    /// displayed as two rows.
37    pub display_two_rows: bool,
38    /// The color of the left part of the split up text or the whole text if
39    /// it's not split up. If `None` is specified, the color is taken from the
40    /// layout.
41    pub left_center_color: Option<Color>,
42    /// The color of the right part of the split up text. This can be ignored if
43    /// the text is not split up. If `None` is specified, the color is taken
44    /// from the layout.
45    pub right_color: Option<Color>,
46    /// The text to be shown.
47    pub text: Text,
48}
49
50/// The text that is supposed to be shown.
51#[derive(Clone, Serialize, Deserialize)]
52pub enum Text {
53    /// A single centered text.
54    Center(String),
55    /// A text that is split up into a left and right part. This is suitable for
56    /// a situation where you have a label and a value.
57    Split(String, String),
58    /// A custom variable with the name specified is supposed to be shown. The
59    /// boolean indicates whether the name should also be shown as a key value
60    /// pair.
61    Variable(String, bool),
62}
63
64/// The text that is supposed to be shown.
65#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
66pub enum TextState {
67    /// A single centered text.
68    Center(String),
69    /// A text that is split up into a left and right part. This is suitable for
70    /// a situation where you have a label and a value.
71    Split(String, String),
72}
73
74impl Default for TextState {
75    fn default() -> Self {
76        TextState::Center(String::new())
77    }
78}
79
80impl Text {
81    /// Returns whether the text is split up into a left and right part.
82    pub const fn is_split(&self) -> bool {
83        match *self {
84            Text::Split(_, _) => true,
85            Text::Center(_) => false,
86            Text::Variable(_, is_split) => is_split,
87        }
88    }
89
90    /// Sets the centered text. If the current mode is split, it is switched to
91    /// centered mode.
92    pub fn set_center<S: PopulateString>(&mut self, text: S) {
93        if let Text::Center(inner) = self {
94            text.populate(inner);
95        } else {
96            *self = Text::Center(text.into_string());
97        }
98    }
99
100    /// Sets the left text. If the current mode is centered, it is switched to
101    /// split mode, with the right text being empty.
102    pub fn set_left<S: PopulateString>(&mut self, text: S) {
103        if let Text::Split(inner, _) = self {
104            text.populate(inner);
105        } else {
106            *self = Text::Split(text.into_string(), String::from(""));
107        }
108    }
109
110    /// Sets the right text. If the current mode is centered, it is switched to
111    /// split mode, with the left text being empty.
112    pub fn set_right<S: PopulateString>(&mut self, text: S) {
113        if let Text::Split(_, inner) = self {
114            text.populate(inner);
115        } else {
116            *self = Text::Split(String::from(""), text.into_string());
117        }
118    }
119}
120
121/// The state object describes the information to visualize for this component.
122#[derive(Serialize, Deserialize, Default)]
123pub struct State {
124    /// The background shown behind the component.
125    pub background: Gradient,
126    /// Specifies whether to display the left and right text is supposed to be
127    /// displayed as two rows.
128    pub display_two_rows: bool,
129    /// The color of the left part of the split up text or the whole text if
130    /// it's not split up. If `None` is specified, the color is taken from the
131    /// layout.
132    pub left_center_color: Option<Color>,
133    /// The color of the right part of the split up text. This can be ignored if
134    /// the text is not split up. If `None` is specified, the color is taken
135    /// from the layout.
136    pub right_color: Option<Color>,
137    /// The text to show for the component.
138    pub text: TextState,
139}
140
141impl Default for Settings {
142    fn default() -> Self {
143        Self {
144            background: key_value::DEFAULT_GRADIENT,
145            display_two_rows: false,
146            left_center_color: None,
147            right_color: None,
148            text: Text::Center(String::from("")),
149        }
150    }
151}
152
153#[cfg(feature = "std")]
154impl State {
155    /// Encodes the state object's information as JSON.
156    pub fn write_json<W>(&self, writer: W) -> serde_json::Result<()>
157    where
158        W: std::io::Write,
159    {
160        serde_json::to_writer(writer, self)
161    }
162}
163
164impl Component {
165    /// Creates a new Text Component.
166    pub fn new() -> Self {
167        Default::default()
168    }
169
170    /// Creates a new Text Component with the given settings.
171    pub const fn with_settings(settings: Settings) -> Self {
172        Self { settings }
173    }
174
175    /// Accesses the settings of the component.
176    pub const fn settings(&self) -> &Settings {
177        &self.settings
178    }
179
180    /// Grants mutable access to the settings of the component.
181    pub fn settings_mut(&mut self) -> &mut Settings {
182        &mut self.settings
183    }
184
185    /// Accesses the name of the component.
186    pub fn name(&self) -> Cow<'_, str> {
187        let name: Cow<'_, str> = match &self.settings.text {
188            Text::Center(text) => text.as_str().into(),
189            Text::Split(left, right) => {
190                let mut name = String::with_capacity(left.len() + right.len() + 1);
191                name.push_str(left);
192                if !left.is_empty() && !right.is_empty() {
193                    name.push(' ');
194                }
195                name.push_str(right);
196                name.into()
197            }
198            Text::Variable(var_name, _) => var_name.as_str().into(),
199        };
200
201        if name.trim().is_empty() {
202            "Text".into()
203        } else {
204            name
205        }
206    }
207
208    /// Updates the component's state based on the timer provided.
209    pub fn update_state(&self, state: &mut State, timer: &Timer) {
210        state.background = self.settings.background;
211        state.display_two_rows = self.settings.text.is_split() && self.settings.display_two_rows;
212        state.left_center_color = self.settings.left_center_color;
213        state.right_color = self.settings.right_color;
214
215        let (left_center, right) = match &self.settings.text {
216            Text::Center(center) => (center.as_str(), None),
217            Text::Split(left, right) => (left.as_str(), Some(right.as_str())),
218            Text::Variable(var_name, is_split) => {
219                let value = timer
220                    .run()
221                    .metadata()
222                    .custom_variable(var_name)
223                    .map(|var| var.value.as_str())
224                    .filter(|value| !value.trim_start().is_empty())
225                    .unwrap_or(formatter::DASH);
226
227                if *is_split {
228                    (var_name.as_str(), Some(value))
229                } else {
230                    (value, None)
231                }
232            }
233        };
234
235        // FIXME: We may not want to keep using an enum for this. This is really
236        // painful to deal with, and we still don't reuse memory in every case.
237        match (&mut state.text, left_center, right) {
238            (TextState::Center(old), new, None) => {
239                old.clear();
240                old.push_str(new);
241            }
242            (TextState::Center(old), new_l, Some(new_r)) => {
243                let mut new_l_mem = mem::take(old);
244                new_l_mem.clear();
245                new_l_mem.push_str(new_l);
246                state.text = TextState::Split(new_l_mem, new_r.to_owned());
247            }
248            (TextState::Split(l, r), new_l, Some(new_r)) => {
249                l.clear();
250                l.push_str(new_l);
251                r.clear();
252                r.push_str(new_r);
253            }
254            (TextState::Split(l, _), new_center, None) => {
255                let mut new_center_mem = mem::take(l);
256                new_center_mem.clear();
257                new_center_mem.push_str(new_center);
258                state.text = TextState::Center(new_center_mem);
259            }
260        }
261    }
262
263    /// Calculates the component's state.
264    pub fn state(&self, timer: &Timer) -> State {
265        let mut state = Default::default();
266        self.update_state(&mut state, timer);
267        state
268    }
269
270    /// Accesses a generic description of the settings available for this
271    /// component and their current values.
272    pub fn settings_description(&self) -> SettingsDescription {
273        let (first, second, is_variable, is_split, left_color, right_color) =
274            match &self.settings.text {
275                Text::Center(text) => (
276                    Field::new("Text".into(), text.to_string().into()),
277                    None,
278                    false,
279                    false,
280                    "Text Color",
281                    "",
282                ),
283                Text::Split(left, right) => (
284                    Field::new("Left".into(), left.to_string().into()),
285                    Some(Field::new("Right".into(), right.to_string().into())),
286                    false,
287                    true,
288                    "Left Color",
289                    "Right Color",
290                ),
291                Text::Variable(var_name, is_split) => (
292                    Field::new("Variable".into(), var_name.to_string().into()),
293                    None,
294                    true,
295                    *is_split,
296                    if *is_split {
297                        "Name Color"
298                    } else {
299                        "Value Color"
300                    },
301                    "Value Color",
302                ),
303            };
304
305        let mut fields = vec![
306            Field::new("Background".into(), self.settings.background.into()),
307            Field::new("Use Variable".into(), is_variable.into()),
308            Field::new("Split".into(), is_split.into()),
309            first,
310            Field::new(left_color.into(), self.settings.left_center_color.into()),
311        ];
312
313        if let Some(second) = second {
314            fields.push(second);
315        }
316
317        if is_split {
318            fields.push(Field::new(
319                right_color.into(),
320                self.settings.right_color.into(),
321            ));
322            fields.push(Field::new(
323                "Display 2 Rows".into(),
324                self.settings.display_two_rows.into(),
325            ));
326        }
327
328        SettingsDescription::with_fields(fields)
329    }
330
331    /// Sets a setting's value by its index to the given value.
332    ///
333    /// # Panics
334    ///
335    /// This panics if the type of the value to be set is not compatible with
336    /// the type of the setting's value. A panic can also occur if the index of
337    /// the setting provided is out of bounds.
338    pub fn set_value(&mut self, mut index: usize, value: Value) {
339        if index >= 5 {
340            if let Text::Variable(_, _) = &self.settings.text {
341                index += 1;
342            }
343        }
344
345        match index {
346            0 => self.settings.background = value.into(),
347            1 => {
348                self.settings.text = match (value.into_bool().unwrap(), &mut self.settings.text) {
349                    (false, Text::Variable(name, true)) => {
350                        Text::Split(mem::take(name), String::new())
351                    }
352                    (false, Text::Variable(name, false)) => Text::Center(mem::take(name)),
353                    (true, Text::Center(center)) => Text::Variable(mem::take(center), false),
354                    (true, Text::Split(left, _)) => Text::Variable(mem::take(left), true),
355                    _ => return,
356                };
357            }
358            2 => {
359                self.settings.text = match (value.into_bool().unwrap(), &mut self.settings.text) {
360                    (true, Text::Center(center)) => {
361                        self.settings.right_color = self.settings.left_center_color;
362                        self.settings.display_two_rows = false;
363
364                        Text::Split(mem::take(center), String::new())
365                    }
366                    (false, Text::Split(left, right)) => {
367                        let mut value = mem::take(left);
368                        let right = mem::take(right);
369                        if !value.is_empty() && !right.is_empty() {
370                            value.push(' ');
371                        }
372                        value.push_str(&right);
373
374                        Text::Center(value)
375                    }
376                    (should_be_split, Text::Variable(_, is_split)) => {
377                        *is_split = should_be_split;
378                        return;
379                    }
380                    _ => return,
381                };
382            }
383            3 => match &mut self.settings.text {
384                Text::Center(center) => *center = value.into(),
385                Text::Split(left, _) => *left = value.into(),
386                Text::Variable(var_name, _) => *var_name = value.into(),
387            },
388            4 => self.settings.left_center_color = value.into(),
389            5 => match &mut self.settings.text {
390                Text::Center(_) => panic!("Can't set right text when there's only a center text"),
391                Text::Split(_, right) => *right = value.into(),
392                Text::Variable(_, _) => {
393                    unreachable!("Shouldn't be able to set value for a variable")
394                }
395            },
396            6 => self.settings.right_color = value.into(),
397            7 => self.settings.display_two_rows = value.into(),
398            _ => panic!("Unsupported Setting Index"),
399        }
400    }
401}