Преглед на файлове

live updates for backpanel

Conflicts:
	jme3-core/src/com/jme3/gde/core/editor/nodes/Diagram.java
	jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/ShaderNodeDiagram.java
	jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/previews/ColorPreview.java
	jme3-materialeditor/src/com/jme3/gde/materials/MaterialPreviewRenderer.java
rickard преди 3 години
родител
ревизия
48ba88769c

+ 587 - 577
jme3-core/src/com/jme3/gde/core/editor/nodes/Diagram.java

@@ -1,577 +1,587 @@
-/*
- *  Copyright (c) 2009-2018 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.editor.nodes;
-
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.Cursor;
-import java.awt.Dimension;
-import java.awt.Font;
-import java.awt.Point;
-import java.awt.Rectangle;
-import java.awt.event.ComponentEvent;
-import java.awt.event.ComponentListener;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseListener;
-import java.awt.event.MouseMotionListener;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import javax.swing.BorderFactory;
-import javax.swing.Icon;
-import javax.swing.JMenuItem;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JPopupMenu;
-import javax.swing.JScrollPane;
-import javax.swing.JSeparator;
-import javax.swing.JViewport;
-import javax.swing.SwingUtilities;
-import javax.swing.border.Border;
-import javax.swing.border.TitledBorder;
-
-/**
- * The Diagram is the main canvas where all nodes {@link NodePanel} and
- * their connections {@link ConnectionEndpoint} {@link Connection} are added onto.
- * @author Nehon
- */
-public abstract class Diagram extends JPanel implements MouseListener, 
-        MouseMotionListener, ComponentListener {
-    // Content
-    protected List<Selectable> selectedItems = new ArrayList<Selectable>();
-    protected List<Connection> connections = new ArrayList<Connection>();
-    protected List<NodePanel> nodes = new ArrayList<NodePanel>();
-    
-    // UI
-    protected final JPopupMenu contextMenu = new JPopupMenu("Add");
-    protected NodeEditor parent;
-    
-    // drag stuff
-    protected ConnectionEndpoint draggedFrom;
-    protected ConnectionEndpoint draggedTo;  
-    private final Point pp = new Point();
-    protected Point clickLoc = new Point(0, 0);    
-    
-    // dynamic switching between the regular and the move cursor (MMB)
-    private final Cursor defCursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
-    private final Cursor hndCursor = Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR);
-    
-    // filled in from componentResize()
-    protected int minWidth = 0;
-    protected int minHeight = 0;
-
-    @SuppressWarnings("LeakingThisInConstructor")
-    public Diagram() {
-        addMouseListener(this);
-        addMouseMotionListener(this);
-        createPopupMenu();
-    }
-    
-    /**
-     * This method is called from within the mousePressed event when the Left 
-     * Mouse Button is pressed. Use this if you need to run before the regular
-     * connection logic. The return value determines whether the event was consumed
-     * 
-     * @param e The Event
-     * @return Whether to continue passing the event (true) or to quit (false) 
-     */
-    protected abstract boolean mouseLMBPrePressedEvent(MouseEvent e);
-
-    @Override
-    public void mouseClicked(MouseEvent e) {}
-
-    @Override
-    public void mousePressed(MouseEvent e) {
-        if (SwingUtilities.isLeftMouseButton(e)) {
-            if (mouseLMBPrePressedEvent(e)) {
-                return; // event already consumed
-            }
-
-            for (Connection connection : connections) {
-                MouseEvent me = SwingUtilities.convertMouseEvent(this, e, connection);
-                connection.select(me);
-                if (me.isConsumed()) {
-                    return;
-                }
-            }
-
-            selectedItems.clear();
-            repaint();
-        } else if (e.getButton() == MouseEvent.BUTTON2) {
-            // change to "move using mouse wheel button" and set the cursor
-            setCursor(hndCursor);
-            pp.setLocation(e.getPoint());
-            ((JScrollPane)getParent().getParent()).setWheelScrollingEnabled(false);
-        }
-    }
-
-    @Override
-    public void mouseReleased(MouseEvent e) {
-        switch (e.getButton()) {
-            case MouseEvent.BUTTON2:
-                setCursor(defCursor);
-                ((JScrollPane) getParent().getParent()).setWheelScrollingEnabled(true);
-                break;
-            case MouseEvent.BUTTON3:
-                contextMenu.show(this, e.getX(), e.getY());
-                clickLoc.setLocation(e.getX(), e.getY());
-                break;
-        }
-
-    }
-    
-    @Override
-    public void mouseEntered(MouseEvent e) {}
-
-    @Override
-    public void mouseExited(MouseEvent e) {}
-    
-    @Override
-    public void mouseMoved(MouseEvent e) {}
-    
-    @Override
-    public void mouseDragged(MouseEvent e) {
-        if (SwingUtilities.isLeftMouseButton(e)) {
-        } else if (SwingUtilities.isMiddleMouseButton(e)) {
-            JViewport vport = (JViewport) getParent();
-            Point cp = e.getPoint();
-            Point vp = vport.getViewPosition();
-            vp.translate(pp.x - cp.x, pp.y - cp.y);
-            scrollRectToVisible(new Rectangle(vp, vport.getSize()));
-            //pp.setLocation(cp);
-        }
-    }
-
-    public NodeEditor getEditorParent() {
-        return parent;
-    }
-    
-    public void setEditorParent(NodeEditor parent) {
-        this.parent = parent;
-    }
-    
-    /**
-     * Create a unique key for this connection based on implementation specifc
-     * data (obj)
-     * @param con the connection
-     * @param obj implementation specific data
-     * @return the key
-     */
-    public abstract String makeKeyForConnection(Connection con, Object obj);
-
-    /**
-     * Add a Connection to this Diagram.
-     * Usecode: Call {@link #connect(com.jme3.gde.materialdefinition.editor.ConnectionEndpoint,
-     * com.jme3.gde.materialdefinition.editor.ConnectionEndpoint) } where
-     * possible instead.
-     * @param conn The connection to add
-     */
-    public void addConnection(Connection conn) {
-        connections.add(conn);
-        add(conn);
-        
-        if (conn.getStart().getNode() instanceof NodePanel) {
-            ((NodePanel)conn.getStart().getNode()).onConnect(conn);
-        }
-        if (conn.getEnd().getNode() instanceof NodePanel) {
-            ((NodePanel)conn.getEnd().getNode()).onConnect(conn);
-        }
-        repaint();
-    }
-
-    /**
-     * This is called when an Editor should be shown for that Node.
-     * Called by {@link NodePanel#edit() }
-     * @param node The node in question
-     */
-    protected abstract void showEdit(NodePanel node);
-
-    public void notifyMappingCreation(Connection conn) {
-        parent.makeMapping(conn);
-    }
-
-    public void addNode(NodePanel node) {
-        add(node);
-        node.setDiagram(this);
-        nodes.add(node);
-        setComponentZOrder(node, 0);
-        node.addComponentListener(this);
-    }
-    
-    protected void removeSelectedConnection(Selectable selectedItem) {        
-        Connection selectedConnection = (Connection) selectedItem;
-        removeConnection(selectedConnection);
-        parent.notifyRemoveConnection(selectedConnection);
-    }
-    
-    /**
-     * Called when user pressed delete after having selected something
-     */
-    protected void removeSelected() {
-        int result = JOptionPane.showConfirmDialog(null, "Delete all selected items, nodes and mappings?", "Delete Selected", JOptionPane.OK_CANCEL_OPTION);
-        
-        if (result == JOptionPane.OK_OPTION) {
-            for (Selectable selectedItem : selectedItems) {
-                if (selectedItem instanceof NodePanel) {
-                    removeNode((NodePanel)selectedItem);
-                }
-                if (selectedItem instanceof Connection) {
-                    removeSelectedConnection(selectedItem);
-                }
-            }
-            selectedItems.clear();
-        }
-    }
-
-    /**
-     * Remove a Node and all it's related connections from the diagram.
-     * @param node The node to remove
-     */
-    public void removeNode(NodePanel node) {
-        nodes.remove(node);
-        for (Iterator<Connection> it = connections.iterator(); it.hasNext();) {
-            Connection conn = it.next();
-            if (conn.start.getNode() == node || conn.end.getNode() == node) {
-                it.remove();
-                removeConnection(conn);
-                parent.notifyRemoveConnection(conn);
-            }
-        }
-
-        node.cleanup();
-        remove(node);
-        repaint();
-        parent.notifyRemoveNode(node);
-    }
-
-    public List<Selectable> getSelectedItems() {
-        return selectedItems;
-    }
-
-    /**
-     * Called by {@link ConnectionEndpoint} when a Curve has been dragged
-     * @param e
-     */
-    protected void draggingDot(MouseEvent e) {}
-
-    /**
-     * Connect two Dots to form a Connection
-     * @param start The Start
-     * @param end The End
-     * @return The Connection
-     */
-    public Connection connect(ConnectionEndpoint start, ConnectionEndpoint end) {
-        Connection conn = new Connection(start, end);
-        start.connect(conn);
-        end.connect(conn);
-        addConnection(conn);
-        return conn;
-    }
-
-    /**
-     * Find a panel which corresponds to the given key (unique id). Use this to
-     * locate nodes on the diagram
-     * 
-     * @param key The key
-     * @return hopefully the correct node
-     */
-    public NodePanel getNodePanel(String key) {
-        for (NodePanel nodePanel: nodes) {
-            if (nodePanel.getKey().equals(key)) {
-                return nodePanel;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Selection from the editor. Select the item and notify the topComponent
-     * @param selectable the item to select
-     * @param multi Whether Multi Selection is allowed
-     */
-    public void selectAndNotify(Selectable selectable, boolean multi) {
-        parent.selectionChanged(doSelect(selectable, multi));
-    }
-    
-    /**
-     * Move one of the selected panels by a given offset
-     * @param movedPanel the panel in question
-     * @param xOffset the movement in x direction
-     * @param yOffset the movement in y direction
-     */
-    public void multiMove(DraggablePanel movedPanel, int xOffset, int yOffset) {
-        for (Selectable selectedItem: selectedItems) {
-            if (selectedItem != movedPanel) {
-                if (selectedItem instanceof DraggablePanel) {
-                    ((DraggablePanel)selectedItem).movePanel(xOffset, yOffset);
-                }
-            }
-        }
-    }
-
-    /**
-     * Prepare dragging multiple selected panels
-     * @param movedPanel The Panel which has been moved.
-     */
-    public void multiStartDrag(DraggablePanel movedPanel){
-        for (Selectable selectedItem: selectedItems) {
-            if (selectedItem != movedPanel) {
-                if (selectedItem instanceof DraggablePanel) {
-                    ((DraggablePanel)selectedItem).saveLocation();
-                }
-            }
-        }
-    }
-    
-    /**
-     * Select the specified item and repaint the window to reflect selection
-     * outlines.
-     *
-     * @param selectable The item which shall be selected
-     * @param multi Whether multiple selection is allowed or if this should
-     * clear previous selections
-     * @return The selected item
-     */
-    private Selectable doSelect(Selectable selectable, boolean multi) {
-        if (!multi && !selectedItems.contains(selectable)) {
-            selectedItems.clear();
-        }
-        if (selectable != null) {
-            selectedItems.add(selectable);
-        }
-        if (selectable instanceof Component) {
-            ((Component)selectable).requestFocusInWindow();
-        }
-        repaint();
-        return selectable;
-    }
-
-    /**
-     * Subclasses which add selectable items to the Diagram have to lookup 
-     * items by their key and return them here for regular dragging/selecting
-     * to work.<br>
-     * If nothing was found (or you don't add custom elements to the diagram),
-     * return null.
-     * 
-     * @param key the unique key
-     * @return The Selectable item or null
-     */
-    protected abstract Selectable trySelect(String key);
-    
-    /**
-     * find the item with the given key and select it without notifying the
-     * topComponent. Since this iterates over all possible panels, subclasses
-     * have to implement {@link #trySelect(java.lang.String) }
-     *
-     * @param key The unique key
-     * @param multi Support Multi Selection?
-     * @return The selected item
-     */
-    public Selectable select(String key, boolean multi) {
-        for (NodePanel nodePanel: nodes) {
-            if (nodePanel.getKey().equals(key)) {
-                return doSelect(nodePanel, multi);
-            }
-        }
-
-        for (Connection connection: connections) {
-            if (connection.getKey().equals(key)) {
-                return doSelect(connection, multi);
-            }
-        }
-        
-        Selectable s = trySelect(key);
-        if (s != null) {
-            return doSelect(s, multi);
-        }
-
-        return null;
-    }
-    
-    public Selectable select(String key) {
-        return select(key, false);
-    }
-    
-    public Selectable select(Selectable selectable, boolean multi) {
-        return doSelect(selectable, multi);
-    }
-
-    public void clear() {
-        removeAll();
-        connections.clear();
-        nodes.clear();
-    }
-
-    /**
-     * Creates a horizontal separator with a black background
-     * @return the separator
-     */
-    protected JSeparator createSeparator() {
-        JSeparator jsep = new JSeparator(JSeparator.HORIZONTAL);
-        jsep.setBackground(Color.BLACK);
-        return jsep;
-    }
-    
-    /**
-     * Creates a MenuItem with the given text and icon.<br>
-     * In addition to calling the constructor this sets the font to Tahoma 10px.
-     * 
-     * @param text The text
-     * @param icon The icon
-     * @return The MenuItem with Tahoma as Font
-     */
-    protected JMenuItem createMenuItem(String text, Icon icon) {
-        JMenuItem item = new JMenuItem(text, icon);
-        item.setFont(new Font("Tahoma", 1, 10)); // NOI18N
-        return item;
-    }
-
-    /**
-     * Override this method to fill the popup/context menu available via 
-     * right clicking the diagram.<br>
-     * It's important to call this (super) first. It will setup fonts and borders.<br>
-     * You can use {@link #createMenuItem(java.lang.String, javax.swing.Icon) }
-     * and {@link #createSeparator() } as helper methods.
-     */
-    protected void createPopupMenu() {
-        contextMenu.setFont(new Font("Tahoma", 1, 10)); // NOI18N
-        contextMenu.setOpaque(true);
-        Border titleUnderline = BorderFactory.createMatteBorder(1, 0, 0, 0, Color.BLACK);
-        TitledBorder labelBorder = BorderFactory.createTitledBorder(
-                titleUnderline, contextMenu.getLabel(),
-                TitledBorder.LEADING, TitledBorder.ABOVE_TOP, contextMenu.getFont(), Color.BLACK);
-
-        contextMenu.setBorder(BorderFactory.createLineBorder(Color.BLACK));
-        contextMenu.setBorder(BorderFactory.createCompoundBorder(contextMenu.getBorder(),
-                labelBorder));
-    }
-
-    private void removeConnection(Connection selectedConnection) {
-        ConnectionEndpoint start = selectedConnection.getStart();
-        ConnectionEndpoint end = selectedConnection.getEnd();
-        
-        connections.remove(selectedConnection);
-        end.disconnect();
-        start.disconnect();
-        remove(selectedConnection);
-        
-        if (start.getNode() instanceof NodePanel) {
-            ((NodePanel)start.getNode()).onDisconnect(selectedConnection);
-        }
-        if (end.getNode() instanceof NodePanel) {
-            ((NodePanel)end.getNode()).onDisconnect(selectedConnection);
-        }
-    }
-
-    /**
-     * As part of the semi-automatical layout, the maximum height of the diagram
-     * has to be calculated.<br>
-     * That means for each custom panel (no nodes), calculate the location.y
-     * and add the element's height.<br>
-     * Then find the largest value over all the custom panels.
-     * 
-     * @return The maximum height for all custom elements (or 0 if none are
-     * present).
-     */
-    protected abstract int calcMaxHeight();
-    
-    /**
-     * As part of the semi-automatical layout, the maximum width of the diagram
-     * has to be calculated.<br>
-     * That means for each custom panel (no nodes), calculate the location.x
-     * and add the element's width.<br>
-     * Then find the largest value over all the custom panels.
-     * 
-     * @return The maximum width for all custom elements (or 0 if none are
-     * present).
-     */
-    protected abstract int calcMaxWidth();
-    
-    /**
-     * This is called on multiple occassions to ensure that the size of this 
-     * diagram is just large enough.
-     */
-    public void fixSize() {
-        int maxWidth = minWidth;
-        int maxHeight = minHeight;
-
-        for (NodePanel nodePanel : nodes) {
-            int w = nodePanel.getLocation().x + nodePanel.getWidth() + 150;
-            if (w > maxWidth) {
-                maxWidth = w;
-            }
-            int h = nodePanel.getLocation().y + nodePanel.getHeight();
-            if (h > maxHeight) {
-                maxHeight = h;
-            }
-        }
-        
-        // Custom Nodes        
-        int w = calcMaxWidth();
-        int h = calcMaxHeight();
-        
-        if (w > maxWidth) {
-            maxWidth = w;
-        }
-        
-        if (h > maxHeight) {
-            maxHeight = h;
-        }
-        
-        setPreferredSize(new Dimension(maxWidth, maxHeight));
-        revalidate();
-    }
-
-    /**
-     * Use this method to layout your elements. Have a look at 
-     * {@link ShaderNodeDiagram#autoLayout() } for an example. Maybe you can
-     * come up with a better/easier solution
-     */
-    public abstract void autoLayout();
-    
-    @Override
-    public void componentResized(ComponentEvent e) {
-        minWidth = e.getComponent().getWidth() - 2;
-        minHeight = e.getComponent().getHeight() - 2;
-        fixSize();
-    }
-
-    @Override
-    public void componentMoved(ComponentEvent e) {}
-
-    @Override
-    public void componentShown(ComponentEvent e) {}
-
-    @Override
-    public void componentHidden(ComponentEvent e) {}
-
-}
+/*
+ *  Copyright (c) 2009-2018 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.editor.nodes;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
+import javax.swing.JViewport;
+import javax.swing.SwingUtilities;
+import javax.swing.border.Border;
+import javax.swing.border.TitledBorder;
+
+/**
+ * The Diagram is the main canvas where all nodes {@link NodePanel} and
+ * their connections {@link ConnectionEndpoint} {@link Connection} are added onto.
+ * @author Nehon
+ */
+public abstract class Diagram extends JPanel implements MouseListener, 
+        MouseMotionListener, ComponentListener {
+    // Content
+    protected List<Selectable> selectedItems = new ArrayList<Selectable>();
+    protected List<Connection> connections = new ArrayList<Connection>();
+    protected List<NodePanel> nodes = new ArrayList<NodePanel>();
+    
+    // UI
+    protected final JPopupMenu contextMenu = new JPopupMenu("Add");
+    protected NodeEditor parent;
+    
+    // drag stuff
+    protected ConnectionEndpoint draggedFrom;
+    protected ConnectionEndpoint draggedTo;  
+    private final Point pp = new Point();
+    protected Point clickLoc = new Point(0, 0);    
+    
+    // dynamic switching between the regular and the move cursor (MMB)
+    private final Cursor defCursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
+    private final Cursor hndCursor = Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR);
+    
+    // filled in from componentResize()
+    protected int minWidth = 0;
+    protected int minHeight = 0;
+
+    @SuppressWarnings("LeakingThisInConstructor")
+    public Diagram() {
+        addMouseListener(this);
+        addMouseMotionListener(this);
+        createPopupMenu();
+    }
+    
+    /**
+     * This method is called from within the mousePressed event when the Left 
+     * Mouse Button is pressed. Use this if you need to run before the regular
+     * connection logic. The return value determines whether the event was consumed
+     * 
+     * @param e The Event
+     * @return Whether to continue passing the event (true) or to quit (false) 
+     */
+    protected abstract boolean mouseLMBPrePressedEvent(MouseEvent e);
+
+    @Override
+    public void mouseClicked(MouseEvent e) {}
+
+    @Override
+    public void mousePressed(MouseEvent e) {
+        if (SwingUtilities.isLeftMouseButton(e)) {
+            if (mouseLMBPrePressedEvent(e)) {
+                return; // event already consumed
+            }
+
+            for (Connection connection : connections) {
+                MouseEvent me = SwingUtilities.convertMouseEvent(this, e, connection);
+                connection.select(me);
+                if (me.isConsumed()) {
+                    return;
+                }
+            }
+
+            selectedItems.clear();
+            repaint();
+        } else if (e.getButton() == MouseEvent.BUTTON2) {
+            // change to "move using mouse wheel button" and set the cursor
+            setCursor(hndCursor);
+            pp.setLocation(e.getPoint());
+            ((JScrollPane)getParent().getParent()).setWheelScrollingEnabled(false);
+        }
+    }
+
+    @Override
+    public void mouseReleased(MouseEvent e) {
+        switch (e.getButton()) {
+            case MouseEvent.BUTTON2:
+                setCursor(defCursor);
+                ((JScrollPane) getParent().getParent()).setWheelScrollingEnabled(true);
+                break;
+            case MouseEvent.BUTTON3:
+                contextMenu.show(this, e.getX(), e.getY());
+                clickLoc.setLocation(e.getX(), e.getY());
+                break;
+        }
+
+    }
+    
+    @Override
+    public void mouseEntered(MouseEvent e) {}
+
+    @Override
+    public void mouseExited(MouseEvent e) {}
+    
+    @Override
+    public void mouseMoved(MouseEvent e) {}
+    
+    @Override
+    public void mouseDragged(MouseEvent e) {
+        if (SwingUtilities.isLeftMouseButton(e)) {
+        } else if (SwingUtilities.isMiddleMouseButton(e)) {
+            JViewport vport = (JViewport) getParent();
+            Point cp = e.getPoint();
+            Point vp = vport.getViewPosition();
+            vp.translate(pp.x - cp.x, pp.y - cp.y);
+            scrollRectToVisible(new Rectangle(vp, vport.getSize()));
+            //pp.setLocation(cp);
+        }
+    }
+
+    public NodeEditor getEditorParent() {
+        return parent;
+    }
+    
+    public void setEditorParent(NodeEditor parent) {
+        this.parent = parent;
+    }
+    
+    /**
+     * Create a unique key for this connection based on implementation specifc
+     * data (obj)
+     * @param con the connection
+     * @param obj implementation specific data
+     * @return the key
+     */
+    public abstract String makeKeyForConnection(Connection con, Object obj);
+
+    /**
+     * Add a Connection to this Diagram.
+     * Usecode: Call {@link #connect(com.jme3.gde.materialdefinition.editor.ConnectionEndpoint,
+     * com.jme3.gde.materialdefinition.editor.ConnectionEndpoint) } where
+     * possible instead.
+     * @param conn The connection to add
+     */
+    public void addConnection(Connection conn) {
+        connections.add(conn);
+        add(conn);
+        
+        if (conn.getStart().getNode() instanceof NodePanel) {
+            ((NodePanel)conn.getStart().getNode()).onConnect(conn);
+        }
+        if (conn.getEnd().getNode() instanceof NodePanel) {
+            ((NodePanel)conn.getEnd().getNode()).onConnect(conn);
+        }
+        repaint();
+    }
+
+    /**
+     * This is called when an Editor should be shown for that Node.
+     * Called by {@link NodePanel#edit() }
+     * @param node The node in question
+     */
+    protected abstract void showEdit(NodePanel node);
+
+    public void notifyMappingCreation(Connection conn) {
+        parent.makeMapping(conn);
+    }
+
+    public void addNode(NodePanel node) {
+        add(node);
+        node.setDiagram(this);
+        nodes.add(node);
+        setComponentZOrder(node, 0);
+        node.addComponentListener(this);
+    }
+    
+    protected void removeSelectedConnection(Selectable selectedItem) {        
+        Connection selectedConnection = (Connection) selectedItem;
+        removeConnection(selectedConnection);
+        parent.notifyRemoveConnection(selectedConnection);
+    }
+    
+    /**
+     * Called when user pressed delete after having selected something
+     */
+    protected void removeSelected() {
+        int result = JOptionPane.showConfirmDialog(null, "Delete all selected items, nodes and mappings?", "Delete Selected", JOptionPane.OK_CANCEL_OPTION);
+        
+        if (result == JOptionPane.OK_OPTION) {
+            for (Selectable selectedItem : selectedItems) {
+                if (selectedItem instanceof NodePanel) {
+                    removeNode((NodePanel)selectedItem);
+                }
+                if (selectedItem instanceof Connection) {
+                    removeSelectedConnection(selectedItem);
+                }
+            }
+            selectedItems.clear();
+        }
+    }
+
+    /**
+     * Remove a Node and all it's related connections from the diagram.
+     * @param node The node to remove
+     */
+    public void removeNode(NodePanel node) {
+        nodes.remove(node);
+        for (Iterator<Connection> it = connections.iterator(); it.hasNext();) {
+            Connection conn = it.next();
+            if (conn.start.getNode() == node || conn.end.getNode() == node) {
+                it.remove();
+                removeConnection(conn);
+                parent.notifyRemoveConnection(conn);
+            }
+        }
+
+        node.cleanup();
+        remove(node);
+        repaint();
+        parent.notifyRemoveNode(node);
+    }
+    
+    public void updateDefaultValue(String name, String value){
+        parent.notifyDefaultValueUpdated(name, value);
+    }
+    
+    public List<Selectable> getSelectedItems() {
+        return selectedItems;
+    }
+
+    /**
+     * Called by {@link ConnectionEndpoint} when a Curve has been dragged
+     * @param e
+     */
+    protected void draggingDot(MouseEvent e) {}
+
+    /**
+     * Connect two Dots to form a Connection
+     * @param start The Start
+     * @param end The End
+     * @return The Connection
+     */
+    public Connection connect(ConnectionEndpoint start, ConnectionEndpoint end) {
+        Connection conn = new Connection(start, end);
+        start.connect(conn);
+        end.connect(conn);
+        addConnection(conn);
+        return conn;
+    }
+
+    /**
+     * Find a panel which corresponds to the given key (unique id). Use this to
+     * locate nodes on the diagram
+     * 
+     * @param key The key
+     * @return hopefully the correct node
+     */
+    public NodePanel getNodePanel(String key) {
+        for (NodePanel nodePanel: nodes) {
+            if (nodePanel.getKey().equals(key)) {
+                return nodePanel;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Selection from the editor. Select the item and notify the topComponent
+     * @param selectable the item to select
+     * @param multi Whether Multi Selection is allowed
+     */
+    public void selectAndNotify(Selectable selectable, boolean multi) {
+        parent.selectionChanged(doSelect(selectable, multi));
+    }
+    
+    /**
+     * Move one of the selected panels by a given offset
+     * @param movedPanel the panel in question
+     * @param xOffset the movement in x direction
+     * @param yOffset the movement in y direction
+     */
+    public void multiMove(DraggablePanel movedPanel, int xOffset, int yOffset) {
+        for (Selectable selectedItem: selectedItems) {
+            if (selectedItem != movedPanel) {
+                if (selectedItem instanceof DraggablePanel) {
+                    ((DraggablePanel)selectedItem).movePanel(xOffset, yOffset);
+                }
+            }
+        }
+    }
+
+    /**
+     * Prepare dragging multiple selected panels
+     * @param movedPanel The Panel which has been moved.
+     */
+    public void multiStartDrag(DraggablePanel movedPanel){
+        for (Selectable selectedItem: selectedItems) {
+            if (selectedItem != movedPanel) {
+                if (selectedItem instanceof DraggablePanel) {
+                    ((DraggablePanel)selectedItem).saveLocation();
+                }
+            }
+        }
+    }
+    
+    /**
+     * Select the specified item and repaint the window to reflect selection
+     * outlines.
+     *
+     * @param selectable The item which shall be selected
+     * @param multi Whether multiple selection is allowed or if this should
+     * clear previous selections
+     * @return The selected item
+     */
+    private Selectable doSelect(Selectable selectable, boolean multi) {
+        if (!multi && !selectedItems.contains(selectable)) {
+            selectedItems.clear();
+        }
+        if (selectable != null) {
+            selectedItems.add(selectable);
+        }
+        if (selectable instanceof Component) {
+            ((Component)selectable).requestFocusInWindow();
+        }
+        repaint();
+        return selectable;
+    }
+
+    /**
+     * Subclasses which add selectable items to the Diagram have to lookup 
+     * items by their key and return them here for regular dragging/selecting
+     * to work.<br>
+     * If nothing was found (or you don't add custom elements to the diagram),
+     * return null.
+     * 
+     * @param key the unique key
+     * @return The Selectable item or null
+     */
+    protected abstract Selectable trySelect(String key);
+    
+    /**
+     * find the item with the given key and select it without notifying the
+     * topComponent. Since this iterates over all possible panels, subclasses
+     * have to implement {@link #trySelect(java.lang.String) }
+     *
+     * @param key The unique key
+     * @param multi Support Multi Selection?
+     * @return The selected item
+     */
+    public Selectable select(String key, boolean multi) {
+        for (NodePanel nodePanel: nodes) {
+            if (nodePanel.getKey().equals(key)) {
+                return doSelect(nodePanel, multi);
+            }
+        }
+
+        for (Connection connection: connections) {
+            if (connection.getKey().equals(key)) {
+                return doSelect(connection, multi);
+            }
+        }
+        
+        Selectable s = trySelect(key);
+        if (s != null) {
+            return doSelect(s, multi);
+        }
+
+        return null;
+    }
+    
+    public Selectable select(String key) {
+        return select(key, false);
+    }
+    
+    public Selectable select(Selectable selectable, boolean multi) {
+        return doSelect(selectable, multi);
+    }
+
+    public void clear() {
+        removeAll();
+        connections.clear();
+        nodes.clear();
+    }
+
+    /**
+     * Creates a horizontal separator with a black background
+     * @return the separator
+     */
+    protected JSeparator createSeparator() {
+        JSeparator jsep = new JSeparator(JSeparator.HORIZONTAL);
+        jsep.setBackground(Color.BLACK);
+        return jsep;
+    }
+    
+    /**
+     * Creates a MenuItem with the given text and icon.<br>
+     * In addition to calling the constructor this sets the font to Tahoma 10px.
+     * 
+     * @param text The text
+     * @param icon The icon
+     * @return The MenuItem with Tahoma as Font
+     */
+    protected JMenuItem createMenuItem(String text, Icon icon) {
+        JMenuItem item = new JMenuItem(text, icon);
+        item.setFont(new Font("Tahoma", 1, 10)); // NOI18N
+        return item;
+    }
+
+    /**
+     * Override this method to fill the popup/context menu available via 
+     * right clicking the diagram.<br>
+     * It's important to call this (super) first. It will setup fonts and borders.<br>
+     * You can use {@link #createMenuItem(java.lang.String, javax.swing.Icon) }
+     * and {@link #createSeparator() } as helper methods.
+     */
+    protected void createPopupMenu() {
+        contextMenu.setFont(new Font("Tahoma", 1, 10)); // NOI18N
+        contextMenu.setOpaque(true);
+        Border titleUnderline = BorderFactory.createMatteBorder(1, 0, 0, 0, Color.BLACK);
+        TitledBorder labelBorder = BorderFactory.createTitledBorder(
+                titleUnderline, contextMenu.getLabel(),
+                TitledBorder.LEADING, TitledBorder.ABOVE_TOP, contextMenu.getFont(), Color.BLACK);
+
+        contextMenu.setBorder(BorderFactory.createLineBorder(Color.BLACK));
+        contextMenu.setBorder(BorderFactory.createCompoundBorder(contextMenu.getBorder(),
+                labelBorder));
+    }
+
+    private void removeConnection(Connection selectedConnection) {
+        ConnectionEndpoint start = selectedConnection.getStart();
+        ConnectionEndpoint end = selectedConnection.getEnd();
+        
+        connections.remove(selectedConnection);
+        end.disconnect();
+        start.disconnect();
+        remove(selectedConnection);
+        
+        if (start.getNode() instanceof NodePanel) {
+            ((NodePanel)start.getNode()).onDisconnect(selectedConnection);
+        }
+        if (end.getNode() instanceof NodePanel) {
+            ((NodePanel)end.getNode()).onDisconnect(selectedConnection);
+        }
+    }
+
+    /**
+     * As part of the semi-automatical layout, the maximum height of the diagram
+     * has to be calculated.<br>
+     * That means for each custom panel (no nodes), calculate the location.y
+     * and add the element's height.<br>
+     * Then find the largest value over all the custom panels.
+     * 
+     * @return The maximum height for all custom elements (or 0 if none are
+     * present).
+     */
+    protected abstract int calcMaxHeight();
+    
+    /**
+     * As part of the semi-automatical layout, the maximum width of the diagram
+     * has to be calculated.<br>
+     * That means for each custom panel (no nodes), calculate the location.x
+     * and add the element's width.<br>
+     * Then find the largest value over all the custom panels.
+     * 
+     * @return The maximum width for all custom elements (or 0 if none are
+     * present).
+     */
+    protected abstract int calcMaxWidth();
+    
+    /**
+     * This is called on multiple occassions to ensure that the size of this 
+     * diagram is just large enough.
+     */
+    public void fixSize() {
+        int maxWidth = minWidth;
+        int maxHeight = minHeight;
+
+        for (NodePanel nodePanel : nodes) {
+            int w = nodePanel.getLocation().x + nodePanel.getWidth() + 150;
+            if (w > maxWidth) {
+                maxWidth = w;
+            }
+            int h = nodePanel.getLocation().y + nodePanel.getHeight();
+            if (h > maxHeight) {
+                maxHeight = h;
+            }
+        }
+        
+        // Custom Nodes        
+        int w = calcMaxWidth();
+        int h = calcMaxHeight();
+        
+        if (w > maxWidth) {
+            maxWidth = w;
+        }
+        
+        if (h > maxHeight) {
+            maxHeight = h;
+        }
+        
+        setPreferredSize(new Dimension(maxWidth, maxHeight));
+        revalidate();
+    }
+
+    /**
+     * Use this method to layout your elements. Have a look at 
+     * {@link ShaderNodeDiagram#autoLayout() } for an example. Maybe you can
+     * come up with a better/easier solution
+     */
+    public abstract void autoLayout();
+    
+    /**
+     * This toggles continuous updates for material previews
+     * @param on
+     */
+    public abstract void toggleUpdateThread(boolean on);
+    
+    @Override
+    public void componentResized(ComponentEvent e) {
+        minWidth = e.getComponent().getWidth() - 2;
+        minHeight = e.getComponent().getHeight() - 2;
+        fixSize();
+    }
+
+    @Override
+    public void componentMoved(ComponentEvent e) {}
+
+    @Override
+    public void componentShown(ComponentEvent e) {}
+
+    @Override
+    public void componentHidden(ComponentEvent e) {}
+
+}

+ 2 - 1
jme3-core/src/com/jme3/gde/core/properties/preview/TexturePreview.java

@@ -57,7 +57,7 @@ import javax.swing.JLabel;
  */
 public class TexturePreview implements SceneListener {
 
-    private final ProjectAssetManager assetManager;
+    final ProjectAssetManager assetManager;
     private JComponent picPreview;
     private final Geometry quad;
     private final Geometry quad3D;
@@ -176,4 +176,5 @@ public class TexturePreview implements SceneListener {
             }
         });
     }
+
 }

+ 9 - 0
jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/BackdropPanel.java

@@ -528,5 +528,14 @@ public class BackdropPanel extends JPanel implements MouseListener, ChangeListen
             ((MouseMotionListener) c).mouseMoved(SwingUtilities.convertMouseEvent(this, e, c));
         }
     }
