From 73ac4f195028c24d8f5184ebd4ba05e711b913ef Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Sat, 19 Jul 2025 00:15:00 +0200 Subject: [PATCH 01/10] The base commit --- README.md | 14 ++++++++++---- pom.xml | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 808d989..4e33463 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,23 @@ -# UView -
- UView Logo + UView Logo -

+

UView

+ [![Tests](https://img.shields.io/github/actions/workflow/status/habedi/uview/tests.yml?label=tests&style=flat&labelColor=282c34&logo=github)](https://github.com/habedi/uview/actions/workflows/tests.yml) [![Code Coverage](https://img.shields.io/codecov/c/github/habedi/uview?style=flat&labelColor=282c34&logo=codecov)](https://codecov.io/gh/habedi/uview) [![Code Quality](https://img.shields.io/codefactor/grade/github/habedi/uview?style=flat&labelColor=282c34&logo=codefactor)](https://www.codefactor.io/repository/github/habedi/uview) [![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-007ec6?style=flat&labelColor=282c34&logo=open-source-initiative)](https://github.com/habedi/uview) [![Release](https://img.shields.io/github/release/habedi/uview.svg?style=flat&labelColor=282c34&logo=github)](https://github.com/habedi/uview/releases/latest) +[![Downloads](https://img.shields.io/github/downloads/habedi/uview/total?style=flat&labelColor=282c34&logo=github)](https://github.com/habedi/gogg/releases) + +A cross platform tool to view and modify `.unitypackage` files without Unity + + + +--- UView is an application for viewing and working with Unity package files without using the Unity Editor. It allows you to view, extract, and modify contents of `.unitypackage` files without needing to import them into a Unity project first. diff --git a/pom.xml b/pom.xml index eeb5ccc..b968003 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.uview uview - 0.1.0-b.1 + 0.1.0-b.2 jar UView From 8829104269aa021df3df384d9d90f3ae94ca9afa Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Sat, 19 Jul 2025 05:24:18 +0200 Subject: [PATCH 02/10] Fix the license badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4e33463..544b780 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![Tests](https://img.shields.io/github/actions/workflow/status/habedi/uview/tests.yml?label=tests&style=flat&labelColor=282c34&logo=github)](https://github.com/habedi/uview/actions/workflows/tests.yml) [![Code Coverage](https://img.shields.io/codecov/c/github/habedi/uview?style=flat&labelColor=282c34&logo=codecov)](https://codecov.io/gh/habedi/uview) [![Code Quality](https://img.shields.io/codefactor/grade/github/habedi/uview?style=flat&labelColor=282c34&logo=codefactor)](https://www.codefactor.io/repository/github/habedi/uview) -[![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-007ec6?style=flat&labelColor=282c34&logo=open-source-initiative)](https://github.com/habedi/uview) +[![License](https://img.shields.io/badge/license-MIT-007ec6?style=flat&labelColor=282c34&logo=open-source-initiative)](LICENSE) [![Release](https://img.shields.io/github/release/habedi/uview.svg?style=flat&labelColor=282c34&logo=github)](https://github.com/habedi/uview/releases/latest) [![Downloads](https://img.shields.io/github/downloads/habedi/uview/total?style=flat&labelColor=282c34&logo=github)](https://github.com/habedi/gogg/releases) From 83444f8fe35f140fa468c3513297a63c1a550f98 Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Sat, 19 Jul 2025 05:41:36 +0200 Subject: [PATCH 03/10] Migrate the project to Pixel Clover Org --- CONTRIBUTING.md | 4 +- LICENSE | 222 +++++++++++++++++++++++++++++++++++++++++++----- README.md | 12 +-- 3 files changed, 209 insertions(+), 29 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6e53f3f..a59ae5e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,7 @@ All contributions are welcome. ## How to Contribute -Please check the [issue tracker](https://github.com/habedi/uview/issues) to see if there is a relevant +Please check the [issue tracker](https://github.com/pixel-clover/uview/issues) to see if there is a relevant issue you would like to work on. ### Reporting Bugs @@ -44,7 +44,7 @@ This project uses `make` to manage common tasks. > [!IMPORTANT] > Unless you explicitly state otherwise, any contribution you intentionally submit for inclusion in the work, as defined -> in MIT License, shall be under the terms and conditions of the MIT License. +> in the Apache-2.0 license, shall be dual-licensed, without any additional terms or conditions. ## Code of Conduct diff --git a/LICENSE b/LICENSE index a2559ba..261eeb9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,201 @@ -MIT License - -Copyright (c) 2025 Hassan Abedi - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 544b780..a5b8672 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,12 @@

UView

-[![Tests](https://img.shields.io/github/actions/workflow/status/habedi/uview/tests.yml?label=tests&style=flat&labelColor=282c34&logo=github)](https://github.com/habedi/uview/actions/workflows/tests.yml) -[![Code Coverage](https://img.shields.io/codecov/c/github/habedi/uview?style=flat&labelColor=282c34&logo=codecov)](https://codecov.io/gh/habedi/uview) -[![Code Quality](https://img.shields.io/codefactor/grade/github/habedi/uview?style=flat&labelColor=282c34&logo=codefactor)](https://www.codefactor.io/repository/github/habedi/uview) -[![License](https://img.shields.io/badge/license-MIT-007ec6?style=flat&labelColor=282c34&logo=open-source-initiative)](LICENSE) -[![Release](https://img.shields.io/github/release/habedi/uview.svg?style=flat&labelColor=282c34&logo=github)](https://github.com/habedi/uview/releases/latest) -[![Downloads](https://img.shields.io/github/downloads/habedi/uview/total?style=flat&labelColor=282c34&logo=github)](https://github.com/habedi/gogg/releases) +[![Tests](https://img.shields.io/github/actions/workflow/status/pixel-clover/uview/tests.yml?label=tests&style=flat&labelColor=282c34&logo=github)](https://github.com/pixel-clover/uview/actions/workflows/tests.yml) +[![Code Coverage](https://img.shields.io/codecov/c/github/pixel-clover/uview?label=coverage&style=flat&labelColor=282c34&logo=codecov)](https://codecov.io/gh/pixel-clover/uview) +[![Code Quality](https://img.shields.io/codefactor/grade/github/pixel-clover/uview?label=quality&style=flat&labelColor=282c34&logo=codefactor)](https://www.codefactor.io/repository/github/pixel-clover/uview) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue?style=flat&labelColor=282c34&logo=open-source-initiative)](LICENSE) +[![Release](https://img.shields.io/github/release/pixel-clover/uview.svg?label=release&style=flat&labelColor=282c34&logo=github)](https://github.com/pixel-clover/uview/releases/latest) +[![Downloads](https://img.shields.io/github/downloads/pixel-clover/uview/total?label=downloads&style=flat&labelColor=282c34&logo=github)](https://github.com/pixel-clover/uview/releases) A cross platform tool to view and modify `.unitypackage` files without Unity From 9cd2fded0f0a8a06ac4a6f35b2f0f22d6f7df857 Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Sat, 19 Jul 2025 05:49:54 +0200 Subject: [PATCH 04/10] Fix the lisense --- .github/workflows/tests.yml | 2 +- README.md | 2 +- pyproject.toml | 2 +- src/main/java/com/uview/gui/MainWindow.java | 113 ++++++++---------- .../java/com/uview/gui/PackageViewPanel.java | 22 +--- src/main/resources/icons/file_css.svg | 30 ++--- src/main/resources/icons/file_font.svg | 28 ++--- src/main/resources/icons/file_html.svg | 36 +++--- 8 files changed, 101 insertions(+), 134 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cb0cdc8..16f99a9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,4 +38,4 @@ jobs: uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} - slug: habedi/uview + slug: pixel-clover/uview diff --git a/README.md b/README.md index a5b8672..4a2c25c 100644 --- a/README.md +++ b/README.md @@ -33,4 +33,4 @@ See [CONTRIBUTING](CONTRIBUTING.md) for details on how to make contributions to ## License -UView is available under MIT License ([LICENSE](LICENSE)). +UView is available under Apache License, Version 2.0 ([LICENSE](LICENSE)). diff --git a/pyproject.toml b/pyproject.toml index ae5c928..cbc93e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "uview" version = "0.1.0" description = "Python environment for UView" readme = "README.md" -license = { text = "MIT" } +license = { text = "Apache-2.0" } authors = [ { name = "Hassan Abedi", email = "hassan.abedi.t@gmail.com" } ] diff --git a/src/main/java/com/uview/gui/MainWindow.java b/src/main/java/com/uview/gui/MainWindow.java index f726849..4c7aabe 100644 --- a/src/main/java/com/uview/gui/MainWindow.java +++ b/src/main/java/com/uview/gui/MainWindow.java @@ -3,10 +3,7 @@ import com.formdev.flatlaf.extras.FlatSVGIcon; import com.uview.App; import com.uview.core.SettingsManager; -import java.awt.BorderLayout; -import java.awt.Cursor; -import java.awt.Desktop; -import java.awt.Dimension; +import java.awt.*; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.event.MouseAdapter; @@ -17,23 +14,7 @@ import java.nio.file.Path; import java.util.List; import java.util.Properties; -import javax.swing.BorderFactory; -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.Icon; -import javax.swing.JFileChooser; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JSeparator; -import javax.swing.JTabbedPane; -import javax.swing.SwingWorker; -import javax.swing.Timer; -import javax.swing.TransferHandler; +import javax.swing.*; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; import javax.swing.filechooser.FileNameExtensionFilter; @@ -72,44 +53,11 @@ public MainWindow() { updateState(); } - private class FileDropHandler extends TransferHandler { - @Override - public boolean canImport(TransferSupport support) { - if (!support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { - return false; - } - try { - Transferable t = support.getTransferable(); - List files = (List) t.getTransferData(DataFlavor.javaFileListFlavor); - for (File file : files) { - if (file.isFile() && file.getName().toLowerCase().endsWith(".unitypackage")) { - return true; // Accept drop if at least one unitypackage is present - } - } - } catch (Exception e) { - return false; - } - return false; - } - - @Override - public boolean importData(TransferSupport support) { - if (!canImport(support)) { - return false; - } - try { - Transferable t = support.getTransferable(); - List files = (List) t.getTransferData(DataFlavor.javaFileListFlavor); - for (File file : files) { - if (file.isFile() && file.getName().toLowerCase().endsWith(".unitypackage")) { - openPackage(file); - } - } - return true; - } catch (Exception e) { - return false; - } - } + private static String formatSize(long bytes) { + if (bytes < 1024) return bytes + " B"; + int exp = (int) (Math.log(bytes) / Math.log(1024)); + char pre = "KMGTPE".charAt(exp - 1); + return String.format("%.1f %sB", bytes / Math.pow(1024, exp), pre); } private JPanel createStatusBar() { @@ -142,13 +90,6 @@ private void updateMemoryUsage() { memoryUsageLabel.setText(String.format("Mem: %s", formatSize(usedMemory))); } - private static String formatSize(long bytes) { - if (bytes < 1024) return bytes + " B"; - int exp = (int) (Math.log(bytes) / Math.log(1024)); - char pre = "KMGTPE".charAt(exp - 1); - return String.format("%.1f %sB", bytes / Math.pow(1024, exp), pre); - } - private JMenuBar createMenuBar() { JMenuBar menuBar = new JMenuBar(); JMenu fileMenu = new JMenu("File"); @@ -468,4 +409,44 @@ private void setWorking(boolean working, String status) { statusLabel.setText(status); } } + + private class FileDropHandler extends TransferHandler { + @Override + public boolean canImport(TransferSupport support) { + if (!support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { + return false; + } + try { + Transferable t = support.getTransferable(); + List files = (List) t.getTransferData(DataFlavor.javaFileListFlavor); + for (File file : files) { + if (file.isFile() && file.getName().toLowerCase().endsWith(".unitypackage")) { + return true; // Accept drop if at least one unitypackage is present + } + } + } catch (Exception e) { + return false; + } + return false; + } + + @Override + public boolean importData(TransferSupport support) { + if (!canImport(support)) { + return false; + } + try { + Transferable t = support.getTransferable(); + List files = (List) t.getTransferData(DataFlavor.javaFileListFlavor); + for (File file : files) { + if (file.isFile() && file.getName().toLowerCase().endsWith(".unitypackage")) { + openPackage(file); + } + } + return true; + } catch (Exception e) { + return false; + } + } + } } diff --git a/src/main/java/com/uview/gui/PackageViewPanel.java b/src/main/java/com/uview/gui/PackageViewPanel.java index 7ca7ac6..cf3b3b6 100644 --- a/src/main/java/com/uview/gui/PackageViewPanel.java +++ b/src/main/java/com/uview/gui/PackageViewPanel.java @@ -6,30 +6,14 @@ import com.uview.gui.tree.TreeEntry; import com.uview.io.PackageIO; import com.uview.model.UnityAsset; -import java.awt.BorderLayout; -import java.awt.Cursor; -import java.awt.Desktop; -import java.awt.Dimension; +import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; import java.nio.file.Path; import java.util.Collection; import java.util.List; -import javax.swing.BorderFactory; -import javax.swing.JFileChooser; -import javax.swing.JFrame; -import javax.swing.JMenuItem; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JScrollPane; -import javax.swing.JSeparator; -import javax.swing.JTextField; -import javax.swing.JTree; -import javax.swing.SwingUtilities; -import javax.swing.SwingWorker; -import javax.swing.Timer; +import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.tree.DefaultMutableTreeNode; @@ -44,8 +28,8 @@ public class PackageViewPanel extends JPanel { private final DefaultTreeModel treeModel; private final JFrame owner; private final Timer searchDebounceTimer; - private File packageFile; private final JTextField searchField; + private File packageFile; public PackageViewPanel(JFrame owner, File packageFile, SettingsManager settingsManager) { super(new BorderLayout()); diff --git a/src/main/resources/icons/file_css.svg b/src/main/resources/icons/file_css.svg index efe4786..ece5ee3 100644 --- a/src/main/resources/icons/file_css.svg +++ b/src/main/resources/icons/file_css.svg @@ -4,19 +4,21 @@ version: "2.25" unicode: "fb08" --> - - - - - + + + + + diff --git a/src/main/resources/icons/file_font.svg b/src/main/resources/icons/file_font.svg index 349ddf8..6b5979b 100644 --- a/src/main/resources/icons/file_font.svg +++ b/src/main/resources/icons/file_font.svg @@ -5,19 +5,19 @@ version: "1.5" unicode: "ebc5" --> - - - - - + + + + + diff --git a/src/main/resources/icons/file_html.svg b/src/main/resources/icons/file_html.svg index 3c75afa..939c39b 100644 --- a/src/main/resources/icons/file_html.svg +++ b/src/main/resources/icons/file_html.svg @@ -4,23 +4,23 @@ version: "2.25" unicode: "fb0c" --> - - - - - - - - - + + + + + + + + + From 98fcb6dde2fda7a60bd4c50402485d8682caa82f Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Sat, 19 Jul 2025 06:17:30 +0200 Subject: [PATCH 05/10] Add a frame for editing meta files --- .../java/com/uview/core/PackageManager.java | 40 ++++++ .../java/com/uview/gui/AssetViewerFrame.java | 64 +++++++++- src/main/java/com/uview/gui/MainWindow.java | 115 ++++++++++-------- .../java/com/uview/gui/MetaEditorFrame.java | 83 +++++++++++++ .../java/com/uview/gui/PackageViewPanel.java | 72 +++++++++-- .../java/com/uview/gui/SyntaxTextPanel.java | 57 ++++++++- .../java/com/uview/model/UnityPackage.java | 8 ++ 7 files changed, 374 insertions(+), 65 deletions(-) create mode 100644 src/main/java/com/uview/gui/MetaEditorFrame.java diff --git a/src/main/java/com/uview/core/PackageManager.java b/src/main/java/com/uview/core/PackageManager.java index 51efabb..7cbe293 100644 --- a/src/main/java/com/uview/core/PackageManager.java +++ b/src/main/java/com/uview/core/PackageManager.java @@ -83,6 +83,46 @@ public void addAsset(Path sourceFile, String assetPath) throws IOException { LOGGER.info("Staged asset {} for addition", assetPath); } + public void updateAssetContent(String assetPath, byte[] newContent) { + UnityAsset oldAsset = activePackage.getAssetByPath(assetPath); + if (oldAsset == null) { + LOGGER.warn("Attempted to update content for non-existent asset: {}", assetPath); + return; + } + UnityAsset updatedAsset = + new UnityAsset( + oldAsset.guid(), + oldAsset.assetPath(), + newContent, + oldAsset.metaContent(), + oldAsset.previewContent()); + activePackage.addAsset(updatedAsset); + isModified = true; + LOGGER.info("Updated content for asset {}", assetPath); + } + + public void updateAssetMeta(String assetPath, byte[] newMetaContent) { + UnityAsset oldAsset = activePackage.getAssetByPath(assetPath); + + if (oldAsset == null) { + LOGGER.warn("Attempted to update meta for non-existent asset: {}", assetPath); + return; + } + + // Create a new asset record with the updated meta content + UnityAsset updatedAsset = + new UnityAsset( + oldAsset.guid(), + oldAsset.assetPath(), + oldAsset.content(), + newMetaContent, + oldAsset.previewContent()); + + activePackage.addAsset(updatedAsset); // Overwrites the old asset due to same GUID + isModified = true; + LOGGER.info("Updated metadata for asset {}", assetPath); + } + public void removeAsset(String assetPath) { activePackage.removeAssetByPath(assetPath); isModified = true; diff --git a/src/main/java/com/uview/gui/AssetViewerFrame.java b/src/main/java/com/uview/gui/AssetViewerFrame.java index 23d4bea..14dfb43 100644 --- a/src/main/java/com/uview/gui/AssetViewerFrame.java +++ b/src/main/java/com/uview/gui/AssetViewerFrame.java @@ -1,13 +1,27 @@ package com.uview.gui; +import com.uview.core.PackageManager; import com.uview.model.UnityAsset; -import java.awt.*; +import java.awt.BorderLayout; +import java.awt.Desktop; +import java.awt.Dimension; +import java.awt.FlowLayout; import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.text.DecimalFormat; import java.util.Set; -import javax.swing.*; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; public class AssetViewerFrame extends JFrame { @@ -39,7 +53,14 @@ public class AssetViewerFrame extends JFrame { private static final Set MEDIA_EXTENSIONS = Set.of("mp4", "mov", "wav", "mp3", "ogg"); private static final DecimalFormat FILE_SIZE_FORMAT = new DecimalFormat("#,##0.0 KB"); - public AssetViewerFrame(JFrame owner, UnityAsset asset) { + private final PackageManager packageManager; + private final Runnable onSaveCallback; + + public AssetViewerFrame( + JFrame owner, UnityAsset asset, PackageManager packageManager, Runnable onSaveCallback) { + this.packageManager = packageManager; + this.onSaveCallback = onSaveCallback; + setTitle("Asset Viewer - " + asset.assetPath()); setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); setSize(900, 700); @@ -95,7 +116,7 @@ private JPanel createContentPanel(UnityAsset asset) { String extension = getFileExtension(asset.assetPath()); if (TEXT_EXTENSIONS.contains(extension)) { - return new SyntaxTextPanel(asset); + return createTextEditorPanel(asset); } JPanel panel = new JPanel(new BorderLayout()); @@ -115,6 +136,41 @@ private JPanel createContentPanel(UnityAsset asset) { return panel; } + private JPanel createTextEditorPanel(UnityAsset asset) { + JPanel editorPanel = new JPanel(new BorderLayout(0, 5)); + + JButton saveButton = new JButton("Save"); + saveButton.setEnabled(false); + JButton revertButton = new JButton("Revert"); + revertButton.setEnabled(false); + + SyntaxTextPanel syntaxTextPanel = + new SyntaxTextPanel( + asset, + isDirty -> { + saveButton.setEnabled(isDirty); + revertButton.setEnabled(isDirty); + }); + + saveButton.addActionListener( + e -> { + byte[] newContent = syntaxTextPanel.getText().getBytes(StandardCharsets.UTF_8); + packageManager.updateAssetContent(asset.assetPath(), newContent); + onSaveCallback.run(); + }); + + revertButton.addActionListener(e -> syntaxTextPanel.revert()); + + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + buttonPanel.add(revertButton); + buttonPanel.add(saveButton); + + editorPanel.add(syntaxTextPanel, BorderLayout.CENTER); + editorPanel.add(buttonPanel, BorderLayout.SOUTH); + + return editorPanel; + } + private void handleMediaAsset(UnityAsset asset) { try { File tempFile = diff --git a/src/main/java/com/uview/gui/MainWindow.java b/src/main/java/com/uview/gui/MainWindow.java index 4c7aabe..0a40933 100644 --- a/src/main/java/com/uview/gui/MainWindow.java +++ b/src/main/java/com/uview/gui/MainWindow.java @@ -3,7 +3,10 @@ import com.formdev.flatlaf.extras.FlatSVGIcon; import com.uview.App; import com.uview.core.SettingsManager; -import java.awt.*; +import java.awt.BorderLayout; +import java.awt.Cursor; +import java.awt.Desktop; +import java.awt.Dimension; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.event.MouseAdapter; @@ -14,7 +17,23 @@ import java.nio.file.Path; import java.util.List; import java.util.Properties; -import javax.swing.*; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.Icon; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JSeparator; +import javax.swing.JTabbedPane; +import javax.swing.SwingWorker; +import javax.swing.Timer; +import javax.swing.TransferHandler; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; import javax.swing.filechooser.FileNameExtensionFilter; @@ -53,11 +72,44 @@ public MainWindow() { updateState(); } - private static String formatSize(long bytes) { - if (bytes < 1024) return bytes + " B"; - int exp = (int) (Math.log(bytes) / Math.log(1024)); - char pre = "KMGTPE".charAt(exp - 1); - return String.format("%.1f %sB", bytes / Math.pow(1024, exp), pre); + private class FileDropHandler extends TransferHandler { + @Override + public boolean canImport(TransferSupport support) { + if (!support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { + return false; + } + try { + Transferable t = support.getTransferable(); + List files = (List) t.getTransferData(DataFlavor.javaFileListFlavor); + for (File file : files) { + if (file.isFile() && file.getName().toLowerCase().endsWith(".unitypackage")) { + return true; // Accept drop if at least one unitypackage is present + } + } + } catch (Exception e) { + return false; + } + return false; + } + + @Override + public boolean importData(TransferSupport support) { + if (!canImport(support)) { + return false; + } + try { + Transferable t = support.getTransferable(); + List files = (List) t.getTransferData(DataFlavor.javaFileListFlavor); + for (File file : files) { + if (file.isFile() && file.getName().toLowerCase().endsWith(".unitypackage")) { + openPackage(file); + } + } + return true; + } catch (Exception e) { + return false; + } + } } private JPanel createStatusBar() { @@ -90,6 +142,13 @@ private void updateMemoryUsage() { memoryUsageLabel.setText(String.format("Mem: %s", formatSize(usedMemory))); } + private static String formatSize(long bytes) { + if (bytes < 1024) return bytes + " B"; + int exp = (int) (Math.log(bytes) / Math.log(1024)); + char pre = "KMGTPE".charAt(exp - 1); + return String.format("%.1f %sB", bytes / Math.pow(1024, exp), pre); + } + private JMenuBar createMenuBar() { JMenuBar menuBar = new JMenuBar(); JMenu fileMenu = new JMenu("File"); @@ -369,7 +428,7 @@ protected void done() { worker.execute(); } - private void updateState() { + void updateState() { PackageViewPanel currentPanel = getCurrentPanel(); boolean hasPanel = currentPanel != null; @@ -409,44 +468,4 @@ private void setWorking(boolean working, String status) { statusLabel.setText(status); } } - - private class FileDropHandler extends TransferHandler { - @Override - public boolean canImport(TransferSupport support) { - if (!support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { - return false; - } - try { - Transferable t = support.getTransferable(); - List files = (List) t.getTransferData(DataFlavor.javaFileListFlavor); - for (File file : files) { - if (file.isFile() && file.getName().toLowerCase().endsWith(".unitypackage")) { - return true; // Accept drop if at least one unitypackage is present - } - } - } catch (Exception e) { - return false; - } - return false; - } - - @Override - public boolean importData(TransferSupport support) { - if (!canImport(support)) { - return false; - } - try { - Transferable t = support.getTransferable(); - List files = (List) t.getTransferData(DataFlavor.javaFileListFlavor); - for (File file : files) { - if (file.isFile() && file.getName().toLowerCase().endsWith(".unitypackage")) { - openPackage(file); - } - } - return true; - } catch (Exception e) { - return false; - } - } - } } diff --git a/src/main/java/com/uview/gui/MetaEditorFrame.java b/src/main/java/com/uview/gui/MetaEditorFrame.java new file mode 100644 index 0000000..7395698 --- /dev/null +++ b/src/main/java/com/uview/gui/MetaEditorFrame.java @@ -0,0 +1,83 @@ +package com.uview.gui; + +import com.uview.core.PackageManager; +import com.uview.model.UnityAsset; +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.nio.charset.StandardCharsets; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JPanel; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.SyntaxConstants; +import org.fife.ui.rsyntaxtextarea.Theme; +import org.fife.ui.rtextarea.RTextScrollPane; + +public class MetaEditorFrame extends JDialog { + + private final RSyntaxTextArea textArea; + private final UnityAsset asset; + private final PackageManager packageManager; + private final Runnable onSaveCallback; + + public MetaEditorFrame( + JFrame owner, UnityAsset asset, PackageManager packageManager, Runnable onSaveCallback) { + super(owner, "Edit Meta: " + asset.assetPath(), true); + this.asset = asset; + this.packageManager = packageManager; + this.onSaveCallback = onSaveCallback; + + setSize(600, 400); + setLocationRelativeTo(owner); + setLayout(new BorderLayout(0, 5)); + + textArea = createTextArea(); + add(new RTextScrollPane(textArea), BorderLayout.CENTER); + add(createButtonPanel(), BorderLayout.SOUTH); + } + + private RSyntaxTextArea createTextArea() { + RSyntaxTextArea rSyntaxTextArea = new RSyntaxTextArea(); + rSyntaxTextArea.setEditable(true); + + if (asset.metaContent() != null) { + rSyntaxTextArea.setText(new String(asset.metaContent(), StandardCharsets.UTF_8)); + } else { + String defaultMeta = String.format("fileFormatVersion: 2\nguid: %s\n", asset.guid()); + rSyntaxTextArea.setText(defaultMeta); + } + rSyntaxTextArea.setCaretPosition(0); + + // Apply syntax highlighting and theme + rSyntaxTextArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_YAML); + try { + Theme theme = + Theme.load( + getClass().getResourceAsStream("/org/fife/ui/rsyntaxtextarea/themes/dark.xml")); + theme.apply(rSyntaxTextArea); + } catch (Exception e) { + // Ignore, fallback to default theme + } + return rSyntaxTextArea; + } + + private JPanel createButtonPanel() { + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + JButton cancelButton = new JButton("Cancel"); + cancelButton.addActionListener(e -> dispose()); + JButton saveButton = new JButton("Save"); + saveButton.addActionListener(e -> saveChanges()); + + buttonPanel.add(cancelButton); + buttonPanel.add(saveButton); + return buttonPanel; + } + + private void saveChanges() { + String newMetaText = textArea.getText(); + packageManager.updateAssetMeta(asset.assetPath(), newMetaText.getBytes(StandardCharsets.UTF_8)); + onSaveCallback.run(); + dispose(); + } +} diff --git a/src/main/java/com/uview/gui/PackageViewPanel.java b/src/main/java/com/uview/gui/PackageViewPanel.java index cf3b3b6..59e3005 100644 --- a/src/main/java/com/uview/gui/PackageViewPanel.java +++ b/src/main/java/com/uview/gui/PackageViewPanel.java @@ -6,14 +6,30 @@ import com.uview.gui.tree.TreeEntry; import com.uview.io.PackageIO; import com.uview.model.UnityAsset; -import java.awt.*; +import java.awt.BorderLayout; +import java.awt.Cursor; +import java.awt.Desktop; +import java.awt.Dimension; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; import java.nio.file.Path; import java.util.Collection; import java.util.List; -import javax.swing.*; +import javax.swing.BorderFactory; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.JTextField; +import javax.swing.JTree; +import javax.swing.SwingUtilities; +import javax.swing.SwingWorker; +import javax.swing.Timer; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.tree.DefaultMutableTreeNode; @@ -28,8 +44,8 @@ public class PackageViewPanel extends JPanel { private final DefaultTreeModel treeModel; private final JFrame owner; private final Timer searchDebounceTimer; - private final JTextField searchField; private File packageFile; + private final JTextField searchField; public PackageViewPanel(JFrame owner, File packageFile, SettingsManager settingsManager) { super(new BorderLayout()); @@ -81,9 +97,14 @@ public void mousePressed(MouseEvent e) { if (SwingUtilities.isRightMouseButton(e)) { // Only show menu if we are not on an info message node TreePath path = tree.getClosestPathForLocation(e.getX(), e.getY()); - if (path != null && tree.getModel().getChildCount(path.getLastPathComponent()) > 0) { - tree.setSelectionPath(path); + if (path != null) { + // If clicking on empty space but a node is selected, keep selection. + // If clicking on a different node, select it. + if (tree.getRowForPath(path) != -1 && !tree.isPathSelected(path)) { + tree.setSelectionPath(path); + } } + if (tree.getSelectionPath() != null) { createPopupMenu().show(e.getComponent(), e.getX(), e.getY()); } @@ -131,6 +152,10 @@ private JPopupMenu createPopupMenu() { viewMenuItem.addActionListener(e -> handleDoubleClick()); popup.add(viewMenuItem); + JMenuItem editMetaMenuItem = new JMenuItem("Edit Meta File"); + editMetaMenuItem.addActionListener(e -> editSelectedMetaFile()); + popup.add(editMetaMenuItem); + popup.add(new JSeparator()); JMenuItem addMenuItem = new JMenuItem("Add File..."); @@ -152,9 +177,12 @@ private JPopupMenu createPopupMenu() { if (isNodeSelected) { DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) selectionPath.getLastPathComponent(); - viewMenuItem.setEnabled(selectedNode.getUserObject() instanceof TreeEntry.AssetEntry); + boolean isAsset = selectedNode.getUserObject() instanceof TreeEntry.AssetEntry; + viewMenuItem.setEnabled(isAsset); + editMetaMenuItem.setEnabled(isAsset); // Enable for any asset } else { viewMenuItem.setEnabled(false); + editMetaMenuItem.setEnabled(false); } return popup; @@ -315,11 +343,41 @@ private void handleDoubleClick() { (DefaultMutableTreeNode) selectionPath.getLastPathComponent(); Object userObject = selectedNode.getUserObject(); if (userObject instanceof TreeEntry.AssetEntry entry) { - AssetViewerFrame viewer = new AssetViewerFrame(owner, entry.asset()); + Runnable onSaveCallback = + () -> { + if (owner instanceof MainWindow) { + ((MainWindow) owner).updateState(); + } + }; + AssetViewerFrame viewer = + new AssetViewerFrame((JFrame) owner, entry.asset(), packageManager, onSaveCallback); viewer.setVisible(true); } } + private void editSelectedMetaFile() { + TreePath selectionPath = tree.getSelectionPath(); + if (selectionPath == null) { + return; + } + + DefaultMutableTreeNode selectedNode = + (DefaultMutableTreeNode) selectionPath.getLastPathComponent(); + if (selectedNode.getUserObject() instanceof TreeEntry.AssetEntry entry) { + // This callback will ask the main window to update its state (e.g., enable Save menu item) + Runnable onSaveCallback = + () -> { + if (owner instanceof MainWindow) { + ((MainWindow) owner).updateState(); + } + }; + + MetaEditorFrame editor = + new MetaEditorFrame((JFrame) owner, entry.asset(), packageManager, onSaveCallback); + editor.setVisible(true); + } + } + public void refreshTree() { // When refreshing, apply the current filter text filterTree(); diff --git a/src/main/java/com/uview/gui/SyntaxTextPanel.java b/src/main/java/com/uview/gui/SyntaxTextPanel.java index 0260cf3..b90bd76 100644 --- a/src/main/java/com/uview/gui/SyntaxTextPanel.java +++ b/src/main/java/com/uview/gui/SyntaxTextPanel.java @@ -1,9 +1,12 @@ package com.uview.gui; import com.uview.model.UnityAsset; -import java.awt.*; +import java.awt.BorderLayout; import java.nio.charset.StandardCharsets; -import javax.swing.*; +import java.util.function.Consumer; +import javax.swing.JPanel; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import org.fife.ui.rsyntaxtextarea.Theme; @@ -11,13 +14,19 @@ public class SyntaxTextPanel extends JPanel { - public SyntaxTextPanel(UnityAsset asset) { + private final RSyntaxTextArea textArea; + private final String initialContent; + private boolean isDirty = false; + + public SyntaxTextPanel(UnityAsset asset, Consumer onDirtyStateChange) { super(new BorderLayout()); - RSyntaxTextArea textArea = new RSyntaxTextArea(); - textArea.setEditable(false); + textArea = new RSyntaxTextArea(); + textArea.setEditable(true); + assert asset.content() != null; - textArea.setText(new String(asset.content(), StandardCharsets.UTF_8)); + initialContent = new String(asset.content(), StandardCharsets.UTF_8); + textArea.setText(initialContent); textArea.setCaretPosition(0); // Scroll to the top setSyntaxStyle(textArea, getFileExtension(asset.assetPath())); @@ -31,10 +40,46 @@ public SyntaxTextPanel(UnityAsset asset) { // Fallback to default theme if there's an issue } + textArea + .getDocument() + .addDocumentListener( + new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + updateDirtyState(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + updateDirtyState(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + updateDirtyState(); + } + + private void updateDirtyState() { + boolean wasDirty = isDirty; + isDirty = !textArea.getText().equals(initialContent); + if (isDirty != wasDirty) { + onDirtyStateChange.accept(isDirty); + } + } + }); + RTextScrollPane scrollPane = new RTextScrollPane(textArea); add(scrollPane, BorderLayout.CENTER); } + public String getText() { + return textArea.getText(); + } + + public void revert() { + textArea.setText(initialContent); + } + private void setSyntaxStyle(RSyntaxTextArea textArea, String extension) { String style = switch (extension) { diff --git a/src/main/java/com/uview/model/UnityPackage.java b/src/main/java/com/uview/model/UnityPackage.java index f9fc1f3..ae1c6e4 100644 --- a/src/main/java/com/uview/model/UnityPackage.java +++ b/src/main/java/com/uview/model/UnityPackage.java @@ -63,6 +63,14 @@ public Map getAssets() { return Collections.unmodifiableMap(assetsByGuid); } + public UnityAsset getAssetByPath(String assetPath) { + String guid = pathToGuid.get(assetPath); + if (guid != null) { + return assetsByGuid.get(guid); + } + return null; + } + public void addAsset(UnityAsset asset) { assetsByGuid.put(asset.guid(), asset); pathToGuid.put(asset.assetPath(), asset.guid()); From c99f2f9813e045fe7c77d305860608f8e55f5286 Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Sat, 19 Jul 2025 06:38:51 +0200 Subject: [PATCH 06/10] Allow saving edited files --- pom.xml | 2 +- .../java/com/uview/gui/AssetViewerFrame.java | 1 + .../com/uview/gui/ButtonTabComponent.java | 24 +++- src/main/java/com/uview/gui/MainWindow.java | 72 +++++++++--- .../java/com/uview/gui/PackageViewPanel.java | 32 +++-- .../java/com/uview/gui/SyntaxTextPanel.java | 110 ++++++++++++------ .../java/com/uview/gui/TreeModelBuilder.java | 7 +- 7 files changed, 180 insertions(+), 68 deletions(-) diff --git a/pom.xml b/pom.xml index b968003..5a8f984 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ UView A desktop application for viewing, extracting, and modifying Unity package files. - https://github.com/habedi/uview + https://github.com/pixel-clover/uview UTF-8 diff --git a/src/main/java/com/uview/gui/AssetViewerFrame.java b/src/main/java/com/uview/gui/AssetViewerFrame.java index 14dfb43..ae6fa0e 100644 --- a/src/main/java/com/uview/gui/AssetViewerFrame.java +++ b/src/main/java/com/uview/gui/AssetViewerFrame.java @@ -156,6 +156,7 @@ private JPanel createTextEditorPanel(UnityAsset asset) { e -> { byte[] newContent = syntaxTextPanel.getText().getBytes(StandardCharsets.UTF_8); packageManager.updateAssetContent(asset.assetPath(), newContent); + syntaxTextPanel.markAsSaved(); // Reset the dirty state onSaveCallback.run(); }); diff --git a/src/main/java/com/uview/gui/ButtonTabComponent.java b/src/main/java/com/uview/gui/ButtonTabComponent.java index e56c188..11c2946 100644 --- a/src/main/java/com/uview/gui/ButtonTabComponent.java +++ b/src/main/java/com/uview/gui/ButtonTabComponent.java @@ -1,11 +1,21 @@ package com.uview.gui; -import java.awt.*; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Graphics; +import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import javax.swing.*; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; import javax.swing.plaf.basic.BasicButtonUI; public class ButtonTabComponent extends JPanel { @@ -65,7 +75,15 @@ public void mouseExited(MouseEvent e) { public void actionPerformed(ActionEvent e) { int i = pane.indexOfTabComponent(ButtonTabComponent.this); if (i != -1) { - pane.remove(i); + Component topLevelAncestor = pane.getTopLevelAncestor(); + if (topLevelAncestor instanceof MainWindow) { + MainWindow mainWindow = (MainWindow) topLevelAncestor; + pane.setSelectedIndex(i); + mainWindow.closePackage(); + } else { + // Fallback just in case, but should not be reached in this app + pane.remove(i); + } } } diff --git a/src/main/java/com/uview/gui/MainWindow.java b/src/main/java/com/uview/gui/MainWindow.java index 0a40933..35ce62a 100644 --- a/src/main/java/com/uview/gui/MainWindow.java +++ b/src/main/java/com/uview/gui/MainWindow.java @@ -11,6 +11,8 @@ import java.awt.datatransfer.Transferable; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -56,10 +58,18 @@ public class MainWindow extends JFrame { /** Constructs the main window and initializes its components. */ public MainWindow() { super("UView"); - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // We will handle the close operation setSize(800, 600); setLocationRelativeTo(null); + addWindowListener( + new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + handleExit(); + } + }); + setJMenuBar(createMenuBar()); tabbedPane = new JTabbedPane(); @@ -199,7 +209,7 @@ public void menuCanceled(MenuEvent e) {} fileMenu.add(new JSeparator()); JMenuItem exitMenuItem = new JMenuItem("Exit"); - exitMenuItem.addActionListener(e -> System.exit(0)); + exitMenuItem.addActionListener(e -> handleExit()); fileMenu.add(exitMenuItem); JMenu helpMenu = new JMenu("Help"); @@ -221,8 +231,8 @@ private void showAboutDialog() { + "

Version: " + version + "

" - + "

A desktop tool for viewing and modifying Unity packages.

" - + "

GitHub: https://github.com/habedi/uview

" + + "

A tool for viewing and modifying Unity packages.

" + + "

GitHub: https://github.com/pixel-clover/uview

" + ""; java.net.URL iconUrl = App.class.getResource("/logo.svg"); @@ -345,22 +355,50 @@ private void saveFileAs() { } } - private void closePackage() { + private boolean confirmAndSaveChanges() { PackageViewPanel currentPanel = getCurrentPanel(); - if (currentPanel == null) return; + if (currentPanel == null || !currentPanel.getPackageManager().isModified()) { + return true; // No changes to save, proceed. + } + + int result = + JOptionPane.showConfirmDialog( + this, + "The package '" + + currentPanel.getTabTitle().replace("*", "") + + "' has unsaved changes. Do you want to save them?", + "Unsaved Changes", + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.WARNING_MESSAGE); + + if (result == JOptionPane.CANCEL_OPTION) { + return false; // User cancelled the action. + } + if (result == JOptionPane.YES_OPTION) { + saveFile(); // Save the changes. + } + return true; // Proceed with closing (either saved or chose not to). + } + + void closePackage() { + if (confirmAndSaveChanges()) { + int selectedIndex = tabbedPane.getSelectedIndex(); + if (selectedIndex != -1) { + tabbedPane.remove(selectedIndex); + } + } + } - if (currentPanel.getPackageManager().isModified()) { - int result = - JOptionPane.showConfirmDialog( - this, - "The current package has unsaved changes. Do you want to save them?", - "Unsaved Changes", - JOptionPane.YES_NO_CANCEL_OPTION, - JOptionPane.WARNING_MESSAGE); - if (result == JOptionPane.CANCEL_OPTION) return; - if (result == JOptionPane.YES_OPTION) saveFile(); + private void handleExit() { + // Iterate through all tabs and check for unsaved changes. + for (int i = 0; i < tabbedPane.getTabCount(); i++) { + tabbedPane.setSelectedIndex(i); + if (!confirmAndSaveChanges()) { + return; // If user cancels at any point, abort the exit. + } } - tabbedPane.remove(tabbedPane.getSelectedIndex()); + dispose(); // Close the window + System.exit(0); // Terminate the application } private void savePackageInBackground(PackageViewPanel panel, File file) { diff --git a/src/main/java/com/uview/gui/PackageViewPanel.java b/src/main/java/com/uview/gui/PackageViewPanel.java index 59e3005..447b27e 100644 --- a/src/main/java/com/uview/gui/PackageViewPanel.java +++ b/src/main/java/com/uview/gui/PackageViewPanel.java @@ -146,7 +146,6 @@ private void filterTree() { private JPopupMenu createPopupMenu() { JPopupMenu popup = new JPopupMenu(); TreePath selectionPath = tree.getSelectionPath(); - boolean isNodeSelected = selectionPath != null; JMenuItem viewMenuItem = new JMenuItem("View"); viewMenuItem.addActionListener(e -> handleDoubleClick()); @@ -163,26 +162,37 @@ private JPopupMenu createPopupMenu() { popup.add(addMenuItem); JMenuItem removeMenuItem = new JMenuItem("Remove"); - removeMenuItem.setEnabled(isNodeSelected); removeMenuItem.addActionListener(e -> removeSelectedAsset()); popup.add(removeMenuItem); popup.add(new JSeparator()); JMenuItem extractSelectedMenuItem = new JMenuItem("Extract Selected..."); - extractSelectedMenuItem.setEnabled(isNodeSelected); extractSelectedMenuItem.addActionListener(e -> extractSelected()); popup.add(extractSelectedMenuItem); - if (isNodeSelected) { + // Default to disabled + viewMenuItem.setEnabled(false); + editMetaMenuItem.setEnabled(false); + removeMenuItem.setEnabled(false); + extractSelectedMenuItem.setEnabled(false); + + if (selectionPath != null) { DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) selectionPath.getLastPathComponent(); - boolean isAsset = selectedNode.getUserObject() instanceof TreeEntry.AssetEntry; - viewMenuItem.setEnabled(isAsset); - editMetaMenuItem.setEnabled(isAsset); // Enable for any asset - } else { - viewMenuItem.setEnabled(false); - editMetaMenuItem.setEnabled(false); + Object userObject = selectedNode.getUserObject(); + + removeMenuItem.setEnabled(true); + extractSelectedMenuItem.setEnabled(true); + + if (userObject instanceof TreeEntry.AssetEntry assetEntry) { + // It's a real asset from the package (file or folder). + // Always allow editing its meta file. + editMetaMenuItem.setEnabled(true); + + // Only enable "View" if it's not a directory (i.e., it has content). + viewMenuItem.setEnabled(!assetEntry.asset().isDirectory()); + } } return popup; @@ -345,6 +355,7 @@ private void handleDoubleClick() { if (userObject instanceof TreeEntry.AssetEntry entry) { Runnable onSaveCallback = () -> { + refreshTree(); if (owner instanceof MainWindow) { ((MainWindow) owner).updateState(); } @@ -367,6 +378,7 @@ private void editSelectedMetaFile() { // This callback will ask the main window to update its state (e.g., enable Save menu item) Runnable onSaveCallback = () -> { + refreshTree(); if (owner instanceof MainWindow) { ((MainWindow) owner).updateState(); } diff --git a/src/main/java/com/uview/gui/SyntaxTextPanel.java b/src/main/java/com/uview/gui/SyntaxTextPanel.java index b90bd76..19d0e8b 100644 --- a/src/main/java/com/uview/gui/SyntaxTextPanel.java +++ b/src/main/java/com/uview/gui/SyntaxTextPanel.java @@ -2,33 +2,42 @@ import com.uview.model.UnityAsset; import java.awt.BorderLayout; +import java.awt.Color; import java.nio.charset.StandardCharsets; import java.util.function.Consumer; import javax.swing.JPanel; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.Style; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; +import org.fife.ui.rsyntaxtextarea.SyntaxScheme; import org.fife.ui.rsyntaxtextarea.Theme; +import org.fife.ui.rsyntaxtextarea.Token; import org.fife.ui.rtextarea.RTextScrollPane; public class SyntaxTextPanel extends JPanel { private final RSyntaxTextArea textArea; - private final String initialContent; - private boolean isDirty = false; + private final Consumer onDirtyStateChange; + private DocumentListener dirtyStateListener; + private String savedContent; public SyntaxTextPanel(UnityAsset asset, Consumer onDirtyStateChange) { super(new BorderLayout()); + this.onDirtyStateChange = onDirtyStateChange; textArea = new RSyntaxTextArea(); textArea.setEditable(true); assert asset.content() != null; - initialContent = new String(asset.content(), StandardCharsets.UTF_8); - textArea.setText(initialContent); + this.savedContent = new String(asset.content(), StandardCharsets.UTF_8); + textArea.setText(savedContent); textArea.setCaretPosition(0); // Scroll to the top + // Clear the undo/redo history after loading the initial content. + textArea.discardAllEdits(); + setSyntaxStyle(textArea, getFileExtension(asset.assetPath())); try { @@ -36,48 +45,83 @@ public SyntaxTextPanel(UnityAsset asset, Consumer onDirtyStateChange) { Theme.load( getClass().getResourceAsStream("/org/fife/ui/rsyntaxtextarea/themes/dark.xml")); theme.apply(textArea); + // Apply custom style overrides after loading the theme. + customizeHighlighting(textArea); } catch (Exception e) { // Fallback to default theme if there's an issue } - textArea - .getDocument() - .addDocumentListener( - new DocumentListener() { - @Override - public void insertUpdate(DocumentEvent e) { - updateDirtyState(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - updateDirtyState(); - } - - @Override - public void changedUpdate(DocumentEvent e) { - updateDirtyState(); - } - - private void updateDirtyState() { - boolean wasDirty = isDirty; - isDirty = !textArea.getText().equals(initialContent); - if (isDirty != wasDirty) { - onDirtyStateChange.accept(isDirty); - } - } - }); - + addDirtyStateListener(); RTextScrollPane scrollPane = new RTextScrollPane(textArea); add(scrollPane, BorderLayout.CENTER); } + private void addDirtyStateListener() { + if (dirtyStateListener == null) { + dirtyStateListener = + new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + handleFirstEdit(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + handleFirstEdit(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + handleFirstEdit(); + } + }; + } + textArea.getDocument().addDocumentListener(dirtyStateListener); + } + + private void handleFirstEdit() { + onDirtyStateChange.accept(true); + // Remove the listener after the first edit for performance. + // The panel is now dirty until explicitly saved or reverted. + textArea.getDocument().removeDocumentListener(dirtyStateListener); + } + + /** Removes distracting highlighting of dots in C# files. */ + private void customizeHighlighting(RSyntaxTextArea textArea) { + // Only apply this customization for C# files. + if (SyntaxConstants.SYNTAX_STYLE_CSHARP.equals(textArea.getSyntaxEditingStyle())) { + SyntaxScheme scheme = textArea.getSyntaxScheme(); + + // Get the default text color and background color from the theme. + Color defaultForegroundColor = scheme.getStyle(Token.IDENTIFIER).foreground; + Color defaultBackgroundColor = textArea.getBackground(); + + // Get the style for the separator token (which handles the dot). + Style separatorStyle = scheme.getStyle(Token.SEPARATOR); + + // Set both foreground and background to the default editor colors. + separatorStyle.foreground = defaultForegroundColor; + separatorStyle.background = defaultBackgroundColor; + } + } + public String getText() { return textArea.getText(); } + public void markAsSaved() { + this.savedContent = textArea.getText(); + textArea.discardAllEdits(); // Clear undo/redo history on save + onDirtyStateChange.accept(false); // Mark as not dirty + addDirtyStateListener(); // Start listening for the next edit + } + public void revert() { - textArea.setText(initialContent); + textArea.getDocument().removeDocumentListener(dirtyStateListener); + textArea.setText(this.savedContent); + textArea.discardAllEdits(); // Clear undo/redo history on revert + onDirtyStateChange.accept(false); // Mark as not dirty + addDirtyStateListener(); // Start listening for the next edit } private void setSyntaxStyle(RSyntaxTextArea textArea, String extension) { diff --git a/src/main/java/com/uview/gui/TreeModelBuilder.java b/src/main/java/com/uview/gui/TreeModelBuilder.java index 33b0150..5080ff5 100644 --- a/src/main/java/com/uview/gui/TreeModelBuilder.java +++ b/src/main/java/com/uview/gui/TreeModelBuilder.java @@ -33,10 +33,9 @@ public static DefaultMutableTreeNode build(Collection assets) { // Now that the parent is guaranteed to exist, create the node for the current asset. if (!nodeMap.containsKey(normalizedPath)) { - TreeEntry entry = - asset.isDirectory() - ? new TreeEntry.DirectoryEntry(path) - : new TreeEntry.AssetEntry(asset); + // FIX: Always use AssetEntry for an actual asset from the package, whether it's + // a file or a folder asset. This preserves the underlying UnityAsset object. + TreeEntry entry = new TreeEntry.AssetEntry(asset); DefaultMutableTreeNode assetNode = new DefaultMutableTreeNode(entry); parentNode.add(assetNode); nodeMap.put(normalizedPath, assetNode); From 27028c806d15dc7bd1859e6bf0564497461790b6 Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Sat, 19 Jul 2025 09:10:38 +0200 Subject: [PATCH 07/10] Improve documentation --- README.md | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4a2c25c..b7d0a07 100644 --- a/README.md +++ b/README.md @@ -19,18 +19,33 @@ A cross platform tool to view and modify `.unitypackage` files without Unity --- -UView is an application for viewing and working with Unity package files without using the Unity Editor. +UView is a Java application for viewing and working with Unity package files without using the Unity Editor. It allows you to view, extract, and modify contents of `.unitypackage` files without needing to import them into a Unity project first. -## Features +### Features - Runs everywhere Java is supported (Windows, macOS, Linux) - Let you, vies, extract, and modify Unity package files -## Contributing +### Getting Started + +To run UView, you need to have Java 17 or later installed on your system. +Typically, you can use your system's package manager to install Java, or you can download it from the [Adoptium](https://adoptium.net/) +website. + +You can download the latest JAR release of UView from the [releases page](https://github.com/pixel-clover/uview/releases). +After downloading, you can run UView using the following command: + +```bash +java -jar uview-.jar +``` + +Replace `` with the actual version number of the JAR file you downloaded. + +### Contributing See [CONTRIBUTING](CONTRIBUTING.md) for details on how to make contributions to this project. -## License +### License UView is available under Apache License, Version 2.0 ([LICENSE](LICENSE)). From f76d25d4c6e9f84b2aeb4c54bf9404a3979cb46b Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Sat, 19 Jul 2025 09:54:28 +0200 Subject: [PATCH 08/10] Add a new logo --- README.md | 44 ++++++++++++++------ logo.svg | 45 ++++++++------------- src/main/java/com/uview/gui/MainWindow.java | 2 +- 3 files changed, 48 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index b7d0a07..dbe2928 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
- UView Logo + UView Logo
@@ -13,39 +13,57 @@ [![Release](https://img.shields.io/github/release/pixel-clover/uview.svg?label=release&style=flat&labelColor=282c34&logo=github)](https://github.com/pixel-clover/uview/releases/latest) [![Downloads](https://img.shields.io/github/downloads/pixel-clover/uview/total?label=downloads&style=flat&labelColor=282c34&logo=github)](https://github.com/pixel-clover/uview/releases) -A cross platform tool to view and modify `.unitypackage` files without Unity +A cross-platform tool for viewing and modifying Unity package files
--- UView is a Java application for viewing and working with Unity package files without using the Unity Editor. -It allows you to view, extract, and modify contents of `.unitypackage` files without needing to import them into a Unity project first. +It allows you to view, extract, and modify the contents of `.unitypackage` files without needing to import them into a Unity project first. ### Features -- Runs everywhere Java is supported (Windows, macOS, Linux) -- Let you, vies, extract, and modify Unity package files +- **Cross-Platform**: Runs on Windows, macOS, and Linux. +- **View & Extract**: Browse the file hierarchy of a package and extract files and folders. +- **Asset Editing**: Edit text-based assets like C# scripts, shaders, and JSON files with syntax highlighting. +- **Meta File Editing**: View and edit the `.meta` files for any asset, including folders. +- **Package Modification**: Add new files to or remove existing files from the package. +- **Modern UI**: A clean, tabbed interface for managing multiple packages. ### Getting Started To run UView, you need to have Java 17 or later installed on your system. -Typically, you can use your system's package manager to install Java, or you can download it from the [Adoptium](https://adoptium.net/) -website. +You can download Java from the [Adoptium](https://adoptium.net/) website. -You can download the latest JAR release of UView from the [releases page](https://github.com/pixel-clover/uview/releases). -After downloading, you can run UView using the following command: +Download the latest JAR release of UView from the [Releases Page](https://github.com/pixel-clover/uview/releases). +After downloading, you can run UView from your terminal or console using the following command: ```bash java -jar uview-.jar -``` +```` -Replace `` with the actual version number of the JAR file you downloaded. +Replace `` with the version you downloaded. + +--- ### Contributing -See [CONTRIBUTING](CONTRIBUTING.md) for details on how to make contributions to this project. +Contributions are welcome! +See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to this project. + +### Acknowledgements + +UView is built using these amazing open-source libraries: + +- [FlatLaf](https://www.formdev.com/flatlaf/) for the modern look and feel. +- [RSyntaxTextArea](https://bobbylight.github.io/RSyntaxTextArea/) for syntax highlighting. +- [Apache Commons Compress](https://commons.apache.org/proper/commons-compress/) for archive handling. + +### Logo + +Box logo is from [SVG Repo](https://www.svgrepo.com/svg/366323/package-inspect). ### License -UView is available under Apache License, Version 2.0 ([LICENSE](LICENSE)). +UView is available under the Apache License, Version 2.0 ([LICENSE](https://www.google.com/search?q=LICENSE)). diff --git a/logo.svg b/logo.svg index d698c92..0eb39e9 100644 --- a/logo.svg +++ b/logo.svg @@ -1,29 +1,16 @@ - - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/uview/gui/MainWindow.java b/src/main/java/com/uview/gui/MainWindow.java index 35ce62a..d188418 100644 --- a/src/main/java/com/uview/gui/MainWindow.java +++ b/src/main/java/com/uview/gui/MainWindow.java @@ -231,7 +231,7 @@ private void showAboutDialog() { + "

Version: " + version + "

" - + "

A tool for viewing and modifying Unity packages.

" + + "

A cross-platform tool for viewing and modifying Unity package files.

" + "

GitHub: https://github.com/pixel-clover/uview

" + ""; From 27c0cf5897971764041178acf687b84748a96a38 Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Sat, 19 Jul 2025 10:11:54 +0200 Subject: [PATCH 09/10] Improve the documentation --- README.md | 29 +++++++++++++++---- docs/assets/screenshots/uview-0.1.0-b.2-1.png | 3 ++ 2 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 docs/assets/screenshots/uview-0.1.0-b.2-1.png diff --git a/README.md b/README.md index dbe2928..5ca4f68 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,29 @@ It allows you to view, extract, and modify the contents of `.unitypackage` files ### Features -- **Cross-Platform**: Runs on Windows, macOS, and Linux. -- **View & Extract**: Browse the file hierarchy of a package and extract files and folders. -- **Asset Editing**: Edit text-based assets like C# scripts, shaders, and JSON files with syntax highlighting. -- **Meta File Editing**: View and edit the `.meta` files for any asset, including folders. -- **Package Modification**: Add new files to or remove existing files from the package. -- **Modern UI**: A clean, tabbed interface for managing multiple packages. +- Runs on Windows, macOS, Linux, or any platform that supports Java +- Allows you to view the contents of `.unitypackage` files and extract specific assets +- Allows you to view and edit the assets, including C# scripts, textures, and prefabs +- Supports editing `.meta` files associated with the assets in the package +- Supports modifying package contents, including adding or removing assets + +### Screenshots + +
+ Main Window +
+ +
+Show more screenshots + +
+ Chat View + Chat View +
+ +
+ +--- ### Getting Started diff --git a/docs/assets/screenshots/uview-0.1.0-b.2-1.png b/docs/assets/screenshots/uview-0.1.0-b.2-1.png new file mode 100644 index 0000000..300bd05 --- /dev/null +++ b/docs/assets/screenshots/uview-0.1.0-b.2-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6b3652d17d476c6bbb935b83a2e86d0b47bd3bc18a67acfbb8da38dd51e35be +size 77119 From 0d9e53bd5c8084490659e31d5c183a882d564990 Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Sat, 19 Jul 2025 10:50:23 +0200 Subject: [PATCH 10/10] Add more screenshots --- README.md | 6 ++--- docs/assets/screenshots/uview-0.1.0-b.2-1.png | 4 ++-- docs/assets/screenshots/uview-0.1.0-b.2-2.png | 3 +++ docs/assets/screenshots/uview-0.1.0-b.2-3.png | 3 +++ logo.svg | 16 -------------- .../java/com/uview/gui/AssetViewerFrame.java | 22 +++++-------------- .../com/uview/gui/ButtonTabComponent.java | 14 ++---------- src/main/java/com/uview/gui/IconManager.java | 3 ++- src/main/java/com/uview/gui/MainWindow.java | 21 ++++++++++++++++-- .../java/com/uview/gui/MetaEditorFrame.java | 8 ++----- .../java/com/uview/gui/PackageViewPanel.java | 22 +++---------------- .../java/com/uview/gui/SyntaxTextPanel.java | 12 +++------- src/main/resources/logo.svg | 16 ++++++++++++++ src/main/resources/logo_metadata.txt | 1 + 14 files changed, 65 insertions(+), 86 deletions(-) create mode 100644 docs/assets/screenshots/uview-0.1.0-b.2-2.png create mode 100644 docs/assets/screenshots/uview-0.1.0-b.2-3.png delete mode 100644 logo.svg create mode 100644 src/main/resources/logo.svg create mode 100644 src/main/resources/logo_metadata.txt diff --git a/README.md b/README.md index 5ca4f68..5929bd8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
- UView Logo + UView Logo
@@ -40,8 +40,8 @@ It allows you to view, extract, and modify the contents of `.unitypackage` files Show more screenshots
- Chat View - Chat View + C# Code + Image View
diff --git a/docs/assets/screenshots/uview-0.1.0-b.2-1.png b/docs/assets/screenshots/uview-0.1.0-b.2-1.png index 300bd05..44b9339 100644 --- a/docs/assets/screenshots/uview-0.1.0-b.2-1.png +++ b/docs/assets/screenshots/uview-0.1.0-b.2-1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c6b3652d17d476c6bbb935b83a2e86d0b47bd3bc18a67acfbb8da38dd51e35be -size 77119 +oid sha256:34a4132c693a27a4852819bf6fac1fcf2e6fe60c37ae7ce6207a7afea2dd543e +size 97116 diff --git a/docs/assets/screenshots/uview-0.1.0-b.2-2.png b/docs/assets/screenshots/uview-0.1.0-b.2-2.png new file mode 100644 index 0000000..5d8d7df --- /dev/null +++ b/docs/assets/screenshots/uview-0.1.0-b.2-2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e250a093cc3a06d490dbe5cd33a4f5869ea23c57f23aedfdd97efc4c928091c +size 73984 diff --git a/docs/assets/screenshots/uview-0.1.0-b.2-3.png b/docs/assets/screenshots/uview-0.1.0-b.2-3.png new file mode 100644 index 0000000..0be955d --- /dev/null +++ b/docs/assets/screenshots/uview-0.1.0-b.2-3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80cb422d133972a28590180757bfc46013156f6a9178195d26541cd05a4c1685 +size 287368 diff --git a/logo.svg b/logo.svg deleted file mode 100644 index 0eb39e9..0000000 --- a/logo.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/java/com/uview/gui/AssetViewerFrame.java b/src/main/java/com/uview/gui/AssetViewerFrame.java index ae6fa0e..71ebe2a 100644 --- a/src/main/java/com/uview/gui/AssetViewerFrame.java +++ b/src/main/java/com/uview/gui/AssetViewerFrame.java @@ -2,26 +2,14 @@ import com.uview.core.PackageManager; import com.uview.model.UnityAsset; -import java.awt.BorderLayout; -import java.awt.Desktop; -import java.awt.Dimension; -import java.awt.FlowLayout; +import java.awt.*; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.text.DecimalFormat; import java.util.Set; -import javax.swing.BorderFactory; -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; +import javax.swing.*; public class AssetViewerFrame extends JFrame { @@ -47,9 +35,11 @@ public class AssetViewerFrame extends JFrame { "yaml", "md", "controller", - "meta"); + "meta", + "lighting"); private static final Set IMAGE_EXTENSIONS = - Set.of("png", "jpg", "jpeg", "gif", "tga", "bmp", "webp", "svg", "ico", "avif", "tiff"); + Set.of( + "png", "jpg", "jpeg", "gif", "tga", "bmp", "webp", "svg", "ico", "avif", "tiff", "tif"); private static final Set MEDIA_EXTENSIONS = Set.of("mp4", "mov", "wav", "mp3", "ogg"); private static final DecimalFormat FILE_SIZE_FORMAT = new DecimalFormat("#,##0.0 KB"); diff --git a/src/main/java/com/uview/gui/ButtonTabComponent.java b/src/main/java/com/uview/gui/ButtonTabComponent.java index 11c2946..6e6d108 100644 --- a/src/main/java/com/uview/gui/ButtonTabComponent.java +++ b/src/main/java/com/uview/gui/ButtonTabComponent.java @@ -1,21 +1,11 @@ package com.uview.gui; -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Component; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.Graphics; -import java.awt.Graphics2D; +import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import javax.swing.BorderFactory; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JTabbedPane; +import javax.swing.*; import javax.swing.plaf.basic.BasicButtonUI; public class ButtonTabComponent extends JPanel { diff --git a/src/main/java/com/uview/gui/IconManager.java b/src/main/java/com/uview/gui/IconManager.java index 8965435..b72425b 100644 --- a/src/main/java/com/uview/gui/IconManager.java +++ b/src/main/java/com/uview/gui/IconManager.java @@ -30,6 +30,7 @@ public final class IconManager { ICON_CACHE.put("bmp", loadIcon("file_image.svg")); ICON_CACHE.put("svg", loadIcon("file_image.svg")); ICON_CACHE.put("tiff", loadIcon("file_image.svg")); + ICON_CACHE.put("tif", loadIcon("file_image.svg")); ICON_CACHE.put("ttf", loadIcon("file_font.svg")); ICON_CACHE.put("shader", loadIcon("file_code.svg")); ICON_CACHE.put("fbx", loadIcon("file_archive.svg")); @@ -40,7 +41,7 @@ public final class IconManager { ICON_CACHE.put("wav", loadIcon("file_audio.svg")); ICON_CACHE.put("mp3", loadIcon("file_audio.svg")); ICON_CACHE.put("txt", loadIcon("file_text.svg")); - ICON_CACHE.put("mat", loadIcon("file_json.svg")); + ICON_CACHE.put("mat", loadIcon("file_text.svg")); ICON_CACHE.put("unity", loadIcon("file_diagram.svg")); ICON_CACHE.put("json", loadIcon("file_json.svg")); ICON_CACHE.put("xml", loadIcon("file_xml.svg")); diff --git a/src/main/java/com/uview/gui/MainWindow.java b/src/main/java/com/uview/gui/MainWindow.java index d188418..ddaf259 100644 --- a/src/main/java/com/uview/gui/MainWindow.java +++ b/src/main/java/com/uview/gui/MainWindow.java @@ -4,6 +4,7 @@ import com.uview.App; import com.uview.core.SettingsManager; import java.awt.BorderLayout; +import java.awt.Component; import java.awt.Cursor; import java.awt.Desktop; import java.awt.Dimension; @@ -231,8 +232,8 @@ private void showAboutDialog() { + "

Version: " + version + "

" - + "

A cross-platform tool for viewing and modifying Unity package files.

" - + "

GitHub: https://github.com/pixel-clover/uview

" + + "

A desktop tool for viewing and modifying Unity packages.

" + + "

GitHub: https://github.com/habedi/uview

" + ""; java.net.URL iconUrl = App.class.getResource("/logo.svg"); @@ -305,6 +306,22 @@ private void openFile() { } private void openPackage(File file) { + // Check if the file is already open in another tab. + if (file != null) { + for (int i = 0; i < tabbedPane.getTabCount(); i++) { + Component comp = tabbedPane.getComponentAt(i); + if (comp instanceof PackageViewPanel panel) { + File openFile = panel.getPackageFile(); + // Compare absolute paths to see if the file is already open. + if (openFile != null && openFile.getAbsolutePath().equals(file.getAbsolutePath())) { + tabbedPane.setSelectedIndex(i); // Switch to the existing tab. + toFront(); // Bring the window to the front. + return; // Stop here, don't open a new tab. + } + } + } + } + setWorking(true, "Loading..."); PackageViewPanel newPanel = new PackageViewPanel(this, file, settingsManager); newPanel.loadPackage( diff --git a/src/main/java/com/uview/gui/MetaEditorFrame.java b/src/main/java/com/uview/gui/MetaEditorFrame.java index 7395698..c5457dd 100644 --- a/src/main/java/com/uview/gui/MetaEditorFrame.java +++ b/src/main/java/com/uview/gui/MetaEditorFrame.java @@ -2,13 +2,9 @@ import com.uview.core.PackageManager; import com.uview.model.UnityAsset; -import java.awt.BorderLayout; -import java.awt.FlowLayout; +import java.awt.*; import java.nio.charset.StandardCharsets; -import javax.swing.JButton; -import javax.swing.JDialog; -import javax.swing.JFrame; -import javax.swing.JPanel; +import javax.swing.*; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import org.fife.ui.rsyntaxtextarea.Theme; diff --git a/src/main/java/com/uview/gui/PackageViewPanel.java b/src/main/java/com/uview/gui/PackageViewPanel.java index 447b27e..e5071f7 100644 --- a/src/main/java/com/uview/gui/PackageViewPanel.java +++ b/src/main/java/com/uview/gui/PackageViewPanel.java @@ -6,30 +6,14 @@ import com.uview.gui.tree.TreeEntry; import com.uview.io.PackageIO; import com.uview.model.UnityAsset; -import java.awt.BorderLayout; -import java.awt.Cursor; -import java.awt.Desktop; -import java.awt.Dimension; +import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; import java.nio.file.Path; import java.util.Collection; import java.util.List; -import javax.swing.BorderFactory; -import javax.swing.JFileChooser; -import javax.swing.JFrame; -import javax.swing.JMenuItem; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JScrollPane; -import javax.swing.JSeparator; -import javax.swing.JTextField; -import javax.swing.JTree; -import javax.swing.SwingUtilities; -import javax.swing.SwingWorker; -import javax.swing.Timer; +import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.tree.DefaultMutableTreeNode; @@ -44,8 +28,8 @@ public class PackageViewPanel extends JPanel { private final DefaultTreeModel treeModel; private final JFrame owner; private final Timer searchDebounceTimer; - private File packageFile; private final JTextField searchField; + private File packageFile; public PackageViewPanel(JFrame owner, File packageFile, SettingsManager settingsManager) { super(new BorderLayout()); diff --git a/src/main/java/com/uview/gui/SyntaxTextPanel.java b/src/main/java/com/uview/gui/SyntaxTextPanel.java index 19d0e8b..618d6a8 100644 --- a/src/main/java/com/uview/gui/SyntaxTextPanel.java +++ b/src/main/java/com/uview/gui/SyntaxTextPanel.java @@ -1,19 +1,13 @@ package com.uview.gui; import com.uview.model.UnityAsset; -import java.awt.BorderLayout; -import java.awt.Color; +import java.awt.*; import java.nio.charset.StandardCharsets; import java.util.function.Consumer; -import javax.swing.JPanel; +import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; -import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; -import org.fife.ui.rsyntaxtextarea.Style; -import org.fife.ui.rsyntaxtextarea.SyntaxConstants; -import org.fife.ui.rsyntaxtextarea.SyntaxScheme; -import org.fife.ui.rsyntaxtextarea.Theme; -import org.fife.ui.rsyntaxtextarea.Token; +import org.fife.ui.rsyntaxtextarea.*; import org.fife.ui.rtextarea.RTextScrollPane; public class SyntaxTextPanel extends JPanel { diff --git a/src/main/resources/logo.svg b/src/main/resources/logo.svg new file mode 100644 index 0000000..7f276a9 --- /dev/null +++ b/src/main/resources/logo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/main/resources/logo_metadata.txt b/src/main/resources/logo_metadata.txt new file mode 100644 index 0000000..fde3916 --- /dev/null +++ b/src/main/resources/logo_metadata.txt @@ -0,0 +1 @@ +Visit https://www.svgrepo.com/svg/366323/package-inspect