/*
- * 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();
return rootShapeCollection;
}
- public HIDInputTracker getHIDInputTracker() {
- return HIDInputTracker;
+ public HIDEventTracker getHIDInputTracker() {
+ return HIDEventTracker;
}
public void addViewUpdateListener(final ViewRenderListener listener) {
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);
}
private void renderFrame() {
- if (isNewRenderingContextNeeded())
- renderingContext = new RenderingContext(getWidth(), getHeight());
-
// paint root geometry collection to the offscreen render buffer
clearCanvas();
rootShapeCollection.paint(this, renderingContext);
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();
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);
}
/**
* 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();
}
// 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) {
lastUpdateMillis = currentTime;
return millisecondsPassedSinceLastUpdate;
}
+
+ public void addViewRenderListener(ViewRenderListener viewRenderListener) {
+ viewRenderListeners.add(viewRenderListener);
+ }
+
+ public void removeViewRenderListener(ViewRenderListener viewRenderListener) {
+ viewRenderListeners.remove(viewRenderListener);
+ }
+
}