+    
+    public MaterialPreviewRenderer getRenderer(){
+        return renderer;
+    }
 
+    public void refreshOnly() {
+        if (mat != null) {
+            renderer.refreshOnly();
+        }
+    }
 }

+ 3 - 0
jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/Bundle.properties

@@ -19,3 +19,6 @@ ShaderNodeToolBar.codeButton.toolTipText=Display code
 NodeToolBar.deleteButton.toolTipText=Delete node
 NodeToolBar.deleteButton.text=
 NodeToolBar.codeButton.toolTipText=Display code
+MatDefEditorToolBar.jToggleButton2.toolTipText=Toggle Live Material Updates
+MatDefEditorToolBar.jToggleButton2.text=toggleLiveUpdates
+MatDefEditorToolBar.jToggleButton2.actionCommand=toggleLiveUpdates

+ 24 - 1
jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/MatDefEditorToolBar.form

@@ -31,7 +31,9 @@
               <Component id="jButton1" min="-2" pref="53" max="-2" attributes="0"/>
               <EmptySpace max="-2" attributes="0"/>
               <Component id="jButton2" min="-2" max="-2" attributes="0"/>
-              <EmptySpace min="0" pref="103" max="32767" attributes="0"/>
+              <EmptySpace max="32767" attributes="0"/>
+              <Component id="jToggleButton2" min="-2" pref="23" max="-2" attributes="0"/>
+              <EmptySpace min="-2" pref="99" max="-2" attributes="0"/>
           </Group>
       </Group>
     </DimensionLayout>
