diff --git a/minecraft-chat/src/component.rs b/minecraft-chat/src/component.rs index c0e4f8ac..c0f7677f 100644 --- a/minecraft-chat/src/component.rs +++ b/minecraft-chat/src/component.rs @@ -23,12 +23,14 @@ impl Component { // if it's primitive, make it a text component if !json.is_array() && !json.is_object() { - component = Component::TextComponent(TextComponent::new(json.to_string())); + component = Component::TextComponent(TextComponent::new( + json.as_str().unwrap_or("").to_string(), + )); } // if it's an object, do things with { text } and stuff else if json.is_object() { if json.get("text").is_some() { - let text = json.get("text").unwrap().to_string(); + let text = json.get("text").unwrap().as_str().unwrap_or("").to_string(); component = Component::TextComponent(TextComponent::new(text)); } else if json.get("translate").is_some() { let translate = json.get("translate").unwrap().to_string(); @@ -133,6 +135,11 @@ impl Component { } } + // var5_17.setStyle((Style)jsonDeserializationContext.deserialize(jsonElement, Style.class)); + let style = Style::deserialize(json); + println!("set style to {:?}", style); + component.get_base().style = style; + return Ok(component); } // ok so it's not an object, if it's an array deserialize every item @@ -172,34 +179,42 @@ impl Component { pub fn to_ansi(&self, parent_style: Option<&mut Style>) -> String { // the siblings of this component let base; - let mut text; + let component_text: String; + let mut styled_component = String::new(); match self { Self::TextComponent(c) => { base = &c.base; - text = c.text.clone(); + component_text = c.text.clone(); } Self::TranslatableComponent(c) => { base = &c.base; - text = c.key.clone(); + component_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 + // if it's the base style, that means we add a style reset at the end + let is_base_style = parent_style.is_none(); + 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); + // the old style is current_style and the new style is the base.style + let ansi_text = current_style.compare_ansi(&base.style); + current_style.apply(&base.style); - text.push_str(&ansi_text); + styled_component.push_str(&ansi_text); + styled_component.push_str(&component_text); for sibling in &base.siblings { - text.push_str(&sibling.to_ansi(Some(current_style))); + styled_component.push_str(&sibling.to_ansi(Some(current_style))); } - text.clone() + if is_base_style { + styled_component.push_str("\x1b[m"); + } + + styled_component.clone() } } diff --git a/minecraft-chat/src/style.rs b/minecraft-chat/src/style.rs index fed5243f..e87bd999 100644 --- a/minecraft-chat/src/style.rs +++ b/minecraft-chat/src/style.rs @@ -2,8 +2,8 @@ use std::collections::HashMap; use serde_json::Value; -#[derive(Clone, PartialEq)] -struct TextColor { +#[derive(Clone, PartialEq, Debug)] +pub struct TextColor { value: u32, name: Option, } @@ -29,21 +29,21 @@ impl TextColor { // private static final Map NAMED_COLORS = (Map)LEGACY_FORMAT_TO_COLOR.values().stream().collect(ImmutableMap.toImmutableMap(textColor -> textColor.name, Function.identity())); let mut LEGACY_FORMAT_TO_COLOR = HashMap::new(); let mut NAMED_COLORS = HashMap::new(); - for i in ChatFormatting::FORMATTERS { - if i.is_format && i != ChatFormatting::RESET { + for formatter in &ChatFormatting::FORMATTERS { + if !formatter.is_format && *formatter != ChatFormatting::RESET { LEGACY_FORMAT_TO_COLOR.insert( - i, + formatter, TextColor { - value: i.color.unwrap(), - name: Some(i.name.to_string()), + value: formatter.color.unwrap(), + name: Some(formatter.name.to_string()), }, ); } } - for i in LEGACY_FORMAT_TO_COLOR.values() { - NAMED_COLORS.insert(i.name.unwrap(), i.clone()); + for color in LEGACY_FORMAT_TO_COLOR.values() { + NAMED_COLORS.insert(color.name.as_ref().unwrap(), color.clone()); } - let color = NAMED_COLORS.get(&value); + let color = NAMED_COLORS.get(&value.to_ascii_uppercase()); if color.is_some() { return Ok(color.unwrap().clone()); } @@ -55,9 +55,22 @@ impl TextColor { } } +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn text_color_named_colors() { + assert_eq!( + TextColor::parse("red".to_string()).unwrap().value, + 16733525u32 + ); + } +} + const PREFIX_CODE: char = '\u{00a7}'; -#[derive(Clone, PartialEq, Eq, Hash)] +#[derive(Clone, PartialEq, Eq, Hash, Debug)] struct ChatFormatting<'a> { name: &'a str, code: char, @@ -140,11 +153,11 @@ impl TextColor { Self { value, name } } - fn format(&self) -> String { + pub fn format(&self) -> String { format!("#{:06X}", self.value) } - fn to_string(&self) -> String { + pub fn to_string(&self) -> String { if let Some(name) = &self.name { name.clone() } else { @@ -153,7 +166,7 @@ impl TextColor { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Style { // @Nullable // final TextColor color; @@ -177,12 +190,12 @@ pub struct Style { // final ResourceLocation font; // these are options instead of just bools because None is different than false in this case - color: Option, - bold: Option, - italic: Option, - underlined: Option, - strikethrough: Option, - obfuscated: Option, + pub color: Option, + pub bold: Option, + pub italic: Option, + pub underlined: Option, + pub strikethrough: Option, + pub obfuscated: Option, } impl Style { @@ -197,7 +210,7 @@ impl Style { } } - fn deserialize(json: Value) { + pub fn deserialize(json: &Value) -> Style { // if (jsonElement.isJsonObject()) { // JsonObject jsonObject = jsonElement.getAsJsonObject(); // if (jsonObject == null) { @@ -216,18 +229,28 @@ impl Style { // return new Style(textColor, bl, bl2, bl3, bl4, bl5, clickEvent, hoverEvent, string, resourceLocation); // } // return null; - if json.is_object() { + return if json.is_object() { let json_object = json.as_object().unwrap(); let bold = json_object.get("bold").and_then(|v| v.as_bool()); let italic = json_object.get("italic").and_then(|v| v.as_bool()); let underlined = json_object.get("underlined").and_then(|v| v.as_bool()); let strikethrough = json_object.get("strikethrough").and_then(|v| v.as_bool()); let obfuscated = json_object.get("obfuscated").and_then(|v| v.as_bool()); - let color = json_object + let color: Option = json_object .get("color") - .and_then(|v| v.as_string()) - .and_then(|v| TextColor::parse(v)); - } + .and_then(|v| v.as_str()) + .and_then(|v| TextColor::parse(v.to_string()).ok()); + Style { + color, + bold, + italic, + underlined, + strikethrough, + obfuscated, + } + } else { + Style::new() + }; } /// Check if a style has no attributes set @@ -270,7 +293,7 @@ impl Style { let mut ansi_codes = String::new(); let before = if should_reset { - ansi_codes.push_str("\x1b[0m"); + ansi_codes.push_str("\x1b[m"); Style::new() } else { self.clone() @@ -321,7 +344,7 @@ impl Style { )); } - return "".to_string(); + ansi_codes } /// Apply another style to this one diff --git a/minecraft-chat/tests/integration_test.rs b/minecraft-chat/tests/integration_test.rs index 9fd9c093..0574861b 100644 --- a/minecraft-chat/tests/integration_test.rs +++ b/minecraft-chat/tests/integration_test.rs @@ -6,10 +6,11 @@ fn test() { let j: Value = serde_json::from_str( r#"{ "text": "hello", - "color": "red" + "color": "red", + "bold": true }"#, ) .unwrap(); let component = Component::new(&j).unwrap(); - println!("println: {}", component.to_ansi(None)); + assert_eq!(component.to_ansi(None), "\x1b[38;2;255;85;85mhello\x1b[m"); }