From 15964832507a075176554db9d77048a500cad57f Mon Sep 17 00:00:00 2001 From: crschnick Date: Fri, 28 Apr 2023 18:30:31 +0000 Subject: [PATCH] More user interface improvements --- .../io/xpipe/app/browser/BookmarkList.java | 45 ++++++++++++++++++- .../io/xpipe/app/browser/FileBrowserComp.java | 6 ++- .../xpipe/app/browser/FileBrowserModel.java | 9 ++++ .../io/xpipe/app/browser/FileFilterComp.java | 31 +++++++------ .../xpipe/app/browser/FileListCompEntry.java | 4 ++ .../app/browser/LocalFileTransferComp.java | 13 ++---- .../java/io/xpipe/app/comp/AppLayoutComp.java | 10 +++-- .../storage/store/StoreCreationBarComp.java | 22 +++++---- .../comp/storage/store/StoreEntrySection.java | 2 +- .../augment/DragPseudoClassAugment.java | 24 ++++++++++ .../xpipe/app/fxcomps/impl/SvgCacheComp.java | 2 +- .../io/xpipe/app/resources/style/browser.css | 17 ++++++- .../xpipe/app/resources/style/header-bars.css | 7 +-- .../app/resources/style/sidebar-comp.css | 4 ++ .../app/resources/style/store-entry-comp.css | 10 +++++ 15 files changed, 157 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/io/xpipe/app/fxcomps/augment/DragPseudoClassAugment.java diff --git a/app/src/main/java/io/xpipe/app/browser/BookmarkList.java b/app/src/main/java/io/xpipe/app/browser/BookmarkList.java index 4a767c193..955572fb6 100644 --- a/app/src/main/java/io/xpipe/app/browser/BookmarkList.java +++ b/app/src/main/java/io/xpipe/app/browser/BookmarkList.java @@ -1,18 +1,30 @@ package io.xpipe.app.browser; -import com.jfoenix.controls.JFXButton; import io.xpipe.app.comp.storage.store.StoreEntryFlatMiniSection; import io.xpipe.app.comp.storage.store.StoreEntryWrapper; import io.xpipe.app.fxcomps.SimpleComp; +import io.xpipe.app.fxcomps.SimpleCompStructure; +import io.xpipe.app.fxcomps.augment.DragPseudoClassAugment; +import io.xpipe.core.store.DataStore; import io.xpipe.core.store.ShellStore; +import javafx.application.Platform; +import javafx.geometry.Point2D; +import javafx.scene.control.Button; import javafx.scene.control.ScrollPane; +import javafx.scene.input.DragEvent; import javafx.scene.layout.Region; import javafx.scene.layout.VBox; import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; final class BookmarkList extends SimpleComp { + public static final Timer DROP_TIMER = new Timer("dnd", true); + private Point2D lastOver = new Point2D(-1, -1); + private TimerTask activeTask; + private final FileBrowserModel model; BookmarkList(FileBrowserModel model) { @@ -28,16 +40,26 @@ final class BookmarkList extends SimpleComp { continue; } - var button = new JFXButton(null, e.getValue()); + var button = new Button(null, e.getValue()); button.setOnAction(event -> { var fileSystem = ((ShellStore) e.getKey().getEntry().getStore()); model.openFileSystem(fileSystem); event.consume(); }); button.prefWidthProperty().bind(list.widthProperty()); + DragPseudoClassAugment.create().augment(new SimpleCompStructure<>(button)); + + button.addEventHandler( + DragEvent.DRAG_OVER, + mouseEvent -> handleHoverTimer(e.getKey().getEntry().getStore(), mouseEvent)); + button.addEventHandler( + DragEvent.DRAG_EXITED, + mouseEvent -> activeTask = null); + list.getChildren().add(button); } list.setFillWidth(true); + list.getStyleClass().add("bookmark-list"); var sp = new ScrollPane(list); sp.setFitToWidth(true); @@ -45,4 +67,23 @@ final class BookmarkList extends SimpleComp { return sp; } + + private void handleHoverTimer(DataStore store, DragEvent event) { + if (lastOver.getX() == event.getX() && lastOver.getY() == event.getY()) { + return; + } + + lastOver = (new Point2D(event.getX(), event.getY())); + activeTask = new TimerTask() { + @Override + public void run() { + if (activeTask != this) { + return; + } + + Platform.runLater(() -> model.openExistingFileSystemIfPresent(store.asNeeded())); + } + }; + DROP_TIMER.schedule(activeTask, 500); + } } diff --git a/app/src/main/java/io/xpipe/app/browser/FileBrowserComp.java b/app/src/main/java/io/xpipe/app/browser/FileBrowserComp.java index 4d3054a21..dc72b1a8e 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileBrowserComp.java +++ b/app/src/main/java/io/xpipe/app/browser/FileBrowserComp.java @@ -5,12 +5,15 @@ import atlantafx.base.controls.Spacer; import atlantafx.base.theme.Styles; import io.xpipe.app.browser.icon.FileIconManager; import io.xpipe.app.fxcomps.SimpleComp; +import io.xpipe.app.fxcomps.SimpleCompStructure; +import io.xpipe.app.fxcomps.augment.GrowAugment; import io.xpipe.app.fxcomps.impl.PrettyImageComp; import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.util.BusyProperty; import io.xpipe.app.util.ThreadHelper; import io.xpipe.core.store.FileSystem; +import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.property.SimpleBooleanProperty; import javafx.collections.ListChangeListener; @@ -237,7 +240,7 @@ public class FileBrowserComp extends SimpleComp { label.addEventHandler(DragEvent.DRAG_ENTERED, new EventHandler() { @Override public void handle(DragEvent mouseEvent) { - tabs.getSelectionModel().select(tab); + Platform.runLater(() -> tabs.getSelectionModel().select(tab)); } }); @@ -249,6 +252,7 @@ public class FileBrowserComp extends SimpleComp { PlatformThread.sync(model.getBusy()))); tab.setGraphic(label); + GrowAugment.create(true, false).augment(new SimpleCompStructure<>(label)); if (!this.model.getMode().equals(FileBrowserModel.Mode.BROWSER)) { label.setManaged(false); diff --git a/app/src/main/java/io/xpipe/app/browser/FileBrowserModel.java b/app/src/main/java/io/xpipe/app/browser/FileBrowserModel.java index 81abdbdd7..39ca96a21 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileBrowserModel.java +++ b/app/src/main/java/io/xpipe/app/browser/FileBrowserModel.java @@ -66,6 +66,15 @@ public class FileBrowserModel { }); } + public void openExistingFileSystemIfPresent(ShellStore store) { + var found = openFileSystems.stream().filter(model -> Objects.equals(model.getStore().getValue(), store)).findFirst(); + if (found.isPresent()) { + selected.setValue(found.get()); + } else { + openFileSystem(store); + } + } + public void openFileSystem(ShellStore store) { // Prevent multiple tabs in non browser modes if (!mode.equals(Mode.BROWSER)) { diff --git a/app/src/main/java/io/xpipe/app/browser/FileFilterComp.java b/app/src/main/java/io/xpipe/app/browser/FileFilterComp.java index b386d9a3f..a939d6dca 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileFilterComp.java +++ b/app/src/main/java/io/xpipe/app/browser/FileFilterComp.java @@ -29,14 +29,14 @@ public class FileFilterComp extends SimpleComp { protected Region createSimple() { var expanded = new SimpleBooleanProperty(); var text = new TextFieldComp(filterString, false).createRegion(); + var button = new Button(); text.focusedProperty().addListener((observable, oldValue, newValue) -> { if (!newValue && filterString.getValue() == null) { - expanded.set(false); - return; - } + if (button.isFocused()) { + return; + } - if (newValue) { - expanded.set(true); + expanded.set(false); } }); filterString.addListener((observable, oldValue, newValue) -> { @@ -56,21 +56,26 @@ public class FileFilterComp extends SimpleComp { }); var fi = new FontIcon("mdi2m-magnify"); - var button = new Button(); GrowAugment.create(false, true).augment(new SimpleCompStructure<>(button)); Shortcuts.addShortcut(button, new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN)); button.setGraphic(fi); button.setOnAction(event -> { - if (expanded.get() && filterString.getValue() == null) { - expanded.set(false); - return; + if (expanded.get()) { + if (filterString.getValue() == null) { + expanded.set(false); + } + event.consume(); + } else { + expanded.set(true); + text.requestFocus(); + event.consume(); } - - text.requestFocus(); - event.consume(); }); - SimpleChangeListener.apply(expanded, val -> { + text.setPrefWidth(0); + button.getStyleClass().add(Styles.FLAT); + expanded.addListener((observable, oldValue, val) -> { + System.out.println(val); if (val) { text.setPrefWidth(250); button.getStyleClass().add(Styles.RIGHT_PILL); diff --git a/app/src/main/java/io/xpipe/app/browser/FileListCompEntry.java b/app/src/main/java/io/xpipe/app/browser/FileListCompEntry.java index 5f135ab5a..3d0bf9b34 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileListCompEntry.java +++ b/app/src/main/java/io/xpipe/app/browser/FileListCompEntry.java @@ -89,6 +89,10 @@ public class FileListCompEntry { return false; } + if (model.getFileSystemModel().getCurrentDirectory() == null) { + return false; + } + // Prevent drag and drops of files into the current directory if (FileBrowserClipboard.currentDragClipboard .getBaseDirectory().getPath() diff --git a/app/src/main/java/io/xpipe/app/browser/LocalFileTransferComp.java b/app/src/main/java/io/xpipe/app/browser/LocalFileTransferComp.java index 02d94e08b..00bb5013f 100644 --- a/app/src/main/java/io/xpipe/app/browser/LocalFileTransferComp.java +++ b/app/src/main/java/io/xpipe/app/browser/LocalFileTransferComp.java @@ -4,6 +4,7 @@ import io.xpipe.app.comp.base.LoadingOverlayComp; import io.xpipe.app.core.AppI18n; import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.SimpleComp; +import io.xpipe.app.fxcomps.augment.DragPseudoClassAugment; import io.xpipe.app.fxcomps.impl.IconButtonComp; import io.xpipe.app.fxcomps.impl.LabelComp; import io.xpipe.app.fxcomps.impl.StackComp; @@ -13,9 +14,6 @@ import io.xpipe.app.fxcomps.util.PlatformThread; import javafx.beans.binding.Bindings; import javafx.collections.FXCollections; import javafx.geometry.Insets; -import javafx.scene.Scene; -import javafx.scene.SnapshotParameters; -import javafx.scene.image.WritableImage; import javafx.scene.input.ClipboardContent; import javafx.scene.input.Dragboard; import javafx.scene.input.TransferMode; @@ -63,7 +61,7 @@ public class LocalFileTransferComp extends SimpleComp { var listBox = new VerticalComp(List.of(list, dragNotice)); var stack = new LoadingOverlayComp( - new StackComp(List.of(backgroundStack, listBox, clearPane)).apply(struc -> { + new StackComp(List.of(backgroundStack, listBox, clearPane)).apply(DragPseudoClassAugment.create()).apply(struc -> { struc.get().setOnDragOver(event -> { // Accept drops from inside the app window if (event.getGestureSource() != null && event.getGestureSource() != struc.get()) { @@ -99,12 +97,9 @@ public class LocalFileTransferComp extends SimpleComp { cc.putFiles(files); db.setContent(cc); - var r = new SelectedFileListComp(FXCollections.observableList(stage.getItems().stream() + var image = SelectedFileListComp.snapshot(FXCollections.observableList(stage.getItems().stream() .map(item -> item.getFileEntry()) - .toList())) - .createRegion(); - new Scene(r); - WritableImage image = r.snapshot(new SnapshotParameters(), null); + .toList())); db.setDragView(image, -20, 15); event.setDragDetect(true); diff --git a/app/src/main/java/io/xpipe/app/comp/AppLayoutComp.java b/app/src/main/java/io/xpipe/app/comp/AppLayoutComp.java index dd766e44c..9be90bedb 100644 --- a/app/src/main/java/io/xpipe/app/comp/AppLayoutComp.java +++ b/app/src/main/java/io/xpipe/app/comp/AppLayoutComp.java @@ -19,9 +19,11 @@ 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; import lombok.SneakyThrows; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; public class AppLayoutComp extends Comp> { @@ -71,6 +73,9 @@ public class AppLayoutComp extends Comp> { @Override public CompStructure createBase() { + var map = new HashMap(); + entries.forEach(entry -> map.put(entry, entry.comp().createRegion())); + var pane = new BorderPane(); var sidebar = new SideMenuBarComp(selected, entries); pane.setCenter(selected.getValue().comp().createRegion()); @@ -80,10 +85,9 @@ public class AppLayoutComp extends Comp> { AppPrefs.get().save(); } - var r = selected.getValue().comp().createRegion(); - pane.setCenter(r); + pane.setCenter(map.get(n)); }); - pane.setCenter(selected.getValue().comp().createRegion()); + pane.setCenter(map.get(selected.getValue())); pane.setPrefWidth(1280); pane.setPrefHeight(720); AppFont.normal(pane); diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreCreationBarComp.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreCreationBarComp.java index 26e843157..3cca49b9d 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreCreationBarComp.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreCreationBarComp.java @@ -1,5 +1,6 @@ package io.xpipe.app.comp.storage.store; +import atlantafx.base.theme.Styles; import io.xpipe.app.comp.base.ButtonComp; import io.xpipe.app.comp.source.store.GuiDsStoreCreator; import io.xpipe.app.core.AppFont; @@ -20,24 +21,19 @@ public class StoreCreationBarComp extends SimpleComp { @Override protected Region createSimple() { - var newOtherStore = new ButtonComp( - AppI18n.observable("addOther"), new FontIcon("mdi2c-card-plus-outline"), () -> { - GuiDsStoreCreator.showCreation(v -> v.getDisplayCategory().equals(DataStoreProvider.DisplayCategory.OTHER)); - }) - .shortcut(new KeyCodeCombination(KeyCode.C, KeyCombination.SHORTCUT_DOWN)) - .apply(new FancyTooltipAugment<>("addOther")); - var newStreamStore = new ButtonComp( AppI18n.observable("addCommand"), new FontIcon("mdi2c-code-greater-than"), () -> { GuiDsStoreCreator.showCreation(v -> v.getDisplayCategory().equals(DataStoreProvider.DisplayCategory.COMMAND)); }) + .styleClass(Styles.FLAT) .shortcut(new KeyCodeCombination(KeyCode.C, KeyCombination.SHORTCUT_DOWN)) .apply(new FancyTooltipAugment<>("addCommand")); - var newHostStore = new ButtonComp( - AppI18n.observable("addHost"), new FontIcon("mdi2h-home-plus"), () -> { - GuiDsStoreCreator.showCreation(v -> v.getDisplayCategory().equals(DataStoreProvider.DisplayCategory.HOST)); - }) + var newHostStore = new ButtonComp(AppI18n.observable("addHost"), new FontIcon("mdi2h-home-plus"), () -> { + GuiDsStoreCreator.showCreation( + v -> v.getDisplayCategory().equals(DataStoreProvider.DisplayCategory.HOST)); + }) + .styleClass(Styles.FLAT) .shortcut(new KeyCodeCombination(KeyCode.H, KeyCombination.SHORTCUT_DOWN)) .apply(new FancyTooltipAugment<>("addHost")); @@ -45,6 +41,7 @@ public class StoreCreationBarComp extends SimpleComp { AppI18n.observable("addShell"), new FontIcon("mdi2t-text-box-multiple"), () -> { GuiDsStoreCreator.showCreation(v -> v.getDisplayCategory().equals(DataStoreProvider.DisplayCategory.SHELL)); }) + .styleClass(Styles.FLAT) .shortcut(new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN)) .apply(new FancyTooltipAugment<>("addShell")); @@ -52,10 +49,11 @@ public class StoreCreationBarComp extends SimpleComp { AppI18n.observable("addDatabase"), new FontIcon("mdi2d-database-plus"), () -> { GuiDsStoreCreator.showCreation(v -> v.getDisplayCategory().equals(DataStoreProvider.DisplayCategory.DATABASE)); }) + .styleClass(Styles.FLAT) .shortcut(new KeyCodeCombination(KeyCode.D, KeyCombination.SHORTCUT_DOWN)) .apply(new FancyTooltipAugment<>("addDatabase")); - var box = new VerticalComp(List.of(newHostStore, newShellStore, newStreamStore, newDbStore)); + var box = new VerticalComp(List.of(newHostStore, newShellStore, newStreamStore, newDbStore)).apply(struc -> struc.get().setFillWidth(true)); box.apply(s -> AppFont.medium(s.get())); var bar = box.createRegion(); bar.getStyleClass().add("bar"); diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntrySection.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntrySection.java index 9b2d020e7..739ac4569 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntrySection.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntrySection.java @@ -40,7 +40,7 @@ public class StoreEntrySection extends Comp> { .apply(struc -> struc.get().setPrefWidth(40)) .disable(BindingsHelper.persist( Bindings.size(section.getChildren()).isEqualTo(0))) - .grow(false, true); + .grow(false, true).styleClass("expand-button"); List> topEntryList = List.of(button, root); var all = section.getChildren(); diff --git a/app/src/main/java/io/xpipe/app/fxcomps/augment/DragPseudoClassAugment.java b/app/src/main/java/io/xpipe/app/fxcomps/augment/DragPseudoClassAugment.java new file mode 100644 index 000000000..b0b412158 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/fxcomps/augment/DragPseudoClassAugment.java @@ -0,0 +1,24 @@ +package io.xpipe.app.fxcomps.augment; + +import io.xpipe.app.fxcomps.CompStructure; +import javafx.css.PseudoClass; +import javafx.scene.input.DragEvent; + +public class DragPseudoClassAugment> implements Augment { + + public static final PseudoClass DRAGGED_PSEUDOCLASS = PseudoClass.getPseudoClass("drag-over"); + + public static > DragPseudoClassAugment create() { + return new DragPseudoClassAugment<>(); + } + + @Override + public void augment(S struc) { + struc.get().addEventFilter(DragEvent.DRAG_ENTERED, event -> { + struc.get().pseudoClassStateChanged(DRAGGED_PSEUDOCLASS, true); + }); + + struc.get().addEventFilter(DragEvent.DRAG_EXITED, event -> struc.get() + .pseudoClassStateChanged(DRAGGED_PSEUDOCLASS, false)); + } +} diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/SvgCacheComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/SvgCacheComp.java index d89be7212..2ccaee071 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/SvgCacheComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/SvgCacheComp.java @@ -65,7 +65,7 @@ public class SvgCacheComp extends SimpleComp { } var pt = new PauseTransition(); - pt.setDuration(Duration.millis(1000)); + pt.setDuration(Duration.millis(500)); pt.setOnFinished(actionEvent -> { if (newValue == null || cache.getCached(newValue).isPresent()) { return; diff --git a/app/src/main/resources/io/xpipe/app/resources/style/browser.css b/app/src/main/resources/io/xpipe/app/resources/style/browser.css index 82369e724..71cc34214 100644 --- a/app/src/main/resources/io/xpipe/app/resources/style/browser.css +++ b/app/src/main/resources/io/xpipe/app/resources/style/browser.css @@ -1,6 +1,6 @@ .download-background { -fx-border-color: -color-neutral-muted; --fx-border-width: 4px 0 0 0; +-fx-border-width: 2px 0 0 0; -fx-padding: 1em; } @@ -21,6 +21,21 @@ -fx-border-radius: 1px; } +.bookmark-list .button { +-fx-border-width: 0; +-fx-border-radius: 0; +-fx-background-radius: 0; +-fx-background-insets: 1px 2px 1px 0; +} + +*:drag-over .download-background { + -fx-background-color: -color-success-muted; +} + +.bookmark-list .button:drag-over { + -fx-background-color: -color-success-muted; +} + .browser .bookmark-list { -fx-border-width: 0 0 1 1; } diff --git a/app/src/main/resources/io/xpipe/app/resources/style/header-bars.css b/app/src/main/resources/io/xpipe/app/resources/style/header-bars.css index 00d34f514..5bd8641f6 100644 --- a/app/src/main/resources/io/xpipe/app/resources/style/header-bars.css +++ b/app/src/main/resources/io/xpipe/app/resources/style/header-bars.css @@ -29,12 +29,7 @@ } .bar .button-comp { --fx-border-width: 0; --fx-border-color: -color-neutral-emphasis; --fx-background-color: transparent; --fx-border-radius: 2px; --fx-background-radius: 2px; --fx-padding: 0.2em 0em 0.2em 0em; +-fx-padding: 0.3em 0em 0.3em 0em; } .collections-bar { diff --git a/app/src/main/resources/io/xpipe/app/resources/style/sidebar-comp.css b/app/src/main/resources/io/xpipe/app/resources/style/sidebar-comp.css index fa7da75b6..12f312b9e 100644 --- a/app/src/main/resources/io/xpipe/app/resources/style/sidebar-comp.css +++ b/app/src/main/resources/io/xpipe/app/resources/style/sidebar-comp.css @@ -6,6 +6,10 @@ -fx-padding: 0; } +.sidebar-comp .button:hover { +-fx-background-color: -color-neutral-muted; +} + .sidebar-comp .big-icon-button-comp { -fx-background-radius: 0; } diff --git a/app/src/main/resources/io/xpipe/app/resources/style/store-entry-comp.css b/app/src/main/resources/io/xpipe/app/resources/style/store-entry-comp.css index 0ea374361..351257139 100644 --- a/app/src/main/resources/io/xpipe/app/resources/style/store-entry-comp.css +++ b/app/src/main/resources/io/xpipe/app/resources/style/store-entry-comp.css @@ -22,9 +22,19 @@ .store-entry-grid:incomplete .icon { -fx-opacity: 0.5; } + .store-entry-comp { -fx-padding: 6px 6px 6px 0; } +.store-entry-comp:hover { +-fx-background-color: -color-neutral-muted; +} + +.expand-button:hover { +-fx-background-color: -color-neutral-muted; +} + +