mirror of
https://github.com/Theaninova/mhlib.git
synced 2025-12-12 20:46:20 +00:00
ui
This commit is contained in:
@@ -5,6 +5,8 @@ use crate::formats::sprites::Sprites;
|
||||
use crate::formats::txt::{decrypt_txt, DecryptError};
|
||||
use crate::formats::ui_xml::UiTag;
|
||||
use binrw::BinRead;
|
||||
use itertools::Itertools;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt::Debug;
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom};
|
||||
@@ -26,6 +28,7 @@ pub enum DatafileFile {
|
||||
Vorbis(Vec<u8>),
|
||||
TileCollision(String),
|
||||
Ui(UiTag),
|
||||
Translations(HashMap<String, Vec<String>>),
|
||||
}
|
||||
|
||||
pub enum Error {
|
||||
@@ -90,26 +93,20 @@ where
|
||||
Ok(DatafileFile::Txt(decr))
|
||||
}
|
||||
}
|
||||
/*Some("rle") => {
|
||||
let image: RleImage = RleImage::read(&mut Cursor::new(data)).unwrap();
|
||||
let path = Path::new(dat_path).with_file_name("res.gif");
|
||||
println!("{:?}", path);
|
||||
let mut encoder = GifEncoder::new(
|
||||
OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.open(path)
|
||||
.unwrap(),
|
||||
);
|
||||
encoder.set_repeat(Repeat::Infinite).unwrap();
|
||||
encoder.try_encode_frames(image.into_frames()).unwrap();
|
||||
}
|
||||
Some("dat") => {
|
||||
let image = level_tile_data_to_image(&data).unwrap();
|
||||
let path = Path::new(dat_path).with_file_name("res.png");
|
||||
println!("{:?}", path);
|
||||
image.save_with_format(path, ImageFormat::Png).unwrap();
|
||||
}*/
|
||||
"csv" => Ok(DatafileFile::Translations(
|
||||
String::from_utf8(data)
|
||||
.unwrap()
|
||||
.split('\n')
|
||||
.map(|l| l.trim())
|
||||
.filter(|l| !l.is_empty())
|
||||
.map(|l| {
|
||||
l.splitn(2, ';')
|
||||
.map(|s| s.to_string())
|
||||
.collect_tuple::<(String, String)>()
|
||||
.expect("Invalid csv")
|
||||
})
|
||||
.into_group_map(),
|
||||
)),
|
||||
ext => Err(Error::UnknownFormat(ext.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ pub struct UiMenu {
|
||||
pub selected: String,
|
||||
#[serde(rename = "OnBack")]
|
||||
pub on_back: Option<String>,
|
||||
#[serde(rename = "$value")]
|
||||
#[serde(rename = "$value", default)]
|
||||
pub children: Vec<UiTag>,
|
||||
}
|
||||
|
||||
@@ -45,13 +45,14 @@ pub struct UiTextButton {
|
||||
pub on_select: String,
|
||||
}
|
||||
|
||||
/// This sometimes appears completely empty
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct UiTextArea {
|
||||
#[serde(deserialize_with = "deserialize_vec2")]
|
||||
#[serde(deserialize_with = "deserialize_vec2", default)]
|
||||
pub position: [i32; 2],
|
||||
#[serde(deserialize_with = "deserialize_vec2")]
|
||||
#[serde(deserialize_with = "deserialize_vec2", default)]
|
||||
pub size: [i32; 2],
|
||||
#[serde(rename = "$value")]
|
||||
#[serde(rename = "$value", default)]
|
||||
pub children: Vec<UiTag>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::formats;
|
||||
use crate::formats::datafile::{Datafile, FileEntry};
|
||||
use crate::formats::sprites::{CropMode, RenderMode, SpriteType};
|
||||
use crate::formats::{load_data, DatafileFile};
|
||||
use crate::godot::font::load_bitmap_font;
|
||||
use crate::godot::game_object::parse_game_object;
|
||||
@@ -10,16 +9,11 @@ use crate::godot::tile_map::{create_tile_map, TileCollision};
|
||||
use crate::godot::ui::convert_ui;
|
||||
use binrw::BinRead;
|
||||
use godot::engine::global::Error;
|
||||
use godot::engine::image::Format;
|
||||
use godot::engine::resource_loader::CacheMode;
|
||||
use godot::engine::resource_saver::SaverFlags;
|
||||
use godot::engine::utilities::{printerr, prints};
|
||||
use godot::engine::{
|
||||
AtlasTexture, AudioStream, AudioStreamOggVorbis, DirAccess, OggPacketSequence,
|
||||
PlaceholderTexture2D, SpriteFrames,
|
||||
};
|
||||
use godot::engine::{Image, PckPacker};
|
||||
use godot::engine::{ImageTexture, ProjectSettings};
|
||||
use godot::engine::utilities::printerr;
|
||||
use godot::engine::ImageTexture;
|
||||
use godot::engine::{AudioStreamOggVorbis, DirAccess, OggPacketSequence, Translation};
|
||||
use godot::engine::{ResourceFormatLoader, ResourceSaver};
|
||||
use godot::engine::{ResourceFormatLoaderVirtual, ResourceLoader};
|
||||
use godot::prelude::*;
|
||||
@@ -137,12 +131,12 @@ impl ResourceFormatLoaderVirtual for DatafileLoader {
|
||||
|
||||
fn load(
|
||||
&self,
|
||||
path: GodotString,
|
||||
virtual_path: GodotString,
|
||||
_original_path: GodotString,
|
||||
_use_sub_threads: bool,
|
||||
_cache_mode: i64,
|
||||
) -> Variant {
|
||||
let datafile_path = convert_path(&path);
|
||||
let datafile_path = convert_path(&virtual_path);
|
||||
if let Some(resource) = self.retrieve_cache::<Resource>(format!(
|
||||
"{}.{}",
|
||||
datafile_path,
|
||||
@@ -178,13 +172,25 @@ impl ResourceFormatLoaderVirtual for DatafileLoader {
|
||||
game_object.to_variant()
|
||||
}
|
||||
Ok(DatafileFile::Ui(ui)) => {
|
||||
let ui = convert_ui(ui, None);
|
||||
let full_path = virtual_path.to_string();
|
||||
let (_, _, base_path) = full_path
|
||||
.rsplitn(3, '/')
|
||||
.collect_tuple()
|
||||
.expect("Illegal path for UI");
|
||||
let ui = convert_ui(ui, None, base_path);
|
||||
let mut scene = PackedScene::new();
|
||||
scene.pack(ui);
|
||||
|
||||
self.save_to_cache(scene.share().upcast(), format!("{}.scn", datafile_path));
|
||||
scene.to_variant()
|
||||
}
|
||||
Ok(DatafileFile::Translations(translations)) => {
|
||||
let mut translation = Translation::new();
|
||||
for (key, message) in translations {
|
||||
translation.add_message(key.into(), message.join("\n").into(), "".into());
|
||||
}
|
||||
translation.to_variant()
|
||||
}
|
||||
Ok(DatafileFile::Vorbis(vorbis)) => {
|
||||
let mut audio = AudioStreamOggVorbis::new();
|
||||
audio.set_loop(true);
|
||||
@@ -198,7 +204,7 @@ impl ResourceFormatLoaderVirtual for DatafileLoader {
|
||||
}
|
||||
Ok(DatafileFile::RleSprite(rle)) => load_rle_as_sprite_frames(*rle).to_variant(),
|
||||
Ok(DatafileFile::Sprites(sprites)) => {
|
||||
let sprite_frames = load_sprite_frames(sprites, path);
|
||||
let sprite_frames = load_sprite_frames(sprites, virtual_path);
|
||||
|
||||
self.save_to_cache(
|
||||
sprite_frames.share().upcast(),
|
||||
|
||||
@@ -1,85 +1,143 @@
|
||||
use crate::formats::ui_xml::{HorizontalAlign, UiTag};
|
||||
use godot::builtin::{Array, Dictionary, GodotString, Signal, ToVariant, Vector2};
|
||||
use godot::builtin::{Array, Dictionary, GodotString, ToVariant, Vector2};
|
||||
use godot::engine::control::{LayoutPreset, SizeFlags};
|
||||
use godot::engine::global::HorizontalAlignment;
|
||||
use godot::engine::node::InternalMode;
|
||||
use godot::engine::{Button, Control, Node, TextureRect};
|
||||
use godot::obj::{Gd, Share};
|
||||
use godot::sys::GDEXTENSION_VARIANT_TYPE_STRING;
|
||||
use godot::engine::{load, Button, Control, Label, Node, SpinBox, TextureRect};
|
||||
use godot::obj::{Gd, Inherits, Share};
|
||||
use itertools::Itertools;
|
||||
|
||||
const ACTION_META_NAME: &str = "action";
|
||||
|
||||
pub fn convert_ui(ui: UiTag, owner: Option<Gd<Node>>) -> Gd<Node> {
|
||||
pub fn convert_ui(ui: UiTag, owner: Option<Gd<Node>>, base_path: &str) -> Gd<Node> {
|
||||
match ui {
|
||||
UiTag::Menu(menu) => {
|
||||
let mut gd_menu = Control::new_alloc();
|
||||
let owner_node = owner.unwrap_or_else(|| gd_menu.share().upcast());
|
||||
|
||||
for child in menu.children {
|
||||
let mut child = convert_ui(child, Some(owner_node.share()));
|
||||
gd_menu.add_child(child.share(), false, InternalMode::INTERNAL_MODE_FRONT);
|
||||
child.set_owner(owner_node.share());
|
||||
}
|
||||
gd_menu.set_anchors_preset(LayoutPreset::PRESET_FULL_RECT, false);
|
||||
attach_children(&mut gd_menu, owner, menu.children, base_path);
|
||||
gd_menu.upcast()
|
||||
}
|
||||
UiTag::Image(image) => {
|
||||
let mut gd_image = TextureRect::new_alloc();
|
||||
let texture = load(format!("{}/sprites/{}.bmp", base_path, image.texture));
|
||||
|
||||
gd_image.set_texture(texture);
|
||||
gd_image.set_name(image.texture.into());
|
||||
gd_image.set_position(
|
||||
Vector2 {
|
||||
x: image.position[0] as f32,
|
||||
y: image.position[1] as f32,
|
||||
},
|
||||
false,
|
||||
);
|
||||
gd_image.set_size(
|
||||
Vector2 {
|
||||
x: image.size[0] as f32,
|
||||
y: image.size[1] as f32,
|
||||
},
|
||||
false,
|
||||
);
|
||||
gd_image.set_position(to_vec2(image.position), false);
|
||||
gd_image.set_size(to_vec2(image.size), false);
|
||||
gd_image.upcast()
|
||||
}
|
||||
UiTag::StaticText(text) => {
|
||||
let mut label = Label::new_alloc();
|
||||
label.set_anchors_preset(LayoutPreset::PRESET_TOP_WIDE, false);
|
||||
label.set_position(to_vec2(text.position), false);
|
||||
label.set_horizontal_alignment(text.horizontal_align.into());
|
||||
label.set_text(text.text.into());
|
||||
label.upcast()
|
||||
}
|
||||
UiTag::TextArea(area) => {
|
||||
let mut text_area = Control::new_alloc();
|
||||
text_area.set_anchors_preset(LayoutPreset::PRESET_FULL_RECT, false);
|
||||
text_area.set_position(to_vec2(area.position), false);
|
||||
text_area.set_size(to_vec2(area.size), false);
|
||||
attach_children(&mut text_area, owner, area.children, base_path);
|
||||
text_area.upcast()
|
||||
}
|
||||
UiTag::ToggleButton(toggle) => {
|
||||
let mut spin_box = SpinBox::new_alloc();
|
||||
spin_box.set_position(to_vec2(toggle.position), false);
|
||||
spin_box.set_min(toggle.min_value as f64);
|
||||
spin_box.set_max(toggle.max_value as f64);
|
||||
spin_box.set_step(toggle.value_step as f64);
|
||||
if let Some(name) = toggle.name {
|
||||
spin_box.set_name(GodotString::from(name));
|
||||
}
|
||||
spin_box.set_meta("text".into(), toggle.text.to_variant());
|
||||
spin_box.set_meta("target".into(), toggle.target.to_variant());
|
||||
spin_box.set_meta(
|
||||
"no_sound".into(),
|
||||
toggle.no_sound.unwrap_or(false).to_variant(),
|
||||
);
|
||||
attach_call_meta(&mut spin_box, toggle.on_change);
|
||||
spin_box.upcast()
|
||||
}
|
||||
UiTag::TextButton(button) => {
|
||||
let mut gd_button = Button::new_alloc();
|
||||
gd_button.set_position(
|
||||
Vector2 {
|
||||
x: button.position[0] as f32,
|
||||
y: button.position[1] as f32,
|
||||
},
|
||||
false,
|
||||
);
|
||||
gd_button.set_text_alignment(match button.horizontal_align {
|
||||
HorizontalAlign::Center => HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER,
|
||||
});
|
||||
gd_button.set_anchors_preset(LayoutPreset::PRESET_TOP_WIDE, false);
|
||||
gd_button.set_flat(true);
|
||||
gd_button.set_position(to_vec2(button.position), false);
|
||||
gd_button.set_text_alignment(button.horizontal_align.into());
|
||||
if let Some(name) = button.name {
|
||||
gd_button.set_name(GodotString::from(name));
|
||||
}
|
||||
gd_button.set_text(GodotString::from(button.text));
|
||||
|
||||
let mut call = button.on_select.split_whitespace().collect_vec();
|
||||
if let Some((name,)) = call.drain(..1).collect_tuple() {
|
||||
gd_button.set_meta(
|
||||
ACTION_META_NAME.into(),
|
||||
Dictionary::from([
|
||||
(&"name".to_variant(), &name.to_variant()),
|
||||
(
|
||||
&"args".to_variant(),
|
||||
&Array::from(
|
||||
call.into_iter()
|
||||
.map(GodotString::from)
|
||||
.collect::<Vec<GodotString>>()
|
||||
.as_slice(),
|
||||
)
|
||||
.to_variant(),
|
||||
),
|
||||
])
|
||||
.to_variant(),
|
||||
);
|
||||
}
|
||||
|
||||
attach_call_meta(&mut gd_button, button.on_select);
|
||||
gd_button.upcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<HorizontalAlignment> for HorizontalAlign {
|
||||
fn into(self) -> HorizontalAlignment {
|
||||
match self {
|
||||
HorizontalAlign::Center => HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER,
|
||||
HorizontalAlign::Left => HorizontalAlignment::HORIZONTAL_ALIGNMENT_LEFT,
|
||||
HorizontalAlign::Right => HorizontalAlignment::HORIZONTAL_ALIGNMENT_RIGHT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn attach_children<T>(
|
||||
node: &mut Gd<T>,
|
||||
owner: Option<Gd<Node>>,
|
||||
children: Vec<UiTag>,
|
||||
base_path: &str,
|
||||
) where
|
||||
T: Inherits<Node>,
|
||||
{
|
||||
let owner_node = owner.unwrap_or_else(|| node.share().upcast());
|
||||
|
||||
for child in children {
|
||||
let mut child = convert_ui(child, Some(owner_node.share()), base_path);
|
||||
node.share()
|
||||
.upcast()
|
||||
.add_child(child.share(), false, InternalMode::INTERNAL_MODE_FRONT);
|
||||
child.set_owner(owner_node.share());
|
||||
}
|
||||
}
|
||||
|
||||
fn to_vec2(vec: [i32; 2]) -> Vector2 {
|
||||
Vector2 {
|
||||
x: vec[0] as f32,
|
||||
y: vec[1] as f32,
|
||||
}
|
||||
}
|
||||
|
||||
fn attach_call_meta<T>(button: &mut Gd<T>, call_string: String)
|
||||
where
|
||||
T: Inherits<Node>,
|
||||
{
|
||||
let mut call = call_string.split_whitespace().collect_vec();
|
||||
if call.is_empty() {
|
||||
return;
|
||||
}
|
||||
if let Some((name,)) = call.drain(..1).collect_tuple() {
|
||||
button.share().upcast().set_meta(
|
||||
ACTION_META_NAME.into(),
|
||||
Dictionary::from([
|
||||
(&"name".to_variant(), &name.to_variant()),
|
||||
(
|
||||
&"args".to_variant(),
|
||||
&Array::from(
|
||||
call.into_iter()
|
||||
.map(GodotString::from)
|
||||
.collect::<Vec<GodotString>>()
|
||||
.as_slice(),
|
||||
)
|
||||
.to_variant(),
|
||||
),
|
||||
])
|
||||
.to_variant(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user