diff --git a/core/src/text/hover_event.rs b/core/src/text/hover_event.rs index 4b8be90..c171ea9 100644 --- a/core/src/text/hover_event.rs +++ b/core/src/text/hover_event.rs @@ -24,6 +24,10 @@ erased_serde::serialize_trait_object!(UpdDebug); rimecraft_primitives::update_trait_object!(UpdDebug); /// Event performed when cursor is hovering on a `Text`. +/// +/// # MCJE Reference +/// +/// This type represents `net.minecraft.text.HoverEvent` (yarn). pub struct HoverEvent { contents: Box, action: &'static ErasedAction, @@ -114,7 +118,7 @@ impl<'de> Deserialize<'de> for HoverEvent { contents: serde_json::Value, } - //TODO: if `contents` is invalid, deserialize text from field `value`. + //TODO: if `contents` not found, deserialize text from field `value`. let Struct { action, contents } = Struct::deserialize(deserializer)?; // Deserializing contents. @@ -139,6 +143,7 @@ pub struct Action { factory: fn() -> T, } +/// Registers an action. pub fn register_action(action: Action) where T: ErasedSerDeUpdate + Debug + Send + Sync, diff --git a/core/src/text/mod.rs b/core/src/text/mod.rs index 716e015..2ec0295 100644 --- a/core/src/text/mod.rs +++ b/core/src/text/mod.rs @@ -7,17 +7,19 @@ use std::{ use rimecraft_primitives::{id, Id}; -use crate::Rgb; +use crate::RGB; -use self::{click_event::ClickEvent, hover_event::HoverEvent}; - -use super::formatting::Formatting; +use super::fmt::Formatting; pub mod click_event; pub mod hover_event; pub mod visit; +pub use click_event::ClickEvent; +pub use hover_event::HoverEvent; + +/// An error that can occur when processing a [`Text`]. #[derive(thiserror::Error, Debug)] pub enum Error { #[error("not a valid color")] @@ -27,37 +29,63 @@ pub enum Error { #[error("unable to parse integer: {0}")] ParseInt(std::num::ParseIntError), #[error("formatting error: {0}")] - Formatting(super::formatting::Error), + Formatting(super::fmt::Error), #[error("invalid name: {0}")] InvalidName(String), } -/// TODO: Implement net.minecraft.text.Text +//TODO: Implement net.minecraft.text.Text pub trait Text { fn style(&self) -> &Style; fn siblings(&self) -> Vec>; - /// TODO: Implement net.minecraft.text.OrderedText - fn as_ordered_text(&self) -> (); + //TODO: Implement net.minecraft.text.OrderedText + fn as_ordered_text(&self); } -/// The style of a [`Text`].\ -/// A style is immutable. -#[derive(PartialEq)] +/// The style of a [`Text`], representing cosmetic attributes. +/// It includes font, color, click event, hover event, etc. +/// +/// A style should be immutable. +/// +/// # MCJE Reference +/// +/// This type represents `net.minecraft.text.Style` (yarn). +#[derive(PartialEq, Debug, Default)] pub struct Style { + /// The color of this style. pub color: Option, + + /// Whether this style has bold formatting. pub bold: Option, + + /// Whether this style has italic formatting. pub italic: Option, + + /// Whether this style has underlined formatting. pub underlined: Option, + + /// Whether this style has strikethrough formatting. pub strikethrough: Option, + + /// Whether this style has obfuscated formatting. pub obfuscated: Option, + + /// The click event of this style. pub click: Option, + + /// The hover event of this style. pub hover: Option, + + /// The insertion text of this style. pub insertion: Option, + + /// The font ID of this style. pub font: Option, } impl Style { - const EMPTY: Self = Self { + /// An empty style. + pub const EMPTY: Self = Self { color: None, bold: None, italic: None, @@ -72,52 +100,82 @@ impl Style { const DEFAULT_FONT_ID: &str = "default"; + /// Returns the color of this style. #[inline] pub fn color(&self) -> Option<&Color> { self.color.as_ref() } + /// Returns whether this style has bold formatting. + /// + /// See [`Formatting::Bold`]. #[inline] - pub fn bold(&self) -> bool { + pub fn is_bold(&self) -> bool { self.bold.unwrap_or(false) } + /// Returns whether this style has italic formatting. + /// + /// See [`Formatting::Italic`]. #[inline] - pub fn italic(&self) -> bool { + pub fn is_italic(&self) -> bool { self.italic.unwrap_or(false) } + /// Returns whether this style has strikethrough formatting. + /// + /// See [`Formatting::Strikethrough`]. #[inline] - pub fn strikethrough(&self) -> bool { + pub fn is_strikethrough(&self) -> bool { self.strikethrough.unwrap_or(false) } + /// Returns whether this style has underlined formatting. + /// + /// See [`Formatting::Underline`]. #[inline] - pub fn underlined(&self) -> bool { + pub fn is_underlined(&self) -> bool { self.underlined.unwrap_or(false) } + /// Returns whether this style has obfuscated formatting. + /// + /// See [`Formatting::Obfuscated`]. #[inline] - pub fn obfuscated(&self) -> bool { + pub fn is_obfuscated(&self) -> bool { self.obfuscated.unwrap_or(false) } - pub fn empty(&self) -> bool { + /// Returns whether this style is empty. + /// + /// See [`Self::EMPTY`]. + #[inline] + pub fn is_empty(&self) -> bool { self == &Self::EMPTY } - pub fn click(&self) -> Option<&ClickEvent> { + /// Returns the click event of this style. + #[inline] + pub fn click_event(&self) -> Option<&ClickEvent> { self.click.as_ref() } - pub fn hover(&self) -> Option<&HoverEvent> { + /// Returns the hover event of this style. + #[inline] + pub fn hover_event(&self) -> Option<&HoverEvent> { self.hover.as_ref() } + /// Returns the insertion text of this style. + /// + /// An insertion text is a text that is inserted into the chat + /// when the player shift-clicks on the text. + #[inline] pub fn insertion(&self) -> Option<&String> { self.insertion.as_ref() } + /// Returns the font ID of this style. pub fn font(&self) -> Cow<'_, Id> { self.font .as_ref() @@ -135,23 +193,26 @@ impl Style { #[derive(Debug, Eq)] pub struct Color { /// A 24-bit color. - rgb: Rgb, + rgb: RGB, name: Option<&'static str>, } impl Color { const RGB_PREFIX: &str = "#"; + /// Returns the inner RGB value of this color. #[inline] - pub fn rgb(&self) -> Rgb { + pub fn rgb(&self) -> RGB { self.rgb } + /// Returns the hex code of this color. #[inline] fn to_hex_code(&self) -> String { format!("{}{:06X}", Self::RGB_PREFIX, self.rgb) } + /// Returns the name of this color. pub fn name(&self) -> Cow<'static, str> { self.name .map(Cow::Borrowed) diff --git a/core/src/util/formatting.rs b/core/src/util/fmt.rs similarity index 90% rename from core/src/util/formatting.rs rename to core/src/util/fmt.rs index cec562c..fd24f00 100644 --- a/core/src/util/formatting.rs +++ b/core/src/util/fmt.rs @@ -1,15 +1,18 @@ use std::{collections::HashMap, fmt::Display, ops::Deref, str::FromStr}; +use fastnbt::de; use once_cell::sync::Lazy; -use super::Rgb; +use super::RGB; +/// An index of a color in [`Formatting`]. #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub struct ColorIndex { value: i8, } impl ColorIndex { + /// Returns the color index for the given value. #[inline] pub fn new(value: Option) -> Self { Self { @@ -17,6 +20,7 @@ impl ColorIndex { } } + /// Returns the value of the color index if valid. #[inline] pub fn value(self) -> Option { if self.value == -1 { @@ -40,7 +44,7 @@ macro_rules! formattings { /// /// There are two types of formattings, color and modifier. Color formattings /// are associated with a specific color, while modifier formattings modify the - /// style, such as by bolding the text. [`Self::RESET`] is a special formatting + /// style, such as by bolding the text. [`Self::Reset`] is a special formatting /// and is not classified as either of these two. #[derive(Clone, Copy, Eq, PartialEq, Hash, Debug, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "snake_case")] @@ -95,10 +99,10 @@ macro_rules! formattings { /// Returns the color of the formatted text, or /// `None` if the formatting has no associated color. #[inline] - pub fn color_value(self) -> Option { + pub fn color_value(self) -> Option { match self { $( - Formatting::$i => $cv.map(Rgb::new), + Formatting::$i => $cv.map(RGB::new), )* } } @@ -148,18 +152,23 @@ formattings! { Reset => "RESET", 'r', false, -1, None, } +/// An error returned when parsing a formatting. #[derive(thiserror::Error, Debug)] pub enum Error { + /// No matching color index found. #[error("no matching color index {0:?} found")] NoMatchingColorIndex(ColorIndex), + /// Invalid code. #[error("invalid code: {0}")] InvalidCode(char), + /// Invalid name. #[error("invalid name: {0}")] InvalidName(String), } impl Formatting { - const CODE_PREFIX: char = '§'; + /// The prefix of formatting codes. + pub const CODE_PREFIX: char = '§'; /// Returns `true` if the formatting is associated with /// a color, `false` otherwise. @@ -173,7 +182,7 @@ impl Formatting { pub fn name(self) -> &'static str { static NAMING_MAP: Lazy> = Lazy::new(|| { Formatting::values() - .into_iter() + .iter() .map(|e| e.name_raw().to_ascii_lowercase()) .collect() }); @@ -189,6 +198,7 @@ impl Formatting { } } +/// Sanitize a formatting name. #[inline] fn name_sanitize(name: &str) -> String { lazy_regex::regex_replace_all!("[^a-z]", &name.to_lowercase(), "").into_owned() @@ -207,7 +217,7 @@ impl FromStr for Formatting { fn from_str(s: &str) -> Result { static NAME_MAP: Lazy> = Lazy::new(|| { Formatting::values() - .into_iter() + .iter() .map(|e| (name_sanitize(e.name_raw()), *e)) .collect() }); @@ -238,7 +248,7 @@ impl TryFrom for Formatting { } else { static CI_MAP: Lazy> = Lazy::new(|| { Formatting::values() - .into_iter() + .iter() .map(|e| (e.color_index(), *e)) .collect() }); @@ -257,7 +267,7 @@ impl TryFrom for Formatting { fn try_from(value: char) -> Result { static CHAR_MAP: Lazy> = Lazy::new(|| { Formatting::values() - .into_iter() + .iter() .map(|e| (e.code(), *e)) .collect() }); @@ -269,6 +279,7 @@ impl TryFrom for Formatting { } /// The iterator returned by [`Formatting::names`]. +#[derive(Debug)] pub struct Names { iter: std::slice::Iter<'static, Formatting>, } @@ -283,16 +294,19 @@ impl Iterator for Names { } /// Item of [`Names`]. +#[derive(Debug)] pub struct Name { value: Formatting, } impl Name { + /// Returns whether the targeting formatting is a color. #[inline] pub fn is_color(&self) -> bool { self.value.is_color() } + /// Returns whether the targeting formatting is a modifier. #[inline] pub fn is_modifier(&self) -> bool { self.value.is_modifier() diff --git a/core/src/util/mod.rs b/core/src/util/mod.rs index a7ccd67..aaebbda 100644 --- a/core/src/util/mod.rs +++ b/core/src/util/mod.rs @@ -1,7 +1,7 @@ -pub mod formatting; +pub mod fmt; pub mod math; -use formatting::Formatting; +use fmt::Formatting; use std::{fmt::UpperHex, str::FromStr}; @@ -35,11 +35,11 @@ pub enum Hand { #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] #[repr(transparent)] -pub struct Rgb { +pub struct RGB { value: u32, } -impl Rgb { +impl RGB { #[inline] pub fn new(value: u32) -> Self { Self { value } @@ -51,18 +51,18 @@ impl Rgb { } } -impl UpperHex for Rgb { +impl UpperHex for RGB { #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:06X}", self.value) } } -impl FromStr for Rgb { +impl FromStr for RGB { type Err = std::num::ParseIntError; #[inline] fn from_str(s: &str) -> Result { - s.parse().map(Rgb::new) + s.parse().map(RGB::new) } } diff --git a/primitives/src/identifier.rs b/primitives/src/identifier.rs index 7f47282..2459cf4 100644 --- a/primitives/src/identifier.rs +++ b/primitives/src/identifier.rs @@ -116,12 +116,12 @@ impl Identifier { /// /// # Examples /// ``` - /// use rimecraft_primitives::id; + /// # use rimecraft_primitives::id; /// assert_eq!(id!("rimecraft", "gold_ingot").trim_fmt(), "gold_ingot"); /// ``` #[inline] pub fn trim_fmt(&self) -> String { - if &*self.namespace == DEFAULT_NAMESPACE { + if *self.namespace == DEFAULT_NAMESPACE { self.path.to_owned() } else { self.to_string() @@ -135,7 +135,6 @@ impl Identifier { /// /// ``` /// # use rimecraft_primitives::id; -/// /// // Either parse or create an identifier directly. /// assert_eq!(id!("namespace:path").to_string(), "namespace:path"); /// assert_eq!(id!("namespace", "path").to_string(), "namespace:path");