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
34fn 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); ui.painter().rect_stroke(
53 rect,
54 corner_radius,
55 (stroke_width, visuals.bg_fill), 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 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 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}