diff --git a/minecraft-chat/src/component.rs b/minecraft-chat/src/component.rs index aa2c598e..c0e4f8ac 100644 --- a/minecraft-chat/src/component.rs +++ b/minecraft-chat/src/component.rs @@ -1,15 +1,14 @@ +use std::borrow::BorrowMut; + use serde_json; use crate::{ base_component::BaseComponent, + style::Style, text_component::TextComponent, translatable_component::{StringOrComponent, TranslatableComponent}, }; -// pub struct Component { -// base: BaseComponent, -// } - #[derive(Clone)] pub enum Component { TextComponent(TextComponent), @@ -149,12 +148,16 @@ impl Component { Ok(component) } + pub fn get_base(&mut self) -> &mut BaseComponent { + match self { + Self::TextComponent(c) => &mut c.base, + Self::TranslatableComponent(c) => &mut c.base, + } + } + /// Add a component as a sibling of this one fn append(&mut self, sibling: Component) { - match self { - Self::TextComponent(c) => c.base.siblings.push(sibling), - Self::TranslatableComponent(c) => c.base.siblings.push(sibling), - } + self.get_base().siblings.push(sibling); } /// Get the "separator" component from the json @@ -165,5 +168,38 @@ impl Component { Ok(None) } - fn to_ansi(&self) {} + /// Convert this component into an ansi string, using parent_style as the running style. + pub fn to_ansi(&self, parent_style: Option<&mut Style>) -> String { + // the siblings of this component + let base; + let mut text; + match self { + Self::TextComponent(c) => { + base = &c.base; + text = c.text.clone(); + } + Self::TranslatableComponent(c) => { + base = &c.base; + text = c.key.clone(); + } + }; + + // we'll fall back to this if there's no parent style + let default_style = &mut Style::new(); + + // apply the style of this component to the current style + let current_style: &mut Style = parent_style.unwrap_or(default_style); + let new_style = &base.style; + current_style.apply(new_style); + + let ansi_text = base.style.compare_ansi(&new_style); + + text.push_str(&ansi_text); + + for sibling in &base.siblings { + text.push_str(&sibling.to_ansi(Some(current_style))); + } + + text.clone() + } } diff --git a/minecraft-chat/src/lib.rs b/minecraft-chat/src/lib.rs index 73485a3e..2fbad937 100644 --- a/minecraft-chat/src/lib.rs +++ b/minecraft-chat/src/lib.rs @@ -2,15 +2,6 @@ pub mod base_component; pub mod component; -pub mod mutable_component; pub mod style; pub mod text_component; pub mod translatable_component; - -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } -} diff --git a/minecraft-chat/src/mutable_component.rs b/minecraft-chat/src/mutable_component.rs deleted file mode 100644 index d294e3b3..00000000 --- a/minecraft-chat/src/mutable_component.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::{base_component::BaseComponent, component::Component}; - -pub trait MutableComponent { - /// Add a component as a sibling of this one - fn append(&self, component: Component); -} diff --git a/minecraft-chat/src/style.rs b/minecraft-chat/src/style.rs index 3e72ac82..d8cd224b 100644 --- a/minecraft-chat/src/style.rs +++ b/minecraft-chat/src/style.rs @@ -1,4 +1,4 @@ -#[derive(Clone)] +#[derive(Clone, PartialEq)] struct TextColor { value: u32, name: Option, @@ -15,6 +15,57 @@ struct ChatFormatting<'a> { } impl<'a> ChatFormatting<'a> { + const BLACK: ChatFormatting<'a> = ChatFormatting::new("BLACK", '0', false, 0, Some(0)); + const DARK_BLUE: ChatFormatting<'a> = + ChatFormatting::new("DARK_BLUE", '1', false, 1, Some(170)); + const DARK_GREEN: ChatFormatting<'a> = + ChatFormatting::new("DARK_GREEN", '2', false, 2, Some(43520)); + const DARK_AQUA: ChatFormatting<'a> = + ChatFormatting::new("DARK_AQUA", '3', false, 3, Some(43690)); + const DARK_RED: ChatFormatting<'a> = + ChatFormatting::new("DARK_RED", '4', false, 4, Some(1114112)); + const DARK_PURPLE: ChatFormatting<'a> = + ChatFormatting::new("DARK_PURPLE", '5', false, 5, Some(11141290)); + const GOLD: ChatFormatting<'a> = ChatFormatting::new("GOLD", '6', false, 6, Some(16755200)); + const GRAY: ChatFormatting<'a> = ChatFormatting::new("GRAY", '7', false, 7, Some(11184810)); + const DARK_GRAY: ChatFormatting<'a> = + ChatFormatting::new("DARK_GRAY", '8', false, 8, Some(5592405)); + const BLUE: ChatFormatting<'a> = ChatFormatting::new("BLUE", '9', false, 9, Some(5592575)); + const GREEN: ChatFormatting<'a> = ChatFormatting::new("GREEN", 'a', false, 10, Some(5635925)); + const AQUA: ChatFormatting<'a> = ChatFormatting::new("AQUA", 'b', false, 11, Some(5636095)); + const RED: ChatFormatting<'a> = ChatFormatting::new("RED", 'c', false, 12, Some(16733525)); + const LIGHT_PURPLE: ChatFormatting<'a> = + ChatFormatting::new("LIGHT_PURPLE", 'd', false, 13, Some(16733695)); + const YELLOW: ChatFormatting<'a> = + ChatFormatting::new("YELLOW", 'e', false, 14, Some(16777045)); + const WHITE: ChatFormatting<'a> = ChatFormatting::new("WHITE", 'f', false, 15, Some(16777215)); + const OBFUSCATED: ChatFormatting<'a> = ChatFormatting::new("OBFUSCATED", 'k', true, -1, None); + const STRIKETHROUGH: ChatFormatting<'a> = + ChatFormatting::new("STRIKETHROUGH", 'm', true, -1, None); + const BOLD: ChatFormatting<'a> = ChatFormatting::new("BOLD", 'l', true, -1, None); + const UNDERLINE: ChatFormatting<'a> = ChatFormatting::new("UNDERLINE", 'n', true, -1, None); + const ITALIC: ChatFormatting<'a> = ChatFormatting::new("ITALIC", 'o', true, -1, None); + const RESET: ChatFormatting<'a> = ChatFormatting::new("RESET", 'r', true, -1, None); + + pub const FORMATTERS: [ChatFormatting<'a>; 16] = [ + ChatFormatting::BLACK, + ChatFormatting::DARK_BLUE, + ChatFormatting::DARK_GREEN, + ChatFormatting::DARK_AQUA, + ChatFormatting::DARK_RED, + ChatFormatting::DARK_PURPLE, + ChatFormatting::GOLD, + ChatFormatting::GRAY, + ChatFormatting::DARK_GRAY, + ChatFormatting::BLUE, + ChatFormatting::GREEN, + ChatFormatting::AQUA, + ChatFormatting::RED, + ChatFormatting::LIGHT_PURPLE, + ChatFormatting::YELLOW, + ChatFormatting::WHITE, + ]; + const fn new( name: &str, code: char, @@ -32,33 +83,6 @@ impl<'a> ChatFormatting<'a> { } } -// pub const BLACK: ChatFormatting = ChatFormatting::new("BLACK", '0', false, 0, Some(0)); -// pub const DARK_BLUE: ChatFormatting = ChatFormatting::new("DARK_BLUE", '1', false, 1, Some(170)); -// pub const DARK_GREEN: ChatFormatting = -// ChatFormatting::new("DARK_GREEN", '2', false, 2, Some(43520)); -// pub const DARK_AQUA: ChatFormatting = ChatFormatting::new("DARK_AQUA", '3', false, 3, Some(43690)); -// pub const DARK_RED: ChatFormatting = ChatFormatting::new("DARK_RED", '4', false, 4, Some(1114112)); -// pub const DARK_PURPLE: ChatFormatting = -// ChatFormatting::new("DARK_PURPLE", '5', false, 5, Some(11141290)); -// pub const GOLD: ChatFormatting = ChatFormatting::new("GOLD", '6', false, 6, Some(16755200)); -// pub const GRAY: ChatFormatting = ChatFormatting::new("GRAY", '7', false, 7, Some(11184810)); -// pub const DARK_GRAY: ChatFormatting = -// ChatFormatting::new("DARK_GRAY", '8', false, 8, Some(5592405)); -// pub const BLUE: ChatFormatting = ChatFormatting::new("BLUE", '9', false, 9, Some(5592575)); -// pub const GREEN: ChatFormatting = ChatFormatting::new("GREEN", 'a', false, 10, Some(5635925)); -// pub const AQUA: ChatFormatting = ChatFormatting::new("AQUA", 'b', false, 11, Some(5636095)); -// pub const RED: ChatFormatting = ChatFormatting::new("RED", 'c', false, 12, Some(16733525)); -// pub const LIGHT_PURPLE: ChatFormatting = -// ChatFormatting::new("LIGHT_PURPLE", 'd', false, 13, Some(16733695)); -// pub const YELLOW: ChatFormatting = ChatFormatting::new("YELLOW", 'e', false, 14, Some(16777045)); -// pub const WHITE: ChatFormatting = ChatFormatting::new("WHITE", 'f', false, 15, Some(16777215)); -// pub const OBFUSCATED: ChatFormatting = ChatFormatting::new("OBFUSCATED", 'k', true, -1, None); -// pub const STRIKETHROUGH: ChatFormatting = ChatFormatting::new("STRIKETHROUGH", 'm', true, -1, None); -// pub const BOLD: ChatFormatting = ChatFormatting::new("BOLD", 'l', true, -1, None); -// pub const UNDERLINE: ChatFormatting = ChatFormatting::new("UNDERLINE", 'n', true, -1, None); -// pub const ITALIC: ChatFormatting = ChatFormatting::new("ITALIC", 'o', true, -1, None); -// pub const RESET: ChatFormatting = ChatFormatting::new("RESET", 'r', true, -1, None); - impl TextColor { fn new(value: u32, name: Option) -> Self { Self { value, name } @@ -113,11 +137,11 @@ impl Style { pub fn new() -> Style { Style { color: None, - bold: Some(false), - italic: Some(false), - underlined: Some(false), - strikethrough: Some(false), - obfuscated: Some(false), + bold: None, + italic: None, + underlined: None, + strikethrough: None, + obfuscated: None, } } @@ -130,4 +154,110 @@ impl Style { && self.strikethrough.is_none() && self.obfuscated.is_none() } + + /// find the necessary ansi code to get from this style to another + pub fn compare_ansi(&self, after: &Style) -> String { + let should_reset = { + // if it used to be bold and now it's not, reset + if self.bold.unwrap_or(false) && !after.bold.unwrap_or(false) { + true + } + // if it used to be italic and now it's not, reset + else if self.italic.unwrap_or(false) && !after.italic.unwrap_or(false) { + true + // if it used to be underlined and now it's not, reset + } else if self.underlined.unwrap_or(false) && !after.underlined.unwrap_or(false) { + true + // if it used to be strikethrough and now it's not, reset + } else if self.strikethrough.unwrap_or(false) && !after.strikethrough.unwrap_or(false) { + true + // if it used to be obfuscated and now it's not, reset + } else if self.obfuscated.unwrap_or(false) && !after.obfuscated.unwrap_or(false) { + true + // if it used to have a color and now it doesn't, reset + } else if self.color.is_some() && after.color.is_none() { + true + } else { + false + } + }; + + let mut ansi_codes = String::new(); + + let before = if should_reset { + ansi_codes.push_str("\x1b[0m"); + Style::new() + } else { + self.clone() + }; + + // if bold used to be false/default and now it's true, set bold + if before.bold.unwrap_or(false) && !after.bold.unwrap_or(false) { + ansi_codes.push_str("\x1b[1m"); + } + // if italic used to be false/default and now it's true, set italic + if before.italic.unwrap_or(false) && !after.italic.unwrap_or(false) { + ansi_codes.push_str("\x1b[3m"); + } + // if underlined used to be false/default and now it's true, set underlined + if before.underlined.unwrap_or(false) && !after.underlined.unwrap_or(false) { + ansi_codes.push_str("\x1b[4m"); + } + // if strikethrough used to be false/default and now it's true, set strikethrough + if before.strikethrough.unwrap_or(false) && !after.strikethrough.unwrap_or(false) { + ansi_codes.push_str("\x1b[9m"); + } + // if obfuscated used to be false/default and now it's true, set obfuscated + if before.obfuscated.unwrap_or(false) && !after.obfuscated.unwrap_or(false) { + ansi_codes.push_str("\x1b[8m"); + } + + // if the new color is different and not none, set color + let color_changed = { + if before.color.is_none() && after.color.is_some() { + true + } else if before.color.is_some() && after.color.is_some() { + before.color.unwrap().value != after.color.as_ref().unwrap().value + } else { + false + } + }; + + if color_changed { + let after_color = after.color.as_ref().unwrap(); + ansi_codes.push_str(&format!( + "\x1b[38;2;{};{};{}m", + // r + (after_color.value >> 16) & 0xFF, + // g + (after_color.value >> 8) & 0xFF, + // b + after_color.value & 0xFF + )); + } + + return "".to_string(); + } + + /// Apply another style to this one + pub fn apply(&mut self, style: &Style) { + if let Some(color) = &style.color { + self.color = Some(color.clone()); + } + if let Some(bold) = &style.bold { + self.bold = Some(*bold); + } + if let Some(italic) = &style.italic { + self.italic = Some(*italic); + } + if let Some(underlined) = &style.underlined { + self.underlined = Some(*underlined); + } + if let Some(strikethrough) = &style.strikethrough { + self.strikethrough = Some(*strikethrough); + } + if let Some(obfuscated) = &style.obfuscated { + self.obfuscated = Some(*obfuscated); + } + } } diff --git a/minecraft-chat/src/text_component.rs b/minecraft-chat/src/text_component.rs index a89264b2..e950ee00 100644 --- a/minecraft-chat/src/text_component.rs +++ b/minecraft-chat/src/text_component.rs @@ -1,4 +1,4 @@ -use crate::{base_component::BaseComponent, mutable_component::MutableComponent}; +use crate::{base_component::BaseComponent, component::Component}; #[derive(Clone)] pub struct TextComponent { diff --git a/minecraft-chat/src/translatable_component.rs b/minecraft-chat/src/translatable_component.rs index 1aecb7a7..327c5e07 100644 --- a/minecraft-chat/src/translatable_component.rs +++ b/minecraft-chat/src/translatable_component.rs @@ -6,7 +6,6 @@ pub enum StringOrComponent { Component(Component), } -// extends BaseComponent implements ContextAwareComponent #[derive(Clone)] pub struct TranslatableComponent { pub base: BaseComponent, diff --git a/minecraft-chat/tests/integration_test.rs b/minecraft-chat/tests/integration_test.rs index 46e18457..9fd9c093 100644 --- a/minecraft-chat/tests/integration_test.rs +++ b/minecraft-chat/tests/integration_test.rs @@ -5,9 +5,11 @@ use serde_json::{Result, Value}; fn test() { let j: Value = serde_json::from_str( r#"{ - "text":"hello" + "text": "hello", + "color": "red" }"#, ) .unwrap(); let component = Component::new(&j).unwrap(); + println!("println: {}", component.to_ansi(None)); }