Passed
Push — master ( 4033fa...c1f31b )
by Seth
03:35
created

repaintOnEDT()   A

Complexity

Conditions 2

Size

Total Lines 5
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 5
cc 2
rs 10
1
package org.gannacademy.cdf.graphics.ui;
2
3
import javax.swing.*;
4
import java.awt.*;
5
6
/**
7
 * <p>Extendable window controller for {@link DrawingPanel} objects</p>
8
 *
9
 * <p>Extend this class to create your own drawings. Override the abstract method {@link #setup()} to make your drawing
10
 * instructions. The recommended approach is to create a new Java class file and to type exactly this (replacing
11
 * {@code MyDrawingApp} with your preferred name, of course):</p>
12
 *
13
 * <pre>
14
 *   public class MyDrawingApp extends AppWindow {}
15
 * </pre>
16
 *
17
 * <p>This will generate a number of errors in your IDE. Go ahead and let the IDE fix them automagically. The IDE will
18
 * import the AppWindow class, and then create a method stub for {@code setup()}, resulting in something like this:</p>
19
 *
20
 * <pre>
21
 *   import org.gannacademy.cdf.graphics.ui.*;
22
 *
23
 *   public class MyDrawingApp extends AppWindow {
24
 *     &#064;Override
25
 *     public void setup() {
26
 *
27
 *     }
28
 *   }
29
 * </pre>
30
 *
31
 * <p>Instantiate a {@code MyDrawingApp} object to create a window containing your drawing.</p>
32
 *
33
 * <p>Additional overrideable methods include {@link #loop()} and {@link #done()}. {@code loop()} will be
34
 * called repeatedly (inside a loop!) while {@code done()} returns {@code false} &mdash; when {@code done()} returns
35
 * {@code true}, the control loop ends (although the application will continue running until the window is closed).</p>
36
 *
37
 * @author <a href="https://github.com/gann-cdf/graphics/issues">Seth Battis</a>
38
 */
