mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-09-30 00:56:56 +13:00
Quick access fixes
This commit is contained in:
parent
86ae1271a4
commit
b1a3caad46
4 changed files with 150 additions and 52 deletions
|
@ -498,7 +498,7 @@ final class BrowserFileListComp extends SimpleComp {
|
||||||
setAccessibleRole(AccessibleRole.TEXT);
|
setAccessibleRole(AccessibleRole.TEXT);
|
||||||
|
|
||||||
var textField = new LazyTextFieldComp(text).minWidth(USE_PREF_SIZE).createStructure().get();
|
var textField = new LazyTextFieldComp(text).minWidth(USE_PREF_SIZE).createStructure().get();
|
||||||
var quickAccess = new BrowserQuickAccessButtonComp(() -> getTableRow().getItem().getRawFileEntry(), fileList.getFileSystemModel(), fileEntry -> {})
|
var quickAccess = new BrowserQuickAccessButtonComp(() -> getTableRow().getItem(), fileList.getFileSystemModel())
|
||||||
.hide(Bindings.createBooleanBinding(() -> {
|
.hide(Bindings.createBooleanBinding(() -> {
|
||||||
var notDir = getTableRow().getItem().getRawFileEntry().getKind() != FileKind.DIRECTORY;
|
var notDir = getTableRow().getItem().getRawFileEntry().getKind() != FileKind.DIRECTORY;
|
||||||
var isParentLink = getTableRow()
|
var isParentLink = getTableRow()
|
||||||
|
|
|
@ -4,10 +4,12 @@ import io.xpipe.app.browser.icon.FileIconManager;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
||||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||||
|
import io.xpipe.app.util.BooleanTimer;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.store.FileKind;
|
import io.xpipe.core.store.FileKind;
|
||||||
import io.xpipe.core.store.FileSystem;
|
import io.xpipe.core.store.FileSystem;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.geometry.Side;
|
import javafx.geometry.Side;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.ContextMenu;
|
import javafx.scene.control.ContextMenu;
|
||||||
|
@ -16,19 +18,19 @@ import javafx.scene.control.MenuItem;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.function.Consumer;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class BrowserQuickAccessButtonComp extends SimpleComp {
|
public class BrowserQuickAccessButtonComp extends SimpleComp {
|
||||||
|
|
||||||
private final Supplier<FileSystem.FileEntry> base;
|
private final Supplier<BrowserEntry> base;
|
||||||
private final OpenFileSystemModel model;
|
private final OpenFileSystemModel model;
|
||||||
private final Consumer<FileSystem.FileEntry> action;
|
|
||||||
|
|
||||||
public BrowserQuickAccessButtonComp(Supplier<FileSystem.FileEntry> base, OpenFileSystemModel model, Consumer<FileSystem.FileEntry> action) {
|
public BrowserQuickAccessButtonComp(Supplier<BrowserEntry> base, OpenFileSystemModel model) {
|
||||||
this.base = base;
|
this.base = base;
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.action = action;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -43,15 +45,6 @@ public class BrowserQuickAccessButtonComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showMenu(Node anchor) {
|
private void showMenu(Node anchor) {
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
|
||||||
var children = model.getFileSystem().listFiles(base.get().getPath());
|
|
||||||
try (var s = children) {
|
|
||||||
var list = s.toList();
|
|
||||||
if (list.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
var cm = new ContextMenu();
|
var cm = new ContextMenu();
|
||||||
cm.addEventHandler(Menu.ON_SHOWING, e -> {
|
cm.addEventHandler(Menu.ON_SHOWING, e -> {
|
||||||
Node content = cm.getSkin().getNode();
|
Node content = cm.getSkin().getNode();
|
||||||
|
@ -62,48 +55,94 @@ public class BrowserQuickAccessButtonComp extends SimpleComp {
|
||||||
});
|
});
|
||||||
cm.setAutoHide(true);
|
cm.setAutoHide(true);
|
||||||
cm.getStyleClass().add("condensed");
|
cm.getStyleClass().add("condensed");
|
||||||
cm.getItems().addAll(list.stream().map(e -> recurse(cm, e)).toList());
|
|
||||||
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
|
var fileEntry = base.get().getRawFileEntry();
|
||||||
|
if (fileEntry.getKind() != FileKind.DIRECTORY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var r = new Menu();
|
||||||
|
var newItems = updateMenuItems(cm, r, fileEntry, true);
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
cm.getItems().addAll(r.getItems());
|
||||||
cm.show(anchor, Side.RIGHT, 0, 0);
|
cm.show(anchor, Side.RIGHT, 0, 0);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private MenuItem recurse(ContextMenu contextMenu, FileSystem.FileEntry fileEntry) {
|
private MenuItem createItem(ContextMenu contextMenu, FileSystem.FileEntry fileEntry) {
|
||||||
|
var browserCm = new BrowserContextMenu(model, new BrowserEntry(fileEntry,model.getFileList(), false));
|
||||||
|
|
||||||
if (fileEntry.getKind() != FileKind.DIRECTORY) {
|
if (fileEntry.getKind() != FileKind.DIRECTORY) {
|
||||||
var m = new MenuItem(
|
var m = new Menu(
|
||||||
fileEntry.getName(),
|
fileEntry.getName(),
|
||||||
PrettyImageHelper.ofFixedSquare(FileIconManager.getFileIcon(fileEntry,false), 16).createRegion());
|
PrettyImageHelper.ofFixedSizeSquare(FileIconManager.getFileIcon(fileEntry,false), 24).createRegion());
|
||||||
m.setMnemonicParsing(false);
|
m.setMnemonicParsing(false);
|
||||||
m.setOnAction(event -> {
|
m.setOnAction(event -> {
|
||||||
action.accept(fileEntry);
|
if (event.getTarget() != m) {
|
||||||
event.consume();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
browserCm.show(m.getStyleableNode(), Side.RIGHT, 0, 0);
|
||||||
});
|
});
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
var m = new Menu(
|
var m = new Menu(
|
||||||
fileEntry.getName(),
|
fileEntry.getName(),
|
||||||
PrettyImageHelper.ofFixedSquare(FileIconManager.getFileIcon(fileEntry,false), 16).createRegion());
|
PrettyImageHelper.ofFixedSizeSquare(FileIconManager.getFileIcon(fileEntry,false), 24).createRegion());
|
||||||
m.setMnemonicParsing(false);
|
m.setMnemonicParsing(false);
|
||||||
m.setOnAction(event -> {
|
var empty = new MenuItem("...");
|
||||||
if (event.getTarget() == m) {
|
m.getItems().add(empty);
|
||||||
if (m.isShowing()) {
|
|
||||||
|
var hover = new SimpleBooleanProperty();
|
||||||
|
m.setOnShowing(event -> {
|
||||||
|
browserCm.hide();
|
||||||
|
hover.set(true);
|
||||||
event.consume();
|
event.consume();
|
||||||
|
});
|
||||||
|
m.setOnHiding(event -> {
|
||||||
|
browserCm.hide();
|
||||||
|
hover.set(false);
|
||||||
|
event.consume();
|
||||||
|
});
|
||||||
|
new BooleanTimer(hover,500, () -> {
|
||||||
|
if (m.isShowing() && !m.getItems().getFirst().equals(empty)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
List<MenuItem> newItems = null;
|
||||||
updateMenuItems(m, fileEntry);
|
try {
|
||||||
});
|
newItems = updateMenuItems(contextMenu, m, fileEntry, false);
|
||||||
action.accept(fileEntry);
|
m.getItems().setAll(newItems);
|
||||||
event.consume();
|
if (!browserCm.isShowing()) {
|
||||||
|
m.hide();
|
||||||
|
m.show();
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
m.setOnAction(event -> {
|
||||||
|
if (event.getTarget() != m) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (browserCm.isShowing()) {
|
||||||
|
browserCm.hide();
|
||||||
|
m.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m.hide();
|
||||||
|
browserCm.show(m.getStyleableNode(), Side.RIGHT, 0, 0);
|
||||||
|
event.consume();
|
||||||
});
|
});
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateMenuItems(Menu m, FileSystem.FileEntry fileEntry) throws Exception {
|
private List<MenuItem> updateMenuItems(ContextMenu contextMenu, Menu m, FileSystem.FileEntry fileEntry, boolean updateInstantly) throws Exception {
|
||||||
var newFiles = model.getFileSystem().listFiles(fileEntry.getPath());
|
var newFiles = model.getFileSystem().listFiles(fileEntry.getPath());
|
||||||
try (var s = newFiles) {
|
try (var s = newFiles) {
|
||||||
var list = s.toList();
|
var list = s.toList();
|
||||||
|
@ -111,19 +150,26 @@ public class BrowserQuickAccessButtonComp extends SimpleComp {
|
||||||
var newItems = new ArrayList<MenuItem>();
|
var newItems = new ArrayList<MenuItem>();
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
newItems.add(new MenuItem("<empty>"));
|
newItems.add(new MenuItem("<empty>"));
|
||||||
} else if (list.size() == 1 && list.getFirst().getKind() == FileKind.DIRECTORY) {
|
|
||||||
var subMenu = recurse(m.getParentPopup(),list.getFirst());
|
|
||||||
updateMenuItems(m, list.getFirst());
|
|
||||||
newItems.add(subMenu);
|
|
||||||
} else {
|
} else {
|
||||||
newItems.addAll(list.stream().map(e -> recurse(m.getParentPopup(), e)).toList());
|
var menus = list.stream().sorted((o1, o2) -> {
|
||||||
|
if (o1.getKind() == FileKind.DIRECTORY && o2.getKind() != FileKind.DIRECTORY) {
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
if (o2.getKind() == FileKind.DIRECTORY && o1.getKind() != FileKind.DIRECTORY) {
|
||||||
Platform.runLater(() -> {
|
return 1;
|
||||||
|
}
|
||||||
|
return o1.getName().compareToIgnoreCase(o2.getName());
|
||||||
|
}).collect(Collectors.toMap(e -> e, e -> createItem(contextMenu, e), (v1, v2) -> v2, LinkedHashMap::new));
|
||||||
|
var dirs = list.stream().filter(e -> e.getKind() == FileKind.DIRECTORY).toList();
|
||||||
|
if (dirs.size() == 1) {
|
||||||
|
updateMenuItems(contextMenu, (Menu) menus.get(dirs.getFirst()), list.getFirst(), updateInstantly);
|
||||||
|
}
|
||||||
|
newItems.addAll(menus.values());
|
||||||
|
}
|
||||||
|
if (updateInstantly) {
|
||||||
m.getItems().setAll(newItems);
|
m.getItems().setAll(newItems);
|
||||||
m.hide();
|
}
|
||||||
m.show();
|
return newItems;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,18 +7,18 @@ import io.xpipe.core.store.FileSystem;
|
||||||
public class BrowserIcons {
|
public class BrowserIcons {
|
||||||
|
|
||||||
public static Comp<?> createDefaultFileIcon() {
|
public static Comp<?> createDefaultFileIcon() {
|
||||||
return PrettyImageHelper.ofFixedSizeSquare("default_file.svg", 22);
|
return PrettyImageHelper.ofFixedSizeSquare("default_file.svg", 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Comp<?> createDefaultDirectoryIcon() {
|
public static Comp<?> createDefaultDirectoryIcon() {
|
||||||
return PrettyImageHelper.ofFixedSizeSquare("default_folder.svg", 22);
|
return PrettyImageHelper.ofFixedSizeSquare("default_folder.svg", 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Comp<?> createIcon(BrowserIconFileType type) {
|
public static Comp<?> createIcon(BrowserIconFileType type) {
|
||||||
return PrettyImageHelper.ofFixedSizeSquare(type.getIcon(), 22);
|
return PrettyImageHelper.ofFixedSizeSquare(type.getIcon(), 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Comp<?> createIcon(FileSystem.FileEntry entry) {
|
public static Comp<?> createIcon(FileSystem.FileEntry entry) {
|
||||||
return PrettyImageHelper.ofFixedSizeSquare(FileIconManager.getFileIcon(entry, false), 22);
|
return PrettyImageHelper.ofFixedSizeSquare(FileIconManager.getFileIcon(entry, false), 24);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
52
app/src/main/java/io/xpipe/app/util/BooleanTimer.java
Normal file
52
app/src/main/java/io/xpipe/app/util/BooleanTimer.java
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package io.xpipe.app.util;
|
||||||
|
|
||||||
|
import javafx.animation.AnimationTimer;
|
||||||
|
import javafx.beans.value.ObservableBooleanValue;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
public class BooleanTimer {
|
||||||
|
|
||||||
|
private final ObservableBooleanValue value;
|
||||||
|
private final int duration;
|
||||||
|
private final Runnable toExecute;
|
||||||
|
|
||||||
|
public BooleanTimer(ObservableBooleanValue value, int duration, Runnable toExecute) {
|
||||||
|
this.value = value;
|
||||||
|
this.duration = duration;
|
||||||
|
this.toExecute = toExecute;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
var timer = new AtomicReference<AnimationTimer>();
|
||||||
|
value.addListener((observable, oldValue, newValue) -> {
|
||||||
|
if (newValue) {
|
||||||
|
if (timer.get() == null) {
|
||||||
|
timer.set(new AnimationTimer() {
|
||||||
|
|
||||||
|
long init =0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(long now) {
|
||||||
|
if (init == 0) {
|
||||||
|
init = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nowMs = now;
|
||||||
|
if ((nowMs - init) > duration * 1000L) {
|
||||||
|
toExecute.run();
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
timer.get().start();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (timer.get() != null) {
|
||||||
|
timer.get().stop();
|
||||||
|
timer.set(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue