mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-09-30 00:56:56 +13:00
Improve window clamping
This commit is contained in:
parent
d04271f632
commit
2b95ea5d6e
4 changed files with 81 additions and 52 deletions
|
@ -6,14 +6,11 @@ import io.xpipe.app.issue.TrackEvent;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.geometry.BoundingBox;
|
|
||||||
import javafx.geometry.Bounds;
|
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Rectangle2D;
|
import javafx.geometry.Rectangle2D;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.control.Alert;
|
import javafx.scene.control.Alert;
|
||||||
import javafx.scene.control.Button;
|
|
||||||
import javafx.scene.control.ButtonType;
|
import javafx.scene.control.ButtonType;
|
||||||
import javafx.scene.input.KeyCode;
|
import javafx.scene.input.KeyCode;
|
||||||
import javafx.scene.input.KeyEvent;
|
import javafx.scene.input.KeyEvent;
|
||||||
|
@ -23,7 +20,9 @@ import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
import javafx.stage.Screen;
|
import javafx.stage.Screen;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.Window;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
@ -76,12 +75,17 @@ public class AppWindowHelper {
|
||||||
});
|
});
|
||||||
|
|
||||||
centerToMainWindow(stage);
|
centerToMainWindow(stage);
|
||||||
clampWindow(stage);
|
clampWindow(stage).ifPresent(rectangle2D -> {
|
||||||
|
stage.setX(rectangle2D.getMinX());
|
||||||
|
stage.setY(rectangle2D.getMinY());
|
||||||
|
stage.setWidth(rectangle2D.getWidth());
|
||||||
|
stage.setHeight(rectangle2D.getHeight());
|
||||||
|
});
|
||||||
});
|
});
|
||||||
return stage;
|
return stage;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void centerToMainWindow(Stage childStage) {
|
private static void centerToMainWindow(Window childStage) {
|
||||||
if (App.getApp() == null) {
|
if (App.getApp() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -92,7 +96,7 @@ public class AppWindowHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showAlert(
|
public static void showAlert(
|
||||||
Consumer<Alert> c, ObservableValue<Boolean> loading, Consumer<Optional<ButtonType>> bt) {
|
Consumer<Alert> c, Consumer<Optional<ButtonType>> bt) {
|
||||||
ThreadHelper.runAsync(() -> {
|
ThreadHelper.runAsync(() -> {
|
||||||
var r = showBlockingAlert(c);
|
var r = showBlockingAlert(c);
|
||||||
if (bt != null) {
|
if (bt != null) {
|
||||||
|
@ -101,30 +105,28 @@ public class AppWindowHelper {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showAndWaitForWindow(Supplier<Stage> s) {
|
|
||||||
if (!Platform.isFxApplicationThread()) {
|
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
s.get().showAndWait();
|
|
||||||
latch.countDown();
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
latch.await();
|
|
||||||
} catch (InterruptedException ignored) {
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
s.get().showAndWait();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Optional<ButtonType> showBlockingAlert(Consumer<Alert> c) {
|
public static Optional<ButtonType> showBlockingAlert(Consumer<Alert> c) {
|
||||||
|
Supplier<Alert> supplier = () -> {
|
||||||
|
Alert a = AppWindowHelper.createEmptyAlert();
|
||||||
|
AppFont.normal(a.getDialogPane());
|
||||||
|
var s = (Stage) a.getDialogPane().getScene().getWindow();
|
||||||
|
a.getDialogPane().getScene().getWindow().setOnShown(event -> {
|
||||||
|
clampWindow((Stage) a.getDialogPane().getScene().getWindow()).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());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return a;
|
||||||
|
};
|
||||||
|
|
||||||
AtomicReference<Optional<ButtonType>> result = new AtomicReference<>();
|
AtomicReference<Optional<ButtonType>> result = new AtomicReference<>();
|
||||||
if (!Platform.isFxApplicationThread()) {
|
if (!Platform.isFxApplicationThread()) {
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
Alert a = AppWindowHelper.createEmptyAlert();
|
Alert a = supplier.get();
|
||||||
AppFont.normal(a.getDialogPane());
|
|
||||||
|
|
||||||
c.accept(a);
|
c.accept(a);
|
||||||
result.set(a.showAndWait());
|
result.set(a.showAndWait());
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
|
@ -134,15 +136,8 @@ public class AppWindowHelper {
|
||||||
} catch (InterruptedException ignored) {
|
} catch (InterruptedException ignored) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Alert a = createEmptyAlert();
|
Alert a = supplier.get();
|
||||||
AppFont.normal(a.getDialogPane());
|
|
||||||
c.accept(a);
|
c.accept(a);
|
||||||
|
|
||||||
Button button = (Button) a.getDialogPane().lookupButton(ButtonType.OK);
|
|
||||||
if (button != null) {
|
|
||||||
button.getStyleClass().add("ok-button");
|
|
||||||
}
|
|
||||||
|
|
||||||
result.set(a.showAndWait());
|
result.set(a.showAndWait());
|
||||||
}
|
}
|
||||||
return result.get();
|
return result.get();
|
||||||
|
@ -200,32 +195,70 @@ public class AppWindowHelper {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void clampWindow(Stage stage) {
|
private static Optional<Rectangle2D> clampWindow(Stage stage) {
|
||||||
|
if (!areNumbersValid(stage.getWidth(), stage.getHeight())) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
var allScreenBounds = computeWindowScreenBounds(stage);
|
var allScreenBounds = computeWindowScreenBounds(stage);
|
||||||
double x = stage.getX();
|
if (!areNumbersValid(allScreenBounds.getMinX(), allScreenBounds.getMinY(), allScreenBounds.getMaxX(), allScreenBounds.getMaxY())) {
|
||||||
if (x < allScreenBounds.getMinX()) {
|
return Optional.empty();
|
||||||
stage.setX(x = allScreenBounds.getMinX());
|
|
||||||
}
|
}
|
||||||
double y = stage.getY();
|
|
||||||
if (y < allScreenBounds.getMinY()) {
|
// Alerts do not have a custom x/y set, but we are able to handle that
|
||||||
stage.setY(y = allScreenBounds.getMinY());
|
|
||||||
|
boolean changed = false;
|
||||||
|
|
||||||
|
double x = 0;
|
||||||
|
if (areNumbersValid(stage.getX())) {
|
||||||
|
x = stage.getX();
|
||||||
|
if (x < allScreenBounds.getMinX()) {
|
||||||
|
x = allScreenBounds.getMinX();
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double y = 0;
|
||||||
|
if (areNumbersValid(stage.getY())) {
|
||||||
|
y = stage.getY();
|
||||||
|
if (y < allScreenBounds.getMinY()) {
|
||||||
|
y = allScreenBounds.getMinY();
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
double w = stage.getWidth();
|
double w = stage.getWidth();
|
||||||
double h = stage.getHeight();
|
double h = stage.getHeight();
|
||||||
if (x + w > allScreenBounds.getMaxX()) {
|
if (x + w > allScreenBounds.getMaxX()) {
|
||||||
stage.setWidth(allScreenBounds.getMaxX() - x);
|
w = allScreenBounds.getMaxX() - x;
|
||||||
|
changed = true;
|
||||||
}
|
}
|
||||||
if (y + h > allScreenBounds.getMaxY()) {
|
if (y + h > allScreenBounds.getMaxY()) {
|
||||||
stage.setHeight(allScreenBounds.getMaxY() - y);
|
h = allScreenBounds.getMaxY() - y;
|
||||||
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return changed ? Optional.of(new Rectangle2D(x, y, w, h)) : Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Bounds computeWindowScreenBounds(Stage stage) {
|
private static boolean areNumbersValid(double... args) {
|
||||||
|
return Arrays.stream(args).allMatch(Double::isFinite);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Screen> getWindowScreens(Stage stage) {
|
||||||
|
if (!areNumbersValid(stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight())) {
|
||||||
|
return stage.getOwner() != null && stage.getOwner() instanceof Stage ownerStage ? getWindowScreens(ownerStage) : List.of(Screen.getPrimary());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Screen.getScreensForRectangle(new Rectangle2D(stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Rectangle2D computeWindowScreenBounds(Stage stage) {
|
||||||
double minX = Double.POSITIVE_INFINITY ;
|
double minX = Double.POSITIVE_INFINITY ;
|
||||||
double minY = Double.POSITIVE_INFINITY ;
|
double minY = Double.POSITIVE_INFINITY ;
|
||||||
double maxX = Double.NEGATIVE_INFINITY ;
|
double maxX = Double.NEGATIVE_INFINITY ;
|
||||||
double maxY = Double.NEGATIVE_INFINITY ;
|
double maxY = Double.NEGATIVE_INFINITY ;
|
||||||
for (Screen screen : Screen.getScreensForRectangle(new Rectangle2D(stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight()))) {
|
for (Screen screen : getWindowScreens(stage)) {
|
||||||
Rectangle2D screenBounds = screen.getBounds();
|
Rectangle2D screenBounds = screen.getBounds();
|
||||||
if (screenBounds.getMinX() < minX) {
|
if (screenBounds.getMinX() < minX) {
|
||||||
minX = screenBounds.getMinX();
|
minX = screenBounds.getMinX();
|
||||||
|
@ -242,7 +275,7 @@ public class AppWindowHelper {
|
||||||
}
|
}
|
||||||
// Taskbar adjustment
|
// Taskbar adjustment
|
||||||
maxY -= 50;
|
maxY -= 50;
|
||||||
return new BoundingBox(minX, minY, maxX-minX, maxY-minY);
|
return new Rectangle2D(minX, minY, maxX-minX, maxY-minY);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void bindSize(Stage stage, Region r) {
|
private static void bindSize(Stage stage, Region r) {
|
||||||
|
|
|
@ -43,8 +43,6 @@ public class UpdateChangelogAlert {
|
||||||
alert.getDialogPane().setContent(markdown);
|
alert.getDialogPane().setContent(markdown);
|
||||||
|
|
||||||
alert.getButtonTypes().add(new ButtonType(AppI18n.get("gotIt"), ButtonBar.ButtonData.OK_DONE));
|
alert.getButtonTypes().add(new ButtonType(AppI18n.get("gotIt"), ButtonBar.ButtonData.OK_DONE));
|
||||||
},
|
}, r -> r.filter(b -> b.getButtonData().isDefaultButton()).ifPresent(t -> {}));
|
||||||
null,
|
|
||||||
r -> r.filter(b -> b.getButtonData().isDefaultButton()).ifPresent(t -> {}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,9 +57,7 @@ public class MacOsPermissions {
|
||||||
a.getButtonTypes().clear();
|
a.getButtonTypes().clear();
|
||||||
a.getButtonTypes().add(ButtonType.CANCEL);
|
a.getButtonTypes().add(ButtonType.CANCEL);
|
||||||
alert.set(a);
|
alert.set(a);
|
||||||
},
|
}, buttonType -> {
|
||||||
null,
|
|
||||||
buttonType -> {
|
|
||||||
alert.get().close();
|
alert.get().close();
|
||||||
state.set(false);
|
state.set(false);
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,7 +13,7 @@ dataSourceIntroText=Text data sources contain readable text that can\ncome in a
|
||||||
dataSourceIntroBinary=Binary data sources contain binary data. They\ncan be used when the data should be handled and preserved byte by byte.
|
dataSourceIntroBinary=Binary data sources contain binary data. They\ncan be used when the data should be handled and preserved byte by byte.
|
||||||
dataSourceIntroCollection=Collection data sources contain multiple sub data sources. \nExamples are zip files or file system directories.
|
dataSourceIntroCollection=Collection data sources contain multiple sub data sources. \nExamples are zip files or file system directories.
|
||||||
storeIntroTitle=Connection Hub
|
storeIntroTitle=Connection Hub
|
||||||
storeIntroDescription=Here you can manage all your shell connections, local and remote, in one place
|
storeIntroDescription=Here you can manage all your local and remote shell connections in one place
|
||||||
storeStreamDescription=Stream connections produce raw byte data\nthat can be used to construct data sources from.
|
storeStreamDescription=Stream connections produce raw byte data\nthat can be used to construct data sources from.
|
||||||
storeMachineDescription=To start off, here you can quickly detect available\nconnections automatically and choose which ones to add.
|
storeMachineDescription=To start off, here you can quickly detect available\nconnections automatically and choose which ones to add.
|
||||||
detectConnections=Search for connections
|
detectConnections=Search for connections
|
||||||
|
|
Loading…
Reference in a new issue