From 22b4c1cdcfcf089135597c724ca77a19773a68a3 Mon Sep 17 00:00:00 2001 From: PikminGuts92 Date: Sun, 7 Jan 2024 20:27:03 -0500 Subject: [PATCH] More preview_ui to other repo --- Cargo.toml | 2 +- apps/ui/preview_ui/Cargo.toml | 25 - apps/ui/preview_ui/src/events.rs | 15 - apps/ui/preview_ui/src/gui/ark.rs | 40 -- apps/ui/preview_ui/src/gui/icons.rs | 56 -- apps/ui/preview_ui/src/gui/milo.rs | 73 --- apps/ui/preview_ui/src/gui/mod.rs | 291 ---------- apps/ui/preview_ui/src/gui/toolbar.rs | 67 --- apps/ui/preview_ui/src/main.rs | 497 ----------------- apps/ui/preview_ui/src/plugins.rs | 96 ---- apps/ui/preview_ui/src/render/loader.rs | 180 ------ apps/ui/preview_ui/src/render/milo_entry.rs | 585 -------------------- apps/ui/preview_ui/src/render/mod.rs | 269 --------- apps/ui/preview_ui/src/settings.rs | 100 ---- apps/ui/preview_ui/src/state.rs | 139 ----- 15 files changed, 1 insertion(+), 2434 deletions(-) delete mode 100644 apps/ui/preview_ui/Cargo.toml delete mode 100644 apps/ui/preview_ui/src/events.rs delete mode 100644 apps/ui/preview_ui/src/gui/ark.rs delete mode 100644 apps/ui/preview_ui/src/gui/icons.rs delete mode 100644 apps/ui/preview_ui/src/gui/milo.rs delete mode 100644 apps/ui/preview_ui/src/gui/mod.rs delete mode 100644 apps/ui/preview_ui/src/gui/toolbar.rs delete mode 100644 apps/ui/preview_ui/src/main.rs delete mode 100644 apps/ui/preview_ui/src/plugins.rs delete mode 100644 apps/ui/preview_ui/src/render/loader.rs delete mode 100644 apps/ui/preview_ui/src/render/milo_entry.rs delete mode 100644 apps/ui/preview_ui/src/render/mod.rs delete mode 100644 apps/ui/preview_ui/src/settings.rs delete mode 100644 apps/ui/preview_ui/src/state.rs diff --git a/Cargo.toml b/Cargo.toml index 50a136db..653d41d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] members = [ "apps/cli/*", - "apps/ui/*", + #"apps/ui/*", "core/*", "utils/*" ] diff --git a/apps/ui/preview_ui/Cargo.toml b/apps/ui/preview_ui/Cargo.toml deleted file mode 100644 index a0e0f032..00000000 --- a/apps/ui/preview_ui/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "preview_ui" -version.workspace = true -authors.workspace = true -edition.workspace = true - -[dependencies] -bevy = { version = "0.11.3", default-features = false, features = [ "bevy_core_pipeline", "bevy_pbr", "bevy_render", "bevy_winit", "x11" ] } -bevy_egui = { version = "0.21.0", features = [ "immutable_ctx" ] } -#bevy_fly_camera = "0.10.0" -bevy_fly_camera = { git = "https://github.com/zachbateman/bevy_fly_camera.git", branch = "bevy_0.11.0" } -bevy_infinite_grid = "0.8.1" -egui_extras = { version = "0.22.0", features = [ "svg" ] } -font-awesome-as-a-crate = "0.3.0" -grim = { workspace = true } -itertools = { workspace = true } -lazy_static = { workspace = true } -log = { workspace = true } -# native-dialog = "0.6.1" -serde = { workspace = true } -serde_json = { workspace = true } -thiserror = { workspace = true } - -[target.'cfg(target_family = "wasm")'.dependencies] -console_error_panic_hook = "0.1.7" diff --git a/apps/ui/preview_ui/src/events.rs b/apps/ui/preview_ui/src/events.rs deleted file mode 100644 index f31a2958..00000000 --- a/apps/ui/preview_ui/src/events.rs +++ /dev/null @@ -1,15 +0,0 @@ -use bevy::prelude::*; -use std::path::PathBuf; - -#[derive(Event)] -pub enum AppEvent { - Exit, - SelectMiloEntry(Option), - ToggleGridLines(bool), - ToggleWireframes(bool), -} - -#[derive(Event)] -pub enum AppFileEvent { - Open(PathBuf), -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/gui/ark.rs b/apps/ui/preview_ui/src/gui/ark.rs deleted file mode 100644 index ef0cbfbf..00000000 --- a/apps/ui/preview_ui/src/gui/ark.rs +++ /dev/null @@ -1,40 +0,0 @@ -use bevy_egui::{EguiContext, EguiPlugin, egui, egui::{Color32, Context, Pos2, Ui}}; -use grim::ark::{Ark, ArkOffsetEntry}; -use super::{AppSettings, AppState, ArkDirNode, AppEvent}; - -pub fn draw_ark_tree(state: &mut AppState, ctx: &mut &Context, ui: &mut Ui) { - if let Some(root) = &state.root { - let entries = &state.ark.as_ref().unwrap().entries; - - draw_node(root, entries, ctx, ui); - } -} - -fn draw_node(node: &ArkDirNode, entries: &Vec, ctx: &mut &Context, ui: &mut Ui) { - egui::CollapsingHeader::new(&node.name) - .id_source(format!("dir_{}", &node.path)) - .default_open(false) - .show(ui, |ui| { - for child in &node.dirs { - draw_node(child, entries, ctx, ui); - } - - egui::Grid::new(format!("files_{}", &node.path)).striped(true).show(ui, |ui| { - for file_idx in &node.files { - let ark_entry = &entries[*file_idx]; - let file_name = get_file_name(&ark_entry.path); - - #[allow(unused_must_use)] { - ui.selectable_label(false, file_name); - } - ui.end_row(); - - //ui.small_button(file_name); - } - }); - }); -} - -pub fn get_file_name(path: &str) -> &str { - path.split('/').last().unwrap_or(path) -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/gui/icons.rs b/apps/ui/preview_ui/src/gui/icons.rs deleted file mode 100644 index 6810bf0d..00000000 --- a/apps/ui/preview_ui/src/gui/icons.rs +++ /dev/null @@ -1,56 +0,0 @@ -use egui_extras::image::RetainedImage; -use font_awesome_as_a_crate as fa; -use lazy_static::lazy_static; - -lazy_static! { - pub static ref FA_GRID: RetainedImage = egui_extras::RetainedImage::from_svg_str( - "fa_grid", - fa::svg(fa::Type::Solid, "table-cells").unwrap() - ).unwrap(); - - pub static ref FA_CUBES: RetainedImage = egui_extras::RetainedImage::from_svg_str( - "fa_cubes", - fa::svg(fa::Type::Solid, "cubes").unwrap() - ).unwrap(); - - pub static ref FA_REFRESH: RetainedImage = egui_extras::RetainedImage::from_svg_str( - "fa_refresh", - fa::svg(fa::Type::Solid, "arrows-rotate").unwrap() - ).unwrap(); - - pub static ref FA_ARROWS_MULTI: RetainedImage = egui_extras::RetainedImage::from_svg_str( - "fa_arrows_multi", - fa::svg(fa::Type::Solid, "arrows-up-down-left-right").unwrap() - ).unwrap(); - - pub static ref FA_EYE: RetainedImage = egui_extras::RetainedImage::from_svg_str( - "fa_eye", - fa::svg(fa::Type::Solid, "eye").unwrap() - ).unwrap(); - - pub static ref FA_TRASH: RetainedImage = egui_extras::RetainedImage::from_svg_str( - "fa_trash", - fa::svg(fa::Type::Solid, "trash-can").unwrap() - ).unwrap(); - - pub static ref FA_EDIT: RetainedImage = egui_extras::RetainedImage::from_svg_str( - "fa_pen_to_square", - fa::svg(fa::Type::Solid, "pen-to-square").unwrap() - ).unwrap(); - - pub static ref FA_SEARCH: RetainedImage = egui_extras::RetainedImage::from_svg_str( - "fa_magnifying_glass", - fa::svg(fa::Type::Solid, "magnifying-glass").unwrap() - ).unwrap(); - - // Pro icon :( - /* pub static ref FA_GLOBE: RetainedImage = egui_extras::RetainedImage::from_svg_str( - "fa_globe", - fa::svg(fa::Type::Regular, "globe").unwrap() - ).unwrap(); */ - - pub static ref FA_CIRCLE: RetainedImage = egui_extras::RetainedImage::from_svg_str( - "fa_circle", - fa::svg(fa::Type::Regular, "circle").unwrap() - ).unwrap(); -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/gui/milo.rs b/apps/ui/preview_ui/src/gui/milo.rs deleted file mode 100644 index ed8a6c37..00000000 --- a/apps/ui/preview_ui/src/gui/milo.rs +++ /dev/null @@ -1,73 +0,0 @@ -use bevy_egui::{EguiContext, EguiPlugin, egui, egui::{Color32, Context, Pos2, Ui}}; -use grim::ark::{Ark, ArkOffsetEntry}; -use itertools::*; -use super::{AppSettings, AppState, ArkDirNode, AppEvent}; - -pub fn draw_milo_tree(state: &mut AppState, _ctx: &mut &Context, ui: &mut Ui) { - if let Some(milo) = state.milo.take() { - let mut entries = milo.get_entries().iter().map(|e| e).collect::>(); - - ui.horizontal(|ui| { - ui.label("Filter:"); - ui.text_edit_singleline(&mut state.milo_view.filter); - - // Selected class name - let classes = entries.iter().map(|x| x.get_type()).unique().sorted().collect::>(); - egui::ComboBox::from_label("") - .width(100.0) - .selected_text(state.milo_view.class_filter.as_ref().unwrap_or(&String::from("(None)"))) - .show_ui(ui, |ui| { - if ui.selectable_label(state.milo_view.class_filter.is_none(), "(None)") - .clicked() { - state.milo_view.class_filter = None; - }; - - for class in classes { - let mut checked = false; - if let Some(filter) = &state.milo_view.class_filter { - checked = filter.eq(class); - } - - if ui.selectable_label(checked, class).clicked() { - state.milo_view.class_filter = Some(class.to_string()); - } - } - }); - }); - - - if let Some(selected_class) = &state.milo_view.class_filter { - entries.retain(|e| e.get_type().eq(selected_class)); - } - - if !state.milo_view.filter.is_empty() { - entries.retain(|e| e.get_name().contains(&state.milo_view.filter)); - } - - // Sort objects - entries.sort_by_key(|e| e.get_name()); - - egui::Grid::new("milo_tree").min_col_width(200.0).striped(true).show(ui, |ui| { - for entry in entries.iter() { - let entry_name = entry.get_name(); - let mut checked = false; - - if let Some(selected) = &state.milo_view.selected_entry { - checked = selected.eq(entry_name); - } - - if ui.selectable_label(checked, entry_name).clicked() { - state.add_event(AppEvent::SelectMiloEntry(Some(entry_name.to_owned()))); - } - ui.end_row(); - } - - if entries.is_empty() { - ui.label("No objects found"); - } - }); - - // Give milo back - state.milo = Some(milo); - } -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/gui/mod.rs b/apps/ui/preview_ui/src/gui/mod.rs deleted file mode 100644 index eba919a0..00000000 --- a/apps/ui/preview_ui/src/gui/mod.rs +++ /dev/null @@ -1,291 +0,0 @@ -mod ark; -mod icons; -mod milo; -mod toolbar; - -use ark::*; -use bevy_egui::{EguiContext, EguiPlugin, egui, egui::{Color32, Context, Pos2, Ui}}; -use milo::*; -use super::{AppSettings, AppState, ArkDirNode, AppEvent}; -use toolbar::*; - -pub fn render_gui(ctx: &mut &Context, settings: &mut AppSettings, state: &mut AppState) { - // Top Toolbar - render_toolbar(ctx, settings, state); - - //ctx.set_visuals(egui::Visuals::light()); - - // Side panel - egui::SidePanel::left("side_panel").min_width(200.0).default_width(300.0).resizable(true).show(ctx, |ui| { - egui::ScrollArea::vertical().show_viewport(ui, |ui, _viewport| { - //ui.horizontal(|ui| { - //ui.set_min_width(300.0); - - ui.vertical(|ui| { - ui.horizontal(|ui| { - for (i, text) in [ "Ark", "Milo" ].iter().enumerate() { - if ui.selectable_label(i == state.side_bar_tab_index, *text).clicked() { - state.side_bar_tab_index = i; - } - } - }); - - match state.side_bar_tab_index { - 0 => draw_ark_tree(state, ctx, ui), - 1 => draw_milo_tree(state, ctx, ui), - _ => todo!() - }; - - /*ui.group(|ui| { - ui.heading("Options"); - ui.label("Do something 1"); - ui.label("Do something 2"); - - let popup_id = ui.make_persistent_id("popup_id"); - let popup_btn = ui.button("Show popup"); - - if popup_btn.clicked() { - ui.memory().toggle_popup(popup_id); - } - - egui::popup::popup_below_widget(ui, popup_id, &popup_btn, |ui| { - ui.group(|ui| { - ui.label("Some more info, or things you can select:"); - ui.label("…"); - }); - }); - });*/ - }); - - ui.separator(); - - ui.style_mut().spacing.interact_size = bevy_egui::egui::Vec2::default(); - - ui.vertical(|ui| { - ui.style_mut().spacing.item_spacing = bevy_egui::egui::Vec2::default(); - - /*if ui.checkbox(&mut settings.show_side_panel, "").changed() { - state.save_settings(&settings); - }*/ - }); - //}); - }); - }); - - /*let mut frame = egui::Frame::default(); - frame.fill = Color32::from_rgba_premultiplied(0, 128, 128, 16); - - egui::CentralPanel::default().frame(frame).show(ctx, |ui| { - ui.label("Hello world"); - });*/ - -/* - let frame = egui::Frame::none().fill(Color32::GREEN).multiply_with_opacity(0.1); - egui::CentralPanel::default().frame(frame).show(ctx, |_| {}); -*/ - // Hide menu shadow - let mut style: egui::Style = (*ctx.style()).clone(); - let shadow_color = style.visuals.window_shadow.color; - style.visuals.window_shadow.color = shadow_color.linear_multiply(0.0); - ctx.set_style(style); - - /*egui::Window::new("Hello").show(ctx, |ui| { - // let mut style = ui.style_mut(); - // style.visuals.code_bg_color = style.visuals.code_bg_color.linear_multiply(0.1); - - ui.label("world"); - });*/ - - let size = ctx.used_size(); - let _size_pos = Pos2::new(size.x, size.y); - - // Camera controls - if settings.show_controls { - egui::Window::new("Controls").resizable(false).collapsible(false).anchor(bevy_egui::egui::Align2::RIGHT_BOTTOM, bevy_egui::egui::Vec2::new(-10.0, -10.0)).show(ctx, |ui| { - egui::Grid::new("grid_controls").striped(true).show(ui, |ui| { - ui.label("Move"); - ui.label("W/A/S/D"); - ui.end_row(); - - ui.label("Up"); - ui.label("Space"); - ui.end_row(); - - ui.label("Down"); - ui.label("L-Shift"); - ui.end_row(); - - ui.label("View"); - ui.label("L-Click + Mouse"); - ui.end_row(); - }); - }); - } - - // Hotbar - egui::Window::new("Hotbar") - .title_bar(false) - .resizable(false) - .collapsible(false) - .anchor(egui::Align2::CENTER_TOP, [0., 10.]) - .auto_sized() - //.fixed_size([12., 12.]) - .show(ctx, |ui| { - const ICON_SIZE: egui::Vec2 = egui::Vec2::splat(12.); - - ui.horizontal(|ui| { - if ui.add( - egui::ImageButton::new( - icons::FA_GRID.texture_id(ctx), - ICON_SIZE - ).selected(settings.show_gridlines)) - .on_hover_text("Grid lines") - .clicked() { - settings.show_gridlines = !settings.show_gridlines; - state.add_event(AppEvent::ToggleGridLines(settings.show_gridlines)); - - state.save_settings(&settings); - } - - if ui.add( - egui::ImageButton::new( - icons::FA_CIRCLE.texture_id(ctx), - ICON_SIZE - ).selected(settings.show_wireframes)) - .on_hover_text("Wireframe") - .clicked() { - settings.show_wireframes = !settings.show_wireframes; - state.add_event(AppEvent::ToggleWireframes(settings.show_wireframes)); - - state.save_settings(&settings); - } - - // TODO: Add to settings or something - ui.add( - egui::ImageButton::new( - icons::FA_CUBES.texture_id(ctx), - ICON_SIZE - )) - .on_hover_text("Meshes"); - - ui.add( - egui::ImageButton::new( - icons::FA_ARROWS_MULTI.texture_id(ctx), - ICON_SIZE - )) - .on_hover_text("Do Something"); - - ui.add( - egui::ImageButton::new( - icons::FA_REFRESH.texture_id(ctx), - ICON_SIZE - )) - .on_hover_text("Refresh"); - }) - }); - - // Bottom Toolbar - /*egui::TopBottomPanel::bottom("bottom_panel").show(ctx, |ui| { - ui.label("Created by PikminGuts92"); - });*/ - - if state.show_options { - egui::Window::new("Options") - //.id("options_window") - .collapsible(false) - .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0]) - .fixed_size(bevy_egui::egui::Vec2::new(600.0, 400.0)) - .open(&mut state.show_options) - .show(ctx, |ui| { - ui.horizontal(|ui| { - ui.group(|ui| { - egui::Grid::new("options_list") - .striped(true) - .min_col_width(120.0) - .show(ui, |ui| { - ui.label("General"); - ui.end_row(); - - ui.label("Ark Paths"); - ui.end_row(); - - ui.label("Preferences"); - ui.end_row(); - }); - }); - - ui.separator(); - - ui.vertical_centered_justified(|ui| { - ui.heading("Ark Paths"); - - egui::Grid::new("ark_paths") - .striped(true) - .show(ui, |ui| { - for g in settings.game_paths.iter() { - ui.label(&g.game.to_string()); - ui.label(&g.platform.to_string()); - ui.label(&g.path); - - ui.end_row(); - } - }); - - ui.add_space(500.0); - }); - }); - - /*ui.columns(2, |cols| { - egui::Grid::new("options_list") - .striped(true) - .show(&mut cols[0], |ui| { - ui.label("Ark Paths"); - ui.end_row(); - - ui.label("Preferences"); - ui.end_row(); - }); - - let ui = &mut cols[1]; - ui.add(egui::Separator::default().vertical()); - - ui.group(|ui| { - ui.vertical_centered_justified(|ui| { - ui.heading("Ark Paths"); - - ui.add_space(500.0); - }); - }); - - /*egui::Grid::new("options_view") - .striped(true) - .show(&mut cols[1], |ui| { - ui.label("Options view goes here"); - ui.end_row(); - });*/ - });*/ - }); - } -} - -pub fn render_gui_info(ctx: &mut &Context, state: &mut AppState) { - //egui::Label::new("vert_face_count") - - let vert_count = state.vert_count; - let face_count = state.face_count; - - egui::Area::new("vert_face_count") - .anchor(egui::Align2::RIGHT_TOP, [-10., 10.]) - .interactable(false) - .movable(false) - .show(ctx, |ui| { - //ui.add_space(32.); - - //ui.label(format!("Vertices: {vert_count} Faces: {face_count}")); - - ui.with_layout(egui::Layout::top_down(egui::Align::RIGHT), |ui| { - ui.label(format!("Vertices: {vert_count}")); - ui.label(format!("Faces: {face_count}")); - }); - }); -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/gui/toolbar.rs b/apps/ui/preview_ui/src/gui/toolbar.rs deleted file mode 100644 index 2462ac17..00000000 --- a/apps/ui/preview_ui/src/gui/toolbar.rs +++ /dev/null @@ -1,67 +0,0 @@ -#![allow(unused_must_use)] - -use bevy_egui::{EguiContext, EguiPlugin, egui, egui::{Color32, Context, Pos2, Ui}}; -use super::{AppSettings, AppState, ArkDirNode, AppEvent}; - -pub fn render_toolbar(ctx: &mut &Context, settings: &mut AppSettings, state: &mut AppState) { - egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { - // ui.heading("Main"); - - egui::menu::bar(ui, |ui| { - // File dropdown - egui::menu::menu_button(ui, "File", |ui| { - ui.set_min_width(80.0); - - ui.button("Open"); - ui.separator(); - - ui.button("Save"); - ui.button("Save As..."); - ui.separator(); - - ui.button("Close"); - ui.separator(); - - if ui.button("Exit").clicked() { - // Close app - state.add_event(AppEvent::Exit); - } - }); - - // Edit dropdown - egui::menu::menu_button(ui, "Edit", |ui| { - ui.set_min_width(80.0); - - ui.button("Undo"); - ui.button("Redo"); - }); - - // View dropdown - egui::menu::menu_button(ui, "View", |ui| { - ui.set_min_width(80.0); - - if ui.checkbox(&mut settings.show_controls, "Controls").changed() { - state.save_settings(&settings); - } - }); - - // Tools dropdown - egui::menu::menu_button(ui, "Tools", |ui| { - ui.set_min_width(80.0); - - if ui.button("Options").clicked() { - state.show_options = true; - } - }); - - // Help dropdown - egui::menu::menu_button(ui, "Help", |ui| { - ui.set_min_width(120.0); - - ui.button("About"); - ui.separator(); - ui.button("Check for Updates"); - }); - }); - }); -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/main.rs b/apps/ui/preview_ui/src/main.rs deleted file mode 100644 index c4394607..00000000 --- a/apps/ui/preview_ui/src/main.rs +++ /dev/null @@ -1,497 +0,0 @@ -#![allow(dead_code)] -#![allow(unused_imports)] - -// Hide console if release build -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] - -mod events; -mod gui; -mod plugins; -mod render; -mod settings; -mod state; - -use events::*; -use gui::*; -use render::{render_milo, render_milo_entry}; -use settings::*; -use bevy::{prelude::*, render::camera::PerspectiveProjection, window::{PresentMode, PrimaryWindow, WindowMode, WindowResized}, winit::WinitWindows}; -use bevy_egui::{EguiContext, EguiPlugin, egui, egui::{Color32, Context, Pos2, Ui}}; -use bevy_fly_camera::{FlyCamera, FlyCameraPlugin}; -use bevy_infinite_grid::{GridShadowCamera, InfiniteGridBundle, InfiniteGrid, InfiniteGridPlugin}; -use grim::*; -use grim::ark::{Ark, ArkOffsetEntry}; -use grim::scene::*; -use log::{debug, info, warn}; -use plugins::*; -use state::*; -use std::{env::args, path::{Path, PathBuf}}; - -use crate::render::open_and_unpack_milo; - -#[derive(Component)] -pub struct WorldMesh { - name: String, - vert_count: usize, - face_count: usize, -} - -fn main() { - App::new() - .add_event::() - .add_event::() - //.insert_resource(ClearColor(Color::BLACK)) - .insert_resource(Msaa::Sample4) - .add_plugins(GrimPlugin) - .add_plugins(EguiPlugin) - .add_plugins(FlyCameraPlugin) - .add_plugins(InfiniteGridPlugin) - .add_systems(Update, render_gui_system) - .add_systems(Update, detect_meshes) - .add_systems(Update, control_camera) - .add_systems(Update, drop_files) - .add_systems(Update, window_resized) - .add_systems(Update, consume_file_events) - .add_systems(Update, consume_app_events) - .add_systems(Startup, setup_args) - .add_systems(Startup, setup) - .run(); -} - -fn render_gui_system(mut settings: ResMut, mut state: ResMut, mut egui_ctx_query: Query<&mut EguiContext, With>, mut event_writer: EventWriter) { - let egui_ctx = egui_ctx_query.single_mut(); - - render_gui(&mut egui_ctx.get(), &mut *settings, &mut *state); - render_gui_info(&mut egui_ctx.get(), &mut *state); - - state.consume_events(|ev| { - event_writer.send(ev); - }); -} - -fn detect_meshes( - mut state: ResMut, - mesh_entities: Query<&WorldMesh>, -) { - let mut vertex_count = 0; - let mut face_count = 0; - - for world_mesh in mesh_entities.iter() { - vertex_count += world_mesh.vert_count; - face_count += world_mesh.face_count; - } - - // Update counts - state.vert_count = vertex_count; - state.face_count = face_count; -} - -fn setup( - mut commands: Commands, - _meshes: ResMut>, - _materials: ResMut>, - mut primary_window_query: Query<&mut Window, With>, - settings: Res, - _state: Res, -) { - // Set primary window to maximized if preferred - if settings.maximized { - let mut window = primary_window_query.single_mut(); - window.set_maximized(true); - } - - // plane - /*commands.spawn_bundle(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })), - material: materials.add(StandardMaterial { - base_color: Color::rgb(0.3, 0.5, 0.3), - double_sided: true, - unlit: false, - ..Default::default() - }), - ..Default::default() - });*/ - - /* - commands.spawn_bundle(PbrBundle { - mesh: meshes.add(Mesh::from( - shape::Icosphere { - radius: 0.8, - subdivisions: 5, - }) - ), - material: materials.add(StandardMaterial { - base_color: Color::rgb(1.0, 0.0, 1.0), - double_sided: true, - unlit: false, - ..Default::default() - }), - transform: Transform::from_xyz(0.0, 2.0, 0.0), - ..Default::default() - }); - - // cube - commands.spawn_bundle(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), - material: materials.add(StandardMaterial { - base_color: Color::rgb(0.8, 0.7, 0.6), - double_sided: true, - unlit: false, - ..Default::default() - }), - transform: Transform::from_xyz(0.0, 0.5, 0.0), - ..Default::default() - });*/ - // light - /*commands.spawn_bundle(LightBundle { - transform: Transform::from_xyz(4.0, 8.0, 4.0), - ..Default::default() - });*/ - // camera - let mut camera = Camera3dBundle::default(); - camera.transform = Transform::from_xyz(-2.0, 2.5, 5.0) - .looking_at(Vec3::ZERO, Vec3::Y); - - commands.spawn(camera).insert(FlyCamera { - enabled: false, - sensitivity: 0.0, - ..Default::default() - }).insert(GridShadowCamera); // Fix camera - - // Infinite grid - commands.spawn(InfiniteGridBundle { - grid: InfiniteGrid { - fadeout_distance: 300., - shadow_color: None, // No shadow - ..InfiniteGrid::default() - }, - visibility: if settings.show_gridlines { - Visibility::Visible - } else { - Visibility::Hidden - }, - ..InfiniteGridBundle::default() - }); -} - -fn setup_args( - _state: ResMut, - mut ev_update_state: EventWriter, -) { - let mut args = args().skip(1).collect::>(); - if args.is_empty() { - return; - } - - ev_update_state.send(AppFileEvent::Open(args.remove(0).into())); -} - -fn consume_file_events( - mut file_events: EventReader, - mut app_event_writer: EventWriter, - mut state: ResMut, -) { - for e in file_events.iter() { - match e { - AppFileEvent::Open(file_path) => { - //milo_event_writer.send(bevy::app::AppExit); - open_file(file_path, &mut state, &mut app_event_writer); - } - } - } -} - -fn consume_app_events( - mut app_events: EventReader, - mut bevy_event_writer: EventWriter, - mut state: ResMut, - mut commands: Commands, - mut meshes: ResMut>, - mut materials: ResMut>, - mut textures: ResMut>, - mut grid: Query<&mut Visibility, With>, - mut wireframe_config: ResMut, - world_meshes: Query<(Entity, &WorldMesh)>, -) { - for e in app_events.iter() { - match e { - AppEvent::Exit => { - bevy_event_writer.send(bevy::app::AppExit); - }, - AppEvent::SelectMiloEntry(entry_name) => { - /*let render_entry = match &state.milo_view.selected_entry { - Some(name) => name.ne(entry_name), - None => true, - };*/ - - // Clear everything - let mut i = 0; - for (entity, _) in world_meshes.iter() { - i += 1; - commands.entity(entity).despawn_recursive(); - } - if i > 0 { - debug!("Removed {} meshes in scene", i); - } - - /*if render_entry { - let milo = state.milo.as_ref().unwrap(); - let info = state.system_info.as_ref().unwrap(); - - render_milo_entry( - &mut commands, - &mut meshes, - &mut materials, - &mut textures, - milo, - Some(entry_name.to_owned()), - info - ); - }*/ - - let milo = state.milo.as_ref().unwrap(); - let milo_path = state.open_file_path.as_ref().unwrap(); - let info = state.system_info.as_ref().unwrap(); - - // Render everything for now - render_milo_entry( - &mut commands, - &mut meshes, - &mut materials, - &mut textures, - milo, - milo_path, - entry_name.to_owned(), - info - ); - - state.milo_view.selected_entry = entry_name.to_owned(); - - debug!("Updated milo"); - }, - AppEvent::ToggleGridLines(show) => { - *grid.single_mut() = if *show { - Visibility::Visible - } else { - Visibility::Hidden - }; - }, - AppEvent::ToggleWireframes(show) => { - //grid.single_mut().is_visible = *show; - wireframe_config.global = *show; - } - /*AppEvent::RefreshMilo => { - return; - - if let Some(milo) = &state.milo { - let info = state.system_info.as_ref().unwrap(); - render_milo( - &mut commands, - &mut meshes, - &mut materials, - &mut textures, - milo, - info - ); - } - - debug!("Updated milo"); - },*/ - } - } -} - -fn open_file( - file_path: &PathBuf, - state: &mut ResMut, - app_event_writer: &mut EventWriter, -) { - // Clear file path - state.open_file_path.take(); - - // Get full file extension - let ext = file_path - .file_name() - .and_then(|n| n.to_str()) - .map(|n| match n.find('.') { - Some(i) => &n[i..], - _ => n - }) - .unwrap(); - - // TODO: Make case-insensitive - if ext.contains("hdr") { - // Open ark - info!("Opening hdr from \"{}\"", file_path.display()); - - let ark_res = Ark::from_path(file_path); - if let Ok(ark) = ark_res { - debug!("Successfully opened ark with {} entries", ark.entries.len()); - - state.root = Some(create_ark_tree(&ark)); - state.ark = Some(ark); - state.open_file_path = Some(file_path.to_owned()); - } - } else if ext.contains("milo") - || ext.contains("gh") - || ext.contains("rnd") { // TODO: Break out into static regex - // Open milo - info!("Opening milo from \"{}\"", file_path.display()); - - match open_and_unpack_milo(file_path) { - Ok((milo, info)) => { - debug!("Successfully opened milo with {} entries", milo.get_entries().len()); - - state.milo = Some(milo); - state.system_info = Some(info); - state.open_file_path = Some(file_path.to_owned()); - - //ev_update_state.send(AppEvent::RefreshMilo); - - const NAME_PREFS: [&str; 5] = ["venue", "top", "lod0", "lod1", "lod2"]; - - let _groups = state.milo - .as_ref() - .unwrap() - .get_entries() - .iter() - .filter(|o| o.get_type() == "Group") - .collect::>(); - - let selected_entry = None; - /*for name in NAME_PREFS { - let group = groups - .iter() - .find(|g| g.get_name().starts_with(name)); - - if let Some(grp) = group { - selected_entry = Some(grp.get_name().to_owned()); - break; - } - }*/ - - app_event_writer.send(AppEvent::SelectMiloEntry(selected_entry)); - }, - Err(err) => { - warn!("Unable to unpack milo file:\n\t: {:?}", err); - } - } - } else { - info!("Unknown file type \"{}\"", file_path.display()); - } -} - -fn create_ark_tree(ark: &Ark) -> ArkDirNode { - let mut root = ArkDirNode { - name: ark.path - .file_name() - .unwrap() - .to_str() - .unwrap() - .to_owned(), // There's gotta be a better conversion... - path: String::from(""), - dirs: Vec::new(), - files: Vec::new(), - loaded: false - }; - - root.expand(ark); - root -} - -fn control_camera( - key_input: Res>, - mouse_input: Res>, - mut egui_ctx_query: Query<&mut EguiContext, With>, - mut cam_query: Query<&mut FlyCamera>, -) { - let mut egui_ctx = egui_ctx_query.single_mut(); - let ctx = egui_ctx.get_mut(); - - let key_down = is_camera_button_down(&key_input); - let mouse_down = mouse_input.pressed(MouseButton::Left); - - for mut cam in cam_query.iter_mut() { - // Disable camera move if mouse button not held - cam.sensitivity = match mouse_down { - true => 3.0, - _ => 0.0 - }; - - cam.enabled = !ctx.wants_pointer_input() - && !ctx.is_pointer_over_area() - && (key_down || mouse_down); - } -} - -fn is_camera_button_down(key_input: &Res>) -> bool { - let control_keys = [ - KeyCode::W, - KeyCode::A, - KeyCode::S, - KeyCode::D, - KeyCode::Space, - KeyCode::ShiftLeft, - ]; - - control_keys - .iter() - .any(|k| key_input.pressed(*k)) -} - -fn window_resized( - mut resize_events: EventReader, - primary_window_query: Query>, - mut settings: ResMut, - app_state: Res, - winit_windows: NonSend, -) { - let primary_window_id = primary_window_query.single(); - let window = winit_windows.get_window(primary_window_id).unwrap(); - let maximized = window.is_maximized(); - - if settings.maximized != maximized { - if maximized { - debug!("Window maximized"); - } else { - debug!("Window unmaximized"); - } - - settings.maximized = maximized; - app_state.save_settings(&settings); - return; - } - - if maximized { - // Ignore resize if maximized - return; - } - - for e in resize_events.iter() { - debug!("Window resized: {}x{}", e.width as u32, e.height as u32); - - settings.window_width = e.width; - settings.window_height = e.height; - app_state.save_settings(&settings); - } -} - -fn drop_files( - mut drag_drop_events: EventReader, - mut file_event_writer: EventWriter, -) { - for d in drag_drop_events.iter() { - if let FileDragAndDrop::DroppedFile { path_buf, .. } = d { - debug!("Dropped \"{}\"", path_buf.to_str().unwrap()); - - file_event_writer.send(AppFileEvent::Open(path_buf.to_owned())); - } - } -} - -fn is_drop_event(dad_event: &FileDragAndDrop) -> bool { - match dad_event { - FileDragAndDrop::DroppedFile { .. } => true, - _ => false - } -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/plugins.rs b/apps/ui/preview_ui/src/plugins.rs deleted file mode 100644 index cc6844e5..00000000 --- a/apps/ui/preview_ui/src/plugins.rs +++ /dev/null @@ -1,96 +0,0 @@ -use bevy::{prelude::*, log::LogPlugin, app::PluginGroupBuilder, window::{PresentMode, WindowMode, WindowResized, WindowResolution}}; -use crate::settings::*; -use crate::state::*; -use log::info; -use std::{env::args, path::{Path, PathBuf}}; - -const SETTINGS_FILE_NAME: &str = "settings.json"; -const PROJECT_NAME: &str = env!("CARGO_PKG_NAME"); -const VERSION: &str = env!("CARGO_PKG_VERSION"); - -struct MinimalPlugins; - -pub struct GrimPlugin; - -// Using until bevy_fly_camera is updated -// https://github.com/mcpar-land/bevy_fly_camera/pull/19 -impl PluginGroup for MinimalPlugins { - fn build(self) -> PluginGroupBuilder { - // Reference: https://github.com/bevyengine/bevy/blob/main/crates/bevy_internal/src/default_plugins.rs - PluginGroupBuilder::start::() - // Basic stuff - .add(bevy::log::LogPlugin::default()) - .add(bevy::core::TaskPoolPlugin::default()) - .add(bevy::core::TypeRegistrationPlugin::default()) - .add(bevy::core::FrameCountPlugin::default()) - .add(bevy::time::TimePlugin::default()) - .add(bevy::transform::TransformPlugin::default()) - .add(bevy::hierarchy::HierarchyPlugin::default()) - .add(bevy::diagnostic::DiagnosticsPlugin::default()) - .add(bevy::input::InputPlugin::default()) - .add(bevy::window::WindowPlugin::default()) - .add(bevy::a11y::AccessibilityPlugin) - // Optional features being used - .add(bevy::asset::AssetPlugin::default()) - .add(bevy::scene::ScenePlugin::default()) - .add(bevy::winit::WinitPlugin::default()) - .add(bevy::render::RenderPlugin::default()) - .add(bevy::render::texture::ImagePlugin::default()) - .add(bevy::core_pipeline::CorePipelinePlugin::default()) - .add(bevy::pbr::PbrPlugin::default()) - .add(bevy::pbr::wireframe::WireframePlugin) - } -} - -impl Plugin for GrimPlugin { - fn build(&self, app: &mut App) { - // Load settings - #[cfg(target_family = "wasm")] std::panic::set_hook(Box::new(console_error_panic_hook::hook)); - #[cfg(target_family = "wasm")] let app_state = AppState::default(); - #[cfg(target_family = "wasm")] let app_settings = AppSettings::default(); - - #[cfg(not(target_family = "wasm"))] let app_state = load_state(); - #[cfg(not(target_family = "wasm"))] let app_settings = load_settings(&app_state.settings_path); - - app - //.add_plugins(DefaultPlugins); - .add_plugins(MinimalPlugins.set(WindowPlugin { - primary_window: Some(Window { - title: format!("Preview v{}", VERSION), - resolution: WindowResolution::new( - app_settings.window_width, - app_settings.window_height - ), - mode: WindowMode::Windowed, - present_mode: PresentMode::Fifo, // vsync - resizable: true, - ..Default::default() - }), - ..Default::default() - })) - .insert_resource(bevy::pbr::wireframe::WireframeConfig { - global: app_settings.show_wireframes - }) - .insert_resource(app_state) - .insert_resource(app_settings); - } -} - -fn load_state() -> AppState { - let exe_path = &std::env::current_exe().unwrap(); - let exe_dir_path = exe_path.parent().unwrap(); - let settings_path = exe_dir_path.join(&format!("{}.{}", PROJECT_NAME, SETTINGS_FILE_NAME)); - - AppState { - settings_path, - //show_options: true, // TODO: Remove after testing - ..Default::default() - } -} - -fn load_settings(settings_path: &Path) -> AppSettings { - let settings = AppSettings::load_from_file(settings_path); - info!("Loaded settings from \"{}\"", settings_path.to_str().unwrap()); - - settings -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/render/loader.rs b/apps/ui/preview_ui/src/render/loader.rs deleted file mode 100644 index 2bfdf2e8..00000000 --- a/apps/ui/preview_ui/src/render/loader.rs +++ /dev/null @@ -1,180 +0,0 @@ -use grim::{Platform, SystemInfo}; -use grim::io::*; -use grim::scene::{GroupObject, MatObject, Matrix, MeshObject, Milo, MiloObject, Object, ObjectDir, PackedObject, RndMesh, Tex, Trans, TransConstraint}; - -use itertools::*; - -use std::collections::HashMap; - -pub struct MiloLoader<'a> { - milo: &'a ObjectDir, - milo_path: &'a std::path::Path, - objects: HashMap<&'a str, &'a Object>, - groups: HashMap<&'a str, &'a GroupObject>, - mats: HashMap<&'a str, &'a MatObject>, - meshes: HashMap<&'a str, &'a MeshObject>, - textures: HashMap<&'a str, &'a Tex>, - cached_textures: HashMap<&'a str, (&'a Tex, Vec, ImageInfo)>, - transforms: HashMap<&'a str, &'a dyn Trans>, -} - -pub enum TextureEncoding { - RGBA, - DXT1, - DXT5, - ATI2 -} - -pub struct ImageInfo { - pub width: u32, - pub height: u32, - pub mips: u32, - pub encoding: TextureEncoding, -} - -impl From<&grim::texture::Bitmap> for ImageInfo { - fn from(b: &grim::texture::Bitmap) -> Self { - Self { - width: b.width as u32, - height: b.height as u32, - mips: b.mip_maps as u32, - encoding: TextureEncoding::RGBA, // Actually set elsewhere - } - } -} - -impl<'a> MiloLoader<'a> { - pub fn new(milo: &'a ObjectDir, milo_path: &'a std::path::Path) -> MiloLoader<'a> { - let entries = milo.get_entries(); - - let objects = entries - .iter() - .fold(HashMap::new(), |mut acc, o| { - acc.insert(o.get_name(), o); - acc - }); - - let groups = get_objects_mapped( - entries, - |o| match o { - Object::Group(g) => Some(g), - _ => None, - }); - - let mats = get_objects_mapped( - entries, - |o| match o { - Object::Mat(m) => Some(m), - _ => None, - }); - - let meshes = get_objects_mapped( - entries, - |o| match o { - Object::Mesh(m) => Some(m), - _ => None, - }); - - let textures = get_objects_mapped( - entries, - |o| match o { - Object::Tex(t) => Some(t), - _ => None, - }); - - let transforms = get_objects_mapped_dyn( - entries, - |o| match o { - Object::Group(grp) => Some(grp as &dyn Trans), - Object::Mesh(mesh) => Some(mesh as &dyn Trans), - Object::Trans(trans) => Some(trans as &dyn Trans), - _ => None, - }); - - MiloLoader { - milo, - milo_path, - objects, - groups, - mats, - meshes, - textures, - cached_textures: HashMap::new(), - transforms, - } - } - - pub fn get_object(&self, name: &str) -> Option<&'a Object> { - self.objects - .get(name) - .and_then(|o| Some(*o)) - } - - pub fn get_group(&self, name: &str) -> Option<&'a GroupObject> { - self.groups - .get(name) - .and_then(|o| Some(*o)) - } - - pub fn get_mat(&self, name: &str) -> Option<&'a MatObject> { - self.mats - .get(name) - .and_then(|o| Some(*o)) - } - - pub fn get_mesh(&self, name: &str) -> Option<&'a MeshObject> { - self.meshes - .get(name) - .and_then(|o| Some(*o)) - } - - pub fn get_texture(&self, name: &str) -> Option<&'a Tex> { - self.textures - .get(name) - .and_then(|o| Some(*o)) - } - - pub fn get_cached_texture(&self, name: &str) -> Option<&(&'a Tex, Vec, ImageInfo)> { - self.cached_textures.get(name) - } - - pub fn set_cached_texture(&mut self, name: &str, rgba: Vec, image_info: ImageInfo) { - let tex = self.get_texture(name).unwrap(); - - self.cached_textures.insert(tex.get_name().as_str(), (tex, rgba, image_info)); - } - - pub fn get_transform(&self, name: &str) -> Option<&'a dyn Trans> { - self.transforms - .get(name) - .and_then(|o| Some(*o)) - } - - pub fn get_milo_path(&self) -> &std::path::Path { - self.milo_path - } -} - -fn get_objects_mapped(objects: &Vec, filter: impl Fn(&Object) -> Option<&T>) -> HashMap<&str, &T> { - objects - .iter() - .map(filter) - .filter(|o| o.is_some()) - .map(|o| o.unwrap()) - .fold(HashMap::new(), |mut acc, o| { - acc.insert(o.get_name().as_str(), o); - acc - }) -} - -fn get_objects_mapped_dyn(objects: &Vec, filter: impl Fn(&Object) -> Option<&T>) -> HashMap<&str, &T> { - objects - .iter() - .map(filter) - .filter(|o| o.is_some()) - .map(|o| o.unwrap()) - .fold(HashMap::new(), |mut acc, o| { - acc.insert(o.get_name().as_str(), o); - acc - }) -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/render/milo_entry.rs b/apps/ui/preview_ui/src/render/milo_entry.rs deleted file mode 100644 index bc969b65..00000000 --- a/apps/ui/preview_ui/src/render/milo_entry.rs +++ /dev/null @@ -1,585 +0,0 @@ -use bevy::prelude::*; -use bevy::render::render_resource::{AddressMode, Extent3d, SamplerDescriptor, TextureDimension, TextureFormat}; -use bevy::render::texture::ImageSampler; - -use itertools::*; -use log::warn; - -use std::collections::HashMap; -use std::error::Error; -use std::fs; -use std::io::Read; -use std::path::{Path, PathBuf}; -use thiserror::Error; - -use grim::{Platform, SystemInfo}; -use grim::io::*; -use grim::scene::{GroupObject, Matrix, MeshObject, Milo, MiloObject, Object, ObjectDir, PackedObject, RndMesh, Tex, Trans, TransConstraint}; -use grim::texture::Bitmap; - -use crate::WorldMesh; -use super::{ImageInfo, map_matrix, MiloLoader, TextureEncoding}; - -pub fn render_milo_entry( - commands: &mut Commands, - bevy_meshes: &mut ResMut>, - materials: &mut ResMut>, - bevy_textures: &mut ResMut>, - milo: &ObjectDir, - milo_path: &Path, - milo_entry: Option, - system_info: &SystemInfo, -) { - let mut loader = MiloLoader::new(milo, milo_path); - - // Get meshes for single object or return all meshes - // TODO: Make less hacky - let meshes = match milo_entry { - Some(entry) => { - let milo_object = loader.get_object(&entry).unwrap(); - get_object_meshes( - milo_object, - &mut loader, - ) - }, - None => milo.get_entries() - .iter() - .map(|e| match e { - Object::Mesh(m) => Some(m), - _ => None, - }) - .filter(|e| e.is_some()) - .map(|e| e.unwrap()) - .collect::>() - }; - - // Translate to bevy coordinate system - let trans_mat = Mat4::from_cols_array(&[ - -1.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 1.0, - ]); - - // Scale down - let scale_mat = Mat4::from_scale(Vec3::new(0.1, 0.1, 0.1)); - - let trans = Transform::from_matrix(trans_mat * scale_mat); - let global_trans = GlobalTransform::from(trans); - - // Root transform - let root_entity = commands.spawn_empty() - .insert(trans) - .insert(global_trans) - .insert(VisibilityBundle::default()) - .id(); - - for mesh in meshes { - // Ignore meshes without geometry (used mostly in GH1) - if mesh.vertices.is_empty() || mesh.name.starts_with("shadow") { - continue; - } - - // Get transform - let mat = get_computed_mat(mesh as &dyn Trans, &mut loader); - - let mut bevy_mesh = Mesh::new(bevy::render::render_resource::PrimitiveTopology::TriangleList); - - let vert_count = mesh.get_vertices().len(); - - let mut positions = vec![Default::default(); vert_count]; - let mut normals = vec![Default::default(); vert_count]; - let mut tangents = vec![Default::default(); vert_count]; - let mut uvs = vec![Default::default(); vert_count]; - - for (i, vert) in mesh.get_vertices().iter().enumerate() { - positions[i] = [vert.pos.x, vert.pos.y, vert.pos.z]; - - // TODO: Figure out normals/tangents - //normals.push([vert.normals.x, vert.normals.y, vert.normals.z]); - normals[i] = [1.0, 1.0, 1.0]; - tangents[i] = [0.0, 0.0, 0.0, 1.0]; - - uvs[i] = [vert.uv.u, vert.uv.v]; - } - - let indices = bevy::render::mesh::Indices::U16( - mesh.faces.iter().flat_map(|f| *f).collect() - ); - - bevy_mesh.set_indices(Some(indices)); - bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions); - bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); - bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_TANGENT, tangents); - bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs); - - // Load textures - let tex_names = loader.get_mat(&mesh.mat) - .map(|mat| ( - mat.diffuse_tex.to_owned(), - mat.normal_map.to_owned(), - mat.emissive_map.to_owned(), - )); - - let (diffuse, normal, emissive) = tex_names - .map(|(diffuse, normal, emissive)| ( - get_texture(&mut loader, &diffuse, system_info) - .map(map_texture) - .map(|t| bevy_textures.add(t)), - get_texture(&mut loader, &normal, system_info) - .map(map_texture) - .map(|t| bevy_textures.add(t)), - get_texture(&mut loader, &emissive, system_info) - .map(map_texture) - .map(|t| bevy_textures.add(t)), - )) - .unwrap_or_default(); - - let bevy_mat = match loader.get_mat(&mesh.mat) { - Some(mat) => StandardMaterial { - base_color: Color::rgba( - mat.color.r, - mat.color.g, - mat.color.b, - mat.alpha, - ), - double_sided: true, - unlit: true, - base_color_texture: diffuse, - normal_map_texture: normal, - emissive_texture: emissive, - //roughness: 0.8, // TODO: Bevy 0.6 migration - /*base_color_texture: get_texture(&mut loader, &mat.diffuse_tex, system_info) - .and_then(map_texture) - .and_then(|t| Some(bevy_textures.add(t))), - normal_map: get_texture(&mut loader, &mat.norm_detail_map, system_info) - .and_then(map_texture) - .and_then(|t| Some(bevy_textures.add(t))), - emissive_texture: get_texture(&mut loader, &mat.emissive_map, system_info) - .and_then(map_texture) - .and_then(|t| Some(bevy_textures.add(t))),*/ - ..Default::default() - }, - None => StandardMaterial { - base_color: Color::rgb(0.3, 0.5, 0.3), - double_sided: true, - unlit: false, - ..Default::default() - }, - }; - - // Add mesh - commands.entity(root_entity) - .with_children(|parent| { - parent.spawn(PbrBundle { - mesh: bevy_meshes.add(bevy_mesh), - material: materials.add(bevy_mat), - transform: Transform::from_matrix(mat), - ..Default::default() - }).insert(WorldMesh { - name: mesh.name.to_owned(), - vert_count: mesh.vertices.len(), - face_count: mesh.faces.len() - }); - }); - } -} - -fn get_object_meshes<'a>( - milo_object: &'a Object, - loader: &mut MiloLoader<'a>, -) -> Vec<&'a MeshObject> { - let mut meshes = Vec::new(); - - match milo_object { - Object::Group(grp) => { - // Iterate sub objects - for obj_name in &grp.objects { - let child_object = loader.get_object(&obj_name); - if child_object.is_none() { - continue; - } - - let mut child_meshes = get_object_meshes( - child_object.unwrap(), - loader, - ); - - meshes.append(&mut child_meshes); - } - }, - Object::Mesh(mesh) => { - meshes.push(mesh); - - // Iterate sub meshes - for sub_draw_name in &mesh.draw_objects { - let child_draw = loader.get_object(&sub_draw_name); - if child_draw.is_none() { - continue; - } - - let mut child_meshes = get_object_meshes( - child_draw.unwrap(), - loader, - ); - - meshes.append(&mut child_meshes); - } - }, - _ => { - - } - }; - - // Return meshes - meshes -} - -fn get_object_meshes_with_transform<'a>( - milo_object: &'a Object, - loader: &mut MiloLoader<'a>, -) -> Vec<(&'a MeshObject, Mat4)> { - let mut meshes = Vec::new(); - - match milo_object { - Object::Group(grp) => { - // Iterate sub objects - for obj_name in &grp.objects { - let child_object = loader.get_object(&obj_name); - if child_object.is_none() { - continue; - } - - let child_meshes = get_object_meshes_with_transform( - child_object.unwrap(), - loader, - ); - - for (mesh, mat) in child_meshes { - meshes.push((mesh, mat)); - } - } - }, - Object::Mesh(mesh) => { - let mat = get_computed_mat(mesh as &dyn Trans, loader); - meshes.push((mesh, mat)); - - // Iterate sub meshes - for sub_draw_name in &mesh.draw_objects { - let child_draw = loader.get_object(&sub_draw_name); - if child_draw.is_none() { - continue; - } - - let child_meshes = get_object_meshes_with_transform( - child_draw.unwrap(), - loader, - ); - - for (mesh, mat) in child_meshes { - meshes.push((mesh, mat)); - } - } - }, - _ => { - - } - }; - - // Return meshes - meshes -} - -fn get_computed_mat<'a>( - milo_object: &'a dyn Trans, - loader: &mut MiloLoader<'a>, -) -> Mat4 { - let parent_name = milo_object.get_parent(); - if parent_name.eq(milo_object.get_name()) { - // References self, use own world transform - return map_matrix(milo_object.get_world_xfm()); - } - - // Use relative transform - if let Some(parent) = loader.get_transform(parent_name) { - let parent_mat = get_computed_mat(parent, loader); - let local_mat = map_matrix(milo_object.get_local_xfm()); - - return parent_mat * local_mat; - } - - if !parent_name.is_empty() { - warn!("Can't find trans for {}", parent_name); - } - - map_matrix(milo_object.get_world_xfm()) -} - -fn get_product_local_mat<'a>( - milo_object: &'a dyn Trans, - loader: &mut MiloLoader<'a>, -) -> Mat4 { - let parent_name = milo_object.get_parent(); - if parent_name.eq(milo_object.get_name()) { - // References self, use own local transform - return map_matrix(milo_object.get_local_xfm()); - } - - // Use relative transform - if let Some(parent) = loader.get_transform(parent_name) { - let parent_mat = get_product_local_mat(parent, loader); - let local_mat = map_matrix(milo_object.get_local_xfm()); - - return parent_mat * local_mat; - } - - if parent_name.is_empty() { - warn!("Can't find trans for {}", parent_name); - } - - map_matrix(milo_object.get_local_xfm()) -} - -fn get_texture<'a, 'b>(loader: &'b mut MiloLoader<'a>, tex_name: &str, system_info: &SystemInfo) -> Option<&'b (&'a Tex, Vec, ImageInfo)> { - // Check for cached texture - if let Some(_cached) = loader.get_cached_texture(tex_name).take() { - // TODO: Figure out why commented out line doesn't work (stupid lifetimes) - //return Some(cached); - return loader.get_cached_texture(tex_name); - } - - let mut ext_tex = None; - - // Get bitmap and decode texture - // TODO: Check for external textures - loader.get_texture(tex_name) - .and_then(|t| { - if t.bitmap.is_some() { - t.bitmap.as_ref() - } else { - // Load external texture - let milo_dir_path = loader.get_milo_path().parent().unwrap(); - - // Insert "gen" sub folder - // TODO: Support loading from milo gen folder too? - let ext_img_path = match t.ext_path.rfind("/") { - Some(last_slash_idx) => { - let (dir_path, file_name) = t.ext_path.split_at(last_slash_idx); - - milo_dir_path.join(dir_path).join("gen").join(&file_name[1..]) - }, - None => milo_dir_path.join("gen").join(&t.ext_path), - }; - - let ext_img_file_stem = ext_img_path.file_stem().and_then(|fs| fs.to_str()).unwrap(); - let ext_img_path_dir = ext_img_path.parent().unwrap(); - - let files = ext_img_path_dir.find_files_with_depth(FileSearchDepth::Immediate).unwrap(); - - // TODO: Do case-insensitive compare - let matching_file = files - .iter() - .find(|f| f - .file_stem() - //.is_some_and(|fs| fs.eq_ignore_ascii_case(ext_img_file_stem)) - .and_then(|fs| fs.to_str()) - .is_some_and(|fs| fs.starts_with(ext_img_file_stem)) - ); - - if let Some(file_path) = matching_file { - log::info!("Found external texture file!\n\t{file_path:?}"); - - let data = if file_path.extension().is_some_and(|ext| ext.eq_ignore_ascii_case("gz")) { - // File is gz compressed - let file_size = grim::io::get_file_size(file_path) as usize; - let mut file = std::fs::File::open(file_path).unwrap(); - - // Read to buffer - let mut file_data = vec![0u8; file_size]; - file.read_exact(&mut file_data).unwrap(); - - // Inflate - grim::io::inflate_gzip_block_no_buffer(&file_data).unwrap() - } else if file_path.extension().is_some_and(|ext| ext.eq_ignore_ascii_case("z")) { - // File is zlib compressed - let file_size = grim::io::get_file_size(file_path) as usize; - let mut file = std::fs::File::open(file_path).unwrap(); - - // Read to buffer - let mut file_data = vec![0u8; file_size]; - file.read_exact(&mut file_data).unwrap(); - - // Inflate - grim::io::inflate_deflate_block_no_buffer(&file_data).unwrap() - } else { - let file_size = grim::io::get_file_size(file_path) as usize; - let mut file = std::fs::File::open(file_path).unwrap(); - - // Read to buffer - let mut file_data = vec![0u8; file_size]; - file.read_exact(&mut file_data).unwrap(); - - file_data - }; - - let mut stream = grim::io::MemoryStream::from_slice_as_read(&data); - let bitmap = grim::texture::Bitmap::from_stream(&mut stream, system_info); - - if bitmap.is_ok() { - log::info!("Successfully opened bitmap"); - } else { - log::warn!("Error opening bitmap"); - } - - ext_tex = bitmap.ok(); - return ext_tex.as_ref(); - } - - None - } - }) - .and_then(|b| match (system_info.platform, b.encoding) { - (Platform::X360 | Platform::PS3, 8 | 24 | 32) => { - let enc = match b.encoding { - 8 => TextureEncoding::DXT1, - 24 => TextureEncoding::DXT5, - 32 | _ => TextureEncoding::ATI2, - }; - - let mut data = b.raw_data.to_owned(); - - if system_info.platform.eq(&Platform::X360) { - // Swap bytes - for ab in data.chunks_mut(2) { - let tmp = ab[0]; - - ab[0] = ab[1]; - ab[1] = tmp; - } - } - - Some((data, ImageInfo { - encoding: enc, - ..b.into() - })) - }, - _ => b.unpack_rgba(system_info).ok() - .and_then(|rgba| Some((rgba, ImageInfo { - encoding: TextureEncoding::RGBA, - ..b.into() - }))) - }) - .and_then(move |(rgba, image_info)| { - // Cache decoded texture - loader.set_cached_texture(tex_name, rgba, image_info); - loader.get_cached_texture(tex_name) - }) -} - -fn map_texture<'a>(tex: &'a (&'a Tex, Vec, ImageInfo)) -> Image { - let (tex, rgba, ImageInfo { width, height, mips: _, encoding: enc }) = tex; - - let bpp: usize = match enc { - TextureEncoding::DXT1 => 4, - TextureEncoding::DXT5 | TextureEncoding::ATI2 => 8, - TextureEncoding::RGBA => 32, - }; - - let tex_size = ((*width as usize) * (*height as usize) * bpp) / 8; - let use_mips = rgba.len() > tex_size; // TODO: Always support mips? - - let img_slice = if use_mips { - &rgba - } else { - &rgba[..tex_size] - }; - - let image_new_fn = match enc { - TextureEncoding::RGBA => image_new_fill, // Use fill method for older textures - _ => image_new, - }; - - let mut texture = /*Image::new_fill*/ image_new_fn( - Extent3d { - width: tex.width.into(), - height: tex.height.into(), - depth_or_array_layers: 1, - }, - TextureDimension::D2, - img_slice, - match enc { - TextureEncoding::DXT1 => TextureFormat::Bc1RgbaUnormSrgb, - TextureEncoding::DXT5 => TextureFormat::Bc3RgbaUnormSrgb, - TextureEncoding::ATI2 => TextureFormat::Bc5RgUnorm, - _ => TextureFormat::Rgba8UnormSrgb, - } - ); - - // Update texture wrap mode - texture.sampler_descriptor = ImageSampler::Descriptor(SamplerDescriptor { - address_mode_u: AddressMode::Repeat, - address_mode_v: AddressMode::Repeat, - anisotropy_clamp: 1, // 16 - ..SamplerDescriptor::default() - }); - - // Set mipmap level - if use_mips { - texture.texture_descriptor.mip_level_count = tex - .bitmap - .as_ref() - .map_or(1, |b| (b.mip_maps as u32) + 1); - } - - texture -} - -fn image_new( - size: Extent3d, - dimension: TextureDimension, - pixel: &[u8], - format: TextureFormat, -) -> Image { - // Problematic!!! - /*debug_assert_eq!( - size.volume() * format.pixel_size(), - data.len(), - "Pixel data, size and format have to match", - );*/ - let mut image = Image { - data: pixel.to_owned(), - ..Default::default() - }; - image.texture_descriptor.dimension = dimension; - image.texture_descriptor.size = size; - image.texture_descriptor.format = format; - image -} - -fn image_new_fill( - size: Extent3d, - dimension: TextureDimension, - pixel: &[u8], - format: TextureFormat, -) -> Image { - let mut value = Image::default(); - value.texture_descriptor.format = format; - value.texture_descriptor.dimension = dimension; - value.resize(size); - - // Problematic!!! - /*debug_assert_eq!( - pixel.len() % format.pixel_size(), - 0, - "Must not have incomplete pixel data." - ); - debug_assert!( - pixel.len() <= value.data.len(), - "Fill data must fit within pixel buffer." - );*/ - - for current_pixel in value.data.chunks_exact_mut(pixel.len()) { - current_pixel.copy_from_slice(pixel); - } - value -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/render/mod.rs b/apps/ui/preview_ui/src/render/mod.rs deleted file mode 100644 index 2d74877c..00000000 --- a/apps/ui/preview_ui/src/render/mod.rs +++ /dev/null @@ -1,269 +0,0 @@ -mod loader; -mod milo_entry; - -use bevy::prelude::*; -use bevy::render::render_resource::{Extent3d, TextureDimension, TextureFormat}; - -pub use loader::*; -pub use milo_entry::*; - -use log::{debug, info, warn, error}; - -use std::collections::HashMap; -use std::error::Error; -use std::fs; -use std::path::{Path, PathBuf}; -use thiserror::Error; - -use grim::{Platform, SystemInfo}; -use grim::io::*; -use grim::scene::{RndMesh, Matrix, MeshObject, MiloObject, Object, ObjectDir, PackedObject, Tex, Trans, TransConstraint}; - -pub fn open_and_unpack_milo>(milo_path: T) -> Result<(ObjectDir, SystemInfo), Box> { - let milo_path = milo_path.as_ref(); - - let mut stream = FileStream::from_path_as_read_open(milo_path)?; - let milo = MiloArchive::from_stream(&mut stream)?; - - let system_info = SystemInfo::guess_system_info(&milo, &milo_path); - let mut obj_dir = milo.unpack_directory(&system_info)?; - obj_dir.unpack_entries(&system_info)?; - - Ok((obj_dir, system_info)) -} - -pub fn render_milo( - commands: &mut Commands, - bevy_meshes: &mut ResMut>, - materials: &mut ResMut>, - bevy_textures: &mut ResMut>, - milo: &ObjectDir, - system_info: &SystemInfo, -) { - let entries = milo.get_entries(); - - let groups = entries - .iter() - .map(|o| match o { - Object::Group(grp) => Some(grp), - _ => None, - }) - .filter(|o| o.is_some()) - .map(|o| o.unwrap()) - .collect::>(); - - let mats = entries - .iter() - .map(|o| match o { - Object::Mat(mat) => Some(mat), - _ => None, - }) - .filter(|o| o.is_some()) - .map(|o| o.unwrap()) - .collect::>(); - - let meshes = entries - .iter() - .map(|o| match o { - Object::Mesh(mesh) => Some(mesh), - _ => None, - }) - .filter(|o| o.is_some()) - .map(|o| o.unwrap()) - .collect::>(); - - let textures = entries - .iter() - .map(|o| match o { - Object::Tex(tex) => Some(tex), - _ => None, - }) - .filter(|o| o.is_some()) - .map(|o| o.unwrap()) - .collect::>(); - - let transforms = entries - .iter() - .map(|o| match o { - Object::Group(grp) => Some(get_transform(grp)), - Object::Mesh(mesh) => Some(get_transform(mesh)), - Object::Trans(trans) => Some(get_transform(trans)), - _ => None, - }) - .filter(|t| t.is_some()) - .map(|o| o.unwrap()) - .collect::>(); - - let mut tex_map = HashMap::new(); - - for tex in textures.iter() { - if let Some(bitmap) = &tex.bitmap { - // TODO: Use bevy supported texture formats instead of converting to rgba - // DXT1 = Bc1RgbaUnorm - // DXT5 = Bc3RgbaUnorm - // ATI2 = Bc5RgUnorm - match bitmap.unpack_rgba(system_info) { - Ok(rgba) => { - debug!("Processing {}", tex.get_name()); - - // TODO: Figure out how bevy can support mip maps - let tex_size = (bitmap.width as usize) * (bitmap.height as usize) * 4; - - let bevy_tex = Image::new_fill( - Extent3d { - width: bitmap.width.into(), - height: bitmap.height.into(), - depth_or_array_layers: 1 - }, - TextureDimension::D2, - &rgba[..tex_size], - TextureFormat::Rgba8UnormSrgb, - ); - - tex_map.insert(tex.get_name().as_str(), bevy_tex); - }, - Err(_err) => { - error!("Failed to convert {}", tex.get_name()); - } - } - } - } - - debug!("Found {} groups, {} meshes, {} textures, and {} materials", groups.len(), meshes.len(), textures.len(), mats.len()); - - for mesh in meshes { - // Ignore meshes without geometry (used mostly in GH1) - if mesh.vertices.is_empty() { - continue; - } - - let mut bevy_mesh = Mesh::new(bevy::render::render_resource::PrimitiveTopology::TriangleList); - - let mut positions = Vec::new(); - let mut normals = Vec::new(); - let mut tangents = Vec::new(); - let mut uvs = Vec::new(); - - for vert in mesh.get_vertices() { - positions.push([vert.pos.x, vert.pos.y, vert.pos.z]); - - // TODO: Figure out normals/tangents - //normals.push([vert.normals.x, vert.normals.y, vert.normals.z]); - normals.push([1.0, 1.0, 1.0]); - tangents.push([0.0, 0.0, 0.0, 1.0]); - - uvs.push([vert.uv.u, vert.uv.v]); - } - - let indices = bevy::render::mesh::Indices::U16( - mesh.faces.iter().flat_map(|f| *f).collect() - ); - - bevy_mesh.set_indices(Some(indices)); - bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions); - bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); - bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_TANGENT, tangents); - bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs); - - // Get base matrix - let base_matrix = transforms - .iter() - .find(|t| t.get_name().eq(mesh.get_parent())) - .and_then(|p| Some(map_matrix(p.get_world_xfm()))) - .unwrap_or(map_matrix(mesh.get_world_xfm())); - - // Translate to bevy coordinate system - let matrix = Mat4::from_cols_array(&[ - -1.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 1.0, - ]); - - let mat = mats - .iter() - .find(|m| m.get_name().eq(&mesh.mat)); - - if mat.is_none() { - warn!("Mat not found for \"{}\"", &mesh.mat); - } else { - let mat = mat.unwrap(); - if !mat.diffuse_tex.is_empty() && tex_map.get(mat.diffuse_tex.as_str()).is_none() { - warn!("Diffuse tex not found for \"{}\"", &mat.diffuse_tex); - } - } - - let bevy_mat = match mat { - Some(mat) => StandardMaterial { - base_color: Color::rgba( - mat.color.r, - mat.color.g, - mat.color.b, - mat.alpha, - ), - double_sided: true, - unlit: true, - base_color_texture: match tex_map.get(mat.diffuse_tex.as_str()) { - Some(texture) - => Some(bevy_textures.add(texture.to_owned())), - None => None, - }, - // TODO: Add extra texture maps - normal_map_texture: match tex_map.get(mat.normal_map.as_str()) { - Some(texture) - => Some(bevy_textures.add(texture.to_owned())), - None => None, - }, - emissive_texture: match tex_map.get(mat.emissive_map.as_str()) { - Some(texture) - => Some(bevy_textures.add(texture.to_owned())), - None => None, - }, - ..Default::default() - }, - None => StandardMaterial { - base_color: Color::rgb(0.3, 0.5, 0.3), - double_sided: true, - unlit: false, - ..Default::default() - }, - }; - - // Add mesh - commands.spawn(PbrBundle { - mesh: bevy_meshes.add(bevy_mesh), - material: materials.add(bevy_mat), - transform: Transform::from_matrix(base_matrix) - * Transform::from_matrix(matrix) - * Transform::from_scale(Vec3::new(0.1, 0.1, 0.1)), - ..Default::default() - }); - - debug!("Added {}", &mesh.name); - } -} - -fn get_transform(trans: &T) -> &dyn Trans { - trans -} - -pub fn map_matrix(m: &Matrix) -> Mat4 { - Mat4::from_cols_array(&[ - m.m11, - m.m12, - m.m13, - m.m14, - m.m21, - m.m22, - m.m23, - m.m24, - m.m31, - m.m32, - m.m33, - m.m34, - m.m41, - m.m42, - m.m43, - m.m44, - ]) -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/settings.rs b/apps/ui/preview_ui/src/settings.rs deleted file mode 100644 index 30fe11de..00000000 --- a/apps/ui/preview_ui/src/settings.rs +++ /dev/null @@ -1,100 +0,0 @@ -use bevy::prelude::*; -use serde::{Deserialize, Serialize}; -use log::error; -use std::fmt::{self, Display, Formatter}; -use std::fs::{read_to_string, self}; -use std::path::Path; - -#[derive(Debug, Deserialize, Serialize)] -pub struct GamePath { - pub path: String, - pub game: Game, - pub platform: Platform, -} - -#[derive(Debug, Deserialize, Serialize)] -pub enum Game { - Unknown, - TBRB, - GDRB -} - -#[derive(Debug, Deserialize, Serialize)] -pub enum Platform { - X360, - PS3, - Wii -} - -impl Display for Game { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -impl Display for Platform { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -#[derive(Debug, Deserialize, Resource, Serialize)] -pub struct AppSettings { - pub show_controls: bool, - pub game_paths: Vec, - pub window_width: f32, - pub window_height: f32, - pub maximized: bool, - pub show_gridlines: bool, - pub show_wireframes: bool, -} - -impl Default for AppSettings { - fn default() -> Self { - AppSettings { - show_controls: true, - game_paths: Vec::new(), - window_width: 1280.0, - window_height: 720.0, - maximized: false, - show_gridlines: true, - show_wireframes: false, - } - } -} - -impl AppSettings { - pub fn load_from_file(json_path: T) -> Self where T: AsRef { - let json_path = json_path.as_ref(); - - if !json_path.exists() { - return AppSettings::default(); - } - - let json_text = read_to_string(json_path); - - if let Ok(text) = json_text { - let settings = serde_json::from_str::(&text); - - if let Err(err) = &settings { - error!("Unable to parse settings: {:?}", err); - } - - return settings.unwrap_or_default(); - } - - AppSettings::default() - } - - pub fn save_to_file(&self, json_path: T) where T: AsRef { - #[cfg(not(target_family = "wasm"))] { - let json_path = json_path.as_ref(); - - // TODO: Check if directory exists and check for error - let json_text = serde_json::to_string_pretty(&self).unwrap(); - - fs::write(json_path, json_text) - .expect("Error writing settings to file"); - } - } -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/state.rs b/apps/ui/preview_ui/src/state.rs deleted file mode 100644 index e4a36b66..00000000 --- a/apps/ui/preview_ui/src/state.rs +++ /dev/null @@ -1,139 +0,0 @@ -use super::{AppSettings, AppEvent}; -use bevy::prelude::*; -use grim::*; -use grim::ark::{Ark, ArkOffsetEntry}; -use grim::scene::*; -use itertools::Itertools; -use log::debug; -use std::{env::args, path::{Path, PathBuf}}; - -type ConsumeEventFn = fn(AppEvent); - -#[derive(Default)] -pub struct MiloView { - pub filter: String, - pub class_filter: Option, - pub selected_entry: Option, -} - -#[derive(Default, Resource)] -pub struct AppState { - pub ark: Option, - pub root: Option, - pub system_info: Option, - pub milo: Option, - pub open_file_path: Option, - pub settings_path: PathBuf, - pub show_options: bool, - pub pending_events: Vec, - pub side_bar_tab_index: usize, - pub milo_view: MiloView, - pub vert_count: usize, - pub face_count: usize, -} - -impl AppState { - pub fn save_settings(&self, settings: &AppSettings) { - settings.save_to_file(&self.settings_path); - debug!("Saved settings to \"{}\"", &self.settings_path.to_str().unwrap()); - } - - pub fn consume_events(&mut self, mut callback: impl FnMut(AppEvent)) { - while !self.pending_events.is_empty() { - callback(self.pending_events.remove(0)); - } - } - - pub fn add_event(&mut self, ev: AppEvent) { - self.pending_events.push(ev); - } -} - -#[derive(Debug)] -pub struct ArkDirNode { - pub name: String, - pub path: String, - pub dirs: Vec, - pub files: Vec, - pub loaded: bool, -} - -impl ArkDirNode { - pub fn expand(&mut self, ark: &Ark) { - if self.loaded { - return; - } - - let (mut dirs, mut files) = get_dirs_and_files(&self.path, ark); - self.dirs.append(&mut dirs); - self.files.append(&mut files); - self.loaded = true; - - // TODO: Rely on lazy load - for c in &mut self.dirs { - c.expand(ark); - } - } -} - -fn get_dirs_and_files(dir: &str, ark: &Ark) -> (Vec, Vec) { - let is_root = match dir { - "" | "." => true, - _ => false, - }; - - if is_root { - let files = ark.entries - .iter() - .enumerate() - .filter(|(_i, e)| !e.path.contains('/') - || (e.path.starts_with("./") && e.path.matches(|c: char | c.eq(&'/')).count() == 1)) - .map(|(i, _)| i) - .collect::>(); - - let dirs = ark.entries - .iter() - .filter(|e| e.path.contains('/')) - .map(|e| e.path.split('/').next().unwrap()) - .unique() - .filter(|s| !s.eq(&".")) - .map(|s| ArkDirNode { - name: s.to_owned(), - path: s.to_owned(), - dirs: Vec::new(), - files: Vec::new(), - loaded: false, - }) - .collect::>(); - - return (dirs, files); - } - - let dir_path = format!["{}/", dir]; - let slash_count = dir_path.matches(|c: char| c.eq(&'/')).count(); - - let files = ark.entries - .iter() - .enumerate() - .filter(|(_i, e)| e.path.starts_with(&dir_path) - && e.path.matches(|c: char| c.eq(&'/')).count() == slash_count) - .map(|(i, _)| i) - .collect::>(); - - let dirs = ark.entries - .iter() - .filter(|e| e.path.starts_with(&dir_path) - && e.path.matches(|c: char| c.eq(&'/')).count() > slash_count) - .map(|e| e.path.split('/').nth(slash_count).unwrap()) - .unique() - .map(|s| ArkDirNode { - name: s.to_owned(), - path: format!("{}{}", dir_path, s), - dirs: Vec::new(), - files: Vec::new(), - loaded: false, - }) - .collect::>(); - - (dirs, files) -} \ No newline at end of file