Code cleanup and formatting.
[sixth-3d.git] / src / main / java / eu / svjatoslav / sixth / e3d / gui / ViewPanel.java
1 /*
2  * Sixth 3D engine. Copyright ©2012-2018, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
3  *
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.
7  *
8  */
9
10 package eu.svjatoslav.sixth.e3d.gui;
11
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;
16
17 import javax.swing.*;
18 import java.awt.*;
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;
24
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(
29             this);
30     private final Avatar avatar = new Avatar();
31     private final ShapeCollection rootShapeCollection = new ShapeCollection();
32     private final List<ViewRenderListener> viewRenderListeners = new ArrayList<>();
33     /**
34      * Last time this view was updated.
35      */
36     private long lastUpdateMillis = 0;
37     private Timer canvasUpdateTimer;
38     private ViewUpdateTimerTask canvasUpdateTimerTask;
39     private RenderingContext renderingContext = null;
40     /**
41      * UI component that mouse is currently hovering over.
42      */
43     private MouseInteractionController currentMouseOverComponent;
44     /**
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.
47      */
48     private int targetFPS = 30;
49     /**
50      * Set to true if it is known than next frame reeds to be painted. Flag is cleared
51      * immediately after frame got updated.
52      */
53     private boolean viewRepaintNeeded = true;
54     public ViewPanel() {
55         viewRenderListeners.add(avatar);
56         viewRenderListeners.add(userInputTracker);
57
58         initializePanelLayout();
59
60         setFrameRate(targetFPS);
61
62         addComponentListener(this);
63     }
64
65     public Avatar getAvatar() {
66         return avatar;
67     }
68
69     public KeyboardFocusTracker getKeyboardFocusTracker() {
70         return keyboardFocusTracker;
71     }
72
73     public ShapeCollection getRootShapeCollection() {
74         return rootShapeCollection;
75     }
76
77     public UserInputTracker getUserInputTracker() {
78         return userInputTracker;
79     }
80
81     public void addViewUpdateListener(final ViewRenderListener listener) {
82         viewRenderListeners.add(listener);
83     }
84
85     @Override
86     public void componentHidden(final ComponentEvent e) {
87
88     }
89
90     @Override
91     public void componentMoved(final ComponentEvent e) {
92
93     }
94
95     @Override
96     public void componentResized(final ComponentEvent e) {
97         viewRepaintNeeded = true;
98     }
99
100     @Override
101     public void componentShown(final ComponentEvent e) {
102         viewRepaintNeeded = true;
103     }
104
105     @Override
106     public Dimension getMaximumSize() {
107         return getPreferredSize();
108     }
109
110     @Override
111     public Dimension getMinimumSize() {
112         return getPreferredSize();
113     }
114
115     @Override
116     public java.awt.Dimension getPreferredSize() {
117         return new java.awt.Dimension(640, 480);
118     }
119
120     public RenderingContext getRenderBuffer() {
121         return renderingContext;
122     }
123
124     public RenderingContext getRenderingContext() {
125         return renderingContext;
126     }
127
128     private void handleDetectedComponentMouseEvents() {
129         if (renderingContext.clickedItem != null) {
130             if (renderingContext.mouseClick.button == 0) {
131                 // mouse over
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;
141                 }
142             } else {
143                 // mouse click
144                 renderingContext.clickedItem.mouseClicked();
145                 viewRepaintNeeded = true;
146             }
147         } else if (currentMouseOverComponent != null) {
148             currentMouseOverComponent.mouseExited();
149             viewRepaintNeeded = true;
150             currentMouseOverComponent = null;
151         }
152     }
153
154     private void initializePanelLayout() {
155         setFocusCycleRoot(true);
156         setOpaque(true);
157         setFocusable(true);
158         setDoubleBuffered(false);
159         setVisible(true);
160         requestFocusInWindow();
161     }
162
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());
170
171         // clear drawing area
172         {
173             renderingContext.graphics.setColor(Color.BLACK);
174             renderingContext.graphics.fillRect(0, 0, getWidth(), getHeight());
175         }
176
177         // paint root geometry collection to the offscreen render buffer
178         rootShapeCollection.paint(this, renderingContext);
179
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);
184     }
185
186     /**
187      * Calling this methods tells 3D engine that current 3D view needs to be
188      * repainted on first opportunity.
189      */
190     public void repaintDuringNextViewUpdate() {
191         viewRepaintNeeded = true;
192     }
193
194     public void setFrameRate(final int frameRate) {
195         if (canvasUpdateTimerTask != null) {
196             canvasUpdateTimerTask.cancel();
197             canvasUpdateTimerTask = null;
198
199             canvasUpdateTimer.cancel();
200             canvasUpdateTimer = null;
201         }
202
203         targetFPS = frameRate;
204
205         if (frameRate > 0) {
206             canvasUpdateTimer = new Timer();
207             canvasUpdateTimerTask = new ViewUpdateTimerTask(this);
208
209             canvasUpdateTimer.schedule(canvasUpdateTimerTask, 0,
210                     1000 / frameRate);
211         }
212     }
213
214     public void stop() {
215         if (canvasUpdateTimerTask != null) {
216             canvasUpdateTimerTask.cancel();
217             canvasUpdateTimerTask = null;
218         }
219
220         if (canvasUpdateTimer != null) {
221             canvasUpdateTimer.cancel();
222             canvasUpdateTimer = null;
223         }
224     }
225
226     /**
227      * This method is executed by periodic timer task, in frequency according to
228      * defined frame rate.
229      * <p>
230      * It tells view to update itself. View can decide if actual re-rendering of
231      * graphics is needed.
232      */
233     public void updateView() {
234         if (renderingContext != null) {
235             renderingContext.mouseClick = null;
236             renderingContext.clickedItem = null;
237         }
238
239         // compute time passed since last view update
240         final long currentTime = System.currentTimeMillis();
241
242         if (lastUpdateMillis == 0) {
243             lastUpdateMillis = currentTime;
244             return;
245         }
246
247         final int millisecondsPassedSinceLastUpdate = (int) (currentTime - lastUpdateMillis);
248         lastUpdateMillis = currentTime;
249
250         // notify update listeners
251         boolean reRenderFrame = false;
252
253         for (final ViewRenderListener listener : viewRenderListeners)
254             if (listener.beforeRender(this, millisecondsPassedSinceLastUpdate))
255                 reRenderFrame = true;
256
257         // abort rendering if window size is invalid
258         if ((getWidth() <= 0) || (getHeight() <= 0))
259             return;
260
261         if (viewRepaintNeeded) {
262             viewRepaintNeeded = false;
263             reRenderFrame = true;
264         }
265
266         if (reRenderFrame) {
267             renderFrame();
268             handleDetectedComponentMouseEvents();
269         }
270     }
271 }