diff --git a/gamercade_core/src/graphics/color.rs b/gamercade_core/src/graphics/color.rs index 9e754cd..08cf9ef 100644 --- a/gamercade_core/src/graphics/color.rs +++ b/gamercade_core/src/graphics/color.rs @@ -76,6 +76,41 @@ impl Color { r | g | b | a } + pub fn to_hex_string(&self) -> String { + let r = self.r as u32; + let g = self.g as u32; + let b = self.b as u32; + format!("#{:02X}{:02X}{:02X}", r, g, b) + } + + pub fn from_hex_string(hex: &str) -> Result { + match (hex.starts_with('#'), hex.len()) { + (true, 7) => { + let hex_value = + u32::from_str_radix(&hex[1..], 16).map_err(|_| "Invalid hex string")?; + let r = ((hex_value >> 16) & 0xff) as u8; + let g = ((hex_value >> 8) & 0xff) as u8; + let b = (hex_value & 0xff) as u8; + return Ok(Self { r, g, b, a: 255 }); + } + (false, 6) => { + let hex_value = u32::from_str_radix(&hex, 16).map_err(|_| "Invalid hex string")?; + let r = ((hex_value >> 16) & 0xff) as u8; + let g = ((hex_value >> 8) & 0xff) as u8; + let b = (hex_value & 0xff) as u8; + return Ok(Self { r, g, b, a: 255 }); + } + _ => { + return Err("Invalid hex string"); + } + } + } + + pub fn update_from_hex_string(&mut self, hex: &str) -> Result<(), &'static str> { + *self = Self::from_hex_string(hex)?; + Ok(()) + } + pub fn into_pixel_data(&self) -> [u8; BYTES_PER_PIXEL] { [self.r, self.g, self.b, self.a] } @@ -91,3 +126,32 @@ impl From<[u8; 4]> for Color { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_color_from_hex_string() { + let color = Color::from_hex_string("#FF0000").unwrap(); + assert_eq!(color, Color::new(255, 0, 0, 255)); + + let color = Color::from_hex_string("#000000").unwrap(); + assert_eq!(color, Color::new(0, 0, 0, 255)); + + let color = Color::from_hex_string("FF0000").unwrap(); + assert_eq!(color, Color::new(255, 0, 0, 255)); + + let color = Color::from_hex_string("ffffff").unwrap(); + assert_eq!(color, Color::new(255, 255, 255, 255)); + + assert!(Color::from_hex_string("#FF000").is_err()); + assert!(Color::from_hex_string("#FF00000").is_err()); + assert!(Color::from_hex_string("#FF00000FF").is_err()); + assert!(Color::from_hex_string("FF00000FF").is_err()); + assert!(Color::from_hex_string("asd").is_err()); + assert!(Color::from_hex_string("pppppppp").is_err()); + assert!(Color::from_hex_string("#pppppp").is_err()); + assert!(Color::from_hex_string("#FFFFFFFF").is_err()); + } +} diff --git a/gamercade_editor/src/ui/graphics/palette_editor_tab/color_editor.rs b/gamercade_editor/src/ui/graphics/palette_editor_tab/color_editor.rs index a60e9b9..00c6603 100644 --- a/gamercade_editor/src/ui/graphics/palette_editor_tab/color_editor.rs +++ b/gamercade_editor/src/ui/graphics/palette_editor_tab/color_editor.rs @@ -1,10 +1,11 @@ -use eframe::egui::{Checkbox, Color32, Image, Slider, TextureHandle, Ui, Vec2}; +use eframe::egui::{ Checkbox, Color32, Image, Slider, TextEdit, TextureHandle, Ui, Vec2 }; use gamercade_core::Color; #[derive(Clone, Debug, Default)] pub struct ColorEditor { prev_color: Color, pub preview: Color, + pub hex_preview: String, } impl ColorEditor { @@ -13,30 +14,42 @@ impl ColorEditor { ui: &mut Ui, current_color: &mut Color, texture_handle: &TextureHandle, - index: usize, + index: usize ) { if self.prev_color != *current_color { self.preview = *current_color; self.prev_color = *current_color; + self.hex_preview = (*current_color).to_hex_string(); } + let mut current_hex = (*current_color).to_hex_string(); + ui.group(|ui| { ui.vertical(|ui| { ui.label("Color Editor"); ui.label(format!("Color index: {index}")); - draw_picker(ui, texture_handle, "Current", false, current_color); - draw_picker(ui, texture_handle, "Preview", true, &mut self.preview); + draw_picker(ui, texture_handle, "Current", false, current_color, &mut current_hex); + draw_picker( + ui, + texture_handle, + "Preview", + true, + &mut self.preview, + &mut self.hex_preview + ); ui.horizontal(|ui| { if ui.button("Revert").clicked() { self.preview = *current_color; + self.hex_preview = (*current_color).to_hex_string(); } if ui.button("Update").clicked() { *current_color = self.preview; self.prev_color = self.preview; + self.hex_preview = current_hex; } }) }); @@ -50,6 +63,7 @@ fn draw_picker( text: &'static str, editable: bool, color: &mut Color, + hex_color: &mut String ) { ui.group(|ui| { ui.label(text); @@ -60,17 +74,30 @@ fn draw_picker( ui.vertical(|ui| { ui.horizontal(|ui| { ui.label("R"); - ui.add_enabled(editable, Slider::new(&mut color.r, 0..=255)); + if ui.add_enabled(editable, Slider::new(&mut color.r, 0..=255)).changed() { + *hex_color = color.to_hex_string(); + } }); ui.horizontal(|ui| { ui.label("G"); - ui.add_enabled(editable, Slider::new(&mut color.g, 0..=255)); + if ui.add_enabled(editable, Slider::new(&mut color.g, 0..=255)).changed() { + *hex_color = color.to_hex_string(); + } }); ui.horizontal(|ui| { ui.label("B"); - ui.add_enabled(editable, Slider::new(&mut color.b, 0..=255)); + if ui.add_enabled(editable, Slider::new(&mut color.b, 0..=255)).changed() { + *hex_color = color.to_hex_string(); + } + }); + + ui.horizontal(|ui| { + ui.label("Hex"); + if ui.add_enabled(editable, TextEdit::singleline(hex_color).desired_width(64.0)).changed() { + let _result = color.update_from_hex_string(hex_color); + } }); ui.horizontal(|ui| {