1use serde::{Deserialize, Serialize};
2
3use crate::color::{Color, DefaultColor};
4use crate::identifier::Identifier;
5
6#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
7#[serde(rename_all = "snake_case", tag = "action", content = "value")]
8pub enum ClickEvent {
9 OpenUrl(String),
10 RunCommand(String),
11 SuggestCommand(String),
12 ChangePage(usize),
13 CopyToClipboard(String),
14}
15
16#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
17#[serde(rename_all = "snake_case", tag = "action", content = "value")]
18pub enum HoverEvent {
19 ShowText(Box<ComponentType>),
20 ShowItem(String),
21 ShowEntity(String),
22}
23
24#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
25#[serde(untagged)]
26pub enum ComponentType {
27 Text(TextComponent),
28 Translation(TranslationComponent),
29 KeyBind(KeyBindComponent),
30 Score(ScoreComponent),
31 Selector(SelectorComponent),
32 Base(BaseComponent),
33}
34
35pub trait Component {
36 fn get_base(&self) -> &BaseComponent;
37}
38
39#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
40#[serde(rename_all = "camelCase")]
41pub struct BaseComponent {
42 #[serde(skip_serializing_if = "Option::is_none")]
43 pub bold: Option<bool>,
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub italic: Option<bool>,
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub underlined: Option<bool>,
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub strikethrough: Option<bool>,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub obfuscated: Option<bool>,
52 #[serde(skip_serializing_if = "Option::is_none")]
53 pub font: Option<Identifier>,
54 #[serde(skip_serializing_if = "Option::is_none")]
55 pub color: Option<Color>,
56 #[serde(skip_serializing_if = "Option::is_none")]
57 pub insertion: Option<String>,
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub click_event: Option<ClickEvent>,
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub hover_event: Option<HoverEvent>,
62 #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")]
63 pub extra: Vec<ComponentType>,
64}
65
66#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
67pub struct TextComponent {
68 pub text: String,
69 #[serde(flatten)]
70 pub base: BaseComponent,
71}
72
73#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
74pub struct TranslationComponent {
75 pub translate: String,
76 #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")]
77 pub with: Vec<ComponentType>,
78 #[serde(flatten)]
79 pub base: BaseComponent,
80}
81
82#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
83pub struct KeyBindComponent {
84 pub keybind: String,
85 #[serde(flatten)]
86 pub base: BaseComponent,
87}
88
89#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
90pub struct ScoreComponent {
91 pub score: Score,
92 #[serde(flatten)]
93 pub base: BaseComponent,
94}
95
96#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
97pub struct Score {
98 pub name: String,
99 pub objective: String,
100 pub value: String,
101}
102
103#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
104pub struct SelectorComponent {
105 pub selector: String,
106 #[serde(flatten)]
107 pub base: BaseComponent,
108}
109
110macro_rules! component {
111 ($name: ident) => {
112 impl Component for $name {
113 fn get_base(&self) -> &BaseComponent {
114 &self.base
115 }
116 }
117
118 impl $name {
119 pub fn reset(&mut self) {
120 self.base.bold = Some(false);
121 self.base.italic = Some(false);
122 self.base.underlined = Some(false);
123 self.base.strikethrough = Some(false);
124 self.base.obfuscated = Some(false);
125 self.base.color = Some(Color::Default(DefaultColor::White));
126 }
127 }
128 };
129 ($($name: ident)*) => {
130 $(component!($name);)*
131 }
132}
133
134impl Component for BaseComponent {
135 fn get_base(&self) -> &BaseComponent {
136 self
137 }
138}
139
140component!(TextComponent TranslationComponent KeyBindComponent ScoreComponent SelectorComponent);
141
142impl TextComponent {
143 pub fn new(text: String) -> Self {
144 TextComponent {
145 text,
146 base: BaseComponent::default(),
147 }
148 }
149}
150
151impl TranslationComponent {
152 pub fn new(translate: String) -> Self {
153 TranslationComponent {
154 translate,
155 with: Vec::new(),
156 base: BaseComponent::default(),
157 }
158 }
159}
160
161impl KeyBindComponent {
162 pub fn new(keybind: String) -> Self {
163 KeyBindComponent {
164 keybind,
165 base: BaseComponent::default(),
166 }
167 }
168}
169
170impl ScoreComponent {
171 pub fn new(score: Score) -> Self {
172 ScoreComponent {
173 score,
174 base: BaseComponent::default(),
175 }
176 }
177}
178
179impl SelectorComponent {
180 pub fn new(selector: String) -> Self {
181 SelectorComponent {
182 selector,
183 base: BaseComponent::default(),
184 }
185 }
186}
187
188impl From<TextComponent> for ComponentType {
189 fn from(component: TextComponent) -> Self {
190 ComponentType::Text(component)
191 }
192}
193
194impl From<TranslationComponent> for ComponentType {
195 fn from(component: TranslationComponent) -> Self {
196 ComponentType::Translation(component)
197 }
198}
199
200impl From<KeyBindComponent> for ComponentType {
201 fn from(component: KeyBindComponent) -> Self {
202 ComponentType::KeyBind(component)
203 }
204}
205
206impl From<ScoreComponent> for ComponentType {
207 fn from(component: ScoreComponent) -> Self {
208 ComponentType::Score(component)
209 }
210}
211
212impl From<SelectorComponent> for ComponentType {
213 fn from(component: SelectorComponent) -> Self {
214 ComponentType::Selector(component)
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use crate::color::HexColor;
221
222 use super::*;
223
224 #[test]
225 pub fn color_ser_test() {
226 assert_eq!(serde_json::ser::to_string(&Color::Default(DefaultColor::Black)).unwrap(), "\"black\"");
227 assert_eq!(serde_json::ser::to_string(&Color::Default(DefaultColor::Aqua)).unwrap(), "\"aqua\"");
228 assert_eq!(serde_json::ser::to_string(&Color::Default(DefaultColor::LightPurple)).unwrap(), "\"light_purple\"");
229 }
230
231 #[test]
232 pub fn color_de_test() {
233 assert_eq!(serde_json::de::from_str::<'_, DefaultColor>("\"black\"").unwrap(), DefaultColor::Black);
234 assert_eq!(serde_json::de::from_str::<'_, DefaultColor>("\"light_purple\"").unwrap(), DefaultColor::LightPurple);
235 }
236
237 #[test]
238 pub fn click_event_ser_test() {
239 assert_eq!(
240 serde_json::ser::to_string(&ClickEvent::OpenUrl("http://google.com".into())).unwrap(),
241 "{\"action\":\"open_url\",\"value\":\"http://google.com\"}"
242 );
243 assert_eq!(
244 serde_json::ser::to_string(&ClickEvent::ChangePage(100)).unwrap(),
245 "{\"action\":\"change_page\",\"value\":100}"
246 )
247 }
248
249 #[test]
250 pub fn component_ser_test() {
251 let mut component = TextComponent::new("hello".into());
252 component.base.bold = Some(true);
253 component.base.color = Some(Color::Default(DefaultColor::Aqua));
254 assert_eq!(
255 serde_json::ser::to_string(&ComponentType::Text(component.clone())).unwrap(),
256 "{\"text\":\"hello\",\"bold\":true,\"color\":\"aqua\"}"
257 );
258 component.base.color = Some(Color::Hex(HexColor::try_from("#ffffff".to_string()).unwrap()));
259 assert_eq!(
260 serde_json::ser::to_string(&ComponentType::Text(component.clone())).unwrap(),
261 "{\"text\":\"hello\",\"bold\":true,\"color\":\"#ffffff\"}"
262 );
263 }
264
265 #[test]
266 pub fn component_de_test() {
267 let json_component: ComponentType = serde_json::de::from_str(
268 "{\"text\":\"hi\",\"color\":\"red\",\"bold\":true,\"extra\":[{\"text\":\"bye\",\"color\":\"white\",\"bold\":false}]}"
269 ).unwrap();
270 let mut component = TextComponent::new("hi".into());
271 component.base.bold = Some(true);
272 component.base.color = Some(DefaultColor::Red.into());
273 component.base.extra = vec![
274 {
275 let mut component = TextComponent::new("bye".into());
276 component.base.color = Some(DefaultColor::White.into());
277 component.base.bold = Some(false);
278 component.into()
279 }
280 ];
281 assert_eq!(json_component, component.into());
282 }
283
284}