dcb/dcb-tools/dcb-file-editor/src/preview_panel.rs

336 lines
8.3 KiB
Rust

//! Preview panel
// Imports
use crate::GameFile;
use anyhow::Context;
use dcb_io::game_file::Path;
use dcb_pak::PakFileReader;
use dcb_tim::{Tim, Tis};
use dcb_util::task::{self, ValueFuture};
use eframe::{
egui::{self, Color32, TextureId},
epi::TextureAllocator,
};
use std::{io::BufReader, sync::Arc};
/// Preview panel
#[derive(PartialEq, Clone)]
pub enum PreviewPanel {
/// Tim image
Tim(TimDisplay),
/// Tim collection
Tis {
/// All tims
tims: Vec<TimDisplay>,
/// Current tim
cur_tim: usize,
},
/// Pak
Pak { paks: Vec<String> },
/// Error
Error {
/// Error
err: String,
},
/// Empty
Empty,
}
impl PreviewPanel {
/// Drops all textures in this panel
pub fn drop_textures(&mut self, tex_allocator: &mut dyn TextureAllocator) {
match self {
PreviewPanel::Tim(tim) => {
for texture_id in tim.pallettes.drain(..) {
tex_allocator.free(texture_id);
}
},
PreviewPanel::Tis { tims, .. } => {
for tim in tims {
for texture_id in tim.pallettes.drain(..) {
tex_allocator.free(texture_id);
}
}
},
_ => (),
}
}
/// Forgets all textures in this panel without freeing them
pub fn forget_textures(&mut self) {
match self {
PreviewPanel::Tim(tim) => {
tim.pallettes.drain(..);
},
PreviewPanel::Tis { tims, .. } => {
tims.drain(..);
},
_ => (),
}
}
/// Displays this panel
pub fn display(&mut self, ctx: &egui::CtxRef) {
egui::CentralPanel::default().show(ctx, |ui| match self {
Self::Tim(tim) => {
tim.display(ctx, ui);
},
Self::Tis { tims, cur_tim } => {
egui::ScrollArea::auto_sized().show(ui, |ui| {
egui::TopBottomPanel::bottom(tims as *const _).show(ctx, |ui| {
ui.label("Image");
ui.horizontal_wrapped(|ui| {
for n in 0..tims.len() {
if ui.selectable_label(*cur_tim == n, format!("{n}")).clicked() {
*cur_tim = n;
}
}
});
});
tims[*cur_tim].display(ctx, ui);
});
},
Self::Pak { ref paks } => {
for pak in paks {
ui.label(pak);
ui.separator();
}
},
Self::Error { ref err } => {
ui.group(|ui| {
ui.heading("Error");
ui.label(err);
});
},
Self::Empty => (),
});
}
}
impl Drop for PreviewPanel {
fn drop(&mut self) {
// Make sure we don't have any textures remaining
let textures_len = match self {
PreviewPanel::Tim(tim) => tim.pallettes.len(),
PreviewPanel::Tis { tims, .. } => tims.iter().map(|tim| tim.pallettes.len()).sum(),
_ => 0,
};
if textures_len != 0 {
log::warn!("Leaking {} textures", textures_len);
}
}
}
/// Preview panel builder
pub enum PreviewPanelBuilder {
/// Tim image
Tim {
tim: Tim,
pallette_pixels: Vec<Box<[Color32]>>,
},
/// Tis images
Tis { tims: Vec<(Tim, Vec<Box<[Color32]>>)> },
/// Pak
Pak { paks: Vec<String> },
/// Error
Error { err: String },
/// Empty
Empty,
}
impl PreviewPanelBuilder {
/// Creates a new builder for a preview Panel
pub fn new(game_file: Arc<GameFile>, path: String) -> ValueFuture<Self> {
task::spawn(move || {
let res: Result<_, anyhow::Error> = try {
match path {
path if path.ends_with(".TIM") => {
// Deserialize the tim
let path = Path::from_ascii(&path).context("Unable to create path")?;
let mut game_file = game_file.game_file();
let mut file = game_file.open_file(path).context("Unable to open file")?;
let tim = Tim::deserialize(&mut file).context("Unable to parse file")?;
let pallette_pixels: Vec<Box<[_]>> = (0..tim.pallettes())
.map(|pallette| {
let pixels = tim
.colors(Some(pallette))
.context("Unable to get image colors")?
.to_vec()
.into_iter()
.map(|[r, g, b, a]| Color32::from_rgba_premultiplied(r, g, b, a))
.collect();
Ok(pixels)
})
.collect::<Result<_, anyhow::Error>>()
.context("Unable to get all pallette colors")?;
Self::Tim { tim, pallette_pixels }
},
path if path.ends_with(".TIS") => {
// Deserialize the tis
let path = Path::from_ascii(&path).context("Unable to create path")?;
let mut game_file = game_file.game_file();
let file = game_file.open_file(path).context("Unable to open file")?;
let mut file = BufReader::new(file);
let tis: Tis = Tis::deserialize(&mut file).context("Unable to parse file")?;
let tims = tis
.tims
.into_iter()
.map(|tim| {
let pallette_pixels: Vec<Box<[_]>> = (0..tim.pallettes())
.map(|pallette| {
let pixels = tim
.colors(Some(pallette))
.context("Unable to get image colors")?
.to_vec()
.into_iter()
.map(|[r, g, b, a]| Color32::from_rgba_premultiplied(r, g, b, a))
.collect();
Ok(pixels)
})
.collect::<Result<_, anyhow::Error>>()
.context("Unable to get all pallette colors")?;
Ok((tim, pallette_pixels))
})
.collect::<Result<_, anyhow::Error>>()
.context("Unable to create images")?;
Self::Tis { tims }
},
path if path.ends_with(".PAK") => {
// Deserialize the pak
let path = Path::from_ascii(&path).context("Unable to create path")?;
let mut game_file = game_file.game_file();
let file = game_file.open_file(path).context("Unable to open file")?;
let mut file = BufReader::new(file);
let mut pak = PakFileReader::new(&mut file);
let paks = (0..)
.map_while(move |idx| {
use dcb_pak::header::Kind;
let entry = match pak.next_entry() {
Ok(Some(entry)) => entry,
Ok(None) => return None,
Err(err) => return Some(Err(err)),
};
let header = entry.header();
let extension = match header.kind {
Kind::Model3DSet => "m3s",
Kind::Unknown1 => "un1",
Kind::GameScript => "msd",
Kind::Animation2D => "a2d",
Kind::Unknown2 => "un2",
Kind::FileContents => "bin",
Kind::AudioSeq => "seq",
Kind::AudioVh => "vh",
Kind::AudioVb => "vb",
};
Some(Ok(format!("{}.{}", idx, extension)))
})
.collect::<Result<Vec<_>, _>>()
.context("Unable to read all entries")?;
Self::Pak { paks }
},
_ => Self::Empty,
}
};
res.unwrap_or_else(|err| Self::Error {
err: format!("{err:?}"),
})
})
}
/// Builds the preview panel
pub fn build(self, tex_allocator: &mut dyn TextureAllocator) -> PreviewPanel {
let res: Result<_, anyhow::Error> = try {
match self {
Self::Tim { tim, pallette_pixels } => PreviewPanel::Tim(TimDisplay {
pallettes: pallette_pixels
.into_iter()
.map(|pixels| {
let [width, height] = tim.size();
tex_allocator.alloc_srgba_premultiplied((width, height), &*pixels)
})
.collect(),
tim,
cur_pallette: 0,
}),
Self::Tis { tims } => PreviewPanel::Tis {
tims: tims
.into_iter()
.map(|(tim, pallette_pixels)| {
let [width, height] = tim.size();
Ok(TimDisplay {
tim,
pallettes: pallette_pixels
.into_iter()
.map(|pixels| tex_allocator.alloc_srgba_premultiplied((width, height), &*pixels))
.collect(),
cur_pallette: 0,
})
})
.collect::<Result<Vec<_>, anyhow::Error>>()?,
cur_tim: 0,
},
Self::Pak { paks } => PreviewPanel::Pak { paks },
Self::Error { err } => PreviewPanel::Error { err },
Self::Empty => PreviewPanel::Empty,
}
};
res.unwrap_or_else(|err| PreviewPanel::Error {
err: format!("{err:?}"),
})
}
}
/// Tim display
#[derive(PartialEq, Clone)]
pub struct TimDisplay {
/// Image
tim: Tim,
/// All textures
pallettes: Vec<TextureId>,
/// Current pallette
cur_pallette: usize,
}
impl TimDisplay {
/// Displays a tim along with it's pallettes
fn display(&mut self, ctx: &egui::CtxRef, ui: &mut egui::Ui) {
egui::TopBottomPanel::bottom(&self.tim as *const _).show(ctx, |ui| {
ui.label("Pallette");
ui.horizontal_wrapped(|ui| {
for n in 0..self.pallettes.len() {
if ui.selectable_label(self.cur_pallette == n, format!("{n}")).clicked() {
self.cur_pallette = n;
}
}
});
});
egui::ScrollArea::auto_sized().show(ui, |ui| {
ui.image(self.pallettes[self.cur_pallette], self.tim.size().map(|dim| dim as f32));
});
}
}