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 = new RenderingContext(1, 1, 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(),
159 // clear drawing area
161 renderingContext.graphics.setColor(Color.BLACK);
162 renderingContext.graphics.fillRect(0, 0, getWidth(), getHeight());
165 // paint root geometry collection to the offscreen render buffer
166 context.getRootShapeCollection().paint(context, renderingContext);
168 // draw rendered offscreen image to visible screen
169 final Graphics graphics = getGraphics();
170 if (graphics != null)
171 graphics.drawImage(renderingContext.bufferedImage, 0, 0, null);
175 * Calling this methods tells 3D engine that current 3D view needs to be
176 * repainted on first opportunity.
178 public void repaintDuringNextViewUpdate() {
179 repaintDuringNextViewUpdate = true;
182 public void setFrameRate(final int frameRate) {
183 if (canvasUpdateTimerTask != null) {
184 canvasUpdateTimerTask.cancel();
185 canvasUpdateTimerTask = null;
187 canvasUpdateTimer.cancel();
188 canvasUpdateTimer = null;
191 targetFramerate = frameRate;
194 canvasUpdateTimer = new Timer();
195 canvasUpdateTimerTask = new ViewUpdateTimerTask(this);
197 canvasUpdateTimer.schedule(canvasUpdateTimerTask, 0,
203 if (canvasUpdateTimerTask != null) {
204 canvasUpdateTimerTask.cancel();
205 canvasUpdateTimerTask = null;
208 if (canvasUpdateTimer != null) {
209 canvasUpdateTimer.cancel();
210 canvasUpdateTimer = null;
215 * This method is executed by periodic timer task, in frequency according to
216 * defined frame rate.
218 * It tells view to update itself. View can decide if actual re-rendering of
219 * graphics is needed.
221 public void updateView() {
222 renderingContext.mouseClick = null;
223 renderingContext.clickedItem = null;
225 // compute time passed since last view update
226 final long currentTime = System.currentTimeMillis();
228 if (lastUpdateMillis == 0) {
229 lastUpdateMillis = currentTime;
233 final int millisecondsPassedSinceLastUpdate = (int) (currentTime - lastUpdateMillis);
234 lastUpdateMillis = currentTime;
236 // notify update listeners
237 boolean rerenderView = false;
239 for (final ViewUpdateListener listener : viewUpdateListeners)
240 if (listener.beforeViewUpdate(context,
241 millisecondsPassedSinceLastUpdate))
244 // abort rendering if window size is invalid
245 if ((getWidth() <= 0) || (getHeight() <= 0))
248 if (repaintDuringNextViewUpdate) {
249 repaintDuringNextViewUpdate = false;
255 handleDetectedComponentMouseEvents();