@@ -42,6 +44,7 @@
               <Component id="techniqueComboBox" alignment="3" min="-2" max="-2" attributes="0"/>
               <Component id="jButton1" alignment="3" min="-2" max="-2" attributes="0"/>
               <Component id="jButton2" alignment="3" min="-2" max="-2" attributes="0"/>
+              <Component id="jToggleButton2" alignment="3" min="-2" max="-2" attributes="0"/>
           </Group>
           <Component id="jSeparator1" alignment="0" max="32767" attributes="0"/>
       </Group>
@@ -106,5 +109,25 @@
         <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButton2ActionPerformed"/>
       </Events>
     </Component>
+    <Component class="javax.swing.JToggleButton" name="jToggleButton2">
+      <Properties>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="com/jme3/gde/materialdefinition/editor/Bundle.properties" key="MatDefEditorToolBar.jToggleButton2.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/materialdefinition/editor/Bundle.properties" key="MatDefEditorToolBar.jToggleButton2.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+        <Property name="actionCommand" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="com/jme3/gde/materialdefinition/editor/Bundle.properties" key="MatDefEditorToolBar.jToggleButton2.actionCommand" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+        <Property name="horizontalAlignment" type="int" value="11"/>
+        <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+          <Dimension value="[99, 24]"/>
+        </Property>
+      </Properties>
+      <Events>
+        <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jToggleButton2ActionPerformed"/>
+      </Events>
+    </Component>
   </SubComponents>
 </Form>

