Package com.mystictri.neotextureedit

Source Code of com.mystictri.neotextureedit.TextureGraphEditorPanel

/**
    Copyright (C) 2010  Holger Dammertz

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package com.mystictri.neotextureedit;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.InputMismatchException;
import java.util.Scanner;
import java.util.TooManyListenersException;
import java.util.Vector;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;

import com.mystictri.neotexture.TextureGraph;
import com.mystictri.neotexture.TextureGraph.TextureNodeConnection;
import com.mystictri.neotexture.TextureGraphListener;
import com.mystictri.neotexture.TextureGraphNode;
import com.mystictri.neotexture.TextureGraphNode.ConnectionPoint;

import engine.base.Logger;
import engine.graphics.synthesis.texture.CacheTileManager;
import engine.graphics.synthesis.texture.Channel;
import engine.graphics.synthesis.texture.Channel.ChannelVizType;
import engine.graphics.synthesis.texture.ChannelChangeListener;
import engine.graphics.synthesis.texture.Pattern;

/**
* This is the main texture graph editing panel that is used to create and modify
* a texture generation graph using TextureGraphNode objects.
* @author Holger Dammertz
*
*/
public final class TextureGraphEditorPanel extends JPanel implements MouseListener, MouseMotionListener, MouseWheelListener, ActionListener, ChannelChangeListener, TextureGraphListener {
  private static final long serialVersionUID = 4535161419971720668L;
  int dragStartX = 0;
  int dragStartY = 0;

  // node drawing settings
  private static final Color col_NodeBG = new Color(128, 128, 128);
  private static final Color col_NodeSlowBG = new Color(128+64, 64, 64);
  private static final Color col_NodeBorder = new Color(255, 255, 255);
  private static final Color col_NodeSelected = new Color(255, 255, 0);
  private static final Color col_NodeOutPort = new Color(0, 230, 64);
  private static final Color col_NodeInPort = new Color(230, 64, 0);
  //private static final BasicStroke connectionLineStroke = new BasicStroke(2.0f);
  private static final BasicStroke connectionLineStroke = new BasicStroke(1.5f);
  //private static final Color ms_PatternColor = new Color(0x929AAF);
  //private static final Color ms_SlowColor = new Color(128,16,16);
  private static final Font font = new Font("Sans", Font.PLAIN, 10);
  private static final int helpW = 16;
  private static final int helpH = 16;
  private static final int helpX = TextureGraphNode.width - helpW;
  private static final int helpY = 0;

  boolean nodeDragging = false;
  boolean desktopDragging = false;
  boolean connectionDragging = false;
  PreviewWindow draggedWindow = null;
  TextureGraphNode.ConnectionPoint connectionSource = null;
  Point connectionOrigin;
  Point connectionTarget;

  Point mousePosition = new Point();

  JPopupMenu newChannelPopupMenu;
  JMenuItem newChannelInsertMenuItem;
 
  JPopupMenu selectedChannelPopupMenu;
  JMenuItem addToPresetsChannelMenuItem;
  TextureGraphNode toCopyTextureGraphNode;
  JMenuItem previewChannelMenuItem;
  JMenuItem copyChannelMenuItem;
  JMenuItem replacepasteChannelMenuItem;
  JMenuItem swapInputsChannelMenuItem;
  JMenuItem deleteChannelMenuItem;
  JMenuItem cloneChannelMenuItem;

  JMenuItem openGLDiffuseMenuItem;
  JMenuItem openGLNormalMenuItem;
  JMenuItem openGLSpecularMenuItem;
  JMenuItem openGLHeightmapMenuItem;
 
  JMenu replaceChannelMenu;

  ChannelParameterEditorPanel paramEditorPanel;
 
 
  TextureGraph graph;
 
 
  /**
   * This interface is used by the TextureGraphEditorPanel to notify about changes in the
   * graph when edit operations are performed.
   * @author Holger Dammertz
   *
   */
  public static interface EditChangeListener {
    /** Called on each edit operation that changes the graph or the graph layout */
    public void graphWasEdited();
  }
 
  final Vector<EditChangeListener> editChangeListener = new Vector<EditChangeListener>();
 
  /**
   * Used in the draw method to generate a preview image that is cached
   * @author Holger Dammertz
   *
   */
  class NodePreviewImage implements ChannelChangeListener {
    BufferedImage previewImage;
    TextureGraphNode node;
   
    public NodePreviewImage(TextureGraphNode node) {
      this.node = node;
      node.getChannel().addChannelChangeListener(this);
      updatePreviewImage();
    }
   
    public void updatePreviewImage() {
      if ((node.getChannel() != null) && (node.getChannel().chechkInputChannels())) {
        if (previewImage == null)
          previewImage = ChannelUtils.createAndComputeImage(node.getChannel(), 64, 64, null, 0);
        else
          ChannelUtils.computeImage(node.getChannel(), previewImage, null, 0);
      } else {
        previewImage = null;
      }
      repaint();
    }
   
    public void channelChanged(Channel source) {
      updatePreviewImage();
    }
  }
 
 
  int desktopX, desktopY;
 
  public class TextureGraphDropTarget extends DropTargetAdapter {

    public void drop(DropTargetDropEvent e) {
      TextureGraphNode n = new TextureGraphNode(Channel.cloneChannel(TextureEditor.INSTANCE.dragndropChannel));
      addTextureNode(n, e.getLocation().x - desktopX, e.getLocation().y - desktopY);
      repaint();
    }
  }
 
 
 
 
 
