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.MouseInteractionController;
16 import java.awt.event.ComponentEvent;
17 import java.awt.event.ComponentListener;
18 import java.util.ArrayList;
19 import java.util.List;
20 import java.util.Timer;
22 public class ViewPanel extends JPanel implements ComponentListener {
24 private static final long serialVersionUID = 1683277888885045387L;
25 private final List<ViewRenderListener> viewRenderListeners = new ArrayList<>();
26 private final ViewContext context = new ViewContext(this);
28 * Last time this view was updated.
30 private long lastUpdateMillis = 0;
31 private Timer canvasUpdateTimer;
32 private ViewUpdateTimerTask canvasUpdateTimerTask;
33 private RenderingContext renderingContext = null;
36 * UI component that mouse is currently hovering over.
38 private MouseInteractionController currentMouseOverComponent;
41 * Currently target FPS for this view. It can be changed at runtime. Also when nothing
42 * changes in the view, then frames are not really repainted.
44 private int targetFPS = 30;
47 * Set to true if it is known than next frame reeds to be painted. Flag is cleared
48 * immediately after frame got updated.
50 private boolean viewRepaintNeeded = true;
53 viewRenderListeners.add(context.getAvatar());
55 // initialize input tracker
56 context.getUserInputTracker().bind(this);
57 viewRenderListeners.add(context.getUserInputTracker());
59 initializePanelLayout();
61 setFrameRate(targetFPS);
63 addComponentListener(this);
66 public void addViewUpdateListener(final ViewRenderListener listener) {
67 viewRenderListeners.add(listener);
71 public void componentHidden(final ComponentEvent e) {
76 public void componentMoved(final ComponentEvent e) {
81 public void componentResized(final ComponentEvent e) {
82 viewRepaintNeeded = true;
86 public void componentShown(final ComponentEvent e) {
87 viewRepaintNeeded = true;
90 public ViewContext getContext() {
95 public Dimension getMaximumSize() {
96 return getPreferredSize();
100 public Dimension getMinimumSize() {
101 return getPreferredSize();
105 public java.awt.Dimension getPreferredSize() {
106 return new java.awt.Dimension(640, 480);
109 public RenderingContext getRenderBuffer() {
110 return renderingContext;
113 public RenderingContext getRenderingContext() {
114 return renderingContext;
117 private void handleDetectedComponentMouseEvents() {
118 if (renderingContext.clickedItem != null) {
119 if (renderingContext.mouseClick.button == 0) {
121 if (currentMouseOverComponent == null) {
122 currentMouseOverComponent = renderingContext.clickedItem;
123 currentMouseOverComponent.mouseEntered();
124 viewRepaintNeeded = true;
125 } else if (currentMouseOverComponent != renderingContext.clickedItem) {
126 currentMouseOverComponent.mouseExited();
127 currentMouseOverComponent = renderingContext.clickedItem;
128 currentMouseOverComponent.mouseEntered();
129 viewRepaintNeeded = true;
133 renderingContext.clickedItem.mouseClicked();
134 viewRepaintNeeded = true;
136 } else if (currentMouseOverComponent != null) {
137 currentMouseOverComponent.mouseExited();
138 viewRepaintNeeded = true;
139 currentMouseOverComponent = null;
143 private void initializePanelLayout() {
144 setFocusCycleRoot(true);
147 setDoubleBuffered(false);
149 requestFocusInWindow();
152 public void renderFrame() {
153 // build new render buffer if needed, this happens when window was just
154 // created or resized
155 if ((renderingContext == null)
156 || (renderingContext.width != getWidth())
157 || (renderingContext.height != getHeight()))
158 renderingContext = new RenderingContext(getWidth(), getHeight());
160 // clear drawing area
162 renderingContext.graphics.setColor(Color.BLACK);
163 renderingContext.graphics.fillRect(0, 0, getWidth(), getHeight());
166 // paint root geometry collection to the offscreen render buffer
167 context.getRootShapeCollection().paint(context, renderingContext);
169 // draw rendered offscreen image to visible screen
170 final Graphics graphics = getGraphics();
171 if (graphics != null)
172 graphics.drawImage(renderingContext.bufferedImage, 0, 0, null);
176 * Calling this methods tells 3D engine that current 3D view needs to be
177 * repainted on first opportunity.
179 public void repaintDuringNextViewUpdate() {
180 viewRepaintNeeded = true;
183 public void setFrameRate(final int frameRate) {
184 if (canvasUpdateTimerTask != null) {
185 canvasUpdateTimerTask.cancel();
186 canvasUpdateTimerTask = null;
188 canvasUpdateTimer.cancel();
189 canvasUpdateTimer = null;
192 targetFPS = frameRate;
195 canvasUpdateTimer = new Timer();
196 canvasUpdateTimerTask = new ViewUpdateTimerTask(this);
198 canvasUpdateTimer.schedule(canvasUpdateTimerTask, 0,
204 if (canvasUpdateTimerTask != null) {
205 canvasUpdateTimerTask.cancel();
206 canvasUpdateTimerTask = null;
209 if (canvasUpdateTimer != null) {
210 canvasUpdateTimer.cancel();
211 canvasUpdateTimer = null;
216 * This method is executed by periodic timer task, in frequency according to
217 * defined frame rate.
219 * It tells view to update itself. View can decide if actual re-rendering of
220 * graphics is needed.
222 public void updateView() {
223 if (renderingContext != null){
224 renderingContext.mouseClick = null;
225 renderingContext.clickedItem = null;
228 // compute time passed since last view update
229 final long currentTime = System.currentTimeMillis();
231 if (lastUpdateMillis == 0) {
232 lastUpdateMillis = currentTime;
236 final int millisecondsPassedSinceLastUpdate = (int) (currentTime - lastUpdateMillis);
237 lastUpdateMillis = currentTime;
239 // notify update listeners
240 boolean reRenderFrame = false;
242 for (final ViewRenderListener listener : viewRenderListeners)
243 if (listener.beforeRender(context,
244 millisecondsPassedSinceLastUpdate))
245 reRenderFrame = true;
247 // abort rendering if window size is invalid
248 if ((getWidth() <= 0) || (getHeight() <= 0))
251 if (viewRepaintNeeded) {
252 viewRepaintNeeded = false;
253 reRenderFrame = true;
258 handleDetectedComponentMouseEvents();