This commit is contained in:
crschnick
2025-03-28 23:27:13 +00:00
parent 2d513af12b
commit 33d396b7e1
12 changed files with 112 additions and 187 deletions

View File

@@ -6,11 +6,9 @@ import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.file.BrowserFileSystemTabComp;
import io.xpipe.app.browser.file.BrowserFileSystemTabModel;
import io.xpipe.app.comp.Comp;
import io.xpipe.app.comp.base.DialogComp;
import io.xpipe.app.comp.base.LeftSplitPaneComp;
import io.xpipe.app.comp.base.StackComp;
import io.xpipe.app.comp.base.VerticalComp;
import io.xpipe.app.comp.base.*;
import io.xpipe.app.comp.store.StoreEntryWrapper;
import io.xpipe.app.core.AppFontSizes;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.ext.ShellStore;
import io.xpipe.app.storage.DataStoreEntryRef;
@@ -21,16 +19,11 @@ import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.FileSystemStore;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.ListChangeListener;
import javafx.geometry.Pos;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
import java.util.List;
import java.util.function.BiConsumer;
@@ -38,65 +31,51 @@ import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class BrowserFileChooserSessionComp extends DialogComp {
public class BrowserFileChooserSessionComp extends ModalOverlayContentComp {
private final Stage stage;
private final BrowserFileChooserSessionModel model;
public BrowserFileChooserSessionComp(Stage stage, BrowserFileChooserSessionModel model) {
this.stage = stage;
public BrowserFileChooserSessionComp(BrowserFileChooserSessionModel model) {
this.model = model;
}
public static void openSingleFile(
Supplier<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileReference> file, boolean save) {
PlatformThread.runLaterIfNeeded(() -> {
var lastWindow = Window.getWindows().stream()
.filter(window -> window.isFocused())
.findFirst();
var model = new BrowserFileChooserSessionModel(BrowserFileSystemTabModel.SelectionMode.SINGLE_FILE);
DialogComp.showWindow(save ? "saveFileTitle" : "openFileTitle", stage -> {
stage.addEventFilter(WindowEvent.WINDOW_HIDDEN, event -> {
lastWindow.ifPresent(window -> window.requestFocus());
});
var comp = new BrowserFileChooserSessionComp(stage, model);
comp.apply(struc -> struc.get().setPrefSize(1200, 700))
.styleClass("browser")
.styleClass("chooser");
return comp;
});
model.setOnFinish(fileStores -> {
file.accept(fileStores.size() > 0 ? fileStores.getFirst() : null);
});
ThreadHelper.runAsync(() -> {
model.openFileSystemAsync(store.get(), null, null);
});
var model = new BrowserFileChooserSessionModel(BrowserFileSystemTabModel.SelectionMode.SINGLE_FILE);
model.setOnFinish(fileStores -> {
file.accept(fileStores.size() > 0 ? fileStores.getFirst() : null);
});
var comp = new BrowserFileChooserSessionComp(model)
.styleClass("browser")
.styleClass("chooser");
var selection = new SimpleStringProperty();
model.getFileSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
selection.set(c.getList().size() > 0 ? c.getList().getFirst().getRawFileEntry().getPath().toString() : null);
});
var selectionField = new TextFieldComp(selection);
selectionField.apply(struc -> {
struc.get().setEditable(false);
AppFontSizes.base(struc.get());
});
selectionField.styleClass("chooser-selection");
selectionField.hgrow();
var modal = ModalOverlay.of(save ? "saveFileTitle" : "openFileTitle", comp);
modal.setRequireCloseButtonForClose(true);
modal.addButtonBarComp(selectionField);
modal.addButton(new ModalButton("select", () -> model.finishChooser(), true, true));
modal.show();
ThreadHelper.runAsync(() -> {
model.openFileSystemAsync(store.get(), null, null);
});
}
@Override
protected String finishKey() {
return "select";
protected void onClose() {
model.closeFileSystem();
}
@Override
protected Comp<?> pane(Comp<?> content) {
return content;
}
@Override
protected void finish() {
stage.close();
model.finishChooser();
}
@Override
protected void discard() {
model.finishWithoutChoice();
}
@Override
public Comp<?> content() {
protected Region createSimple() {
Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> {
return (storeEntryWrapper.getEntry().getStore() instanceof ShellStore)
&& storeEntryWrapper.getEntry().getValidity().isUsable();
@@ -163,33 +142,6 @@ public class BrowserFileChooserSessionComp extends DialogComp {
struc.getLeft().setMinWidth(200);
struc.getLeft().setMaxWidth(500);
});
return splitPane;
}
@Override
public Comp<?> bottom() {
return Comp.of(() -> {
var selected = new HBox();
selected.setAlignment(Pos.CENTER_LEFT);
model.getFileSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
PlatformThread.runLaterIfNeeded(() -> {
selected.getChildren()
.setAll(c.getList().stream()
.map(s -> {
var field = new TextField(
s.getRawFileEntry().getPath().toString());
field.setEditable(false);
field.getStyleClass().add("chooser-selection");
HBox.setHgrow(field, Priority.ALWAYS);
return field;
})
.toList());
});
});
var bottomBar = new HBox(selected);
HBox.setHgrow(selected, Priority.ALWAYS);
bottomBar.setAlignment(Pos.CENTER);
return bottomBar;
});
return splitPane.createRegion();
}
}

View File

@@ -65,7 +65,7 @@ public class BrowserFileChooserSessionModel extends BrowserAbstractSessionModel<
onFinish.accept(stores);
}
public void finishWithoutChoice() {
public void closeFileSystem() {
synchronized (BrowserFileChooserSessionModel.this) {
var open = selectedEntry.getValue();
if (open != null) {

View File

@@ -46,7 +46,7 @@ public class BrowserNavBarComp extends Comp<BrowserNavBarComp.Structure> {
? BrowserIconManager.getFileIcon(model.getCurrentDirectory())
: null;
},
model.getCurrentPath());
PlatformThread.sync(model.getCurrentPath()));
var breadcrumbsGraphic = PrettyImageHelper.ofFixedSize(graphic, 24, 24)
.styleClass("path-graphic")
.createRegion();
@@ -85,7 +85,7 @@ public class BrowserNavBarComp extends Comp<BrowserNavBarComp.Structure> {
&& !model.getInOverview().get();
},
pathRegion.focusedProperty(),
model.getInOverview()));
PlatformThread.sync(model.getInOverview())));
var stack = new StackPane(pathRegion, breadcrumbsRegion);
stack.setAlignment(Pos.CENTER_LEFT);
pathRegion.prefHeightProperty().bind(stack.heightProperty());
@@ -135,7 +135,9 @@ public class BrowserNavBarComp extends Comp<BrowserNavBarComp.Structure> {
private Comp<CompStructure<TextField>> createPathBar() {
var path = new SimpleStringProperty();
model.getCurrentPath().subscribe((newValue) -> {
path.set(newValue != null ? newValue.toString() : null);
PlatformThread.runLaterIfNeeded(() -> {
path.set(newValue != null ? newValue.toString() : null);
});
});
path.addListener((observable, oldValue, newValue) -> {
ThreadHelper.runFailableAsync(() -> {

View File

@@ -25,29 +25,6 @@ import java.util.function.Function;
public abstract class DialogComp extends Comp<CompStructure<Region>> {
public static void showWindow(String titleKey, Function<Stage, DialogComp> f) {
var loading = new SimpleBooleanProperty();
var dialog = new AtomicReference<DialogComp>();
Platform.runLater(() -> {
var stage = AppWindowHelper.sideWindow(
AppI18n.get(titleKey),
window -> {
var c = f.apply(window);
dialog.set(c);
loading.bind(c.busy());
return c;
},
false,
loading);
stage.setOnCloseRequest(event -> {
if (dialog.get() != null) {
dialog.get().discard();
}
});
stage.show();
});
}
protected Region createNavigation() {
HBox buttons = new HBox();
buttons.setFillHeight(true);

View File

@@ -189,6 +189,7 @@ public class ModalOverlayComp extends SimpleComp {
if (newValue.getButtons().size() > 0) {
var buttonBar = new HBox();
buttonBar.getStyleClass().add("button-bar");
buttonBar.setSpacing(10);
buttonBar.setAlignment(Pos.CENTER_RIGHT);
for (var o : newValue.getButtons()) {
@@ -199,7 +200,7 @@ public class ModalOverlayComp extends SimpleComp {
}
}
content.getChildren().add(buttonBar);
AppFontSizes.xs(buttonBar);
AppFontSizes.base(buttonBar);
}
var modalBox = new ModalBox(pane, content) {

View File

@@ -57,6 +57,8 @@ public class AppProperties {
boolean aotTrainMode;
boolean debugPlatformThreadAccess;
AppArguments arguments;
XPipeDaemonMode explicitMode;
@@ -105,6 +107,9 @@ public class AppProperties {
debugThreads = Optional.ofNullable(System.getProperty("io.xpipe.app.debugThreads"))
.map(Boolean::parseBoolean)
.orElse(false);
debugPlatformThreadAccess = Optional.ofNullable(System.getProperty("io.xpipe.app.debugPlatformThreadAccess"))
.map(Boolean::parseBoolean)
.orElse(false);
defaultDataDir = Path.of(System.getProperty("user.home"), isStaging() ? ".xpipe-ptb" : ".xpipe");
dataDir = Optional.ofNullable(System.getProperty("io.xpipe.app.dataDir"))
.map(s -> {

View File

@@ -10,6 +10,7 @@ import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.prefs.CloseBehaviourDialog;
import io.xpipe.app.resources.AppImages;
import io.xpipe.app.util.LicenseProvider;
import io.xpipe.app.util.NodeCallback;
import io.xpipe.app.util.PlatformThread;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.OsType;
@@ -97,6 +98,9 @@ public class AppMainWindow {
return stage.isFocused() ? 1.0 : 0.8;
},
stage.focusedProperty()));
if (AppProperties.get().isDebugPlatformThreadAccess()) {
NodeCallback.watchPlatformThreadChanges(content);
}
var scene = new Scene(content, -1, -1, false);
content.prefWidthProperty().bind(scene.widthProperty());
content.prefHeightProperty().bind(scene.heightProperty());

View File

@@ -84,32 +84,6 @@ public class AppWindowHelper {
});
}
public static Stage sideWindow(
String title, Function<Stage, Comp<?>> contentFunc, boolean bindSize, ObservableValue<Boolean> loading) {
var stage = AppWindowBounds.centerStage();
ModifiedStage.prepareStage(stage);
if (AppMainWindow.getInstance() != null) {
stage.initOwner(AppMainWindow.getInstance().getStage());
}
stage.setTitle(title);
addIcons(stage);
setupContent(stage, contentFunc, bindSize, loading);
setupStylesheets(stage.getScene());
AppWindowHelper.setupClickShield(stage);
AppWindowBounds.fixInvalidStagePosition(stage);
AppWindowHelper.addFontSize(stage);
if (AppPrefs.get() != null && AppPrefs.get().enforceWindowModality().get()) {
stage.initModality(Modality.WINDOW_MODAL);
}
stage.setOnShown(e -> {
AppTheme.initThemeHandlers(stage);
});
return stage;
}
public static void setContent(Alert alert, String s) {
alert.getDialogPane().setMinWidth(505);
alert.getDialogPane().setPrefWidth(505);
@@ -267,52 +241,4 @@ public class AppWindowHelper {
}
});
}
public static void setupContent(
Stage stage, Function<Stage, Comp<?>> contentFunc, boolean bindSize, ObservableValue<Boolean> loading) {
var baseComp = contentFunc.apply(stage);
var content = loading != null ? LoadingOverlayComp.noProgress(baseComp, loading) : baseComp;
var contentR = content.createRegion();
var scene = new Scene(bindSize ? new Pane(contentR) : contentR, -1, -1, false);
scene.setFill(Color.TRANSPARENT);
stage.setScene(scene);
contentR.requestFocus();
if (bindSize) {
bindSize(stage, contentR);
stage.setResizable(false);
}
scene.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
if (new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN).match(event)) {
stage.close();
event.consume();
}
});
}
private static void bindSize(Stage stage, Region r) {
if (r.getPrefWidth() == Region.USE_COMPUTED_SIZE) {
r.widthProperty().addListener((c, o, n) -> {
stage.sizeToScene();
});
} else {
stage.setWidth(r.getPrefWidth());
r.prefWidthProperty().addListener((c, o, n) -> {
stage.sizeToScene();
});
}
if (r.getPrefHeight() == Region.USE_COMPUTED_SIZE) {
r.heightProperty().addListener((c, o, n) -> {
stage.sizeToScene();
});
} else {
stage.setHeight(r.getPrefHeight());
r.prefHeightProperty().addListener((c, o, n) -> {
stage.sizeToScene();
});
}
stage.sizeToScene();
}
}

View File

@@ -0,0 +1,53 @@
package io.xpipe.app.util;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
import javafx.scene.Node;
import javafx.scene.Parent;
import java.util.function.Consumer;
public class NodeCallback {
public static void watchGraph(Node node, Consumer<Node> callback) {
if (node instanceof Parent p) {
for (Node c : p.getChildrenUnmodifiable()) {
watchGraph(c, callback);
}
p.getChildrenUnmodifiable().addListener((ListChangeListener<? super Node>) change -> {
for (Node c : change.getList()) {
watchGraph(c, callback);
}
});
}
callback.accept(node);
}
public static void watchPlatformThreadChanges(Node node) {
watchGraph(node, c -> {
if (c instanceof Parent p) {
p.getChildrenUnmodifiable().addListener((ListChangeListener<? super Node>) change -> {
checkPlatformThread();
});
}
c.visibleProperty().addListener((observable, oldValue, newValue) -> {
checkPlatformThread();
});
c.boundsInParentProperty().addListener((observable, oldValue, newValue) -> {
checkPlatformThread();
});
c.managedProperty().addListener((observable, oldValue, newValue) -> {
checkPlatformThread();
});
c.opacityProperty().addListener((observable, oldValue, newValue) -> {
checkPlatformThread();
});
});
}
private static void checkPlatformThread() {
if (!Platform.isFxApplicationThread()) {
throw new IllegalStateException("Not in Fx application thread");
}
}
}

View File

@@ -191,8 +191,13 @@
-fx-border-color: -color-border-default;
}
.browser .chooser-selection {
-fx-background-color: -color-bg-default;
.browser.chooser {
-fx-padding: -11 -3 -10 -3;
}
.chooser-selection {
-fx-background-color: -color-bg-subtle;
-fx-background-radius: 2;
}
.browser .singular {

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
dist/logo/logo.ico vendored
View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 403 KiB

After

Width:  |  Height:  |  Size: 131 KiB