  public TextureGraphEditorPanel() {
    graph = new TextureGraph();
    graph.graphListener = this;
   
    setBackground(Color.darkGray);
    setLayout(null);

    addMouseListener(this);
    addMouseWheelListener(this);
    addMouseMotionListener(this);

    createPopupMenus();
    paramEditorPanel = new ChannelParameterEditorPanel();
   
    DropTarget t = new DropTarget();
    try {
      t.addDropTargetListener(new TextureGraphDropTarget());
    } catch (TooManyListenersException e) {
      e.printStackTrace();
    }
    setDropTarget(t);
   
    setPreferredSize(new Dimension(512, 512));
   
   
   
  }

 

  public ChannelParameterEditorPanel getParameterEditorPanel() {
    return paramEditorPanel;
  }
 

  private void createPopupMenus() {
    newChannelPopupMenu = new JPopupMenu("Create Channel");
    replaceChannelMenu = new JMenu("Replace Channel");

    newChannelInsertMenuItem = createChannelPopupMenuItem(newChannelPopupMenu, "Paste Node");
    newChannelPopupMenu.addSeparator();
    for (Class<?> c : TextureEditor.INSTANCE.allPatterns) {
      newChannelPopupMenu.add(createMenuItem_CreateFilter(c.getSimpleName(), c, true, false));
      replaceChannelMenu.add(createMenuItem_CreateFilter(c.getSimpleName(), c, true, true));
    }
    newChannelPopupMenu.addSeparator();
    replaceChannelMenu.addSeparator();
    for (Class<?> c : TextureEditor.INSTANCE.allChannels) {
      newChannelPopupMenu.add(createMenuItem_CreateFilter(c.getSimpleName(), c, false, false));
      replaceChannelMenu.add(createMenuItem_CreateFilter(c.getSimpleName(), c, false, true));
    }

    selectedChannelPopupMenu = new JPopupMenu();
    cloneChannelMenuItem = createChannelPopupMenuItem(selectedChannelPopupMenu, "Clone");
    copyChannelMenuItem = createChannelPopupMenuItem(selectedChannelPopupMenu, "Copy");
    replacepasteChannelMenuItem = createChannelPopupMenuItem(selectedChannelPopupMenu, "Paste Replace");
   
    selectedChannelPopupMenu.addSeparator();
    previewChannelMenuItem = createChannelPopupMenuItem(selectedChannelPopupMenu, "Preview Node");
   
    JMenu exportImage = new JMenu("Export Image"); // export sub menu
    selectedChannelPopupMenu.add(exportImage);
    createChannelPopupSubMenuItem(exportImage, "256x256");
    createChannelPopupSubMenuItem(exportImage, "512x512");
    createChannelPopupSubMenuItem(exportImage, "1024x1024");
    createChannelPopupSubMenuItem(exportImage, "specify").setActionCommand("arbitraryResolutionExport");

    addToPresetsChannelMenuItem = createChannelPopupMenuItem(selectedChannelPopupMenu, "Add to Presets");
    swapInputsChannelMenuItem = createChannelPopupMenuItem(selectedChannelPopupMenu, "Swap First 2 Inputs");
    selectedChannelPopupMenu.add(replaceChannelMenu);
    selectedChannelPopupMenu.addSeparator();

    if (TextureEditor.GL_ENABLED) {
      JMenu opengl = new JMenu("OpenGL"); // export sub menu
      selectedChannelPopupMenu.add(opengl);
      openGLDiffuseMenuItem = createChannelPopupSubMenuItem(opengl, "Set as Diffuse");
      openGLNormalMenuItem = createChannelPopupSubMenuItem(opengl, "Set as Normal");
      openGLSpecularMenuItem = createChannelPopupSubMenuItem(opengl, "Set as Specular");
      openGLHeightmapMenuItem = createChannelPopupSubMenuItem(opengl, "Set as Height");
    }

    deleteChannelMenuItem = createChannelPopupMenuItem(selectedChannelPopupMenu, "Delete");
   
  }

  private JMenuItem createChannelPopupSubMenuItem(JMenu menu, String name) {
    JMenuItem ret = new JMenuItem(name);
    ret.addActionListener(this);
    menu.add(ret);
    return ret;
  }

  private JMenuItem createChannelPopupMenuItem(JPopupMenu menu, String name) {
    JMenuItem ret = new JMenuItem(name);
    ret.addActionListener(this);
    menu.add(ret);
    return ret;
  }

  class CreateMenuItem extends JMenuItem {
    private static final long serialVersionUID = 3053710502290069301L;
    public Class<?> classType;
    public boolean isAReplaceCall; // this is set to true in events from the replace channel menu

    public CreateMenuItem(String name, boolean isReplace) {
      super(name);
      isAReplaceCall = isReplace;
    }

    public CreateMenuItem(String name, ImageIcon icon, boolean isReplace) {
      super(name, icon);
      isAReplaceCall = isReplace;
    }
  }

