From 9bc8c2d694d390296cdbbd3e52e4a982470b5345 Mon Sep 17 00:00:00 2001 From: "Claude (auto-RE)" Date: Wed, 29 Jul 2026 20:33:29 +0200 Subject: [PATCH] viewer: preview reassembled UI screens in the PAK browser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When an opened GP_*.pak RATC entry is a .rat build, compose the screen (ui_layout::compose_build) and show it atop the RATC detail, labelled 'UI screen', with the sprite children below. Reuses the existing pak-open + T8aD-decode path — no new browser/threading. Entry summaries mark UI builds. cargo check -p sylpheed-viewer: clean. --- crates/sylpheed-viewer/src/iso_loader.rs | 17 +++++++++-- crates/sylpheed-viewer/src/ui.rs | 39 +++++++++++++++++++++--- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/crates/sylpheed-viewer/src/iso_loader.rs b/crates/sylpheed-viewer/src/iso_loader.rs index 62fc99a..b4cc159 100644 --- a/crates/sylpheed-viewer/src/iso_loader.rs +++ b/crates/sylpheed-viewer/src/iso_loader.rs @@ -160,8 +160,9 @@ pub enum PakContent { T8ad(ImageRgba), /// An LSTA sprite list — inline T8aD frames. Lsta(Vec), - /// A RATC bundle — its listed children (T8aD children carry a decoded image). - Ratc(Vec), + /// A RATC bundle — its listed children (T8aD children carry a decoded + /// image), plus the reassembled UI screen when the bundle is a `.rat` build. + Ratc(Vec, Option), /// Plain-text / XML payload, with its encoding label. Text { text: String, encoding: String }, } @@ -192,6 +193,13 @@ impl ImageRgba { rgba: img.rgba, } } + fn from_composed(s: sylpheed_formats::ui_layout::ComposedScreen) -> Self { + Self { + width: s.width, + height: s.height, + rgba: s.rgba, + } + } fn pixels(&self) -> usize { (self.width as usize) * (self.height as usize) } @@ -1469,7 +1477,10 @@ fn classify_content(payload: &[u8]) -> PakContent { }) .collect(); if !entries.is_empty() { - return PakContent::Ratc(entries); + // If this bundle is a `.rat` UI build, reassemble the screen. + let ui_screen = sylpheed_formats::ui_layout::compose_build(payload, false) + .map(ImageRgba::from_composed); + return PakContent::Ratc(entries, ui_screen); } } } diff --git a/crates/sylpheed-viewer/src/ui.rs b/crates/sylpheed-viewer/src/ui.rs index bc77a4d..955d4f2 100644 --- a/crates/sylpheed-viewer/src/ui.rs +++ b/crates/sylpheed-viewer/src/ui.rs @@ -672,7 +672,9 @@ fn draw_pak_browser(ui: &mut egui::Ui, pak: &mut PakView) { PakContent::Png(img) => draw_png_detail(ui, row, img, img_tex), PakContent::T8ad(img) => draw_t8ad_detail(ui, row, img, img_tex), PakContent::Lsta(frames) => draw_lsta_detail(ui, row, frames, img_tex), - PakContent::Ratc(children) => draw_ratc_detail(ui, row, children, img_tex), + PakContent::Ratc(children, ui_screen) => { + draw_ratc_detail(ui, row, children, ui_screen.as_ref(), img_tex) + } PakContent::Text { text, encoding } => { draw_text_detail(ui, row, text, encoding) } @@ -699,7 +701,10 @@ fn row_kind(row: &crate::iso_loader::PakRow) -> String { PakContent::Png(img) => format!("PNG {}×{}", img.width, img.height), PakContent::T8ad(img) => format!("T8aD {}×{}", img.width, img.height), PakContent::Lsta(f) => format!("LSTA · {} sprite(s)", f.len()), - PakContent::Ratc(c) => format!("RATC · {} item(s)", c.len()), + PakContent::Ratc(c, screen) => { + let s = if screen.is_some() { " · UI screen" } else { "" }; + format!("RATC · {} item(s){s}", c.len()) + } PakContent::Text { .. } => "text".into(), PakContent::None => row.identity.clone(), } @@ -939,21 +944,47 @@ fn draw_ratc_detail( ui: &mut egui::Ui, row: &crate::iso_loader::PakRow, children: &[crate::iso_loader::RatcEntry], + ui_screen: Option<&ImageRgba>, img_tex: &mut ImgCache, ) { ui.horizontal(|ui| { ui.heading("RATC bundle"); ui.separator(); ui.label(format!("{} item(s)", children.len())); + if ui_screen.is_some() { + ui.separator(); + ui.strong("🖼 UI screen"); + } ui.separator(); ui.weak("colours unverified"); }); ui.separator(); - let refs: Vec<&ImageRgba> = children.iter().filter_map(|c| c.image.as_ref()).collect(); + // Cache the reassembled screen (if any) as texture 0, then child thumbnails. + let mut refs: Vec<&ImageRgba> = Vec::new(); + if let Some(s) = ui_screen { + refs.push(s); + } + let child_base = refs.len(); + refs.extend(children.iter().filter_map(|c| c.image.as_ref())); ensure_textures(ui, row.hash, &refs, img_tex); - let mut img_i = 0; + // The reassembled screen, scaled to fit the panel width. + if ui_screen.is_some() { + if let Some(tex) = img_tex.as_ref().and_then(|(_, t)| t.first()) { + ui.label("Reassembled from this screen's .rat layout records:"); + let sz = tex.size_vec2(); + let scale = (ui.available_width() / sz.x.max(1.0)).min(1.0); + ui.add(egui::Image::new(egui::load::SizedTexture::new( + tex.id(), + [sz.x * scale, sz.y * scale], + ))); + ui.weak("Frame/glow decorations (no .rat) and the live 3D background are omitted."); + ui.separator(); + } + } + + let mut img_i = child_base; for c in children { ui.horizontal(|ui| { if c.image.is_some() {