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