Skip to main content

egui_charts/ui_kit/list_item/
visuals.rs

1//! ListVisuals - Color computation methods for list items
2//!
3//! Instead of pre-computing colors, this struct provides methods that compute
4//! colors based on the current state.
5
6use egui::{Color32, Visuals};
7
8/// Visual state information for list items with color computation methods
9#[derive(Clone, Debug)]
10pub struct ListVisuals {
11    /// Whether the item is hovered
12    pub hovered: bool,
13    /// Whether the item is selected
14    pub selected: bool,
15    /// Whether the item is in active state (e.g., expanded)
16    pub active: bool,
17    /// Whether the item is interactive (clickable)
18    pub interactive: bool,
19    /// Whether the text should be rendered with strong emphasis
20    pub strong: bool,
21    /// For collapsible items, the current openness (0.0 = closed, 1.0 = open)
22    pub openness: Option<f32>,
23}
24
25impl Default for ListVisuals {
26    fn default() -> Self {
27        Self {
28            hovered: false,
29            selected: false,
30            active: false,
31            interactive: true,
32            strong: false,
33            openness: None,
34        }
35    }
36}
37
38impl ListVisuals {
39    /// Create a new ListVisuals with default state
40    pub fn new() -> Self {
41        Self::default()
42    }
43
44    /// Set the hovered state
45    #[must_use]
46    pub fn with_hovered(mut self, hovered: bool) -> Self {
47        self.hovered = hovered;
48        self
49    }
50
51    /// Set the selected state
52    #[must_use]
53    pub fn with_selected(mut self, selected: bool) -> Self {
54        self.selected = selected;
55        self
56    }
57
58    /// Set the active state
59    #[must_use]
60    pub fn with_active(mut self, active: bool) -> Self {
61        self.active = active;
62        self
63    }
64
65    /// Set the interactive state
66    #[must_use]
67    pub fn with_interactive(mut self, interactive: bool) -> Self {
68        self.interactive = interactive;
69        self
70    }
71
72    /// Set the strong emphasis state
73    #[must_use]
74    pub fn with_strong(mut self, strong: bool) -> Self {
75        self.strong = strong;
76        self
77    }
78
79    /// Set the openness for collapsible items
80    #[must_use]
81    pub fn with_openness(mut self, openness: f32) -> Self {
82        self.openness = Some(openness);
83        self
84    }
85
86    /// Compute background color based on state
87    ///
88    /// Returns None if no background should be drawn (transparent).
89    pub fn bg_color(&self, visuals: &Visuals) -> Option<Color32> {
90        if self.selected {
91            Some(visuals.selection.bg_fill)
92        } else if self.active {
93            Some(visuals.widgets.active.bg_fill)
94        } else if self.hovered && self.interactive {
95            Some(visuals.widgets.hovered.bg_fill)
96        } else {
97            None
98        }
99    }
100
101    /// Compute text color based on state
102    pub fn text_color(&self, visuals: &Visuals) -> Color32 {
103        if self.selected {
104            visuals.selection.stroke.color
105        } else if !self.interactive {
106            visuals.widgets.noninteractive.fg_stroke.color
107        } else if self.hovered || self.active || self.strong {
108            visuals.widgets.hovered.fg_stroke.color
109        } else {
110            visuals.widgets.inactive.fg_stroke.color
111        }
112    }
113
114    /// Compute icon tint color based on state
115    pub fn icon_tint(&self, visuals: &Visuals) -> Color32 {
116        if self.selected {
117            visuals.selection.stroke.color
118        } else if (self.hovered && self.interactive) || self.active {
119            visuals.widgets.hovered.fg_stroke.color
120        } else {
121            visuals.widgets.noninteractive.fg_stroke.color
122        }
123    }
124
125    /// Compute icon tint for interactive icons within list items
126    ///
127    /// Use this for icons that have their own hover state (e.g., action buttons)
128    pub fn interactive_icon_tint(&self, icon_hovered: bool, visuals: &Visuals) -> Color32 {
129        if icon_hovered && !self.selected {
130            visuals.widgets.hovered.fg_stroke.color
131        } else {
132            self.icon_tint(visuals)
133        }
134    }
135
136    /// Check if this item is collapsible
137    pub fn is_collapsible(&self) -> bool {
138        self.openness.is_some()
139    }
140
141    /// Check if this item is collapsed (for collapsible items)
142    pub fn is_collapsed(&self) -> bool {
143        self.openness.is_none_or(|o| o <= 0.0)
144    }
145
146    /// Get the current openness value (0.0 if not collapsible)
147    pub fn openness_value(&self) -> f32 {
148        self.openness.unwrap_or(0.0)
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn test_collapsible() {
158        let non_collapsible = ListVisuals::new();
159        assert!(!non_collapsible.is_collapsible());
160        assert!(non_collapsible.is_collapsed());
161
162        let collapsed = ListVisuals::new().with_openness(0.0);
163        assert!(collapsed.is_collapsible());
164        assert!(collapsed.is_collapsed());
165
166        let expanded = ListVisuals::new().with_openness(1.0);
167        assert!(expanded.is_collapsible());
168        assert!(!expanded.is_collapsed());
169    }
170}