Improve split panes

This commit is contained in:
crschnick committed 2023-11-06 19:21:57 +00:00
1 parent 36a9a78896
commit 812a97d365
13 files changed
+202 -93

No files matched your search

@@ -7,10 +7,13 @@ import io.xpipe.app.browser.icon.DirectoryType;
import io.xpipe.app.browser.icon.FileIconManager;
import io.xpipe.app.browser.icon.FileType;
import io.xpipe.app.comp.base.MultiContentComp;
import io.xpipe.app.comp.base.SideSplitPaneComp;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
@@ -26,12 +29,12 @@ import javafx.collections.ListChangeListener;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.input.DragEvent;
import javafx.scene.layout.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -55,42 +58,35 @@ public class BrowserComp extends SimpleComp {
FileIconManager.loadIfNecessary();
});
var bookmarksList = new BrowserBookmarkList(model).createRegion();
VBox.setVgrow(bookmarksList, Priority.ALWAYS);
var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage())
.hide(PlatformThread.sync(Bindings.createBooleanBinding(
() -> {
if (model.getOpenFileSystems().size() == 0) {
return true;
}
var bookmarksList = new BrowserBookmarkList(model).vgrow();
var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage()).hide(
PlatformThread.sync(Bindings.createBooleanBinding(() -> {
if (model.getOpenFileSystems().size() == 0) {
return true;
}
if (model.getMode().isChooser()) {
return true;
}
if (model.getMode().isChooser()) {
return true;
}
// Also show on local
if (model.getSelected().getValue() != null) {
// return model.getSelected().getValue().isLocal();
}
// Also show on local
if (model.getSelected().getValue() != null) {
// return model.getSelected().getValue().isLocal();
}
return false;
},
model.getOpenFileSystems(),
model.getSelected())))
.createRegion();
localDownloadStage.setPrefHeight(200);
localDownloadStage.setMaxHeight(200);
var vertical = new VBox(bookmarksList, localDownloadStage);
vertical.setFillWidth(true);
return false;
}, model.getOpenFileSystems(), model.getSelected())));
localDownloadStage.prefHeight(200);
localDownloadStage.maxHeight(200);
var vertical = new VerticalComp(List.of(bookmarksList, localDownloadStage));
var splitPane = new SplitPane(vertical, createTabs());
splitPane
.widthProperty()
.addListener(
// set sidebar width in pixels depending on split pane width
(obs, old, val) -> splitPane.setDividerPosition(0, 360 / splitPane.getWidth()));
var r = addBottomBar(splitPane);
var splitPane = new SideSplitPaneComp(vertical, createTabs()).withInitialWidth(
AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth()).withOnDividerChange(
AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth).apply(struc -> {
struc.getLeft().setMinWidth(200);
struc.getLeft().setMaxWidth(500);
});
var r = addBottomBar(splitPane.createRegion());
r.getStyleClass().add("browser");
// AppFont.small(r);
return r;
@@ -108,16 +104,12 @@ public class BrowserComp extends SimpleComp {
selected.setSpacing(10);
model.getSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
PlatformThread.runLaterIfNeeded(() -> {
selected.getChildren()
.setAll(c.getList().stream()
.map(s -> {
var field =
new TextField(s.getRawFileEntry().getPath());
field.setEditable(false);
field.setPrefWidth(400);
return field;
})
.toList());
selected.getChildren().setAll(c.getList().stream().map(s -> {
var field = new TextField(s.getRawFileEntry().getPath());
field.setEditable(false);
field.setPrefWidth(500);
return field;
}).toList());
});
});
var spacer = new Spacer(Orientation.HORIZONTAL);
@@ -135,15 +127,14 @@ public class BrowserComp extends SimpleComp {
return layout;
}
private Node createTabs() {
var multi = new MultiContentComp(Map.<Comp<?>, ObservableValue<Boolean>>of(
Comp.of(() -> createTabPane()),
private Comp<?> createTabs() {
var multi = new MultiContentComp(Map.<Comp<?>, ObservableValue<Boolean>>of(Comp.of(() -> createTabPane()),
BindingsHelper.persist(Bindings.isNotEmpty(model.getOpenFileSystems())),
new BrowserWelcomeComp(model).apply(struc -> StackPane.setAlignment(struc.get(), Pos.CENTER_LEFT)),
Bindings.createBooleanBinding(() -> {
return model.getOpenFileSystems().size() == 0 && !model.getMode().isChooser();
}, model.getOpenFileSystems())));
return multi.createRegion();
return multi;
}
private TabPane createTabPane() {
@@ -163,8 +154,7 @@ public class BrowserComp extends SimpleComp {
map.put(v, t);
tabs.getTabs().add(t);
});
tabs.getSelectionModel()
.select(model.getOpenFileSystems().indexOf(model.getSelected().getValue()));
tabs.getSelectionModel().select(model.getOpenFileSystems().indexOf(model.getSelected().getValue()));
// Used for ignoring changes by the tabpane when new tabs are added. We want to perform the selections manually!
var modifying = new SimpleBooleanProperty();
@@ -180,9 +170,9 @@ public class BrowserComp extends SimpleComp {
return;
}
var source = map.entrySet().stream()
.filter(openFileSystemModelTabEntry ->
openFileSystemModelTabEntry.getValue().equals(newValue))
var source = map.entrySet()
.stream()
.filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getValue().equals(newValue))
.findAny()
.map(Map.Entry::getKey)
.orElse(null);
@@ -197,9 +187,9 @@ public class BrowserComp extends SimpleComp {
return;
}
var toSelect = map.entrySet().stream()
.filter(openFileSystemModelTabEntry ->
openFileSystemModelTabEntry.getKey().equals(newValue))
var toSelect = map.entrySet()
.stream()
.filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getKey().equals(newValue))
.findAny()
.map(Map.Entry::getValue)
.orElse(null);
@@ -238,9 +228,9 @@ public class BrowserComp extends SimpleComp {
tabs.getTabs().addListener((ListChangeListener<? super Tab>) c -> {
while (c.next()) {
for (var r : c.getRemoved()) {
var source = map.entrySet().stream()
.filter(openFileSystemModelTabEntry ->
openFileSystemModelTabEntry.getValue().equals(r))
var source = map.entrySet()
.stream()
.filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getValue().equals(r))
.findAny()
.orElse(null);
@@ -263,19 +253,14 @@ public class BrowserComp extends SimpleComp {
ring.setMinSize(16, 16);
ring.setPrefSize(16, 16);
ring.setMaxSize(16, 16);
ring.progressProperty()
.bind(Bindings.createDoubleBinding(
() -> model.getBusy().get() ? -1d : 0, PlatformThread.sync(model.getBusy())));
ring.progressProperty().bind(Bindings.createDoubleBinding(() -> model.getBusy().get() ? -1d : 0, PlatformThread.sync(model.getBusy())));
var image = model.getEntry().get().getProvider().getDisplayIconFileName(model.getEntry().getStore());
var logo = PrettyImageHelper.ofFixedSquare(image, 16).createRegion();
tab.graphicProperty()
.bind(Bindings.createObjectBinding(
() -> {
return model.getBusy().get() ? ring : logo;
},
PlatformThread.sync(model.getBusy())));
tab.graphicProperty().bind(Bindings.createObjectBinding(() -> {
return model.getBusy().get() ? ring : logo;
}, PlatformThread.sync(model.getBusy())));
tab.setText(model.getName());
tab.setContent(new OpenFileSystemComp(model).createSimple());
@@ -301,9 +286,7 @@ public class BrowserComp extends SimpleComp {
c.getStyleClass().add(color.getId());
}
new FancyTooltipAugment<>(new SimpleStringProperty(model.getTooltip())).augment(c);
c.addEventHandler(
DragEvent.DRAG_ENTERED,
mouseEvent -> Platform.runLater(() -> tabs.getSelectionModel().select(tab)));
c.addEventHandler(DragEvent.DRAG_ENTERED, mouseEvent -> Platform.runLater(() -> tabs.getSelectionModel().select(tab)));
});
}
});
@@ -0,0 +1,81 @@
package io.xpipe.app.comp.base;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import javafx.scene.control.SplitPane;
import javafx.scene.layout.Region;
import lombok.Value;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
public class SideSplitPaneComp extends Comp<SideSplitPaneComp.Structure> {
private final Comp<?> left;
private final Comp<?> center;
private Double initialWidth;
private Consumer<Double> onDividerChange;
public SideSplitPaneComp(Comp<?> left, Comp<?> center) {
this.left = left;
this.center = center;
}
@Override
public Structure createBase() {
var c = center.createRegion();
var sidebar = left.createRegion();
if (initialWidth != null) {
sidebar.setPrefWidth(initialWidth);
}
var r = new SplitPane(sidebar, c);
AtomicBoolean setInitial = new AtomicBoolean(false);
r.widthProperty().addListener((observable, oldValue, newValue) -> {
if (newValue.doubleValue() <= 0) {
return;
}
if (!setInitial.get() && initialWidth != null) {
r.getDividers().get(0).setPosition(initialWidth / newValue.doubleValue());
setInitial.set(true);
}
});
SplitPane.setResizableWithParent(sidebar, false);
r.getDividers().get(0).positionProperty().addListener((observable, oldValue, newValue) -> {
if (r.getWidth() <= 0) {
return;
}
if (onDividerChange != null) {
onDividerChange.accept(newValue.doubleValue() * r.getWidth());
}
});
r.getStyleClass().add("side-split-pane-comp");
return new Structure(sidebar, c, r, r.getDividers().get(0));
}
public SideSplitPaneComp withInitialWidth(double val) {
this.initialWidth = val;
return this;
}
public SideSplitPaneComp withOnDividerChange(Consumer<Double> onDividerChange) {
this.onDividerChange = onDividerChange;
return this;
}
@Value
public static class Structure implements CompStructure<SplitPane> {
Region left;
Region center;
SplitPane pane;
SplitPane.Divider divider;
@Override
public SplitPane get() {
return pane;
}
}
}
@@ -17,6 +17,7 @@ import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.*;
import javafx.scene.text.TextAlignment;
import org.kordamp.ikonli.javafx.FontIcon;
public class StoreEntryListStatusComp extends SimpleComp {
@@ -74,6 +75,8 @@ public class StoreEntryListStatusComp extends SimpleComp {
private Region createButtons() {
var menu = new MenuButton(AppI18n.get("addConnections"), new FontIcon("mdi2p-plus-thick"));
menu.setAlignment(Pos.CENTER);
menu.setTextAlignment(TextAlignment.CENTER);
AppFont.medium(menu);
GrowAugment.create(true, false).augment(menu);
StoreCreationMenu.addButtons(menu);
@@ -1,12 +1,12 @@
package io.xpipe.app.comp.store;
import io.xpipe.app.comp.base.SideSplitPaneComp;
import io.xpipe.app.core.AppActionLinkDetector;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Region;
public class StoreLayoutComp extends SimpleComp {
@@ -19,14 +19,12 @@ public class StoreLayoutComp extends SimpleComp {
@Override
protected Region createSimple() {
var listComp = new StoreEntryListComp().apply(GrowAugment.create(false, true));
var r = new BorderPane();
var listR = listComp.createRegion();
var groupHeader = new StoreSidebarComp().createRegion();
r.setLeft(groupHeader);
r.setCenter(listR);
r.getStyleClass().add("layout");
return r;
var struc = new SideSplitPaneComp(new StoreSidebarComp(), new StoreEntryListComp()).withInitialWidth(
AppLayoutModel.get().getSavedState().getSidebarWidth()).withOnDividerChange(aDouble -> {
AppLayoutModel.get().getSavedState().setSidebarWidth(aDouble);
}).createStructure();
struc.getLeft().setMaxWidth(400);
struc.get().getStyleClass().add("store-layout");
return struc.get();
}
}
@@ -10,7 +10,10 @@ import io.xpipe.app.util.LicenseProvider;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.extern.jackson.Jacksonized;
import java.util.ArrayList;
import java.util.List;
@@ -18,6 +21,15 @@ import java.util.List;
@Getter
public class AppLayoutModel {
@Data
@Builder
@Jacksonized
public static class SavedState {
double sidebarWidth;
double browserConnectionsWidth;
}
private static AppLayoutModel INSTANCE;
public static AppLayoutModel get() {
@@ -25,13 +37,22 @@ public class AppLayoutModel {
}
public static void init() {
INSTANCE = new AppLayoutModel();
var state = AppCache.get("layoutState", SavedState.class, () -> new SavedState(250, 300));
INSTANCE = new AppLayoutModel(state);
}
public static void reset() {
AppCache.update("layoutState", INSTANCE.savedState);
INSTANCE = null;
}
@Getter
private final SavedState savedState;
private final List<Entry> entries;
private final Property<Entry> selected;
public AppLayoutModel() {
public AppLayoutModel(SavedState savedState) {
this.savedState = savedState;
this.entries = createEntryList();
this.selected = new SimpleObjectProperty<>(entries.get(1));
}
@@ -182,7 +182,7 @@ public class AppMainWindow {
}
private void saveState() {
if (!AppPrefs.get().saveWindowLocation.get()) {
if (!AppPrefs.get().saveWindowLocation().get()) {
return;
}
@@ -201,7 +201,7 @@ public class AppMainWindow {
}
private WindowState loadState() {
if (!AppPrefs.get().saveWindowLocation.get()) {
if (!AppPrefs.get().saveWindowLocation().get()) {
return null;
}
@@ -62,6 +62,7 @@ public abstract class PlatformMode extends OperationMode {
TrackEvent.info("mode", "Shutting down platform components");
onSwitchFrom();
StoreViewState.reset();
AppLayoutModel.reset();
PlatformState.teardown();
TrackEvent.info("mode", "Platform shutdown finished");
BACKGROUND.finalTeardown();
@@ -83,6 +83,15 @@ public abstract class Comp<S extends CompStructure<?>> {
return apply(struc -> struc.get().setPrefHeight(height));
}
public Comp<S> maxWidth(int width) {
return apply(struc -> struc.get().setMaxWidth(width));
}
public Comp<S> maxHeight(int height) {
return apply(struc -> struc.get().setMaxHeight(height));
}
public Comp<S> hgrow() {
return apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS));
}
@@ -112,8 +112,7 @@ public class AppPrefs {
return connectionTimeOut;
}
private final BooleanProperty saveWindowLocationInternal = typed(new SimpleBooleanProperty(false), Boolean.class);
public final ReadOnlyBooleanProperty saveWindowLocation = saveWindowLocationInternal;
private final BooleanProperty saveWindowLocation = typed(new SimpleBooleanProperty(true), Boolean.class);
// External terminal
// =================
@@ -336,6 +335,10 @@ public class AppPrefs {
return windowOpacity;
}
public ObservableBooleanValue saveWindowLocation() {
return saveWindowLocation;
}
public ObservableBooleanValue developerDisableUpdateVersionCheck() {
return developerDisableUpdateVersionCheckEffective;
}
@@ -591,7 +594,7 @@ public class AppPrefs {
Setting.of("useSystemFont", BooleanField.ofBooleanType(useSystemFontInternal).render(() -> new CustomToggleControl()), useSystemFontInternal),
Setting.of("tooltipDelay", tooltipDelayInternal, tooltipDelayMin, tooltipDelayMax),
Setting.of("language", languageControl, languageInternal)),
Group.of("windowOptions", Setting.of("saveWindowLocation", BooleanField.ofBooleanType(saveWindowLocationInternal).render(() -> new CustomToggleControl()), saveWindowLocationInternal))),
Group.of("windowOptions", Setting.of("saveWindowLocation", BooleanField.ofBooleanType(saveWindowLocation).render(() -> new CustomToggleControl()), saveWindowLocation))),
Category.of(
"connections",
Group.of(
@@ -0,0 +1,10 @@
.side-split-pane-comp.split-pane-divider {
-fx-padding: 1;
}
.side-split-pane-comp .split-pane-divider .horizontal-grabber {
-fx-padding: 0;
-fx-background-color: transparent;
-fx-background-insets: 0;
-fx-shape: " ";
}
@@ -13,8 +13,8 @@
-fx-padding: 1.2em;
}
.layout {
.store-layout .split-pane-divider {
-fx-background-color: transparent;
}
.root:dark.background {
+2 -2
View File
@@ -18,7 +18,7 @@ def daemonCommand = attachDebugger ? ':app:runAttachedDebugger' : ':app:run'
test {
workingDir = rootDir
jvmArgs += ["--enable-preview", "-Xmx2g"]
jvmArgs += ["-Xmx2g"]
// Daemon properties
systemProperty "io.xpipe.beacon.daemonArgs",
@@ -51,7 +51,7 @@ task productionTest(type: Test) {
useJUnitPlatform()
workingDir = rootDir
jvmArgs += ["--enable-preview", "-Xmx2g"]
jvmArgs += ["-Xmx2g"]
// Daemon properties
systemProperty "io.xpipe.beacon.daemonArgs",
+1 -1
View File
@@ -4,7 +4,7 @@ tasks.withType(JavaCompile).configureEach {
modularity.inferModulePath = true
options.encoding = 'UTF-8'
options.compilerArgs << "-Xlint:unchecked"
options.compilerArgs << "--enable-preview"
// options.compilerArgs << "--enable-preview"
// These settings are explicitly specified as they can cause problems with annotation processors
options.compilerArgs << "-implicit:none"