+ 30 - 2
jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/MatDefEditorToolBar.java

@@ -42,6 +42,7 @@ import javax.swing.JLabel;
 import javax.swing.JList;
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
+import javax.swing.JToggleButton;
 import javax.swing.ListCellRenderer;
 
 /**
@@ -101,6 +102,7 @@ public class MatDefEditorToolBar extends JPanel {
         jButton1 = new javax.swing.JButton();
         jSeparator1 = new javax.swing.JSeparator();
         jButton2 = new javax.swing.JButton();
+        jToggleButton2 = new javax.swing.JToggleButton();
 
         setPreferredSize(new java.awt.Dimension(474, 20));
 
@@ -133,6 +135,17 @@ public class MatDefEditorToolBar extends JPanel {
             }
         });
 
+        org.openide.awt.Mnemonics.setLocalizedText(jToggleButton2, org.openide.util.NbBundle.getMessage(MatDefEditorToolBar.class, "MatDefEditorToolBar.jToggleButton2.text")); // NOI18N
+        jToggleButton2.setToolTipText(org.openide.util.NbBundle.getMessage(MatDefEditorToolBar.class, "MatDefEditorToolBar.jToggleButton2.toolTipText")); // NOI18N
+        jToggleButton2.setActionCommand(org.openide.util.NbBundle.getMessage(MatDefEditorToolBar.class, "MatDefEditorToolBar.jToggleButton2.actionCommand")); // NOI18N
+        jToggleButton2.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING);
+        jToggleButton2.setPreferredSize(new java.awt.Dimension(99, 24));
+        jToggleButton2.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                jToggleButton2ActionPerformed(evt);
+            }
+        });
+
         javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
         this.setLayout(layout);
         layout.setHorizontalGroup(
@@ -147,7 +160,9 @@ public class MatDefEditorToolBar extends JPanel {
                 .addComponent(jButton1, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE)
                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                 .addComponent(jButton2)
-                .addGap(0, 103, Short.MAX_VALUE))
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+                .addComponent(jToggleButton2, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
+                .addGap(99, 99, 99))
         );
         layout.setVerticalGroup(
             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@@ -155,7 +170,8 @@ public class MatDefEditorToolBar extends JPanel {
                 .addComponent(jLabel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                 .addComponent(techniqueComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                 .addComponent(jButton1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
-                .addComponent(jButton2))
+                .addComponent(jButton2)
+                .addComponent(jToggleButton2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
             .addComponent(jSeparator1)
         );
     }// </editor-fold>//GEN-END:initComponents
@@ -187,16 +203,28 @@ public class MatDefEditorToolBar extends JPanel {
         }
     }//GEN-LAST:event_jButton1ActionPerformed
 
+    /**
+     * Autolayout button
+     * @param evt 
+     */
     private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton2ActionPerformed
        parent.getDiagram().autoLayout();
     }//GEN-LAST:event_jButton2ActionPerformed
 
+    /**
+     * Toggle continuous updates thread 
+     * @param evt 
+     */
+    private void jToggleButton2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jToggleButton2ActionPerformed
+        parent.getDiagram().toggleUpdateThread(((JToggleButton)evt.getSource()).isSelected());
+    }//GEN-LAST:event_jToggleButton2ActionPerformed
 
     // Variables declaration - do not modify//GEN-BEGIN:variables
     private javax.swing.JButton jButton1;
     private javax.swing.JButton jButton2;
     private javax.swing.JLabel jLabel1;
     private javax.swing.JSeparator jSeparator1;
+    private javax.swing.JToggleButton jToggleButton2;
     private javax.swing.JComboBox<TechniqueBlock> techniqueComboBox;
     // End of variables declaration//GEN-END:variables
 }

+ 7 - 9
jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/MatDefEditorlElement.java

@@ -39,6 +39,7 @@ import com.jme3.gde.core.editor.nodes.NodePanel;
 import com.jme3.gde.core.editor.nodes.Selectable;
 import com.jme3.asset.ShaderNodeDefinitionKey;
 import com.jme3.gde.core.assets.ProjectAssetManager;
+import com.jme3.gde.core.errorreport.ExceptionPanel;
 import com.jme3.gde.materialdefinition.EditableMatDefFile;
 import com.jme3.gde.materialdefinition.MatDefDataObject;
 import com.jme3.gde.materialdefinition.MatDefMetaData;
@@ -81,6 +82,8 @@ import org.netbeans.core.spi.multiview.CloseOperationState;
 import org.netbeans.core.spi.multiview.MultiViewElement;
 import org.netbeans.core.spi.multiview.MultiViewElementCallback;
 import org.netbeans.core.spi.multiview.text.MultiViewEditorElement;
+import org.openide.DialogDescriptor;
+import org.openide.DialogDisplayer;
 import org.openide.awt.UndoRedo;
 import org.openide.cookies.EditorCookie;
 import org.openide.filesystems.FileObject;
@@ -769,15 +772,10 @@ public final class MatDefEditorlElement extends JPanel implements
             }
         } else {
             diagram1.clear();
-            JLabel error = new JLabel("<html><center>Cannot load material definition.<br>Please see the error log and fix it in the text editor</center></html>");
-            error.setForeground(Color.ORANGE);
-            error.setFont(new Font("Arial", Font.BOLD, 24));
-            error.setBounds(0, 0, 400, 100);
-            jScrollPane1.getHorizontalScrollBar().setValue(0);
-            error.setLocation(jScrollPane1.getViewport().getWidth() / 2 - 200, jScrollPane1.getViewport().getHeight() / 2 - 50);
-            diagram1.add(error);
-            diagram1.repaint();
-
+            ExceptionPanel ep = new ExceptionPanel("Please see the error log and fix it in the text editor", false);
+            DialogDescriptor d = new DialogDescriptor(ep, "Cannot load material definition", true, new Object[] { DialogDescriptor.OK_OPTION }, DialogDescriptor.DEFAULT_OPTION, DialogDescriptor.DEFAULT_ALIGN, null, null);
+            DialogDisplayer.getDefault().notifyLater(d);
+            
         }
     }
 }

+ 539 - 492
jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/ShaderNodeDiagram.java

