mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-05-19 13:58:37 -04:00
Cleanup
This commit is contained in:
@@ -11,7 +11,7 @@ public class DaemonFocusExchangeImpl extends DaemonFocusExchange {
|
||||
@Override
|
||||
public Object handle(HttpExchange exchange, Request msg) {
|
||||
OperationMode.switchUp(OperationMode.GUI);
|
||||
var w = AppMainWindow.getInstance();
|
||||
var w = AppMainWindow.get();
|
||||
if (w != null) {
|
||||
w.focus();
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ public class BrowserFullSessionComp extends SimpleComp {
|
||||
loadingStack.apply(struc -> struc.get().setPickOnBounds(false));
|
||||
var delayedStack = new DelayedInitComp(
|
||||
left, () -> StoreViewState.get() != null && StoreViewState.get().isInitialized());
|
||||
delayedStack.hide(AppMainWindow.getInstance().getStage().widthProperty().lessThan(1000));
|
||||
delayedStack.hide(AppMainWindow.get().getStage().widthProperty().lessThan(1000));
|
||||
var splitPane = new LeftSplitPaneComp(delayedStack, loadingStack)
|
||||
.withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth())
|
||||
.withOnDividerChange(d -> {
|
||||
|
||||
@@ -3,7 +3,6 @@ package io.xpipe.app.browser.file;
|
||||
import io.xpipe.app.browser.icon.BrowserIcons;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.augment.GrowAugment;
|
||||
import io.xpipe.app.comp.base.HorizontalComp;
|
||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||
import io.xpipe.app.ext.FileEntry;
|
||||
@@ -42,7 +41,7 @@ public class BrowserFileOverviewComp extends SimpleComp {
|
||||
event.consume();
|
||||
});
|
||||
l.setAlignment(Pos.CENTER_LEFT);
|
||||
GrowAugment.create(true, false).augment(l);
|
||||
l.setMaxWidth(10000);
|
||||
return l;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||
import io.xpipe.app.comp.base.PrettyImageHelper;
|
||||
import io.xpipe.app.core.AppStyle;
|
||||
import io.xpipe.app.core.window.AppWindowHelper;
|
||||
import io.xpipe.app.core.window.AppWindowStyle;
|
||||
import io.xpipe.app.platform.BindingsHelper;
|
||||
import io.xpipe.app.platform.PlatformThread;
|
||||
|
||||
@@ -42,7 +42,7 @@ public class BrowserFileSelectionListComp extends SimpleComp {
|
||||
public static Image snapshot(ObservableList<BrowserEntry> list) {
|
||||
var r = new BrowserFileSelectionListComp(list).styleClass("drag").createRegion();
|
||||
var scene = new Scene(r);
|
||||
AppWindowHelper.setupStylesheets(scene);
|
||||
AppWindowStyle.addStylesheets(scene);
|
||||
AppStyle.addStylesheets(scene);
|
||||
SnapshotParameters parameters = new SnapshotParameters();
|
||||
parameters.setFill(Color.TRANSPARENT);
|
||||
|
||||
@@ -301,7 +301,7 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab<File
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AppMainWindow.getInstance().getStage().getWidth() <= 1380) {
|
||||
if (AppMainWindow.get().getStage().getWidth() <= 1380) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package io.xpipe.app.comp;
|
||||
|
||||
import io.xpipe.app.comp.augment.Augment;
|
||||
import io.xpipe.app.comp.augment.GrowAugment;
|
||||
import io.xpipe.app.comp.base.TooltipHelper;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.platform.BindingsHelper;
|
||||
import io.xpipe.app.platform.PlatformThread;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
@@ -211,7 +212,63 @@ public abstract class Comp<S extends CompStructure<?>> {
|
||||
}
|
||||
|
||||
public Comp<S> grow(boolean width, boolean height) {
|
||||
return apply(GrowAugment.create(width, height));
|
||||
return apply(struc -> {
|
||||
struc.get().parentProperty().addListener((c, o, n) -> {
|
||||
if (o instanceof Region) {
|
||||
if (width) {
|
||||
struc.get().prefWidthProperty().unbind();
|
||||
}
|
||||
if (height) {
|
||||
struc.get().prefHeightProperty().unbind();
|
||||
}
|
||||
}
|
||||
|
||||
bindGrow(struc.get(), n, width, height);
|
||||
});
|
||||
|
||||
bindGrow(struc.get(), struc.get().getParent(), width, height);
|
||||
});
|
||||
}
|
||||
|
||||
private void bindGrow(Region r, Node parent, boolean width, boolean height) {
|
||||
if (!(parent instanceof Region p)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (width) {
|
||||
r.prefWidthProperty()
|
||||
.bind(Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
var val = p.getWidth()
|
||||
- p.getInsets().getLeft()
|
||||
- p.getInsets().getRight();
|
||||
if (val <= 0) {
|
||||
return Region.USE_COMPUTED_SIZE;
|
||||
}
|
||||
|
||||
// Floor to prevent rounding issues which cause an infinite growing
|
||||
return Math.floor(val);
|
||||
},
|
||||
p.widthProperty(),
|
||||
p.insetsProperty()));
|
||||
}
|
||||
if (height) {
|
||||
r.prefHeightProperty()
|
||||
.bind(Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
var val = p.getHeight()
|
||||
- p.getInsets().getTop()
|
||||
- p.getInsets().getBottom();
|
||||
if (val <= 0) {
|
||||
return Region.USE_COMPUTED_SIZE;
|
||||
}
|
||||
|
||||
// Floor to prevent rounding issues which cause an infinite growing
|
||||
return Math.floor(val);
|
||||
},
|
||||
p.heightProperty(),
|
||||
p.insetsProperty()));
|
||||
}
|
||||
}
|
||||
|
||||
public Comp<S> tooltip(ObservableValue<String> text) {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# FxComps - Compound Components for JavaFX
|
||||
# Compound Components
|
||||
|
||||
The FxComps library provides a new approach to creating JavaFX interfaces and
|
||||
offers a quicker and more robust user interface development workflow.
|
||||
This library is compatible and can be used with any other JavaFX library.
|
||||
As a basis, JavaFX nodes are created and manage via comps (compound components).
|
||||
|
||||
## Principles
|
||||
|
||||
@@ -12,11 +10,6 @@ It is advantageous to define a certain component to be a factory
|
||||
that can create an instances of a JavaFX Node each time it is called.
|
||||
By using this factory architecture, the scene contents can
|
||||
be rebuilt entirely by invoking the root component factory.
|
||||
See the [hot reload](#Hot-Reload) section on how this can be used.
|
||||
|
||||
Of course, if a component is a compound component that has children,
|
||||
the parent factory has to incorporate the child factories into its creation process.
|
||||
This can be done in fxcomps.
|
||||
|
||||
#### A comp should produce a transparent representation of Regions and Controls
|
||||
|
||||
@@ -86,44 +79,3 @@ If you for example bind your IDE Hot Reload to F4 and your Scene reload listener
|
||||
you can almost instantly apply any changes made to your GUI code without restarting.
|
||||
You can also implement a similar solution to also reload your stylesheets and translations.
|
||||
|
||||
## Library contents
|
||||
|
||||
Aside from the base classes needed to implement the principles listed above,
|
||||
this library also comes with a few very basic Comp implementations and some Augments.
|
||||
These are very general implementations and can be seen as example implementations.
|
||||
|
||||
#### Comps
|
||||
|
||||
- [HorizontalComp](src/main/java/io/xpipe/fxcomps/comp/HorizontalComp.java) /
|
||||
[VerticalComp](src/main/java/io/xpipe/fxcomps/comp/VerticalComp.java): Simple Comp implementation to create a
|
||||
HBox/VBox using Comps as input
|
||||
- [StackComp](src/main/java/io/xpipe/fxcomps/comp/StackComp.java): Simple Comp implementation to easily create a stack
|
||||
pane using Comps as input
|
||||
- [StackComp](src/main/java/io/xpipe/fxcomps/comp/LabelComp.java): Simple Comp implementation for a label
|
||||
|
||||
#### Augments
|
||||
|
||||
- [GrowAugment](src/main/java/io/xpipe/fxcomps/augment/GrowAugment.java): Binds the width/height of a Comp to its
|
||||
parent, adjusted for parent padding
|
||||
- [PopupMenuComp](src/main/java/io/xpipe/fxcomps/augment/PopupMenuAugment.java): Allows you to show a context menu when
|
||||
a comp is left-clicked in addition to right-click
|
||||
|
||||
## Creating a basic comp
|
||||
|
||||
As the central idea of this library is that you create your own Comps, it is designed to be very simple:
|
||||
|
||||
````java
|
||||
var b = Comp.of(() -> new Button("Button"));
|
||||
var l = Comp.of(() -> new Label("Label"));
|
||||
|
||||
// Create an HBox factory and apply some Augments to it
|
||||
var layoutFactory = new HorizontalComp(List.of(b, l))
|
||||
.apply(struc -> struc.get().setAlignment(Pos.CENTER))
|
||||
.apply(GrowAugment.create(true, true))
|
||||
.styleClass("layout");
|
||||
|
||||
// You can now create node instances of your layout
|
||||
var region = layoutFactory.createRegion();
|
||||
````
|
||||
|
||||
Most simple Comp definitions can be defined inline with the `Comp.of(...)` method.
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
package io.xpipe.app.comp.augment;
|
||||
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
public class GrowAugment<S extends CompStructure<?>> implements Augment<S> {
|
||||
|
||||
private final boolean width;
|
||||
private final boolean height;
|
||||
|
||||
private GrowAugment(boolean width, boolean height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public static <S extends CompStructure<?>> GrowAugment<S> create(boolean width, boolean height) {
|
||||
return new GrowAugment<>(width, height);
|
||||
}
|
||||
|
||||
private void bind(Region r, Node parent) {
|
||||
if (!(parent instanceof Region p)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (width) {
|
||||
r.prefWidthProperty()
|
||||
.bind(Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
var val = p.getWidth()
|
||||
- p.getInsets().getLeft()
|
||||
- p.getInsets().getRight();
|
||||
if (val <= 0) {
|
||||
return Region.USE_COMPUTED_SIZE;
|
||||
}
|
||||
|
||||
// Floor to prevent rounding issues which cause an infinite growing
|
||||
return Math.floor(val);
|
||||
},
|
||||
p.widthProperty(),
|
||||
p.insetsProperty()));
|
||||
}
|
||||
if (height) {
|
||||
r.prefHeightProperty()
|
||||
.bind(Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
var val = p.getHeight()
|
||||
- p.getInsets().getTop()
|
||||
- p.getInsets().getBottom();
|
||||
if (val <= 0) {
|
||||
return Region.USE_COMPUTED_SIZE;
|
||||
}
|
||||
|
||||
// Floor to prevent rounding issues which cause an infinite growing
|
||||
return Math.floor(val);
|
||||
},
|
||||
p.heightProperty(),
|
||||
p.insetsProperty()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void augment(S struc) {
|
||||
struc.get().parentProperty().addListener((c, o, n) -> {
|
||||
if (o instanceof Region) {
|
||||
if (width) {
|
||||
struc.get().prefWidthProperty().unbind();
|
||||
}
|
||||
if (height) {
|
||||
struc.get().prefHeightProperty().unbind();
|
||||
}
|
||||
}
|
||||
|
||||
bind(struc.get(), n);
|
||||
});
|
||||
|
||||
bind(struc.get(), struc.get().getParent());
|
||||
}
|
||||
}
|
||||
@@ -126,7 +126,7 @@ public class AppMainWindowContentComp extends SimpleComp {
|
||||
|
||||
overlay.addListener((ListChangeListener<? super ModalOverlay>) c -> {
|
||||
if (c.next() && c.wasAdded()) {
|
||||
AppMainWindow.getInstance().focus();
|
||||
AppMainWindow.get().focus();
|
||||
|
||||
// Close blocking modal windows
|
||||
var childWindows = Window.getWindows().stream()
|
||||
|
||||
@@ -3,8 +3,8 @@ package io.xpipe.app.comp.base;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.core.AppActionLinkDetector;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppOpenArguments;
|
||||
import io.xpipe.app.platform.PlatformThread;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
@@ -18,6 +18,7 @@ import javafx.scene.input.MouseButton;
|
||||
import atlantafx.base.controls.CustomTextField;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class FilterComp extends Comp<CompStructure<CustomTextField>> {
|
||||
@@ -76,7 +77,7 @@ public class FilterComp extends Comp<CompStructure<CustomTextField>> {
|
||||
filter.textProperty().addListener((observable, oldValue, n) -> {
|
||||
// Handle pasted xpipe URLs
|
||||
if (n != null && n.startsWith("xpipe://")) {
|
||||
AppActionLinkDetector.handle(n, false);
|
||||
AppOpenArguments.handle(List.of(n));
|
||||
filter.setText(null);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ public class PrettyImageHelper {
|
||||
|
||||
private static ObservableValue<String> rasterizedImageIfExistsScaled(
|
||||
String img, int height, int... availableSizes) {
|
||||
ObservableDoubleValue obs = AppMainWindow.getInstance() != null
|
||||
? AppMainWindow.getInstance().displayScale()
|
||||
ObservableDoubleValue obs = AppMainWindow.get() != null
|
||||
? AppMainWindow.get().displayScale()
|
||||
: new SimpleDoubleProperty(1.0);
|
||||
return Bindings.createStringBinding(
|
||||
() -> {
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package io.xpipe.app.core;
|
||||
|
||||
import io.xpipe.app.core.window.AppDialog;
|
||||
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.DataFormat;
|
||||
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class AppActionLinkDetector {
|
||||
|
||||
@Setter
|
||||
private static String lastDetectedAction;
|
||||
|
||||
private static String getClipboardAction() {
|
||||
var content = Clipboard.getSystemClipboard().getContent(DataFormat.URL);
|
||||
if (content == null) {
|
||||
content = Clipboard.getSystemClipboard().getContent(DataFormat.PLAIN_TEXT);
|
||||
}
|
||||
|
||||
return content != null ? content.toString() : null;
|
||||
}
|
||||
|
||||
public static void handle(String content, boolean showAlert) {
|
||||
var detected = AppOpenArguments.parseActions(content);
|
||||
if (detected.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (showAlert && !showAlert()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppOpenArguments.handle(List.of(content));
|
||||
}
|
||||
|
||||
public static void detectOnPaste() {
|
||||
var content = getClipboardAction();
|
||||
if (content == null) {
|
||||
return;
|
||||
}
|
||||
lastDetectedAction = content;
|
||||
handle(content, false);
|
||||
}
|
||||
|
||||
private static boolean showAlert() {
|
||||
return AppDialog.confirm("clipboardActionDetected");
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ public class AppDesktopIntegration {
|
||||
});
|
||||
|
||||
// Set dock icon explicitly on macOS
|
||||
// This is necessary in case XPipe was started through a script as it will have no icon otherwise
|
||||
// This is necessary in case the app was started through a script as it will have no icon otherwise
|
||||
if (AppProperties.get().isDeveloperMode()
|
||||
&& AppLogs.get().isWriteToSysout()
|
||||
&& Taskbar.isTaskbarSupported()) {
|
||||
|
||||
@@ -19,6 +19,8 @@ public class AppFont {
|
||||
// Load ikonli fonts
|
||||
TrackEvent.info("Loading ikonli fonts ...");
|
||||
new FontIcon("mdi2s-stop");
|
||||
new FontIcon("mdal-360");
|
||||
new FontIcon("bi-alarm");
|
||||
|
||||
TrackEvent.info("Loading bundled fonts ...");
|
||||
AppResources.with(
|
||||
|
||||
@@ -108,7 +108,7 @@ public class AppFontSizes {
|
||||
}
|
||||
}
|
||||
|
||||
private static AppFontSizes interSize(AppFontSizes s) {
|
||||
private static AppFontSizes fallbackFontSize(AppFontSizes s) {
|
||||
if (s == BASE_10) {
|
||||
return BASE_10;
|
||||
} else if (s == BASE_10_5) {
|
||||
@@ -128,7 +128,7 @@ public class AppFontSizes {
|
||||
case OsType.MacOs ignored -> mac;
|
||||
case OsType.Windows ignored -> windows;
|
||||
};
|
||||
return inter ? interSize(r) : r;
|
||||
return inter ? fallbackFontSize(r) : r;
|
||||
}
|
||||
|
||||
public static AppFontSizes getDefault() {
|
||||
|
||||
@@ -215,7 +215,7 @@ public class AppTheme {
|
||||
}
|
||||
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
var window = AppMainWindow.getInstance();
|
||||
var window = AppMainWindow.get();
|
||||
if (window == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ public class AppGnomeScaleDialog {
|
||||
return;
|
||||
}
|
||||
|
||||
if (AppMainWindow.getInstance() == null) {
|
||||
if (AppMainWindow.get() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ public class BaseMode extends OperationMode {
|
||||
return;
|
||||
}
|
||||
|
||||
// For debugging
|
||||
// For debugging error handling
|
||||
// if (true) throw new IllegalStateException();
|
||||
|
||||
TrackEvent.info("Initializing base mode components ...");
|
||||
|
||||
@@ -40,7 +40,7 @@ public class GuiMode extends PlatformMode {
|
||||
LicenseProvider.get().init();
|
||||
|
||||
PlatformThread.runLaterIfNeededBlocking(() -> {
|
||||
AppMainWindow.getInstance().show();
|
||||
AppMainWindow.get().show();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.xpipe.app.core.mode;
|
||||
|
||||
import io.xpipe.app.beacon.AppBeaconServer;
|
||||
import io.xpipe.app.browser.BrowserFullSessionModel;
|
||||
import io.xpipe.app.core.*;
|
||||
import io.xpipe.app.core.check.AppDebugModeCheck;
|
||||
import io.xpipe.app.core.check.AppDirectoryPermissionsCheck;
|
||||
@@ -17,7 +16,6 @@ import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.update.AppDistributionType;
|
||||
import io.xpipe.app.util.*;
|
||||
import io.xpipe.core.FailableRunnable;
|
||||
import io.xpipe.core.OsType;
|
||||
import io.xpipe.core.XPipeDaemonMode;
|
||||
|
||||
import javafx.application.Platform;
|
||||
@@ -141,8 +139,8 @@ public abstract class OperationMode {
|
||||
|
||||
public static XPipeDaemonMode getStartupMode() {
|
||||
var event = TrackEvent.withInfo("Startup mode determined");
|
||||
if (AppMainWindow.getInstance() != null
|
||||
&& AppMainWindow.getInstance().getStage().isShowing()) {
|
||||
if (AppMainWindow.get() != null
|
||||
&& AppMainWindow.get().getStage().isShowing()) {
|
||||
event.tag("mode", "gui").tag("reason", "windowShowing").handle();
|
||||
return XPipeDaemonMode.GUI;
|
||||
}
|
||||
@@ -374,8 +372,8 @@ public abstract class OperationMode {
|
||||
|
||||
BACKGROUND.onSwitchTo();
|
||||
if (newMode != GUI
|
||||
&& AppMainWindow.getInstance() != null
|
||||
&& AppMainWindow.getInstance().getStage().isShowing()) {
|
||||
&& AppMainWindow.get() != null
|
||||
&& AppMainWindow.get().getStage().isShowing()) {
|
||||
GUI.onSwitchTo();
|
||||
newMode = GUI;
|
||||
} else {
|
||||
|
||||
@@ -11,7 +11,7 @@ import io.xpipe.app.prefs.CloseBehaviourDialog;
|
||||
import io.xpipe.app.update.AppDistributionType;
|
||||
import io.xpipe.app.platform.NativeWinWindowControl;
|
||||
import io.xpipe.app.platform.PlatformThread;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.app.util.GlobalTimer;
|
||||
import io.xpipe.core.OsType;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
@@ -35,7 +35,6 @@ import lombok.extern.jackson.Jacksonized;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
public class AppMainWindow {
|
||||
@@ -52,7 +51,6 @@ public class AppMainWindow {
|
||||
private final Stage stage;
|
||||
|
||||
private final BooleanProperty windowActive = new SimpleBooleanProperty(false);
|
||||
private Thread thread;
|
||||
private volatile Instant lastUpdate;
|
||||
private boolean shown = false;
|
||||
|
||||
@@ -105,11 +103,12 @@ public class AppMainWindow {
|
||||
if (AppPrefs.get() != null) {
|
||||
stage.opacityProperty().bind(PlatformThread.sync(AppPrefs.get().windowOpacity()));
|
||||
}
|
||||
AppWindowHelper.addIcons(stage);
|
||||
AppWindowHelper.setupStylesheets(stage.getScene());
|
||||
AppWindowHelper.setupClickShield(stage);
|
||||
AppWindowHelper.addMaximizedPseudoClass(stage);
|
||||
AppWindowHelper.addFontSize(stage);
|
||||
AppWindowStyle.addIcons(stage);
|
||||
AppWindowStyle.addStylesheets(stage.getScene());
|
||||
AppWindowStyle.addNavigationStyleClasses(stage.getScene());
|
||||
AppWindowStyle.addClickShield(stage);
|
||||
AppWindowStyle.addMaximizedPseudoClass(stage);
|
||||
AppWindowStyle.addFontSize(stage);
|
||||
AppTheme.initThemeHandlers(stage);
|
||||
|
||||
AppWindowTitle.getTitle().subscribe(s -> {
|
||||
@@ -147,7 +146,7 @@ public class AppMainWindow {
|
||||
});
|
||||
}
|
||||
|
||||
public static AppMainWindow getInstance() {
|
||||
public static AppMainWindow get() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@@ -186,29 +185,18 @@ public class AppMainWindow {
|
||||
}
|
||||
|
||||
private synchronized void onChange() {
|
||||
lastUpdate = Instant.now();
|
||||
if (thread == null) {
|
||||
thread = ThreadHelper.unstarted(() -> {
|
||||
while (true) {
|
||||
var toStop = lastUpdate.plus(Duration.of(1, ChronoUnit.SECONDS));
|
||||
if (Instant.now().isBefore(toStop)) {
|
||||
var toSleep = Duration.between(Instant.now(), toStop);
|
||||
if (!toSleep.isNegative()) {
|
||||
var ms = toSleep.toMillis();
|
||||
ThreadHelper.sleep(ms);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
var timestamp = Instant.now();
|
||||
lastUpdate = timestamp;
|
||||
// Reduce printed window updates
|
||||
GlobalTimer.delay(() -> {
|
||||
if (!timestamp.equals(lastUpdate)) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (AppMainWindow.this) {
|
||||
logChange();
|
||||
thread = null;
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
}
|
||||
synchronized (AppMainWindow.this) {
|
||||
logChange();
|
||||
}
|
||||
}, Duration.ofSeconds(1));
|
||||
}
|
||||
|
||||
private void logChange() {
|
||||
|
||||
@@ -83,8 +83,8 @@ public class AppModifiedStage extends Stage {
|
||||
case OsType.Linux ignored -> {}
|
||||
case OsType.MacOs ignored -> {
|
||||
var ctrl = new NativeMacOsWindowControl(stage);
|
||||
var seamlessFrame = AppMainWindow.getInstance() != null
|
||||
&& AppMainWindow.getInstance().getStage() == stage
|
||||
var seamlessFrame = AppMainWindow.get() != null
|
||||
&& AppMainWindow.get().getStage() == stage
|
||||
&& !AppPrefs.get().performanceMode().get()
|
||||
&& mergeFrame();
|
||||
var seamlessFrameApplied = ctrl.setAppearance(
|
||||
@@ -108,8 +108,8 @@ public class AppModifiedStage extends Stage {
|
||||
boolean seamlessFrame;
|
||||
if (AppPrefs.get().performanceMode().get()
|
||||
|| !mergeFrame()
|
||||
|| AppMainWindow.getInstance() == null
|
||||
|| stage != AppMainWindow.getInstance().getStage()) {
|
||||
|| AppMainWindow.get() == null
|
||||
|| stage != AppMainWindow.get().getStage()) {
|
||||
seamlessFrame = false;
|
||||
} else {
|
||||
// This is not available on Windows 10
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package io.xpipe.app.core.window;
|
||||
|
||||
import io.xpipe.app.platform.PlatformInit;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class AppSideWindow {
|
||||
|
||||
public static Optional<ButtonType> showBlockingAlert(Consumer<Alert> c) {
|
||||
PlatformInit.init(true);
|
||||
|
||||
Supplier<Alert> supplier = () -> {
|
||||
Alert a = createEmptyAlert();
|
||||
var s = (Stage) a.getDialogPane().getScene().getWindow();
|
||||
s.setOnShown(event -> {
|
||||
Platform.runLater(() -> {
|
||||
AppWindowBounds.clampWindow(s).ifPresent(rectangle2D -> {
|
||||
s.setX(rectangle2D.getMinX());
|
||||
s.setY(rectangle2D.getMinY());
|
||||
// Somehow we have to set max size as setting the normal size does not work?
|
||||
s.setMaxWidth(rectangle2D.getWidth());
|
||||
s.setMaxHeight(rectangle2D.getHeight());
|
||||
});
|
||||
});
|
||||
event.consume();
|
||||
});
|
||||
AppWindowBounds.fixInvalidStagePosition(s);
|
||||
AppWindowStyle.addFontSize(s);
|
||||
a.getDialogPane().getScene().addEventHandler(KeyEvent.KEY_PRESSED, event -> {
|
||||
if (new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN).match(event)) {
|
||||
s.close();
|
||||
event.consume();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getCode().equals(KeyCode.ESCAPE)) {
|
||||
s.close();
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
return a;
|
||||
};
|
||||
|
||||
AtomicReference<Optional<ButtonType>> result = new AtomicReference<>();
|
||||
if (!Platform.isFxApplicationThread()) {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
Alert a = supplier.get();
|
||||
c.accept(a);
|
||||
result.set(a.showAndWait());
|
||||
} catch (Throwable t) {
|
||||
result.set(Optional.empty());
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
} else {
|
||||
Alert a = supplier.get();
|
||||
c.accept(a);
|
||||
result.set(a.showAndWait());
|
||||
}
|
||||
return result.get();
|
||||
}
|
||||
|
||||
public static Alert createEmptyAlert() {
|
||||
Alert alert = new Alert(Alert.AlertType.NONE);
|
||||
if (AppMainWindow.get() != null) {
|
||||
alert.initOwner(AppMainWindow.get().getStage());
|
||||
}
|
||||
alert.getDialogPane().getScene().setFill(Color.TRANSPARENT);
|
||||
var stage = (Stage) alert.getDialogPane().getScene().getWindow();
|
||||
AppModifiedStage.prepareStage(stage);
|
||||
AppWindowStyle.addIcons(stage);
|
||||
AppWindowStyle.addStylesheets(alert.getDialogPane().getScene());
|
||||
AppWindowStyle.addNavigationStyleClasses(alert.getDialogPane().getScene());
|
||||
return alert;
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,6 @@ package io.xpipe.app.core.window;
|
||||
|
||||
import io.xpipe.app.core.*;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.platform.InputHelper;
|
||||
import io.xpipe.app.platform.PlatformInit;
|
||||
import io.xpipe.core.OsType;
|
||||
|
||||
import javafx.application.Platform;
|
||||
@@ -11,23 +9,15 @@ import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.input.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class AppWindowHelper {
|
||||
public class AppWindowStyle {
|
||||
|
||||
public static void addMaximizedPseudoClass(Stage stage) {
|
||||
stage.getScene().rootProperty().subscribe(root -> {
|
||||
@@ -43,6 +33,29 @@ public class AppWindowHelper {
|
||||
});
|
||||
}
|
||||
|
||||
public static void addNavigationStyleClasses(Scene scene) {
|
||||
Consumer<Boolean> onInput = kb -> {
|
||||
var r = scene.getRoot();
|
||||
if (r != null) {
|
||||
// This property is broken on some systems
|
||||
var acc = Platform.isAccessibilityActive();
|
||||
r.pseudoClassStateChanged(PseudoClass.getPseudoClass("key-navigation"), kb);
|
||||
r.pseudoClassStateChanged(PseudoClass.getPseudoClass("normal-navigation"), !kb);
|
||||
r.pseudoClassStateChanged(PseudoClass.getPseudoClass("accessibility-navigation"), acc);
|
||||
}
|
||||
};
|
||||
|
||||
scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
|
||||
var c = event.getCode();
|
||||
var list = List.of(KeyCode.SPACE, KeyCode.ENTER, KeyCode.SHIFT, KeyCode.TAB);
|
||||
onInput.accept(list.stream().anyMatch(keyCode -> keyCode == c)
|
||||
|| event.getCode().isNavigationKey());
|
||||
});
|
||||
scene.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
|
||||
onInput.accept(false);
|
||||
});
|
||||
}
|
||||
|
||||
public static void addIcons(Stage stage) {
|
||||
stage.getIcons().clear();
|
||||
|
||||
@@ -59,82 +72,7 @@ public class AppWindowHelper {
|
||||
});
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static Optional<ButtonType> showBlockingAlert(Consumer<Alert> c) {
|
||||
PlatformInit.init(true);
|
||||
|
||||
Supplier<Alert> supplier = () -> {
|
||||
Alert a = AppWindowHelper.createEmptyAlert();
|
||||
var s = (Stage) a.getDialogPane().getScene().getWindow();
|
||||
s.setOnShown(event -> {
|
||||
Platform.runLater(() -> {
|
||||
AppWindowBounds.clampWindow(s).ifPresent(rectangle2D -> {
|
||||
s.setX(rectangle2D.getMinX());
|
||||
s.setY(rectangle2D.getMinY());
|
||||
// Somehow we have to set max size as setting the normal size does not work?
|
||||
s.setMaxWidth(rectangle2D.getWidth());
|
||||
s.setMaxHeight(rectangle2D.getHeight());
|
||||
});
|
||||
});
|
||||
event.consume();
|
||||
});
|
||||
AppWindowBounds.fixInvalidStagePosition(s);
|
||||
AppWindowHelper.addFontSize(s);
|
||||
a.getDialogPane().getScene().addEventHandler(KeyEvent.KEY_PRESSED, event -> {
|
||||
if (new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN).match(event)) {
|
||||
s.close();
|
||||
event.consume();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getCode().equals(KeyCode.ESCAPE)) {
|
||||
s.close();
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
return a;
|
||||
};
|
||||
|
||||
AtomicReference<Optional<ButtonType>> result = new AtomicReference<>();
|
||||
if (!Platform.isFxApplicationThread()) {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
Alert a = supplier.get();
|
||||
c.accept(a);
|
||||
result.set(a.showAndWait());
|
||||
} catch (Throwable t) {
|
||||
result.set(Optional.empty());
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
} else {
|
||||
Alert a = supplier.get();
|
||||
c.accept(a);
|
||||
result.set(a.showAndWait());
|
||||
}
|
||||
return result.get();
|
||||
}
|
||||
|
||||
public static Alert createEmptyAlert() {
|
||||
Alert alert = new Alert(Alert.AlertType.NONE);
|
||||
if (AppMainWindow.getInstance() != null) {
|
||||
alert.initOwner(AppMainWindow.getInstance().getStage());
|
||||
}
|
||||
alert.getDialogPane().getScene().setFill(Color.TRANSPARENT);
|
||||
var stage = (Stage) alert.getDialogPane().getScene().getWindow();
|
||||
AppModifiedStage.prepareStage(stage);
|
||||
addIcons(stage);
|
||||
setupStylesheets(alert.getDialogPane().getScene());
|
||||
return alert;
|
||||
}
|
||||
|
||||
public static void setupStylesheets(Scene scene) {
|
||||
public static void addStylesheets(Scene scene) {
|
||||
AppStyle.addStylesheets(scene);
|
||||
|
||||
scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
|
||||
@@ -146,20 +84,9 @@ public class AppWindowHelper {
|
||||
}
|
||||
});
|
||||
TrackEvent.debug("Set stylesheet reload listener");
|
||||
|
||||
InputHelper.onNavigationInput(scene, (kb) -> {
|
||||
var r = scene.getRoot();
|
||||
if (r != null) {
|
||||
// This property is broken on some systems
|
||||
var acc = Platform.isAccessibilityActive();
|
||||
r.pseudoClassStateChanged(PseudoClass.getPseudoClass("key-navigation"), kb);
|
||||
r.pseudoClassStateChanged(PseudoClass.getPseudoClass("normal-navigation"), !kb);
|
||||
r.pseudoClassStateChanged(PseudoClass.getPseudoClass("accessibility-navigation"), acc);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void setupClickShield(Stage stage) {
|
||||
public static void addClickShield(Stage stage) {
|
||||
if (OsType.getLocal() != OsType.MACOS) {
|
||||
return;
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.xpipe.app.hub.comp;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.augment.GrowAugment;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
@@ -139,8 +138,6 @@ public class DenseStoreEntryComp extends StoreEntryComp {
|
||||
HBox.setHgrow(cr, Priority.ALWAYS);
|
||||
grid.addRow(0, controls);
|
||||
|
||||
GrowAugment.create(true, false).augment(grid);
|
||||
|
||||
grid.getStyleClass().add("store-entry-grid");
|
||||
grid.getStyleClass().add("dense");
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.comp.augment.ContextMenuAugment;
|
||||
import io.xpipe.app.comp.augment.GrowAugment;
|
||||
import io.xpipe.app.comp.base.*;
|
||||
import io.xpipe.app.core.*;
|
||||
import io.xpipe.app.hub.action.HubBranchProvider;
|
||||
@@ -126,7 +125,6 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||
|
||||
var button = new Button();
|
||||
button.setGraphic(r);
|
||||
GrowAugment.create(true, false).augment(new SimpleCompStructure<>(r));
|
||||
button.getStyleClass().add("store-entry-comp");
|
||||
button.setPadding(Insets.EMPTY);
|
||||
button.setMaxWidth(5000);
|
||||
|
||||
@@ -4,7 +4,6 @@ import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.base.DelayedInitComp;
|
||||
import io.xpipe.app.comp.base.LeftSplitPaneComp;
|
||||
import io.xpipe.app.core.AppActionLinkDetector;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.core.window.AppMainWindow;
|
||||
import io.xpipe.app.platform.InputHelper;
|
||||
@@ -26,7 +25,7 @@ public class StoreLayoutComp extends SimpleComp {
|
||||
|
||||
private Region createContent() {
|
||||
var left = new StoreSidebarComp();
|
||||
left.hide(AppMainWindow.getInstance().getStage().widthProperty().lessThan(1000));
|
||||
left.hide(AppMainWindow.get().getStage().widthProperty().lessThan(1000));
|
||||
left.minWidth(270);
|
||||
left.maxWidth(500);
|
||||
left.minHeight(0);
|
||||
@@ -40,13 +39,6 @@ public class StoreLayoutComp extends SimpleComp {
|
||||
AppLayoutModel.get().getSavedState().setSidebarWidth(aDouble);
|
||||
});
|
||||
comp.styleClass("store-layout");
|
||||
comp.apply(struc -> {
|
||||
InputHelper.onKeyCombination(
|
||||
struc.get(), new KeyCodeCombination(KeyCode.V, KeyCombination.SHORTCUT_DOWN), true, keyEvent -> {
|
||||
AppActionLinkDetector.detectOnPaste();
|
||||
keyEvent.consume();
|
||||
});
|
||||
});
|
||||
return comp.createRegion();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.xpipe.app.issue;
|
||||
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.augment.GrowAugment;
|
||||
import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
@@ -66,7 +65,7 @@ public class ErrorHandlerComp extends SimpleComp {
|
||||
});
|
||||
});
|
||||
b.disable(busy);
|
||||
b.apply(GrowAugment.create(true, false));
|
||||
b.maxWidth(2000);
|
||||
return b.createRegion();
|
||||
}
|
||||
|
||||
|
||||
@@ -62,16 +62,4 @@ public class InputHelper {
|
||||
target.addEventHandler(KeyEvent.KEY_PRESSED, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onNavigationInput(EventTarget target, Consumer<Boolean> r) {
|
||||
target.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
|
||||
var c = event.getCode();
|
||||
var list = List.of(KeyCode.SPACE, KeyCode.ENTER, KeyCode.SHIFT, KeyCode.TAB);
|
||||
r.accept(list.stream().anyMatch(keyCode -> keyCode == c)
|
||||
|| event.getCode().isNavigationKey());
|
||||
});
|
||||
target.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
|
||||
r.accept(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ public class TerminalDockComp extends SimpleComp {
|
||||
}
|
||||
|
||||
private void setupListeners(StackPane stack) {
|
||||
var s = AppMainWindow.getInstance().getStage();
|
||||
var s = AppMainWindow.get().getStage();
|
||||
|
||||
var bounds = new ChangeListener<Bounds>() {
|
||||
@Override
|
||||
|
||||
@@ -2,7 +2,8 @@ package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.comp.base.SecretFieldComp;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.window.AppWindowHelper;
|
||||
import io.xpipe.app.core.window.AppSideWindow;
|
||||
import io.xpipe.app.core.window.AppWindowStyle;
|
||||
import io.xpipe.app.secret.SecretManager;
|
||||
import io.xpipe.app.secret.SecretQueryResult;
|
||||
import io.xpipe.app.secret.SecretQueryState;
|
||||
@@ -24,7 +25,7 @@ public class AskpassAlert {
|
||||
|
||||
public static SecretQueryResult queryRaw(String prompt, InPlaceSecretValue secretValue, boolean stealFocus) {
|
||||
var prop = new SimpleObjectProperty<>(secretValue);
|
||||
var r = AppWindowHelper.showBlockingAlert(alert -> {
|
||||
var r = AppSideWindow.showBlockingAlert(alert -> {
|
||||
alert.initModality(Modality.NONE);
|
||||
alert.setTitle(AppI18n.get("askpassAlertTitle"));
|
||||
alert.setHeaderText(prompt);
|
||||
|
||||
@@ -2,7 +2,8 @@ package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.comp.base.TextFieldComp;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.window.AppWindowHelper;
|
||||
import io.xpipe.app.core.window.AppSideWindow;
|
||||
import io.xpipe.app.core.window.AppWindowStyle;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.application.Platform;
|
||||
@@ -17,7 +18,7 @@ public class AsktextAlert {
|
||||
|
||||
public static Optional<String> query(String prompt) {
|
||||
var prop = new SimpleObjectProperty<String>();
|
||||
var r = AppWindowHelper.showBlockingAlert(alert -> {
|
||||
var r = AppSideWindow.showBlockingAlert(alert -> {
|
||||
alert.setTitle(AppI18n.get("asktextAlertTitle"));
|
||||
alert.setHeaderText(prompt);
|
||||
alert.setAlertType(Alert.AlertType.CONFIRMATION);
|
||||
|
||||
@@ -77,9 +77,9 @@ public class RemminaHelper {
|
||||
.getValue(),
|
||||
password != null ? password : "",
|
||||
Math.round(
|
||||
AppMainWindow.getInstance().getStage().getWidth()),
|
||||
AppMainWindow.get().getStage().getWidth()),
|
||||
Math.round(
|
||||
AppMainWindow.getInstance().getStage().getHeight()));
|
||||
AppMainWindow.get().getStage().getHeight()));
|
||||
Files.createDirectories(file.getParent());
|
||||
Files.writeString(file, string);
|
||||
return file;
|
||||
|
||||
@@ -78,7 +78,7 @@ public class LocalIdentityConvertHubLeafProvider implements HubLeafProvider<Loca
|
||||
|
||||
// Ugly solution to sync key file if needed
|
||||
Platform.runLater(() -> {
|
||||
var found = AppMainWindow.getInstance()
|
||||
var found = AppMainWindow.get()
|
||||
.getStage()
|
||||
.getScene()
|
||||
.getRoot()
|
||||
|
||||
Reference in New Issue
Block a user