mirror of
https://github.com/G2-Games/lbee-utils.git
synced 2025-04-19 07:12:55 -05:00
Made experimental UI
This commit is contained in:
parent
3cb07bc74d
commit
f6374eecfc
5 changed files with 223 additions and 26 deletions
|
@ -10,6 +10,10 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
cz = { path = "../cz/", features = ["png"] }
|
||||
eframe = { version = "0.28.1", default-features = false, features = ["wayland", "x11", "accesskit", "default_fonts", "wgpu"] }
|
||||
egui_extras = "0.28.1"
|
||||
luca_pak = { path = "../luca_pak/" }
|
||||
rfd = "0.14.1"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
@ -1,10 +1,157 @@
|
|||
use std::time::Instant;
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
fn main() {
|
||||
let mut cz_file = cz::open("test_file.cz3").unwrap();
|
||||
//cz_file.save_as_png("test.png").unwrap();
|
||||
use std::path::PathBuf;
|
||||
|
||||
cz_file.header_mut().set_version(4).unwrap();
|
||||
use eframe::{egui::{self, text::{LayoutJob, TextWrapping}, ColorImage, Image, Rgba, TextBuffer, TextureFilter, TextureHandle, TextureOptions}, epaint::Fonts};
|
||||
use luca_pak::{entry::EntryType, header, Pak};
|
||||
|
||||
cz_file.save_as_cz("test_file.cz4").unwrap();
|
||||
fn main() -> eframe::Result {
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default().with_inner_size([1024.0, 800.0]),
|
||||
follow_system_theme: true,
|
||||
..Default::default()
|
||||
};
|
||||
eframe::run_native(
|
||||
"LUCA PAK Explorer",
|
||||
options,
|
||||
Box::new(|cc| {
|
||||
// This gives us image support:
|
||||
egui_extras::install_image_loaders(&cc.egui_ctx);
|
||||
|
||||
Ok(Box::<PakExplorer>::default())
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
struct PakExplorer {
|
||||
open_file: Option<Pak>,
|
||||
selected_entry: Option<luca_pak::entry::Entry>,
|
||||
image_texture: Option<egui::TextureHandle>,
|
||||
hex_string: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl Default for PakExplorer {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
open_file: None,
|
||||
selected_entry: None,
|
||||
image_texture: None,
|
||||
hex_string: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for PakExplorer {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ctx.set_pixels_per_point(1.5);
|
||||
ui.heading("PAK File Explorer");
|
||||
|
||||
if ui.button("Open file…").clicked() {
|
||||
if let Some(path) = rfd::FileDialog::new().pick_file() {
|
||||
let pak = Pak::open(&path).unwrap();
|
||||
self.open_file = Some(pak);
|
||||
self.selected_entry = None;
|
||||
self.image_texture = None;
|
||||
self.hex_string = None;
|
||||
}
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
if let Some(pak) = &self.open_file {
|
||||
ui.label(format!("Opened {}", pak.path().file_name().unwrap().to_string_lossy()));
|
||||
ui.label(format!("Contains {} Entries", pak.entries().len()));
|
||||
|
||||
let selection = if let Some(entry) = &self.selected_entry {
|
||||
entry.display_name()
|
||||
} else {
|
||||
"None".to_string()
|
||||
};
|
||||
|
||||
egui::ComboBox::from_id_source("my-combobox")
|
||||
.selected_text(selection)
|
||||
.truncate()
|
||||
.show_ui(ui, |ui|
|
||||
{
|
||||
ui.selectable_value(&mut self.selected_entry, None, "");
|
||||
for entry in pak.entries() {
|
||||
if ui.selectable_value(
|
||||
&mut self.selected_entry,
|
||||
Some(entry.clone()),
|
||||
entry.display_name(),
|
||||
).clicked() {
|
||||
self.image_texture = None;
|
||||
};
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ui.centered_and_justified(|ui|
|
||||
ui.label("No File Opened")
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(entry) = &self.selected_entry {
|
||||
if ui.button("Save entry…").clicked() {
|
||||
if let Some(path) = rfd::FileDialog::new()
|
||||
.set_file_name(entry.display_name())
|
||||
.save_file()
|
||||
{
|
||||
entry.save(&path).unwrap();
|
||||
}
|
||||
}
|
||||
match entry.file_type() {
|
||||
EntryType::CZ0 | EntryType::CZ1
|
||||
| EntryType::CZ2 | EntryType::CZ3
|
||||
| EntryType::CZ4 | EntryType::CZ5 =>
|
||||
{
|
||||
if ui.button("Save as PNG…").clicked() {
|
||||
let mut display_name = entry.display_name();
|
||||
display_name.push_str(".png");
|
||||
if let Some(path) = rfd::FileDialog::new()
|
||||
.set_file_name(display_name)
|
||||
.save_file()
|
||||
{
|
||||
let cz = cz::DynamicCz::decode(&mut std::io::Cursor::new(entry.as_bytes())).unwrap();
|
||||
cz.save_as_png(&path).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
let texture: &TextureHandle = self.image_texture.get_or_insert_with(|| {
|
||||
let cz = cz::DynamicCz::decode(&mut std::io::Cursor::new(entry.as_bytes())).unwrap();
|
||||
let image = ColorImage::from_rgba_unmultiplied(
|
||||
[cz.header().width() as usize, cz.header().height() as usize],
|
||||
cz.as_raw()
|
||||
);
|
||||
ui.ctx().load_texture("eventframe", image, TextureOptions {
|
||||
magnification: TextureFilter::Nearest,
|
||||
minification: TextureFilter::Linear,
|
||||
..Default::default()
|
||||
})
|
||||
});
|
||||
|
||||
ui.centered_and_justified(|ui|
|
||||
ui.add(
|
||||
Image::from_texture(texture)
|
||||
.show_loading_spinner(true)
|
||||
.shrink_to_fit()
|
||||
.rounding(2.0)
|
||||
)
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
ui.centered_and_justified(|ui|
|
||||
ui.label("No Preview Available")
|
||||
);
|
||||
},
|
||||
}
|
||||
} else {
|
||||
ui.centered_and_justified(|ui|
|
||||
ui.label("Select an Entry")
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
use std::{
|
||||
error::Error,
|
||||
fs::File,
|
||||
io::{BufWriter, Write},
|
||||
path::Path,
|
||||
error::Error, fmt, fs::File, io::{BufWriter, Write}, path::Path
|
||||
};
|
||||
|
||||
/// A single file entry in a PAK file
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Entry {
|
||||
pub(super) index: usize,
|
||||
|
||||
|
@ -42,18 +39,6 @@ impl Entry {
|
|||
|
||||
/// Save an [`Entry`] as its underlying data to a file
|
||||
pub fn save<P: ?Sized + AsRef<Path>>(&self, path: &P) -> Result<(), Box<dyn Error>> {
|
||||
let mut path = path.as_ref().to_path_buf();
|
||||
if !path.is_dir() {
|
||||
return Err("Path must be a directory".into());
|
||||
}
|
||||
|
||||
// Save the file to <folder> + <file name>
|
||||
if let Some(name) = &self.name {
|
||||
path.push(name);
|
||||
} else {
|
||||
path.push(&self.id.to_string())
|
||||
}
|
||||
|
||||
let mut out_file = BufWriter::new(File::create(path)?);
|
||||
|
||||
out_file.write_all(&self.data)?;
|
||||
|
@ -70,4 +55,63 @@ impl Entry {
|
|||
pub fn as_bytes(&self) -> &Vec<u8> {
|
||||
&self.data
|
||||
}
|
||||
|
||||
pub fn display_name(&self) -> String {
|
||||
let mut name = self.name().clone().unwrap_or(self.id().to_string());
|
||||
let entry_type = self.file_type();
|
||||
name.push_str(&entry_type.extension());
|
||||
|
||||
name
|
||||
}
|
||||
|
||||
pub fn file_type(&self) -> EntryType {
|
||||
if self.data[0..2] == [b'C', b'Z'] {
|
||||
match self.data[2] {
|
||||
b'0' => EntryType::CZ0,
|
||||
b'1' => EntryType::CZ1,
|
||||
b'2' => EntryType::CZ2,
|
||||
b'3' => EntryType::CZ3,
|
||||
b'4' => EntryType::CZ4,
|
||||
b'5' => EntryType::CZ5,
|
||||
_ => EntryType::Unknown,
|
||||
}
|
||||
} else if self.data[0..3] == [b'M', b'V', b'T'] {
|
||||
EntryType::MVT
|
||||
} else {
|
||||
EntryType::Unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum EntryType {
|
||||
CZ0,
|
||||
CZ1,
|
||||
CZ2,
|
||||
CZ3,
|
||||
CZ4,
|
||||
CZ5,
|
||||
|
||||
/// An MVT video file
|
||||
MVT,
|
||||
|
||||
/// Who knows!
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl EntryType {
|
||||
/// Get the file extension for the file
|
||||
pub fn extension(&self) -> &'static str {
|
||||
match self {
|
||||
Self::CZ0 => ".cz0",
|
||||
Self::CZ1 => ".cz1",
|
||||
Self::CZ2 => ".cz2",
|
||||
Self::CZ3 => ".cz3",
|
||||
Self::CZ4 => ".cz4",
|
||||
Self::CZ5 => ".cz5",
|
||||
Self::MVT => ".mvt",
|
||||
Self::Unknown => "",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
mod entry;
|
||||
mod header;
|
||||
pub mod entry;
|
||||
pub mod header;
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use header::Header;
|
||||
|
|
|
@ -68,7 +68,9 @@ fn main() {
|
|||
}
|
||||
|
||||
for entry in pak.entries() {
|
||||
entry.save(&output).unwrap();
|
||||
let mut outpath = output.clone();
|
||||
outpath.push(entry.display_name());
|
||||
entry.save(&outpath).unwrap();
|
||||
}
|
||||
},
|
||||
Commands::Replace { batch, name, id, replacement, output } => {
|
||||
|
|
Loading…
Reference in a new issue