@@ -1,492 +1,539 @@
-/*
- *  Copyright (c) 2009-2018 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.materialdefinition.editor;
-
-import com.jme3.gde.core.editor.nodes.Connection;
-import com.jme3.gde.core.editor.nodes.Diagram;
-import com.jme3.gde.core.editor.nodes.NodePanel;
-import com.jme3.gde.core.editor.nodes.Selectable;
-import com.jme3.gde.core.assets.ProjectAssetManager;
-import com.jme3.gde.materialdefinition.dialog.AddAttributeDialog;
-import com.jme3.gde.materialdefinition.dialog.AddMaterialParameterDialog;
-import com.jme3.gde.materialdefinition.dialog.AddNodeDialog;
-import com.jme3.gde.materialdefinition.dialog.AddWorldParameterDialog;
-import com.jme3.gde.materialdefinition.editor.ShaderNodePanel.NodeType;
-import com.jme3.gde.materialdefinition.fileStructure.ShaderNodeBlock;
-import com.jme3.gde.materialdefinition.fileStructure.leaves.MappingBlock;
-import com.jme3.gde.core.editor.icons.Icons;
-import com.jme3.gde.materialdefinition.utils.MaterialUtils;
-import com.jme3.material.Material;
-import com.jme3.shader.Shader;
-//import static com.jme3.gde.materialdefinition.editor.ShaderNodePanel.NodeType;
-import com.jme3.shader.ShaderNodeDefinition;
-import com.jme3.shader.ShaderNodeVariable;
-import com.jme3.shader.UniformBinding;
-import com.jme3.shader.VarType;
-import java.awt.Point;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.ComponentEvent;
-import java.awt.event.ComponentListener;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseMotionListener;
-import java.util.ArrayList;
-import java.util.List;
-import javax.swing.JMenuItem;
-import javax.swing.JViewport;
-import javax.swing.SwingUtilities;
-
-/**
- * The Diagram is the main canvas where all nodes {@link DraggablePanel} and
- * their connections {@link ConnectionEndpoint} {@link Connection} are added onto.
- * @author Nehon
- */
-public class ShaderNodeDiagram extends Diagram implements ComponentListener {
-
-    protected List<ShaderOutBusPanel> outBuses = new ArrayList<ShaderOutBusPanel>();
-    private String currentTechniqueName;
-    private final BackdropPanel backDrop = new BackdropPanel();
-    private final Point pp = new Point();
-
-    @SuppressWarnings("LeakingThisInConstructor")
-    public ShaderNodeDiagram() {
-        super();
-    }
-    
-    @Override
-    protected boolean mouseLMBPrePressedEvent(MouseEvent e) {
-        for (ShaderOutBusPanel outBusPanel : outBuses) {
-            Point p = SwingUtilities.convertPoint(this, e.getX(), e.getY(), outBusPanel);
-            if (outBusPanel.contains(p)) {
-                MouseEvent me = SwingUtilities.convertMouseEvent(this, e, outBusPanel);
-                outBusPanel.dispatchEvent(me);
-                if (me.isConsumed()) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-    
-    @Override
-    public void mouseReleased(MouseEvent e) {
-        if (SwingUtilities.isLeftMouseButton(e)) {
-            if (draggedFrom != null && draggedFrom.getNode() instanceof ShaderOutBusPanel) {
-                MouseEvent me = SwingUtilities.convertMouseEvent(this, e, draggedFrom.getNode());
-                draggedFrom.getNode().dispatchEvent(me);
-                if (me.isConsumed()) {
-                    return;
-                }
-            }
-
-            dispatchToOutBuses(e);
-        } else {
-            super.mouseReleased(e); // Handle all the UI Stuff
-        }
-
-    }
-    
-    @Override
-    public void mouseMoved(MouseEvent e) {
-        super.mouseMoved(e);
-        dispatchToOutBuses(e);
-    }
-    
-    @Override
-    public void mouseDragged(MouseEvent e) {
-        if (SwingUtilities.isLeftMouseButton(e)) {
-            if (draggedFrom == null) {
-                for (Selectable selectedItem : selectedItems) {
-                    if (selectedItem instanceof ShaderOutBusPanel) {
-                        ShaderOutBusPanel bus = (ShaderOutBusPanel) selectedItem;
-                        MouseEvent me = SwingUtilities.convertMouseEvent(this, e, bus);
-                        bus.dispatchEvent(me);
-                    }
-                }
-            }
-        } else {
-            super.mouseDragged(e); // Handle all the UI Stuff
-        }
-    }
-
-    /**
-     * Called by {@link ConnectionEndpoint} when a Curve has been dragged
-     */
-    @Override
-    protected void draggingDot(MouseEvent e) {
-        for (ShaderOutBusPanel outBusPanel: outBuses) {
-            Point p = SwingUtilities.convertPoint(this, e.getX(), e.getY(), outBusPanel);
-            if (outBusPanel.contains(p)) {
-                MouseEvent me = SwingUtilities.convertMouseEvent(this, e, outBusPanel);
-                outBusPanel.draggingDot(me);
-                if (me.isConsumed()) {
-                    return;
-                }
-            }
-        }
-    }
-    
-    
-    public void refreshPreviews(Material mat, String technique) {
-        for (ShaderOutBusPanel outBusPanel : outBuses) {
-            outBusPanel.updatePreview(mat, technique);
-        }
-        if (backDrop.isVisible()) {
-            backDrop.showMaterial(mat, technique);
-        }
-    }
-
-    public void displayBackdrop() {
-        if (backDrop.getParent() == null) {
-            add(backDrop);
-            ((JViewport)getParent()).addChangeListener(backDrop);
-        }
-
-        backDrop.setVisible(true);
-        backDrop.update(((JViewport)getParent()));
-    }
-    
-    public void setCurrentTechniqueName(String currentTechniqueName) {
-        this.currentTechniqueName = currentTechniqueName;
-    }
-
-    public String getCurrentTechniqueName() {
-        return currentTechniqueName;
-    }
-
-    @Override
-    public void addConnection(Connection conn) {
-        super.addConnection(conn);
-        
-        // Adjust outBuses and repaint again
-        for (ShaderOutBusPanel bus : outBuses) {
-            setComponentZOrder(bus, getComponentCount() - 1);
-        }
-        repaint();
-    }
-    
-    @Override
-    protected void showEdit(NodePanel node) {
-        if (node instanceof ShaderNodePanel &&
-                parent instanceof MatDefEditorlElement) {
-            ((MatDefEditorlElement)parent).showShaderEditor(node.getName(), 
-                    ((ShaderNodePanel)node).getType(), 
-                    ((ShaderNodePanel)node).filePaths);
-        }
-    }
-
-    @Override
-    public void addNode(NodePanel node) {
-        super.addNode(node);
-        if (node instanceof ShaderNodePanel) {
-            ((ShaderNodePanel)node).setTechName(currentTechniqueName);
-        }
-    }
-
-    public void addOutBus(ShaderOutBusPanel bus) {
-        outBuses.add(bus);
-        bus.setDiagram(this);
-        add(bus);
-        setComponentZOrder(bus, getComponentCount() - 1);
-        addComponentListener(bus);
-        bus.componentResized(new ComponentEvent(this, ActionEvent.ACTION_PERFORMED));
-        bus.revalidate();
-    }
-
-    private String fixNodeName(String name) {
-        return fixNodeName(name, 0);
-    }
-
-    private String fixNodeName(String name, int count) {
-        for (NodePanel nodePanel : nodes) {
-            if ((name + (count == 0 ? "" : count)).equals(nodePanel.getName())) {
-                return fixNodeName(name, count + 1);
-            }
-        }
-        return name + (count == 0 ? "" : count);
-    }
-    
-    public void addNodesFromDefs(List<ShaderNodeDefinition> defList, String path, Point clickPosition) {
-        int i = 0;
-        for (ShaderNodeDefinition def : defList) {
-            ShaderNodeBlock sn = new ShaderNodeBlock(def, path);
-            sn.setName(fixNodeName(sn.getName()));
-            NodePanel np = new ShaderNodePanel(sn, def);
-            addNode(np);
-            np.setLocation(clickPosition.x + i * 150, clickPosition.y);
-            sn.setSpatialOrder(np.getLocation().x);
-            i++;
-            np.revalidate();
-            ((MatDefEditorlElement)getEditorParent()).notifyAddNode(sn, def);
-        }
-        repaint();
-    }
-
-    public void addMatParam(String type, String name, Point point) {
-        String fixedType = type;
-        if (type.equals("Color")) {
-            fixedType = "Vector4";
-        }
-        ShaderNodeVariable param = new ShaderNodeVariable(VarType.valueOf(fixedType).getGlslType(), name);
-        NodePanel np = new ShaderNodePanel(param, NodeType.MatParam);
-        addNode(np);
-        np.setLocation(point.x, point.y);
-        np.revalidate();
-        repaint();
-        ((MatDefEditorlElement)getEditorParent()).notifyAddMapParam(type, name);
-    }
-
-    public void addWorldParam(UniformBinding binding, Point point) {
-        ShaderNodeVariable param = new ShaderNodeVariable(binding.getGlslType(), binding.name());
-        NodePanel np = new ShaderNodePanel(param, NodeType.WorldParam);
-        addNode(np);
-        np.setLocation(point.x, point.y);
-        np.revalidate();
-        repaint();
-        ((MatDefEditorlElement)getEditorParent()).notifyAddWorldParam(binding.name());
-    }
-
-    public void addAttribute(String name, String type, Point point) {
-        ShaderNodeVariable param = new ShaderNodeVariable(type, "Attr", name);
-        NodePanel np = new ShaderNodePanel(param, NodeType.Attribute);
-        addNode(np);
-        np.setLocation(point.x, point.y);
-        np.revalidate();
-        repaint();
-    }
-
-    @Override
-    public String makeKeyForConnection(Connection con, Object obj) {
-        return MaterialUtils.makeKey((MappingBlock)obj, currentTechniqueName);
-    }
-
-    /**
-     * Find a OutBusPanel which corresponds to the given key (unique id). 
-     * Use this to locate busses on the diagram
-     * 
-     * @param key The key
-     * @return hopefully the correct panel
-     */
-    public ShaderOutBusPanel getOutBusPanel(String key) {
-        for (ShaderOutBusPanel out : outBuses) {
-            if (out.getKey().equals(key)) {
-                return out;
-            }
-        }
-        return null;
-    }
-
-    @Override
-    protected Selectable trySelect(String key) {
-        for (ShaderOutBusPanel outBusPanel : outBuses) {
-            if (outBusPanel.getKey().equals(key)) {
-                return outBusPanel;
-            }
-        }
-        
-        return null;
-    }
-    
-    @Override
-    public void clear() {
-        super.clear();
-        outBuses.clear();
-    }
-
-    @Override
-    protected void createPopupMenu() {
-        super.createPopupMenu();
-        JMenuItem nodeItem = createMenuItem("Node", Icons.node);
-        nodeItem.addActionListener(new ActionListener() {
-            @Override
-            public void actionPerformed(ActionEvent e) {
-                AddNodeDialog d = new AddNodeDialog(null, true, 
-                        ((MatDefEditorlElement)parent).obj.getLookup()
-                            .lookup(ProjectAssetManager.class), 
-                    ShaderNodeDiagram.this, clickLoc);
-                d.setLocationRelativeTo(null);
-                d.setVisible(true);
-            }
-        });
-
-        contextMenu.add(nodeItem);
-        contextMenu.add(createSeparator());
-        JMenuItem matParamItem = createMenuItem("Material Parameter", Icons.mat);
-        matParamItem.addActionListener(new ActionListener() {
-            @Override
-            public void actionPerformed(ActionEvent e) {
-                AddMaterialParameterDialog d = new AddMaterialParameterDialog(null, true, ShaderNodeDiagram.this, clickLoc);
-                d.setLocationRelativeTo(null);
-                d.setVisible(true);
-            }
-        });
-        contextMenu.add(matParamItem);
-        JMenuItem worldParamItem = createMenuItem("World Parameter", Icons.world);
-        worldParamItem.addActionListener(new ActionListener() {
-            @Override
-            public void actionPerformed(ActionEvent e) {
-                AddWorldParameterDialog d = new AddWorldParameterDialog(null, true, ShaderNodeDiagram.this, clickLoc);
-                d.setLocationRelativeTo(null);
-                d.setVisible(true);
-            }
-        });
-        contextMenu.add(worldParamItem);
-        JMenuItem attributeItem = createMenuItem("Attribute", Icons.attrib);
-        attributeItem.addActionListener(new ActionListener() {
-            @Override
-            public void actionPerformed(ActionEvent e) {
-                AddAttributeDialog d = new AddAttributeDialog(null, true, ShaderNodeDiagram.this, clickLoc);
-                d.setLocationRelativeTo(null);
-                d.setVisible(true);
-            }
-        });
-        contextMenu.add(attributeItem);
-        contextMenu.add(createSeparator());
-        JMenuItem outputItem = createMenuItem("Output color", Icons.output);
-        contextMenu.add(outputItem);
-        outputItem.addActionListener(new ActionListener() {
-            @Override
-            public void actionPerformed(ActionEvent e) {
-                ShaderOutBusPanel p2 = new ShaderOutBusPanel("color" + (outBuses.size() - 1), Shader.ShaderType.Fragment);
-                p2.setBounds(0, 350 + 50 * (outBuses.size() - 1), p2.getWidth(), p2.getHeight());
-
-                addOutBus(p2);
-
-            }
-        });
-    }
-
-    private void dispatchToOutBuses(MouseEvent e) {
-        for (ShaderOutBusPanel outBusPanel : outBuses) {
-            Point p = SwingUtilities.convertPoint(this, e.getX(), e.getY(), outBusPanel);
-            if (outBusPanel.contains(p)) {
-                MouseEvent me = SwingUtilities.convertMouseEvent(this, e, outBusPanel);
-                outBusPanel.dispatchEvent(me);
-                if (me.isConsumed()) {
-                    return;
-                }
-            }
-        }
-    }
-
-    @Override
-    protected int calcMaxWidth() {
-        int maxHeight = 0;
-        for (ShaderOutBusPanel outBusPanel : outBuses) {
-            int h = outBusPanel.getLocation().y + outBusPanel.getHeight();
-            if (h > maxHeight) {
-                maxHeight = h;
-            }
-        }
-        return maxHeight;
-    }
-    
-    @Override
-    protected int calcMaxHeight() {
-        return 0;
-    }
-    
-    int minWidth = 0;
-    int minHeight = 0;
-
-    @Override
-    public void autoLayout() {
-
-        int offset = 550;
-        for (ShaderOutBusPanel outBus : outBuses) {
-            if (outBus.getKey().equalsIgnoreCase("position")) {
-                outBus.setLocation(0, 100);
-                
-            } else {
-                outBus.setLocation(0, offset);
-                offset += 260;
-            }
-            getEditorParent().savePositionToMetaData(outBus.getKey(), outBus.getLocation().x, outBus.getLocation().y);
-        }
-        offset = 0;
-        String keys = "";
-        for (NodePanel nodeP: nodes) {
-            ShaderNodePanel node;
-            if (nodeP instanceof ShaderNodePanel) {
-                node = (ShaderNodePanel)nodeP;
-            } else {
-                continue; // Don't layout foreign nodes, actually they shouldnt
-                // even be there...
-            }
-            if (node.getType() == NodeType.Vertex || node.getType() == NodeType.Fragment) {
-                node.setLocation(offset + 200, getNodeTop(node));
-                getEditorParent().savePositionToMetaData(node.getKey(), node.getLocation().x, node.getLocation().y);
-                int pad = getNodeTop(node);
-                for (Connection connection: connections) {
-                    if (connection.getEnd().getNode() == node) {
-                        if (connection.getStart().getNode() instanceof ShaderNodePanel) {
-                            ShaderNodePanel startP = (ShaderNodePanel)connection.getStart().getNode();
-                            if (startP.getType() != NodeType.Vertex && startP.getType() != NodeType.Fragment) {
-                                startP.setLocation(offset + 30, pad);
-                                getEditorParent().savePositionToMetaData(startP.getKey(), startP.getLocation().x, startP.getLocation().y);
-                                keys += startP.getKey() + "|";
-                                pad += 50;
-                            }
-                        }
-                    }
-                }
-            }
-            offset += 320;
-        }
-        offset = 0;
-        for (NodePanel nodeP: nodes) {
-            ShaderNodePanel node;
-            if (nodeP instanceof ShaderNodePanel) {
-                node = (ShaderNodePanel)nodeP;
-            } else {
-                continue; // Don't layout foreign nodes, actually they shouldnt
-                // even be there...
-            }
-            if (node.getType() != NodeType.Vertex && node.getType() != NodeType.Fragment && !(keys.contains(node.getKey()))) {
-                node.setLocation(offset + 10, 0);
-                getEditorParent().savePositionToMetaData(node.getKey(), node.getLocation().x, node.getLocation().y);
-                offset += 130;
-            }
-        }
-    }
-
-    private int getNodeTop(ShaderNodePanel node) {
-        if (node.getType() == NodeType.Vertex) {
-            return 150;
-        }
-        if (node.getType() == NodeType.Fragment) {
-            return 400;
-        }
-        return 0;
-
-    }
-}
+/*
+ *  Copyright (c) 2009-2018 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.materialdefinition.editor;
+
+import com.jme3.gde.core.editor.nodes.Connection;
+import com.jme3.gde.core.editor.nodes.Diagram;
+import com.jme3.gde.core.editor.nodes.NodePanel;
+import com.jme3.gde.core.editor.nodes.Selectable;
+import com.jme3.gde.core.assets.ProjectAssetManager;
+import com.jme3.gde.materialdefinition.dialog.AddAttributeDialog;
+import com.jme3.gde.materialdefinition.dialog.AddMaterialParameterDialog;
+import com.jme3.gde.materialdefinition.dialog.AddNodeDialog;
+import com.jme3.gde.materialdefinition.dialog.AddWorldParameterDialog;
+import com.jme3.gde.materialdefinition.editor.ShaderNodePanel.NodeType;
+import com.jme3.gde.materialdefinition.fileStructure.ShaderNodeBlock;
+import com.jme3.gde.materialdefinition.fileStructure.leaves.MappingBlock;
+import com.jme3.gde.core.editor.icons.Icons;
+import com.jme3.gde.core.errorreport.ExceptionUtils;
+import com.jme3.gde.materialdefinition.utils.MaterialUtils;
+import com.jme3.material.Material;
+import com.jme3.shader.Shader;
+import com.jme3.shader.ShaderNodeDefinition;
+import com.jme3.shader.ShaderNodeVariable;
+import com.jme3.shader.UniformBinding;
+import com.jme3.shader.VarType;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.JMenuItem;
+import javax.swing.JViewport;
+import javax.swing.SwingUtilities;
+
+/**
+ * The Diagram is the main canvas where all nodes {@link DraggablePanel} and
+ * their connections {@link ConnectionEndpoint} {@link Connection} are added onto.
+ * @author Nehon
+ */
+public class ShaderNodeDiagram extends Diagram implements ComponentListener {
+
+    protected List<ShaderOutBusPanel> outBuses = new ArrayList<ShaderOutBusPanel>();
+    private String currentTechniqueName;
+    private final BackdropPanel backDrop = new BackdropPanel();
+    private final Point pp = new Point();
+    private UpdateBackgroundRunnable backgroundUpdate = new UpdateBackgroundRunnable();
+
+    @SuppressWarnings("LeakingThisInConstructor")
+    public ShaderNodeDiagram() {
+        super();
+    }
+    
+    @Override
+    protected boolean mouseLMBPrePressedEvent(MouseEvent e) {
+        for (ShaderOutBusPanel outBusPanel : outBuses) {
+            Point p = SwingUtilities.convertPoint(this, e.getX(), e.getY(), outBusPanel);
+            if (outBusPanel.contains(p)) {
+                MouseEvent me = SwingUtilities.convertMouseEvent(this, e, outBusPanel);
+                outBusPanel.dispatchEvent(me);
+                if (me.isConsumed()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+    
+    @Override
+    public void mouseReleased(MouseEvent e) {
+        if (SwingUtilities.isLeftMouseButton(e)) {
+            if (draggedFrom != null && draggedFrom.getNode() instanceof ShaderOutBusPanel) {
+                MouseEvent me = SwingUtilities.convertMouseEvent(this, e, draggedFrom.getNode());
+                draggedFrom.getNode().dispatchEvent(me);
+                if (me.isConsumed()) {
+                    return;
+                }
+            }
+            dispatchToOutBuses(e);
+        } else {
+            super.mouseReleased(e); // Handle all the UI Stuff
+        }
+
+    }
+    
+    @Override
+    public void mouseMoved(MouseEvent e) {
+        super.mouseMoved(e);
+        dispatchToOutBuses(e);
+    }
+    
+    @Override
+    public void mouseDragged(MouseEvent e) {
+        if (SwingUtilities.isLeftMouseButton(e)) {
+            if (draggedFrom == null) {
+                for (Selectable selectedItem : selectedItems) {
+                    if (selectedItem instanceof ShaderOutBusPanel) {
+                        ShaderOutBusPanel bus = (ShaderOutBusPanel) selectedItem;
+                        MouseEvent me = SwingUtilities.convertMouseEvent(this, e, bus);
+                        bus.dispatchEvent(me);
+                    }
+                }
+//                if (!e.isConsumed()){
+//                    setLocation(e.getX(), e.getY());
+//                }
+            }
+        } else {
+            super.mouseDragged(e); // Handle all the UI Stuff
+        }
+    }
+
+    /**
+     * Called by {@link ConnectionEndpoint} when a Curve has been dragged
+     */
+    @Override
+    protected void draggingDot(MouseEvent e) {
+        for (ShaderOutBusPanel outBusPanel: outBuses) {
+            Point p = SwingUtilities.convertPoint(this, e.getX(), e.getY(), outBusPanel);
+            if (outBusPanel.contains(p)) {
+                MouseEvent me = SwingUtilities.convertMouseEvent(this, e, outBusPanel);
+                outBusPanel.draggingDot(me);
+                if (me.isConsumed()) {
+                    return;
+                }
+            }
+        }
+    }
+    
+    
+    public void refreshPreviews(Material mat, String technique) {
+        for (ShaderOutBusPanel outBusPanel : outBuses) {
+            outBusPanel.updatePreview(mat, technique);
+        }
+        if (backDrop.isVisible()) {
+            backDrop.showMaterial(mat, technique);
+        }
+    }
+
+    public void displayBackdrop() {
+        if (backDrop.getParent() == null) {
+            add(backDrop);
+            ((JViewport)getParent()).addChangeListener(backDrop);
+        }
+
+        backDrop.setVisible(true);
+        backDrop.update(((JViewport)getParent()));
+    }
+    
+    public void setCurrentTechniqueName(String currentTechniqueName) {
+        this.currentTechniqueName = currentTechniqueName;
+    }
+
+    public String getCurrentTechniqueName() {
+        return currentTechniqueName;
+    }
+
+    @Override
+    public void addConnection(Connection conn) {
+        super.addConnection(conn);
+        
+        // Adjust outBuses and repaint again
+        for (ShaderOutBusPanel bus : outBuses) {
+            setComponentZOrder(bus, getComponentCount() - 1);
+        }
+        repaint();
+    }
+    
+    @Override
+    protected void showEdit(NodePanel node) {
+        if (node instanceof ShaderNodePanel &&
+                parent instanceof MatDefEditorlElement) {
+            ((MatDefEditorlElement)parent).showShaderEditor(node.getName(), 
+                    ((ShaderNodePanel)node).getType(), 
+                    ((ShaderNodePanel)node).filePaths);
+        }
+    }
+
+    @Override
+    public void addNode(NodePanel node) {
+        super.addNode(node);
+        if (node instanceof ShaderNodePanel) {
+            ((ShaderNodePanel)node).setTechName(currentTechniqueName);
+        }
+    }
+
+    public void addOutBus(ShaderOutBusPanel bus) {
+        outBuses.add(bus);
+        bus.setDiagram(this);
+        add(bus);
+        setComponentZOrder(bus, getComponentCount() - 1);
+        addComponentListener(bus);
+        bus.componentResized(new ComponentEvent(this, ActionEvent.ACTION_PERFORMED));
+        bus.revalidate();
+    }
+
+    private String fixNodeName(String name) {
+        return fixNodeName(name, 0);
+    }
+
+    private String fixNodeName(String name, int count) {
+        for (NodePanel nodePanel : nodes) {
+            if ((name + (count == 0 ? "" : count)).equals(nodePanel.getName())) {
+                return fixNodeName(name, count + 1);
+            }
+        }
+        return name + (count == 0 ? "" : count);
+    }
+    
+    public void addNodesFromDefs(List<ShaderNodeDefinition> defList, String path, Point clickPosition) {
+        int i = 0;
+        for (ShaderNodeDefinition def : defList) {
+            ShaderNodeBlock sn = new ShaderNodeBlock(def, path);
+            sn.setName(fixNodeName(sn.getName()));
+            NodePanel np = new ShaderNodePanel(sn, def);
+            addNode(np);
+            np.setLocation(clickPosition.x + i * 150, clickPosition.y);
+            sn.setSpatialOrder(np.getLocation().x);
+            i++;
+            np.revalidate();
+            ((MatDefEditorlElement)getEditorParent()).notifyAddNode(sn, def);
+        }
+        repaint();
+    }
+
+    public void addMatParam(String type, String name, Point point) {
+        String fixedType = type;
+        if (type.equals("Color")) {
+            fixedType = "Vector4";
+        }
+        ShaderNodeVariable param = new ShaderNodeVariable(VarType.valueOf(fixedType).getGlslType(), name);
+        NodePanel np = new ShaderNodePanel(param, NodeType.MatParam);
+        addNode(np);
+        np.setLocation(point.x, point.y);
+        np.revalidate();
+        repaint();
+        ((MatDefEditorlElement)getEditorParent()).notifyAddMapParam(type, name);
+    }
+
+    public void addWorldParam(UniformBinding binding, Point point) {
+        ShaderNodeVariable param = new ShaderNodeVariable(binding.getGlslType(), binding.name());
+        NodePanel np = new ShaderNodePanel(param, NodeType.WorldParam);
+        addNode(np);
+        np.setLocation(point.x, point.y);
+        np.revalidate();
+        repaint();
+        ((MatDefEditorlElement)getEditorParent()).notifyAddWorldParam(binding.name());
+    }
+
+    public void addAttribute(String name, String type, Point point) {
+        ShaderNodeVariable param = new ShaderNodeVariable(type, "Attr", name);
+        NodePanel np = new ShaderNodePanel(param, NodeType.Attribute);
+        addNode(np);
+        np.setLocation(point.x, point.y);
+        np.revalidate();
+        repaint();
+    }
+
+    @Override
+    public String makeKeyForConnection(Connection con, Object obj) {
+        return MaterialUtils.makeKey((MappingBlock)obj, currentTechniqueName);
+    }
+
+    /**
+     * Find a OutBusPanel which corresponds to the given key (unique id). 
+     * Use this to locate busses on the diagram
+     * 
+     * @param key The key
+     * @return hopefully the correct panel
+     */
+    public ShaderOutBusPanel getOutBusPanel(String key) {
+        for (ShaderOutBusPanel out : outBuses) {
+            if (out.getKey().equals(key)) {
+                return out;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    protected Selectable trySelect(String key) {
+        for (ShaderOutBusPanel outBusPanel : outBuses) {
+            if (outBusPanel.getKey().equals(key)) {
+                return outBusPanel;
+            }
+        }
+        
+        return null;
+    }
+    
+    @Override
+    public void clear() {
+        super.clear();
+        outBuses.clear();
+        backgroundUpdate.setRunning(false);
+    }
+
+    @Override
+    protected void createPopupMenu() {
+        super.createPopupMenu();
+        JMenuItem nodeItem = createMenuItem("Node", Icons.node);
+        nodeItem.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                AddNodeDialog d = new AddNodeDialog(null, true, 
+                        ((MatDefEditorlElement)parent).obj.getLookup()
+                            .lookup(ProjectAssetManager.class), 
+                    ShaderNodeDiagram.this, clickLoc);
+                d.setLocationRelativeTo(null);
+                d.setVisible(true);
+            }
+        });
+
+        contextMenu.add(nodeItem);
+        contextMenu.add(createSeparator());
+        JMenuItem matParamItem = createMenuItem("Material Parameter", Icons.mat);
+        matParamItem.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                AddMaterialParameterDialog d = new AddMaterialParameterDialog(null, true, ShaderNodeDiagram.this, clickLoc);
+                d.setLocationRelativeTo(null);
+                d.setVisible(true);
+            }
+        });
+        contextMenu.add(matParamItem);
+        JMenuItem worldParamItem = createMenuItem("World Parameter", Icons.world);
+        worldParamItem.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                AddWorldParameterDialog d = new AddWorldParameterDialog(null, true, ShaderNodeDiagram.this, clickLoc);
+                d.setLocationRelativeTo(null);
+                d.setVisible(true);
+            }
+        });
+        contextMenu.add(worldParamItem);
+        JMenuItem attributeItem = createMenuItem("Attribute", Icons.attrib);
+        attributeItem.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                AddAttributeDialog d = new AddAttributeDialog(null, true, ShaderNodeDiagram.this, clickLoc);
+                d.setLocationRelativeTo(null);
+                d.setVisible(true);
+            }
+        });
+        contextMenu.add(attributeItem);
+        contextMenu.add(createSeparator());
+        JMenuItem outputItem = createMenuItem("Output color", Icons.output);
+        contextMenu.add(outputItem);
+        outputItem.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                ShaderOutBusPanel p2 = new ShaderOutBusPanel("color" + (outBuses.size() - 1), Shader.ShaderType.Fragment);
+                p2.setBounds(0, 350 + 50 * (outBuses.size() - 1), p2.getWidth(), p2.getHeight());
+
+                addOutBus(p2);
+
+            }
+        });
+    }
+
+    private void dispatchToOutBuses(MouseEvent e) {
+        for (ShaderOutBusPanel outBusPanel : outBuses) {
+            Point p = SwingUtilities.convertPoint(this, e.getX(), e.getY(), outBusPanel);
+            if (outBusPanel.contains(p)) {
+                MouseEvent me = SwingUtilities.convertMouseEvent(this, e, outBusPanel);
+                outBusPanel.dispatchEvent(me);
+                if (me.isConsumed()) {
+                    return;
+                }
+            }
+        }
+    }
+
+    @Override
+    protected int calcMaxWidth() {
+        int maxHeight = 0;
+        for (ShaderOutBusPanel outBusPanel : outBuses) {
+            int h = outBusPanel.getLocation().y + outBusPanel.getHeight();
+            if (h > maxHeight) {
+                maxHeight = h;
+            }
+        }
+        return maxHeight;
+    }
+    
+    @Override
+    protected int calcMaxHeight() {
+        return 0;
+    }
+    
+    int minWidth = 0;
+    int minHeight = 0;
+
+    @Override
+    public void autoLayout() {
+
+        int offset = 550;
+        for (ShaderOutBusPanel outBus : outBuses) {
+            if (outBus.getKey().equalsIgnoreCase("position")) {
+                outBus.setLocation(0, 100);
+                
+            } else {
+                outBus.setLocation(0, offset);
+                offset += 260;
+            }
+            getEditorParent().savePositionToMetaData(outBus.getKey(), outBus.getLocation().x, outBus.getLocation().y);
+        }
+        offset = 0;
+        String keys = "";
+        for (NodePanel nodeP: nodes) {
+            ShaderNodePanel node;
+            if (nodeP instanceof ShaderNodePanel) {
+                node = (ShaderNodePanel)nodeP;
+            } else {
+                continue; // Don't layout foreign nodes, actually they shouldnt
+                // even be there...
+            }
+            if (node.getType() == NodeType.Vertex || node.getType() == NodeType.Fragment) {
+                node.setLocation(offset + 200, getNodeTop(node));
+                getEditorParent().savePositionToMetaData(node.getKey(), node.getLocation().x, node.getLocation().y);
+                int pad = getNodeTop(node);
+                for (Connection connection: connections) {
+                    if (connection.getEnd().getNode() == node) {
+                        if (connection.getStart().getNode() instanceof ShaderNodePanel) {
+                            ShaderNodePanel startP = (ShaderNodePanel)connection.getStart().getNode();
+                            if (startP.getType() != NodeType.Vertex && startP.getType() != NodeType.Fragment) {
+                                startP.setLocation(offset + 30, pad);
+                                getEditorParent().savePositionToMetaData(startP.getKey(), startP.getLocation().x, startP.getLocation().y);
+                                keys += startP.getKey() + "|";
+                                pad += 50;
+                            }
+                        }
+                    }
+                }
+            }
+            offset += 320;
+        }
+        offset = 0;
+        for (NodePanel nodeP: nodes) {
+            ShaderNodePanel node;
+            if (nodeP instanceof ShaderNodePanel) {
+                node = (ShaderNodePanel)nodeP;
+            } else {
+                continue; // Don't layout foreign nodes, actually they shouldnt
+                // even be there...
+            }
+            if (node.getType() != NodeType.Vertex && node.getType() != NodeType.Fragment && !(keys.contains(node.getKey()))) {
+                node.setLocation(offset + 10, 0);
+                getEditorParent().savePositionToMetaData(node.getKey(), node.getLocation().x, node.getLocation().y);
+                offset += 130;
+            }
+        }
+    }
+
+    private int getNodeTop(ShaderNodePanel node) {
+        if (node.getType() == NodeType.Vertex) {
+            return 150;
+        }
+        if (node.getType() == NodeType.Fragment) {
+            return 400;
+        }
+        return 0;
+
+    }
+
+    @Override
+    public void toggleUpdateThread(boolean on) {
+        if(on && !backgroundUpdate.isRunning()){
+            backgroundUpdate.setRunning(true);
+            new Thread(backgroundUpdate).start();
+        } else if (!on && backgroundUpdate.isRunning()){
+            backgroundUpdate.setRunning(false);
+        }
+    }
+
+    private final class UpdateBackgroundRunnable implements Runnable{
+
+        private boolean running;
+        @Override
+        public void run() {
+            while(running) {
+                if (backDrop.isVisible() && !backDrop.getRenderer().isPreviewRequested()) {
+                    backDrop.refreshOnly();
+                }
+                try {
+                    Thread.sleep(20);
+                } catch (InterruptedException ex) {
+                    running = false;
+                    ExceptionUtils.caughtException(ex, "Material update thread caught an exception and shut down.");
+                }
+                if(!ShaderNodeDiagram.this.isShowing()){
+                    running = false;
+                }
+            }
+            Logger.getLogger(ShaderNodeDiagram.class.getName()).log(Level.INFO, "UpdateThread stopped");
+        }
+        
+        public boolean isRunning() {
+            return running;
+        }
+        
+        public void setRunning(boolean on) {
+            this.running = on;
+        }
+    }
+    
+}

