cursive_core/style/
effect.rs

1use enum_map::{Enum, EnumMap};
2use enumset::{EnumSet, EnumSetType};
3use std::str::FromStr;
4
5/// A concrete set of effects to enable.
6///
7/// Every missing effect should be disabled.
8pub type ConcreteEffects = EnumSet<Effect>;
9
10/// Text effect
11#[allow(clippy::derived_hash_with_manual_eq)] // We do derive it through EnumSetType
12#[derive(EnumSetType, Enum, Debug, Hash)]
13pub enum Effect {
14    /// No effect
15    Simple,
16
17    /// Reverses foreground and background colors
18    Reverse,
19
20    /// Prints foreground as "dim" or "faint" (has no effect for ncurses/pancurses/blt backends)
21    Dim,
22
23    /// Prints foreground in bold
24    Bold,
25
26    /// Prints foreground in italic
27    Italic,
28
29    /// Prints foreground with strikethrough (has no effect for ncurses and blt backends)
30    Strikethrough,
31
32    /// Prints foreground with underline
33    Underline,
34
35    /// Foreground text blinks (background color is static).
36    Blink,
37}
38
39impl Effect {
40    /// Returns the order of the effect in the effect set/map.
41    ///
42    /// This is very brittle and should be kept in sync with the enum definition. Might benefit
43    /// from a proc macro.
44    ///
45    /// This is all because enum_map's Enum derive is trait-based and does not support const fn.
46    pub(crate) const fn ordinal(self) -> usize {
47        match self {
48            Effect::Simple => 0,
49            Effect::Reverse => 1,
50            Effect::Dim => 2,
51            Effect::Bold => 3,
52            Effect::Italic => 4,
53            Effect::Strikethrough => 5,
54            Effect::Underline => 6,
55            Effect::Blink => 7,
56        }
57    }
58}
59
60/// A set of effects status.
61///
62/// Describes what to do for each effect: enable, disable, preserve, xor.
63#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
64pub struct Effects {
65    /// The status of each effect.
66    pub statuses: EnumMap<Effect, EffectStatus>,
67}
68
69impl Default for Effects {
70    fn default() -> Self {
71        Self::empty()
72    }
73}
74
75impl From<ConcreteEffects> for Effects {
76    fn from(other: ConcreteEffects) -> Self {
77        let mut result = Self::default();
78        for effect in other {
79            result.statuses[effect] = EffectStatus::OppositeParent;
80        }
81        result
82    }
83}
84
85impl Effects {
86    /// An empty set of effects.
87    pub const EMPTY: Self = Effects::empty();
88
89    /// Return an empty set of effects.
90    ///
91    /// They will all be set to `InheritParent`.
92    pub const fn empty() -> Self {
93        let statuses = [EffectStatus::InheritParent; Effect::LENGTH];
94        Self {
95            statuses: EnumMap::from_array(statuses),
96        }
97    }
98
99    /// Sets the given effect to be `InheritParent`.
100    pub fn remove(&mut self, effect: Effect) {
101        self.statuses[effect] = EffectStatus::InheritParent;
102    }
103
104    /// Sets the given effect to be `OppositeParent`.
105    pub fn insert(&mut self, effect: Effect) {
106        self.statuses[effect] = EffectStatus::OppositeParent;
107    }
108
109    /// Helper function to implement `Self::only()`.
110    const fn status_for(i: usize, effect: Effect) -> EffectStatus {
111        if i == effect.ordinal() {
112            EffectStatus::OppositeParent
113        } else {
114            EffectStatus::InheritParent
115        }
116    }
117
118    /// Return a set of effects with only one effect.
119    ///
120    /// It will be set to `OppositeParent`. Every other effect will be `InheritParent`.
121    pub const fn only(effect: Effect) -> Self {
122        // TODO: make this less brittle?
123        let statuses = [
124            Self::status_for(0, effect),
125            Self::status_for(1, effect),
126            Self::status_for(2, effect),
127            Self::status_for(3, effect),
128            Self::status_for(4, effect),
129            Self::status_for(5, effect),
130            Self::status_for(6, effect),
131            Self::status_for(7, effect),
132        ];
133
134        Self {
135            statuses: EnumMap::from_array(statuses),
136        }
137    }
138
139    /// Resolve an effects directive into concrete effects.
140    pub fn resolve(&self, old: ConcreteEffects) -> ConcreteEffects {
141        let mut result = ConcreteEffects::default();
142        for (effect, status) in self.statuses {
143            if matches!(
144                (status, old.contains(effect)),
145                (EffectStatus::On, _)
146                    | (EffectStatus::InheritParent, true)
147                    | (EffectStatus::OppositeParent, false)
148            ) {
149                result.insert(effect);
150            }
151        }
152        result
153    }
154
155    /// Merge the two sets of effects.
156    pub fn merge(mut old: Self, new: Self) -> Self {
157        for (effect, status) in new.statuses {
158            old.statuses[effect] = EffectStatus::merge(old.statuses[effect], status);
159        }
160        old
161    }
162}
163
164impl std::ops::Index<Effect> for Effects {
165    type Output = EffectStatus;
166
167    fn index(&self, index: Effect) -> &Self::Output {
168        &self.statuses[index]
169    }
170}
171
172impl std::ops::IndexMut<Effect> for Effects {
173    fn index_mut(&mut self, index: Effect) -> &mut Self::Output {
174        &mut self.statuses[index]
175    }
176}
177
178/// Describes what to do with an effect.
179#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
180pub enum EffectStatus {
181    /// Force the effect on, regardless of the parent.
182    On,
183
184    /// Force the effect off, regardless of the parent.
185    Off,
186
187    /// Keep the same effect status as the parent.
188    InheritParent,
189
190    /// Use the opposite state from the parent (XOR).
191    OppositeParent,
192}
193
194impl EffectStatus {
195    /// Returns the opposite status.
196    ///
197    /// * Swaps `On` and `Off`.
198    /// * Swaps `InheritParent` and `OppositeParent`.
199    pub const fn swap(self) -> Self {
200        match self {
201            EffectStatus::On => EffectStatus::Off,
202            EffectStatus::Off => EffectStatus::On,
203            EffectStatus::InheritParent => EffectStatus::OppositeParent,
204            EffectStatus::OppositeParent => EffectStatus::InheritParent,
205        }
206    }
207
208    /// Merges the old status with the new one.
209    pub const fn merge(old: Self, new: Self) -> Self {
210        match new {
211            EffectStatus::On => EffectStatus::On,
212            EffectStatus::Off => EffectStatus::Off,
213            EffectStatus::InheritParent => old,
214            EffectStatus::OppositeParent => old.swap(),
215        }
216    }
217}
218impl FromStr for EffectStatus {
219    type Err = ();
220
221    fn from_str(s: &str) -> Result<Self, Self::Err> {
222        Ok(match s {
223            "On" | "on" | "true" => Self::On,
224            "Off" | "off" | "false" => Self::Off,
225            "InheritParent" | "inherit_parent" => Self::InheritParent,
226            "OppositeParent" | "opposite_parent" => Self::OppositeParent,
227            _ => return Err(()),
228        })
229    }
230}
231
232impl FromStr for Effect {
233    type Err = super::NoSuchColor;
234
235    fn from_str(s: &str) -> Result<Self, Self::Err> {
236        Ok(match s {
237            "Simple" | "simple" => Effect::Simple,
238            "Reverse" | "reverse" => Effect::Reverse,
239            "Dim" | "dim" => Effect::Dim,
240            "Bold" | "bold" => Effect::Bold,
241            "Italic" | "italic" => Effect::Italic,
242            "Strikethrough" | "strikethrough" => Effect::Strikethrough,
243            "Underline" | "underline" => Effect::Underline,
244            "Blink" | "blink" => Effect::Blink,
245            _ => return Err(super::NoSuchColor),
246        })
247    }
248}