  private CreateMenuItem createMenuItem_CreateFilter(String name, Class<?> c, boolean genIcon, boolean replace) {
    CreateMenuItem ret;
    if (genIcon) {
      Channel chan = null;
      try {
        chan = (Channel) c.newInstance();
      } catch (InstantiationException e) {
        e.printStackTrace();
      } catch (IllegalAccessException e) {
        e.printStackTrace();
      }
      ret = new CreateMenuItem(name, new ImageIcon(ChannelUtils.createAndComputeImage(chan, 16, 16, null, 0)), replace);
    } else
      ret = new CreateMenuItem(name, replace);
    ret.classType = c;
    ret.addActionListener(this);
    return ret;
  }
 
 
 
 
  private void askFileAndExportTexture(int resX, int resY) {
    TextureEditor.INSTANCE.m_TextureFileChooser_SaveLoadImage.setDialogTitle("Export Texture to " + resX + "x" + resY + " Image...");
    if (TextureEditor.INSTANCE.m_TextureFileChooser_SaveLoadImage.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
      String name = TextureEditor.INSTANCE.m_TextureFileChooser_SaveLoadImage.getSelectedFile().getAbsolutePath();
      if (!name.endsWith(".png"))
        name += ".png";
      boolean useCache = ChannelUtils.useCache;
      try {
        ChannelUtils.useCache = false;
        ImageIO.write(ChannelUtils.createAndComputeImage(graph.selectedNodes.lastElement().getChannel(), resX, resY, TextureEditor.INSTANCE.m_ProgressDialog, 3), "png", new File(name));
        Logger.log(this, "Saved image to " + name + ".");
      } catch (IOException exc) {
        exc.printStackTrace();
        Logger.logError(this, "IO Exception while exporting image: " + exc);
      }
      ChannelUtils.useCache = useCache;
    }
  }
 
 
  private void action_DeleteSelectedNodes() {
    graph.deleteSelection();
    repaint();
  }

  /**
   * This is the main action method for TextureGraphEditorPanel. Here the actions from the popup-menus
   * are processed.
   */
  @Override
  public void actionPerformed(ActionEvent e) {
    System.out.println(e);
   
    if (e.getSource().getClass() == CreateMenuItem.class) { // this was one menu item from the create new channel menu
      CreateMenuItem mi = (CreateMenuItem)e.getSource();

      try {
        if (!mi.isAReplaceCall) { // insert a new Node
          Channel chan = (Channel) mi.classType.newInstance();
          addTextureNode(new TextureGraphNode(chan), mousePosition.x - desktopX, mousePosition.y - desktopY);
          repaint();
        } else { // try to replace an existing node as good as possible
          TextureGraphNode node = graph.selectedNodes.get(0);
          if (node != null) {
            TextureGraphNode newNode = new TextureGraphNode((Channel) mi.classType.newInstance());
            replaceTextureNode(node, newNode);
            repaint();
          } else {
            Logger.logWarning(this, "No node selected for replace.");
          }
        }
      } catch (InstantiationException e1) {
        e1.printStackTrace();
      } catch (IllegalAccessException e1) {
        e1.printStackTrace();
      }
    } else if (e.getSource() == newChannelInsertMenuItem) {
      if (toCopyTextureGraphNode == null) {
        Logger.logError(this, "No node copied to insert.");
      } else {
        addTextureNode(toCopyTextureGraphNode.cloneThisNode(), mousePosition.x - desktopX, mousePosition.y - desktopY);
        repaint();
      }
    } else if (e.getSource() == copyChannelMenuItem) {
      if (graph.selectedNodes.size() > 0) {
        toCopyTextureGraphNode = graph.selectedNodes.get(0).cloneThisNode();
      } else {
        Logger.logError(this, "no selection in copyChannel popup menu.");
      }
    } else if (e.getSource() == replacepasteChannelMenuItem) {
      if (toCopyTextureGraphNode == null) {
        Logger.logError(this, "No node copied to replace paste.");
      } else if (graph.selectedNodes.size() > 0) {
        replaceTextureNode(graph.selectedNodes.get(0), toCopyTextureGraphNode.cloneThisNode());
        repaint();
      } else {
        Logger.logError(this, "no selection in insert-replaceChannel popup menu.");
      }
    } else if (e.getSource() == previewChannelMenuItem) {
      if (graph.selectedNodes.size() > 0) {
        addPreviewWindow(graph.selectedNodes.get(0));
        repaint();
      }
    } else if (e.getSource() == cloneChannelMenuItem) { // --------------------------------------------------------
      if (graph.selectedNodes.size() > 0) {
        TextureGraphNode orig = graph.selectedNodes.get(0);
        TextureGraphNode n = new TextureGraphNode(Channel.cloneChannel(graph.selectedNodes.get(0).getChannel()));
        addTextureNode(n, orig.getX()+32, orig.getY()+32);
        repaint();
      } else {
        Logger.logError(this, "no selection in cloneChannel popup menu.");
      }
    } else if (e.getSource() == swapInputsChannelMenuItem) { // --------------------------------------------------------
      TextureGraphNode node = graph.selectedNodes.get(0);
      if (node != null) {
        if (node.getChannel().getNumInputChannels() < 2) return;
        ConnectionPoint p0 = node.getInputConnectionPointByChannelIndex(0);
        ConnectionPoint p1 = node.getInputConnectionPointByChannelIndex(1);
        TextureNodeConnection c0 = graph.getConnectionAtInputPoint(p0);
        TextureNodeConnection c1 = graph.getConnectionAtInputPoint(p1);
        graph.removeConnection(c0);
        graph.removeConnection(c1);
        if (c0 != null && c1 != null) {
          ConnectionPoint temp = c0.target;
          c0.target = c1.target;
          c1.target = temp;
          graph.addConnection(c0);
          graph.addConnection(c1);
        } else if (c1 != null) {
          c1.target = p0;
          graph.addConnection(c1);
        } else if (c0 != null) {
          c0.target = p1;
          graph.addConnection(c0);
        } else {
          return;
        }
        repaint();
      }
    } else if (e.getSource() == addToPresetsChannelMenuItem) { // --------------------------------------------------------
      if (graph.selectedNodes.size() > 0) {
        if (graph.selectedNodes.get(0).getChannel() instanceof Pattern)
          TextureEditor.INSTANCE.m_PatternSelector.addPatternPreset((Pattern)Channel.cloneChannel((Pattern)graph.selectedNodes.get(0).getChannel()));
        else Logger.logError(this, "Invalid action 'Add to Presets': selected node is not a pattern");
      } else Logger.logError(this, "Invalid action 'Add To Presets': no selected nodes exists.");
    } else if (e.getSource() == deleteChannelMenuItem) { // --------------------------------------------------------
      action_DeleteSelectedNodes();
    } else if (e.getActionCommand().equals("arbitraryResolutionExport")) {
      String resolution = JOptionPane.showInputDialog(this, "Specify your desried resolution (for example 1024x1024)", "What Resolution?", JOptionPane.QUESTION_MESSAGE);
      if (resolution != null && resolution.matches("\\d+x\\d+")) {
        int resX = Integer.parseInt(resolution.substring(0, resolution.indexOf('x')));
        int resY = Integer.parseInt(resolution.substring(resolution.indexOf('x') + 1, resolution.length()));
        askFileAndExportTexture(resX, resY);
      }
    } else if (e.getActionCommand().matches("\\d+x\\d+")) {
      String s = e.getActionCommand();
      int resX = Integer.parseInt(s.substring(0, s.indexOf('x')));
      int resY = Integer.parseInt(s.substring(s.indexOf('x') + 1, s.length()));
      askFileAndExportTexture(resX, resY);
    }
    else {
      // ----------------------- OpenGL ---------------------------
      if (TextureEditor.GL_ENABLED) {
        TextureGraphNode n = graph.selectedNodes.get(0);
        if (n.getChannel().chechkInputChannels()) {
          if (e.getSource() == openGLDiffuseMenuItem) {
            TextureEditor.INSTANCE.m_OpenGLPreviewPanel.setDiffuseTextureNode(n);
            repaint();
          } else if (e.getSource() == openGLNormalMenuItem) {
            TextureEditor.INSTANCE.m_OpenGLPreviewPanel.setNormalTextureNode(n);
            repaint();
          } else if (e.getSource() == openGLSpecularMenuItem) {
            TextureEditor.INSTANCE.m_OpenGLPreviewPanel.setSpecWeightTextureNode(n);
            repaint();
          } else if (e.getSource() == openGLHeightmapMenuItem) {
            TextureEditor.INSTANCE.m_OpenGLPreviewPanel.setHeightmapTextureNode(n);
            repaint();
          }
        } else Logger.logWarning(this, "Incomplete channel for preview.");
      // --------------------------------------------------------
      }
    }

  }
 
 
 
