2022-07-14 23:18:20 -07:00
|
|
|
//! Formatted text.
|
|
|
|
|
2022-04-14 14:55:45 -07:00
|
|
|
use std::borrow::Cow;
|
2022-09-08 21:39:08 -07:00
|
|
|
use std::io::Write;
|
2022-12-29 02:26:19 -06:00
|
|
|
use std::{fmt, ops};
|
2022-04-14 14:55:45 -07:00
|
|
|
|
2022-12-31 22:59:22 -08:00
|
|
|
use anyhow::Context;
|
2022-04-14 14:55:45 -07:00
|
|
|
use serde::de::Visitor;
|
2022-10-11 01:10:49 -07:00
|
|
|
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
2022-04-14 14:55:45 -07:00
|
|
|
|
2022-12-23 06:30:57 -08:00
|
|
|
use crate::{Decode, Encode, Ident, Result};
|
2022-04-14 14:55:45 -07:00
|
|
|
|
|
|
|
/// Represents formatted text in Minecraft's JSON text format.
|
|
|
|
///
|
|
|
|
/// Text is used in various places such as chat, window titles,
|
|
|
|
/// disconnect messages, written books, signs, and more.
|
|
|
|
///
|
2022-07-11 05:08:02 -07:00
|
|
|
/// For more information, see the relevant [Minecraft Wiki article].
|
2022-04-14 14:55:45 -07:00
|
|
|
///
|
2022-07-11 05:08:02 -07:00
|
|
|
/// [Minecraft Wiki article]: https://minecraft.fandom.com/wiki/Raw_JSON_text_format
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
2022-04-14 14:55:45 -07:00
|
|
|
/// With [`TextFormat`] in scope, you can write the following:
|
|
|
|
/// ```
|
2022-11-13 06:10:42 -08:00
|
|
|
/// use valence_protocol::text::{Color, Text, TextFormat};
|
2022-04-14 14:55:45 -07:00
|
|
|
///
|
|
|
|
/// let txt = "The text is ".into_text()
|
|
|
|
/// + "Red".color(Color::RED)
|
|
|
|
/// + ", "
|
|
|
|
/// + "Green".color(Color::GREEN)
|
|
|
|
/// + ", and also "
|
|
|
|
/// + "Blue".color(Color::BLUE)
|
|
|
|
/// + "!\nAnd maybe even "
|
|
|
|
/// + "Italic".italic()
|
|
|
|
/// + ".";
|
|
|
|
///
|
|
|
|
/// assert_eq!(
|
2022-11-20 00:48:01 +00:00
|
|
|
/// txt.to_string(),
|
2022-04-14 14:55:45 -07:00
|
|
|
/// "The text is Red, Green, and also Blue!\nAnd maybe even Italic."
|
|
|
|
/// );
|
|
|
|
/// ```
|
2022-11-20 00:48:01 +00:00
|
|
|
#[derive(Clone, PartialEq, Default, Debug, Serialize, Deserialize)]
|
|
|
|
#[serde(transparent)]
|
|
|
|
pub struct Text(Box<TextInner>);
|
|
|
|
|
2022-04-14 14:55:45 -07:00
|
|
|
#[derive(Clone, PartialEq, Default, Debug, Serialize, Deserialize)]
|
2022-04-17 17:05:13 -07:00
|
|
|
#[serde(rename_all = "camelCase")]
|
2022-11-20 00:48:01 +00:00
|
|
|
struct TextInner {
|
2022-04-14 14:55:45 -07:00
|
|
|
#[serde(flatten)]
|
|
|
|
content: TextContent,
|
|
|
|
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
color: Option<Color>,
|
|
|
|
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
font: Option<Cow<'static, str>>,
|
|
|
|
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
bold: Option<bool>,
|
|
|
|
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
italic: Option<bool>,
|
|
|
|
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
underlined: Option<bool>,
|
|
|
|
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
strikethrough: Option<bool>,
|
|
|
|
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
obfuscated: Option<bool>,
|
|
|
|
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
insertion: Option<Cow<'static, str>>,
|
|
|
|
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
click_event: Option<ClickEvent>,
|
|
|
|
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
hover_event: Option<HoverEvent>,
|
|
|
|
|
|
|
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
|
|
extra: Vec<Text>,
|
|
|
|
}
|
|
|
|
|
2022-11-20 00:48:01 +00:00
|
|
|
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
|
|
|
#[serde(untagged)]
|
|
|
|
enum TextContent {
|
|
|
|
Text {
|
|
|
|
text: Cow<'static, str>,
|
|
|
|
},
|
|
|
|
/// A piece of text that will be translated on the client based on the
|
|
|
|
/// client language. If no corresponding translation can be found, the
|
|
|
|
/// identifier itself is used as the translated text.
|
|
|
|
Translate {
|
|
|
|
/// A translation identifier, corresponding to the identifiers found in
|
|
|
|
/// loaded language files.
|
|
|
|
translate: Cow<'static, str>,
|
|
|
|
/// Optional list of text components to be inserted into slots in the
|
|
|
|
/// translation text. Ignored if `translate` is not present.
|
|
|
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
|
|
with: Vec<Text>,
|
|
|
|
},
|
|
|
|
/// Displays a score holder's current score in an objective.
|
|
|
|
ScoreboardValue {
|
|
|
|
score: ScoreboardValueContent,
|
|
|
|
},
|
|
|
|
/// Displays the name of one or more entities found by a [`selector`].
|
|
|
|
///
|
|
|
|
/// [`selector`]: https://minecraft.fandom.com/wiki/Target_selectors
|
|
|
|
EntityNames {
|
|
|
|
/// A string containing a [`selector`].
|
|
|
|
///
|
|
|
|
/// [`selector`]: https://minecraft.fandom.com/wiki/Target_selectors
|
|
|
|
selector: Cow<'static, str>,
|
|
|
|
/// An optional custom separator used when the selector returns multiple
|
|
|
|
/// entities. Defaults to the ", " text with gray color.
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
separator: Option<Text>,
|
|
|
|
},
|
|
|
|
/// Displays the name of the button that is currently bound to a certain
|
|
|
|
/// configurable control on the client.
|
|
|
|
Keybind {
|
|
|
|
/// A [`keybind identifier`], to be displayed as the name of the button
|
|
|
|
/// that is currently bound to that action.
|
|
|
|
///
|
|
|
|
/// [`keybind identifier`]: https://minecraft.fandom.com/wiki/Controls#Configurable_controls
|
|
|
|
keybind: Cow<'static, str>,
|
|
|
|
},
|
|
|
|
/// Displays NBT values from block entities.
|
|
|
|
BlockNbt {
|
|
|
|
block: Cow<'static, str>,
|
|
|
|
nbt: Cow<'static, str>,
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
interpret: Option<bool>,
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
separator: Option<Text>,
|
|
|
|
},
|
|
|
|
/// Displays NBT values from entities.
|
|
|
|
EntityNbt {
|
|
|
|
entity: Cow<'static, str>,
|
|
|
|
nbt: Cow<'static, str>,
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
interpret: Option<bool>,
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
separator: Option<Text>,
|
|
|
|
},
|
|
|
|
/// Displays NBT values from command storage.
|
|
|
|
StorageNbt {
|
|
|
|
storage: Ident<String>,
|
|
|
|
nbt: Cow<'static, str>,
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
interpret: Option<bool>,
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
separator: Option<Text>,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
|
|
|
struct ScoreboardValueContent {
|
|
|
|
/// The name of the score holder whose score should be displayed. This
|
|
|
|
/// can be a [`selector`] or an explicit name.
|
|
|
|
///
|
|
|
|
/// [`selector`]: https://minecraft.fandom.com/wiki/Target_selectors
|
|
|
|
name: Cow<'static, str>,
|
|
|
|
/// The internal name of the objective to display the player's score in.
|
|
|
|
objective: Cow<'static, str>,
|
|
|
|
/// If present, this value is displayed regardless of what the score
|
|
|
|
/// would have been.
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
value: Option<Cow<'static, str>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Text color
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
|
|
|
pub struct Color {
|
|
|
|
/// Red channel
|
|
|
|
pub r: u8,
|
|
|
|
/// Green channel
|
|
|
|
pub g: u8,
|
|
|
|
/// Blue channel
|
|
|
|
pub b: u8,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
|
|
|
#[serde(tag = "action", content = "value", rename_all = "snake_case")]
|
|
|
|
enum ClickEvent {
|
|
|
|
OpenUrl(Cow<'static, str>),
|
|
|
|
/// Only usable by internal servers for security reasons.
|
|
|
|
OpenFile(Cow<'static, str>),
|
|
|
|
RunCommand(Cow<'static, str>),
|
|
|
|
SuggestCommand(Cow<'static, str>),
|
|
|
|
ChangePage(i32),
|
|
|
|
CopyToClipboard(Cow<'static, str>),
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
|
|
|
#[serde(tag = "action", content = "contents", rename_all = "snake_case")]
|
|
|
|
#[allow(clippy::enum_variant_names)]
|
|
|
|
enum HoverEvent {
|
|
|
|
ShowText(Text),
|
|
|
|
ShowItem {
|
|
|
|
id: Ident<String>,
|
|
|
|
count: Option<i32>,
|
|
|
|
// TODO: tag
|
|
|
|
},
|
|
|
|
ShowEntity {
|
|
|
|
name: Text,
|
|
|
|
#[serde(rename = "type")]
|
|
|
|
kind: Ident<String>,
|
|
|
|
// TODO: id (hyphenated entity UUID as a string)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2022-08-09 14:44:04 -07:00
|
|
|
#[allow(clippy::self_named_constructors)]
|
2022-07-06 00:16:07 -07:00
|
|
|
impl Text {
|
2022-08-09 14:44:04 -07:00
|
|
|
/// Constructs a new plain text object.
|
|
|
|
pub fn text(plain: impl Into<Cow<'static, str>>) -> Self {
|
2022-11-20 00:48:01 +00:00
|
|
|
Self(Box::new(TextInner {
|
2022-08-09 14:44:04 -07:00
|
|
|
content: TextContent::Text { text: plain.into() },
|
2022-11-20 00:48:01 +00:00
|
|
|
..Default::default()
|
|
|
|
}))
|
2022-08-09 14:44:04 -07:00
|
|
|
}
|
|
|
|
|
2022-11-20 00:48:01 +00:00
|
|
|
/// Create translated text based on the given translation key, with extra
|
|
|
|
/// text components to be inserted into the slots of the translation text.
|
|
|
|
pub fn translate(key: impl Into<Cow<'static, str>>, with: impl Into<Vec<Text>>) -> Self {
|
|
|
|
Self(Box::new(TextInner {
|
2022-08-09 14:44:04 -07:00
|
|
|
content: TextContent::Translate {
|
|
|
|
translate: key.into(),
|
2022-11-20 00:48:01 +00:00
|
|
|
with: with.into(),
|
2022-08-09 14:44:04 -07:00
|
|
|
},
|
2022-11-20 00:48:01 +00:00
|
|
|
..Default::default()
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a score from the scoreboard with an optional custom value.
|
|
|
|
pub fn score(
|
|
|
|
name: impl Into<Cow<'static, str>>,
|
|
|
|
objective: impl Into<Cow<'static, str>>,
|
|
|
|
value: Option<Cow<'static, str>>,
|
|
|
|
) -> Self {
|
|
|
|
Self(Box::new(TextInner {
|
|
|
|
content: TextContent::ScoreboardValue {
|
|
|
|
score: ScoreboardValueContent {
|
|
|
|
name: name.into(),
|
|
|
|
objective: objective.into(),
|
|
|
|
value,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
..Default::default()
|
|
|
|
}))
|
2022-08-09 14:44:04 -07:00
|
|
|
}
|
|
|
|
|
2022-11-20 00:48:01 +00:00
|
|
|
/// Creates a text component for selecting entity names with an optional
|
|
|
|
/// custom separator.
|
|
|
|
pub fn selector(selector: impl Into<Cow<'static, str>>, separator: Option<Text>) -> Self {
|
|
|
|
Self(Box::new(TextInner {
|
|
|
|
content: TextContent::EntityNames {
|
|
|
|
selector: selector.into(),
|
|
|
|
separator,
|
|
|
|
},
|
|
|
|
..Default::default()
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates a text component for a keybind. The keybind should be a valid
|
|
|
|
/// [`keybind identifier`].
|
|
|
|
///
|
|
|
|
/// [`keybind identifier`]: https://minecraft.fandom.com/wiki/Controls#Configurable_controls
|
|
|
|
pub fn keybind(keybind: impl Into<Cow<'static, str>>) -> Self {
|
|
|
|
Self(Box::new(TextInner {
|
|
|
|
content: TextContent::Keybind {
|
|
|
|
keybind: keybind.into(),
|
|
|
|
},
|
|
|
|
..Default::default()
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates a text component for a block NBT tag.
|
|
|
|
pub fn block_nbt(
|
|
|
|
block: impl Into<Cow<'static, str>>,
|
|
|
|
nbt: impl Into<Cow<'static, str>>,
|
|
|
|
interpret: Option<bool>,
|
|
|
|
separator: Option<Text>,
|
|
|
|
) -> Self {
|
|
|
|
Self(Box::new(TextInner {
|
|
|
|
content: TextContent::BlockNbt {
|
|
|
|
block: block.into(),
|
|
|
|
nbt: nbt.into(),
|
|
|
|
interpret,
|
|
|
|
separator,
|
|
|
|
},
|
|
|
|
..Default::default()
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates a text component for an entity NBT tag.
|
|
|
|
pub fn entity_nbt(
|
|
|
|
entity: impl Into<Cow<'static, str>>,
|
|
|
|
nbt: impl Into<Cow<'static, str>>,
|
|
|
|
interpret: Option<bool>,
|
|
|
|
separator: Option<Text>,
|
|
|
|
) -> Self {
|
|
|
|
Self(Box::new(TextInner {
|
|
|
|
content: TextContent::EntityNbt {
|
|
|
|
entity: entity.into(),
|
|
|
|
nbt: nbt.into(),
|
|
|
|
interpret,
|
|
|
|
separator,
|
|
|
|
},
|
|
|
|
..Default::default()
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates a text component for a command storage NBT tag.
|
|
|
|
pub fn storage_nbt(
|
|
|
|
storage: impl Into<Ident<String>>,
|
|
|
|
nbt: impl Into<Cow<'static, str>>,
|
|
|
|
interpret: Option<bool>,
|
|
|
|
separator: Option<Text>,
|
|
|
|
) -> Self {
|
|
|
|
Self(Box::new(TextInner {
|
|
|
|
content: TextContent::StorageNbt {
|
|
|
|
storage: storage.into(),
|
|
|
|
nbt: nbt.into(),
|
|
|
|
interpret,
|
|
|
|
separator,
|
|
|
|
},
|
|
|
|
..Default::default()
|
|
|
|
}))
|
2022-08-09 14:44:04 -07:00
|
|
|
}
|
|
|
|
|
2022-11-20 00:48:01 +00:00
|
|
|
/// Writes the string representation of this text object to the provided
|
|
|
|
/// writer.
|
|
|
|
pub fn write_string(&self, mut w: impl fmt::Write) -> fmt::Result {
|
|
|
|
fn write_string_inner(this: &Text, w: &mut impl fmt::Write) -> fmt::Result {
|
|
|
|
match &this.0.content {
|
2022-10-19 01:52:02 -07:00
|
|
|
TextContent::Text { text } => w.write_str(text.as_ref())?,
|
2022-11-20 00:48:01 +00:00
|
|
|
TextContent::Translate { translate, with } => {
|
|
|
|
w.write_str(translate.as_ref())?;
|
|
|
|
|
|
|
|
if !with.is_empty() {
|
|
|
|
w.write_char('[')?;
|
|
|
|
for (i, slot) in with.iter().enumerate() {
|
|
|
|
if i > 0 {
|
|
|
|
w.write_str(", ")?;
|
|
|
|
}
|
|
|
|
w.write_char(char::from_digit((i + 1) as u32, 10).unwrap_or('?'))?;
|
|
|
|
w.write_char('=')?;
|
|
|
|
write_string_inner(slot, w)?;
|
|
|
|
}
|
|
|
|
w.write_char(']')?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
TextContent::ScoreboardValue { score } => {
|
|
|
|
let ScoreboardValueContent {
|
|
|
|
name,
|
|
|
|
objective,
|
|
|
|
value,
|
|
|
|
} = score;
|
|
|
|
|
|
|
|
write!(w, "scoreboard_value[name={name}, objective={objective}")?;
|
|
|
|
|
|
|
|
if let Some(value) = value {
|
|
|
|
if !value.is_empty() {
|
|
|
|
w.write_str(", value=")?;
|
|
|
|
w.write_str(value)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
w.write_char(']')?;
|
|
|
|
}
|
|
|
|
TextContent::EntityNames {
|
|
|
|
selector,
|
|
|
|
separator,
|
|
|
|
} => {
|
|
|
|
write!(w, "entity_names[selector={selector}")?;
|
|
|
|
|
|
|
|
if let Some(separator) = separator {
|
|
|
|
if !separator.is_empty() {
|
|
|
|
w.write_str(", separator={separator}")?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
w.write_char(']')?;
|
|
|
|
}
|
|
|
|
TextContent::Keybind { keybind } => write!(w, "keybind[{keybind}]")?,
|
|
|
|
TextContent::BlockNbt {
|
|
|
|
block,
|
|
|
|
nbt,
|
|
|
|
interpret,
|
|
|
|
separator,
|
|
|
|
} => {
|
|
|
|
write!(w, "block_nbt[nbt={nbt}")?;
|
|
|
|
|
|
|
|
if let Some(interpret) = interpret {
|
|
|
|
write!(w, ", interpret={interpret}")?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(separator) = separator {
|
|
|
|
if !separator.is_empty() {
|
|
|
|
write!(w, "separator={separator}")?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
write!(w, "block={block}")?;
|
|
|
|
|
|
|
|
w.write_char(']')?;
|
|
|
|
}
|
|
|
|
TextContent::EntityNbt {
|
|
|
|
entity,
|
|
|
|
nbt,
|
|
|
|
interpret,
|
|
|
|
separator,
|
|
|
|
} => {
|
|
|
|
write!(w, "entity_nbt[nbt={nbt}")?;
|
|
|
|
|
|
|
|
if let Some(interpret) = interpret {
|
|
|
|
write!(w, ", interpret={interpret}")?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(separator) = separator {
|
|
|
|
if !separator.is_empty() {
|
|
|
|
write!(w, "separator={separator}")?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
write!(w, ", entity={entity}")?;
|
|
|
|
|
|
|
|
w.write_char(']')?;
|
|
|
|
}
|
|
|
|
TextContent::StorageNbt {
|
|
|
|
storage,
|
|
|
|
nbt,
|
|
|
|
interpret,
|
|
|
|
separator,
|
|
|
|
} => {
|
|
|
|
write!(w, "storage_nbt[nbt={nbt}")?;
|
|
|
|
|
|
|
|
if let Some(interpret) = interpret {
|
|
|
|
write!(w, ", interpret={interpret}")?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(separator) = separator {
|
|
|
|
if !separator.is_empty() {
|
|
|
|
write!(w, "separator=")?;
|
|
|
|
write_string_inner(separator, w)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
write!(w, ", storage={storage}")?;
|
|
|
|
|
|
|
|
w.write_char(']')?;
|
|
|
|
}
|
2022-10-19 01:52:02 -07:00
|
|
|
}
|
2022-08-09 14:44:04 -07:00
|
|
|
|
2022-11-20 00:48:01 +00:00
|
|
|
for child in &this.0.extra {
|
|
|
|
write_string_inner(child, w)?;
|
2022-10-19 01:52:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
2022-08-09 14:44:04 -07:00
|
|
|
}
|
|
|
|
|
2022-11-20 00:48:01 +00:00
|
|
|
write_string_inner(self, &mut w)
|
2022-08-09 14:44:04 -07:00
|
|
|
}
|
|
|
|
|
2022-07-11 05:08:02 -07:00
|
|
|
/// Returns `true` if the text contains no characters. Returns `false`
|
|
|
|
/// otherwise.
|
2022-07-06 00:16:07 -07:00
|
|
|
pub fn is_empty(&self) -> bool {
|
2022-11-20 00:48:01 +00:00
|
|
|
for extra in &self.0.extra {
|
2022-07-06 00:16:07 -07:00
|
|
|
if !extra.is_empty() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-20 00:48:01 +00:00
|
|
|
match &self.0.content {
|
2022-07-06 00:16:07 -07:00
|
|
|
TextContent::Text { text } => text.is_empty(),
|
2022-11-20 00:48:01 +00:00
|
|
|
TextContent::Translate { translate, .. } => translate.is_empty(),
|
|
|
|
TextContent::ScoreboardValue { score } => {
|
|
|
|
let ScoreboardValueContent {
|
|
|
|
name, objective, ..
|
|
|
|
} = score;
|
|
|
|
|
|
|
|
name.is_empty() || objective.is_empty()
|
|
|
|
}
|
|
|
|
TextContent::EntityNames { selector, .. } => selector.is_empty(),
|
|
|
|
TextContent::Keybind { keybind } => keybind.is_empty(),
|
|
|
|
TextContent::BlockNbt { nbt, .. } => nbt.is_empty(),
|
|
|
|
TextContent::EntityNbt { nbt, .. } => nbt.is_empty(),
|
|
|
|
TextContent::StorageNbt { nbt, .. } => nbt.is_empty(),
|
2022-07-06 00:16:07 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-14 14:55:45 -07:00
|
|
|
/// Provides the methods necessary for working with [`Text`] objects.
|
|
|
|
///
|
2022-04-29 00:48:41 -07:00
|
|
|
/// This trait exists to allow using `Into<Text>` types without having to first
|
2022-07-11 05:08:02 -07:00
|
|
|
/// convert the type into [`Text`]. A blanket implementation exists for all
|
2022-04-14 14:55:45 -07:00
|
|
|
/// `Into<Text>` types, including [`Text`] itself.
|
|
|
|
pub trait TextFormat: Into<Text> {
|
2022-07-14 23:18:20 -07:00
|
|
|
/// Converts this type into a [`Text`] object.
|
2022-04-14 14:55:45 -07:00
|
|
|
fn into_text(self) -> Text {
|
|
|
|
self.into()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn color(self, color: Color) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.color = Some(color);
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn clear_color(self) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.color = None;
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn font(self, font: impl Into<Cow<'static, str>>) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.font = Some(font.into());
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn clear_font(self) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.font = None;
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn bold(self) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.bold = Some(true);
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn not_bold(self) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.bold = Some(false);
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn clear_bold(self) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.bold = None;
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn italic(self) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.italic = Some(true);
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn not_italic(self) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.italic = Some(false);
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn clear_italic(self) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.italic = None;
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn underlined(self) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.underlined = Some(true);
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn not_underlined(self) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.underlined = Some(false);
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn clear_underlined(self) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.underlined = None;
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn strikethrough(self) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.strikethrough = Some(true);
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn not_strikethrough(self) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.strikethrough = Some(false);
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn clear_strikethrough(self) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.strikethrough = None;
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn obfuscated(self) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.obfuscated = Some(true);
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn not_obfuscated(self) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.obfuscated = Some(false);
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn clear_obfuscated(self) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.obfuscated = None;
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn insertion(self, insertion: impl Into<Cow<'static, str>>) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.insertion = Some(insertion.into());
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn clear_insertion(self) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.insertion = None;
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn on_click_open_url(self, url: impl Into<Cow<'static, str>>) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.click_event = Some(ClickEvent::OpenUrl(url.into()));
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn on_click_run_command(self, command: impl Into<Cow<'static, str>>) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.click_event = Some(ClickEvent::RunCommand(command.into()));
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn on_click_suggest_command(self, command: impl Into<Cow<'static, str>>) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.click_event = Some(ClickEvent::SuggestCommand(command.into()));
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn on_click_change_page(self, page: impl Into<i32>) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.click_event = Some(ClickEvent::ChangePage(page.into()));
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn on_click_copy_to_clipboard(self, text: impl Into<Cow<'static, str>>) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.click_event = Some(ClickEvent::CopyToClipboard(text.into()));
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn clear_click_event(self) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.click_event = None;
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn on_hover_show_text(self, text: impl Into<Text>) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.hover_event = Some(HoverEvent::ShowText(text.into()));
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn clear_hover_event(self) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.hover_event = None;
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add_child(self, text: impl Into<Text>) -> Text {
|
|
|
|
let mut t = self.into();
|
2022-11-20 00:48:01 +00:00
|
|
|
t.0.extra.push(text.into());
|
2022-04-14 14:55:45 -07:00
|
|
|
t
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: Into<Text>> TextFormat for T {}
|
|
|
|
|
2022-12-29 02:26:19 -06:00
|
|
|
impl<T: Into<Text>> ops::Add<T> for Text {
|
2022-04-14 14:55:45 -07:00
|
|
|
type Output = Self;
|
|
|
|
|
|
|
|
fn add(self, rhs: T) -> Self::Output {
|
|
|
|
self.add_child(rhs)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-29 02:26:19 -06:00
|
|
|
impl<T: Into<Text>> ops::AddAssign<T> for Text {
|
2022-04-14 14:55:45 -07:00
|
|
|
fn add_assign(&mut self, rhs: T) {
|
2022-11-20 00:48:01 +00:00
|
|
|
self.0.extra.push(rhs.into());
|
2022-04-14 14:55:45 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-14 03:40:26 -07:00
|
|
|
impl From<char> for Text {
|
|
|
|
fn from(c: char) -> Self {
|
|
|
|
Text::text(String::from(c))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-14 14:55:45 -07:00
|
|
|
impl From<String> for Text {
|
|
|
|
fn from(s: String) -> Self {
|
|
|
|
Text::text(s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<&'static str> for Text {
|
|
|
|
fn from(s: &'static str) -> Self {
|
|
|
|
Text::text(s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Cow<'static, str>> for Text {
|
|
|
|
fn from(s: Cow<'static, str>) -> Self {
|
|
|
|
Text::text(s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for Text {
|
2022-10-19 01:52:02 -07:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
2022-11-20 00:48:01 +00:00
|
|
|
self.write_string(f)
|
2022-04-14 14:55:45 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Encode for Text {
|
2022-11-13 06:10:42 -08:00
|
|
|
fn encode(&self, w: impl Write) -> Result<()> {
|
|
|
|
serde_json::to_string(self)?.encode(w)
|
2022-04-14 14:55:45 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-13 06:10:42 -08:00
|
|
|
impl Decode<'_> for Text {
|
|
|
|
fn decode(r: &mut &[u8]) -> Result<Self> {
|
|
|
|
let string = <&str>::decode(r)?;
|
2022-12-31 22:59:22 -08:00
|
|
|
if string.is_empty() {
|
|
|
|
Ok(Self::default())
|
|
|
|
} else {
|
|
|
|
serde_json::from_str(string).context("decoding text JSON")
|
|
|
|
}
|
2022-04-14 14:55:45 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for TextContent {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::Text { text: "".into() }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Color {
|
|
|
|
pub const AQUA: Color = Color::new(85, 255, 255);
|
|
|
|
pub const BLACK: Color = Color::new(0, 0, 0);
|
|
|
|
pub const BLUE: Color = Color::new(85, 85, 255);
|
|
|
|
pub const DARK_AQUA: Color = Color::new(0, 170, 170);
|
|
|
|
pub const DARK_BLUE: Color = Color::new(0, 0, 170);
|
|
|
|
pub const DARK_GRAY: Color = Color::new(85, 85, 85);
|
|
|
|
pub const DARK_GREEN: Color = Color::new(0, 170, 0);
|
|
|
|
pub const DARK_PURPLE: Color = Color::new(170, 0, 170);
|
|
|
|
pub const DARK_RED: Color = Color::new(170, 0, 0);
|
|
|
|
pub const GOLD: Color = Color::new(255, 170, 0);
|
|
|
|
pub const GRAY: Color = Color::new(170, 170, 170);
|
|
|
|
pub const GREEN: Color = Color::new(85, 255, 85);
|
|
|
|
pub const LIGHT_PURPLE: Color = Color::new(255, 85, 255);
|
|
|
|
pub const RED: Color = Color::new(255, 85, 85);
|
|
|
|
pub const WHITE: Color = Color::new(255, 255, 255);
|
|
|
|
pub const YELLOW: Color = Color::new(255, 255, 85);
|
|
|
|
|
2022-07-14 23:18:20 -07:00
|
|
|
/// Constructs a new color from red, green, and blue components.
|
2022-04-14 14:55:45 -07:00
|
|
|
pub const fn new(r: u8, g: u8, b: u8) -> Self {
|
|
|
|
Self { r, g, b }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Serialize for Color {
|
|
|
|
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
|
|
|
format!("#{:02x}{:02x}{:02x}", self.r, self.g, self.b).serialize(serializer)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'de> Deserialize<'de> for Color {
|
|
|
|
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
|
|
|
deserializer.deserialize_str(ColorVisitor)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct ColorVisitor;
|
|
|
|
|
|
|
|
impl<'de> Visitor<'de> for ColorVisitor {
|
|
|
|
type Value = Color;
|
|
|
|
|
|
|
|
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
write!(f, "a hex color of the form #rrggbb")
|
|
|
|
}
|
|
|
|
|
2022-10-11 01:10:49 -07:00
|
|
|
fn visit_str<E: de::Error>(self, s: &str) -> Result<Self::Value, E> {
|
2022-04-14 14:55:45 -07:00
|
|
|
color_from_str(s).ok_or_else(|| E::custom("invalid hex color"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn color_from_str(s: &str) -> Option<Color> {
|
|
|
|
let to_num = |d| match d {
|
|
|
|
b'0'..=b'9' => Some(d - b'0'),
|
|
|
|
b'a'..=b'f' => Some(d - b'a' + 0xa),
|
|
|
|
b'A'..=b'F' => Some(d - b'A' + 0xa),
|
|
|
|
_ => None,
|
|
|
|
};
|
|
|
|
|
|
|
|
match s.as_bytes() {
|
|
|
|
[b'#', r0, r1, g0, g1, b0, b1] => Some(Color {
|
|
|
|
r: to_num(*r0)? << 4 | to_num(*r1)?,
|
|
|
|
g: to_num(*g0)? << 4 | to_num(*g1)?,
|
|
|
|
b: to_num(*b0)? << 4 | to_num(*b1)?,
|
|
|
|
}),
|
2022-09-06 02:24:33 +03:00
|
|
|
_ => match s {
|
|
|
|
"aqua" => Some(Color::AQUA),
|
|
|
|
"black" => Some(Color::BLACK),
|
|
|
|
"blue" => Some(Color::BLUE),
|
|
|
|
"dark_aqua" => Some(Color::DARK_AQUA),
|
|
|
|
"dark_blue" => Some(Color::DARK_BLUE),
|
|
|
|
"dark_gray" => Some(Color::DARK_GRAY),
|
|
|
|
"dark_green" => Some(Color::DARK_GREEN),
|
|
|
|
"dark_purple" => Some(Color::DARK_PURPLE),
|
|
|
|
"dark_red" => Some(Color::DARK_RED),
|
|
|
|
"gold" => Some(Color::GOLD),
|
|
|
|
"gray" => Some(Color::GRAY),
|
|
|
|
"green" => Some(Color::GREEN),
|
|
|
|
"light_purple" => Some(Color::LIGHT_PURPLE),
|
|
|
|
"red" => Some(Color::RED),
|
|
|
|
"white" => Some(Color::WHITE),
|
|
|
|
"yellow" => Some(Color::YELLOW),
|
|
|
|
_ => None,
|
|
|
|
},
|
2022-04-14 14:55:45 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2022-11-20 00:48:01 +00:00
|
|
|
use crate::ident;
|
2022-04-14 14:55:45 -07:00
|
|
|
|
|
|
|
#[test]
|
2022-11-20 00:48:01 +00:00
|
|
|
fn text_round_trip() {
|
2022-04-14 14:55:45 -07:00
|
|
|
let before = "foo".color(Color::RED).bold()
|
|
|
|
+ ("bar".obfuscated().color(Color::YELLOW)
|
|
|
|
+ "baz".underlined().not_bold().italic().color(Color::BLACK));
|
|
|
|
|
2022-11-20 00:48:01 +00:00
|
|
|
assert_eq!(before.to_string(), "foobarbaz");
|
2022-04-14 14:55:45 -07:00
|
|
|
|
|
|
|
let json = serde_json::to_string_pretty(&before).unwrap();
|
|
|
|
|
|
|
|
let after: Text = serde_json::from_str(&json).unwrap();
|
|
|
|
|
|
|
|
println!("==== Before ====\n");
|
|
|
|
println!("{before:#?}");
|
|
|
|
println!("==== After ====\n");
|
|
|
|
println!("{after:#?}");
|
|
|
|
|
|
|
|
assert_eq!(before, after);
|
2022-11-20 00:48:01 +00:00
|
|
|
assert_eq!(before.to_string(), after.to_string());
|
2022-04-14 14:55:45 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2022-11-20 00:48:01 +00:00
|
|
|
fn text_color() {
|
2022-04-14 14:55:45 -07:00
|
|
|
assert_eq!(
|
|
|
|
color_from_str("#aBcDeF"),
|
|
|
|
Some(Color::new(0xab, 0xcd, 0xef))
|
|
|
|
);
|
|
|
|
assert_eq!(color_from_str("#fFfFfF"), Some(Color::new(255, 255, 255)));
|
|
|
|
assert_eq!(color_from_str("#00000000"), None);
|
|
|
|
assert_eq!(color_from_str("#000000"), Some(Color::BLACK));
|
|
|
|
assert_eq!(color_from_str("#"), None);
|
2022-09-06 02:24:33 +03:00
|
|
|
assert_eq!(color_from_str("red"), Some(Color::RED));
|
|
|
|
assert_eq!(color_from_str("blue"), Some(Color::BLUE));
|
2022-04-14 14:55:45 -07:00
|
|
|
}
|
2022-07-06 00:16:07 -07:00
|
|
|
|
2022-11-20 00:48:01 +00:00
|
|
|
#[test]
|
|
|
|
fn translate() {
|
2022-11-27 13:12:08 +00:00
|
|
|
let txt = Text::translate(
|
|
|
|
valence_protocol::translation_key::CHAT_TYPE_ADVANCEMENT_TASK,
|
|
|
|
["arg1".into(), "arg2".into()],
|
|
|
|
);
|
2022-11-20 00:48:01 +00:00
|
|
|
let serialized = serde_json::to_string(&txt).unwrap();
|
|
|
|
let deserialized: Text = serde_json::from_str(&serialized).unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
serialized,
|
2022-11-27 13:12:08 +00:00
|
|
|
"{\"translate\":\"chat.type.advancement.task\",\"with\":[{\"text\":\"arg1\"},{\"text\"\
|
|
|
|
:\"arg2\"}]}"
|
2022-11-20 00:48:01 +00:00
|
|
|
);
|
|
|
|
assert_eq!(txt, deserialized);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn score() {
|
|
|
|
let txt = Text::score("foo", "bar", Some(Cow::from("baz")));
|
|
|
|
let serialized = serde_json::to_string(&txt).unwrap();
|
|
|
|
let deserialized: Text = serde_json::from_str(&serialized).unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
serialized,
|
|
|
|
"{\"score\":{\"name\":\"foo\",\"objective\":\"bar\",\"value\":\"baz\"}}"
|
|
|
|
);
|
|
|
|
assert_eq!(txt, deserialized);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn selector() {
|
|
|
|
let separator = Text::text("bar").color(Color::RED).bold();
|
|
|
|
let txt = Text::selector("foo", Some(separator));
|
|
|
|
let serialized = serde_json::to_string(&txt).unwrap();
|
|
|
|
let deserialized: Text = serde_json::from_str(&serialized).unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
serialized,
|
|
|
|
"{\"selector\":\"foo\",\"separator\":{\"text\":\"bar\",\"color\":\"#ff5555\",\"bold\":\
|
|
|
|
true}}"
|
|
|
|
);
|
|
|
|
assert_eq!(txt, deserialized);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn keybind() {
|
|
|
|
let txt = Text::keybind("foo");
|
|
|
|
let serialized = serde_json::to_string(&txt).unwrap();
|
|
|
|
let deserialized: Text = serde_json::from_str(&serialized).unwrap();
|
|
|
|
assert_eq!(serialized, "{\"keybind\":\"foo\"}");
|
|
|
|
assert_eq!(txt, deserialized);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn block_nbt() {
|
|
|
|
let txt = Text::block_nbt("foo", "bar", Some(true), Some("baz".into()));
|
|
|
|
let serialized = serde_json::to_string(&txt).unwrap();
|
|
|
|
let deserialized: Text = serde_json::from_str(&serialized).unwrap();
|
|
|
|
let expected = "{\"block\":\"foo\",\"nbt\":\"bar\",\"interpret\":true,\"separator\":{\"\
|
|
|
|
text\":\"baz\"}}";
|
|
|
|
assert_eq!(serialized, expected);
|
|
|
|
assert_eq!(txt, deserialized);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn entity_nbt() {
|
|
|
|
let txt = Text::entity_nbt("foo", "bar", Some(true), Some("baz".into()));
|
|
|
|
let serialized = serde_json::to_string(&txt).unwrap();
|
|
|
|
let deserialized: Text = serde_json::from_str(&serialized).unwrap();
|
|
|
|
let expected = "{\"entity\":\"foo\",\"nbt\":\"bar\",\"interpret\":true,\"separator\":{\"\
|
|
|
|
text\":\"baz\"}}";
|
|
|
|
assert_eq!(serialized, expected);
|
|
|
|
assert_eq!(txt, deserialized);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn storage_nbt() {
|
|
|
|
let txt = Text::storage_nbt(ident!("foo"), "bar", Some(true), Some("baz".into()));
|
|
|
|
let serialized = serde_json::to_string(&txt).unwrap();
|
|
|
|
let deserialized: Text = serde_json::from_str(&serialized).unwrap();
|
|
|
|
let expected = "{\"storage\":\"foo\",\"nbt\":\"bar\",\"interpret\":true,\"separator\":{\"\
|
|
|
|
text\":\"baz\"}}";
|
|
|
|
assert_eq!(serialized, expected);
|
|
|
|
assert_eq!(txt, deserialized);
|
2022-07-06 00:16:07 -07:00
|
|
|
}
|
2022-04-14 14:55:45 -07:00
|
|
|
}
|