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 final UserInputTracker userInputTracker = new UserInputTracker(this);
28 private final KeyboardFocusTracker keyboardFocusTracker = new KeyboardFocusTracker(
31 private final Avatar avatar = new Avatar();
33 private final ShapeCollection rootShapeCollection = new ShapeCollection();
36 public Avatar getAvatar() {
40 public KeyboardFocusTracker getKeyboardFocusTracker() {
41 return keyboardFocusTracker;
44 public ShapeCollection getRootShapeCollection() {
45 return rootShapeCollection;
48 public UserInputTracker getUserInputTracker() {
49 return userInputTracker;
52 private static final long serialVersionUID = 1683277888885045387L;
53 private final List<ViewRenderListener> viewRenderListeners = new ArrayList<>();
55 * Last time this view was updated.
57 private long lastUpdateMillis = 0;
58 private Timer canvasUpdateTimer;
59 private ViewUpdateTimerTask canvasUpdateTimerTask;
60 private RenderingContext renderingContext = null;
63 * UI component that mouse is currently hovering over.
65 private MouseInteractionController currentMouseOverComponent;
68 * Currently target FPS for this view. It can be changed at runtime. Also when nothing
69 * changes in the view, then frames are not really repainted.
71 private int targetFPS = 30;
74 * Set to true if it is known than next frame reeds to be painted. Flag is cleared
75 * immediately after frame got updated.
77 private boolean viewRepaintNeeded = true;
80 viewRenderListeners.add(avatar);
81 viewRenderListeners.add(userInputTracker);
83 initializePanelLayout();
85 setFrameRate(targetFPS);
87 addComponentListener(this);
90 public void addViewUpdateListener(final ViewRenderListener listener) {
91 viewRenderListeners.add(listener);
95 public void componentHidden(final ComponentEvent e) {
100 public void componentMoved(final ComponentEvent e) {
105 public void componentResized(final ComponentEvent e) {
106 viewRepaintNeeded = true;
110 public void componentShown(final ComponentEvent e) {
111 viewRepaintNeeded = true;
115 public Dimension getMaximumSize() {
116 return getPreferredSize();
120 public Dimension getMinimumSize() {
121 return getPreferredSize();
125 public java.awt.Dimension getPreferredSize() {
126 return new java.awt.Dimension(640, 480);
129 public RenderingContext getRenderBuffer() {
130 return renderingContext;
133 public RenderingContext getRenderingContext() {
134 return renderingContext;
137 private void handleDetectedComponentMouseEvents() {
138 if (renderingContext.clickedItem != null) {
139 if (renderingContext.mouseClick.button == 0) {
141 if (currentMouseOverComponent == null) {
142 currentMouseOverComponent = renderingContext.clickedItem;
143 currentMouseOverComponent.mouseEntered();
144 viewRepaintNeeded = true;
145 } else if (currentMouseOverComponent != renderingContext.clickedItem) {
146 currentMouseOverComponent.mouseExited();
147 currentMouseOverComponent = renderingContext.clickedItem;
148 currentMouseOverComponent.mouseEntered();
149 viewRepaintNeeded = true;
153 renderingContext.clickedItem.mouseClicked();
154 viewRepaintNeeded = true;
156 } else if (currentMouseOverComponent != null) {
157 currentMouseOverComponent.mouseExited();
158 viewRepaintNeeded = true;
159 currentMouseOverComponent = null;
163 private void initializePanelLayout() {
164 setFocusCycleRoot(true);
167 setDoubleBuffered(false);
169 requestFocusInWindow();
172 public void renderFrame() {
173 // build new render buffer if needed, this happens when window was just
174 // created or resized
175 if ((renderingContext == null)
176 || (renderingContext.width != getWidth())
177 || (renderingContext.height != getHeight()))
178 renderingContext = new RenderingContext(getWidth(), getHeight());
180 // clear drawing area
182 renderingContext.graphics.setColor(Color.BLACK);
183 renderingContext.graphics.fillRect(0, 0, getWidth(), getHeight());
186 // paint root geometry collection to the offscreen render buffer
187 rootShapeCollection.paint(this, renderingContext);
189 // draw rendered offscreen image to visible screen
190 final Graphics graphics = getGraphics();
191 if (graphics != null)
192 graphics.drawImage(renderingContext.bufferedImage, 0, 0, null);
196 * Calling this methods tells 3D engine that current 3D view needs to be
197 * repainted on first opportunity.
199 public void repaintDuringNextViewUpdate() {
200 viewRepaintNeeded = true;
203 public void setFrameRate(final int frameRate) {
204 if (canvasUpdateTimerTask != null) {
205 canvasUpdateTimerTask.cancel();
206 canvasUpdateTimerTask = null;
208 canvasUpdateTimer.cancel();
209 canvasUpdateTimer = null;
212 targetFPS = frameRate;
215 canvasUpdateTimer = new Timer();
216 canvasUpdateTimerTask = new ViewUpdateTimerTask(this);
218 canvasUpdateTimer.schedule(canvasUpdateTimerTask, 0,
224 if (canvasUpdateTimerTask != null) {
225 canvasUpdateTimerTask.cancel();
226 canvasUpdateTimerTask = null;
229 if (canvasUpdateTimer != null) {
230 canvasUpdateTimer.cancel();
231 canvasUpdateTimer = null;
236 * This method is executed by periodic timer task, in frequency according to
237 * defined frame rate.
239 * It tells view to update itself. View can decide if actual re-rendering of
240 * graphics is needed.
242 public void updateView() {
243 if (renderingContext != null){
244 renderingContext.mouseClick = null;
245 renderingContext.clickedItem = null;
248 // compute time passed since last view update
249 final long currentTime = System.currentTimeMillis();
251 if (lastUpdateMillis == 0) {
252 lastUpdateMillis = currentTime;
256 final int millisecondsPassedSinceLastUpdate = (int) (currentTime - lastUpdateMillis);
257 lastUpdateMillis = currentTime;
259 // notify update listeners
260 boolean reRenderFrame = false;
262 for (final ViewRenderListener listener : viewRenderListeners)
263 if (listener.beforeRender(this, millisecondsPassedSinceLastUpdate))
264 reRenderFrame = true;
266 // abort rendering if window size is invalid
267 if ((getWidth() <= 0) || (getHeight() <= 0))
270 if (viewRepaintNeeded) {
271 viewRepaintNeeded = false;
272 reRenderFrame = true;
277 handleDetectedComponentMouseEvents();