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