Updated copyright
[sixth-3d.git] / src / main / java / eu / svjatoslav / sixth / e3d / gui / ViewPanel.java
index 87f0dd3..8c57e37 100755 (executable)
@@ -1,5 +1,5 @@
 /*
- * Sixth 3D engine. Copyright ©2012-2018, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
+ * Sixth 3D engine. Copyright ©2012-2019, 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
@@ -9,21 +9,29 @@
 
 package eu.svjatoslav.sixth.e3d.gui;
 
-import eu.svjatoslav.sixth.e3d.gui.humaninput.MouseInteractionController;
+import eu.svjatoslav.sixth.e3d.gui.humaninput.HIDInputTracker;
+import eu.svjatoslav.sixth.e3d.gui.humaninput.KeyboardFocusStack;
+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.
+ */
 public class ViewPanel extends JPanel implements ComponentListener {
-
     private static final long serialVersionUID = 1683277888885045387L;
-    private final List<ViewRenderListener> viewRenderListeners = new ArrayList<>();
-    private final ViewContext context = new ViewContext(this);
+    public Color backgroundColor = Color.BLACK;
+    private final HIDInputTracker HIDInputTracker = new HIDInputTracker(this);
+    private final KeyboardFocusStack keyboardFocusStack;
+    private final Avatar avatar = new Avatar();
+    private final ShapeCollection rootShapeCollection = new ShapeCollection();
+    private final Set<ViewRenderListener> viewRenderListeners = ConcurrentHashMap.newKeySet();
     /**
      * Last time this view was updated.
      */
@@ -31,27 +39,46 @@ public class ViewPanel extends JPanel implements ComponentListener {
     private Timer canvasUpdateTimer;
     private ViewUpdateTimerTask canvasUpdateTimerTask;
     private RenderingContext renderingContext = null;
-    private MouseInteractionController currentMouseOverComponent;
     /**
-     * Currently target FPS for this view. It might change at runtime.
+     * 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.
+     */
+    private int targetFPS = 30;
+    /**
+     * Set to true if it is known than next frame reeds to be painted. Flag is cleared
+     * immediately after frame got updated.
      */
-    private int targetFramerate = 30;
-    private boolean repaintDuringNextViewUpdate = true;
+    private boolean viewRepaintNeeded = true;
 
     public ViewPanel() {
-        viewRenderListeners.add(context.getAvatar());
+        viewRenderListeners.add(avatar);
+        viewRenderListeners.add(HIDInputTracker);
 
-        // initialize input tracker
-        context.getUserInputTracker().bind(this);
-        viewRenderListeners.add(context.getUserInputTracker());
+        keyboardFocusStack = new KeyboardFocusStack(this);
 
         initializePanelLayout();
 
-        setFrameRate(targetFramerate);
+        setFrameRate(targetFPS);
 
         addComponentListener(this);
     }
 
+    public Avatar getAvatar() {
+        return avatar;
+    }
+
+    public KeyboardFocusStack getKeyboardFocusStack() {
+        return keyboardFocusStack;
+    }
+
+    public ShapeCollection getRootShapeCollection() {
+        return rootShapeCollection;
+    }
+
+    public HIDInputTracker getHIDInputTracker() {
+        return HIDInputTracker;
+    }
+
     public void addViewUpdateListener(final ViewRenderListener listener) {
         viewRenderListeners.add(listener);
     }
@@ -68,16 +95,12 @@ public class ViewPanel extends JPanel implements ComponentListener {
 
     @Override
     public void componentResized(final ComponentEvent e) {
-        repaintDuringNextViewUpdate = true;
+        viewRepaintNeeded = true;
     }
 
     @Override
     public void componentShown(final ComponentEvent e) {
-        repaintDuringNextViewUpdate = true;
-    }
-
-    public ViewContext getContext() {
-        return context;
+        viewRepaintNeeded = true;
     }
 
     @Override
@@ -95,40 +118,10 @@ public class ViewPanel extends JPanel implements ComponentListener {
         return new java.awt.Dimension(640, 480);
     }
 
-    public RenderingContext getRenderBuffer() {
-        return renderingContext;
-    }
-
     public RenderingContext getRenderingContext() {
         return renderingContext;
     }
 
-    private void handleDetectedComponentMouseEvents() {
-        if (renderingContext.clickedItem != null) {
-            if (renderingContext.mouseClick.button == 0) {
-                // mouse over
-                if (currentMouseOverComponent == null) {
-                    currentMouseOverComponent = renderingContext.clickedItem;
-                    currentMouseOverComponent.mouseEntered();
-                    repaintDuringNextViewUpdate = true;
-                } else if (currentMouseOverComponent != renderingContext.clickedItem) {
-                    currentMouseOverComponent.mouseExited();
-                    currentMouseOverComponent = renderingContext.clickedItem;
-                    currentMouseOverComponent.mouseEntered();
-                    repaintDuringNextViewUpdate = true;
-                }
-            } else {
-                // mouse click
-                renderingContext.clickedItem.mouseClicked();
-                repaintDuringNextViewUpdate = true;
-            }
-        } else if (currentMouseOverComponent != null) {
-            currentMouseOverComponent.mouseExited();
-            repaintDuringNextViewUpdate = true;
-            currentMouseOverComponent = null;
-        }
-    }
-
     private void initializePanelLayout() {
         setFocusCycleRoot(true);
         setOpaque(true);
@@ -138,35 +131,28 @@ public class ViewPanel extends JPanel implements ComponentListener {
         requestFocusInWindow();
     }
 
-    public void renderFrame() {
-        // build new render buffer if needed, this happens when window was just
-        // created or resized
-        if ((renderingContext == null)
-                || (renderingContext.width != getWidth())
-                || (renderingContext.height != getHeight()))
-            renderingContext = new RenderingContext(getWidth(), getHeight());
-
-        // clear drawing area
-        {
-            renderingContext.graphics.setColor(Color.BLACK);
-            renderingContext.graphics.fillRect(0, 0, getWidth(), getHeight());
-        }
-
+    private void renderFrame() {
         // paint root geometry collection to the offscreen render buffer
-        context.getRootShapeCollection().paint(context, renderingContext);
+        clearCanvas();
+        rootShapeCollection.paint(this, renderingContext);
 
-        // draw rendered offscreen image to visible screen
+        // draw rendered offscreen buffer to visible screen
         final Graphics graphics = getGraphics();
         if (graphics != null)
             graphics.drawImage(renderingContext.bufferedImage, 0, 0, null);
     }
 
+    private void clearCanvas() {
+        renderingContext.graphics.setColor(backgroundColor);
+        renderingContext.graphics.fillRect(0, 0, getWidth(), getHeight());
+    }
+
     /**
      * Calling this methods tells 3D engine that current 3D view needs to be
      * repainted on first opportunity.
      */
     public void repaintDuringNextViewUpdate() {
-        repaintDuringNextViewUpdate = true;
+        viewRepaintNeeded = true;
     }
 
     public void setFrameRate(final int frameRate) {
@@ -178,7 +164,7 @@ public class ViewPanel extends JPanel implements ComponentListener {
             canvasUpdateTimer = null;
         }
 
-        targetFramerate = frameRate;
+        targetFPS = frameRate;
 
         if (frameRate > 0) {
             canvasUpdateTimer = new Timer();
@@ -208,43 +194,69 @@ public class ViewPanel extends JPanel implements ComponentListener {
      * It tells view to update itself. View can decide if actual re-rendering of
      * graphics is needed.
      */
-    public void updateView() {
-        if (renderingContext != null){
-            renderingContext.mouseClick = null;
-            renderingContext.clickedItem = null;
+    void updateView() {
+        maintainRenderingContext();
+
+        final int millisecondsPassedSinceLastUpdate = getMillisecondsPassedSinceLastUpdate();
+
+        boolean renderFrame = notifyViewRenderListeners(millisecondsPassedSinceLastUpdate);
+
+        if (viewRepaintNeeded) {
+            viewRepaintNeeded = false;
+            renderFrame = true;
         }
 
-        // compute time passed since last view update
-        final long currentTime = System.currentTimeMillis();
+        // abort rendering if window size is invalid
+        if ((getWidth() > 0) && (getHeight() > 0) && renderFrame) {
+            renderFrame();
+            viewRepaintNeeded = renderingContext.handleDetectedComponentMouseEvents();
+        }
 
-        if (lastUpdateMillis == 0) {
-            lastUpdateMillis = currentTime;
+    }
+
+    private void maintainRenderingContext() {
+        int panelWidth = getWidth();
+        int panelHeight = getHeight();
+
+        if (panelWidth <= 0 || panelHeight <=0){
+            renderingContext = null;
             return;
         }
 
-        final int millisecondsPassedSinceLastUpdate = (int) (currentTime - lastUpdateMillis);
-        lastUpdateMillis = currentTime;
+        if ((renderingContext == null)
+                || (renderingContext.width != panelWidth)
+                || (renderingContext.height != panelHeight)) {
+            renderingContext = new RenderingContext(panelWidth, panelHeight);
+        }
 
-        // notify update listeners
-        boolean reRenderFrame = false;
+        renderingContext.prepareForNewFrameRendering();
+    }
 
+    private boolean notifyViewRenderListeners(int millisecondsPassedSinceLastUpdate) {
+        boolean reRenderFrame = false;
         for (final ViewRenderListener listener : viewRenderListeners)
-            if (listener.beforeRender(context,
-                    millisecondsPassedSinceLastUpdate))
+            if (listener.beforeRender(this, millisecondsPassedSinceLastUpdate))
                 reRenderFrame = true;
+        return reRenderFrame;
+    }
 
-        // abort rendering if window size is invalid
-        if ((getWidth() <= 0) || (getHeight() <= 0))
-            return;
+    private int getMillisecondsPassedSinceLastUpdate() {
+        final long currentTime = System.currentTimeMillis();
 
-        if (repaintDuringNextViewUpdate) {
-            repaintDuringNextViewUpdate = false;
-            reRenderFrame = true;
-        }
+        if (lastUpdateMillis == 0)
+            lastUpdateMillis = currentTime;
 
-        if (reRenderFrame) {
-            renderFrame();
-            handleDetectedComponentMouseEvents();
-        }
+        final int millisecondsPassedSinceLastUpdate = (int) (currentTime - lastUpdateMillis);
+        lastUpdateMillis = currentTime;
+        return millisecondsPassedSinceLastUpdate;
+    }
+
+    public void addViewRenderListener(ViewRenderListener viewRenderListener) {
+        viewRenderListeners.add(viewRenderListener);
     }
+
+    public void removeViewRenderListener(ViewRenderListener viewRenderListener) {
+        viewRenderListeners.remove(viewRenderListener);
+    }
+
 }