Skip to main content

tui_lipan/widgets/effect_scope/
mod.rs

1mod layout;
2mod node;
3mod reconcile;
4
5use std::sync::Arc;
6
7pub(crate) use self::layout::measure_effect_scope;
8pub use self::node::EffectScopeNode;
9pub(crate) use self::reconcile::reconcile_effect_scope;
10
11use crate::app::ContrastPolicy;
12use crate::core::element::{Element, ElementKind};
13use crate::style::{CellEffect, Color, ColorTransform, LayoutConstraints, Length, VisualEffect};
14
15/// Apply render-time color effects to an entire child subtree.
16///
17/// `EffectScope` post-processes the rendered cells inside its child bounds, so
18/// explicit colors inside the subtree are still affected. This is useful for
19/// dimming inactive panes, tinting overlays, or applying contrast adjustments
20/// to a whole section at once.
21#[derive(Clone, Default)]
22pub struct EffectScope {
23    pub(crate) child: Option<Box<Element>>,
24    pub(crate) effects: Vec<VisualEffect>,
25}
26
27impl EffectScope {
28    /// Create an empty effect scope.
29    pub fn new() -> Self {
30        Self::default()
31    }
32
33    /// Set wrapped child content.
34    pub fn child(mut self, child: impl Into<Element>) -> Self {
35        self.child = Some(Box::new(child.into()));
36        self
37    }
38
39    /// Dim the rendered subtree by an explicit amount.
40    pub fn dim_by(self, amount: f32) -> Self {
41        self.effect(VisualEffect::ColorTransform {
42            fg: Some(ColorTransform::Dim(amount)),
43            bg: Some(ColorTransform::Dim(amount)),
44        })
45    }
46
47    /// Lighten the rendered subtree by an explicit amount.
48    pub fn lighten_by(self, amount: f32) -> Self {
49        self.effect(VisualEffect::ColorTransform {
50            fg: Some(ColorTransform::Lighten(amount)),
51            bg: Some(ColorTransform::Lighten(amount)),
52        })
53    }
54
55    /// Tint the rendered subtree toward a color.
56    pub fn tint_by(self, color: Color, alpha: f32) -> Self {
57        self.effect(VisualEffect::ColorTransform {
58            fg: Some(ColorTransform::Tint(color, alpha)),
59            bg: Some(ColorTransform::Tint(color, alpha)),
60        })
61    }
62
63    /// Apply a relative transform to the resolved foreground color of the subtree.
64    pub fn transform_fg(self, transform: ColorTransform) -> Self {
65        self.effect(VisualEffect::transform_fg(transform))
66    }
67
68    /// Apply a relative transform to the resolved background color of the subtree.
69    pub fn transform_bg(self, transform: ColorTransform) -> Self {
70        self.effect(VisualEffect::transform_bg(transform))
71    }
72
73    /// Override contrast adjustment for the rendered subtree.
74    pub fn contrast_policy(self, policy: ContrastPolicy) -> Self {
75        self.effect(VisualEffect::ContrastPolicy(policy))
76    }
77
78    /// Append a visual effect to this scope.
79    pub fn effect(mut self, effect: VisualEffect) -> Self {
80        self.effects.push(effect);
81        self
82    }
83
84    /// Append a user-defined per-cell visual effect to this scope.
85    pub fn custom_effect(self, effect: impl CellEffect) -> Self {
86        self.effect(VisualEffect::Custom(Arc::new(effect)))
87    }
88
89    /// Append visual effects from an iterator.
90    pub fn effects<I>(mut self, effects: I) -> Self
91    where
92        I: IntoIterator<Item = VisualEffect>,
93    {
94        self.effects.extend(effects);
95        self
96    }
97
98    /// Remove all visual effects from this scope.
99    pub fn clear_effects(mut self) -> Self {
100        self.effects.clear();
101        self
102    }
103}
104
105impl From<EffectScope> for Element {
106    fn from(value: EffectScope) -> Self {
107        let (min_w, min_h) = measure_effect_scope(&value, None, None);
108        Element::new(ElementKind::EffectScope(value)).with_layout(
109            LayoutConstraints::default()
110                .min_width(Length::Px(min_w))
111                .min_height(Length::Px(min_h)),
112        )
113    }
114}
115
116impl crate::layout::hash::LayoutHash for EffectScope {
117    fn layout_hash(
118        &self,
119        hasher: &mut impl std::hash::Hasher,
120        recurse: &dyn Fn(&Element) -> Option<u64>,
121    ) -> Option<()> {
122        use std::hash::Hash;
123        self.effects.hash(hasher);
124        if let Some(child) = self.child.as_ref() {
125            recurse(child.as_ref())?.hash(hasher);
126        } else {
127            0u8.hash(hasher);
128        }
129        Some(())
130    }
131}