浏览代码

Assetbrowser (#506)

* initial commit

* drag and drop working

* drag and drop works for spatials and materials (somewhat)
assetbrowser resizes correctly.

* resizing previews
actually use generated preview

* resizing previews
actually use generated preview

* fixing material vs spatial drop
clean up

* fixing material vs spatial drop
fix proper drop location in SceneViewer
clean up

* delete obsolete files

* reverting changes to SceneComposer

* hooking up delete

* fixing a lot of the outstanding issues with AssetBrowser

* a bit of formatting and fixing

* tweaks

* special handling of project path for gradle projects
Rickard Edén 1 年之前
父节点
当前提交
697a5f237a
共有 51 个文件被更改,包括 3199 次插入77 次删除
  1. 8 0
      jme3-assetbrowser/build.xml
  2. 7 0
      jme3-assetbrowser/manifest.mf
  3. 45 0
      jme3-assetbrowser/nbproject/build-impl.xml
  4. 8 0
      jme3-assetbrowser/nbproject/genfiles.properties
  5. 7 0
      jme3-assetbrowser/nbproject/project.properties
  6. 260 0
      jme3-assetbrowser/nbproject/project.xml
  7. 1 0
      jme3-assetbrowser/nbproject/suite.properties
  8. 155 0
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/AssetBrowser.form
  9. 548 0
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/AssetBrowser.java
  10. 48 0
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/AssetBrowserTopComponent.form
  11. 112 0
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/AssetBrowserTopComponent.java
  12. 10 0
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/Bundle.properties
  13. 43 0
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/Constants.java
  14. 310 0
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/PreviewHelper.java
  15. 48 0
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/dnd/AssetPreviewPopupMenu.java
  16. 94 0
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/dnd/AssetPreviewWidgetMouseListener.java
  17. 55 0
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/icons/Icons.java
  18. 二进制
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/icons/asset.png
  19. 1 0
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/icons/remove_texture.svg
  20. 二进制
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/icons/sound-waves.png
  21. 61 0
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/icons/sound-waves.svg
  22. 68 0
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/widgets/AssetPreviewWidget.form
  23. 182 0
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/widgets/AssetPreviewWidget.java
  24. 4 0
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/widgets/Bundle.properties
  25. 47 0
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/widgets/MatDefPreview.java
  26. 48 0
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/widgets/MaterialPreview.java
  27. 48 0
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/widgets/ModelPreview.java
  28. 45 0
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/widgets/PreviewInteractionListener.java
  29. 46 0
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/widgets/SoundPreview.java
  30. 22 0
      jme3-assetbrowser/src/com/jme3/gde/assetbrowser/widgets/TexturePreview.java
  31. 1 1
      jme3-behaviortrees/nbproject/genfiles.properties
  32. 5 4
      jme3-core/nbproject/project.xml
  33. 58 33
      jme3-core/src/com/jme3/gde/core/assets/ProjectAssetManager.java
  34. 89 0
      jme3-core/src/com/jme3/gde/core/dnd/AssetGrabHandler.java
  35. 16 0
      jme3-core/src/com/jme3/gde/core/dnd/AssetNameHolder.java
  36. 60 0
      jme3-core/src/com/jme3/gde/core/dnd/AssetTransferable.java
  37. 10 0
      jme3-core/src/com/jme3/gde/core/dnd/MaterialDataFlavor.java
  38. 80 0
      jme3-core/src/com/jme3/gde/core/dnd/MaterialDropTargetListener.java
  39. 94 0
      jme3-core/src/com/jme3/gde/core/dnd/SceneViewerDropTargetListener.java
  40. 41 0
      jme3-core/src/com/jme3/gde/core/dnd/SpatialDataFlavor.java
  41. 50 0
      jme3-core/src/com/jme3/gde/core/dnd/StringDataFlavor.java
  42. 41 0
      jme3-core/src/com/jme3/gde/core/dnd/TextureDataFlavor.java
  43. 3 1
      jme3-core/src/com/jme3/gde/core/scene/PreviewRequest.java
  44. 62 6
      jme3-core/src/com/jme3/gde/core/sceneviewer/SceneViewerTopComponent.java
  45. 30 0
      jme3-materialeditor/src/com/jme3/gde/materials/MaterialPreviewOpenSupport.java
  46. 111 0
      jme3-materialeditor/src/com/jme3/gde/materials/dnd/TextureDropTargetListener.java
  47. 25 1
      jme3-materialeditor/src/com/jme3/gde/materials/multiview/widgets/TexturePanel.java
  48. 25 1
      jme3-materialeditor/src/com/jme3/gde/materials/multiview/widgets/TexturePanelSquare.java
  49. 19 25
      jme3-scenecomposer/src/com/jme3/gde/scenecomposer/OpenSceneComposer.java
  50. 42 0
      jme3-scenecomposer/src/com/jme3/gde/scenecomposer/layer.xml
  51. 6 5
      nbproject/project.properties

+ 8 - 0
jme3-assetbrowser/build.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- You may freely edit this file. See harness/README in the NetBeans platform -->
+<!-- for some information on what you could do (e.g. targets to override). -->
+<!-- If you delete this file and reopen the project it will be recreated. -->
+<project name="com.jme3.assetbrowser" default="netbeans" basedir=".">
+    <description>Builds, tests, and runs the project com.jme3.assetbrowser.</description>
+    <import file="nbproject/build-impl.xml"/>
+</project>

+ 7 - 0
jme3-assetbrowser/manifest.mf

@@ -0,0 +1,7 @@
+Manifest-Version: 1.0
+AutoUpdate-Show-In-Client: true
+OpenIDE-Module: com.jme3.gde.assetbrowser
+OpenIDE-Module-Localizing-Bundle: com/jme3/gde/assetbrowser/Bundle.properties
+OpenIDE-Module-Requires: org.openide.windows.WindowManager
+OpenIDE-Module-Specification-Version: 1.0
+

+ 45 - 0
jme3-assetbrowser/nbproject/build-impl.xml

@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+*** GENERATED FROM project.xml - DO NOT EDIT  ***
+***         EDIT ../build.xml INSTEAD         ***
+-->
+<project name="com.jme3.gde.assetbrowser-impl" basedir="..">
+    <fail message="Please build using Ant 1.7.1 or higher.">
+        <condition>
+            <not>
+                <antversion atleast="1.7.1"/>
+            </not>
+        </condition>
+    </fail>
+    <property file="nbproject/private/suite-private.properties"/>
+    <property file="nbproject/suite.properties"/>
+    <fail unless="suite.dir">You must set 'suite.dir' to point to your containing module suite</fail>
+    <property file="${suite.dir}/nbproject/private/platform-private.properties"/>
+    <property file="${suite.dir}/nbproject/platform.properties"/>
+    <macrodef name="property" uri="http://www.netbeans.org/ns/nb-module-project/2">
+        <attribute name="name"/>
+        <attribute name="value"/>
+        <sequential>
+            <property name="@{name}" value="${@{value}}"/>
+        </sequential>
+    </macrodef>
+    <macrodef name="evalprops" uri="http://www.netbeans.org/ns/nb-module-project/2">
+        <attribute name="property"/>
+        <attribute name="value"/>
+        <sequential>
+            <property name="@{property}" value="@{value}"/>
+        </sequential>
+    </macrodef>
+    <property file="${user.properties.file}"/>
+    <nbmproject2:property name="harness.dir" value="nbplatform.${nbplatform.active}.harness.dir" xmlns:nbmproject2="http://www.netbeans.org/ns/nb-module-project/2"/>
+    <nbmproject2:property name="nbplatform.active.dir" value="nbplatform.${nbplatform.active}.netbeans.dest.dir" xmlns:nbmproject2="http://www.netbeans.org/ns/nb-module-project/2"/>
+    <nbmproject2:evalprops property="cluster.path.evaluated" value="${cluster.path}" xmlns:nbmproject2="http://www.netbeans.org/ns/nb-module-project/2"/>
+    <fail message="Path to 'platform' cluster missing in $${cluster.path} property or using corrupt Netbeans Platform (missing harness).">
+        <condition>
+            <not>
+                <contains string="${cluster.path.evaluated}" substring="platform"/>
+            </not>
+        </condition>
+    </fail>
+    <import file="${harness.dir}/build.xml"/>
+</project>

+ 8 - 0
jme3-assetbrowser/nbproject/genfiles.properties

@@ -0,0 +1,8 @@
+build.xml.data.CRC32=9efa5489
+build.xml.script.CRC32=f284e28d
[email protected]
+# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
+# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
+nbproject/build-impl.xml.data.CRC32=37385f23
+nbproject/build-impl.xml.script.CRC32=a691b4aa
+nbproject/[email protected]

+ 7 - 0
jme3-assetbrowser/nbproject/project.properties

@@ -0,0 +1,7 @@
+#Thu, 25 Aug 2011 20:26:49 +0200
+javac.source=17
+javac.compilerargs=-Xlint -Xlint:-serial
+license.file=../license-jme.txt
+nbm.homepage=https://www.jmonkeyengine.org
+nbm.module.author=Normen Hansen
+nbm.needs.restart=true

+ 260 - 0
jme3-assetbrowser/nbproject/project.xml

@@ -0,0 +1,260 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://www.netbeans.org/ns/project/1">
+    <type>org.netbeans.modules.apisupport.project</type>
+    <configuration>
+        <data xmlns="http://www.netbeans.org/ns/nb-module-project/3">
+            <code-name-base>com.jme3.gde.assetbrowser</code-name-base>
+            <suite-component/>
+            <module-dependencies>
+                <dependency>
+                    <code-name-base>com.jme3.gde.core</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>3.6.0</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>com.jme3.gde.materials</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>3.6.0</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>com.jme3.gde.scenecomposer</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>3.6.0</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>com.jme3.gde.textureeditor</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>3.6.0</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>com.jme3.gde.core.baselibs</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>3.6.0</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>com.jme3.gde.core.libraries</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>3.6.0</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.api.templates</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>1.6.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.api.visual</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>2.43.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.core.multiview</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.40.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.editor.document</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>1.5.1.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.editor.lib</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>3</release-version>
+                        <specification-version>3.49.2.22.43</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.projectapi</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.60.2</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.projectuiapi</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.78.1.8</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.settings</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.45.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.websvc.jaxws21api</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.34.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.spi.navigator</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.33.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.openide.actions</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>6.38.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.openide.awt</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>7.62.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.openide.dialogs</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>7.38.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.openide.explorer</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>6.57.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.openide.filesystems</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>9.7.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.openide.filesystems.nb</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>9.7.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.openide.loaders</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>7.57.2</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.openide.nodes</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>7.39.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.openide.text</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>6.62.2</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.openide.util</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>8.39.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.openide.util.lookup</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>8.25.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.openide.util.ui</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>9.4.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.openide.windows</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>6.71.1</specification-version>
+                    </run-dependency>
+                </dependency>
+            </module-dependencies>
+            <public-packages>
+                <package>com.jme3.gde.assetBrowser</package>
+            </public-packages>
+        </data>
+    </configuration>
+</project>

+ 1 - 0
jme3-assetbrowser/nbproject/suite.properties

@@ -0,0 +1 @@
+suite.dir=${basedir}/..

+ 155 - 0
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/AssetBrowser.form

@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.8" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+  <Properties>
+    <Property name="alignmentX" type="float" value="0.0"/>
+    <Property name="alignmentY" type="float" value="0.0"/>
+    <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+      <Dimension value="[2000, 2000]"/>
+    </Property>
+  </Properties>
+  <AuxValues>
+    <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
+    <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
+    <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+    <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+    <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-117"/>
+  </AuxValues>
+
+  <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout">
+    <Property name="axis" type="int" value="3"/>
+  </Layout>
+  <SubComponents>
+    <Component class="javax.swing.JLabel" name="projectLabel">
+      <Properties>
+        <Property name="horizontalAlignment" type="int" value="2"/>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="com/jme3/gde/assetbrowser/Bundle.properties" key="AssetBrowser.projectLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+        <Property name="verticalAlignment" type="int" value="1"/>
+        <Property name="alignmentX" type="float" value="0.5"/>
+        <Property name="alignmentY" type="float" value="0.0"/>
+        <Property name="horizontalTextPosition" type="int" value="10"/>
+        <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+          <Dimension value="[32000, 32000]"/>
+        </Property>
+      </Properties>
+      <Events>
+        <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="projectLabelMouseClicked"/>
+      </Events>
+    </Component>
+    <Container class="javax.swing.JScrollPane" name="jScrollPane1">
+      <Properties>
+        <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+          <Dimension value="[150, 150]"/>
+        </Property>
+        <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+          <Dimension value="[2000, 3000]"/>
+        </Property>
+      </Properties>
+
+      <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
+      <SubComponents>
+        <Container class="javax.swing.JPanel" name="previewsPanel">
+          <Properties>
+            <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+              <Dimension value="[200, 300]"/>
+            </Property>
+          </Properties>
+
+          <Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
+        </Container>
+      </SubComponents>
+    </Container>
+    <Container class="javax.swing.JPanel" name="jPanel2">
+      <Properties>
+        <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+          <Dimension value="[2147483647, 23]"/>
+        </Property>
+        <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+          <Dimension value="[104, 33]"/>
+        </Property>
+      </Properties>
+
+      <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout">
+        <Property name="alignment" type="int" value="3"/>
+      </Layout>
+      <SubComponents>
+        <Component class="javax.swing.JTextField" name="filterField">
+          <Properties>
+            <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+              <ResourceString bundle="com/jme3/gde/assetbrowser/Bundle.properties" key="AssetBrowser.filterField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+            </Property>
+            <Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+              <ResourceString bundle="com/jme3/gde/assetbrowser/Bundle.properties" key="AssetBrowser.filterField.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+            </Property>
+            <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+              <Dimension value="[40, 23]"/>
+            </Property>
+            <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+              <Dimension value="[250, 23]"/>
+            </Property>
+          </Properties>
+          <Events>
+            <EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="filterFieldFocusLost"/>
+            <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="filterFieldMouseClicked"/>
+            <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="filterFieldActionPerformed"/>
+            <EventHandler event="keyPressed" listener="java.awt.event.KeyListener" parameters="java.awt.event.KeyEvent" handler="filterFieldKeyPressed"/>
+          </Events>
+        </Component>
+        <Component class="javax.swing.JButton" name="clearFilterButton">
+          <Properties>
+            <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
+              <Connection code="Icons.clearFilter" type="code"/>
+            </Property>
+            <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+              <ResourceString bundle="com/jme3/gde/assetbrowser/Bundle.properties" key="AssetBrowser.clearFilterButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+            </Property>
+            <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+              <Dimension value="[23, 23]"/>
+            </Property>
+            <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+              <Dimension value="[23, 23]"/>
+            </Property>
+            <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+              <Dimension value="[23, 23]"/>
+            </Property>
+          </Properties>
+          <Events>
+            <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="clearFilterButtonMouseClicked"/>
+          </Events>
+        </Component>
+        <Component class="javax.swing.Box$Filler" name="filler1">
+          <Properties>
+            <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+              <Dimension value="[32767, 0]"/>
+            </Property>
+          </Properties>
+          <AuxValues>
+            <AuxValue name="classDetails" type="java.lang.String" value="Box.Filler.HorizontalGlue"/>
+          </AuxValues>
+        </Component>
+        <Component class="javax.swing.JSlider" name="sizeSlider">
+          <Properties>
+            <Property name="maximum" type="int" value="2"/>
+            <Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+              <ResourceString bundle="com/jme3/gde/assetbrowser/Bundle.properties" key="AssetBrowser.sizeSlider.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+            </Property>
+            <Property name="name" type="java.lang.String" value="sizeSlider" noResource="true"/>
+            <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+              <Dimension value="[100, 20]"/>
+            </Property>
+          </Properties>
+          <Events>
+            <EventHandler event="stateChanged" listener="javax.swing.event.ChangeListener" parameters="javax.swing.event.ChangeEvent" handler="sizeSliderStateChanged"/>
+          </Events>
+        </Component>
+      </SubComponents>
+    </Container>
+  </SubComponents>
+</Form>

+ 548 - 0
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/AssetBrowser.java

@@ -0,0 +1,548 @@
+/*
+ *  Copyright (c) 2009-2023 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.assetbrowser;
+
+import com.jme3.gde.assetbrowser.icons.Icons;
+import com.jme3.gde.assetbrowser.widgets.AssetPreviewWidget;
+import com.jme3.gde.assetbrowser.widgets.MatDefPreview;
+import com.jme3.gde.assetbrowser.widgets.MaterialPreview;
+import com.jme3.gde.assetbrowser.widgets.ModelPreview;
+import com.jme3.gde.assetbrowser.widgets.PreviewInteractionListener;
+import com.jme3.gde.assetbrowser.widgets.SoundPreview;
+import com.jme3.gde.assetbrowser.widgets.TexturePreview;
+import com.jme3.gde.core.assets.BinaryModelDataObject;
+import com.jme3.gde.core.assets.ProjectAssetManager;
+import com.jme3.gde.core.util.ProjectSelection;
+import com.jme3.gde.materials.JMEMaterialDataObject;
+import com.jme3.gde.materials.multiview.MaterialOpenSupport;
+import com.jme3.gde.scenecomposer.OpenSceneComposer;
+import com.jme3.gde.scenecomposer.SceneComposerTopComponent;
+import com.jme3.gde.textureeditor.JmeTextureDataObject;
+import com.jme3.gde.textureeditor.OpenTexture;
+import com.jme3.scene.Spatial;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.event.KeyEvent;
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.swing.JOptionPane;
+import org.netbeans.api.project.Project;
+import org.openide.DialogDisplayer;
+import org.openide.NotifyDescriptor;
+import org.openide.filesystems.FileAttributeEvent;
+import org.openide.filesystems.FileChangeListener;
+import org.openide.filesystems.FileEvent;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileRenameEvent;
+import org.openide.loaders.DataObject;
+import org.openide.loaders.DataObjectNotFoundException;
+import org.openide.util.Exceptions;
+
+/**
+ * Top component for AssetBrowser
+ *
+ * @author rickard
+ */
+public class AssetBrowser extends javax.swing.JPanel implements PreviewInteractionListener {
+
+    private static final String MATERIALS = "Materials";
+    private static final String MAT_DEFS = "MatDefs";
+    private static final String MODELS = "Models";
+    private static final String TEXTURES = "Textures";
+    private static final String SOUNDS = "Sounds";
+    private ProjectAssetManager assetManager;
+    private PreviewHelper previewUtil;
+    private String projectName;
+
+    private int lastGridColumns = 0;
+    private int lastGridRows = 0;
+    private String lastFilter;
+
+    private int sizeX = Constants.sizeX;
+    private int sizeY = Constants.sizeY;
+    private int imageSize = Constants.imageSize;
+    private int oldSliderValue = 2;
+
+    private boolean componentListenerAdded = false;
+    private ComponentListener resizeListener = new ComponentListener() {
+            @Override
+            public void componentResized(ComponentEvent e) {
+                setSize(getParent().getSize());
+                setPreferredSize(getParent().getSize());
+                getLayout().layoutContainer(AssetBrowser.this);
+                java.awt.EventQueue.invokeLater(() -> {
+
+                    loadAssets(lastFilter);
+
+                });
+            }
+
+            @Override
+            public void componentMoved(ComponentEvent e) {
+//                setSize(new Dimension(0,0));
+            }
+
+            @Override
+            public void componentShown(ComponentEvent e) {
+            }
+
+            @Override
+            public void componentHidden(ComponentEvent e) {
+            }
+        };
+
+    /**
+     * Creates new form AssetBrowser
+     */
+    public AssetBrowser() {
+
+        initComponents();
+        
+        addComponentListener(resizeListener);
+    }
+
+    /**
+     * Will recalculate grid, and remove all previews and regenerate if rows or
+     * columns or filter has changed
+     *
+     * @param filter only show previews containing filter
+     */
+    private void loadAssets(String filter) {
+        if (assetManager == null) {
+            return;
+        }
+
+        // this is required to make the panel resize
+        if (!componentListenerAdded && getParent() != null) {
+            getParent().addComponentListener(resizeListener);
+            componentListenerAdded = true;
+            removeComponentListener(resizeListener);
+        }
+        Dimension size = previewsPanel.getSize();
+
+        int rows = Math.min(size.height, getHeight() - 30) / sizeY;
+
+        final var textures = Arrays.stream(assetManager.getTextures()).filter(s -> filter.isEmpty() || s.toLowerCase().contains(filter)).collect(Collectors.toList());
+        final var materials = Arrays.stream(assetManager.getMaterials()).filter(s -> filter.isEmpty() || s.toLowerCase().contains(filter)).collect(Collectors.toList());
+        final var models = Arrays.stream(assetManager.getModels()).filter(s -> filter.isEmpty() || s.toLowerCase().contains(filter)).collect(Collectors.toList());
+        final var sounds = Arrays.stream(assetManager.getSounds()).filter(s -> filter.isEmpty() || s.toLowerCase().contains(filter)).collect(Collectors.toList());
+        final var matdefs = Arrays.stream(assetManager.getMatDefs()).filter(s -> filter.isEmpty() || s.toLowerCase().contains(filter)).collect(Collectors.toList());
+        int numAssets = textures.size() + materials.size() + models.size() + sounds.size() + matdefs.size();
+        int columns = Math.max(numAssets / rows, 1);
+
+        Dimension newSize = new Dimension(columns * sizeX, rows * sizeY);
+        if (columns != lastGridColumns || rows != lastGridRows || !lastFilter.equals(filter)) {
+            GridBagConstraints constraints = new GridBagConstraints();
+            previewsPanel.setLayout(new GridBagLayout());
+            constraints.fill = GridBagConstraints.BOTH;
+            constraints.gridx = sizeX;
+            constraints.gridy = sizeY;
+            previewsPanel.removeAll();
+            previewsPanel.setSize(newSize);
+            previewsPanel.setPreferredSize(newSize);
+
+            previewsPanel.setLayout(new GridBagLayout());
+
+            int index = addAssets(textures, TEXTURES, constraints, columns, rows, 0);
+            index = addAssets(materials, MATERIALS, constraints, columns, rows, index);
+            index = addAssets(models, MODELS, constraints, columns, rows, index);
+            index = addAssets(sounds, SOUNDS, constraints, columns, rows, index);
+            index = addAssets(matdefs, MAT_DEFS, constraints, columns, rows, index);
+            lastGridColumns = columns;
+            lastGridRows = rows;
+            lastFilter = filter;
+        }
+    }
+
+    /**
+     * Add assets of a specific type to the grid
+     *
+     * @param items the assets to preview
+     * @param type type of asset
+     * @param constraints
+     * @param columns columns in the grid
+     * @param rows rows in the grid
+     * @param startIndex last used index when adding previews
+     * @return
+     */
+    private int addAssets(List<String> items, String type, GridBagConstraints constraints, int columns, int rows, int startIndex) {
+        Collections.sort(items);
+        int index = startIndex;
+        for (String item : items) {
+            AssetPreviewWidget preview = null;
+
+            constraints.gridx = index % columns;
+            constraints.gridy = (int) (((float) index-1) / (columns));
+            if (type.startsWith(TEXTURES)) {
+                preview = new TexturePreview(this, previewUtil.getOrCreateTexturePreview(item, imageSize));
+            } else if (type.startsWith(MATERIALS)) {
+                preview = new MaterialPreview(this);
+                preview.setPreviewImage(previewUtil.getOrCreateMaterialPreview(item, preview, imageSize));
+            } else if (type.startsWith(MODELS)) {
+                preview = new ModelPreview(this);
+                preview.setPreviewImage(previewUtil.getOrCreateModelPreview(item, preview, imageSize));
+            } else if (type.startsWith(SOUNDS)) {
+                preview = new SoundPreview(this, previewUtil.getSoundPreview(item, imageSize));
+            } else if (type.startsWith(MAT_DEFS)) {
+                preview = new MatDefPreview(this, previewUtil.getDefaultIcon(item, imageSize));
+            }
+            if (preview == null) {
+                continue;
+            }
+            preview.setMinimumSize(new Dimension(sizeX, sizeY));
+            preview.setPreferredSize(new Dimension(sizeX, sizeY));
+            if (assetManager.getAbsoluteAssetPath(item) != null) {
+                preview.setEditable(true);
+            }
+            preview.setPreviewName(item);
+            previewsPanel.add(preview, constraints);
+            index++;
+        }
+        return index;
+    }
+
+    /**
+     * Creates the base folder for previews in the project directory
+     *
+     * @param assetManager
+     */
+    private void createAssetBrowserFolder(ProjectAssetManager assetManager) {
+        final FileObject fileObject = assetManager.getProject().getProjectDirectory();
+        
+        final String path = assetManager.isGradleProject() ? 
+            fileObject.getParent().getPath() : fileObject.getPath();
+     
+        final File file = new File(path, ".assetBrowser/");
+        if (!file.exists()) {
+            file.mkdirs();
+        }
+    }
+    
+
+    /**
+     * This method is called from within the constructor to initialize the form.
+     * WARNING: Do NOT modify this code. The content of this method is always
+     * regenerated by the Form Editor.
+     */
+    @SuppressWarnings("unchecked")
+    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
+    private void initComponents() {
+
+        projectLabel = new javax.swing.JLabel();
+        jScrollPane1 = new javax.swing.JScrollPane();
+        previewsPanel = new javax.swing.JPanel();
+        jPanel2 = new javax.swing.JPanel();
+        filterField = new javax.swing.JTextField();
+        clearFilterButton = new javax.swing.JButton();
+        filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(32767, 0));
+        sizeSlider = new javax.swing.JSlider();
+
+        setAlignmentX(0.0F);
+        setAlignmentY(0.0F);
+        setPreferredSize(new java.awt.Dimension(2000, 2000));
+        setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.PAGE_AXIS));
+
+        projectLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
+        org.openide.awt.Mnemonics.setLocalizedText(projectLabel, org.openide.util.NbBundle.getMessage(AssetBrowser.class, "AssetBrowser.projectLabel.text")); // NOI18N
+        projectLabel.setVerticalAlignment(javax.swing.SwingConstants.TOP);
+        projectLabel.setAlignmentX(0.5F);
+        projectLabel.setAlignmentY(0.0F);
+        projectLabel.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING);
+        projectLabel.setMaximumSize(new java.awt.Dimension(32000, 32000));
+        projectLabel.addMouseListener(new java.awt.event.MouseAdapter() {
+            public void mouseClicked(java.awt.event.MouseEvent evt) {
+                projectLabelMouseClicked(evt);
+            }
+        });
+        add(projectLabel);
+
+        jScrollPane1.setMinimumSize(new java.awt.Dimension(150, 150));
+        jScrollPane1.setPreferredSize(new java.awt.Dimension(2000, 3000));
+
+        previewsPanel.setPreferredSize(new java.awt.Dimension(200, 300));
+        previewsPanel.setLayout(new java.awt.GridBagLayout());
+        jScrollPane1.setViewportView(previewsPanel);
+
+        add(jScrollPane1);
+
+        jPanel2.setMaximumSize(new java.awt.Dimension(2147483647, 23));
+        jPanel2.setMinimumSize(new java.awt.Dimension(104, 33));
+        jPanel2.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEADING));
+
+        filterField.setText(org.openide.util.NbBundle.getMessage(AssetBrowser.class, "AssetBrowser.filterField.text")); // NOI18N
+        filterField.setToolTipText(org.openide.util.NbBundle.getMessage(AssetBrowser.class, "AssetBrowser.filterField.toolTipText")); // NOI18N
+        filterField.setMinimumSize(new java.awt.Dimension(40, 23));
+        filterField.setPreferredSize(new java.awt.Dimension(250, 23));
+        filterField.addFocusListener(new java.awt.event.FocusAdapter() {
+            public void focusLost(java.awt.event.FocusEvent evt) {
+                filterFieldFocusLost(evt);
+            }
+        });
+        filterField.addMouseListener(new java.awt.event.MouseAdapter() {
+            public void mouseClicked(java.awt.event.MouseEvent evt) {
+                filterFieldMouseClicked(evt);
+            }
+        });
+        filterField.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                filterFieldActionPerformed(evt);
+            }
+        });
+        filterField.addKeyListener(new java.awt.event.KeyAdapter() {
+            public void keyPressed(java.awt.event.KeyEvent evt) {
+                filterFieldKeyPressed(evt);
+            }
+        });
+        jPanel2.add(filterField);
+
+        clearFilterButton.setIcon(Icons.clearFilter);
+        org.openide.awt.Mnemonics.setLocalizedText(clearFilterButton, org.openide.util.NbBundle.getMessage(AssetBrowser.class, "AssetBrowser.clearFilterButton.text")); // NOI18N
+        clearFilterButton.setMaximumSize(new java.awt.Dimension(23, 23));
+        clearFilterButton.setMinimumSize(new java.awt.Dimension(23, 23));
+        clearFilterButton.setPreferredSize(new java.awt.Dimension(23, 23));
+        clearFilterButton.addMouseListener(new java.awt.event.MouseAdapter() {
+            public void mouseClicked(java.awt.event.MouseEvent evt) {
+                clearFilterButtonMouseClicked(evt);
+            }
+        });
+        jPanel2.add(clearFilterButton);
+        jPanel2.add(filler1);
+
+        sizeSlider.setMaximum(2);
+        sizeSlider.setToolTipText(org.openide.util.NbBundle.getMessage(AssetBrowser.class, "AssetBrowser.sizeSlider.toolTipText")); // NOI18N
+        sizeSlider.setName("sizeSlider"); // NOI18N
+        sizeSlider.setPreferredSize(new java.awt.Dimension(100, 20));
+        sizeSlider.addChangeListener(new javax.swing.event.ChangeListener() {
+            public void stateChanged(javax.swing.event.ChangeEvent evt) {
+                sizeSliderStateChanged(evt);
+            }
+        });
+        jPanel2.add(sizeSlider);
+
+        add(jPanel2);
+    }// </editor-fold>//GEN-END:initComponents
+
+    /**
+     * Select project to view
+     *
+     * @param evt
+     */
+    private void projectLabelMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_projectLabelMouseClicked
+        assetManager = ProjectSelection.getProjectAssetManager("Select project");
+        projectName = assetManager.getProject().getProjectDirectory().getName();
+        projectLabel.setText(projectName);
+        previewUtil = new PreviewHelper(assetManager);
+        createAssetBrowserFolder(assetManager);
+        // Check which assets was added/deleted/renamed/changed? Nah, just load
+        // everything!
+        assetManager.getAssetFolder().addRecursiveListener(new FileChangeListener() {
+            @Override
+            public void fileFolderCreated(FileEvent fe) {
+                loadAssets(lastFilter);
+            }
+
+            @Override
+            public void fileDataCreated(FileEvent fe) {
+                loadAssets(lastFilter);
+            }
+
+            @Override
+            public void fileChanged(FileEvent fe) {
+                loadAssets(lastFilter);
+            }
+
+            @Override
+            public void fileDeleted(FileEvent fe) {
+                loadAssets(lastFilter);
+            }
+
+            @Override
+            public void fileRenamed(FileRenameEvent fre) {
+                loadAssets(lastFilter);
+            }
+
+            @Override
+            public void fileAttributeChanged(FileAttributeEvent fae) {
+                loadAssets(lastFilter);
+            }
+        });
+        loadAssets("");
+    }//GEN-LAST:event_projectLabelMouseClicked
+
+    private void filterFieldFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_filterFieldFocusLost
+
+    }//GEN-LAST:event_filterFieldFocusLost
+
+    private void filterFieldKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_filterFieldKeyPressed
+        if (evt.getKeyCode() == KeyEvent.VK_TAB || evt.getKeyCode() == KeyEvent.VK_ENTER) {
+            previewsPanel.requestFocusInWindow();
+            loadAssets(filterField.getText().toLowerCase());
+        }
+    }//GEN-LAST:event_filterFieldKeyPressed
+
+    private void filterFieldMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_filterFieldMouseClicked
+        //filterField.setSelectionStart(0);
+        //filterField.setSelectionEnd(filterField.getSelectedText().length());
+    }//GEN-LAST:event_filterFieldMouseClicked
+
+    private void filterFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_filterFieldActionPerformed
+        // TODO add your handling code here:
+    }//GEN-LAST:event_filterFieldActionPerformed
+
+    /**
+     * Change size of previews
+     *
+     * @param evt
+     */
+    private void sizeSliderStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_sizeSliderStateChanged
+        final var value = sizeSlider.getValue();
+        switch (value) {
+            case 0:
+                sizeX = (int) (Constants.sizeX * 0.5f);
+                sizeY = (int) (Constants.sizeY * 0.5f);
+                imageSize = (int) (Constants.imageSize * 0.5f);
+                break;
+            case 1:
+                sizeX = (int) (Constants.sizeY * 0.75f);
+                sizeY = (int) (Constants.sizeX * 0.75f);
+                imageSize = (int) (Constants.imageSize * 0.75f);
+                break;
+            case 2:
+                sizeX = Constants.sizeY;
+                sizeY = Constants.sizeX;
+                imageSize = Constants.imageSize;
+                break;
+        }
+        if (value != oldSliderValue) {
+            loadAssets(lastFilter);
+            oldSliderValue = value;
+        }
+    }//GEN-LAST:event_sizeSliderStateChanged
+
+    private void clearFilterButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_clearFilterButtonMouseClicked
+        filterField.setText("");
+        lastFilter = "";
+        loadAssets("");
+    }//GEN-LAST:event_clearFilterButtonMouseClicked
+
+
+    // Variables declaration - do not modify//GEN-BEGIN:variables
+    private javax.swing.JButton clearFilterButton;
+    private javax.swing.Box.Filler filler1;
+    private javax.swing.JTextField filterField;
+    private javax.swing.JPanel jPanel2;
+    private javax.swing.JScrollPane jScrollPane1;
+    private javax.swing.JPanel previewsPanel;
+    private javax.swing.JLabel projectLabel;
+    private javax.swing.JSlider sizeSlider;
+    // End of variables declaration//GEN-END:variables
+
+    /**
+     * Double click an asset to open it (if supported)
+     */
+    @Override
+    public void openAsset(AssetPreviewWidget widget) {
+        FileObject pf = assetManager.getAssetFileObject(widget.getPreviewName());
+        if (widget instanceof MaterialPreview) {
+            try {
+                JMEMaterialDataObject matObject = (JMEMaterialDataObject) DataObject.find(pf);
+                new MaterialOpenSupport(matObject.getPrimaryEntry()).open();
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        } else if (widget instanceof TexturePreview) {
+            try {
+                JmeTextureDataObject textureObject = (JmeTextureDataObject) DataObject.find(pf);
+                OpenTexture openTexture = new OpenTexture(textureObject);
+                openTexture.actionPerformed(null);
+            } catch (DataObjectNotFoundException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+
+        } else if (widget instanceof ModelPreview) {
+            try {
+                BinaryModelDataObject model = (BinaryModelDataObject) DataObject.find(pf);
+                Runnable call = () -> {
+                    assetManager.clearCache();
+                    final Spatial asset = model.loadAsset();
+                    if (asset != null) {
+                        java.awt.EventQueue.invokeLater(() -> {
+                            SceneComposerTopComponent composer = SceneComposerTopComponent.findInstance();
+                            composer.openScene(asset, model, assetManager);
+                        });
+                    } else {
+                        NotifyDescriptor.Confirmation msg = new NotifyDescriptor.Confirmation(
+                                "Error opening " + model.getPrimaryFile().getNameExt(),
+                                NotifyDescriptor.OK_CANCEL_OPTION,
+                                NotifyDescriptor.ERROR_MESSAGE);
+                        DialogDisplayer.getDefault().notify(msg);
+                    }
+
+                };
+                new Thread(call).start();
+            } catch (DataObjectNotFoundException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        } else {
+            JOptionPane.showMessageDialog(null, "Not yet supported");
+        }
+    }
+
+    @Override
+    public void refreshPreview(AssetPreviewWidget widget) {
+        // not yet implemented
+    }
+
+    /**
+     * Delete the asset
+     */
+    @Override
+    public void deleteAsset(AssetPreviewWidget widget) {
+        int result = JOptionPane.showConfirmDialog(null, "Delete asset? " + widget.getAssetName());
+        if (result == JOptionPane.OK_OPTION) {
+            FileObject pf = assetManager.getAssetFileObject(widget.getPreviewName());
+            try {
+                pf.delete();
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+    }
+
+}

+ 48 - 0
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/AssetBrowserTopComponent.form

@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you 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.
+
+-->
+
+<Form version="1.4" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+  <AuxValues>
+    <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
+    <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
+    <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+    <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+    <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
+  </AuxValues>
+
+  <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+  <SubComponents>
+    <Component class="com.jme3.gde.assetbrowser.AssetBrowser" name="assetBrowser1">
+      <Constraints>
+        <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+          <BorderConstraints direction="South"/>
+        </Constraint>
+      </Constraints>
+    </Component>
+  </SubComponents>
+</Form>

+ 112 - 0
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/AssetBrowserTopComponent.java

@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2003-2023 jMonkeyEngine
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * 
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * 
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * 
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.assetbrowser;
+
+import org.netbeans.api.settings.ConvertAsProperties;
+import org.openide.awt.ActionID;
+import org.openide.awt.ActionReference;
+import org.openide.util.NbBundle;
+import org.openide.windows.TopComponent;
+import org.openide.util.NbBundle.Messages;
+
+/**
+ * Top component which displays something.
+ */
+@ConvertAsProperties(
+        dtd = "-//com.jme3.gde.assetbrowser//AssetBrowser//EN",
+        autostore = false
+)
[email protected](
+        preferredID = "AssetBrowserTopComponent",
+        //iconBase="SET/PATH/TO/ICON/HERE",
+        persistenceType = TopComponent.PERSISTENCE_ALWAYS
+)
[email protected](mode = "navigator", openAtStartup = true)
+@ActionID(category = "Window", id = "com.jme3.gde.assetbrowser.AssetBrowserTopComponent")
+@ActionReference(path = "Menu/Window" /*, position = 333 */)
[email protected](
+        displayName = "#CTL_AssetBrowserAction",
+        preferredID = "AssetBrowserTopComponent"
+)
+//@Messages({
+//    "CTL_AssetBrowserAction=AssetBrowser",
+//    "CTL_AssetBrowserTopComponent=AssetBrowser Window",
+//    "HINT_AssetBrowserTopComponent=This is a AssetBrowser window"
+//})
+public final class AssetBrowserTopComponent extends TopComponent {
+
+    public AssetBrowserTopComponent() {
+        initComponents();
+        setName(NbBundle.getMessage(AssetBrowserTopComponent.class, "CTL_AssetBrowserTopComponent"));
+//        setName(Bundle.CTL_AssetBrowserTopComponent());
+        setToolTipText(NbBundle.getMessage(AssetBrowserTopComponent.class, "HINT_AssetBrowserTopComponent"));
+    }
+
+    /**
+     * This method is called from within the constructor to initialize the form.
+     * WARNING: Do NOT modify this code. The content of this method is always
+     * regenerated by the Form Editor.
+     */
+    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
+    private void initComponents() {
+
+        assetBrowser1 = new com.jme3.gde.assetbrowser.AssetBrowser();
+
+        setLayout(new java.awt.BorderLayout());
+        add(assetBrowser1, java.awt.BorderLayout.SOUTH);
+    }// </editor-fold>//GEN-END:initComponents
+
+    // Variables declaration - do not modify//GEN-BEGIN:variables
+    private com.jme3.gde.assetbrowser.AssetBrowser assetBrowser1;
+    // End of variables declaration//GEN-END:variables
+    @Override
+    public void componentOpened() {
+        // TODO add custom code on component opening
+    }
+
+    @Override
+    public void componentClosed() {
+        // TODO add custom code on component closing
+    }
+
+    void writeProperties(java.util.Properties p) {
+        // better to version settings since initial version as advocated at
+        // http://wiki.apidesign.org/wiki/PropertyFiles
+        p.setProperty("version", "1.0");
+        // TODO store your settings
+    }
+
+    void readProperties(java.util.Properties p) {
+        String version = p.getProperty("version");
+        // TODO read your settings according to their version
+    }
+}

+ 10 - 0
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/Bundle.properties

@@ -0,0 +1,10 @@
+OpenIDE-Module-Display-Category=jMonkeyEngine
+OpenIDE-Module-Name=AssetBrowser
+CTL_AssetBrowserAction=AssetBrowser
+CTL_AssetBrowserTopComponent=AssetBrowser
+HINT_AssetBrowserTopComponent=AssetBrowser
+AssetBrowser.projectLabel.text=No project selected
+AssetBrowser.filterField.text=
+AssetBrowser.sizeSlider.toolTipText=Size of previews
+AssetBrowser.clearFilterButton.text=
+AssetBrowser.filterField.toolTipText=Enter text to filter

+ 43 - 0
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/Constants.java

@@ -0,0 +1,43 @@
+/*
+ *  Copyright (c) 2009-2023 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.assetbrowser;
+
+/**
+ *
+ * @author rickard
+ */
+public class Constants {
+    
+    static final int sizeX = 170;
+    static final int sizeY = 180;
+    static final int imageSize = 150;
+}

+ 310 - 0
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/PreviewHelper.java

@@ -0,0 +1,310 @@
+/*
+ *  Copyright (c) 2009-2023 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.assetbrowser;
+
+import com.jme3.gde.assetbrowser.icons.Icons;
+import com.jme3.gde.assetbrowser.widgets.AssetPreviewWidget;
+import com.jme3.gde.core.assets.ProjectAssetManager;
+import com.jme3.gde.core.icons.IconList;
+import com.jme3.gde.core.scene.PreviewRequest;
+import com.jme3.gde.core.scene.SceneApplication;
+import com.jme3.gde.core.scene.SceneListener;
+import com.jme3.gde.core.scene.SceneRequest;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Box;
+import com.jme3.texture.Image;
+import com.jme3.texture.Texture;
+import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import javax.imageio.ImageIO;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import jme3tools.converters.ImageToAwt;
+import org.openide.filesystems.FileObject;
+import org.openide.util.Exceptions;
+
+/**
+ * Helper class for generating preview images
+ * 
+ * @author rickard
+ */
+public class PreviewHelper {
+
+    private static final int PREVIEW_SIZE = 150;
+    private final ProjectAssetManager assetManager;
+
+    private static final Vector3f previewLocation = new Vector3f(4, 4, 7);
+    private static final Vector3f previewLookAt = new Vector3f(0, 0, 0);
+
+    public PreviewHelper(ProjectAssetManager assetManager) {
+        this.assetManager = assetManager;
+    }
+
+    public Icon getOrCreateTexturePreview(String asset, int size) {
+        final var icon = tryGetPreview(asset, size);
+        if (icon != null) {
+            return icon;
+        }
+        System.out.println("creating preview ");
+        Texture texture = assetManager.loadTexture(asset);
+        Image image = texture.getImage();
+
+        BufferedImage buff = ImageToAwt.convert(image, false, false, 0);
+
+        BufferedImage scaled = scaleDown(buff, 150, 150);
+        BufferedImage noAlpha = convertImage(scaled);
+        savePreview(assetManager, asset.split("\\.")[0], noAlpha);
+        return new ImageIcon(noAlpha);
+    }
+
+    public Icon getSoundPreview(String asset, int size) {
+        return Icons.soundIcon;
+    }
+
+    public Icon getDefaultIcon(String asset, int size) {
+        return Icons.assetIcon;
+    }
+
+    public Icon getOrCreateMaterialPreview(String asset, AssetPreviewWidget widget, int size) {
+        final var icon = tryGetPreview(asset, size);
+        if (icon != null) {
+            return icon;
+        }
+
+        Material mat = assetManager.loadMaterial(asset);
+
+        Box boxMesh = new Box(1.75f, 1.75f, 1.75f);
+        Geometry box = new Geometry("previewBox", boxMesh);
+        box.setMaterial(mat);
+        PreviewListener listener = new PreviewListener(assetManager, mat.getAssetName().split("\\.")[0], widget);
+        SceneApplication.getApplication().addSceneListener(listener);
+        SceneApplication.getApplication().enqueue(() -> {
+            SceneApplication.getApplication().getRenderManager().preloadScene(box);
+            java.awt.EventQueue.invokeLater(() -> {
+                MikktspaceTangentGenerator.generate(box);
+                PreviewRequest request = new PreviewRequest(listener, box, PREVIEW_SIZE, PREVIEW_SIZE);
+                request.getCameraRequest().setLocation(previewLocation);
+                request.getCameraRequest().setLookAt(previewLookAt, Vector3f.UNIT_Y);
+                SceneApplication.getApplication().createPreview(request);
+            });
+        });
+        return IconList.asset;
+    }
+
+    private Icon tryGetPreview(String asset, int size) {
+        final var assetPath = assetManager.getAbsoluteAssetPath(asset);
+
+        final FileTime assetModificationTime = getAssetModificationTime(assetPath);
+
+        final File previewFile = loadPreviewFile(assetManager, asset.split("\\.")[0]);
+
+        if (previewFile != null && assetModificationTime != null) {
+            final Path previewPath = previewFile.toPath();
+            if(previewPath == null) {
+                return null;
+            }
+            try {
+                final BasicFileAttributes previewAttributes = Files.readAttributes(
+                        previewPath, BasicFileAttributes.class);
+                final FileTime previewCreationTime = previewAttributes.creationTime();
+
+                if (previewCreationTime.compareTo(assetModificationTime) > 0) {
+                    System.out.println("existing preview OK " + previewFile);
+                    BufferedImage image = ImageIO.read(previewFile);
+                    if (image != null) {
+                        return new ImageIcon(size != PREVIEW_SIZE ? image.getScaledInstance(size, size, 0) : image);
+                    }
+                }
+
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return null;
+    }
+
+    public Icon getOrCreateModelPreview(String asset, AssetPreviewWidget widget, int size) {
+        final var icon = tryGetPreview(asset, size);
+        if (icon != null) {
+            return icon;
+        }
+
+        Material unshaded = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        unshaded.setColor("Color", ColorRGBA.Red);
+
+        Spatial spatial = assetManager.loadModel(asset);
+
+        recurseApplyDefaultMaterial(spatial, unshaded);
+
+        PreviewListener listener = new PreviewListener(assetManager, asset.split("\\.")[0], widget);
+        SceneApplication.getApplication().addSceneListener(listener);
+        SceneApplication.getApplication().enqueue(() -> {
+            SceneApplication.getApplication().getRenderManager().preloadScene(spatial);
+            java.awt.EventQueue.invokeLater(() -> {
+                PreviewRequest request = new PreviewRequest(listener, spatial, PREVIEW_SIZE, PREVIEW_SIZE);
+                request.getCameraRequest().setLocation(previewLocation);
+                request.getCameraRequest().setLookAt(previewLookAt, Vector3f.UNIT_Y);
+                SceneApplication.getApplication().createPreview(request);
+            });
+        });
+        return IconList.asset;
+    }
+
+    /**
+     * Applies unshaded MatDef if spatial has no material already
+     *
+     * @param spatial
+     * @param material
+     */
+    private void recurseApplyDefaultMaterial(Spatial spatial, Material material) {
+        if (spatial instanceof Node) {
+            ((Node) spatial).getChildren().forEach(child -> recurseApplyDefaultMaterial(child, material));
+        } else if (spatial instanceof Geometry) {
+            if (((Geometry) spatial).getMaterial() == null) {
+                spatial.setMaterial(material);
+            }
+        }
+    }
+
+    private FileTime getAssetModificationTime(String assetPath) {
+        if (assetPath == null) {
+            return null;
+        }
+        Path path = new File(assetPath).toPath();
+
+        try {
+            // creating BasicFileAttributes class object using
+            // readAttributes method
+            BasicFileAttributes file_att = Files.readAttributes(
+                    path, BasicFileAttributes.class);
+            return file_att.lastModifiedTime();
+        } catch (IOException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+        return null;
+    }
+
+    private File loadPreviewFile(ProjectAssetManager assetManager, String id) {
+        FileObject fileObject = assetManager.getProject().getProjectDirectory();
+        return new File(fileObject.getPath() + "/.assetBrowser/", id + ".jpg");
+    }
+
+    private void savePreview(ProjectAssetManager assetManager, String id, BufferedImage preview) {
+        FileObject fileObject = assetManager.getProject().getProjectDirectory();
+        String[] fileSections = id.split("/");
+        String fileName = fileSections[fileSections.length - 1];
+        File path = new File(fileObject.getPath() + "/.assetBrowser/" + id.substring(0, id.length() - fileName.length()));
+        File file = new File(path, fileName + ".jpg");
+        try {
+            path.mkdirs();
+            file.createNewFile();
+            ImageIO.write(preview, "jpg", file);
+        } catch (IOException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+    }
+
+    private BufferedImage scaleDown(BufferedImage sourceImage, int targetWidth, int targetHeight) {
+        int sourceWidth = sourceImage.getWidth();
+        int sourceHeight = sourceImage.getHeight();
+
+        BufferedImage targetImage = new BufferedImage(targetWidth, targetHeight, sourceImage.getType());
+
+        Graphics2D g = targetImage.createGraphics();
+        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+        g.drawImage(sourceImage, 0, 0, targetWidth, targetHeight, 0, 0, sourceWidth, sourceHeight, null);
+        g.dispose();
+
+        return targetImage;
+    }
+
+    private class PreviewListener implements SceneListener {
+
+        final AssetPreviewWidget widget;
+        final ProjectAssetManager assetManager;
+        private final String assetName;
+
+        public PreviewListener(ProjectAssetManager assetManager, String assetName, AssetPreviewWidget widget) {
+            this.widget = widget;
+            this.assetManager = assetManager;
+            this.assetName = assetName;
+        }
+
+        @Override
+        public void sceneOpened(SceneRequest request) {
+        }
+
+        @Override
+        public void sceneClosed(SceneRequest request) {
+        }
+
+        @Override
+        public void previewCreated(PreviewRequest request) {
+            if (request.getRequester() == this) {
+                final var image = convertImage(request.getImage());
+                java.awt.EventQueue.invokeLater(() -> {
+                    widget.setPreviewImage(new ImageIcon(image));
+                    savePreview(assetManager, assetName, image);
+                    widget.revalidate();
+                });
+            }
+        }
+    };
+
+    private static BufferedImage convertImage(BufferedImage preview) {
+        final int width = preview.getWidth();
+        final int height = preview.getHeight();
+        BufferedImage converted = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+        Graphics2D g = converted.createGraphics();
+        g.setColor(Color.WHITE);
+        g.fillRect(0, 0, width, height);
+        int w = preview.getWidth();
+        int h = preview.getHeight();
+        g.drawImage(preview, 0, 0, w, h, 0, h, w, 0, null);
+        g.dispose();
+        return converted;
+    }
+}

+ 48 - 0
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/dnd/AssetPreviewPopupMenu.java

@@ -0,0 +1,48 @@
+/*
+ *  Copyright (c) 2009-2023 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.assetbrowser.dnd;
+
+import java.awt.event.ActionListener;
+import javax.swing.JPopupMenu;
+
+/**
+ * Pop up menu for actions on asset previews
+ *
+ * @author rickard
+ */
+public class AssetPreviewPopupMenu extends JPopupMenu {
+
+    public AssetPreviewPopupMenu(ActionListener listener) {
+        add("Refresh").addActionListener(listener);
+        add("Delete").addActionListener(listener);
+    }
+}

+ 94 - 0
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/dnd/AssetPreviewWidgetMouseListener.java

@@ -0,0 +1,94 @@
+/*
+ *  Copyright (c) 2009-2023 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.assetbrowser.dnd;
+
+import com.jme3.gde.assetbrowser.widgets.AssetPreviewWidget;
+import com.jme3.gde.assetbrowser.widgets.PreviewInteractionListener;
+import java.awt.event.MouseAdapter;
+import javax.swing.JOptionPane;
+import javax.swing.TransferHandler;
+
+/**
+ * For handling drag and drop of assets.
+ *
+ * @author rickard
+ */
+public final class AssetPreviewWidgetMouseListener extends MouseAdapter {
+
+    private final AssetPreviewWidget previewWidget;
+    private final PreviewInteractionListener listener;
+    private boolean pressed, moved;
+
+    public AssetPreviewWidgetMouseListener(AssetPreviewWidget previewWidget, PreviewInteractionListener listener) {
+        this.previewWidget = previewWidget;
+        this.listener = listener;
+    }
+
+    @Override
+    public void mouseClicked(final java.awt.event.MouseEvent evt) {
+        if (evt.getClickCount() == 2) {
+            evt.consume();
+            if (previewWidget.isEditable()) {
+                listener.openAsset(previewWidget);
+            } else {
+                JOptionPane.showMessageDialog(null, "Project dependencies can't be edited");
+            }
+        }
+    }
+
+    @Override
+    public void mousePressed(final java.awt.event.MouseEvent evt) {
+        pressed = true;
+
+    }
+
+    @Override
+    public void mouseReleased(final java.awt.event.MouseEvent evt) {
+        pressed = false;
+        moved = false;
+    }
+
+    @Override
+    public void mouseMoved(final java.awt.event.MouseEvent evt) {
+    }
+
+    @Override
+    public void mouseDragged(final java.awt.event.MouseEvent evt) {
+        if (pressed) {
+            moved = true;
+            TransferHandler handler = previewWidget.getTransferHandler();
+            if (handler != null) {
+                handler.exportAsDrag(previewWidget, evt, TransferHandler.COPY);
+            }
+        }
+    }
+}

+ 55 - 0
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/icons/Icons.java

@@ -0,0 +1,55 @@
+/*
+ *  Copyright (c) 2009-2023 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.assetbrowser.icons;
+
+import javax.swing.ImageIcon;
+import org.openide.util.ImageUtilities;
+
+/**
+ * Lists all icons used by the AssetBrowser
+ * @author rickard
+ */
+public class Icons {
+    
+    public static final String ICONS_PATH = "com/jme3/gde/assetbrowser/icons/";
+    public static final String TEXTURE_REMOVE = ICONS_PATH + "remove_texture.svg";
+    // use png for asset preview
+    public static final String SOUND_WAVES = ICONS_PATH + "sound_waves.png";
+    public static final String ASSET = ICONS_PATH + "asset.png";
+    
+    public static final ImageIcon clearFilter =
+            ImageUtilities.loadImageIcon(TEXTURE_REMOVE, false);
+    public static final ImageIcon soundIcon =
+            ImageUtilities.loadImageIcon(SOUND_WAVES, false);
+    public static final ImageIcon assetIcon =
+            ImageUtilities.loadImageIcon(ASSET, false);
+}

二进制
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/icons/asset.png


+ 1 - 0
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/icons/remove_texture.svg

@@ -0,0 +1 @@
+<svg style="height: 16px; width: 16px;" width="16px" height="16px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><g class="" style="" transform="translate(0,0)"><path d="M256 16C123.45 16 16 123.45 16 256s107.45 240 240 240 240-107.45 240-240S388.55 16 256 16zm0 60c99.41 0 180 80.59 180 180s-80.59 180-180 180S76 355.41 76 256 156.59 76 256 76zm-80.625 60c-.97-.005-2.006.112-3.063.313v-.032c-18.297 3.436-45.264 34.743-33.375 46.626l73.157 73.125-73.156 73.126c-14.63 14.625 29.275 58.534 43.906 43.906L256 299.906l73.156 73.156c14.63 14.628 58.537-29.28 43.906-43.906l-73.156-73.125 73.156-73.124c14.63-14.625-29.275-58.5-43.906-43.875L256 212.157l-73.156-73.125c-2.06-2.046-4.56-3.015-7.47-3.03z" fill="#d0021b" fill-opacity="1" stroke="#000000" stroke-opacity="1" stroke-width="4" style="--darkreader-inline-fill: #ffffff; --darkreader-inline-stroke: #e8e6e3;" data-darkreader-inline-fill="" data-darkreader-inline-stroke=""></path></g></svg>

二进制
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/icons/sound-waves.png


+ 61 - 0
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/icons/sound-waves.svg

@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   style="height: 150px; width: 150px;"
+   viewBox="0 0 150 150"
+   version="1.1"
+   id="svg6"
+   sodipodi:docname="sound-waves.svg"
+   inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
+  <metadata
+     id="metadata12">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs10" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1331"
+     inkscape:window-height="750"
+     id="namedview8"
+     showgrid="false"
+     inkscape:zoom="1.84375"
+     inkscape:cx="-8.1963326"
+     inkscape:cy="183.23839"
+     inkscape:window-x="40"
+     inkscape:window-y="0"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg6" />
+  <g
+     class=""
+     transform="matrix(0.25,0,0,0.25,11.414015,15.582851)"
+     id="g4">
+    <path
+       d="M 468.53,236.03 H 486 v 39.94 h -17.47 z m -34.426,51.634 h 17.47 v -63.328 h -17.47 z m -33.848,32.756 h 17.47 V 191.58 h -17.47 z m -32.177,25.276 h 17.47 V 167.483 h -17.47 v 178.17 z m -34.448,-43.521 h 17.47 v -92.35 h -17.47 z m -34.994,69.879 h 17.47 v -236.06 h -17.525 v 236.06 z M 264.2,405.9 h 17.47 V 106.1 H 264.2 Z m -33.848,-46.284 h 17.47 V 152.383 h -17.47 v 207.234 z m -35.016,-58.85 h 17.47 v -87.35 h -17.47 z m -33.847,-20.823 h 17.47 V 231.98 h -17.47 v 48.042 z m -33.848,25.66 h 17.47 v -99.24 h -17.47 v 99.272 z m -33.302,48.04 h 17.47 V 152.678 H 94.34 v 201 z M 60.492,322.941 h 17.47 V 187.333 H 60.492 V 322.975 Z M 26,287.664 H 43.47 V 224.336 H 26 Z"
+       style="fill:#76df76;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-opacity:1"
+       data-darkreader-inline-fill=""
+       data-darkreader-inline-stroke=""
+       id="path2"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>

+ 68 - 0
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/widgets/AssetPreviewWidget.form

@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.4" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+  <Properties>
+    <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+      <Dimension value="[170, 180]"/>
+    </Property>
+    <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+      <Dimension value="[170, 180]"/>
+    </Property>
+  </Properties>
+  <Events>
+    <EventHandler event="mousePressed" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="formMousePressed"/>
+  </Events>
+  <AuxValues>
+    <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
+    <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
+    <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+    <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+  </AuxValues>
+
+  <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+  <SubComponents>
+    <Component class="javax.swing.JLabel" name="assetNameLabel">
+      <Properties>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="com/jme3/gde/assetbrowser/widgets/Bundle.properties" key="AssetPreviewWidget.assetNameLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+      <AccessibilityProperties>
+        <Property name="AccessibleContext.accessibleName" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="com/jme3/gde/assetbrowser/widgets/Bundle.properties" key="AssetPreviewWidget.assetNameLabel.AccessibleContext.accessibleName" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </AccessibilityProperties>
+      <Constraints>
+        <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+          <BorderConstraints direction="South"/>
+        </Constraint>
+      </Constraints>
+    </Component>
+    <Component class="javax.swing.JLabel" name="assetPreviewLabel">
+      <Properties>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="com/jme3/gde/assetbrowser/widgets/Bundle.properties" key="AssetPreviewWidget.assetPreviewLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+        <Property name="verticalAlignment" type="int" value="1"/>
+        <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+          <Dimension value="[150, 150]"/>
+        </Property>
+      </Properties>
+      <AccessibilityProperties>
+        <Property name="AccessibleContext.accessibleName" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="com/jme3/gde/assetbrowser/widgets/Bundle.properties" key="AssetPreviewWidget.assetPreviewLabel.AccessibleContext.accessibleName" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </AccessibilityProperties>
+      <Constraints>
+        <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+          <BorderConstraints direction="Center"/>
+        </Constraint>
+      </Constraints>
+    </Component>
+  </SubComponents>
+</Form>

+ 182 - 0
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/widgets/AssetPreviewWidget.java

@@ -0,0 +1,182 @@
+/*
+ *  Copyright (c) 2009-2023 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.assetbrowser.widgets;
+
+import com.jme3.gde.assetbrowser.dnd.AssetPreviewPopupMenu;
+import com.jme3.gde.assetbrowser.dnd.AssetPreviewWidgetMouseListener;
+import com.jme3.gde.core.icons.IconList;
+import com.jme3.gde.core.scene.PreviewRequest;
+import com.jme3.gde.core.scene.SceneListener;
+import com.jme3.gde.core.scene.SceneRequest;
+import com.jme3.gde.core.dnd.AssetNameHolder;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import javax.swing.Icon;
+
+/**
+ * Displays an asset as an image in the AssetBrowser and handles open action and
+ * dragging (if supported)
+ *
+ * @author rickard
+ */
+public class AssetPreviewWidget extends javax.swing.JPanel implements SceneListener, AssetNameHolder, ActionListener {
+
+    private boolean editable;
+    private PreviewInteractionListener listener;
+
+    /**
+     * Creates new form AssetPreviewWidget
+     */
+    public AssetPreviewWidget() {
+        initComponents();
+    }
+
+    public AssetPreviewWidget(final PreviewInteractionListener listener, Icon icon) {
+        this(listener);
+        assetPreviewLabel.setIcon(icon);
+    }
+
+    public AssetPreviewWidget(final PreviewInteractionListener listener) {
+        this();
+        this.listener = listener;
+        final var mouseListener = new AssetPreviewWidgetMouseListener(this, listener);
+        addMouseListener(mouseListener);
+        addMouseMotionListener(mouseListener);
+        setComponentPopupMenu(new AssetPreviewPopupMenu(this));
+    }
+
+    public void setPreviewImage(Icon icon) {
+        assetPreviewLabel.setIcon(icon);
+    }
+
+    public void setPreviewName(String name) {
+        assetNameLabel.setText(name);
+        setToolTipText(name);
+    }
+
+    public String getPreviewName() {
+        return assetNameLabel.getText();
+    }
+
+    /**
+     * This method is called from within the constructor to initialize the form.
+     * WARNING: Do NOT modify this code. The content of this method is always
+     * regenerated by the Form Editor.
+     */
+    @SuppressWarnings("unchecked")
+    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
+    private void initComponents() {
+
+        assetNameLabel = new javax.swing.JLabel();
+        assetPreviewLabel = new javax.swing.JLabel();
+
+        setMinimumSize(new java.awt.Dimension(170, 180));
+        setPreferredSize(new java.awt.Dimension(170, 180));
+        addMouseListener(new java.awt.event.MouseAdapter() {
+            public void mousePressed(java.awt.event.MouseEvent evt) {
+                formMousePressed(evt);
+            }
+        });
+        setLayout(new java.awt.BorderLayout());
+
+        org.openide.awt.Mnemonics.setLocalizedText(assetNameLabel, org.openide.util.NbBundle.getMessage(AssetPreviewWidget.class, "AssetPreviewWidget.assetNameLabel.text")); // NOI18N
+        add(assetNameLabel, java.awt.BorderLayout.SOUTH);
+        assetNameLabel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(AssetPreviewWidget.class, "AssetPreviewWidget.assetNameLabel.AccessibleContext.accessibleName")); // NOI18N
+
+        org.openide.awt.Mnemonics.setLocalizedText(assetPreviewLabel, org.openide.util.NbBundle.getMessage(AssetPreviewWidget.class, "AssetPreviewWidget.assetPreviewLabel.text")); // NOI18N
+        assetPreviewLabel.setVerticalAlignment(javax.swing.SwingConstants.TOP);
+        assetPreviewLabel.setPreferredSize(new java.awt.Dimension(150, 150));
+        add(assetPreviewLabel, java.awt.BorderLayout.CENTER);
+        assetPreviewLabel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(AssetPreviewWidget.class, "AssetPreviewWidget.assetPreviewLabel.AccessibleContext.accessibleName")); // NOI18N
+    }// </editor-fold>//GEN-END:initComponents
+
+    private void formMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_formMousePressed
+
+    }//GEN-LAST:event_formMousePressed
+
+    // Variables declaration - do not modify//GEN-BEGIN:variables
+    private javax.swing.JLabel assetNameLabel;
+    private javax.swing.JLabel assetPreviewLabel;
+    // End of variables declaration//GEN-END:variables
+
+    @Override
+    public void sceneOpened(SceneRequest request) {
+    }
+
+    @Override
+    public void sceneClosed(SceneRequest request) {
+    }
+
+    @Override
+    public void previewCreated(PreviewRequest request) {
+        if (request.getRequester() == this) {
+            java.awt.EventQueue.invokeLater(() -> {
+                assetPreviewLabel.setIcon(IconList.asset);
+//                    invalidate();
+                revalidate();
+                repaint();
+//                    updateUI();
+            });
+        }
+    }
+
+    @Override
+    public String getAssetName() {
+        return assetNameLabel.getText();
+    }
+
+    @Override
+    public void setAssetName(String name) {
+        assetNameLabel.setText(name);
+    }
+
+    public void setEditable(boolean editable) {
+        this.editable = editable;
+    }
+
+    public boolean isEditable() {
+        return editable;
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        switch (e.getActionCommand()) {
+            case "Refresh":
+                listener.refreshPreview(this);
+                break;
+            case "Delete":
+                listener.deleteAsset(this);
+                break;
+        }
+    }
+
+}

+ 4 - 0
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/widgets/Bundle.properties

@@ -0,0 +1,4 @@
+AssetPreviewWidget.assetNameLabel.text=assetName
+AssetPreviewWidget.assetNameLabel.AccessibleContext.accessibleName=assetNameLabel
+AssetPreviewWidget.assetPreviewLabel.AccessibleContext.accessibleName=assetPreviewLabel
+AssetPreviewWidget.assetPreviewLabel.text=

+ 47 - 0
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/widgets/MatDefPreview.java

@@ -0,0 +1,47 @@
+/*
+ *  Copyright (c) 2009-2023 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.assetbrowser.widgets;
+
+import javax.swing.Icon;
+
+/**
+ * A preview of a MatDef in the AssetBrowser
+ *
+ * @author rickard
+ */
+public class MatDefPreview extends AssetPreviewWidget {
+
+    public MatDefPreview(PreviewInteractionListener listener, Icon icon) {
+        super(listener, icon);
+    }
+
+}

+ 48 - 0
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/widgets/MaterialPreview.java

@@ -0,0 +1,48 @@
+/*
+ *  Copyright (c) 2009-2023 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.assetbrowser.widgets;
+
+import com.jme3.gde.core.dnd.MaterialDataFlavor;
+import com.jme3.gde.core.dnd.AssetGrabHandler;
+
+/**
+ *
+ * @author rickard
+ */
+public class MaterialPreview extends AssetPreviewWidget {
+
+    public MaterialPreview(PreviewInteractionListener listener) {
+        super(listener);
+        setTransferHandler(new AssetGrabHandler(this, new MaterialDataFlavor()));
+    }
+
+}

+ 48 - 0
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/widgets/ModelPreview.java

@@ -0,0 +1,48 @@
+/*
+ *  Copyright (c) 2009-2023 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.assetbrowser.widgets;
+
+import com.jme3.gde.core.dnd.AssetGrabHandler;
+import com.jme3.gde.core.dnd.SpatialDataFlavor;
+
+/**
+ *
+ * @author rickard
+ */
+public class ModelPreview extends AssetPreviewWidget {
+
+    public ModelPreview(PreviewInteractionListener listener) {
+        super(listener);
+        setTransferHandler(new AssetGrabHandler(this, new SpatialDataFlavor()));
+    }
+
+}

+ 45 - 0
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/widgets/PreviewInteractionListener.java

@@ -0,0 +1,45 @@
+/*
+ *  Copyright (c) 2009-2023 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.assetbrowser.widgets;
+
+/**
+ *
+ * @author rickard
+ */
+public interface PreviewInteractionListener {
+
+    void openAsset(AssetPreviewWidget widget);
+
+    void refreshPreview(AssetPreviewWidget widget);
+
+    void deleteAsset(AssetPreviewWidget widget);
+}

+ 46 - 0
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/widgets/SoundPreview.java

@@ -0,0 +1,46 @@
+/*
+ *  Copyright (c) 2009-2023 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.assetbrowser.widgets;
+
+import javax.swing.Icon;
+
+/**
+ * Displaying a preview of a sound in the AssetBrowser
+ *
+ * @author rickard
+ */
+public class SoundPreview extends AssetPreviewWidget {
+
+    public SoundPreview(PreviewInteractionListener listener, Icon icon) {
+        super(listener, icon);
+    }
+}

+ 22 - 0
jme3-assetbrowser/src/com/jme3/gde/assetbrowser/widgets/TexturePreview.java

@@ -0,0 +1,22 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
+ */
+package com.jme3.gde.assetbrowser.widgets;
+
+import com.jme3.gde.core.dnd.AssetGrabHandler;
+import com.jme3.gde.core.dnd.TextureDataFlavor;
+import javax.swing.Icon;
+
+/**
+ *
+ * @author rickard
+ */
+public class TexturePreview extends AssetPreviewWidget {
+
+    public TexturePreview(PreviewInteractionListener listener, Icon icon) {
+        super(listener, icon);
+        setTransferHandler(new AssetGrabHandler(this, new TextureDataFlavor()));
+    }
+
+}

+ 1 - 1
jme3-behaviortrees/nbproject/genfiles.properties

@@ -5,4 +5,4 @@ [email protected]
 # Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
 nbproject/build-impl.xml.data.CRC32=8aca329b
 nbproject/build-impl.xml.script.CRC32=35831a68
-nbproject/build-impl.xml.stylesheet.CRC32=[email protected].1
+nbproject/build-impl.xml.stylesheet.CRC32=[email protected]1

+ 5 - 4
jme3-core/nbproject/project.xml

@@ -12,7 +12,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>1</release-version>
-                        <specification-version>3.3.0</specification-version>
+                        <specification-version>3.6.0</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
@@ -21,20 +21,20 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>1</release-version>
-                        <specification-version>3.3.0</specification-version>
+                        <specification-version>3.6.0</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
                     <code-name-base>com.jme3.gde.core.updatecenters</code-name-base>
                     <run-dependency>
-                        <specification-version>3.3.0</specification-version>
+                        <specification-version>3.6.0</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
                     <code-name-base>com.jme3.gde.project.testdata</code-name-base>
                     <run-dependency>
                         <release-version>1</release-version>
-                        <specification-version>3.3.0</specification-version>
+                        <specification-version>3.6.0</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
@@ -434,6 +434,7 @@
                 <package>com.jme3.gde.core.assets.nodes</package>
                 <package>com.jme3.gde.core.codeless</package>
                 <package>com.jme3.gde.core.completion</package>
+                <package>com.jme3.gde.core.dnd</package>
                 <package>com.jme3.gde.core.editor.icons</package>
                 <package>com.jme3.gde.core.editor.nodes</package>
                 <package>com.jme3.gde.core.errorreport</package>

+ 58 - 33
jme3-core/src/com/jme3/gde/core/assets/ProjectAssetManager.java

@@ -455,64 +455,85 @@ public class ProjectAssetManager extends DesktopAssetManager {
         return input;
     }
 
-    @Deprecated
-    public AssetManager getManager() {
-        return this;
-    }
-
     public String[] getModels() {
-        return filesWithSuffix("j3o");
+        return getModels(true);
     }
 
+    public String[] getModels(boolean includeDependencies) {
+        return filesWithSuffix("j3o", includeDependencies);
+    }
+    
     public String[] getMaterials() {
-        return filesWithSuffix("j3m");
+        return getMaterials(true);
     }
 
+    public String[] getMaterials(boolean includeDependencies) {
+        return filesWithSuffix("j3m", includeDependencies);
+    }
+    
     public String[] getSounds() {
-        ArrayList<String> list = new ArrayList<String>();
-        list.addAll(collectFilesWithSuffix("wav"));
-        list.addAll(collectFilesWithSuffix("ogg"));
-        return list.toArray(new String[list.size()]);
+        return getSounds(true);
     }
 
+    public String[] getSounds(boolean includeDependencies) {
+        ArrayList<String> list = new ArrayList<>();
+        list.addAll(collectFilesWithSuffix("wav", includeDependencies));
+        list.addAll(collectFilesWithSuffix("ogg", includeDependencies));
+        return list.toArray(String[]::new);
+    }
+    
     public String[] getTextures() {
-        ArrayList<String> list = new ArrayList<String>();
-        list.addAll(collectFilesWithSuffix("jpg"));
-        list.addAll(collectFilesWithSuffix("jpeg"));
-        list.addAll(collectFilesWithSuffix("gif"));
-        list.addAll(collectFilesWithSuffix("png"));
-        list.addAll(collectFilesWithSuffix("dds"));
-        list.addAll(collectFilesWithSuffix("pfm"));
-        list.addAll(collectFilesWithSuffix("hdr"));
-        list.addAll(collectFilesWithSuffix("tga"));
-        return list.toArray(new String[list.size()]);
+        return getTextures(true);
+    }
+
+    public String[] getTextures(boolean includeDependencies) {
+        ArrayList<String> list = new ArrayList<>();
+        list.addAll(collectFilesWithSuffix("jpg", includeDependencies));
+        list.addAll(collectFilesWithSuffix("jpeg", includeDependencies));
+        list.addAll(collectFilesWithSuffix("gif", includeDependencies));
+        list.addAll(collectFilesWithSuffix("png", includeDependencies));
+        list.addAll(collectFilesWithSuffix("dds", includeDependencies));
+        list.addAll(collectFilesWithSuffix("pfm", includeDependencies));
+        list.addAll(collectFilesWithSuffix("hdr", includeDependencies));
+        list.addAll(collectFilesWithSuffix("tga", includeDependencies));
+        return list.toArray(String[]::new);
+    }
+    
+    public String[] getMatDefs() {
+        return getMatDefs(true);
     }
 
-    public String[] getMatDefs() {
-        return filesWithSuffix("j3md");
+    public String[] getMatDefs(boolean includeDependencies) {
+        return filesWithSuffix("j3md", includeDependencies);
     }
 
     public List<String>  getProjectShaderNodeDefs() {       
-        return collectProjectFilesWithSuffix("j3sn", new LinkedList<String>());
+        return collectProjectFilesWithSuffix("j3sn", new LinkedList<>());
     }
 
     public  List<String> getDependenciesShaderNodeDefs() {        
-        return collectDependenciesFilesWithSuffix("j3sn", new LinkedList<String>());
+        return collectDependenciesFilesWithSuffix("j3sn", new LinkedList<>());
     }
-
+    
     public String[] getAssetsWithSuffix(String string) {
-        return filesWithSuffix(string);
+        return getAssetsWithSuffix(string, true);
+    }
+
+    public String[] getAssetsWithSuffix(String string, boolean includeDependencies) {
+        return filesWithSuffix(string, includeDependencies);
     }
 
-    private String[] filesWithSuffix(String string) {
-        List<String> list = collectFilesWithSuffix(string);
-        return list.toArray(new String[list.size()]);
+    private String[] filesWithSuffix(String string, boolean includeDependencies) {
+        List<String> list = collectFilesWithSuffix(string, includeDependencies);
+        return list.toArray(String[]::new);
     }
 
-    private List<String> collectFilesWithSuffix(String suffix) {
-        List<String> list = new LinkedList<String>();
+    private List<String> collectFilesWithSuffix(String suffix, boolean includeDependencies) {
+        List<String> list = new LinkedList<>();
         collectProjectFilesWithSuffix(suffix, list);
-        collectDependenciesFilesWithSuffix(suffix, list);
+        if(includeDependencies) {
+            collectDependenciesFilesWithSuffix(suffix, list);
+        }
         return list;
     }
 
@@ -682,6 +703,10 @@ public class ProjectAssetManager extends DesktopAssetManager {
             }
         });
     }
+    
+    public boolean isGradleProject() {
+        return GradleBaseProject.get(project) != null;
+    }
 
     public Mutex mutex() {
         return mutex;

+ 89 - 0
jme3-core/src/com/jme3/gde/core/dnd/AssetGrabHandler.java

@@ -0,0 +1,89 @@
+/*
+ *  Copyright (c) 2009-2023 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.core.dnd;
+
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.JComponent;
+import javax.swing.TransferHandler;
+import javax.swing.TransferHandler.TransferSupport;
+
+/**
+ * Based on:
+ * https://stackoverflow.com/questions/23225958/dragging-between-two-components-in-swing
+ *
+ * @author rickard
+ * @param <T>
+ */
+public class AssetGrabHandler<T extends DataFlavor> extends TransferHandler {
+
+    private static final long serialVersionUID = 1L;
+    private final DataFlavor flavor;
+    private final AssetNameHolder origin;
+
+    public AssetGrabHandler(AssetNameHolder origin, T flavor) {
+        this.origin = origin;
+        this.flavor = flavor;
+    }
+
+    @Override
+    public boolean canImport(TransferSupport info) {
+        return info.isDataFlavorSupported(flavor);
+    }
+
+    @Override
+    public boolean importData(TransferSupport transferSupport) {
+        final Transferable t = transferSupport.getTransferable();
+        try {
+            return t.getTransferData(flavor) != null;
+        } catch (UnsupportedFlavorException | IOException e) {
+            Logger.getLogger(AssetGrabHandler.class.getName()).log(Level.WARNING, "Non-supported flavor {0}", t);
+        }
+        return false;
+    }
+
+    @Override
+    public int getSourceActions(JComponent c) {
+        return TransferHandler.COPY;
+    }
+
+    @Override
+    public Transferable createTransferable(JComponent source) {
+        // We need the values from the list as an object array, otherwise the data flavor won't match in importData
+        return new AssetTransferable(origin, flavor);
+    }
+
+}

+ 16 - 0
jme3-core/src/com/jme3/gde/core/dnd/AssetNameHolder.java

@@ -0,0 +1,16 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
+ */
+package com.jme3.gde.core.dnd;
+
+/**
+ *
+ * @author rickard
+ */
+public interface AssetNameHolder {
+
+    String getAssetName();
+
+    void setAssetName(String name);
+}

+ 60 - 0
jme3-core/src/com/jme3/gde/core/dnd/AssetTransferable.java

@@ -0,0 +1,60 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
+ */
+package com.jme3.gde.core.dnd;
+
+import com.jme3.gde.core.dnd.AssetNameHolder;
+import com.jme3.gde.core.dnd.StringDataFlavor;
+import com.jme3.gde.core.dnd.TextureDataFlavor;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.IOException;
+import javax.swing.JPanel;
+
+/**
+ *
+ * @author rickard
+ * @param <T>
+ */
+public class AssetTransferable<T extends DataFlavor> implements Transferable {
+
+    private DataFlavor[] flavors;
+    private AssetNameHolder string;
+
+    public AssetTransferable(AssetNameHolder name, T flavor) {
+        this.string = name;
+        flavors = new DataFlavor[]{flavor};
+    }
+
+    @Override
+    public DataFlavor[] getTransferDataFlavors() {
+        return flavors;
+    }
+
+    @Override
+    public boolean isDataFlavorSupported(DataFlavor flavor) {
+        for (DataFlavor mine : getTransferDataFlavors()) {
+            if (mine.equals(flavor)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public AssetNameHolder getString() {
+        return string;
+    }
+
+    @Override
+    public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
+        if (isDataFlavorSupported(flavor)) {
+            return getString();
+        } else {
+            throw new UnsupportedFlavorException(flavor);
+        }
+
+    }
+
+}

+ 10 - 0
jme3-core/src/com/jme3/gde/core/dnd/MaterialDataFlavor.java

@@ -0,0 +1,10 @@
+package com.jme3.gde.core.dnd;
+
+/**
+ *
+ * @author rickard
+ */
+public class MaterialDataFlavor extends StringDataFlavor {
+
+    public final static MaterialDataFlavor instance = new MaterialDataFlavor();
+}

+ 80 - 0
jme3-core/src/com/jme3/gde/core/dnd/MaterialDropTargetListener.java

@@ -0,0 +1,80 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
+ */
+package com.jme3.gde.core.dnd;
+
+import com.jme3.gde.core.sceneviewer.SceneViewerTopComponent;
+import com.jme3.math.Vector2f;
+import java.awt.Cursor;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.dnd.DropTargetContext;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.dnd.DropTargetEvent;
+import java.awt.dnd.DropTargetListener;
+
+/**
+ *
+ * @author rickard
+ */
+public class MaterialDropTargetListener implements DropTargetListener {
+
+    private static final Cursor droppableCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
+    private static final Cursor notDroppableCursor = Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
+
+    private final SceneViewerTopComponent rootPanel;
+
+    public MaterialDropTargetListener(SceneViewerTopComponent rootPanel) {
+        this.rootPanel = rootPanel;
+    }
+
+    @Override
+    public void dragEnter(DropTargetDragEvent dtde) {
+    }
+
+    @Override
+    public void dragOver(DropTargetDragEvent dtde) {
+        if (!this.rootPanel.getCursor().equals(droppableCursor)) {
+            this.rootPanel.setCursor(droppableCursor);
+        }
+    }
+
+    @Override
+    public void dropActionChanged(DropTargetDragEvent dtde) {
+    }
+
+    @Override
+    public void dragExit(DropTargetEvent dte) {
+        this.rootPanel.setCursor(notDroppableCursor);
+    }
+
+    @Override
+    public void drop(DropTargetDropEvent dtde) {
+        this.rootPanel.setCursor(Cursor.getDefaultCursor());
+
+        Object transferableObj = null;
+        try {
+            final DataFlavor dragAndDropPanelFlavor = new MaterialDataFlavor();
+
+            final Transferable transferable = dtde.getTransferable();
+
+            if (transferable.isDataFlavorSupported(dragAndDropPanelFlavor)) {
+                transferableObj = dtde.getTransferable().getTransferData(dragAndDropPanelFlavor);
+            }
+
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        }
+
+        if (transferableObj == null) {
+            return;
+        }
+        final int dropYLoc = dtde.getLocation().y;
+        final int dropXLoc = dtde.getLocation().x;
+
+        rootPanel.applyMaterial(((AssetNameHolder) transferableObj).getAssetName(), new Vector2f(dropXLoc, dropYLoc));
+    }
+
+}

+ 94 - 0
jme3-core/src/com/jme3/gde/core/dnd/SceneViewerDropTargetListener.java

@@ -0,0 +1,94 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
+ */
+package com.jme3.gde.core.dnd;
+
+import com.jme3.gde.core.sceneviewer.SceneViewerTopComponent;
+import com.jme3.math.Vector2f;
+import java.awt.Cursor;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.dnd.DropTargetEvent;
+import java.awt.dnd.DropTargetListener;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Handles dropping Materials or Spatial from the AssetBrowser to the
+ * SceneViewer
+ * @author rickard
+ */
+public class SceneViewerDropTargetListener implements DropTargetListener {
+
+    private static final Cursor droppableCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
+    private static final Cursor notDroppableCursor = Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
+
+    private final SceneViewerTopComponent rootPanel;
+
+    public SceneViewerDropTargetListener(final SceneViewerTopComponent rootPanel) {
+        this.rootPanel = rootPanel;
+    }
+
+    @Override
+    public void dragEnter(final DropTargetDragEvent dtde) {
+    }
+
+    @Override
+    public void dragOver(final DropTargetDragEvent dtde) {
+        if (!this.rootPanel.getCursor().equals(droppableCursor)) {
+            this.rootPanel.setCursor(droppableCursor);
+        }
+    }
+
+    @Override
+    public void dropActionChanged(DropTargetDragEvent dtde) {
+    }
+
+    @Override
+    public void dragExit(final DropTargetEvent dte) {
+        this.rootPanel.setCursor(notDroppableCursor);
+    }
+
+    @Override
+    public void drop(final DropTargetDropEvent dtde) {
+        this.rootPanel.setCursor(Cursor.getDefaultCursor());
+
+        AssetNameHolder transferableObj = null;
+        Transferable transferable = null;
+        DataFlavor flavor = null;
+
+        try {
+            transferable = dtde.getTransferable();
+            final DataFlavor[] flavors = transferable.getTransferDataFlavors();
+
+            flavor = flavors[0];
+            // What does the Transferable support
+            if (transferable.isDataFlavorSupported(flavor)) {
+                transferableObj = (AssetNameHolder) dtde.getTransferable().getTransferData(flavor);
+            }
+
+        } catch (UnsupportedFlavorException | IOException ex) {
+            Logger.getLogger(SceneViewerDropTargetListener.class.getName()).log(Level.WARNING, "Non-supported flavor {0}", transferable);
+        }
+
+        if (transferable == null || transferableObj == null) {
+            return;
+        }
+
+        final int dropYLoc = dtde.getLocation().y;
+        final int dropXLoc = dtde.getLocation().x;
+
+        if (flavor instanceof SpatialDataFlavor) {
+            rootPanel.addModel(transferableObj.getAssetName(), new Vector2f(dropXLoc, dropYLoc));
+        } else if (flavor instanceof MaterialDataFlavor) {
+            rootPanel.applyMaterial(transferableObj.getAssetName(), new Vector2f(dropXLoc, dropYLoc));
+        }
+
+    }
+
+}

+ 41 - 0
jme3-core/src/com/jme3/gde/core/dnd/SpatialDataFlavor.java

@@ -0,0 +1,41 @@
+/*
+ *  Copyright (c) 2009-2023 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */package com.jme3.gde.core.dnd;
+
+/**
+ *
+ * @author rickard
+ */
+public class SpatialDataFlavor extends StringDataFlavor {
+
+    public final static SpatialDataFlavor instance = new SpatialDataFlavor();
+
+}

+ 50 - 0
jme3-core/src/com/jme3/gde/core/dnd/StringDataFlavor.java

@@ -0,0 +1,50 @@
+/*
+ *  Copyright (c) 2009-2023 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.core.dnd;
+
+import java.awt.datatransfer.DataFlavor;
+
+/**
+ * Based on:
+ * https://stackoverflow.com/questions/23225958/dragging-between-two-components-in-swing
+ *
+ * @author rickard
+ */
+public class StringDataFlavor extends DataFlavor {
+
+    public StringDataFlavor() {
+
+        super("text/plain", null);
+
+    }
+
+}

+ 41 - 0
jme3-core/src/com/jme3/gde/core/dnd/TextureDataFlavor.java

@@ -0,0 +1,41 @@
+/*
+ *  Copyright (c) 2009-2023 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */package com.jme3.gde.core.dnd;
+
+/**
+ *
+ * @author rickard
+ */
+public class TextureDataFlavor extends StringDataFlavor {
+
+    public final static TextureDataFlavor instance = new TextureDataFlavor();
+
+}

+ 3 - 1
jme3-core/src/com/jme3/gde/core/scene/PreviewRequest.java

@@ -37,7 +37,9 @@ import com.jme3.scene.Spatial;
 import java.awt.image.BufferedImage;
 
 /**
- *
+ * Used to render an image of a scene using SceneApplication. Used by 
+ * Material Editor and Shader Editor, for example.
+ * 
  * @author normenhansen
  */
 public class PreviewRequest {

+ 62 - 6
jme3-core/src/com/jme3/gde/core/sceneviewer/SceneViewerTopComponent.java

@@ -24,13 +24,26 @@
  */
 package com.jme3.gde.core.sceneviewer;
 
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.MaterialKey;
+import com.jme3.collision.CollisionResult;
+import com.jme3.collision.CollisionResults;
+import com.jme3.gde.core.dnd.SceneViewerDropTargetListener;
 import com.jme3.gde.core.filters.FilterExplorerTopComponent;
 import com.jme3.gde.core.icons.IconList;
 import com.jme3.gde.core.scene.SceneApplication;
 import com.jme3.gde.core.scene.SceneRequest;
 import com.jme3.input.awt.AwtKeyInput;
 import com.jme3.input.event.KeyInputEvent;
+import com.jme3.material.Material;
+import com.jme3.math.Ray;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
 import java.awt.Component;
+import java.awt.dnd.DropTarget;
 import java.awt.event.KeyEvent;
 import java.awt.event.KeyListener;
 import java.awt.event.MouseWheelEvent;
@@ -51,7 +64,7 @@ import org.openide.windows.WindowManager;
  * It also contains the top bar.
  */
 @ConvertAsProperties(dtd = "-//com.jme3.gde.core.sceneviewer//SceneViewer//EN",
-autostore = false)
+        autostore = false)
 public final class SceneViewerTopComponent extends TopComponent {
 
     private static SceneViewerTopComponent instance;
@@ -101,10 +114,9 @@ public final class SceneViewerTopComponent extends TopComponent {
                         String action;
                         if (e.getWheelRotation() < 0) {
                             action = "MouseWheel";
-                        }else if (e.getWheelRotation() > 0) {
+                        } else if (e.getWheelRotation() > 0) {
                             action = "MouseWheel-";
-                        }
-                        else {
+                        } else {
                             return null;
                         }
                         if (app.getActiveCameraController() != null) {
@@ -154,6 +166,7 @@ public final class SceneViewerTopComponent extends TopComponent {
         });
         //}
 
+        oGLPanel.setDropTarget(new DropTarget(this, new SceneViewerDropTargetListener(this)));
     }
 
     /**
@@ -352,7 +365,8 @@ public final class SceneViewerTopComponent extends TopComponent {
      * only, i.e. deserialization routines; otherwise you could get a
      * non-deserialized instance. To obtain the singleton instance, use
      * {@link #findInstance}.
-     * @return 
+     *
+     * @return
      */
     public static synchronized SceneViewerTopComponent getDefault() {
         if (instance == null) {
@@ -364,7 +378,8 @@ public final class SceneViewerTopComponent extends TopComponent {
     /**
      * Obtain the SceneViewerTopComponent instance. Never call
      * {@link #getDefault} directly!
-     * @return 
+     *
+     * @return
      */
     public static synchronized SceneViewerTopComponent findInstance() {
         TopComponent win = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
@@ -454,4 +469,45 @@ public final class SceneViewerTopComponent extends TopComponent {
     public UndoRedo getUndoRedo() {
         return Lookup.getDefault().lookup(UndoRedo.class);
     }
+
+    public void applyMaterial(String assetName, Vector2f cursorPosition) {
+        AssetManager assetManager = app.getAssetManager();
+        Spatial spatial = pickWorldSpatial(app.getCamera(), new Vector2f(cursorPosition.x, app.getCamera().getHeight() - cursorPosition.y), app.getRootNode());
+        System.out.println("position " + new Vector2f(cursorPosition.x, app.getCamera().getHeight() - cursorPosition.y));
+        if (spatial != null) {
+            Material material = assetManager.loadAsset(new MaterialKey(assetName));
+            spatial.setMaterial(material);
+        }
+    }
+
+    public void addModel(String assetName, Vector2f cursorPosition) {
+        AssetManager assetManager = app.getAssetManager();
+        Spatial spatial = assetManager.loadModel(assetName);
+        CollisionResult cr = pick(app.getCamera(), cursorPosition, app.getRootNode());
+        spatial.setLocalTranslation(cr != null ? 
+                cr.getContactPoint() : app.getCamera().getWorldCoordinates(cursorPosition, 100f));
+        app.getRootNode().attachChild(spatial);
+    }
+
+    public static Spatial pickWorldSpatial(Camera cam, Vector2f mouseLoc, Node jmeRootNode) {
+        CollisionResult cr = pick(cam, mouseLoc, jmeRootNode);
+        if (cr != null) {
+            return cr.getGeometry();
+        } else {
+            return null;
+        }
+    }
+
+    private static CollisionResult pick(Camera cam, Vector2f mouseLoc, Node node) {
+        CollisionResults results = new CollisionResults();
+        Ray ray = new Ray();
+        Vector3f pos = cam.getWorldCoordinates(mouseLoc, 0).clone();
+        Vector3f dir = cam.getWorldCoordinates(mouseLoc, 0.125f).clone();
+        dir.subtractLocal(pos).normalizeLocal();
+        ray.setOrigin(pos);
+        ray.setDirection(dir);
+        node.collideWith(ray, results);
+        CollisionResult result = results.getClosestCollision();
+        return result;
+    }
 }

+ 30 - 0
jme3-materialeditor/src/com/jme3/gde/materials/MaterialPreviewOpenSupport.java

@@ -0,0 +1,30 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
+ */
+package com.jme3.gde.materials;
+
+import com.jme3.gde.materials.JMEMaterialDataObject;
+import com.jme3.gde.materials.multiview.MaterialEditorTopComponent;
+import org.openide.cookies.CloseCookie;
+import org.openide.cookies.OpenCookie;
+import org.openide.loaders.OpenSupport;
+import org.openide.windows.CloneableTopComponent;
+
+/**
+ *
+ * @author rickard
+ */
+public class MaterialPreviewOpenSupport extends OpenSupport implements OpenCookie, CloseCookie {
+
+    public MaterialPreviewOpenSupport(JMEMaterialDataObject.Entry entry) {
+        super(entry);
+    }
+
+    @Override
+    protected CloneableTopComponent createCloneableTopComponent() {
+        JMEMaterialDataObject dobj = (JMEMaterialDataObject) entry.getDataObject();
+        MaterialEditorTopComponent tc = new MaterialEditorTopComponent(dobj);
+        return tc;
+    }
+}

+ 111 - 0
jme3-materialeditor/src/com/jme3/gde/materials/dnd/TextureDropTargetListener.java

@@ -0,0 +1,111 @@
+/*
+ *  Copyright (c) 2009-2023 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.materials.dnd;
+
+import com.jme3.gde.core.dnd.AssetNameHolder;
+import com.jme3.gde.core.dnd.TextureDataFlavor;
+import java.awt.Cursor;
+import java.awt.datatransfer.Transferable;
+import java.awt.dnd.DropTargetContext;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.dnd.DropTargetEvent;
+import java.awt.dnd.DropTargetListener;
+
+/**
+ *
+ * @author rickard
+ */
+public class TextureDropTargetListener implements DropTargetListener {
+
+    private static final Cursor droppableCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
+    private static final Cursor notDroppableCursor = Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
+
+    private final TextureDropTarget rootPanel;
+
+    public TextureDropTargetListener(TextureDropTarget rootPanel) {
+        this.rootPanel = rootPanel;
+    }
+
+    @Override
+    public void dragEnter(DropTargetDragEvent dtde) {
+    }
+
+    @Override
+    public void dragOver(DropTargetDragEvent dtde) {
+        if (!this.rootPanel.getCursor().equals(droppableCursor)) {
+            this.rootPanel.setCursor(droppableCursor);
+        }
+    }
+
+    @Override
+    public void dropActionChanged(DropTargetDragEvent dtde) {
+    }
+
+    @Override
+    public void dragExit(DropTargetEvent dte) {
+        this.rootPanel.setCursor(notDroppableCursor);
+    }
+
+    @Override
+    public void drop(DropTargetDropEvent dtde) {
+        this.rootPanel.setCursor(Cursor.getDefaultCursor());
+
+        Object transferableObj = null;
+        try {
+
+            final Transferable transferable = dtde.getTransferable();
+
+            if (transferable.isDataFlavorSupported(TextureDataFlavor.instance)) {
+                transferableObj = dtde.getTransferable().getTransferData(TextureDataFlavor.instance);
+            }
+
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        }
+
+        if (transferableObj == null) {
+            return;
+        }
+
+        rootPanel.setTexture("\"" + ((AssetNameHolder) transferableObj).getAssetName() + "\"");
+    }
+
+    public interface TextureDropTarget {
+
+        void setTexture(String texture);
+
+        void setCursor(Cursor cursor);
+
+        Cursor getCursor();
+    }
+}

+ 25 - 1
jme3-materialeditor/src/com/jme3/gde/materials/multiview/widgets/TexturePanel.java

@@ -15,10 +15,13 @@ import com.jme3.gde.core.assets.ProjectAssetManager;
 import com.jme3.gde.core.properties.TexturePropertyEditor;
 import com.jme3.gde.core.properties.preview.TexturePreview;
 import com.jme3.gde.materials.MaterialProperty;
+import com.jme3.gde.materials.dnd.TextureDropTargetListener;
+import com.jme3.gde.materials.dnd.TextureDropTargetListener.TextureDropTarget;
 import com.jme3.gde.materials.multiview.MaterialEditorTopComponent;
 import com.jme3.gde.materials.multiview.widgets.icons.Icons;
 import java.awt.Component;
 import java.awt.Graphics2D;
+import java.awt.dnd.DropTarget;
 import java.awt.image.BufferedImage;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.logging.Level;
@@ -30,7 +33,7 @@ import java.util.logging.Logger;
  *
  * @author normenhansen
  */
-public class TexturePanel extends MaterialPropertyWidget {
+public class TexturePanel extends MaterialPropertyWidget implements TextureDropTarget{
 
     private TexturePropertyEditor editor;
     private ProjectAssetManager manager;
@@ -54,6 +57,8 @@ public class TexturePanel extends MaterialPropertyWidget {
         this.manager = manager;
         editor = new TexturePropertyEditor(manager);
         initComponents();
+        
+        setDropTarget(new DropTarget(this, new TextureDropTargetListener(this)));
     }
 
     private void displayPreview() {
@@ -333,4 +338,23 @@ public class TexturePanel extends MaterialPropertyWidget {
     private javax.swing.JSeparator jSeparator1;
     private javax.swing.JLabel texturePreview;
     // End of variables declaration//GEN-END:variables
+
+    @Override
+    public void setTexture(String name) {
+        property.setValue("");
+        java.awt.EventQueue.invokeLater(() -> {
+            if(name.startsWith("\"")){
+                textureName = name;
+            } else {
+                textureName = "\"" + name + "\"";
+            }
+            property.setValue(textureName);
+            displayPreview();
+            updateFlipRepeat();
+            java.awt.EventQueue.invokeLater(() -> {
+                fireChanged();
+            });
+        });
+        
+    }
 }

+ 25 - 1
jme3-materialeditor/src/com/jme3/gde/materials/multiview/widgets/TexturePanelSquare.java

@@ -37,8 +37,12 @@ import com.jme3.gde.core.assets.ProjectAssetManager;
 import com.jme3.gde.core.properties.TexturePropertyEditor;
 import com.jme3.gde.core.properties.preview.TexturePreview;
 import com.jme3.gde.materials.MaterialProperty;
+import com.jme3.gde.core.dnd.AssetNameHolder;
+import com.jme3.gde.materials.dnd.TextureDropTargetListener;
+import com.jme3.gde.materials.dnd.TextureDropTargetListener.TextureDropTarget;
 import com.jme3.gde.materials.multiview.MaterialEditorTopComponent;
 import java.awt.Component;
+import java.awt.dnd.DropTarget;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
@@ -49,7 +53,7 @@ import java.util.logging.Logger;
  * A more compact texture panel designed for the shader node editor.
  * @author rickard
  */
-public class TexturePanelSquare extends MaterialPropertyWidget {
+public class TexturePanelSquare extends MaterialPropertyWidget implements TextureDropTarget {
 
     private final TexturePropertyEditor editor;
     private final ProjectAssetManager manager;
@@ -99,6 +103,7 @@ public class TexturePanelSquare extends MaterialPropertyWidget {
             public void mouseExited(MouseEvent e) {
             }
         });
+        setDropTarget(new DropTarget(this, new TextureDropTargetListener(this)));
     }
 
     private void displayPreview() {
@@ -305,4 +310,23 @@ public class TexturePanelSquare extends MaterialPropertyWidget {
     private javax.swing.JPanel jPanel1;
     private javax.swing.JLabel texturePreview;
     // End of variables declaration//GEN-END:variables
+
+    @Override
+    public void setTexture(String name) {
+        property.setValue("");
+        java.awt.EventQueue.invokeLater(() -> {
+            if(name.startsWith("\"")){
+                textureName = name;
+            } else {
+                textureName = "\"" + name + "\"";
+            }
+            property.setValue(textureName);
+            displayPreview();
+            updateFlipRepeat();
+            java.awt.EventQueue.invokeLater(() -> {
+                fireChanged();
+            });
+        });
+        
+    }
 }

+ 19 - 25
jme3-scenecomposer/src/com/jme3/gde/scenecomposer/OpenSceneComposer.java

@@ -27,32 +27,26 @@ public final class OpenSceneComposer implements ActionListener {
         if (manager == null) {
             return;
         }
-        Runnable call = new Runnable() {
-
-            public void run() {
-                ProgressHandle progressHandle = ProgressHandle.createHandle("Opening in SceneComposer");
-                progressHandle.start();
-                try {
-                    manager.clearCache();
-                    final Spatial asset = context.loadAsset();
-                    if (asset != null) {
-                        java.awt.EventQueue.invokeLater(new Runnable() {
-
-                            public void run() {
-                                SceneComposerTopComponent composer = SceneComposerTopComponent.findInstance();
-                                composer.openScene(asset, context, manager);
-                            }
-                        });
-                    } else {
-                        Confirmation msg = new NotifyDescriptor.Confirmation(
-                                "Error opening " + context.getPrimaryFile().getNameExt(),
-                                NotifyDescriptor.OK_CANCEL_OPTION,
-                                NotifyDescriptor.ERROR_MESSAGE);
-                        DialogDisplayer.getDefault().notify(msg);
-                    }
-                } finally {
-                    progressHandle.finish();
+        Runnable call = () -> {
+            ProgressHandle progressHandle = ProgressHandle.createHandle("Opening in SceneComposer");
+            progressHandle.start();
+            try {
+                manager.clearCache();
+                final Spatial asset = context.loadAsset();
+                if (asset != null) {
+                    java.awt.EventQueue.invokeLater(() -> {
+                        SceneComposerTopComponent composer = SceneComposerTopComponent.findInstance();
+                        composer.openScene(asset, context, manager);
+                    });
+                } else {
+                    Confirmation msg = new NotifyDescriptor.Confirmation(
+                            "Error opening " + context.getPrimaryFile().getNameExt(),
+                            NotifyDescriptor.OK_CANCEL_OPTION,
+                            NotifyDescriptor.ERROR_MESSAGE);
+                    DialogDisplayer.getDefault().notify(msg);
                 }
+            } finally {
+                progressHandle.finish();
             }
         };
         new Thread(call).start();

+ 42 - 0
jme3-scenecomposer/src/com/jme3/gde/scenecomposer/layer.xml

@@ -66,6 +66,48 @@
                 </folder>
             </folder>
         </folder>
+        <folder name="text">
+            <folder name="ogrexml+xml">
+                <folder name="Actions">
+                   <file name="scenecomposer_sep-1.instance">
+                        <attr name="instanceClass" stringvalue="javax.swing.JSeparator"/>
+                        <attr name="position" intvalue="50"/>
+                    </file>
+                    <file name="com-jme3-gde-scenecomposer-AddSceneComposer.shadow">
+                        <attr name="originalFile" stringvalue="Actions/SceneComposer/com-jme3-gde-scenecomposer-AddSceneComposer.instance"/>
+                        <attr name="position" intvalue="52"/>
+                    </file>
+                    <file name="com-jme3-gde-scenecomposer-LinkSceneComposer.shadow">
+                        <attr name="originalFile" stringvalue="Actions/SceneComposer/com-jme3-gde-scenecomposer-LinkSceneComposer.instance"/>
+                        <attr name="position" intvalue="53"/>
+                    </file>
+                    <file name="scenecomposer_sep-2.instance">
+                        <attr name="instanceClass" stringvalue="javax.swing.JSeparator"/>
+                        <attr name="position" intvalue="69"/>
+                    </file>
+                 </folder>
+            </folder>
+            <folder name="ogrescene+xml">
+                <folder name="Actions">
+                   <file name="scenecomposer_sep-1.instance">
+                        <attr name="instanceClass" stringvalue="javax.swing.JSeparator"/>
+                        <attr name="position" intvalue="50"/>
+                    </file>
+                    <file name="com-jme3-gde-scenecomposer-AddSceneComposer.shadow">
+                        <attr name="originalFile" stringvalue="Actions/SceneComposer/com-jme3-gde-scenecomposer-AddSceneComposer.instance"/>
+                        <attr name="position" intvalue="52"/>
+                    </file>
+                    <file name="com-jme3-gde-scenecomposer-LinkSceneComposer.shadow">
+                        <attr name="originalFile" stringvalue="Actions/SceneComposer/com-jme3-gde-scenecomposer-LinkSceneComposer.instance"/>
+                        <attr name="position" intvalue="53"/>
+                    </file>
+                    <file name="scenecomposer_sep-2.instance">
+                        <attr name="instanceClass" stringvalue="javax.swing.JSeparator"/>
+                        <attr name="position" intvalue="69"/>
+                    </file>
+                </folder>
+            </folder>
+        </folder>
     </folder>
     <!--<folder name="Menu">
         <folder name="Window">

+ 6 - 5
nbproject/project.properties

@@ -1,4 +1,4 @@
-#Sat, 19 Mar 2022 09:34:49 +0100
+#Thu, 20 Apr 2023 18:24:23 +0200
 app.icon=branding/core/core.jar/org/netbeans/core/startup/frame48.gif
 #same as ${branding.token}
 app.name=jmonkeyplatform
@@ -7,10 +7,10 @@ app.description=A complete 3D game development suite written purely in Java.
 app.categories=Development,Graphics,IDE,3DGraphics,Java
 app.icon.icns=jmonkeyplatform.icns
 #version name used for application and settings folder, no spaces!
-app.version=3.4.1-SNAPSHOT
+app.version=3.6.0-make_texture_panel_resizable-SNAPSHOT
 #version number used for plugins, only 3 numbers (e.g. 3.1.3)
-plugins.version=3.4.1
-nbm.revision=2026
+plugins.version=3.6.0
+nbm.revision=2401
 #command line args
 run.args.extra=-J-Dsun.java2d.noddraw\=true -J--add-opens\=java.base/java.net\=ALL-UNNAMED -J--add-exports\=java.desktop/sun.awt\=ALL-UNNAMED -J--add-opens\=java.desktop/javax.swing.text\=ALL-UNNAMED -J--add-opens\=java.desktop/javax.swing\=ALL-UNNAMED -J--add-opens\=java.prefs/java.util.prefs\=ALL-UNNAMED -J--add-opens\=java.base/java.security\=ALL-UNNAMED -J--add-exports\=java.base/sun.reflect.annotation\=ALL-UNNAMED -J--add-opens\=java.desktop/javax.swing.plaf.basic\=ALL-UNNAMED
 auxiliary.org-netbeans-modules-apisupport-installer.license-file=license-jme.txt
@@ -19,9 +19,10 @@ auxiliary.org-netbeans-modules-apisupport-installer.os-macosx=true
 auxiliary.org-netbeans-modules-apisupport-installer.os-solaris=false
 auxiliary.org-netbeans-modules-apisupport-installer.os-windows=true
 auxiliary.org-netbeans-modules-apisupport-installer.pack200-enabled=true
-modules=${project.com.jme3.gde.core}\:${project.com.jme3.gde.core.baselibs}\:${project.com.jme3.gde.core.libraries}\:${project.com.jme3.gde.templates}\:${project.com.jme3.gde.project.baselibs}\:${project.com.jme3.gde.project.libraries}\:${project.com.jme3.gde.tests}\:${project.com.jme3.gde.project.testdata}\:${project.com.jme3.gde.scenecomposer}\:${project.com.jme3.gde.materials}\:${project.com.jme3.gde.gui}\:${project.com.jme3.gde.codepalette}\:${project.com.jme3.gde.textureeditor}\:${project.com.jme3.gde.core.updatecenters}\:${project.com.jme3.gde.wavefront}\:${project.com.jme3.gde.terraineditor}\:${project.com.jme3.gde.assetpack}\:${project.com.jme3.gde.modelimporter}\:${project.com.jme3.gde.lwjgl.applet}\:${project.com.jme3.gde.desktop.executables}\:${project.com.jme3.gde.cinematics}\:${project.com.jme3.gde.vehiclecreator}\:${project.com.jme3.gde.welcome}\:${project.com.jme3.gde.codecheck}\:${project.com.jme3.gde.obfuscate}\:${project.com.jme3.gde.blender}\:${project.com.jme3.gde.angelfont}\:${project.com.jme3.gde.android}\:${project.com.jme3.gde.nmgen}\:${project.com.jme3.gde.docs}\:${project.org.jme3.netbeans.plaf.darkmonkey}\:${project.com.jme3.gde.glsl.highlighter}
+modules=${project.com.jme3.gde.core}\:${project.com.jme3.gde.core.baselibs}\:${project.com.jme3.gde.core.libraries}\:${project.com.jme3.gde.templates}\:${project.com.jme3.gde.project.baselibs}\:${project.com.jme3.gde.project.libraries}\:${project.com.jme3.gde.tests}\:${project.com.jme3.gde.project.testdata}\:${project.com.jme3.gde.scenecomposer}\:${project.com.jme3.gde.materials}\:${project.com.jme3.gde.gui}\:${project.com.jme3.gde.codepalette}\:${project.com.jme3.gde.textureeditor}\:${project.com.jme3.gde.core.updatecenters}\:${project.com.jme3.gde.wavefront}\:${project.com.jme3.gde.terraineditor}\:${project.com.jme3.gde.assetpack}\:${project.com.jme3.gde.modelimporter}\:${project.com.jme3.gde.lwjgl.applet}\:${project.com.jme3.gde.desktop.executables}\:${project.com.jme3.gde.cinematics}\:${project.com.jme3.gde.vehiclecreator}\:${project.com.jme3.gde.welcome}\:${project.com.jme3.gde.codecheck}\:${project.com.jme3.gde.obfuscate}\:${project.com.jme3.gde.blender}\:${project.com.jme3.gde.angelfont}\:${project.com.jme3.gde.android}\:${project.com.jme3.gde.nmgen}\:${project.com.jme3.gde.docs}\:${project.org.jme3.netbeans.plaf.darkmonkey}\:${project.com.jme3.gde.glsl.highlighter}\:${project.com.jme3.gde.assetbrowser}
 project.com.jme3.gde.android=jme3-android
 project.com.jme3.gde.angelfont=jme3-angelfont
+project.com.jme3.gde.assetbrowser=jme3-assetbrowser
 project.com.jme3.gde.blender=jme3-blender
 project.com.jme3.gde.codecheck=jme3-code-check
 project.com.jme3.gde.customcontrols=jme3-custom-controls