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 View extends JPanel implements ComponentListener {
24 private static final long serialVersionUID = 1683277888885045387L;
25 private final List<ViewListener> viewListeners = new ArrayList<>();
26 private final List<ViewUpdateListener> viewUpdateListeners = new ArrayList<>();
27 private final ViewContext context = new ViewContext(this);
29 * Last time this view was updated
31 long lastUpdateMillis = 0;
32 private Timer canvasUpdateTimer;
33 private ViewUpdateTimerTask canvasUpdateTimerTask;
34 private RenderingContext renderingContext = null;
35 private MouseInteractionController currentMouseOverComponent;
37 * Currently target FPS for this view. It might change at runtime.
39 private int targetFramerate = 30;
40 private boolean repaintDuringNextViewUpdate = true;
43 viewUpdateListeners.add(context.getAvatar());
45 // initialize input tracker
46 context.getUserInputTracker().bind(this);
47 viewUpdateListeners.add(context.getUserInputTracker());
49 initializePanelLayout();
51 setFrameRate(targetFramerate);
53 addComponentListener(this);
56 public void addViewListener(final ViewListener listener) {
57 getViewListeners().add(listener);
60 public void addViewUpdateListener(final ViewUpdateListener listener) {
61 viewUpdateListeners.add(listener);
65 public void componentHidden(final ComponentEvent e) {
70 public void componentMoved(final ComponentEvent e) {
75 public void componentResized(final ComponentEvent e) {
76 repaintDuringNextViewUpdate = true;
80 public void componentShown(final ComponentEvent e) {
81 repaintDuringNextViewUpdate = true;
84 public ViewContext getContext() {
89 public Dimension getMaximumSize() {
90 return getPreferredSize();
94 public Dimension getMinimumSize() {
95 return getPreferredSize();
99 public java.awt.Dimension getPreferredSize() {
100 return new java.awt.Dimension(640, 480);
103 public RenderingContext getRenderBuffer() {
104 return renderingContext;
107 public RenderingContext getRenderingContext() {
108 return renderingContext;
111 public List<ViewListener> getViewListeners() {
112 return viewListeners;
115 private void handleDetectedComponentMouseEvents() {
116 if (renderingContext.clickedItem != null) {
117 if (renderingContext.mouseClick.button == 0) {
119 if (currentMouseOverComponent == null) {
120 currentMouseOverComponent = renderingContext.clickedItem;
121 currentMouseOverComponent.mouseEntered();
122 repaintDuringNextViewUpdate = true;
123 } else if (currentMouseOverComponent != renderingContext.clickedItem) {
124 currentMouseOverComponent.mouseExited();
125 currentMouseOverComponent = renderingContext.clickedItem;
126 currentMouseOverComponent.mouseEntered();
127 repaintDuringNextViewUpdate = true;
131 renderingContext.clickedItem.mouseClicked();
132 repaintDuringNextViewUpdate = true;
134 } else if (currentMouseOverComponent != null) {
135 currentMouseOverComponent.mouseExited();
136 repaintDuringNextViewUpdate = true;
137 currentMouseOverComponent = null;
141 private void initializePanelLayout() {
142 setFocusCycleRoot(true);
145 setDoubleBuffered(false);
147 requestFocusInWindow();
150 public void renderFrame() {
151 // build new render buffer if needed, this happens when window was just
152 // created or resized
153 if ((renderingContext == null)
154 || (renderingContext.width != getWidth())
155 || (renderingContext.height != getHeight()))
156 renderingContext = new RenderingContext(getWidth(), getHeight());
158 // clear drawing area
160 renderingContext.graphics.setColor(Color.BLACK);
161 renderingContext.graphics.fillRect(0, 0, getWidth(), getHeight());
164 // paint root geometry collection to the offscreen render buffer
165 context.getRootShapeCollection().paint(context, renderingContext);
167 // draw rendered offscreen image to visible screen
168 final Graphics graphics = getGraphics();
169 if (graphics != null)
170 graphics.drawImage(renderingContext.bufferedImage, 0, 0, null);
174 * Calling this methods tells 3D engine that current 3D view needs to be
175 * repainted on first opportunity.
177 public void repaintDuringNextViewUpdate() {
178 repaintDuringNextViewUpdate = true;
181 public void setFrameRate(final int frameRate) {
182 if (canvasUpdateTimerTask != null) {
183 canvasUpdateTimerTask.cancel();
184 canvasUpdateTimerTask = null;
186 canvasUpdateTimer.cancel();
187 canvasUpdateTimer = null;
190 targetFramerate = frameRate;
193 canvasUpdateTimer = new Timer();
194 canvasUpdateTimerTask = new ViewUpdateTimerTask(this);
196 canvasUpdateTimer.schedule(canvasUpdateTimerTask, 0,
202 if (canvasUpdateTimerTask != null) {
203 canvasUpdateTimerTask.cancel();
204 canvasUpdateTimerTask = null;
207 if (canvasUpdateTimer != null) {
208 canvasUpdateTimer.cancel();
209 canvasUpdateTimer = null;
214 * This method is executed by periodic timer task, in frequency according to
215 * defined frame rate.
217 * It tells view to update itself. View can decide if actual re-rendering of
218 * graphics is needed.
220 public void updateView() {
221 if (renderingContext != null){
222 renderingContext.mouseClick = null;
223 renderingContext.clickedItem = null;
226 // compute time passed since last view update
227 final long currentTime = System.currentTimeMillis();
229 if (lastUpdateMillis == 0) {
230 lastUpdateMillis = currentTime;
234 final int millisecondsPassedSinceLastUpdate = (int) (currentTime - lastUpdateMillis);
235 lastUpdateMillis = currentTime;
237 // notify update listeners
238 boolean reRenderFrame = false;
240 for (final ViewUpdateListener listener : viewUpdateListeners)
241 if (listener.beforeViewUpdate(context,
242 millisecondsPassedSinceLastUpdate))
243 reRenderFrame = true;
245 // abort rendering if window size is invalid
246 if ((getWidth() <= 0) || (getHeight() <= 0))
249 if (repaintDuringNextViewUpdate) {
250 repaintDuringNextViewUpdate = false;
251 reRenderFrame = true;
256 handleDetectedComponentMouseEvents();