Skip to content

Commit

Permalink
Use 4-state effect statuses in styles
Browse files Browse the repository at this point in the history
This allows styled strings to support these use-cases:

* This word should be bold, regardless of the parent (absolute values).
* This word should be bold, unless the parent was bold (XOR).
* This word boldness should be just like the parent, but it should also
  be underlined.
  • Loading branch information
gyscos committed Jun 5, 2024
1 parent a865034 commit ea2e0be
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 52 deletions.
2 changes: 1 addition & 1 deletion cursive-core/src/direction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ impl Orientation {
}
}

/// Same as [`get_mut`].
/// Same as [`Self::get_mut()`].
#[deprecated]
pub fn get_ref<T>(self, v: &mut XY<T>) -> &mut T {
self.get_mut(v)
Expand Down
5 changes: 2 additions & 3 deletions cursive-core/src/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,9 +346,8 @@ impl Tree {
#[cfg(test)]
mod tests {
use super::*;
use crate::theme::{ColorStyle, ColorType, Style};
use crate::theme::{ColorStyle, ColorType, Effects, Style};
use crate::utils::span::Span;
use enumset::EnumSet;

#[test]
fn test_styled_label_delimiter() {
Expand All @@ -359,7 +358,7 @@ mod tests {
let expected_spans: Vec<Span<Style>> = vec![Span {
content: DELIMITER.source(),
attr: &Style {
effects: EnumSet::EMPTY,
effects: Effects::EMPTY,
color: ColorStyle {
front: ColorType::InheritParent,
back: ColorType::InheritParent,
Expand Down
185 changes: 183 additions & 2 deletions cursive-core/src/theme/effect.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use enumset::EnumSetType;
use enum_map::{Enum, EnumMap};
use enumset::{EnumSet, EnumSetType};
use std::str::FromStr;

/// Text effect
#[allow(clippy::derived_hash_with_manual_eq)] // We do derive it through EnumSetType
#[derive(EnumSetType, Debug, Hash)]
#[derive(EnumSetType, Enum, Debug, Hash)]
pub enum Effect {
/// No effect
Simple,
Expand All @@ -30,6 +31,186 @@ pub enum Effect {
Blink,
}

impl Effect {
/// Returns the order of the effect in the effect set/map.
///
/// This is very brittle and should be kept in sync with the enum definition. Might benefit
/// from a proc macro.
///
/// This is all because enum_map's Enum derive is trait-based and does not support const fn.
pub(crate) const fn ordinal(self) -> usize {
match self {
Effect::Simple => 0,
Effect::Reverse => 1,
Effect::Dim => 2,
Effect::Bold => 3,
Effect::Italic => 4,
Effect::Strikethrough => 5,
Effect::Underline => 6,
Effect::Blink => 7,
}
}
}

/// A set of effects status.
///
/// Describes what to do for each effect: enable, disable, preserve, xor.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub struct Effects {
/// The status of each effect.
pub statuses: EnumMap<Effect, EffectStatus>,
}

impl Default for Effects {
fn default() -> Self {
Self::empty()
}
}

impl From<ConcreteEffects> for Effects {
fn from(other: ConcreteEffects) -> Self {
let mut result = Self::default();
for effect in other {
result.statuses[effect] = EffectStatus::OppositeParent;
}
result
}
}

impl Effects {
/// An empty set of effects.
pub const EMPTY: Self = Effects::empty();

/// Return an empty set of effects.
///
/// They will all be set to `InheritParent`.
pub const fn empty() -> Self {
let statuses = [EffectStatus::InheritParent; Effect::LENGTH];
Self {
statuses: EnumMap::from_array(statuses),
}
}

/// Sets the given effect to be `OppositeParent`.
pub fn insert(&mut self, effect: Effect) {
self.statuses[effect] = EffectStatus::OppositeParent;
}

/// Helper function to implement `Self::only()`.
const fn status_for(i: usize, effect: Effect) -> EffectStatus {
if i == effect.ordinal() {
EffectStatus::OppositeParent
} else {
EffectStatus::InheritParent
}
}

/// Return a set of effects with only one effect.
///
/// It will be set to `OppositeParent`. Every other effect will be `InheritParent`.
pub const fn only(effect: Effect) -> Self {
// TODO: make this less brittle?
let statuses = [
Self::status_for(0, effect),
Self::status_for(1, effect),
Self::status_for(2, effect),
Self::status_for(3, effect),
Self::status_for(4, effect),
Self::status_for(5, effect),
Self::status_for(6, effect),
Self::status_for(7, effect),
];

Self {
statuses: EnumMap::from_array(statuses),
}
}

/// Resolve an effects directive into concrete effects.
pub fn resolve(&self, old: ConcreteEffects) -> ConcreteEffects {
let mut result = ConcreteEffects::default();
for (effect, status) in self.statuses {
if matches!(
(status, old.contains(effect)),
(EffectStatus::On, _)
| (EffectStatus::InheritParent, true)
| (EffectStatus::OppositeParent, false)
) {
result.insert(effect);
}
}
result
}

/// Merge the two sets of effects.
pub fn merge(mut old: Self, new: Self) -> Self {
for (effect, status) in new.statuses {
old.statuses[effect] = EffectStatus::merge(old.statuses[effect], status);
}
old
}
}

impl std::ops::Index<Effect> for Effects {
type Output = EffectStatus;

fn index(&self, index: Effect) -> &Self::Output {
&self.statuses[index]
}
}

impl std::ops::IndexMut<Effect> for Effects {
fn index_mut(&mut self, index: Effect) -> &mut Self::Output {
&mut self.statuses[index]
}
}

/// A concrete set of effects to enable.
///
/// Every missing effect should be disabled.
pub type ConcreteEffects = EnumSet<Effect>;

/// Describes what to do with an effect.
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum EffectStatus {
/// Force the effect on, regardless of the parent.
On,

/// Force the effect off, regardless of the parent.
Off,

/// Keep the same effect status as the parent.
InheritParent,

/// Use the opposite state from the parent.
OppositeParent,
}

impl EffectStatus {
/// Returns the opposite status.
///
/// * Swaps `On` and `Off`.
/// * Swaps `InheritParent` and `OppositeParent`.
pub const fn swap(self) -> Self {
match self {
EffectStatus::On => EffectStatus::Off,
EffectStatus::Off => EffectStatus::On,
EffectStatus::InheritParent => EffectStatus::OppositeParent,
EffectStatus::OppositeParent => EffectStatus::InheritParent,
}
}

/// Merges the old status with the new one.
pub const fn merge(old: Self, new: Self) -> Self {
match new {
EffectStatus::On => EffectStatus::On,
EffectStatus::Off => EffectStatus::Off,
EffectStatus::InheritParent => old,
EffectStatus::OppositeParent => old.swap(),
}
}
}

impl FromStr for Effect {
type Err = super::NoSuchColor;

Expand Down
2 changes: 1 addition & 1 deletion cursive-core/src/theme/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ pub use self::border_style::BorderStyle;
pub use self::color::{BaseColor, Color};
pub use self::color_pair::ColorPair;
pub use self::color_style::{ColorStyle, ColorType};
pub use self::effect::Effect;
pub use self::effect::{ConcreteEffects, Effect, EffectStatus, Effects};
pub use self::palette::{Palette, PaletteColor, PaletteNode, PaletteStyle};
pub use self::style::{ConcreteStyle, Style, StyleType};
#[cfg(feature = "toml")]
Expand Down
9 changes: 5 additions & 4 deletions cursive-core/src/theme/palette.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{Color, NoSuchColor, Style};
use super::{Color, Effects, NoSuchColor, Style};
use enum_map::{enum_map, Enum, EnumMap};
#[cfg(feature = "toml")]
use log::warn;
Expand Down Expand Up @@ -101,15 +101,15 @@ fn default_styles() -> EnumMap<PaletteStyle, Style> {
TitleSecondary => ColorStyle::title_secondary().into(),
Highlight => Style {
color: ColorStyle::highlight().invert(),
effects: enumset::enum_set!(Effect::Reverse),
effects: Effects::only(Effect::Reverse),
},
HighlightInactive => Style {
color: ColorStyle::highlight_inactive().invert(),
effects: enumset::enum_set!(Effect::Reverse),
effects: Effects::only(Effect::Reverse),
},
EditableText => Style {
color: ColorStyle::secondary(),
effects: enumset::enum_set!(Effect::Reverse),
effects: Effects::only(Effect::Reverse),
},
EditableTextCursor => ColorStyle::secondary().into(),
EditableTextInactive => ColorStyle::secondary().into(),
Expand Down Expand Up @@ -258,6 +258,7 @@ impl Palette {
pub(crate) fn load_toml_styles(&mut self, table: &toml::value::Table) {
// TODO: use serde for that?
for (key, value) in table {
// Find out which palette style this defines.
let key = match key.parse() {
Ok(key) => key,
_ => {
Expand Down
Loading

0 comments on commit ea2e0be

Please sign in to comment.