  void addTextureNode(TextureGraphNode n, int x, int y) {
    graph.addNode(n, x, y);
    setSelectedNode(n);
  }
 
  void replaceTextureNode(TextureGraphNode oldNode, TextureGraphNode newNode) {
    graph.replaceNode(oldNode, newNode);
    setSelectedNode(newNode);
  }

  public void setSelectedNode(TextureGraphNode node) {
    paramEditorPanel.setTextureNode(node);
    graph.setSelectedNode(node);
  }

  public void addSelectedNode(TextureGraphNode node) {
    graph.addOrRemoveNodeToSelection(node);
  }
 
  public boolean isNodeInSelection(TextureGraphNode node) {
    return graph.selectedNodes.contains(node);
  }
 
  public void deleteFullGraph() {
    paramEditorPanel.setTextureNode(null);
    removeAllPreviewWindows();
    graph.deleteFullGraph();
    CacheTileManager.clearCache();
    repaint();
   
  }
 
 
  public void save(String filename) {
    Logger.log(this, "Saving TextureGraph to " + filename);
    try {
      BufferedWriter w = new BufferedWriter(new FileWriter(filename));
      graph.save(w);
     
      // now the openGL settings
      if (TextureEditor.GL_ENABLED) TextureEditor.INSTANCE.m_OpenGLPreviewPanel.save(w);
      w.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
 
  public boolean load(String filename, boolean eraseOld) {
    try {
      Scanner s = new Scanner(new BufferedReader(new FileReader(filename)));
      if (eraseOld) deleteFullGraph();
     
      graph.load(s);
      if (TextureEditor.GL_ENABLED) TextureEditor.INSTANCE.m_OpenGLPreviewPanel.load(s);
      repaint();
     
      return true;
    } catch (FileNotFoundException e) {
      Logger.logError(this, "Could not load " + filename);
      return false;
    } catch (InputMismatchException ime) {
      ime.printStackTrace();
      Logger.logError(this, "Could not load " + filename);
      return false;
    }
   
  }

 
  @Override
  public void nodeDeleted(TextureGraphNode node) {
    if (TextureEditor.GL_ENABLED) TextureEditor.INSTANCE.m_OpenGLPreviewPanel.notifyTextureNodeRemoved(node);
    if (paramEditorPanel.getActiveTextureNode() == node)
      paramEditorPanel.setTextureNode(null);
   
    removePreviewWindowByTextureNode(node);
   
    repaint();
  }
 
  // utility method to draw a conneciton line.
  public static void drawConnectionLine(Graphics2D g, int x0, int y0, int x1, int y1) {
    final int ofs = 12;
   
    g.drawLine(x0, y0, x0, y0 - ofs);
    g.drawLine(x0, y0 - ofs, x1, y1 + ofs);
    g.drawLine(x1, y1 + ofs, x1, y1);
  }
 
 
  /*
  public void setPreviewNode(TextureGraphNode n) {
    if (n == previewNode) return;
    if (previewNode != null) {
      previewNode.getChannel().removeChannelChangeListener(this);
    }
    if (n == null) {
      previewNode = null;
    } else {
      previewNode = n;
      previewNode.getChannel().addChannelChangeListener(this);
    }
    updatePreview();
  }
 
  void updatePreview() {
    if (previewNode != null) {
      if (previewNode.getChannel().chechkInputChannels()) {
        ChannelUtils.computeImage(previewNode.getChannel(), previewNodeImage, null, 0);
      } else {
        Graphics g = previewNodeImage.getGraphics();
        g.fillRect(0, 0, previewNodeImage.getWidth(), previewNodeImage.getHeight());
      }
    }
    repaint();
  }
  */
 
 
 
 
 
  public void drawConnectionPoint(Graphics2D g, int ox, int oy, ConnectionPoint p) {
    if (p.channelIndex == -1) { // input
      g.setColor(col_NodeInPort);
      g.fillRect(ox+p.getX(), oy+p.getY(), p.width, p.height);
    } else { // output
      g.setColor(col_NodeOutPort);
      g.fillRect(ox+p.getX(), oy+p.getY(), p.width, p.height);
    }
  }
 
  public void drawNode(Graphics2D g, TextureGraphNode node) {
    final int roundRad = 16;
    if (node.userData == null) node.userData = new NodePreviewImage(node);

    if (node.getChannel().vizType == ChannelVizType.SLOW)
      g.setColor(col_NodeSlowBG);
    else
      g.setColor(col_NodeBG);
    int x = node.getX() + desktopX;
    int y = node.getY() + desktopY;
    int w = node.getWidth();
    int h = node.getHeight();
   
    g.fillRoundRect(x, y, w, h, roundRad, roundRad);
   
    //if (threadIsRecomputing) return;
   
    if (!node.isFolded()) {
      g.drawImage(((NodePreviewImage)node.userData).previewImage, x + 4, y+12+12, this);
    }
   
    g.setFont(font);
   
    g.setColor(Color.white);
    g.drawString(node.getChannel().getName(), x+2, y+12+8);

    g.setColor(col_NodeBorder);
   
    g.drawRoundRect(x, y, w, h, roundRad, roundRad);
   
   
    for (ConnectionPoint p : node.getAllConnectionPointsVector()) {
      drawConnectionPoint(g, x, y, p);
    }
   
    g.setColor(Color.yellow);
    g.drawString("?", x+helpX+6, y+helpY+12);

    g.setColor(Color.yellow);
    g.drawString("-", x+helpX-8, y+helpY+12);
   
    if (node.getChannel().isMarkedForExport()) {
      g.drawString("E", x+4, y+helpY+10);
      /*int h = g.getFontMetrics().getHeight() + 2;
      int x = getX() + 2;
      int y = getY() + h;
     
      g.setColor(new Color(0x00505084));
      g.fillRect(x, y-h, 12, h);
      g.setColor(Color.white);
     
      g.drawString("E", x+1, y-2);*/
    }
  }
 
 
  Color gridColor = new Color(0xFF606060);
 
  BufferedImage canvas;
 
  float zoom = 1.0f;
 
  public void paint(Graphics gr) {
//    super.paint(gr);
   
    if (canvas == null || (int)(getWidth()*zoom) != canvas.getWidth() || (int)(getHeight()*zoom) != canvas.getHeight()) {
      canvas = new BufferedImage((int)(getWidth()*zoom), (int)(getHeight()*zoom), BufferedImage.TYPE_INT_RGB);
    }
     
    Graphics2D g = (Graphics2D)canvas.getGraphics();
   
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

    g.setBackground(Color.DARK_GRAY);
    g.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
   
    int w = canvas.getWidth();
    int h = canvas.getHeight();
   
    g.setColor(gridColor);
    for (int y = desktopY % 16; y < h; y+=16) {
      g.drawLine(0, y, w, y);
    }
    for (int x = desktopX % 16; x < w; x+=16) {
      g.drawLine(x, 0, x, h);
    }
   
    // draw the connection lines
    g.setColor(Color.white);
    g.setStroke(connectionLineStroke);
    for (TextureNodeConnection c : graph.allConnections) {
      drawConnectionLine(g, desktopX + c.target.getWorldSpaceX(), desktopY + c.target.getWorldSpaceY(), desktopX + c.source.getWorldSpaceX(), desktopY + c.source.getWorldSpaceY());
    }

    g.setColor(col_NodeSelected);
    for (TextureGraphNode n : graph.selectedNodes) {
      //Rectangle r = new n.getBounds();
      g.fillRoundRect(desktopX + n.getX() - 3, desktopY + n.getY() - 3, n.getWidth() + 6, n.getHeight() + 6, 20, 20);
    }
   
    g.setColor(Color.blue);
    for (TextureGraphNode n : graph.allNodes) {
      //Rectangle r = new n.getBounds();
      //g.drawRect(n.getX(), n.getY(), n.width, n.height);
      drawNode(g, n);
    }

   
    if (TextureEditor.GL_ENABLED) {
      TextureEditor.INSTANCE.m_OpenGLPreviewPanel.drawTokens(g, desktopX, desktopY);
    }
   
    if (connectionDragging) {
      g.setColor(Color.red);
      drawConnectionLine(g, connectionTarget.x, connectionTarget.y, desktopX + connectionOrigin.x, desktopY + connectionOrigin.y);
    }
   
   
    for (int i = previewWindows.size() - 1; i >= 0; i--) {
      previewWindows.get(i).draw(g);
    }
   
   
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);

    if (getWidth() != canvas.getWidth() || getHeight() != canvas.getHeight()) {
      gr.drawImage(canvas.getScaledInstance(getWidth(), getHeight(), Image.SCALE_SMOOTH), 0, 0, null);
      //gr.drawImage(canvas.getScaledInstance(getWidth(), getHeight(), Image.SCALE_FAST), 0, 0, null);
    } else {
      gr.drawImage(canvas, 0, 0, null);
    }
   
   
  }
 
 
 
 
 
  public void moveDesktop(int dx, int dy) {
    desktopX += dx;
    desktopY += dy;
  }
 
  /**
   *  computes the bounding box of all positions and centers it computes the bounding box of all positions and centers it
   */
  public void centerDesktop() {
    int minX = Integer.MAX_VALUE;
    int minY = Integer.MAX_VALUE;
    int maxX = Integer.MIN_VALUE;
    int maxY = Integer.MIN_VALUE;
    for (TextureGraphNode n : graph.allNodes) {
      minX = Math.min(minX, n.getX());
      minY = Math.min(minY, n.getY());
      maxX = Math.max(maxX, n.getX() + n.getWidth());
      maxY = Math.max(maxY, n.getY() + n.getHeight());
    }
    int dx = getWidth()/2 - (minX+maxX)/2;
    int dy = getHeight()/2 - (minY+maxY)/2;
    desktopX = dx;
    desktopY = dy;
    repaint();
  }

 
  void showSelectedChannelPopupMenu(TextureGraphNode node, int x, int y) {
    if (node.getChannel() instanceof Pattern)
      addToPresetsChannelMenuItem.setEnabled(true);
    else addToPresetsChannelMenuItem.setEnabled(false);
   
    replacepasteChannelMenuItem.setEnabled(toCopyTextureGraphNode != null);
    selectedChannelPopupMenu.show(this, x, y); //!!TODO: refactoring artifact
  }
 
  void showNewChannelPopupMenu(Component c, int x, int y) {
    newChannelInsertMenuItem.setEnabled(toCopyTextureGraphNode != null);
    newChannelPopupMenu.show(c, x, y);
  }
 
 
  @Override
  public void mouseDragged(MouseEvent e) {
    if (desktopDragging) {
      int dx = (int)((e.getXOnScreen()*zoom) - dragStartX);
      int dy = (int)((e.getYOnScreen()*zoom) - dragStartY);
      moveDesktop(dx, dy);
      repaint();
    } else if (nodeDragging) {
      for (TextureGraphNode node : graph.selectedNodes) {
        node.movePosition((int)(e.getXOnScreen()*zoom) - dragStartX, (int)(e.getYOnScreen()*zoom) - dragStartY);
      }
      repaint();
    } else if (connectionDragging) {
      //connectionTarget = e.getPoint();
      connectionTarget.x = (int)(e.getX()*zoom);
      connectionTarget.y = (int)(e.getY()*zoom);
      repaint();
    } else if (draggedWindow != null) {
      int dx = (int)((e.getXOnScreen()) - dragStartX/zoom);
      int dy = (int)((e.getYOnScreen()) - dragStartY/zoom);
      draggedWindow.move(dx, dy);
      repaint();
    }
   
    dragStartX = (int)(e.getXOnScreen()*zoom);
    dragStartY = (int)(e.getYOnScreen()*zoom);
  }
 

  @Override
  public void mouseMoved(MouseEvent e) {
  }

  @Override
  public void mouseClicked(MouseEvent arg0) {
  }

  @Override
  public void mouseEntered(MouseEvent arg0) {
  }

  @Override
  public void mouseExited(MouseEvent arg0) {
  }

 
  // 1: drag component
  // 2: output node clicked
  // 3: Help button clicked
  // < 0: input node clicked; index = -val - 1
  public int getActionTypeForMouseClick(int x, int y, TextureGraphNode n) {
    // check if we clicked in the output node
    if (n.getOutputConnectionPoint().inside(x, y)) {
      return 2;
    }
    // now check if we clicked into an input node
    for (ConnectionPoint cp : n.getAllConnectionPointsVector()) {
      if (cp.inside(x, y))
        return -cp.channelIndex - 1;
    }
   
    if ((x >= helpX+n.getX()) && (x <= (helpX+n.getX() + helpW)) && (y >= helpY+n.getY()) && (y <= (helpY+n.getY() + helpH))) {
      JOptionPane.showMessageDialog(this, n.getChannel().getHelpText(), n.getChannel().getName() + " Help", JOptionPane.PLAIN_MESSAGE);
      return 3;
    }

    if ((x >= helpX+n.getX() - 12) && (x <= (helpX+n.getX() + helpW - 12)) && (y >= helpY+n.getY()) && (y <= (helpY+n.getY() + helpH))) {
      n.setFolded(!n.isFolded());
      return 4;
    }

    return 1;
  }

  @Override
  public void mousePressed(MouseEvent e) {
    mousePosition.x = (int)(e.getX()*zoom);
    mousePosition.y = (int)(e.getY()*zoom);
   
    dragStartX = (int)(e.getXOnScreen()*zoom);
    dragStartY = (int)(e.getYOnScreen()*zoom);
    int wsX = (int)((e.getX())*zoom) - desktopX;
    int wsY = (int)((e.getY())*zoom) - desktopY;
   
    if (e.getButton() == 2 || (e.isControlDown())) { // Desktop Dragging
      desktopDragging = true;
    } else if ((draggedWindow = getPreviewWindowAtPosition(mousePosition.x, mousePosition.y)) != null) {
      previewWindows.setElementAt(previewWindows.firstElement(), previewWindows.indexOf(draggedWindow));
      previewWindows.setElementAt(draggedWindow, 0);
      if (draggedWindow.doClick(mousePosition.x, mousePosition.y)) {
        draggedWindow = null;
      }
    } else {
      TextureGraphNode node = graph.getNodeAtPosition(wsX, wsY);
      if (node != null) {
        if (e.getButton() == 3) { // Popup menu for a TextureGraphNode
          if (!isNodeInSelection(node)) setSelectedNode(node);
          showSelectedChannelPopupMenu(node, e.getX(), e.getY());
        } else { // if it was not a popup we look if we clicked on a connection point or on the rest of the node
          int actionType = getActionTypeForMouseClick(wsX, wsY, node);
          if (e.isShiftDown()) { // add to selection of nodes
            addSelectedNode(node);
          } else if (actionType == 1) { // want to drag the position of the node
            if (!isNodeInSelection(node)) setSelectedNode(node);
            nodeDragging = true;
          }
          else if (actionType == 2) { // dragging from the output node of a channel
            connectionDragging = true;
            connectionSource = node.getOutputConnectionPoint();
            connectionOrigin = new Point(connectionSource.getWorldSpaceX(), connectionSource.getWorldSpaceY());
            connectionTarget = e.getPoint();
          }
          else if (actionType < 0) { // dragging an existing connection of an input away
            int index = -actionType - 1;
            TextureGraphNode.ConnectionPoint inputPoint = node.getInputConnectionPointByChannelIndex(index);
            TextureNodeConnection connection = graph.getConnectionAtInputPoint(inputPoint);
            if (connection != null) {
              graph.removeConnection(connection);
              connectionDragging = true;
              connectionSource = connection.source;
              connectionOrigin = new Point(connectionSource.getWorldSpaceX(), connectionSource.getWorldSpaceY());
              // pat.updatePreviewImage();
              connectionTarget = e.getPoint();
              //connectionTarget.x += node.getX();
              //connectionTarget.y += node.getY();
            }
          }
        }
      } else if (e.getButton() == 3) {
        showNewChannelPopupMenu(e.getComponent(), e.getX(), e.getY());
      } else {
        setSelectedNode(null);
      }
    }
   

    repaint();
  }
 
 
  @Override
  public void mouseReleased(MouseEvent e) {
    mousePosition.x = (int)(e.getX()*zoom);
    mousePosition.y = (int)(e.getY()*zoom);
   
    int wsX = (int)((mousePosition.x)*zoom) - desktopX;
    int wsY = (int)((mousePosition.y)*zoom) - desktopY;
   
    //!!TODO: this notifies already on selecting a node; it should fire only when the node was actually moved
    if (nodeDragging || connectionDraggingnotifyEditChangeListener();
   
    if (connectionDragging) {
      TextureGraphNode targetPat = graph.getNodeAtPosition(wsX, wsY);
      if (targetPat != null) {
        int actionType = getActionTypeForMouseClick(wsX, wsY, targetPat);
        if (actionType < 0) { // we dragged onto an input node
          int index = -actionType - 1;
         
         
          if (e.isControlDown()) { // connect all inputs
            for (int i = 0; i < targetPat.getChannel().getNumInputChannels(); i++) {
              TextureGraphNode.ConnectionPoint ip = targetPat.getInputConnectionPointByChannelIndex(i);
              graph.addConnection(new TextureNodeConnection(connectionSource, ip));
            }
          } else { // normal single connection
            TextureGraphNode.ConnectionPoint inputPoint = targetPat.getInputConnectionPointByChannelIndex(index);
            graph.addConnection(new TextureNodeConnection(connectionSource, inputPoint));
          }
        }
      }     
    }

    nodeDragging = false;
    connectionDragging = false;
    desktopDragging = false;
    draggedWindow = null;
   
    repaint();
  }
 
 
  public void addEditChangeListener(EditChangeListener listener) {
    editChangeListener.add(listener);
  }
 
  public boolean removeEditChangeListener(EditChangeListener listener) {
    return editChangeListener.remove(listener);
  }

  //!!TODO: this is not yet correctly called when the parameter of a channel changes
  protected void notifyEditChangeListener() {
    //!!TODO: make the undo-stack here also
   
    for (EditChangeListener l : editChangeListener) l.graphWasEdited();
  }

  @Override
  public void channelChanged(Channel source) {
    notifyEditChangeListener();
  }



  @Override
  public void mouseWheelMoved(MouseWheelEvent e) {
    // zoom currently disabled
//    if (e.getWheelRotation() < 0) {
//      if (zoom > 0.5f) {
//        //float x = e.getXOnScreen()*zoom;
//        //float y = e.getXOnScreen()*zoom;
//
//        zoom /= 2.0f;
//        repaint();
//      }
//    } else if (e.getWheelRotation() > 0) {
//      if (zoom < 4.0f) {
//        zoom *= 2.0f;
//        repaint();
//      }
//    }
  }
 
  Vector<PreviewWindow> previewWindows = new Vector<PreviewWindow>();
 
  /** Checks if a PreviewWindow exists for the given TextureGraphNode and
   * removes it.
   * @param node
   */
  void removePreviewWindowByTextureNode(TextureGraphNode node) {
    for (int i = 0; i < previewWindows.size(); i++) {
      PreviewWindow w = previewWindows.get(i);
      if (w.previewNode == node) {
        removePreviewWindow(w);
        i--;
      }
    }
  }
 
  void removeAllPreviewWindows() {
    for (int i = previewWindows.size()-1; i >= 0; i--) {
      removePreviewWindow(previewWindows.get(i));
    }
  }
 
  void removePreviewWindow(PreviewWindow w) {
    w.previewNode.getChannel().removeChannelChangeListener(w);
    previewWindows.remove(w);
  }
 
 
  void addPreviewWindow(TextureGraphNode node) {
    PreviewWindow w = new PreviewWindow(node);
   
    previewWindows.add(w);
  }
 
  PreviewWindow getPreviewWindowAtPosition(int x, int y) {
    for (PreviewWindow w : previewWindows) {
      if (w.isInside(x, y)) return w;
    }
    return null;
  }

  class PreviewWindow implements ChannelChangeListener {
    int posX;
    int posY;
    final BufferedImage previewNodeImage;
    BufferedImage tempImage;
    final TextureGraphNode previewNode;
   
    float zoom = 1.0f;
   

    //ad-hoc solution for button areas
    class MiniButton {
      int px, py;
      int w, h;
      String t;
     
      public MiniButton(int posX, int posY, int width, int height, String type) {
        px = posX;
        py = posY;
        w = width;
        h = height;
        t = type;
      }
     
      public void draw(Graphics2D g, int ox, int oy) {
        g.drawString(t, px+ox+4, py+oy+h-4);
      }
     
      boolean inside(int x, int y) {
        return (x >= px && y >= py && x <= (px+w) && y <= (py+h));
      }
    }
   
    Vector<MiniButton> miniButtons = new Vector<MiniButton>();
   
   
    public PreviewWindow(TextureGraphNode node) {
      previewNode = node;
      node.getChannel().addChannelChangeListener(this);
      previewNodeImage = new BufferedImage(256, 256, BufferedImage.TYPE_INT_RGB);
      updatePreviewImage();
     
      miniButtons.add(new MiniButton(256-16,0,16,16, "X"));

      miniButtons.add(new MiniButton(0,0,16,16, "+"));
      miniButtons.add(new MiniButton(16,0,16,16, "-"));
    }
   
   
    public void move(int dx, int dy) {
      posX += dx;
      posY += dy;
     
      if (posX < -128) posX = -128;
      if (posY < -128) posY = -128;
     
      if (posX > getWidth()-128) posX = getWidth()-128;
      if (posY > getHeight()-128) posY = getHeight()-128;
    }
   
    public void draw(Graphics2D g) {
      g.drawImage(previewNodeImage, posX, posY+16, null);
     
      g.setColor(Color.black);
      g.fillRect(posX, posY, previewNodeImage.getWidth(), 16);
      g.setColor(Color.white);
     
      g.drawRect(posX + -1, posY + -1, 258, 256+16+2);

      for (MiniButton b : miniButtons) {
        b.draw(g, posX, posY);
      }
     
      g.drawString(String.format("Zoom: %.3f", zoom), posX + 48, posY + 12);
     
//      if (previewNode != null) {
//        g.setColor(Color.blue);
//        g.drawLine(previewNode.getX() + desktopX, previewNode.getY() + desktopY, previewNodeImage.getWidth()/2, previewNodeImage.getHeight()/2);
//        g.drawImage(previewNodeImage, 0, 0, this);
//      }

    }
   
    /** call this with world space position to make a click inside the window */
    public boolean doClick(int x, int y) {
      for (MiniButton b : miniButtons) {
        if (b.inside(x - posX, y -posY)) {
          if ("X".equals(b.t)) {
            removePreviewWindow(this);
          } else if ("+".equals(b.t)) {
            if (zoom < 256.0f) {
              zoom *= 2.0f;
              updatePreviewImage();
            }
          } else if ("-".equals(b.t)) {
            if (zoom > 0.125) {
              zoom /= 2.0f;
              updatePreviewImage();
            }
          }
          return true;
        }
      }
     
     
      return false;
    }
   
    public boolean isInside(int x, int y) {
      int width = previewNodeImage.getWidth();
      int height = previewNodeImage.getHeight()+16;
      return (x >= posX) && (x <= posX+width) && (y >= posY) && (y <= posY + height);
    }

   
   
   
    void updatePreviewImage() {
      if (previewNode != null) {
        if (previewNode.getChannel().chechkInputChannels()) {
         
          if (zoom == 1.0f) {
            ChannelUtils.computeImage(previewNode.getChannel(), previewNodeImage, null, 0);
          } else if (zoom > 1.0f) {
            ChannelUtils.computeImage(previewNode.getChannel(), previewNodeImage, null, 0, (int)(previewNodeImage.getWidth()*zoom), (int)(previewNodeImage.getHeight()*zoom), 0, 0);
          } else if (zoom < 1.0f) {
            int lx = (int)(previewNodeImage.getWidth()*zoom);
            int ly = (int)(previewNodeImage.getHeight()*zoom);
            if (tempImage == null || tempImage.getWidth() != lx || tempImage.getHeight() != ly) {
              tempImage = new BufferedImage(lx, ly, BufferedImage.TYPE_INT_RGB);
            }
            ChannelUtils.computeImage(previewNode.getChannel(), tempImage, null, 0);
            Graphics g = previewNodeImage.getGraphics();
            for (int py = 0; py < previewNodeImage.getHeight(); py += ly) {
              for (int px = 0; px < previewNodeImage.getWidth(); px += lx) {
                g.drawImage(tempImage, px, py, null);
              }
            }
          }
        } else {
          Graphics g = previewNodeImage.getGraphics();
          g.fillRect(0, 0, previewNodeImage.getWidth(), previewNodeImage.getHeight());
        }
      }
    }

    @Override
    public void channelChanged(Channel source) {
      // TODO Auto-generated method stub
      updatePreviewImage();
    }
   
   
  }

}
TOP

Related Classes of com.mystictri.neotextureedit.TextureGraphEditorPanel

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.