2 * Sixth 3D engine. Copyright ©2012-2018, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of version 3 of the GNU Lesser General Public License
6 * or later as published by the Free Software Foundation.
10 package eu.svjatoslav.sixth.e3d.gui;
12 import eu.svjatoslav.sixth.e3d.gui.humaninput.KeyboardFocusStack;
13 import eu.svjatoslav.sixth.e3d.gui.humaninput.MouseInteractionController;
14 import eu.svjatoslav.sixth.e3d.gui.humaninput.HIDInputTracker;
15 import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
19 import java.awt.event.ComponentEvent;
20 import java.awt.event.ComponentListener;
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.Timer;
26 * Java Swing GUI panel that contains canvas for 3D rendering.
28 public class ViewPanel extends JPanel implements ComponentListener {
29 private static final long serialVersionUID = 1683277888885045387L;
30 public Color backgroundColor = Color.BLACK;
31 private final HIDInputTracker HIDInputTracker = new HIDInputTracker(this);
32 private final KeyboardFocusStack keyboardFocusStack = new KeyboardFocusStack(this);
33 private final Avatar avatar = new Avatar();
34 private final ShapeCollection rootShapeCollection = new ShapeCollection();
35 private final List<ViewRenderListener> viewRenderListeners = new ArrayList<>();
37 * Last time this view was updated.
39 private long lastUpdateMillis = 0;
40 private Timer canvasUpdateTimer;
41 private ViewUpdateTimerTask canvasUpdateTimerTask;
42 private RenderingContext renderingContext = null;
44 * UI component that mouse is currently hovering over.
46 private MouseInteractionController currentMouseOverComponent;
48 * Currently target FPS for this view. It can be changed at runtime. Also when nothing
49 * changes in the view, then frames are not really repainted.
51 private int targetFPS = 30;
53 * Set to true if it is known than next frame reeds to be painted. Flag is cleared
54 * immediately after frame got updated.
56 private boolean viewRepaintNeeded = true;
58 viewRenderListeners.add(avatar);
59 viewRenderListeners.add(HIDInputTracker);
61 initializePanelLayout();
63 setFrameRate(targetFPS);
65 addComponentListener(this);
68 public Avatar getAvatar() {
72 public KeyboardFocusStack getKeyboardFocusStack() {
73 return keyboardFocusStack;
76 public ShapeCollection getRootShapeCollection() {
77 return rootShapeCollection;
80 public HIDInputTracker getHIDInputTracker() {
81 return HIDInputTracker;
84 public void addViewUpdateListener(final ViewRenderListener listener) {
85 viewRenderListeners.add(listener);
89 public void componentHidden(final ComponentEvent e) {
94 public void componentMoved(final ComponentEvent e) {
99 public void componentResized(final ComponentEvent e) {
100 viewRepaintNeeded = true;
104 public void componentShown(final ComponentEvent e) {
105 viewRepaintNeeded = true;
109 public Dimension getMaximumSize() {
110 return getPreferredSize();
114 public Dimension getMinimumSize() {
115 return getPreferredSize();
119 public java.awt.Dimension getPreferredSize() {
120 return new java.awt.Dimension(640, 480);
123 public RenderingContext getRenderingContext() {
124 return renderingContext;
127 private void handleDetectedComponentMouseEvents() {
128 if (renderingContext.clickedItem != null) {
129 if (renderingContext.mouseClick.button == 0) {
131 if (currentMouseOverComponent == null) {
132 currentMouseOverComponent = renderingContext.clickedItem;
133 currentMouseOverComponent.mouseEntered();
134 viewRepaintNeeded = true;
135 } else if (currentMouseOverComponent != renderingContext.clickedItem) {
136 currentMouseOverComponent.mouseExited();
137 currentMouseOverComponent = renderingContext.clickedItem;
138 currentMouseOverComponent.mouseEntered();
139 viewRepaintNeeded = true;
143 renderingContext.clickedItem.mouseClicked();
144 viewRepaintNeeded = true;
146 } else if (currentMouseOverComponent != null) {
147 currentMouseOverComponent.mouseExited();
148 viewRepaintNeeded = true;
149 currentMouseOverComponent = null;
153 private void initializePanelLayout() {
154 setFocusCycleRoot(true);
157 setDoubleBuffered(false);
159 requestFocusInWindow();
162 private void renderFrame() {
163 if (isNewRenderingContextNeeded())
164 renderingContext = new RenderingContext(getWidth(), getHeight());
166 // paint root geometry collection to the offscreen render buffer
168 rootShapeCollection.paint(this, renderingContext);
170 // draw rendered offscreen buffer to visible screen
171 final Graphics graphics = getGraphics();
172 if (graphics != null)
173 graphics.drawImage(renderingContext.bufferedImage, 0, 0, null);
176 private void clearCanvas() {
177 renderingContext.graphics.setColor(backgroundColor);
178 renderingContext.graphics.fillRect(0, 0, getWidth(), getHeight());
181 private boolean isNewRenderingContextNeeded() {
182 return (renderingContext == null)
183 || (renderingContext.width != getWidth())
184 || (renderingContext.height != getHeight());
188 * Calling this methods tells 3D engine that current 3D view needs to be
189 * repainted on first opportunity.
191 public void repaintDuringNextViewUpdate() {
192 viewRepaintNeeded = true;
195 public void setFrameRate(final int frameRate) {
196 if (canvasUpdateTimerTask != null) {
197 canvasUpdateTimerTask.cancel();
198 canvasUpdateTimerTask = null;
200 canvasUpdateTimer.cancel();
201 canvasUpdateTimer = null;
204 targetFPS = frameRate;
207 canvasUpdateTimer = new Timer();
208 canvasUpdateTimerTask = new ViewUpdateTimerTask(this);
210 canvasUpdateTimer.schedule(canvasUpdateTimerTask, 0,
216 if (canvasUpdateTimerTask != null) {
217 canvasUpdateTimerTask.cancel();
218 canvasUpdateTimerTask = null;
221 if (canvasUpdateTimer != null) {
222 canvasUpdateTimer.cancel();
223 canvasUpdateTimer = null;
228 * This method is executed by periodic timer task, in frequency according to
229 * defined frame rate.
231 * It tells view to update itself. View can decide if actual re-rendering of
232 * graphics is needed.
235 if (renderingContext != null) {
236 renderingContext.mouseClick = null;
237 renderingContext.clickedItem = null;
240 final int millisecondsPassedSinceLastUpdate = getMillisecondsPassedSinceLastUpdate();
242 boolean renderFrame = notifyViewRenderListeners(millisecondsPassedSinceLastUpdate);
244 if (viewRepaintNeeded) {
245 viewRepaintNeeded = false;
249 // abort rendering if window size is invalid
250 if ((getWidth() <= 0) || (getHeight() <= 0))
255 handleDetectedComponentMouseEvents();
259 private boolean notifyViewRenderListeners(int millisecondsPassedSinceLastUpdate) {
260 boolean reRenderFrame = false;
261 for (final ViewRenderListener listener : viewRenderListeners)
262 if (listener.beforeRender(this, millisecondsPassedSinceLastUpdate))
263 reRenderFrame = true;
264 return reRenderFrame;
267 private int getMillisecondsPassedSinceLastUpdate() {
268 final long currentTime = System.currentTimeMillis();
270 if (lastUpdateMillis == 0)
271 lastUpdateMillis = currentTime;
273 final int millisecondsPassedSinceLastUpdate = (int) (currentTime - lastUpdateMillis);
274 lastUpdateMillis = currentTime;
275 return millisecondsPassedSinceLastUpdate;