Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.javafx.util;
import static com.sun.javafx.FXPermissions.ACCESS_WINDOW_LIST_PERMISSION;
import javafx.application.Platform;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.geometry.HPos;
import javafx.geometry.NodeOrientation;
import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.paint.Stop;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.Window;
import java.math.BigDecimal;
import java.util.List;
import com.sun.javafx.PlatformUtil;
import java.security.AccessController;
import java.security.PrivilegedAction;
import com.sun.glass.utils.NativeLibLoader;
import com.sun.prism.impl.PrismSettings;
/**
* Some basic utilities which need to be in java (for shifting operations or
* other reasons), which are not toolkit dependent.
*
*/
public class Utils {
/***************************************************************************
* *
* Math-related utilities *
* *
**************************************************************************/
/**
* Simple utility function which clamps the given value to be strictly
* between the min and max values.
*/
public static float clamp(float min, float value, float max) {
if (value < min) return min;
if (value > max) return max;
return value;
}
/**
* Simple utility function which clamps the given value to be strictly
* between the min and max values.
*/
public static int clamp(int min, int value, int max) {
if (value < min) return min;
if (value > max) return max;
return value;
}
/**
* Simple utility function which clamps the given value to be strictly
* between the min and max values.
*/
public static double clamp(double min, double value, double max) {
if (value < min) return min;
if (value > max) return max;
return value;
}
/**
* Simple utility function which clamps the given value to be strictly
* between the min and max values.
*/
public static long clamp(long min, long value, long max) {
if (value < min) return min;
if (value > max) return max;
return value;
}
/**
* Simple utility function which clamps the given value to be strictly
* between the min and max values.
*/
public static BigDecimal clamp(BigDecimal min, BigDecimal value, BigDecimal max) {
if (value.compareTo(min) < 0) return min;
if (value.compareTo(max) > 0) return max;
return value;
}
/**
* Simple utility function which clamps the given value to be strictly
* above the min value.
*/
public static double clampMin(double value, double min) {
if (value < min) return min;
return value;
}
/**
* Simple utility function which clamps the given value to be strictly
* under the max value.
*/
public static int clampMax(int value, int max) {
if (value > max) return max;
return value;
}
/**
* Utility function which returns either {@code less} or {@code more}
* depending on which {@code value} is closer to. If {@code value}
* is perfectly between them, then either may be returned.
*/
public static double nearest(double less, double value, double more) {
double lessDiff = value - less;
double moreDiff = more - value;
if (lessDiff < moreDiff) return less;
return more;
}
/***************************************************************************
* *
* String-related utilities *
* *
**************************************************************************/
/**
* Helper to remove leading and trailing quotes from a string.
* Works with single or double quotes.
*/
public static String stripQuotes(String str) {
if (str == null) return str;
if (str.length() == 0) return str;
int beginIndex = 0;
final char openQuote = str.charAt(beginIndex);
if ( openQuote == '\"' || openQuote=='\'' ) beginIndex += 1;
int endIndex = str.length();
final char closeQuote = str.charAt(endIndex - 1);
if ( closeQuote == '\"' || closeQuote=='\'' ) endIndex -= 1;
if ((endIndex - beginIndex) < 0) return str;
// note that String.substring returns "this" if beginIndex == 0 && endIndex == count
// or a new string that shares the character buffer with the original string.
return str.substring(beginIndex, endIndex);
}
/**
* Because mobile doesn't have string.split(s) function, this function
* was written.
*/
public static String[] split(String str, String separator) {
if (str == null || str.length() == 0) return new String[] { };
if (separator == null || separator.length() == 0) return new String[] { };
if (separator.length() > str.length()) return new String[] { };
java.util.List result = new java.util.ArrayList<>();
int index = str.indexOf(separator);
while (index >= 0) {
String newStr = str.substring(0, index);
if (newStr != null && newStr.length() > 0) {
result.add(newStr);
}
str = str.substring(index + separator.length());
index = str.indexOf(separator);
}
if (str != null && str.length() > 0) {
result.add(str);
}
return result.toArray(new String[] { });
}
/**
* Because mobile doesn't have string.contains(s) function, this function
* was written.
*/
public static boolean contains(String src, String s) {
if (src == null || src.length() == 0) return false;
if (s == null || s.length() == 0) return false;
if (s.length() > src.length()) return false;
return src.indexOf(s) > -1;
}
/***************************************************************************
* *
* Color-related utilities *
* *
**************************************************************************/
/**
* Calculates a perceptual brightness for a color between 0.0 black and 1.0 while
*/
public static double calculateBrightness(Color color) {
return (0.3*color.getRed()) + (0.59*color.getGreen()) + (0.11*color.getBlue());
}
/**
* Derives a lighter or darker of a given color.
*
* @param c The color to derive from
* @param brightness The brightness difference for the new color -1.0 being 100% dark which is always black, 0.0 being
* no change and 1.0 being 100% lighter which is always white
*/
public static Color deriveColor(Color c, double brightness) {
double baseBrightness = calculateBrightness(c);
double calcBrightness = brightness;
// Fine adjustments to colors in ranges of brightness to adjust the contrast for them
if (brightness > 0) {
if (baseBrightness > 0.85) {
calcBrightness = calcBrightness * 1.6;
} else if (baseBrightness > 0.6) {
// no change
} else if (baseBrightness > 0.5) {
calcBrightness = calcBrightness * 0.9;
} else if (baseBrightness > 0.4) {
calcBrightness = calcBrightness * 0.8;
} else if (baseBrightness > 0.3) {
calcBrightness = calcBrightness * 0.7;
} else {
calcBrightness = calcBrightness * 0.6;
}
} else {
if (baseBrightness < 0.2) {
calcBrightness = calcBrightness * 0.6;
}
}
// clamp brightness
if (calcBrightness < -1) { calcBrightness = -1; } else if (calcBrightness > 1) {calcBrightness = 1;}
// window two take the calculated brightness multiplyer and derive color based on source color
double[] hsb = RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue());
// change brightness
if (calcBrightness > 0) { // brighter
hsb[1] *= 1 - calcBrightness;
hsb[2] += (1 - hsb[2]) * calcBrightness;
} else { // darker
hsb[2] *= calcBrightness + 1;
}
// clip saturation and brightness
if (hsb[1] < 0) { hsb[1] = 0;} else if (hsb[1] > 1) {hsb[1] = 1;}
if (hsb[2] < 0) { hsb[2] = 0;} else if (hsb[2] > 1) {hsb[2] = 1;}
// convert back to color
Color c2 = Color.hsb((int)hsb[0], hsb[1], hsb[2],c.getOpacity());
return Color.hsb((int)hsb[0], hsb[1], hsb[2],c.getOpacity());
/* var hsb:Number[] = RGBtoHSB(c.red,c.green,c.blue);
// change brightness
if (brightness > 0) {
//var bright:Number = brightness * (1-calculateBrightness(c));
var bright:Number = if (calculateBrightness(c)<0.65 and brightness > 0.5) {
if (calculateBrightness(c)<0.2) then brightness * 0.55 else brightness * 0.7
} else brightness;
// brighter
hsb[1] *= 1 - bright;
hsb[2] += (1 - hsb[2]) * bright;
} else {
// darker
hsb[2] *= brightness+1;
}
// clip saturation and brightness
if (hsb[1] < 0) { hsb[1] = 0;} else if (hsb[1] > 1) {hsb[1] = 1}
if (hsb[2] < 0) { hsb[2] = 0;} else if (hsb[2] > 1) {hsb[2] = 1}
// convert back to color
return Color.hsb(hsb[0],hsb[1],hsb[2]) */
}
/**
* interpolate at a set {@code position} between two colors {@code color1} and {@code color2}.
* The interpolation is done is linear RGB color space not the default sRGB color space.
*/
private static Color interpolateLinear(double position, Color color1, Color color2) {
Color c1Linear = convertSRGBtoLinearRGB(color1);
Color c2Linear = convertSRGBtoLinearRGB(color2);
return convertLinearRGBtoSRGB(Color.color(
c1Linear.getRed() + (c2Linear.getRed() - c1Linear.getRed()) * position,
c1Linear.getGreen() + (c2Linear.getGreen() - c1Linear.getGreen()) * position,
c1Linear.getBlue() + (c2Linear.getBlue() - c1Linear.getBlue()) * position,
c1Linear.getOpacity() + (c2Linear.getOpacity() - c1Linear.getOpacity()) * position
));
}
/**
* Get the color at the give {@code position} in the ladder of color stops
*/
private static Color ladder(final double position, final Stop[] stops) {
Stop prevStop = null;
for (int i=0; i g) ? r : g;
if (b > cmax) cmax = b;
double cmin = (r < g) ? r : g;
if (b < cmin) cmin = b;
brightness = cmax;
if (cmax != 0)
saturation = (cmax - cmin) / cmax;
else
saturation = 0;
if (saturation == 0) {
hue = 0;
} else {
double redc = (cmax - r) / (cmax - cmin);
double greenc = (cmax - g) / (cmax - cmin);
double bluec = (cmax - b) / (cmax - cmin);
if (r == cmax)
hue = bluec - greenc;
else if (g == cmax)
hue = 2.0 + redc - bluec;
else
hue = 4.0 + greenc - redc;
hue = hue / 6.0;
if (hue < 0)
hue = hue + 1.0;
}
hsbvals[0] = hue * 360;
hsbvals[1] = saturation;
hsbvals[2] = brightness;
return hsbvals;
}
/**
* Helper function to convert a color in sRGB space to linear RGB space.
*/
public static Color convertSRGBtoLinearRGB(Color color) {
double[] colors = new double[] { color.getRed(), color.getGreen(), color.getBlue() };
for (int i=0; iwidth and refer to the width and height of the
* node/popup that is needing to be repositioned, not of the parent.
*
* Don't use the BASELINE vpos, it doesn't make sense and would produce wrong result.
*/
public static Point2D pointRelativeTo(Object parent, double width,
double height, double screenX, double screenY, HPos hpos, VPos vpos)
{
double finalScreenX = screenX;
double finalScreenY = screenY;
final Bounds parentBounds = getBounds(parent);
// ...and then we get the bounds of this screen
final Screen currentScreen = getScreen(parent);
final Rectangle2D screenBounds =
hasFullScreenStage(currentScreen)
? currentScreen.getBounds()
: currentScreen.getVisualBounds();
// test if this layout will force the node to appear outside
// of the screens bounds. If so, we must reposition the item to a better position.
// We firstly try to do this intelligently, so as to not overlap the parent if
// at all possible.
if (hpos != null) {
// Firstly we consider going off the right hand side
if ((finalScreenX + width) > screenBounds.getMaxX()) {
finalScreenX = positionX(parentBounds, width, getHPosOpposite(hpos, vpos));
}
// don't let the node go off to the left of the current screen
if (finalScreenX < screenBounds.getMinX()) {
finalScreenX = positionX(parentBounds, width, getHPosOpposite(hpos, vpos));
}
}
if (vpos != null) {
// don't let the node go off the bottom of the current screen
if ((finalScreenY + height) > screenBounds.getMaxY()) {
finalScreenY = positionY(parentBounds, height, getVPosOpposite(hpos,vpos));
}
// don't let the node out of the top of the current screen
if (finalScreenY < screenBounds.getMinY()) {
finalScreenY = positionY(parentBounds, height, getVPosOpposite(hpos,vpos));
}
}
// --- after all the moving around, we do one last check / rearrange.
// Unlike the check above, this time we are just fully committed to keeping
// the item on screen at all costs, regardless of whether or not that results
// in overlapping the parent object.
if ((finalScreenX + width) > screenBounds.getMaxX()) {
finalScreenX -= (finalScreenX + width - screenBounds.getMaxX());
}
if (finalScreenX < screenBounds.getMinX()) {
finalScreenX = screenBounds.getMinX();
}
if ((finalScreenY + height) > screenBounds.getMaxY()) {
finalScreenY -= (finalScreenY + height - screenBounds.getMaxY());
}
if (finalScreenY < screenBounds.getMinY()) {
finalScreenY = screenBounds.getMinY();
}
return new Point2D(finalScreenX, finalScreenY);
}
/**
* Utility function that returns the x-axis position that an object should be positioned at,
* given the parents screen bounds, the width of the object, and
* the required HPos.
*/
private static double positionX(Bounds parentBounds, double width, HPos hpos) {
if (hpos == HPos.CENTER) {
// this isn't right, but it is needed for root menus to show properly
return parentBounds.getMinX();
} else if (hpos == HPos.RIGHT) {
return parentBounds.getMaxX();
} else if (hpos == HPos.LEFT) {
return parentBounds.getMinX() - width;
} else {
return 0;
}
}
/**
* Utility function that returns the y-axis position that an object should be positioned at,
* given the parents screen bounds, the height of the object, and
* the required VPos.
*
* The BASELINE vpos doesn't make sense here, 0 is returned for it.
*/
private static double positionY(Bounds parentBounds, double height, VPos vpos) {
if (vpos == VPos.BOTTOM) {
return parentBounds.getMaxY();
} else if (vpos == VPos.CENTER) {
return parentBounds.getMinY();
} else if (vpos == VPos.TOP) {
return parentBounds.getMinY() - height;
} else {
return 0;
}
}
/**
* To facilitate multiple types of parent object, we unfortunately must allow for
* Objects to be passed in. This method handles determining the bounds of the
* given Object. If the Object type is not supported, a default Bounds will be returned.
*/
private static Bounds getBounds(Object obj) {
if (obj instanceof Node) {
final Node n = (Node)obj;
Bounds b = n.localToScreen(n.getLayoutBounds());
return b != null ? b : new BoundingBox(0, 0, 0, 0);
} else if (obj instanceof Window) {
final Window window = (Window)obj;
return new BoundingBox(window.getX(), window.getY(), window.getWidth(), window.getHeight());
} else {
return new BoundingBox(0, 0, 0, 0);
}
}
/*
* Simple utitilty function to return the 'opposite' value of a given HPos, taking
* into account the current VPos value. This is used to try and avoid overlapping.
*/
private static HPos getHPosOpposite(HPos hpos, VPos vpos) {
if (vpos == VPos.CENTER) {
if (hpos == HPos.LEFT){
return HPos.RIGHT;
} else if (hpos == HPos.RIGHT){
return HPos.LEFT;
} else if (hpos == HPos.CENTER){
return HPos.CENTER;
} else {
// by default center for now
return HPos.CENTER;
}
} else {
return HPos.CENTER;
}
}
/*
* Simple utitilty function to return the 'opposite' value of a given VPos, taking
* into account the current HPos value. This is used to try and avoid overlapping.
*/
private static VPos getVPosOpposite(HPos hpos, VPos vpos) {
if (hpos == HPos.CENTER) {
if (vpos == VPos.BASELINE){
return VPos.BASELINE;
} else if (vpos == VPos.BOTTOM){
return VPos.TOP;
} else if (vpos == VPos.CENTER){
return VPos.CENTER;
} else if (vpos == VPos.TOP){
return VPos.BOTTOM;
} else {
// by default center for now
return VPos.CENTER;
}
} else {
return VPos.CENTER;
}
}
public static boolean hasFullScreenStage(final Screen screen) {
@SuppressWarnings("removal")
final List allWindows = AccessController.doPrivileged(
(PrivilegedAction>) () -> Window.getWindows(),
null,
ACCESS_WINDOW_LIST_PERMISSION);
for (final Window window : allWindows) {
if (window instanceof Stage) {
final Stage stage = (Stage) window;
if (stage.isFullScreen() && (getScreen(stage) == screen)) {
return true;
}
}
}
return false;
}
/*
* Returns true if the primary Screen has QVGA dimensions, in landscape or portrait mode.
*/
public static boolean isQVGAScreen() {
Rectangle2D bounds = Screen.getPrimary().getBounds();
return ((bounds.getWidth() == 320 && bounds.getHeight() == 240) ||
(bounds.getWidth() == 240 && bounds.getHeight() == 320));
}
/**
* This function attempts to determine the best screen given the parent object
* from which we are wanting to position another item relative to. This is particularly
* important when we want to keep items from going off screen, and for handling
* multiple monitor support.
*/
public static Screen getScreen(Object obj) {
final Bounds parentBounds = getBounds(obj);
final Rectangle2D rect = new Rectangle2D(
parentBounds.getMinX(),
parentBounds.getMinY(),
parentBounds.getWidth(),
parentBounds.getHeight());
return getScreenForRectangle(rect);
}
public static Screen getScreenForRectangle(final Rectangle2D rect) {
final List screens = Screen.getScreens();
final double rectX0 = rect.getMinX();
final double rectX1 = rect.getMaxX();
final double rectY0 = rect.getMinY();
final double rectY1 = rect.getMaxY();
Screen selectedScreen;
selectedScreen = null;
double maxIntersection = 0;
for (final Screen screen: screens) {
final Rectangle2D screenBounds = screen.getBounds();
final double intersection =
getIntersectionLength(rectX0, rectX1,
screenBounds.getMinX(),
screenBounds.getMaxX())
* getIntersectionLength(rectY0, rectY1,
screenBounds.getMinY(),
screenBounds.getMaxY());
if (maxIntersection < intersection) {
maxIntersection = intersection;
selectedScreen = screen;
}
}
if (selectedScreen != null) {
return selectedScreen;
}
selectedScreen = Screen.getPrimary();
double minDistance = Double.MAX_VALUE;
for (final Screen screen: screens) {
final Rectangle2D screenBounds = screen.getBounds();
final double dx = getOuterDistance(rectX0, rectX1,
screenBounds.getMinX(),
screenBounds.getMaxX());
final double dy = getOuterDistance(rectY0, rectY1,
screenBounds.getMinY(),
screenBounds.getMaxY());
final double distance = dx * dx + dy * dy;
if (minDistance > distance) {
minDistance = distance;
selectedScreen = screen;
}
}
return selectedScreen;
}
public static Screen getScreenForPoint(final double x, final double y) {
final List screens = Screen.getScreens();
// first check whether the point is inside some screen
for (final Screen screen: screens) {
// can't use screen.bounds.contains, because it returns true for
// the min + width point
final Rectangle2D screenBounds = screen.getBounds();
if ((x >= screenBounds.getMinX())
&& (x < screenBounds.getMaxX())
&& (y >= screenBounds.getMinY())
&& (y < screenBounds.getMaxY())) {
return screen;
}
}
// the point is not inside any screen, find the closest screen now
Screen selectedScreen = Screen.getPrimary();
double minDistance = Double.MAX_VALUE;
for (final Screen screen: screens) {
final Rectangle2D screenBounds = screen.getBounds();
final double dx = getOuterDistance(screenBounds.getMinX(),
screenBounds.getMaxX(),
x);
final double dy = getOuterDistance(screenBounds.getMinY(),
screenBounds.getMaxY(),
y);
final double distance = dx * dx + dy * dy;
if (minDistance >= distance) {
minDistance = distance;
selectedScreen = screen;
}
}
return selectedScreen;
}
private static double getIntersectionLength(
final double a0, final double a1,
final double b0, final double b1) {
// (a0 <= a1) && (b0 <= b1)
return (a0 <= b0) ? getIntersectionLengthImpl(b0, b1, a1)
: getIntersectionLengthImpl(a0, a1, b1);
}
private static double getIntersectionLengthImpl(
final double v0, final double v1, final double v) {
// (v0 <= v1)
if (v <= v0) {
return 0;
}
return (v <= v1) ? v - v0 : v1 - v0;
}
private static double getOuterDistance(
final double a0, final double a1,
final double b0, final double b1) {
// (a0 <= a1) && (b0 <= b1)
if (a1 <= b0) {
return b0 - a1;
}
if (b1 <= a0) {
return b1 - a0;
}
return 0;
}
private static double getOuterDistance(final double v0,
final double v1,
final double v) {
// (v0 <= v1)
if (v <= v0) {
return v0 - v;
}
if (v >= v1) {
return v - v1;
}
return 0;
}
/***************************************************************************
* *
* Miscellaneous utilities *
* *
**************************************************************************/
/**
* To force initialization of a class
* @param classToInit
*/
public static void forceInit(final Class> classToInit) {
try {
Class.forName(classToInit.getName(), true,
classToInit.getClassLoader());
} catch (final ClassNotFoundException e) {
throw new AssertionError(e); // Can't happen
}
}
/**
* @return true if and only if assertions are enabled at runtime.
*/
public static boolean assertionEnabled() {
boolean assertsEnabled = false;
// The following assertion check will always pass. The side-effect
// of the `assertsEnabled = true` assignment is intentional. It will
// be executed if-and-only-if assertions are enabled at runtime.
assert (assertsEnabled = true) == true;
return assertsEnabled;
}
/**
* Returns true if the operating system is a form of Windows.
*/
public static boolean isWindows(){
return PlatformUtil.isWindows();
}
/**
* Returns true if the operating system is a form of Mac OS.
*/
public static boolean isMac(){
return PlatformUtil.isMac();
}
/**
* Returns true if the operating system is a form of Unix, including Linux.
*/
public static boolean isUnix(){
return PlatformUtil.isUnix();
}
/***************************************************************************
* *
* Unicode-related utilities *
* *
**************************************************************************/
public static String convertUnicode(String src) {
/** The input buffer, index of next character to be read,
* index of one past last character in buffer.
*/
char[] buf;
int bp;
int buflen;
/** The current character.
*/
char ch;
/** The buffer index of the last converted unicode character
*/
int unicodeConversionBp = -1;
buf = src.toCharArray();
buflen = buf.length;
bp = -1;
char[] dst = new char[buflen];
int dstIndex = 0;
while (bp < buflen - 1) {
ch = buf[++bp];
if (ch == '\\') {
if (unicodeConversionBp != bp) {
bp++; ch = buf[bp];
if (ch == 'u') {
do {
bp++; ch = buf[bp];
} while (ch == 'u');
int limit = bp + 3;
if (limit < buflen) {
char c = ch;
int result = Character.digit(c, 16);
if (result >= 0 && c > 0x7f) {
//lexError(pos+1, "illegal.nonascii.digit");
ch = "0123456789abcdef".charAt(result);
}
int d = result;
int code = d;
while (bp < limit && d >= 0) {
bp++; ch = buf[bp];
char c1 = ch;
int result1 = Character.digit(c1, 16);
if (result1 >= 0 && c1 > 0x7f) {
//lexError(pos+1, "illegal.nonascii.digit");
ch = "0123456789abcdef".charAt(result1);
}
d = result1;
code = (code << 4) + d;
}
if (d >= 0) {
ch = (char)code;
unicodeConversionBp = bp;
}
}
//lexError(bp, "illegal.unicode.esc");
} else {
bp--;
ch = '\\';
}
}
}
dst[dstIndex++] = ch;
}
return new String(dst, 0, dstIndex);
}
@SuppressWarnings("removal")
public static synchronized void loadNativeSwingLibrary() {
AccessController.doPrivileged((PrivilegedAction) () -> {
String libName = "prism_common";
if (PrismSettings.verbose) {
System.out.println("Loading Prism common native library ...");
}
NativeLibLoader.loadLibrary(libName);
if (PrismSettings.verbose) {
System.out.println("\tsucceeded.");
}
return null;
});
}
/**
* Ensures that a code segment is run on the FX thread.
*
* @param runnable a {@code Runnable} encapsulating the code
*/
public static void runOnFxThread(Runnable runnable) {
if (Platform.isFxApplicationThread()) {
runnable.run();
} else {
Platform.runLater(runnable);
}
}
}