mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-04-23 16:09:20 -04:00
Rework
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(() -> {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 -> {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
53
app/src/main/java/io/xpipe/app/util/NodeCallback.java
Normal file
53
app/src/main/java/io/xpipe/app/util/NodeCallback.java
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
BIN
dist/logo/ico/logo_48x48.png
vendored
BIN
dist/logo/ico/logo_48x48.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.0 KiB |
BIN
dist/logo/logo.ico
vendored
BIN
dist/logo/logo.ico
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 403 KiB After Width: | Height: | Size: 131 KiB |
Reference in New Issue
Block a user