Skip to main content

paiagram_core/
colors.rs

1use bevy::color::{Srgba, palettes::tailwind::*};
2use bevy::prelude::*;
3use egui::Color32;
4use egui::color_picker::{Alpha, color_picker_color32, show_color_at};
5use egui_i18n::tr;
6use serde::{Deserialize, Serialize};
7use strum::IntoEnumIterator;
8use strum_macros::{EnumCount, EnumIter};
9
10#[derive(Reflect, Debug, Clone, Copy, Serialize, Deserialize)]
11#[reflect(opaque, Serialize, Deserialize)]
12pub enum DisplayedColor {
13    Predefined(PredefinedColor),
14    Custom(Color32),
15}
16
17impl DisplayedColor {
18    pub fn from_seed(data: impl AsRef<[u8]>) -> Self {
19        let bytes = data.as_ref();
20        let mut sum = 0u8;
21        for byte in bytes.iter().copied() {
22            sum = sum.wrapping_add(byte);
23        }
24        Self::Predefined(PredefinedColor::from_index(sum as usize))
25    }
26}
27
28impl Default for DisplayedColor {
29    fn default() -> Self {
30        Self::Predefined(PredefinedColor::Neutral)
31    }
32}
33
34// this is copied from egui
35fn color_button(ui: &mut egui::Ui, color: Color32, open: bool) -> egui::Response {
36    let size = ui.spacing().interact_size;
37    let (rect, response) = ui.allocate_exact_size(size, egui::Sense::click());
38    response.widget_info(|| egui::WidgetInfo::new(egui::WidgetType::ColorButton));
39
40    if ui.is_rect_visible(rect) {
41        let visuals = if open {
42            &ui.visuals().widgets.open
43        } else {
44            ui.style().interact(&response)
45        };
46        let rect = rect.expand(visuals.expansion);
47
48        let stroke_width = 1.0;
49        show_color_at(ui.painter(), color, rect.shrink(stroke_width));
50
51        let corner_radius = visuals.corner_radius.at_most(2); // Can't do more rounding because the background grid doesn't do any rounding
52        ui.painter().rect_stroke(
53            rect,
54            corner_radius,
55            (stroke_width, visuals.bg_fill), // Using fill for stroke is intentional, because default style has no border
56            egui::StrokeKind::Inside,
57        );
58    }
59
60    response
61}
62
63impl egui::Widget for &mut DisplayedColor {
64    fn ui(self, ui: &mut egui::Ui) -> egui::Response {
65        let is_dark = ui.visuals().dark_mode;
66        let button_res = color_button(ui, self.get(is_dark), false);
67
68        let current_predefined = match *self {
69            DisplayedColor::Predefined(p) => Some(p),
70            DisplayedColor::Custom(_) => None,
71        };
72
73        egui::Popup::menu(&button_res)
74            .close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside)
75            .show(|ui| {
76                ui.horizontal(|ui| {
77                    ui.vertical(|ui| {
78                        ui.label("Predefined");
79                        ui.set_max_width(200.0);
80                        ui.horizontal_wrapped(|ui| {
81                            ui.style_mut().spacing.item_spacing = egui::Vec2::splat(4.0);
82                            for predefined in PredefinedColor::iter() {
83                                let color = predefined.get(is_dark);
84                                let is_selected = current_predefined == Some(predefined);
85                                let button = egui::Button::new("")
86                                    .fill(color)
87                                    .min_size(egui::vec2(24.0, 24.0))
88                                    .stroke(if is_selected {
89                                        ui.visuals().selection.stroke
90                                    } else {
91                                        ui.visuals().widgets.inactive.bg_stroke
92                                    });
93
94                                if ui.add(button).clicked() {
95                                    *self = DisplayedColor::Predefined(predefined);
96                                }
97                            }
98                        });
99                    });
100                    ui.separator();
101                    ui.vertical(|ui| {
102                        ui.label("Custom");
103                        let mut custom_color = match *self {
104                            DisplayedColor::Custom(c) => c,
105                            DisplayedColor::Predefined(p) => p.get(is_dark),
106                        };
107                        if color_picker_color32(ui, &mut custom_color, Alpha::Opaque) {
108                            *self = DisplayedColor::Custom(custom_color);
109                        }
110                    });
111                })
112            });
113        button_res
114    }
115}
116
117impl DisplayedColor {
118    /// get the color as [`Color32`]
119    pub fn get(self, is_dark: bool) -> Color32 {
120        match self {
121            Self::Predefined(p) => p.get(is_dark),
122            Self::Custom(c) => c,
123        }
124    }
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCount, Serialize, Deserialize)]
128pub enum PredefinedColor {
129    Red,
130    Orange,
131    Amber,
132    Yellow,
133    Lime,
134    Green,
135    Emerald,
136    Teal,
137    Cyan,
138    Sky,
139    Blue,
140    Indigo,
141    Violet,
142    Purple,
143    Fuchsia,
144    Pink,
145    Rose,
146    Slate,
147    Gray,
148    Zinc,
149    Neutral,
150    Stone,
151}
152
153impl PredefinedColor {
154    pub const ALL: [Self; 22] = [
155        Self::Red,
156        Self::Orange,
157        Self::Amber,
158        Self::Yellow,
159        Self::Lime,
160        Self::Green,
161        Self::Emerald,
162        Self::Teal,
163        Self::Cyan,
164        Self::Sky,
165        Self::Blue,
166        Self::Indigo,
167        Self::Violet,
168        Self::Purple,
169        Self::Fuchsia,
170        Self::Pink,
171        Self::Rose,
172        Self::Slate,
173        Self::Gray,
174        Self::Zinc,
175        Self::Neutral,
176        Self::Stone,
177    ];
178    pub fn from_index(i: usize) -> Self {
179        Self::ALL[i % 22]
180    }
181    #[rustfmt::skip]
182    pub fn name(self) -> impl AsRef<str> {
183        match self {
184            Self::Red       => tr!("colour-red"),
185            Self::Orange    => tr!("colour-orange"),
186            Self::Amber     => tr!("colour-amber"),
187            Self::Yellow    => tr!("colour-yellow"),
188            Self::Lime      => tr!("colour-lime"),
189            Self::Green     => tr!("colour-green"),
190            Self::Emerald   => tr!("colour-emerald"),
191            Self::Teal      => tr!("colour-teal"),
192            Self::Cyan      => tr!("colour-cyan"),
193            Self::Sky       => tr!("colour-sky"),
194            Self::Blue      => tr!("colour-blue"),
195            Self::Indigo    => tr!("colour-indigo"),
196            Self::Violet    => tr!("colour-violet"),
197            Self::Purple    => tr!("colour-purple"),
198            Self::Fuchsia   => tr!("colour-fuchsia"),
199            Self::Pink      => tr!("colour-pink"),
200            Self::Rose      => tr!("colour-rose"),
201            Self::Slate     => tr!("colour-slate"),
202            Self::Gray      => tr!("colour-gray"),
203            Self::Zinc      => tr!("colour-zinc"),
204            Self::Neutral   => tr!("colour-neutral"),
205            Self::Stone     => tr!("colour-stone"),
206        }
207    }
208
209    // use 700 shade if light, otherwise use 400
210    // neutral is special
211    pub const fn get(self, is_dark: bool) -> Color32 {
212        #[rustfmt::skip]
213        let c = match (self, is_dark) {
214            (Self::Red, true)       => RED_400,
215            (Self::Red, false)      => RED_700,
216            (Self::Orange, true)    => ORANGE_400,
217            (Self::Orange, false)   => ORANGE_700,
218            (Self::Amber, true)     => AMBER_400,
219            (Self::Amber, false)    => AMBER_700,
220            (Self::Yellow, true)    => YELLOW_400,
221            (Self::Yellow, false)   => YELLOW_700,
222            (Self::Lime, true)      => LIME_400,
223            (Self::Lime, false)     => LIME_700,
224            (Self::Green, true)     => GREEN_400,
225            (Self::Green, false)    => GREEN_700,
226            (Self::Emerald, true)   => EMERALD_400,
227            (Self::Emerald, false)  => EMERALD_700,
228            (Self::Teal, true)      => TEAL_400,
229            (Self::Teal, false)     => TEAL_700,
230            (Self::Cyan, true)      => CYAN_400,
231            (Self::Cyan, false)     => CYAN_700,
232            (Self::Sky, true)       => SKY_400,
233            (Self::Sky, false)      => SKY_700,
234            (Self::Blue, true)      => BLUE_400,
235            (Self::Blue, false)     => BLUE_700,
236            (Self::Indigo, true)    => INDIGO_400,
237            (Self::Indigo, false)   => INDIGO_700,
238            (Self::Violet, true)    => VIOLET_400,
239            (Self::Violet, false)   => VIOLET_700,
240            (Self::Purple, true)    => PURPLE_400,
241            (Self::Purple, false)   => PURPLE_700,
242            (Self::Fuchsia, true)   => FUCHSIA_400,
243            (Self::Fuchsia, false)  => FUCHSIA_700,
244            (Self::Pink, true)      => PINK_400,
245            (Self::Pink, false)     => PINK_700,
246            (Self::Rose, true)      => ROSE_400,
247            (Self::Rose, false)     => ROSE_700,
248            (Self::Slate, true)     => SLATE_400,
249            (Self::Slate, false)    => SLATE_700,
250            (Self::Gray, true)      => GRAY_400,
251            (Self::Gray, false)     => GRAY_700,
252            (Self::Zinc, true)      => ZINC_400,
253            (Self::Zinc, false)     => ZINC_700,
254            (Self::Neutral, true)   => NEUTRAL_400,
255            (Self::Neutral, false)  => NEUTRAL_700,
256            (Self::Stone, true)     => STONE_400,
257            (Self::Stone, false)    => STONE_700,
258        };
259        translate_srgba_to_color32(c)
260    }
261}
262
263pub const fn translate_srgba_to_color32(c: Srgba) -> Color32 {
264    Color32::from_rgba_unmultiplied_const(
265        (c.red * 256.0) as u8,
266        (c.green * 256.0) as u8,
267        (c.blue * 256.0) as u8,
268        (c.alpha * 256.0) as u8,
269    )
270}