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.KeyboardFocusTracker;
13 import eu.svjatoslav.sixth.e3d.gui.humaninput.MouseInteractionController;
14 import eu.svjatoslav.sixth.e3d.gui.humaninput.UserInputTracker;
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;
25 public class ViewPanel extends JPanel implements ComponentListener {
26 private static final long serialVersionUID = 1683277888885045387L;
27 private final UserInputTracker userInputTracker = new UserInputTracker(this);
28 private final KeyboardFocusTracker keyboardFocusTracker = new KeyboardFocusTracker(
30 private final Avatar avatar = new Avatar();
31 private final ShapeCollection rootShapeCollection = new ShapeCollection();
32 private final List<ViewRenderListener> viewRenderListeners = new ArrayList<>();
34 * Last time this view was updated.
36 private long lastUpdateMillis = 0;
37 private Timer canvasUpdateTimer;
38 private ViewUpdateTimerTask canvasUpdateTimerTask;
39 private RenderingContext renderingContext = null;
41 * UI component that mouse is currently hovering over.
43 private MouseInteractionController currentMouseOverComponent;
45 * Currently target FPS for this view. It can be changed at runtime. Also when nothing
46 * changes in the view, then frames are not really repainted.
48 private int targetFPS = 30;
50 * Set to true if it is known than next frame reeds to be painted. Flag is cleared
51 * immediately after frame got updated.
53 private boolean viewRepaintNeeded = true;
55 viewRenderListeners.add(avatar);
56 viewRenderListeners.add(userInputTracker);
58 initializePanelLayout();
60 setFrameRate(targetFPS);
62 addComponentListener(this);
65 public Avatar getAvatar() {
69 public KeyboardFocusTracker getKeyboardFocusTracker() {
70 return keyboardFocusTracker;
73 public ShapeCollection getRootShapeCollection() {
74 return rootShapeCollection;
77 public UserInputTracker getUserInputTracker() {
78 return userInputTracker;
81 public void addViewUpdateListener(final ViewRenderListener listener) {
82 viewRenderListeners.add(listener);
86 public void componentHidden(final ComponentEvent e) {
91 public void componentMoved(final ComponentEvent e) {
96 public void componentResized(final ComponentEvent e) {
97 viewRepaintNeeded = true;
101 public void componentShown(final ComponentEvent e) {
102 viewRepaintNeeded = true;
106 public Dimension getMaximumSize() {
107 return getPreferredSize();
111 public Dimension getMinimumSize() {
112 return getPreferredSize();
116 public java.awt.Dimension getPreferredSize() {
117 return new java.awt.Dimension(640, 480);
120 public RenderingContext getRenderBuffer() {
121 return renderingContext;
124 public RenderingContext getRenderingContext() {
125 return renderingContext;
128 private void handleDetectedComponentMouseEvents() {
129 if (renderingContext.clickedItem != null) {
130 if (renderingContext.mouseClick.button == 0) {
132 if (currentMouseOverComponent == null) {
133 currentMouseOverComponent = renderingContext.clickedItem;
134 currentMouseOverComponent.mouseEntered();
135 viewRepaintNeeded = true;
136 } else if (currentMouseOverComponent != renderingContext.clickedItem) {
137 currentMouseOverComponent.mouseExited();
138 currentMouseOverComponent = renderingContext.clickedItem;
139 currentMouseOverComponent.mouseEntered();
140 viewRepaintNeeded = true;
144 renderingContext.clickedItem.mouseClicked();
145 viewRepaintNeeded = true;
147 } else if (currentMouseOverComponent != null) {
148 currentMouseOverComponent.mouseExited();
149 viewRepaintNeeded = true;
150 currentMouseOverComponent = null;
154 private void initializePanelLayout() {
155 setFocusCycleRoot(true);
158 setDoubleBuffered(false);
160 requestFocusInWindow();
163 public void renderFrame() {
164 // build new render buffer if needed, this happens when window was just
165 // created or resized
166 if ((renderingContext == null)
167 || (renderingContext.width != getWidth())
168 || (renderingContext.height != getHeight()))
169 renderingContext = new RenderingContext(getWidth(), getHeight());
171 // clear drawing area
173 renderingContext.graphics.setColor(Color.BLACK);
174 renderingContext.graphics.fillRect(0, 0, getWidth(), getHeight());
177 // paint root geometry collection to the offscreen render buffer
178 rootShapeCollection.paint(this, renderingContext);
180 // draw rendered offscreen image to visible screen
181 final Graphics graphics = getGraphics();
182 if (graphics != null)
183 graphics.drawImage(renderingContext.bufferedImage, 0, 0, null);
187 * Calling this methods tells 3D engine that current 3D view needs to be
188 * repainted on first opportunity.
190 public void repaintDuringNextViewUpdate() {
191 viewRepaintNeeded = true;
194 public void setFrameRate(final int frameRate) {
195 if (canvasUpdateTimerTask != null) {
196 canvasUpdateTimerTask.cancel();
197 canvasUpdateTimerTask = null;
199 canvasUpdateTimer.cancel();
200 canvasUpdateTimer = null;
203 targetFPS = frameRate;
206 canvasUpdateTimer = new Timer();
207 canvasUpdateTimerTask = new ViewUpdateTimerTask(this);
209 canvasUpdateTimer.schedule(canvasUpdateTimerTask, 0,
215 if (canvasUpdateTimerTask != null) {
216 canvasUpdateTimerTask.cancel();
217 canvasUpdateTimerTask = null;
220 if (canvasUpdateTimer != null) {
221 canvasUpdateTimer.cancel();
222 canvasUpdateTimer = null;
227 * This method is executed by periodic timer task, in frequency according to
228 * defined frame rate.
230 * It tells view to update itself. View can decide if actual re-rendering of
231 * graphics is needed.
233 public void updateView() {
234 if (renderingContext != null) {
235 renderingContext.mouseClick = null;
236 renderingContext.clickedItem = null;
239 // compute time passed since last view update
240 final long currentTime = System.currentTimeMillis();
242 if (lastUpdateMillis == 0) {
243 lastUpdateMillis = currentTime;
247 final int millisecondsPassedSinceLastUpdate = (int) (currentTime - lastUpdateMillis);
248 lastUpdateMillis = currentTime;
250 // notify update listeners
251 boolean reRenderFrame = false;
253 for (final ViewRenderListener listener : viewRenderListeners)
254 if (listener.beforeRender(this, millisecondsPassedSinceLastUpdate))
255 reRenderFrame = true;
257 // abort rendering if window size is invalid
258 if ((getWidth() <= 0) || (getHeight() <= 0))
261 if (viewRepaintNeeded) {
262 viewRepaintNeeded = false;
263 reRenderFrame = true;
268 handleDetectedComponentMouseEvents();