39
public abstract class AppWindow extends JFrame {
40
    public static final String DEFAULT_TITLE = "Gann Graphics App";
41
    public static final boolean DEFAULT_FULLSCREEN = false;
42
    public static final long DEFAULT_REPAINT_DELAY = 10;
43
44
    private DrawingPanel drawingPanel;
45
    private boolean threadStarted = false;
0 ignored issues
show
Unused Code introduced by
Consider removing the unused private field threadStarted.
Loading history...
46
47
    /**
48
     * Construct a new {@link DrawingPanel} in a window with the default title
49
     */
50
    public AppWindow() {
51
        this(DEFAULT_TITLE, DEFAULT_FULLSCREEN, DEFAULT_REPAINT_DELAY);
52
    }
53
54
    /**
55
     * Construct a new {@link DrawingPanel} in a window with a custom name
56
     *
57
     * @param title The title of the window (shown in the draggable titlebar)
58
     */
59
    public AppWindow(String title) {
60
        this(title, DEFAULT_FULLSCREEN, DEFAULT_REPAINT_DELAY);
61
    }
62
63
    public AppWindow(String title, boolean isFullScreen) {
64
        this(title, isFullScreen, DEFAULT_REPAINT_DELAY);
65
    }
66
67
    /**
68
     * <p>Construct a new {@link DrawingPanel} in a window</p>
69
     *
70
     * <p>Full screen windows default to the main display.</p>
71
     *
72
     * @param title        for the window
73
     * @param isFullScreen whether or not the window is framed or full screen
74
     */
75
    public AppWindow(String title, boolean isFullScreen, long repaintDelay) {
76
        super(title);
77
        AppWindow self = this;
78
        SwingUtilities.invokeLater(new Runnable() {
79
            @Override
80
            public void run() {
81
                setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
82
                drawingPanel = new DrawingPanel();
83
                add(drawingPanel);
84
                if (isFullScreen) {
85
                    setExtendedState(JFrame.MAXIMIZED_BOTH);
86
                    setUndecorated(true);
87
                    DisplayMode display = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()[0].getDisplayMode();
88
                    setSize(display.getWidth(), display.getHeight());
89
                }
90
                pack();
91
                setup();
92
                setLocationRelativeTo(null);
93
                setVisible(true);
94
                (new Repainter(self, repaintDelay)).execute();
95
                (new Animator(self)).execute();
96
            }
97
        });
98
    }
99
    
100
    private void repaintOnEDT() {
101
        SwingUtilities.invokeLater(new Runnable() {
102
            @Override
103
            public void run() {
104
                repaint();
105
            }
106
        });
107
    }
108
109
    private static class Animator extends SwingWorker<Void,Void> {
110
111
        AppWindow window;
112
113
        Animator(AppWindow window) {
114
            this.window = window;
115
        }
116
117
        @Override
118
        protected Void doInBackground() throws Exception {
119
            while(!window.done()) {
120
                window.loop();
121
                window.repaintOnEDT();
122
            }
123
            return null;
124
        }
125
    }
126
127
    private static class Repainter extends SwingWorker<Void,Void> {
128
        AppWindow window;
129
        long delay;
130
131
        Repainter(AppWindow window, long delay) {
132
            this.window = window;
133
            this.delay = Math.max(1, delay);
134
        }
135
136
        @Override
137
        protected Void doInBackground() throws Exception {
138
            while (!window.done()) {
139
                window.repaintOnEDT();
140
                Thread.sleep(delay);
141
            }
142
            return null;
143
        }
144
    }
145
    /**
146
     * Set the size of the window
147
     *
148
     * @param width  in pixels
149
     * @param height in pixels
150
     */
151
    public void setSize(int width, int height) {
152
        drawingPanel.setPreferredSize(new Dimension(width, height));
153
        pack();
154
        setLocationRelativeTo(null);
155
    }
156
157
    /**
158
     * Pause the control loop
159
     *
160
     * @param delay in milliseconds
161
     */
162
    protected void sleep(long delay) {
163
        try {
164
            Thread.sleep(delay);
165
        } catch (InterruptedException e) {
0 ignored issues
show
introduced by
Either re-interrupt this method or rethrow the "InterruptedException".
Loading history...
166
            e.printStackTrace();
0 ignored issues
show
Best Practice introduced by
Throwable.printStackTrace writes to the console which might not be available at runtime. Using a logger is preferred.
Loading history...
167
        }
168
    }
169
170
    /**
171
     * <p>Override this method to define your drawing</p>
172
     *
173
     * <p>This method is called before the window is made visible. All drawing components that need to be rendered at the
174
     * start of the program run should be defined in this method.</p>
175
     */
176
    protected abstract void setup();
177
178
    /**
179
     * <p>Override this method to update drawings in the control loop</p>
180
     *
181
     * <p>This method represents a <i>single</i> iteration of the control loop. Care should be taken to avoid writing
182
     * blocking code in this method (e.g. {@code for} loops designed to animate a single object) &mdash; rather, drawing
183
     * components should be adjusted based on the values of instance variables, whose values are changed incrementally
184
     * in each call to this method. For example:</p>
185
     *
186
     * <pre>
187
     *   private double x, y;
188
     *   private Rectangle r;
189
     *
190
     *   // setup() and other methods&hellip;
191
     *
192
     *   &#064;Override
193
     *   public void loop() {
194
     *     x += 1;
195
     *     y = 20 * Math.sin(x * 20);
196
     *     r.moveTo(x, y);
197
     *   }
198
     * </pre>
199
     *
200
     * <p>The above code animates a rectangle moving elegantly in a sine wave (scaled up 20&times;)across the window.</p>
201
     */
202
    protected void loop() {
203
    }
204
205
    /**
206
     * <p>Override this method to set the condition that ends the control loop</p>
207
     *
208
     * <p>By default, this method will always return {@code false}, so that the control loop runs indefinitely.</p>
209
     *
210
     * @return {@code true} if the control loop should end, {@code false} otherwise
211
     */
212
    protected boolean done() {
213
        return false;
214
    }
215
216
    /**
217
     * <p>Access the {@link DrawingPanel} object contained in this window</p>
218
     *
219
     * <p>All drawing instructions require a drawing panel on which to execute them. This is the default drawing panel.</p>
220
     *
221
     * @return The drawing panel in this window
222
     */
223
    public DrawingPanel getDrawingPanel() {
224
        return drawingPanel;
225
    }
226
}
227