Skip to main content

egui_table_kit/filter/
highlight.rs

1//! Color highlight options and layout configuration widgets.
2
3use std::sync::{Arc, LazyLock};
4
5use fluent_zero::t;
6use parking_lot::RwLock;
7
8use crate::error::TableError;
9
10/// Thread-safe global selection color index state.
11pub static SELECT_COLOR: LazyLock<Arc<RwLock<u8>>> =
12    LazyLock::new(|| Arc::new(RwLock::new(Default::default())));
13
14/// Resolves a color option by its index against organizational and user-defined palettes.
15pub fn select_color_meta(
16    index: u8,
17    org: &[[u8; 3]],
18    user: &[[u8; 3]],
19) -> Result<(usize, usize, [u8; 3]), TableError> {
20    let index = index as usize;
21    let org_color_len = org.len();
22
23    let color = if index < org_color_len {
24        Some(*org.get(index).ok_or(TableError::CorruptedState)?)
25    } else {
26        user.get(index - org_color_len).copied()
27    };
28    Ok((index, org_color_len, color.unwrap_or_else(Default::default)))
29}
30
31/// Resolves a raw RGB array for the color at the specified index.
32pub fn select_color(index: u8, org: &[[u8; 3]], user: &[[u8; 3]]) -> Result<[u8; 3], TableError> {
33    select_color_meta(index, org, user).map(|(_, _, color)| color)
34}
35
36/// Renders a color selection button within the highlight filter popups.
37pub fn color_select_button(
38    ui: &mut egui::Ui,
39    color: egui::Color32,
40    selected: bool,
41    skinny: bool,
42) -> egui::Response {
43    let size = ui.spacing().interact_size;
44    let (rect, response) = ui.allocate_exact_size(
45        egui::vec2(if skinny { size.x / 2.0 } else { size.x }, size.y),
46        egui::Sense::click(),
47    );
48    response.widget_info(|| egui::WidgetInfo::new(egui::WidgetType::ColorButton));
49
50    if ui.is_rect_visible(rect) {
51        let visuals = if selected {
52            let mut selected_visuals = ui.visuals().widgets.open;
53            selected_visuals.bg_stroke.color = egui::Color32::DARK_BLUE;
54            selected_visuals.bg_stroke.width = 2.0;
55            selected_visuals
56        } else {
57            *ui.style().interact(&response)
58        };
59
60        let rect = rect.expand(visuals.expansion);
61        egui::color_picker::show_color_at(ui.painter(), color, rect);
62
63        let rounding = visuals.corner_radius.at_most(2);
64        ui.painter()
65            .rect_stroke(rect, rounding, visuals.bg_stroke, egui::StrokeKind::Middle);
66    }
67
68    response
69}
70
71/// Renders an optional color select button allowing highlight toggles.
72pub fn opt_color_select_button(
73    ui: &mut egui::Ui,
74    label: &str,
75    color: &mut Option<u8>,
76    selected: bool,
77    skinny: bool,
78    highlight_options_r1: &[[u8; 3]],
79    highlight_options_r2: &[[u8; 3]],
80) -> Result<(), TableError> {
81    ui.label(label);
82
83    match color {
84        Some(inner) => {
85            let [r, g, b] = select_color(*inner, highlight_options_r1, highlight_options_r2)?;
86            if color_select_button(ui, egui::Color32::from_rgb(r, g, b), selected, skinny).clicked()
87            {
88                *color = None;
89            }
90        }
91        None => {
92            if ui.button("➕").clicked() {
93                *color = Some(*SELECT_COLOR.read());
94            }
95        }
96    }
97
98    Ok(())
99}
100
101/// Highlighting selection filter element.
102pub struct HighlightFilter<'a> {
103    label: &'a str,
104}
105
106impl<'a> HighlightFilter<'a> {
107    /// Constructs a new highlight filter selector.
108    #[must_use]
109    pub const fn new(label: &'a str) -> Self {
110        Self { label }
111    }
112
113    /// Renders highlight selection controls.
114    pub fn ui(
115        self,
116        ui: &mut egui::Ui,
117        highlight_select: &mut Option<Option<u8>>,
118        highlight_options_r1: &[[u8; 3]],
119        highlight_options_r2: &[[u8; 3]],
120    ) -> Result<(), TableError> {
121        ui.horizontal(|ui| {
122            if let Some(highlight_filter) = highlight_select {
123                opt_color_select_button(
124                    ui,
125                    self.label,
126                    highlight_filter,
127                    false,
128                    false,
129                    highlight_options_r1,
130                    highlight_options_r2,
131                )?;
132
133                if ui.button("❌").on_hover_text(t!("remove-filter")).clicked() {
134                    *highlight_select = None;
135                }
136            } else if ui.button(self.label).clicked() {
137                *highlight_select = Some(None);
138            }
139
140            Ok(())
141        })
142        .inner
143    }
144}