mirror of
https://github.com/Theaninova/mhlib.git
synced 2025-12-12 20:46:20 +00:00
add archive tests
This commit is contained in:
@@ -1 +1,2 @@
|
|||||||
pub mod archive;
|
pub mod archive;
|
||||||
|
pub mod media;
|
||||||
|
|||||||
1
rust/springylib/src/media/mod.rs
Normal file
1
rust/springylib/src/media/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod ui;
|
||||||
30
rust/springylib/src/media/ui/image.rs
Normal file
30
rust/springylib/src/media/ui/image.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
use crate::media::ui::vec::deserialize_vec2;
|
||||||
|
use crate::media::ui::FadeMode;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct UiImage {
|
||||||
|
pub texture: String,
|
||||||
|
#[serde(deserialize_with = "deserialize_vec2")]
|
||||||
|
pub position: [i32; 2],
|
||||||
|
#[serde(deserialize_with = "deserialize_vec2")]
|
||||||
|
pub size: [i32; 2],
|
||||||
|
#[serde(rename = "fademode", default)]
|
||||||
|
pub fade_mode: FadeMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::media::ui::image::UiImage;
|
||||||
|
|
||||||
|
// language=xml
|
||||||
|
const IMAGE: &str = "<Image texture='tex' position='1,2' size='3,4' />";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_should_read() {
|
||||||
|
let image: UiImage = serde_xml_rs::from_str(IMAGE).unwrap();
|
||||||
|
assert_eq!(image.texture, "tex".to_string());
|
||||||
|
assert_eq!(image.position, [1, 2]);
|
||||||
|
assert_eq!(image.size, [3, 4]);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
rust/springylib/src/media/ui/menu.rs
Normal file
11
rust/springylib/src/media/ui/menu.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
use crate::media::ui::UiTag;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct UiMenu {
|
||||||
|
pub selected: String,
|
||||||
|
#[serde(rename = "OnBack")]
|
||||||
|
pub on_back: Option<String>,
|
||||||
|
#[serde(rename = "$value", default)]
|
||||||
|
pub children: Vec<UiTag>,
|
||||||
|
}
|
||||||
88
rust/springylib/src/media/ui/mod.rs
Normal file
88
rust/springylib/src/media/ui/mod.rs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
use crate::media::ui::image::UiImage;
|
||||||
|
use crate::media::ui::menu::UiMenu;
|
||||||
|
use crate::media::ui::static_text::UiStaticText;
|
||||||
|
use crate::media::ui::text_area::UiTextArea;
|
||||||
|
use crate::media::ui::text_button::UiTextButton;
|
||||||
|
use crate::media::ui::text_field::UiTextField;
|
||||||
|
use crate::media::ui::toggle_button::UiToggleButton;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
pub mod image;
|
||||||
|
pub mod menu;
|
||||||
|
pub mod static_text;
|
||||||
|
pub mod text_area;
|
||||||
|
pub mod text_button;
|
||||||
|
pub mod text_field;
|
||||||
|
pub mod toggle_button;
|
||||||
|
pub mod vec;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub enum UiTag {
|
||||||
|
Menu(UiMenu),
|
||||||
|
Image(UiImage),
|
||||||
|
TextButton(UiTextButton),
|
||||||
|
TextArea(UiTextArea),
|
||||||
|
TextField(UiTextField),
|
||||||
|
StaticText(UiStaticText),
|
||||||
|
ToggleButton(UiToggleButton),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum HorizontalAlign {
|
||||||
|
Left,
|
||||||
|
Center,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for HorizontalAlign {
|
||||||
|
fn default() -> HorizontalAlign {
|
||||||
|
HorizontalAlign::Left
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum FadeMode {
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FadeMode {
|
||||||
|
fn default() -> Self {
|
||||||
|
FadeMode::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiTag {
|
||||||
|
pub fn post_process(&mut self) {
|
||||||
|
if let UiTag::Menu(menu) = self {
|
||||||
|
let children: Vec<UiTag> = menu.children.drain(..).collect();
|
||||||
|
let mut area_stack: Vec<Vec<UiTag>> = vec![vec![]];
|
||||||
|
|
||||||
|
for mut child in children {
|
||||||
|
child.post_process();
|
||||||
|
if let UiTag::TextArea(mut area) = child {
|
||||||
|
let children = area_stack.pop().unwrap();
|
||||||
|
let opening_tag = area_stack.last_mut().map(|it| it.last_mut());
|
||||||
|
|
||||||
|
if let Some(Some(UiTag::TextArea(opening_tag))) = opening_tag {
|
||||||
|
opening_tag.children = children;
|
||||||
|
} else {
|
||||||
|
area_stack.push(children);
|
||||||
|
}
|
||||||
|
|
||||||
|
if area.position.is_some() && area.size.is_some() {
|
||||||
|
let children = area.children.drain(..).collect();
|
||||||
|
area_stack.last_mut().unwrap().push(UiTag::TextArea(area));
|
||||||
|
area_stack.push(children);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
area_stack.last_mut().unwrap().push(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.children = area_stack.pop().unwrap();
|
||||||
|
debug_assert!(area_stack.is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
rust/springylib/src/media/ui/static_text.rs
Normal file
29
rust/springylib/src/media/ui/static_text.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
use crate::media::ui::vec::deserialize_vec2;
|
||||||
|
use crate::media::ui::{FadeMode, HorizontalAlign};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct UiStaticText {
|
||||||
|
pub text: String,
|
||||||
|
#[serde(deserialize_with = "deserialize_vec2")]
|
||||||
|
pub position: [i32; 2],
|
||||||
|
#[serde(rename = "halign", default)]
|
||||||
|
pub horizontal_align: HorizontalAlign,
|
||||||
|
#[serde(rename = "fademode", default)]
|
||||||
|
pub fade_mode: FadeMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::media::ui::static_text::UiStaticText;
|
||||||
|
|
||||||
|
// language=xml
|
||||||
|
const STATIC_TEXT: &str = "<StaticText text='test' position='1,2' />";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_should_read() {
|
||||||
|
let static_text: UiStaticText = serde_xml_rs::from_str(STATIC_TEXT).unwrap();
|
||||||
|
assert_eq!(static_text.text, "test".to_string());
|
||||||
|
assert_eq!(static_text.position, [1, 2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
rust/springylib/src/media/ui/text_area.rs
Normal file
15
rust/springylib/src/media/ui/text_area.rs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
use crate::media::ui::vec::deserialize_vec2_opt;
|
||||||
|
use crate::media::ui::UiTag;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// This is a really weird node, sometimes it has children and sometimes, don't ask me why,
|
||||||
|
/// it appears as a normal tag and then gets closed by an empty tag of this kind.
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct UiTextArea {
|
||||||
|
#[serde(deserialize_with = "deserialize_vec2_opt", default)]
|
||||||
|
pub position: Option<[i32; 2]>,
|
||||||
|
#[serde(deserialize_with = "deserialize_vec2_opt", default)]
|
||||||
|
pub size: Option<[i32; 2]>,
|
||||||
|
#[serde(rename = "$value", default)]
|
||||||
|
pub children: Vec<UiTag>,
|
||||||
|
}
|
||||||
35
rust/springylib/src/media/ui/text_button.rs
Normal file
35
rust/springylib/src/media/ui/text_button.rs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
use crate::media::ui::vec::deserialize_vec2;
|
||||||
|
use crate::media::ui::{FadeMode, HorizontalAlign};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct UiTextButton {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub text: String,
|
||||||
|
#[serde(deserialize_with = "deserialize_vec2")]
|
||||||
|
pub position: [i32; 2],
|
||||||
|
#[serde(rename = "halign", default)]
|
||||||
|
pub horizontal_align: HorizontalAlign,
|
||||||
|
#[serde(rename = "fademode", default)]
|
||||||
|
pub fade_mode: FadeMode,
|
||||||
|
#[serde(rename = "OnSelect")]
|
||||||
|
pub on_select: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::media::ui::text_button::UiTextButton;
|
||||||
|
|
||||||
|
// language=xml
|
||||||
|
const BUTTON: &str =
|
||||||
|
"<TextButton name='test' text='abc' position='1,2' OnSelect='StartGame' />";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_should_read() {
|
||||||
|
let button: UiTextButton = serde_xml_rs::from_str(BUTTON).unwrap();
|
||||||
|
assert_eq!(button.name, Some("test".to_string()));
|
||||||
|
assert_eq!(button.text, "abc".to_string());
|
||||||
|
assert_eq!(button.position, [1, 2]);
|
||||||
|
assert_eq!(button.on_select, "StartGame".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
40
rust/springylib/src/media/ui/text_field.rs
Normal file
40
rust/springylib/src/media/ui/text_field.rs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
use crate::media::ui::vec::{deserialize_vec2, deserialize_vec4};
|
||||||
|
use crate::media::ui::{FadeMode, HorizontalAlign};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct UiTextField {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub text: String,
|
||||||
|
#[serde(deserialize_with = "deserialize_vec2")]
|
||||||
|
pub position: [i32; 2],
|
||||||
|
#[serde(rename = "bufferVar")]
|
||||||
|
pub buffer_var: String,
|
||||||
|
#[serde(deserialize_with = "deserialize_vec4")]
|
||||||
|
pub area: [i32; 4],
|
||||||
|
#[serde(rename = "halign", default)]
|
||||||
|
pub horizontal_align: HorizontalAlign,
|
||||||
|
#[serde(rename = "fademode", default)]
|
||||||
|
pub fade_mode: FadeMode,
|
||||||
|
#[serde(rename = "OnSelect")]
|
||||||
|
pub on_select: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::media::ui::text_field::UiTextField;
|
||||||
|
|
||||||
|
// language=xml
|
||||||
|
const TEXT_FIELD: &str = "<TextField name='test' text='abc' position='1,2' bufferVar='var' area='1,2,3,4' OnSelect='click' />";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_should_read() {
|
||||||
|
let text_field: UiTextField = serde_xml_rs::from_str(TEXT_FIELD).unwrap();
|
||||||
|
assert_eq!(text_field.name, Some("test".to_string()));
|
||||||
|
assert_eq!(text_field.text, "abc".to_string());
|
||||||
|
assert_eq!(text_field.position, [1, 2]);
|
||||||
|
assert_eq!(text_field.buffer_var, "var".to_string());
|
||||||
|
assert_eq!(text_field.area, [1, 2, 3, 4]);
|
||||||
|
assert_eq!(text_field.on_select, "click".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
72
rust/springylib/src/media/ui/toggle_button.rs
Normal file
72
rust/springylib/src/media/ui/toggle_button.rs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
use crate::media::ui::vec::deserialize_vec2;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
||||||
|
pub struct UiToggleButton {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub text: String,
|
||||||
|
#[serde(deserialize_with = "deserialize_vec2")]
|
||||||
|
pub position: [i32; 2],
|
||||||
|
pub value: String,
|
||||||
|
#[serde(rename = "minValue")]
|
||||||
|
pub min_value: i32,
|
||||||
|
#[serde(rename = "maxValue")]
|
||||||
|
pub max_value: i32,
|
||||||
|
#[serde(rename = "valueStep")]
|
||||||
|
pub value_step: i32,
|
||||||
|
pub target: String,
|
||||||
|
#[serde(rename = "targetLOffset", deserialize_with = "deserialize_vec2")]
|
||||||
|
pub target_l_offset: [i32; 2],
|
||||||
|
#[serde(rename = "targetROffset", deserialize_with = "deserialize_vec2")]
|
||||||
|
pub target_r_offset: [i32; 2],
|
||||||
|
#[serde(rename = "noSound", default)]
|
||||||
|
pub no_sound: bool,
|
||||||
|
#[serde(rename = "OnChange")]
|
||||||
|
pub on_change: String,
|
||||||
|
#[serde(rename = "OnSelect")]
|
||||||
|
pub on_select: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::media::ui::toggle_button::UiToggleButton;
|
||||||
|
|
||||||
|
// language=xml
|
||||||
|
const TOGGLE_BUTTON: &str = "<ToggleButton \
|
||||||
|
name='test' \
|
||||||
|
text='abc' \
|
||||||
|
position='1,2' \
|
||||||
|
value='val' \
|
||||||
|
minValue='0' \
|
||||||
|
maxValue='10' \
|
||||||
|
valueStep='1' \
|
||||||
|
target='target' \
|
||||||
|
targetLOffset='3,4' \
|
||||||
|
targetROffset='5,6' \
|
||||||
|
noSound='false' \
|
||||||
|
OnChange='change' \
|
||||||
|
OnSelect='select' />";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_should_read() {
|
||||||
|
let toggle_button: UiToggleButton = serde_xml_rs::from_str(TOGGLE_BUTTON).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
toggle_button,
|
||||||
|
UiToggleButton {
|
||||||
|
name: Some("test".to_string()),
|
||||||
|
text: "abc".to_string(),
|
||||||
|
position: [1, 2],
|
||||||
|
value: "val".to_string(),
|
||||||
|
min_value: 0,
|
||||||
|
max_value: 10,
|
||||||
|
value_step: 1,
|
||||||
|
target: "target".to_string(),
|
||||||
|
target_l_offset: [3, 4],
|
||||||
|
target_r_offset: [5, 6],
|
||||||
|
no_sound: false,
|
||||||
|
on_change: "change".to_string(),
|
||||||
|
on_select: "select".to_string(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
67
rust/springylib/src/media/ui/vec.rs
Normal file
67
rust/springylib/src/media/ui/vec.rs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
use serde::{Deserialize, Deserializer};
|
||||||
|
use serde::de::Error;
|
||||||
|
|
||||||
|
pub fn deserialize_vec2_opt<'de, D>(deserializer: D) -> Result<Option<[i32; 2]>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
if let Some(buf) = Option::<String>::deserialize(deserializer)? {
|
||||||
|
to_vec2::<D>(buf).map(Some)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize_vec2<'de, D>(deserializer: D) -> Result<[i32; 2], D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
to_vec2::<D>(String::deserialize(deserializer)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize_vec4<'de, D>(deserializer: D) -> Result<[i32; 4], D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
to_vec4::<D>(String::deserialize(deserializer)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_vec<'de, D>(buf: String) -> Result<Vec<i32>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
buf.split(',')
|
||||||
|
.into_iter()
|
||||||
|
.map(|value| {
|
||||||
|
// there's some typos so we have to cover that...
|
||||||
|
value.split_ascii_whitespace().collect::<Vec<&str>>()[0]
|
||||||
|
.trim()
|
||||||
|
.parse::<i32>()
|
||||||
|
.map_err(|err| Error::custom(err.to_string()))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_vec4<'de, D>(buf: String) -> Result<[i32; 4], D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let mut values = to_vec::<D>(buf)?;
|
||||||
|
let w = values.pop().ok_or(Error::custom("InvalidField"))?;
|
||||||
|
let z = values.pop().ok_or(Error::custom("InvalidField"))?;
|
||||||
|
let y = values.pop().ok_or(Error::custom("InvalidField"))?;
|
||||||
|
let x = values.pop().ok_or(Error::custom("InvalidField"))?;
|
||||||
|
|
||||||
|
Ok([x, y, z, w])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_vec2<'de, D>(buf: String) -> Result<[i32; 2], D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let mut values = to_vec::<D>(buf)?;
|
||||||
|
let y = values.pop().ok_or(Error::custom("InvalidField"))?;
|
||||||
|
let x = values.pop().ok_or(Error::custom("InvalidField"))?;
|
||||||
|
|
||||||
|
Ok([x, y])
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user