cubic_chat/
component.rs

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}