source: trunk/src/org/expeditee/gio/javafx/JavaFXGraphicsManager.java@ 1097

Last change on this file since 1097 was 1097, checked in by davidb, 6 years ago

Newly structured files from Corey's work on logic/graphics separation

File size: 18.5 KB
Line 
1package org.expeditee.gio.javafx;
2
3import java.util.HashSet;
4import java.util.Set;
5import java.util.Stack;
6
7import org.expeditee.Util;
8import org.expeditee.core.Clip;
9import org.expeditee.core.Colour;
10import org.expeditee.core.Cursor;
11import org.expeditee.core.Dimension;
12import org.expeditee.core.Fill;
13import org.expeditee.core.Font;
14import org.expeditee.core.GradientFill;
15import org.expeditee.core.Image;
16import org.expeditee.core.Point;
17import org.expeditee.core.Stroke;
18import org.expeditee.core.TextLayout;
19import org.expeditee.core.bounds.AxisAlignedBoxBounds;
20import org.expeditee.core.bounds.PolygonBounds;
21import org.expeditee.gio.GraphicsManager;
22import org.expeditee.gio.GraphicsSurfaceStack;
23
24import javafx.application.Application;
25import javafx.application.Platform;
26import javafx.geometry.Bounds;
27import javafx.scene.Group;
28import javafx.scene.Scene;
29import javafx.scene.SnapshotParameters;
30import javafx.scene.canvas.Canvas;
31import javafx.scene.canvas.GraphicsContext;
32import javafx.scene.effect.BlendMode;
33import javafx.scene.input.KeyCombination;
34import javafx.scene.paint.Color;
35import javafx.stage.Screen;
36import javafx.stage.Stage;
37
38public class JavaFXGraphicsManager extends GraphicsManager {
39
40 /** Singleton instance. */
41 private static JavaFXGraphicsManager _instance;
42
43 /** Singleton instantiator. */
44 public static JavaFXGraphicsManager getInstance()
45 {
46 if (_instance == null) _instance = new JavaFXGraphicsManager();
47
48 return _instance;
49 }
50
51 /** The largest size a window icon can be. */
52 private static final int MAX_ICON_SIZE = 256;
53 /** The smallest size a window icon can be. */
54 private static final int MIN_ICON_SIZE = 8;
55
56 /** The startup thread for the JavaFX application. */
57 private static Thread _jfxAppThread;
58
59 /** The stack of drawing surfaces. */
60 private GraphicsSurfaceStack<Canvas> _surfaceStack;
61
62 /** A stack for surface clips to aid in clip clearing. */
63 private Clip _currentClip;
64 private Stack<Clip> _clipStack;
65
66 private JavaFXGraphicsManager()
67 {
68 // Create the clip stack
69 _currentClip = null;
70 _clipStack = new Stack<Clip>();
71
72 // Set the ExpediteeApplication manager
73 ExpediteeApplication.manager = this;
74
75 // Start up the JavaFX thread on another thread as it blocks until program exit
76 _jfxAppThread = new Thread("_jfxAppThread") {
77 @Override
78 public void run()
79 {
80 Application.launch(ExpediteeApplication.class);
81 }
82 };
83
84 // Block the main thread until the JavaFX thread is ready
85 synchronized(_jfxAppThread) {
86 _jfxAppThread.start();
87 try {
88 _jfxAppThread.wait();
89 } catch (InterruptedException e) {
90 System.err.println("Error while initialising GraphicsManager. Aborting...");
91 e.printStackTrace(System.err);
92 System.exit(1);
93 }
94 }
95
96 // Create the surface stack
97 _surfaceStack = new GraphicsSurfaceStack<Canvas>() {
98 @Override
99 public Canvas getSurfaceFromImage(Image image)
100 {
101 Canvas canvas = new Canvas(image.getWidth(), image.getHeight());
102 canvas.getGraphicsContext2D().drawImage(JavaFXMiscManager.getIfUsingJavaFXImageManager().getInternalImage(image), 0, 0);
103 return canvas;
104 }
105
106 @Override
107 public void setSurfaceClip(Canvas surface, Clip clip)
108 {
109 if (surface == null) return;
110
111 if (clip != null && clip.isFullyClipped()) return;
112
113 GraphicsContext g = surface.getGraphicsContext2D();
114
115 // Restore the pre-clipped state
116 g.restore();
117
118 // If we are clearing the clip, we are done
119 if (clip == null || clip.getBounds() == null) return;
120
121 // Re-save the pre-clipped state
122 g.save();
123
124 // Set the clip
125 g.beginPath();
126 AxisAlignedBoxBounds clipBounds = clip.getBounds();
127 g.rect(clipBounds.getMinX(), clipBounds.getMinY(), clipBounds.getWidth(), clipBounds.getHeight());
128 g.closePath();
129 g.clip();
130 }
131 };
132 _surfaceStack.setRootSurface(ExpediteeApplication.theCanvas);
133
134 // Disable JavaFX's fullscreen exiting system
135 ExpediteeApplication.theStage.setFullScreenExitHint("");
136 ExpediteeApplication.theStage.setFullScreenExitKeyCombination(KeyCombination.NO_MATCH);
137
138 // Block until the window is ready
139 JavaFXMiscManager.waitUntilPropertyEquals(ExpediteeApplication.theStage.showingProperty(), true);
140
141 }
142
143 @Override
144 protected GraphicsSurfaceStack<?> getGraphicsSurfaceStack()
145 {
146 return _surfaceStack;
147 }
148
149 @Override
150 public Dimension getScreenSize()
151 {
152 return JavaFXConversions.fromJavaFXRectangle2D(Screen.getPrimary().getBounds()).getSize();
153 }
154
155 @Override
156 public Point getWindowLocation()
157 {
158 return translateCanvasToScreen(new Point(0, 0));
159 }
160
161 @Override
162 public void setWindowLocation(Point p)
163 {
164 if (p == null) return;
165
166 Point currentCanvasPos = getWindowLocation();
167 Point currentStagePos = new Point((int) ExpediteeApplication.theStage.getX(), (int) ExpediteeApplication.theStage.getY());
168
169 Point delta = Point.difference(currentStagePos, currentCanvasPos);
170
171 Point newStagePos = p.clone().add(delta);
172
173 ExpediteeApplication.theStage.setX(newStagePos.x);
174 ExpediteeApplication.theStage.setY(newStagePos.y);
175 }
176
177 @Override
178 public Dimension getWindowSize()
179 {
180 return new Dimension((int) ExpediteeApplication.theScene.getWidth(), (int) ExpediteeApplication.theScene.getHeight());
181 }
182
183 @Override
184 public void setWindowSize(Dimension d)
185 {
186 if (d == null) return;
187
188 Dimension decorationDimensions = getDecorationDimensions();
189
190 ExpediteeApplication.theStage.setWidth(d.width + decorationDimensions.width);
191 ExpediteeApplication.theStage.setHeight(d.height + decorationDimensions.height);
192 ExpediteeApplication.theCanvas.setWidth(d.width);
193 ExpediteeApplication.theCanvas.setHeight(d.height);
194
195 }
196
197 @Override
198 public void requestFocus()
199 {
200 ExpediteeApplication.theStage.requestFocus();
201 }
202
203 @Override
204 public void setWindowIcon(Image image)
205 {
206 // TODO: Work out why this doesn't work. cts16
207 JavaFXImageManager imageManager = JavaFXMiscManager.getIfUsingJavaFXImageManager();
208
209 if (imageManager == null || !imageManager.isImageValid(image)) return;
210
211 ExpediteeApplication.theStage.getIcons().clear();
212
213 ExpediteeApplication.theStage.getIcons().add(imageManager.getInternalImage(image));
214
215 /*
216 Set<Image> iconSet = createIconSet(image);
217
218 if (iconSet != null) {
219 for (Image icon : iconSet) ExpediteeApplication.theStage.getIcons().add(imageManager.getInternalImage(icon));
220 }
221 */
222 }
223
224 @Override
225 public void setWindowTitle(String title)
226 {
227 if (title == null) return;
228
229 ExpediteeApplication.theStage.setTitle(title);
230 }
231
232 private Point _preFullscreenWindowLocation = null;
233 private Dimension _preFullscreenWindowSize = null;
234
235 @Override
236 public boolean canGoFullscreen()
237 {
238 // TODO: Check if this is always true. cts16
239 return true;
240 }
241
242 @Override
243 public boolean isFullscreen()
244 {
245 return ExpediteeApplication.theStage.isFullScreen();
246 }
247
248 @Override
249 public void goFullscreen()
250 {
251 _preFullscreenWindowLocation = getWindowLocation();
252 _preFullscreenWindowSize = getWindowSize();
253 ExpediteeApplication.theStage.setFullScreen(true);
254 }
255
256 @Override
257 public void exitFullscreen()
258 {
259 ExpediteeApplication.theStage.setFullScreen(false);
260 setWindowLocation(_preFullscreenWindowLocation);
261 setWindowSize(_preFullscreenWindowSize);
262 }
263
264 @Override
265 public void pushDrawingSurface(Image image)
266 {
267 _surfaceStack.push(image);
268 _clipStack.push(_currentClip);
269 _currentClip = null;
270 }
271
272 @Override
273 public Image popDrawingSurface()
274 {
275 // Get the current canvas
276 Canvas canvas = _surfaceStack.getCurrentSurface();
277
278 // Get its corresponding image
279 Image image = _surfaceStack.pop();
280
281 // Check we haven't hit the bottom of the stack
282 if (image == null) return null;
283
284 // Write the canvas contents back to the image
285 SnapshotParameters params = new SnapshotParameters();
286 params.setFill(Color.TRANSPARENT);
287 canvas.snapshot(params, JavaFXMiscManager.getIfUsingJavaFXImageManager().getInternalImage(image));
288
289 // Reload the next surface's clip
290 _currentClip = _clipStack.pop();
291
292 return image;
293 }
294
295 @Override
296 public void setCompositeAlpha(float alpha)
297 {
298 GraphicsContext g = _surfaceStack.getCurrentSurface().getGraphicsContext2D();
299
300 // Make sure we are setting to the base of the stack
301 g.restore();
302
303 // Set the blend alpha
304 g.setGlobalBlendMode(BlendMode.SRC_OVER);
305 g.setGlobalAlpha(alpha);
306
307 // Re-clip
308 _surfaceStack.ensureClip();
309 }
310
311 @Override
312 public void setAntialiasing(boolean on)
313 {
314 // TODO: Implement. cts16
315 }
316
317 @Override
318 public void setFont(Font font)
319 {
320 GraphicsContext g = _surfaceStack.getCurrentSurface().getGraphicsContext2D();
321
322 g.restore();
323
324 g.setFont(JavaFXMiscManager.getIfUsingJavaFXFontManager().getInternalFont(font));
325
326 // Re-clip
327 _surfaceStack.ensureClip();
328 }
329
330 @Override
331 public void drawImage(Image image, Point topLeft, Dimension size, double angle, Point cropTopLeft, Dimension cropSize)
332 {
333 // Can't draw nothing
334 if (image == null) return;
335
336 // Can't draw nowhere
337 if (topLeft == null) return;
338
339 // If the cropped area is degenerate, abort
340 if (cropSize != null && (cropSize.width <= 0 || cropSize.height <= 0)) return;
341
342 // If the crop area is the entire image, pretend we are not cropping
343 if (Point.ORIGIN.equals(cropTopLeft) && image.getSize().equals(cropSize)) {
344 cropTopLeft = null;
345 cropSize = null;
346 }
347
348 JavaFXImageManager manager = JavaFXMiscManager.getIfUsingJavaFXImageManager();
349
350 // Default is the entire image
351 Image croppedImage = image;
352
353 // If we are cropping, do this now into a temporary image
354 if (cropTopLeft != null && cropSize != null) {
355 croppedImage = manager.createImage(cropSize.width, cropSize.height);
356 manager.getInternalImage(croppedImage).getPixelWriter().setPixels(0, 0, cropSize.width, cropSize.height, manager.getInternalImage(image).getPixelReader(), cropTopLeft.x, cropTopLeft.y);
357 }
358
359 GraphicsContext g = _surfaceStack.getCurrentSurface().getGraphicsContext2D();
360
361 g.save();
362 if (size != null) g.scale(((double) size.width) / croppedImage.getWidth(), ((double) size.height) / croppedImage.getHeight());
363 if (angle != 0.0) g.rotate(angle);
364 if (topLeft != null) g.translate(topLeft.x, topLeft.y);
365 g.drawImage(JavaFXMiscManager.getIfUsingJavaFXImageManager().getInternalImage(croppedImage), 0, 0);
366 g.restore();
367 }
368
369 @Override
370 public void drawRectangle(Point topLeft, Dimension size, double angle, Fill fill, Colour borderColour, Stroke borderStroke, Dimension cornerRadius)
371 {
372 // Can't draw nowhere
373 if (topLeft == null) return;
374
375 // Can't draw nothing
376 if (size == null) return;
377
378 // If not filled and has no border, why bother?
379 if (fill == null && (borderColour == null || borderStroke == null)) return;
380
381 GraphicsContext g = _surfaceStack.getCurrentSurface().getGraphicsContext2D();
382
383 g.save();
384
385 Point centre = new Point(topLeft.x + size.width / 2, topLeft.y + size.height / 2);
386 g.translate(-centre.x, -centre.y);
387 if (angle != 0.0) g.rotate(angle);
388 g.translate(centre.x, centre.y);
389
390 if (fill != null) {
391 if (fill instanceof GradientFill) {
392 g.setFill(JavaFXConversions.toJavaFXLinearGradient((GradientFill) fill));
393 } else {
394 g.setFill(JavaFXConversions.toJavaFXColor(fill.getColour()));
395 }
396
397 if (cornerRadius == null) {
398 g.fillRect(topLeft.x, topLeft.y, size.width, size.height);
399 } else {
400 g.fillRoundRect(topLeft.x, topLeft.y, size.width, size.height, cornerRadius.width, cornerRadius.height);
401 }
402 }
403
404 if (borderColour != null && borderStroke != null) {
405 g.setStroke(JavaFXConversions.toJavaFXColor(borderColour));
406 setStroke(g, borderStroke);
407
408 if (cornerRadius == null) {
409 g.strokeRect(topLeft.x, topLeft.y, size.width, size.height);
410 } else {
411 g.strokeRoundRect(topLeft.x, topLeft.y, size.width, size.height, cornerRadius.width, cornerRadius.height);
412 }
413 }
414
415 g.restore();
416 }
417
418 @Override
419 public void drawOval(Point centre, Dimension diameters, double angle, Fill fill, Colour borderColour, Stroke borderStroke)
420 {
421 // Can't draw nowhere
422 if (centre == null) return;
423
424 // Can't draw nothing
425 if (diameters == null) return;
426
427 // If not filled and has no border, why bother?
428 if (fill == null && (borderColour == null || borderStroke == null)) return;
429
430 GraphicsContext g = _surfaceStack.getCurrentSurface().getGraphicsContext2D();
431
432 g.save();
433
434 if (angle != 0.0) {
435 g.translate(-centre.x, -centre.y);
436 g.rotate(angle);
437 g.translate(centre.x, centre.y);
438 }
439
440 if (fill != null) {
441 if (fill instanceof GradientFill) {
442 g.setFill(JavaFXConversions.toJavaFXLinearGradient((GradientFill) fill));
443 } else {
444 g.setFill(JavaFXConversions.toJavaFXColor(fill.getColour()));
445 }
446
447 g.fillOval(centre.x - diameters.width / 2, centre.y - diameters.height / 2, diameters.width, diameters.height);
448 }
449
450 if (borderColour != null && borderStroke != null) {
451 g.setStroke(JavaFXConversions.toJavaFXColor(borderColour));
452 setStroke(g, borderStroke);
453
454 g.strokeOval(centre.x - diameters.width / 2, centre.y - diameters.height / 2, diameters.width, diameters.height);
455 }
456
457 g.restore();
458 }
459
460 @Override
461 public void drawPolygon(PolygonBounds points, Point offset, Dimension scale, double angle, Fill fill, Colour borderColour, Stroke borderStroke)
462 {
463 if (points == null || points.getPointCount() < 2) return;
464 if (fill == null && (borderColour == null || borderStroke == null)) return;
465
466 GraphicsContext g = _surfaceStack.getCurrentSurface().getGraphicsContext2D();
467
468 g.save();
469
470 Point centre = points.getCentre();
471 g.translate(-centre.x, -centre.y);
472 if (angle != 0.0) g.rotate(angle);
473 if (scale != null) {
474 double xScale = ((double) scale.width) / (points.getMaxX() - points.getMinX());
475 double yScale = ((double) scale.height) / (points.getMaxY() - points.getMinY());
476 g.scale(xScale, yScale);
477 }
478 g.translate(centre.x, centre.y);
479 if (offset != null) g.translate(offset.x, offset.y);
480
481 Point[] pointArray = points.toArray();
482 int nPoints = pointArray.length;
483 double[] xArray = new double[nPoints];
484 double[] yArray = new double[nPoints];
485
486 for (int i = 0; i < nPoints; i++) {
487 xArray[i] = pointArray[i].x + 0.5;
488 yArray[i] = pointArray[i].y + 0.5;
489 }
490
491 if (fill != null) {
492 if (fill instanceof GradientFill) {
493 g.setFill(JavaFXConversions.toJavaFXLinearGradient((GradientFill) fill));
494 } else {
495 g.setFill(JavaFXConversions.toJavaFXColor(fill.getColour()));
496 }
497
498 g.fillPolygon(xArray, yArray, nPoints);
499 }
500
501 if (borderColour != null && borderStroke != null) {
502 g.setStroke(JavaFXConversions.toJavaFXColor(borderColour));
503 setStroke(g, borderStroke);
504
505 if (points.isClosed()) {
506 g.strokePolygon(xArray, yArray, nPoints);
507 } else {
508 g.strokePolyline(xArray, yArray, nPoints);
509 }
510 }
511
512 g.restore();
513 }
514
515 @Override
516 public void drawLine(int x1, int y1, int x2, int y2, Colour colour, Stroke stroke)
517 {
518 if (colour == null || stroke == null) return;
519
520 GraphicsContext g = _surfaceStack.getCurrentSurface().getGraphicsContext2D();
521
522 g.save();
523
524 g.setStroke(JavaFXConversions.toJavaFXColor(colour));
525 setStroke(g, stroke);
526
527 g.beginPath();
528 g.moveTo(x1 + 0.5, y1 + 0.5);
529 g.lineTo(x2 + 0.5, y2 + 0.5);
530 g.stroke();
531
532 g.restore();
533 }
534
535 @Override
536 public void drawString(String string, Point position, Font font, Colour colour)
537 {
538 // TODO: Implement. cts16
539 }
540
541 @Override
542 public void drawTextLayout(TextLayout layout, Point position, Colour colour)
543 {
544 if (layout == null || position == null || colour == null) return;
545
546 JavaFXTextLayoutManager layoutManager = JavaFXMiscManager.getIfUsingJavaFXTextLayoutManager();
547 if (layoutManager == null) {
548 drawString(layout.getLine(), position, layout.getFont(), colour);
549 return;
550 }
551
552 javafx.scene.text.Text text = layoutManager.getInternalLayout(layout);
553
554 GraphicsContext g = _surfaceStack.getCurrentSurface().getGraphicsContext2D();
555
556 g.save();
557
558 g.setFont(text.getFont());
559 g.setTextAlign(text.getTextAlignment());
560
561 g.setFill(JavaFXConversions.toJavaFXColor(colour));
562
563 g.fillText(text.getText(), position.x, position.y);
564
565 g.restore();
566 }
567
568 @Override
569 public void setCursor(Cursor cursor)
570 {
571 // TODO: Implement. cts16
572 }
573
574 @Override
575 public Dimension getBestCursorSize(Dimension desiredSize)
576 {
577 // TODO: Implement. cts16
578 return new Dimension(32, 32);
579 }
580
581 @Override
582 public boolean showDialog(String title, String message)
583 {
584 // TODO: Implement. cts16
585 return false;
586 }
587
588 @Override
589 public Dimension getCurrentDrawingSurfaceSize()
590 {
591 Canvas currentSurface = _surfaceStack.getCurrentSurface();
592
593 int width = (int) currentSurface.getWidth();
594 int height = (int) currentSurface.getHeight();
595
596 return new Dimension(width, height);
597 }
598
599 private Point translateCanvasToScreen(Point p)
600 {
601 if (p == null) return null;
602
603 Bounds canvasBounds = ExpediteeApplication.theCanvas.localToScreen(ExpediteeApplication.theCanvas.getBoundsInLocal());
604
605 return p.clone().add(canvasBounds.getMinX(), canvasBounds.getMinY());
606 }
607
608 private Dimension getDecorationDimensions()
609 {
610 int decorWidth = (int) (ExpediteeApplication.theStage.getWidth() - ExpediteeApplication.theScene.getWidth());
611 int decorHeight = (int) (ExpediteeApplication.theStage.getHeight() - ExpediteeApplication.theScene.getHeight());
612
613 return new Dimension(decorWidth, decorHeight);
614 }
615
616 private void setStroke(GraphicsContext g, Stroke stroke)
617 {
618 g.setLineWidth(stroke.thickness);
619 g.setLineCap(JavaFXConversions.toJavaFXStrokeLineCap(stroke.capType));
620 g.setLineJoin(JavaFXConversions.toJavaFXStrokeLineJoin(stroke.joinType));
621 g.setLineDashes(Util.toDoubleArray(stroke.dashArray));
622 g.setLineDashOffset(stroke.dashPhase);
623 }
624
625 private Set<Image> createIconSet(Image image)
626 {
627 if (image == null) return null;
628
629 Set<Image> iconSet = new HashSet<Image>();
630
631 for (int size = MAX_ICON_SIZE; size >= MIN_ICON_SIZE; size >>= 1) {
632 Image tempImage = Image.createImage(size, size);
633 this.pushDrawingSurface(tempImage);
634 this.drawImage(image, Point.ORIGIN, new Dimension(size, size));
635 this.popDrawingSurface();
636 iconSet.add(tempImage);
637 }
638
639 return iconSet;
640 }
641
642 public static class ExpediteeApplication extends Application {
643
644 public static Stage theStage = null;
645 public static Scene theScene = null;
646 public static Group theGroup = null;
647 public static Canvas theCanvas = null;
648
649 public static JavaFXGraphicsManager manager = null;
650
651 @Override
652 public void start(Stage primaryStage) throws Exception
653 {
654 // Set up the canvas for drawing operations
655 theStage = primaryStage;
656 theGroup = new Group();
657 theScene = new Scene(theGroup);
658 theCanvas = new Canvas();
659 theGroup.getChildren().add(theCanvas);
660
661 // Set the scene
662 manager.setWindowIcon();
663 primaryStage.setScene(theScene);
664 primaryStage.show();
665
666 Platform.runLater(new Runnable() {
667 @Override
668 public void run() {
669 synchronized (_jfxAppThread) {
670 _jfxAppThread.notify();
671 }
672 }
673 });
674 }
675
676 }
677
678}
Note: See TracBrowser for help on using the repository browser.