+ 2 - 2
jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/ShaderNodePanel.java

@@ -109,9 +109,9 @@ public class ShaderNodePanel extends NodePanel implements InOut,
     public ShaderNodePanel(ShaderNodeVariable singleOut, NodeType type) {
         super();
         this.type = type;
-        List<ShaderNodeVariable> outputs = new ArrayList<ShaderNodeVariable>();
+        List<ShaderNodeVariable> outputs = new ArrayList<>();
         outputs.add(singleOut);
-        init(new ArrayList<ShaderNodeVariable>(), outputs);
+        init(new ArrayList<>(), outputs);
         setToolbar(new ShaderNodeToolBar(this));
     }
     

+ 276 - 256
jme3-materialeditor/src/com/jme3/gde/materials/MaterialPreviewRenderer.java

@@ -1,256 +1,276 @@
-/*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
- */
-package com.jme3.gde.materials;
-
-import com.jme3.asset.AssetNotFoundException;
-import com.jme3.asset.MaterialKey;
-import com.jme3.gde.core.assets.ProjectAssetManager;
-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.gde.core.editor.icons.Icons;
-import com.jme3.material.MatParam;
-import com.jme3.material.Material;
-import com.jme3.math.FastMath;
-import com.jme3.math.Quaternion;
-import com.jme3.math.Vector3f;
-import com.jme3.renderer.RendererException;
-import com.jme3.scene.Geometry;
-import com.jme3.scene.shape.Box;
-import com.jme3.scene.shape.Quad;
-import com.jme3.scene.shape.Sphere;
-import com.jme3.util.TangentBinormalGenerator;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import javax.swing.ImageIcon;
-import javax.swing.JLabel;
-
-/**
- *
- * @author Nehon
- */
-public class MaterialPreviewRenderer implements SceneListener {
-
-    private Geometry sphere;
-    private Geometry box;
-    private Geometry quad;
-    private Geometry currentGeom;
-    private Material currentMaterial;
-    private boolean init = false;
-    private final JLabel label;
-    private final ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(5);
-
-    public enum DisplayType {
-
-        Sphere,
-        Box,
-        Quad
-    }
-
-    public MaterialPreviewRenderer(JLabel label) {
-        this.label = label;
-    }
-
-    private void init() {
-        SceneApplication.getApplication().addSceneListener(this);
-        Sphere sphMesh = new Sphere(32, 32, 2.5f);
-        sphMesh.setTextureMode(Sphere.TextureMode.Projected);
-        sphMesh.updateGeometry(32, 32, 2.5f, false, false);
-        Logger log = Logger.getLogger(TangentBinormalGenerator.class.getName());
-        log.setLevel(Level.SEVERE);
-        TangentBinormalGenerator.generate(sphMesh);
-        sphere = new Geometry("previewSphere", sphMesh);
-        sphere.setLocalRotation(new Quaternion().fromAngleAxis(FastMath.QUARTER_PI, Vector3f.UNIT_X));
-
-        Box boxMesh = new Box(1.75f, 1.75f, 1.75f);
-        TangentBinormalGenerator.generate(boxMesh);
-        box = new Geometry("previewBox", boxMesh);
-        box.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.DEG_TO_RAD * 30, Vector3f.UNIT_X).multLocal(new Quaternion().fromAngleAxis(FastMath.QUARTER_PI, Vector3f.UNIT_Y)));
-
-        Quad quadMesh = new Quad(4.5f, 4.5f);
-        TangentBinormalGenerator.generate(quadMesh);
-        quad = new Geometry("previewQuad", quadMesh);
-        quad.setLocalTranslation(new Vector3f(-2.25f, -2.25f, 0));
-        currentGeom = sphere;
-        init = true;
-    }
-
-    @SuppressWarnings("unchecked")
-    public void showMaterial(final ProjectAssetManager assetManager,final String materialFileName) {
-        if (!init) {
-            init();
-        }
-        exec.execute(new Runnable() {
-
-            @Override
-            public void run() {
-                MaterialKey key = new MaterialKey(assetManager.getRelativeAssetPath(materialFileName));
-                assetManager.deleteFromCache(key);
-                Material mat = assetManager.loadAsset(key);
-                if (mat != null) {
-                    showMaterial(mat);
-                }
-            }
-        });
-        
-
-    }
-
-    public void showMaterial(final Material m) {
-        showMaterial(m, null);
-    }
-
-    public void showMaterial(final Material m, final String techniqueName) {
-        if (!init) {
-            init();
-        }
-        SceneApplication.getApplication().enqueue(new Callable<Material>() {
-
-            @Override
-            public Material call() throws Exception {
-                if (techniqueName != null) {
-                    try {
-                        m.selectTechnique(techniqueName, SceneApplication.getApplication().getRenderManager());
-                    } catch (Exception e) {
-                        //
-                    }
-                }
-                final Material mat = reloadMaterial(m);
-                if (mat != null) {
-                    java.awt.EventQueue.invokeLater(new Runnable() {
-                        @Override
-                        public void run() {
-                            currentMaterial = mat;
-                            currentGeom.setMaterial(mat);
-                            try {
-                                if (currentGeom.getMaterial() != null) {
-                                    PreviewRequest request = new PreviewRequest(MaterialPreviewRenderer.this, currentGeom, label.getWidth(), label.getHeight());
-                                    request.getCameraRequest().setLocation(new Vector3f(0, 0, 7));
-                                    request.getCameraRequest().setLookAt(new Vector3f(0, 0, 0), Vector3f.UNIT_Y);
-                                    SceneApplication.getApplication().createPreview(request);
-                                }
-                            } catch (Exception e) {
-                                java.awt.EventQueue.invokeLater(new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        label.setIcon(Icons.error);
-                                    }
-                                });
-                                smartLog("Error rendering material{0}", e.getMessage());
-                            }
-                        }
-                    });
-
-                }
-                return mat;
-            }
-        });
-    }
-
-    private static int lastErrorHash = 0;
-
-    private void smartLog(String expText, String message) {
-        int hash = message.hashCode();
-        if (hash != lastErrorHash) {
-            Logger.getLogger(MaterialPreviewRenderer.class.getName()).log(Level.SEVERE, expText, message);
-            lastErrorHash = hash;
-        }
-    }
-
-    public Material reloadMaterial(Material mat) {
-        Material dummy;
-        try {
-            ((ProjectAssetManager) mat.getMaterialDef().getAssetManager()).clearCache();
-
-            //creating a dummy mat with the mat def of the mat to reload
-            dummy = new Material(mat.getMaterialDef());
-        
-            for (MatParam matParam : mat.getParams()) {
-                dummy.setParam(matParam.getName(), matParam.getVarType(), matParam.getValue());
-            }
-            if (mat.getActiveTechnique() != null) {
-                dummy.selectTechnique(mat.getActiveTechnique().getDef().getName(), SceneApplication.getApplication().getRenderManager());
-            }
-            dummy.getAdditionalRenderState().set(mat.getAdditionalRenderState());
-
-            //creating a dummy geom and assigning the dummy material to it
-            Geometry dummyGeom = new Geometry("dummyGeom", new Box(1f, 1f, 1f));
-            dummyGeom.setMaterial(dummy);
-
-            //preloading the dummyGeom, this call will compile the shader again
-            SceneApplication.getApplication().getRenderManager().preloadScene(dummyGeom);
-        } catch (RendererException e) {
-            //compilation error, the shader code will be output to the console
-            //the following code will output the error
-            //System.err.println(e.getMessage());
-            //Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.SEVERE, e.getMessage());
-            smartLog("{0}", e.getMessage());
-
-            java.awt.EventQueue.invokeLater(new Runnable() {
-                @Override
-                public void run() {
-                    label.setIcon(Icons.error);
-                }
-            });
-            return null;
-        } catch (NullPointerException npe) {
-            //utterly bad, but for some reason I get random NPE here and can't figure out why so to avoid bigger issues, I just catch it.
-            //the printStackTrace is intended, it will show up in debug mode, but won't be displayed in standzrd mode
-            npe.printStackTrace();
-            return null;
-        } catch (AssetNotFoundException a) {
-            smartLog("Could not fully load Shader: Missing File: {0}", a.getMessage());
-            return null;
-        }
-
-        //Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.INFO, "Material succesfully reloaded");
-        //System.out.println("Material succesfully reloaded");
-        return dummy;
-    }
-
-    public void switchDisplay(DisplayType type) {
-        switch (type) {
-            case Box:
-                currentGeom = box;
-                break;
-            case Sphere:
-                currentGeom = sphere;
-                break;
-            case Quad:
-                currentGeom = quad;
-                break;
-        }
-        showMaterial(currentMaterial);
-    }
-
-    @Override
-    public void sceneOpened(SceneRequest request) {
-    }
-
-    @Override
-    public void sceneClosed(SceneRequest request) {
-    }
-
-    @Override
-    public void previewCreated(PreviewRequest request) {
-        if (request.getRequester() == this) {
-            final ImageIcon icon = new ImageIcon(request.getImage());
-            java.awt.EventQueue.invokeLater(new Runnable() {
-                @Override
-                public void run() {
-                    label.setIcon(icon);
-                }
-            });
-        }
-    }
-
-    public void cleanUp() {
-        SceneApplication.getApplication().removeSceneListener(this);
-        exec.shutdownNow();
-    }
-}
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.gde.materials;
+
+import com.jme3.asset.AssetNotFoundException;
+import com.jme3.asset.MaterialKey;
+import com.jme3.gde.core.assets.ProjectAssetManager;
+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.gde.core.editor.icons.Icons;
+import com.jme3.material.MatParam;
+import com.jme3.material.Material;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RendererException;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Quad;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.util.TangentBinormalGenerator;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+
+/**
+ *
+ * @author Nehon
+ */
+public class MaterialPreviewRenderer implements SceneListener {
+
+    private Geometry sphere;
+    private Geometry box;
+    private Geometry quad;
+    private Geometry currentGeom;
+    private Material currentMaterial;
+    private boolean init = false;
+    final JLabel label;
+    private final ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(5);
+    private boolean previewRequested;
+
+    public enum DisplayType {
+
+        Sphere,
+        Box,
+        Quad
+    }
+
+    public MaterialPreviewRenderer(JLabel label) {
+        this.label = label;
+    }
+
+    private void init() {
+        SceneApplication.getApplication().addSceneListener(this);
+        Sphere sphMesh = new Sphere(64, 64, 2.5f);
+        sphMesh.setTextureMode(Sphere.TextureMode.Polar);
+        sphMesh.updateGeometry(64, 64, 2.5f, false, false);
+        Logger log = Logger.getLogger(TangentBinormalGenerator.class.getName());
+        log.setLevel(Level.SEVERE);
+        TangentBinormalGenerator.generate(sphMesh);
+        sphere = new Geometry("previewSphere", sphMesh);
+        sphere.setLocalRotation(new Quaternion().fromAngleAxis(FastMath.QUARTER_PI, Vector3f.UNIT_X));
+
+        Box boxMesh = new Box(1.75f, 1.75f, 1.75f);
+        TangentBinormalGenerator.generate(boxMesh);
+        box = new Geometry("previewBox", boxMesh);
+        box.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.DEG_TO_RAD * 30, Vector3f.UNIT_X).multLocal(new Quaternion().fromAngleAxis(FastMath.QUARTER_PI, Vector3f.UNIT_Y)));
+
+        Quad quadMesh = new Quad(4.5f, 4.5f);
+        TangentBinormalGenerator.generate(quadMesh);
+        quad = new Geometry("previewQuad", quadMesh);
+        quad.setLocalTranslation(new Vector3f(-2.25f, -2.25f, 0));
+        currentGeom = sphere;
+        init = true;
+    }
+
+    @SuppressWarnings("unchecked")
+    public void showMaterial(final ProjectAssetManager assetManager,final String materialFileName) {
+        if (!init) {
+            init();
+        }
+        exec.execute(new Runnable() {
+
+            @Override
+            public void run() {
+                MaterialKey key = new MaterialKey(assetManager.getRelativeAssetPath(materialFileName));
+                assetManager.deleteFromCache(key);
+                Material mat = assetManager.loadAsset(key);
+                if (mat != null) {
+                    showMaterial(mat);
+                }
+            }
+        });
+        
+
+    }
+
+    public void showMaterial(final Material m) {
+        showMaterial(m, null);
+    }
+
+    public void showMaterial(final Material m, final String techniqueName) {
+        if (!init) {
+            init();
+        }
+        SceneApplication.getApplication().enqueue(new Callable<Material>() {
+
+            @Override
+            public Material call() throws Exception {
+                if (techniqueName != null) {
+                    try {
+                        m.selectTechnique(techniqueName, SceneApplication.getApplication().getRenderManager());
+                    } catch (Exception e) {
+                        //
+                    }
+                }
+                final Material mat = reloadMaterial(m);
+                if (mat != null) {
+                    java.awt.EventQueue.invokeLater(new Runnable() {
+                        @Override
+                        public void run() {
+                            currentMaterial = mat;
+                            currentGeom.setMaterial(mat);
+                            try {
+                                if (currentGeom.getMaterial() != null) {
+                                    PreviewRequest request = new PreviewRequest(MaterialPreviewRenderer.this, currentGeom, label.getWidth(), label.getHeight());
+                                    request.getCameraRequest().setLocation(new Vector3f(0, 0, 7));
+                                    request.getCameraRequest().setLookAt(new Vector3f(0, 0, 0), Vector3f.UNIT_Y);
+                                    SceneApplication.getApplication().createPreview(request);
+                                }
+                            } catch (Exception e) {
+                                java.awt.EventQueue.invokeLater(new Runnable() {
+                                    @Override
+                                    public void run() {
+                                        label.setIcon(Icons.error);
+                                    }
+                                });
+                                smartLog("Error rendering material{0}", e.getMessage());
+                            }
+                        }
+                    });
+
+                }
+                return mat;
+            }
+        });
+    }
+
+    private static int lastErrorHash = 0;
+
+    private void smartLog(String expText, String message) {
+        int hash = message.hashCode();
+        if (hash != lastErrorHash) {
+            Logger.getLogger(MaterialPreviewRenderer.class.getName()).log(Level.SEVERE, expText, message);
+            lastErrorHash = hash;
+        }
+    }
+
+    public Material reloadMaterial(Material mat) {
+        Material dummy;
+        try {
+            ((ProjectAssetManager) mat.getMaterialDef().getAssetManager()).clearCache();
+
+            //creating a dummy mat with the mat def of the mat to reload
+            dummy = new Material(mat.getMaterialDef());
+        
+            for (MatParam matParam : mat.getParams()) {
+                dummy.setParam(matParam.getName(), matParam.getVarType(), matParam.getValue());
+            }
+            if (mat.getActiveTechnique() != null) {
+                dummy.selectTechnique(mat.getActiveTechnique().getDef().getName(), SceneApplication.getApplication().getRenderManager());
+            }
+            dummy.getAdditionalRenderState().set(mat.getAdditionalRenderState());
+
+            //creating a dummy geom and assigning the dummy material to it
+            Geometry dummyGeom = new Geometry("dummyGeom", new Box(1f, 1f, 1f));
+            dummyGeom.setMaterial(dummy);
+
+            //preloading the dummyGeom, this call will compile the shader again
+            SceneApplication.getApplication().getRenderManager().preloadScene(dummyGeom);
+        } catch (RendererException e) {
+            //compilation error, the shader code will be output to the console
+            //the following code will output the error
+            //System.err.println(e.getMessage());
+            //Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.SEVERE, e.getMessage());
+            smartLog("{0}", e.getMessage());
+
+            java.awt.EventQueue.invokeLater(new Runnable() {
+                @Override
+                public void run() {
+                    label.setIcon(Icons.error);
+                }
+            });
+            return null;
+        } catch (NullPointerException npe) {
+            //utterly bad, but for some reason I get random NPE here and can't figure out why so to avoid bigger issues, I just catch it.
+            //the printStackTrace is intended, it will show up in debug mode, but won't be displayed in standzrd mode
+            npe.printStackTrace();
+            return null;
+        } catch (AssetNotFoundException a) {
+            smartLog("Could not fully load Shader: Missing File: {0}", a.getMessage());
+            return null;
+        }
+
+        //Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.INFO, "Material succesfully reloaded");
+        //System.out.println("Material succesfully reloaded");
+        return dummy;
+    }
+
+    public void switchDisplay(DisplayType type) {
+        switch (type) {
+            case Box:
+                currentGeom = box;
+                break;
+            case Sphere:
+                currentGeom = sphere;
+                break;
+            case Quad:
+                currentGeom = quad;
+                break;
+        }
+        showMaterial(currentMaterial);
+    }
+
+    @Override
+    public void sceneOpened(SceneRequest request) {
+    }
+
+    @Override
+    public void sceneClosed(SceneRequest request) {
+    }
+
+    @Override
+    public void previewCreated(PreviewRequest request) {
+        if (request.getRequester() == this) {
+            final ImageIcon icon = new ImageIcon(request.getImage());
+            java.awt.EventQueue.invokeLater(new Runnable() {
+                @Override
+                public void run() {
+                    label.setIcon(icon);
+                }
+            });
+            previewRequested = false;
+        }
+    }
+
+    public void cleanUp() {
+        SceneApplication.getApplication().removeSceneListener(this);
+        exec.shutdownNow();
+    }
+    
+    public boolean isPreviewRequested(){
+        return previewRequested;
+    }
+    
+    public void refreshOnly(){
+        previewRequested = true;
+        SceneApplication.getApplication().enqueue((Callable<Object>) () -> {
+            if (currentGeom.getMaterial() != null) {
+                PreviewRequest request = new PreviewRequest(MaterialPreviewRenderer.this, currentGeom, label.getWidth(), label.getHeight());
+                request.getCameraRequest().setLocation(new Vector3f(0, 0, 7));
+                request.getCameraRequest().setLookAt(new Vector3f(0, 0, 0), Vector3f.UNIT_Y);
+                SceneApplication.getApplication().createPreview(request);
+            }
+            return null;
+        });
+    }
+    
+}