Fixed git clone URL
[sixth-3d.git] / src / main / java / eu / svjatoslav / sixth / e3d / gui / ViewPanel.java
index b23bf5b..e068bf5 100755 (executable)
@@ -1,62 +1,65 @@
 /*
- * Sixth 3D engine. Copyright ©2012-2018, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of version 3 of the GNU Lesser General Public License
- * or later as published by the Free Software Foundation.
- *
+ * Sixth 3D engine. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
  */
-
 package eu.svjatoslav.sixth.e3d.gui;
 
+import eu.svjatoslav.sixth.e3d.gui.humaninput.HIDEventTracker;
 import eu.svjatoslav.sixth.e3d.gui.humaninput.KeyboardFocusStack;
-import eu.svjatoslav.sixth.e3d.gui.humaninput.MouseInteractionController;
-import eu.svjatoslav.sixth.e3d.gui.humaninput.HIDInputTracker;
 import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
 
 import javax.swing.*;
 import java.awt.*;
 import java.awt.event.ComponentEvent;
 import java.awt.event.ComponentListener;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Set;
 import java.util.Timer;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Java Swing GUI panel that contains canvas for 3D rendering.
+ * Usually it is used as a part of {@link ViewFrame}.
  */
 public class ViewPanel extends JPanel implements ComponentListener {
     private static final long serialVersionUID = 1683277888885045387L;
-    public Color backgroundColor = Color.BLACK;
-    private final HIDInputTracker HIDInputTracker = new HIDInputTracker(this);
-    private final KeyboardFocusStack keyboardFocusStack = new KeyboardFocusStack(this);
+    private final HIDEventTracker HIDEventTracker = new HIDEventTracker(this);
+    private final KeyboardFocusStack keyboardFocusStack;
     private final Avatar avatar = new Avatar();
     private final ShapeCollection rootShapeCollection = new ShapeCollection();
-    private final List<ViewRenderListener> viewRenderListeners = new ArrayList<>();
+    private final Set<ViewRenderListener> viewRenderListeners = ConcurrentHashMap.newKeySet();
+    public Color backgroundColor = Color.BLACK;
+
     /**
-     * Last time this view was updated.
+     * Stores milliseconds when last frame was updated. This is needed to calculate time delta between frames.
+     * Time delta is used to calculate smooth animation.
      */
     private long lastUpdateMillis = 0;
+
+    /**
+     * Timer that is used to update canvas at target FPS rate.
+     */
     private Timer canvasUpdateTimer;
+
     private ViewUpdateTimerTask canvasUpdateTimerTask;
     private RenderingContext renderingContext = null;
+
     /**
-     * UI component that mouse is currently hovering over.
-     */
-    private MouseInteractionController currentMouseOverComponent;
-    /**
-     * Currently target FPS for this view. It can be changed at runtime. Also when nothing
-     * changes in the view, then frames are not really repainted.
+     * Currently target frames per second rate for this view. Target FPS can be changed at runtime.
+     * 3D engine tries to be smart and only repaints screen when there are visible changes.
      */
-    private int targetFPS = 30;
+    private int targetFPS = 60;
+
     /**
      * Set to true if it is known than next frame reeds to be painted. Flag is cleared
      * immediately after frame got updated.
      */
     private boolean viewRepaintNeeded = true;
+
     public ViewPanel() {
         viewRenderListeners.add(avatar);
-        viewRenderListeners.add(HIDInputTracker);
+        viewRenderListeners.add(HIDEventTracker);
+
+        keyboardFocusStack = new KeyboardFocusStack(this);
 
         initializePanelLayout();
 
@@ -77,8 +80,8 @@ public class ViewPanel extends JPanel implements ComponentListener {
         return rootShapeCollection;
     }
 
-    public HIDInputTracker getHIDInputTracker() {
-        return HIDInputTracker;
+    public HIDEventTracker getHIDInputTracker() {
+        return HIDEventTracker;
     }
 
     public void addViewUpdateListener(final ViewRenderListener listener) {
@@ -124,32 +127,6 @@ public class ViewPanel extends JPanel implements ComponentListener {
         return renderingContext;
     }
 
-    private void handleDetectedComponentMouseEvents() {
-        if (renderingContext.clickedItem != null) {
-            if (renderingContext.mouseClick.button == 0) {
-                // mouse over
-                if (currentMouseOverComponent == null) {
-                    currentMouseOverComponent = renderingContext.clickedItem;
-                    currentMouseOverComponent.mouseEntered();
-                    viewRepaintNeeded = true;
-                } else if (currentMouseOverComponent != renderingContext.clickedItem) {
-                    currentMouseOverComponent.mouseExited();
-                    currentMouseOverComponent = renderingContext.clickedItem;
-                    currentMouseOverComponent.mouseEntered();
-                    viewRepaintNeeded = true;
-                }
-            } else {
-                // mouse click
-                renderingContext.clickedItem.mouseClicked();
-                viewRepaintNeeded = true;
-            }
-        } else if (currentMouseOverComponent != null) {
-            currentMouseOverComponent.mouseExited();
-            viewRepaintNeeded = true;
-            currentMouseOverComponent = null;
-        }
-    }
-
     private void initializePanelLayout() {
         setFocusCycleRoot(true);
         setOpaque(true);
@@ -160,9 +137,6 @@ public class ViewPanel extends JPanel implements ComponentListener {
     }
 
     private void renderFrame() {
-        if (isNewRenderingContextNeeded())
-            renderingContext = new RenderingContext(getWidth(), getHeight());
-
         // paint root geometry collection to the offscreen render buffer
         clearCanvas();
         rootShapeCollection.paint(this, renderingContext);
@@ -178,20 +152,18 @@ public class ViewPanel extends JPanel implements ComponentListener {
         renderingContext.graphics.fillRect(0, 0, getWidth(), getHeight());
     }
 
-    private boolean isNewRenderingContextNeeded() {
-        return (renderingContext == null)
-                || (renderingContext.width != getWidth())
-                || (renderingContext.height != getHeight());
-    }
-
     /**
-     * Calling this methods tells 3D engine that current 3D view needs to be
+     * Calling these methods tells 3D engine that current 3D view needs to be
      * repainted on first opportunity.
      */
     public void repaintDuringNextViewUpdate() {
         viewRepaintNeeded = true;
     }
 
+    /**
+     * Set target frames per second rate for this view. Target FPS can be changed at runtime.
+     * @param frameRate target frames per second rate for this view.
+     */
     public void setFrameRate(final int frameRate) {
         if (canvasUpdateTimerTask != null) {
             canvasUpdateTimerTask.cancel();
@@ -203,25 +175,21 @@ public class ViewPanel extends JPanel implements ComponentListener {
 
         targetFPS = frameRate;
 
-        if (frameRate > 0) {
-            canvasUpdateTimer = new Timer();
-            canvasUpdateTimerTask = new ViewUpdateTimerTask(this);
+        if (frameRate <= 0) return;
 
-            canvasUpdateTimer.schedule(canvasUpdateTimerTask, 0,
-                    1000 / frameRate);
-        }
+        canvasUpdateTimer = new Timer();
+        canvasUpdateTimerTask = new ViewUpdateTimerTask(this);
+
+        // schedule timer task to run in frequency according to defined frame rate
+        canvasUpdateTimer.schedule(canvasUpdateTimerTask, 0,
+                1000 / frameRate);
     }
 
+    /**
+     * Stops rendering of this view.
+     */
     public void stop() {
-        if (canvasUpdateTimerTask != null) {
-            canvasUpdateTimerTask.cancel();
-            canvasUpdateTimerTask = null;
-        }
-
-        if (canvasUpdateTimer != null) {
-            canvasUpdateTimer.cancel();
-            canvasUpdateTimer = null;
-        }
+        setFrameRate(0);
     }
 
     /**
@@ -231,11 +199,8 @@ public class ViewPanel extends JPanel implements ComponentListener {
      * It tells view to update itself. View can decide if actual re-rendering of
      * graphics is needed.
      */
-    void updateView() {
-        if (renderingContext != null) {
-            renderingContext.mouseClick = null;
-            renderingContext.clickedItem = null;
-        }
+    void ensureThatViewIsUpToDate() {
+        maintainRenderingContext();
 
         final int millisecondsPassedSinceLastUpdate = getMillisecondsPassedSinceLastUpdate();
 
@@ -247,13 +212,30 @@ public class ViewPanel extends JPanel implements ComponentListener {
         }
 
         // abort rendering if window size is invalid
-        if ((getWidth() <= 0) || (getHeight() <= 0))
-            renderFrame = false;
-
-        if (renderFrame) {
+        if ((getWidth() > 0) && (getHeight() > 0) && renderFrame) {
             renderFrame();
-            handleDetectedComponentMouseEvents();
+            viewRepaintNeeded = renderingContext.handlePossibleComponentMouseEvent();
+        }
+
+    }
+
+    private void maintainRenderingContext() {
+        int panelWidth = getWidth();
+        int panelHeight = getHeight();
+
+        if (panelWidth <= 0 || panelHeight <= 0) {
+            renderingContext = null;
+            return;
+        }
+
+        // create new rendering context if window size has changed
+        if ((renderingContext == null)
+                || (renderingContext.width != panelWidth)
+                || (renderingContext.height != panelHeight)) {
+            renderingContext = new RenderingContext(panelWidth, panelHeight);
         }
+
+        renderingContext.prepareForNewFrameRendering();
     }
 
     private boolean notifyViewRenderListeners(int millisecondsPassedSinceLastUpdate) {
@@ -274,4 +256,13 @@ public class ViewPanel extends JPanel implements ComponentListener {
         lastUpdateMillis = currentTime;
         return millisecondsPassedSinceLastUpdate;
     }
+
+    public void addViewRenderListener(ViewRenderListener viewRenderListener) {
+        viewRenderListeners.add(viewRenderListener);
+    }
+
+    public void removeViewRenderListener(ViewRenderListener viewRenderListener) {
+        viewRenderListeners.remove(viewRenderListener);
+    }
+
 }