Texture view updates

This commit is contained in:
Luke Street
2023-02-19 02:52:14 -05:00
parent f7f5d9d8a5
commit 52f98c35a8
6 changed files with 280 additions and 91 deletions
+9 -1
View File
@@ -70,10 +70,18 @@ unlicensed = "deny"
# See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
allow = [
"MIT",
"Apache-2.0",
"BSD-2-Clause",
"BSD-3-Clause",
"BSL-1.0",
"CC0-1.0",
"ISC",
"LicenseRef-UFL-1.0",
"MIT",
"MIT-0",
"OFL-1.1",
"Unicode-DFS-2016",
"Zlib",
]
# List of explictly disallowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
+6
View File
@@ -1,7 +1,13 @@
[package]
name = "retrolib"
description = "Tools for working with Retro game formats."
authors = ["Luke Street <luke@street.dev>"]
license = "MIT OR Apache-2.0"
version = "0.1.0"
edition = "2021"
publish = false
repository = "https://github.com/PrimeDecomp/retrotool"
readme = "README.md"
[dependencies]
anyhow = "1.0.69"
+1 -1
View File
@@ -774,7 +774,7 @@ pub struct ModelData<'a> {
impl ModelData<'_> {
pub fn slice<'a>(data: &'a [u8], meta: &[u8], e: Endian) -> Result<ModelData<'a>> {
let (cmdl_desc, mut cmdl_data, _) = FormDescriptor::slice(&data, Endian::Little)?;
let (cmdl_desc, mut cmdl_data, _) = FormDescriptor::slice(data, Endian::Little)?;
ensure!(cmdl_desc.id == K_FORM_CMDL);
ensure!(cmdl_desc.version_a == 114);
ensure!(cmdl_desc.version_b == 125);
+31 -6
View File
@@ -19,7 +19,7 @@ pub const K_CHUNK_GPU: FourCC = FourCC(*b"GPU ");
#[binrw]
#[repr(u32)]
#[brw(repr(u32))]
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ETextureType {
_1D = 0,
_2D = 1,
@@ -35,7 +35,7 @@ pub enum ETextureType {
#[binrw]
#[repr(u8)]
#[brw(repr(u8))]
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ETextureWrap {
ClampToEdge = 0,
Repeat = 1,
@@ -48,7 +48,7 @@ pub enum ETextureWrap {
#[binrw]
#[repr(u8)]
#[brw(repr(u8))]
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ETextureFilter {
Nearest = 0,
Linear = 1,
@@ -57,7 +57,7 @@ pub enum ETextureFilter {
#[binrw]
#[repr(u8)]
#[brw(repr(u8))]
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ETextureMipFilter {
Nearest = 0,
Linear = 1,
@@ -66,7 +66,7 @@ pub enum ETextureMipFilter {
#[binrw]
#[repr(u8)]
#[brw(repr(u8))]
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ETextureAnisotropicRatio {
None = u8::MAX,
_1 = 0,
@@ -145,7 +145,7 @@ pub struct STextureMetaData {
#[binrw]
#[repr(u32)]
#[brw(repr(u32))]
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ETextureFormat {
R8Unorm = 0,
R8Snorm = 1,
@@ -303,6 +303,31 @@ impl ETextureFormat {
)
}
pub fn is_srgb(self) -> bool {
matches!(
self,
ETextureFormat::Rgba8Srgb
| ETextureFormat::RgbaBc1Srgb
| ETextureFormat::RgbaBc2Srgb
| ETextureFormat::RgbaBc3Srgb
| ETextureFormat::RgbaAstc4x4Srgb
| ETextureFormat::RgbaAstc5x4Srgb
| ETextureFormat::RgbaAstc5x5Srgb
| ETextureFormat::RgbaAstc6x5Srgb
| ETextureFormat::RgbaAstc6x6Srgb
| ETextureFormat::RgbaAstc8x5Srgb
| ETextureFormat::RgbaAstc8x6Srgb
| ETextureFormat::RgbaAstc8x8Srgb
| ETextureFormat::RgbaAstc10x5Srgb
| ETextureFormat::RgbaAstc10x6Srgb
| ETextureFormat::RgbaAstc10x8Srgb
| ETextureFormat::RgbaAstc10x10Srgb
| ETextureFormat::RgbaAstc12x10Srgb
| ETextureFormat::RgbaAstc12x12Srgb
| ETextureFormat::BptcUnormSrgb
)
}
pub fn bytes_per_pixel(self) -> u32 {
match self {
ETextureFormat::R8Unorm
+9 -9
View File
@@ -15,7 +15,7 @@ use bevy::{
prelude::*,
};
use binrw::Endian;
use image::{ImageBuffer, RgbaImage};
use image::{ RgbaImage};
use retrolib::{
format::{
foot::locate_meta,
@@ -54,7 +54,7 @@ impl AssetIo for RetroAssetIo {
if let Some(id) =
path.file_stem().and_then(|name| Uuid::try_parse(&name.to_string_lossy()).ok())
{
// Load pak header only
// Find pak for UUID and load asset
Box::pin(async move {
let mut package_path: Option<PathBuf> = None;
if let Ok(packages) = self.packages.packages.read() {
@@ -182,20 +182,20 @@ impl AssetLoader for PackageAssetLoader {
#[derive(Debug, Clone, bevy::reflect::TypeUuid)]
#[uuid = "83269869-1209-408e-8835-bc6f2496e828"]
pub struct TxtrData {
pub data: TextureData,
pub struct TextureAsset {
pub inner: TextureData,
pub rgba: Option<Vec<u8>>,
}
pub struct TxtrAssetLoader;
pub struct TextureAssetLoader;
impl Plugin for TxtrAssetLoader {
impl Plugin for TextureAssetLoader {
fn build(&self, app: &mut App) {
app.add_asset::<TxtrData>().add_asset_loader(TxtrAssetLoader);
app.add_asset::<TextureAsset>().add_asset_loader(TextureAssetLoader);
}
}
impl AssetLoader for TxtrAssetLoader {
impl AssetLoader for TextureAssetLoader {
fn load<'a>(
&'a self,
bytes: &'a [u8],
@@ -220,7 +220,7 @@ impl AssetLoader for TxtrAssetLoader {
rgba = Some(image.into_raw());
}
println!("Loaded texture {:?}", data.head);
load_context.set_default_asset(LoadedAsset::new(TxtrData { data, rgba }));
load_context.set_default_asset(LoadedAsset::new(TextureAsset { inner: data, rgba }));
Ok(())
})
}
+224 -74
View File
@@ -1,12 +1,12 @@
mod loaders;
use std::fs::FileType;
use bevy::{
asset::LoadState,
prelude::*,
render::render_resource::{
Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
render::{
render_resource::{
Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
},
},
};
use bevy_egui::{
@@ -15,29 +15,18 @@ use bevy_egui::{
EguiContext, EguiPlugin, EguiSettings,
};
use egui::TextureId;
use retrolib::format::txtr::K_FORM_TXTR;
use retrolib::format::{
txtr::{ETextureFormat, ETextureType, K_FORM_TXTR},
FourCC,
};
use uuid::Uuid;
use walkdir::{DirEntry, WalkDir};
use crate::loaders::{
package_loader_system, PackageAssetLoader, PackageDirectory, RetroAssetIoPlugin,
TxtrAssetLoader, TxtrData,
package_loader_system, PackageAssetLoader, PackageDirectory, RetroAssetIoPlugin, TextureAsset,
TextureAssetLoader,
};
struct Images {
bevy_icon: Handle<Image>,
bevy_icon_inverted: Handle<Image>,
}
impl FromWorld for Images {
fn from_world(world: &mut World) -> Self {
let asset_server = world.get_resource_mut::<AssetServer>().unwrap();
Self {
bevy_icon: asset_server.load("icon.png"),
bevy_icon_inverted: asset_server.load("icon_inverted.png"),
}
}
}
/// This example demonstrates the following functionality and use-cases of bevy_egui:
/// - rendering loaded assets;
/// - toggling hidpi scaling (by pressing '/' button);
@@ -46,26 +35,17 @@ fn main() {
App::new()
.insert_resource(ClearColor(Color::rgb(0.0, 0.0, 0.0)))
.insert_resource(Msaa { samples: 1 })
.insert_resource(bevy::render::settings::WgpuSettings {
features: bevy::render::settings::WgpuFeatures::TEXTURE_COMPRESSION_BC,
..Default::default()
})
.init_resource::<UiState>()
.init_resource::<Packages>()
.init_resource::<Textures>()
.init_resource::<OpenAsset>()
.add_plugins(
DefaultPlugins
.build()
// the custom asset io plugin must be inserted in-between the
// `CorePlugin' and `AssetPlugin`. It needs to be after the
// CorePlugin, so that the IO task pool has already been constructed.
// And it must be before the `AssetPlugin` so that the asset plugin
// doesn't create another instance of an asset server. In general,
// the AssetPlugin should still run so that other aspects of the
// asset system are initialized correctly.
.add_before::<AssetPlugin, _>(RetroAssetIoPlugin),
)
.add_plugins(DefaultPlugins.build().add_before::<AssetPlugin, _>(RetroAssetIoPlugin))
.add_plugin(PackageAssetLoader)
.add_plugin(TxtrAssetLoader)
.add_plugin(TextureAssetLoader)
.add_plugin(EguiPlugin)
.add_startup_system(configure_visuals)
.add_startup_system(preload_package)
.add_system(update_ui_scale_factor)
.add_system(ui_example)
@@ -74,9 +54,15 @@ fn main() {
.run();
}
#[derive(Debug, Clone, Default, Eq, PartialEq)]
struct AssetRef {
id: Uuid,
kind: FourCC,
}
enum TabType {
Directory,
Asset(HandleUntyped, Option<TextureId>),
Texture(AssetRef, Handle<TextureAsset>, Vec<TextureId>),
Empty,
}
@@ -96,8 +82,9 @@ impl Default for UiState {
struct TabViewer<'a> {
server: Res<'a, AssetServer>,
packages: Res<'a, Assets<PackageDirectory>>,
textures: Res<'a, Assets<TxtrData>>,
textures: Res<'a, Assets<TextureAsset>>,
open_asset: ResMut<'a, OpenAsset>,
tab_assets: Vec<AssetRef>,
}
impl egui_dock::TabViewer for TabViewer<'_> {
@@ -106,7 +93,10 @@ impl egui_dock::TabViewer for TabViewer<'_> {
fn ui(&mut self, ui: &mut egui::Ui, tab: &mut Self::Tab) {
match tab {
TabType::Directory => {
for (_, package) in self.packages.iter() {
let mut packages_sorted =
self.packages.iter().map(|(_, p)| p).collect::<Vec<&PackageDirectory>>();
packages_sorted.sort_by_key(|p| &p.name);
for package in packages_sorted {
egui::CollapsingHeader::new(&package.name).show(ui, |ui| {
for entry in &package.entries {
let mut job = LayoutJob::simple(
@@ -122,30 +112,71 @@ impl egui_dock::TabViewer for TabViewer<'_> {
TextFormat::simple(FontId::monospace(12.0), Color32::WHITE),
);
}
if egui::SelectableLabel::new(false, job).ui(ui).clicked() {
self.open_asset.0 = match entry.kind {
let asset_ref = AssetRef { id: entry.id, kind: entry.kind };
if egui::SelectableLabel::new(self.tab_assets.contains(&asset_ref), job)
.ui(ui)
.clicked()
{
*self.open_asset = OpenAsset(asset_ref, match entry.kind {
K_FORM_TXTR => Some(
self.server
.load::<TxtrData, _>(format!(
.load::<TextureAsset, _>(format!(
"{}.{}",
entry.id, entry.kind
))
.into(),
),
_ => None,
};
});
}
}
});
}
}
TabType::Asset(handle, image) => {
if let Some(txtr) = self.textures.get(&handle.typed_weak::<TxtrData>()) {
if let Some(image) = image {
ui.add(egui::widgets::Image::new(*image, egui::Vec2 {
x: txtr.data.head.width as f32,
y: txtr.data.head.height as f32,
}));
TabType::Texture(asset_ref, handle, images) => {
ui.label(format!("{} {}", asset_ref.kind, asset_ref.id));
if let Some(txtr) = self.textures.get(handle) {
ui.label(format!("Type: {:?}", txtr.inner.head.kind));
ui.label(format!("Format: {:?}", txtr.inner.head.format));
ui.label(format!(
"Dimensions: {}x{}x{} (mips: {})",
txtr.inner.head.width,
txtr.inner.head.height,
txtr.inner.head.layers,
txtr.inner.head.mip_sizes.len()
));
if txtr.inner.head.kind == ETextureType::Cube && images.len() == 6 {
let width = txtr.inner.head.width * 2;
let height = txtr.inner.head.height * 2;
let (_, rect) = ui.allocate_space(egui::Vec2 {
x: (width * 4) as f32,
y: (height * 3) as f32,
});
let size = egui::Vec2 { x: width as f32, y: height as f32 };
let mut draw_image = |i: usize, x: u32, y: u32| {
let min = egui::Vec2 { x: (width * x) as f32, y: (height * y) as f32 };
let max = egui::Vec2 {
x: (width * (x + 1)) as f32,
y: (height * (y + 1)) as f32,
};
egui::widgets::Image::new(images[i], size).paint_at(ui, egui::Rect {
min: rect.min + min,
max: rect.min + max,
});
};
draw_image(2, 1, 0);
draw_image(1, 0, 1);
draw_image(4, 1, 1);
draw_image(0, 2, 1);
draw_image(5, 3, 1);
draw_image(3, 1, 2);
} else {
for image in images {
ui.add(egui::widgets::Image::new(*image, egui::Vec2 {
x: txtr.inner.head.width as f32,
y: txtr.inner.head.height as f32,
}));
}
}
}
}
@@ -156,22 +187,16 @@ impl egui_dock::TabViewer for TabViewer<'_> {
fn title(&mut self, tab: &mut Self::Tab) -> egui::WidgetText {
match tab {
TabType::Directory => "Directory".into(),
TabType::Asset(_, _) => "Asset view".into(),
TabType::Texture(asset_ref, _, _) => {
format!("{} {}", asset_ref.kind, asset_ref.id).into()
}
TabType::Empty => "Placeholder".into(),
}
}
}
fn configure_visuals(mut egui_ctx: ResMut<EguiContext>) {
// egui_ctx
// .ctx_mut()
// .set_visuals(egui::Visuals { window_rounding: 0.0.into(), ..Default::default() });
}
#[derive(Default, Resource)]
struct Packages(Vec<Handle<PackageDirectory>>);
#[derive(Default, Resource)]
struct Textures(Vec<Handle<TxtrData>>);
fn is_hidden(entry: &DirEntry) -> bool {
entry.file_name().to_str().map(|s| s.starts_with('.')).unwrap_or(false)
@@ -190,42 +215,147 @@ fn check_assets_ready(
server: Res<AssetServer>,
mut loading: ResMut<OpenAsset>,
mut ui_state: ResMut<UiState>,
textures: Res<Assets<TxtrData>>,
textures: Res<Assets<TextureAsset>>,
mut egui_ctx: ResMut<EguiContext>,
mut images: ResMut<Assets<Image>>,
) {
if let Some(handle) = &loading.0 {
let OpenAsset(asset_ref, opt_handle) = &mut *loading;
if let Some(handle) = &*opt_handle {
let state = server.get_load_state(handle);
match state {
LoadState::NotLoaded => {}
LoadState::Loading => {}
LoadState::Loaded => {
let handle = std::mem::take(&mut loading.0).unwrap();
let txtr = textures.get(&handle.typed_weak::<TxtrData>()).unwrap();
let image = if let Some(rgba) = &txtr.rgba {
let handle = std::mem::take(opt_handle).unwrap().typed::<TextureAsset>();
let txtr = textures.get(&handle).unwrap();
let mut texture_ids = Vec::new();
if let Some(rgba) = &txtr.rgba {
let image_handle = images.add(Image {
data: rgba.clone(),
texture_descriptor: TextureDescriptor {
label: None,
size: Extent3d {
width: txtr.data.head.width,
height: txtr.data.head.height,
width: txtr.inner.head.width,
height: txtr.inner.head.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: TextureFormat::Rgba8Unorm,
format: if txtr.inner.head.format.is_srgb() {
TextureFormat::Rgba8UnormSrgb
} else {
TextureFormat::Rgba8Unorm
},
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
},
sampler_descriptor: Default::default(),
texture_view_descriptor: None,
});
Some(egui_ctx.add_image(image_handle))
texture_ids.push(egui_ctx.add_image(image_handle));
} else {
None
let array_stride: usize = (txtr.inner.head.mip_sizes.iter().sum::<u32>()
/ txtr.inner.head.layers)
as usize;
for layer in 0..txtr.inner.head.layers as usize {
let image_handle = images.add(Image {
data: txtr.inner.data
[layer * array_stride..(layer * array_stride) + array_stride]
.to_vec(),
texture_descriptor: TextureDescriptor {
label: None,
size: Extent3d {
width: txtr.inner.head.width,
height: txtr.inner.head.height,
depth_or_array_layers: 1,
},
mip_level_count: txtr.inner.head.mip_sizes.len() as u32,
sample_count: 1,
dimension: match txtr.inner.head.kind {
ETextureType::_1D => TextureDimension::D1,
ETextureType::_2D => TextureDimension::D2,
ETextureType::_3D => TextureDimension::D3,
ETextureType::Cube => TextureDimension::D2,
ETextureType::_1DArray => TextureDimension::D1,
ETextureType::_2DArray => TextureDimension::D2,
ETextureType::_2DMultisample => TextureDimension::D2,
ETextureType::_2DMultisampleArray => TextureDimension::D2,
ETextureType::CubeArray => TextureDimension::D2,
},
format: match txtr.inner.head.format {
ETextureFormat::R8Unorm => TextureFormat::Rgba8Unorm,
ETextureFormat::R8Snorm => TextureFormat::R8Snorm,
ETextureFormat::R8Uint => TextureFormat::R8Uint,
ETextureFormat::R8Sint => TextureFormat::R8Sint,
ETextureFormat::R16Unorm => TextureFormat::R16Unorm,
ETextureFormat::R16Snorm => TextureFormat::R16Snorm,
ETextureFormat::R16Uint => TextureFormat::R16Uint,
ETextureFormat::R16Sint => TextureFormat::R16Sint,
ETextureFormat::R16Float => TextureFormat::R16Float,
ETextureFormat::R32Uint => TextureFormat::R32Uint,
ETextureFormat::R32Sint => TextureFormat::R32Sint,
ETextureFormat::Rgba8Unorm => TextureFormat::Rgba8Unorm,
ETextureFormat::Rgba8Srgb => TextureFormat::Rgba8UnormSrgb,
ETextureFormat::Rgba16Float => TextureFormat::Rgba16Float,
ETextureFormat::Rgba32Float => TextureFormat::Rgba32Float,
ETextureFormat::Depth16Unorm => TextureFormat::Depth16Unorm,
ETextureFormat::Depth16Unorm2 => TextureFormat::Depth16Unorm,
ETextureFormat::Depth24S8Unorm => {
TextureFormat::Depth24PlusStencil8
}
ETextureFormat::Depth32Float => TextureFormat::Depth32Float,
ETextureFormat::RgbaBc1Unorm => TextureFormat::Bc1RgbaUnorm,
ETextureFormat::RgbaBc1Srgb => TextureFormat::Bc1RgbaUnormSrgb,
ETextureFormat::RgbaBc2Unorm => TextureFormat::Bc2RgbaUnorm,
ETextureFormat::RgbaBc2Srgb => TextureFormat::Bc2RgbaUnormSrgb,
ETextureFormat::RgbaBc3Unorm => TextureFormat::Bc3RgbaUnorm,
ETextureFormat::RgbaBc3Srgb => TextureFormat::Bc3RgbaUnormSrgb,
ETextureFormat::RgbaBc4Unorm => TextureFormat::Bc4RUnorm,
ETextureFormat::RgbaBc4Snorm => TextureFormat::Bc4RSnorm,
ETextureFormat::RgbaBc5Unorm => TextureFormat::Bc5RgUnorm,
ETextureFormat::RgbaBc5Snorm => TextureFormat::Bc5RgSnorm,
ETextureFormat::Rg11B10Float => TextureFormat::Rg11b10Float,
ETextureFormat::R32Float => TextureFormat::R32Float,
ETextureFormat::Rg8Unorm => TextureFormat::Rg8Unorm,
ETextureFormat::Rg8Snorm => TextureFormat::Rg8Snorm,
ETextureFormat::Rg8Uint => TextureFormat::Rg8Uint,
ETextureFormat::Rg8Sint => TextureFormat::Rg8Sint,
ETextureFormat::Rg16Float => TextureFormat::Rg16Float,
ETextureFormat::Rg16Unorm => TextureFormat::Rg16Unorm,
ETextureFormat::Rg16Snorm => TextureFormat::Rg16Snorm,
ETextureFormat::Rg16Uint => TextureFormat::Rg16Uint,
ETextureFormat::Rg16Sint => TextureFormat::Rg16Sint,
ETextureFormat::Rgb10A2Unorm => TextureFormat::Rgb10a2Unorm,
ETextureFormat::Rg32Uint => TextureFormat::Rg32Uint,
ETextureFormat::Rg32Sint => TextureFormat::Rg32Sint,
ETextureFormat::Rg32Float => TextureFormat::Rg32Float,
ETextureFormat::Rgba16Unorm => TextureFormat::Rgba16Unorm,
ETextureFormat::Rgba16Snorm => TextureFormat::Rgba16Snorm,
ETextureFormat::Rgba16Uint => TextureFormat::Rgba16Uint,
ETextureFormat::Rgba16Sint => TextureFormat::Rgba16Sint,
ETextureFormat::Rgba32Uint => TextureFormat::Rgba32Uint,
ETextureFormat::Rgba32Sint => TextureFormat::Rgba32Sint,
ETextureFormat::BptcUfloat => TextureFormat::Bc6hRgbUfloat,
ETextureFormat::BptcSfloat => TextureFormat::Bc6hRgbSfloat,
ETextureFormat::BptcUnorm => TextureFormat::Bc7RgbaUnorm,
ETextureFormat::BptcUnormSrgb => {
TextureFormat::Bc7RgbaUnormSrgb
}
_ => todo!(),
},
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
},
sampler_descriptor: Default::default(),
texture_view_descriptor: None,
});
texture_ids.push(egui_ctx.add_image(image_handle));
}
};
ui_state.tree.push_to_first_leaf(TabType::Asset(handle, image));
ui_state.tree.push_to_first_leaf(TabType::Texture(
asset_ref.clone(),
handle,
texture_ids,
));
}
LoadState::Failed => {}
LoadState::Unloaded => {}
@@ -251,13 +381,13 @@ fn update_ui_scale_factor(
}
#[derive(Default, Resource)]
struct OpenAsset(Option<HandleUntyped>);
struct OpenAsset(AssetRef, Option<HandleUntyped>);
fn ui_example(
mut egui_ctx: ResMut<EguiContext>,
mut ui_state: ResMut<UiState>,
packages: Res<Assets<PackageDirectory>>,
textures: Res<Assets<TxtrData>>,
textures: Res<Assets<TextureAsset>>,
server: Res<AssetServer>,
open_asset: ResMut<OpenAsset>,
) {
@@ -272,7 +402,27 @@ fn ui_example(
});
});
let mut tab_assets = vec![];
for node in ui_state.tree.iter() {
if let egui_dock::Node::Leaf { tabs, .. } = node {
for tab in tabs {
match tab {
TabType::Directory => {}
TabType::Texture(asset_ref, _, _) => {
tab_assets.push(asset_ref.clone());
}
TabType::Empty => {}
}
}
}
}
egui_dock::DockArea::new(&mut ui_state.tree)
.style(egui_dock::Style::from_egui(egui_ctx.ctx_mut().style().as_ref()))
.show(egui_ctx.ctx_mut(), &mut TabViewer { server, packages, textures, open_asset });
.show(egui_ctx.ctx_mut(), &mut TabViewer {
server,
packages,
textures,
open_asset,
tab_assets,
});
}