From 9ad5b6f7f536b06734f701038a908661067ef1c9 Mon Sep 17 00:00:00 2001 From: crschnick Date: Fri, 29 Mar 2024 18:32:00 +0000 Subject: [PATCH] Browser quick access improvements --- .../app/browser/BrowserFileListComp.java | 15 +- .../app/browser/BrowserFileListModel.java | 8 +- .../browser/BrowserQuickAccessButtonComp.java | 198 +--------- .../BrowserQuickAccessContextMenu.java | 348 ++++++++++++++++++ .../io/xpipe/app/issue/ErrorHandlerComp.java | 13 +- .../java/io/xpipe/app/util/JfxHelper.java | 41 --- .../io/xpipe/app/resources/style/browser.css | 7 +- 7 files changed, 386 insertions(+), 244 deletions(-) create mode 100644 app/src/main/java/io/xpipe/app/browser/BrowserQuickAccessContextMenu.java diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java index 5fce54b5..0dde9d04 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java @@ -9,6 +9,7 @@ import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.SimpleCompStructure; import io.xpipe.app.fxcomps.augment.ContextMenuAugment; import io.xpipe.app.fxcomps.impl.PrettyImageHelper; +import io.xpipe.app.fxcomps.util.BindingsHelper; import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.app.util.BooleanScope; import io.xpipe.app.util.HumanReadableFormat; @@ -503,20 +504,16 @@ final class BrowserFileListComp extends SimpleComp { .get(); var quickAccess = new BrowserQuickAccessButtonComp( () -> getTableRow().getItem(), fileList.getFileSystemModel()) - .hide(Bindings.createBooleanBinding( + .hide(BindingsHelper.persist(Bindings.createBooleanBinding( () -> { - var notDir = getTableRow() - .getItem() - .getRawFileEntry() - .getKind() - != FileKind.DIRECTORY; - var isParentLink = getTableRow() - .getItem() + var item = getTableRow().getItem(); + var notDir = item.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY; + var isParentLink = item .getRawFileEntry() .equals(fileList.getFileSystemModel().getCurrentParentDirectory()); return notDir || isParentLink; }, - itemProperty())) + itemProperty()))) .createRegion(); editing.addListener((observable, oldValue, newValue) -> { diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFileListModel.java b/app/src/main/java/io/xpipe/app/browser/BrowserFileListModel.java index 2f1f9c45..ac21ec5d 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFileListModel.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFileListModel.java @@ -87,20 +87,20 @@ public final class BrowserFileListModel { : all.getValue(); var listCopy = new ArrayList<>(filtered); - sort(listCopy); + listCopy.sort(order()); shown.setValue(listCopy); } - private void sort(List l) { + public Comparator order() { var syntheticFirst = Comparator.comparing(path -> !path.isSynthetic()); var dirsFirst = Comparator.comparing( path -> path.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY); var comp = comparatorProperty.getValue(); - Comparator us = comp != null + Comparator us = comp != null ? syntheticFirst.thenComparing(dirsFirst).thenComparing(comp) : syntheticFirst.thenComparing(dirsFirst); - l.sort(us); + return us; } public boolean rename(String filename, String newName) { diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserQuickAccessButtonComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserQuickAccessButtonComp.java index c13298c8..9a27aae6 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserQuickAccessButtonComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserQuickAccessButtonComp.java @@ -1,28 +1,12 @@ package io.xpipe.app.browser; -import io.xpipe.app.browser.icon.FileIconManager; import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.impl.IconButtonComp; -import io.xpipe.app.fxcomps.impl.PrettyImageHelper; -import io.xpipe.app.util.BooleanAnimationTimer; -import io.xpipe.app.util.ThreadHelper; -import io.xpipe.core.store.FileKind; -import io.xpipe.core.store.FileSystem; import javafx.application.Platform; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.geometry.Side; -import javafx.scene.Node; -import javafx.scene.control.ContextMenu; import javafx.scene.control.Menu; -import javafx.scene.control.MenuItem; import javafx.scene.layout.Region; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; -import java.util.stream.Collectors; public class BrowserQuickAccessButtonComp extends SimpleComp { @@ -36,188 +20,28 @@ public class BrowserQuickAccessButtonComp extends SimpleComp { @Override protected Region createSimple() { - var cm = new ContextMenu(); + var cm = new BrowserQuickAccessContextMenu(base, model); var button = new IconButtonComp("mdi2c-chevron-double-right"); button.apply(struc -> { struc.get().setOnAction(event -> { if (!cm.isShowing()) { - showMenu(cm, struc.get()); + cm.showMenu(struc.get()); } else { cm.hide(); } event.consume(); }); + cm.addEventFilter(Menu.ON_HIDDEN, e -> { + Platform.runLater(() -> { + struc.get().requestFocus(); + }); + }); + BrowserQuickAccessContextMenu.onRight(struc.get(), false, keyEvent -> { + cm.showMenu(struc.get()); + keyEvent.consume(); + }); }); button.styleClass("quick-access-button"); return button.createRegion(); } - - private void showMenu(ContextMenu cm, Node anchor) { - cm.getItems().clear(); - cm.addEventHandler(Menu.ON_SHOWING, e -> { - Node content = cm.getSkin().getNode(); - if (content instanceof Region r) { - r.setMaxWidth(500); - } - }); - cm.setAutoHide(true); - cm.getStyleClass().add("condensed"); - - ThreadHelper.runFailableAsync(() -> { - var fileEntry = base.get().getRawFileEntry(); - if (fileEntry.getKind() != FileKind.DIRECTORY) { - return; - } - - var actionsMenu = new AtomicReference(); - var r = new Menu(); - var newItems = updateMenuItems(cm, r, fileEntry, true, actionsMenu); - Platform.runLater(() -> { - cm.getItems().addAll(r.getItems()); - cm.show(anchor, Side.RIGHT, 0, 0); - }); - }); - } - - private MenuItem createItem( - ContextMenu contextMenu, FileSystem.FileEntry fileEntry, AtomicReference showingActionsMenu) { - var browserCm = new BrowserContextMenu(model, new BrowserEntry(fileEntry, model.getFileList(), false)); - browserCm.setOnAction(e -> { - contextMenu.hide(); - }); - - if (fileEntry.getKind() != FileKind.DIRECTORY) { - var m = new Menu( - fileEntry.getName(), - PrettyImageHelper.ofFixedSizeSquare(FileIconManager.getFileIcon(fileEntry, false), 24) - .createRegion()); - m.setMnemonicParsing(false); - m.setOnAction(event -> { - if (event.getTarget() != m) { - return; - } - - browserCm.show(m.getStyleableNode(), Side.RIGHT, 0, 0); - showingActionsMenu.set(browserCm); - }); - m.getStyleClass().add("leaf"); - return m; - } - - var m = new Menu( - fileEntry.getName(), - PrettyImageHelper.ofFixedSizeSquare(FileIconManager.getFileIcon(fileEntry, false), 24) - .createRegion()); - m.setMnemonicParsing(false); - var empty = new MenuItem("..."); - m.getItems().add(empty); - - var hover = new SimpleBooleanProperty(); - m.setOnShowing(event -> { - var actionsMenu = showingActionsMenu.get(); - if (actionsMenu != null) { - actionsMenu.hide(); - showingActionsMenu.set(null); - } - hover.set(true); - event.consume(); - }); - m.setOnHiding(event -> { - var actionsMenu = showingActionsMenu.get(); - if (actionsMenu != null) { - actionsMenu.hide(); - showingActionsMenu.set(null); - } - hover.set(false); - event.consume(); - }); - new BooleanAnimationTimer(hover, 100, () -> { - if (m.isShowing() && !m.getItems().getFirst().equals(empty)) { - return; - } - - ThreadHelper.runFailableAsync(() -> { - var newItems = updateMenuItems(contextMenu, m, fileEntry, false, showingActionsMenu); - Platform.runLater(() -> { - m.getItems().setAll(newItems); - if (!browserCm.isShowing() && m.isShowing()) { - m.hide(); - m.show(); - } - }); - }); - }) - .start(); - m.setOnAction(event -> { - if (event.getTarget() != m) { - return; - } - - var actionsMenu = showingActionsMenu.get(); - if (actionsMenu != null && actionsMenu.isShowing()) { - actionsMenu.hide(); - showingActionsMenu.set(null); - m.show(); - return; - } - - m.hide(); - browserCm.show(m.getStyleableNode(), Side.RIGHT, 0, 0); - showingActionsMenu.set(browserCm); - event.consume(); - }); - return m; - } - - private List updateMenuItems( - ContextMenu contextMenu, - Menu m, - FileSystem.FileEntry fileEntry, - boolean updateInstantly, - AtomicReference showingActionsMenu) - throws Exception { - var newFiles = model.getFileSystem().listFiles(fileEntry.getPath()); - try (var s = newFiles) { - var list = s.toList(); - // Wait until all files are listed, i.e. do not skip the stream elements - list = list.subList(0, Math.min(list.size(), 150)); - - var newItems = new ArrayList(); - if (list.isEmpty()) { - newItems.add(new MenuItem("")); - } else { - 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) { - return 1; - } - return o1.getName().compareToIgnoreCase(o2.getName()); - }) - .collect(Collectors.toMap( - e -> e, - e -> createItem(contextMenu, e, showingActionsMenu), - (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()), - dirs.getFirst(), - true, - showingActionsMenu); - } - newItems.addAll(menus.values()); - } - if (updateInstantly) { - m.getItems().setAll(newItems); - } - return newItems; - } - } } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserQuickAccessContextMenu.java b/app/src/main/java/io/xpipe/app/browser/BrowserQuickAccessContextMenu.java new file mode 100644 index 00000000..0937c36e --- /dev/null +++ b/app/src/main/java/io/xpipe/app/browser/BrowserQuickAccessContextMenu.java @@ -0,0 +1,348 @@ +package io.xpipe.app.browser; + +import io.xpipe.app.browser.icon.FileIconManager; +import io.xpipe.app.fxcomps.impl.PrettyImageHelper; +import io.xpipe.app.util.BooleanAnimationTimer; +import io.xpipe.app.util.ThreadHelper; +import io.xpipe.core.store.FileKind; +import javafx.application.Platform; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.event.EventHandler; +import javafx.event.EventTarget; +import javafx.geometry.Side; +import javafx.scene.Node; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuItem; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.Region; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class BrowserQuickAccessContextMenu extends ContextMenu { + + static void onLeft(EventTarget target, boolean filter, Consumer r) { + EventHandler keyEventEventHandler = event -> { + if (event.getCode() == KeyCode.LEFT || event.getCode() == KeyCode.NUMPAD4) { + r.accept(event); + } + }; + if (filter) { + target.addEventFilter(KeyEvent.KEY_PRESSED, keyEventEventHandler); + } else { + target.addEventHandler(KeyEvent.KEY_PRESSED, keyEventEventHandler); + } + } + + static void onRight(EventTarget target, boolean filter, Consumer r) { + EventHandler keyEventEventHandler = event -> { + if (event.getCode() == KeyCode.RIGHT || event.getCode() == KeyCode.NUMPAD6) { + r.accept(event); + } + }; + if (filter) { + target.addEventFilter(KeyEvent.KEY_PRESSED, keyEventEventHandler); + } else { + target.addEventHandler(KeyEvent.KEY_PRESSED, keyEventEventHandler); + } + } + + @Getter + class QuickAccessMenu { + private final BrowserEntry browserEntry; + private ContextMenu browserActionMenu; + private final Menu menu; + + public QuickAccessMenu(BrowserEntry browserEntry) { + this.browserEntry = browserEntry; + this.menu = new Menu( + // Use original name, not the link target + browserEntry.getRawFileEntry().getName(), + PrettyImageHelper.ofFixedSizeSquare(FileIconManager.getFileIcon(browserEntry.getRawFileEntry(), false), 24) + .createRegion()); + createMenu(); + addInputListeners(); + } + + private void createMenu() { + var fileEntry = browserEntry.getRawFileEntry(); + if (fileEntry.resolved().getKind() != FileKind.DIRECTORY) { + createFileMenu(); + } else { + createDirectoryMenu(); + } + } + + private void createFileMenu() { + var fileEntry = browserEntry.getRawFileEntry(); + menu.setMnemonicParsing(false); + menu.addEventFilter(Menu.ON_SHOWN, event -> { + menu.hide(); + if (keyBasedNavigation && expandBrowserActionMenuKey) { + if (!hideBrowserActionsMenu()) { + showBrowserActionsMenu(); + } + } + }); + menu.setOnAction(event -> { + if (event.getTarget() != menu) { + return; + } + + if (!hideBrowserActionsMenu()) { + showBrowserActionsMenu(); + } + }); + menu.getStyleClass().add("leaf"); + + var empty = new MenuItem("..."); + empty.setDisable(true); + menu.getItems().add(empty); + onRight(empty, true, keyEvent -> { + keyEvent.consume(); + }); + } + + private void createDirectoryMenu() { + var fileEntry = browserEntry.getRawFileEntry().resolved(); + menu.setMnemonicParsing(false); + var empty = new MenuItem("..."); + empty.setDisable(true); + menu.getItems().add(empty); + addHoverHandling(menu, empty); + + menu.setOnAction(event -> { + if (event.getTarget() != menu) { + return; + } + + if (hideBrowserActionsMenu()) { + menu.show(); + event.consume(); + return; + } + + showBrowserActionsMenu(); + event.consume(); + }); + + menu.addEventFilter(Menu.ON_SHOWING, event -> { + hideBrowserActionsMenu(); + }); + + menu.addEventFilter(Menu.ON_SHOWN, event -> { + if (keyBasedNavigation && expandBrowserActionMenuKey) { + if (hideBrowserActionsMenu()) { + menu.show(); + } else { + showBrowserActionsMenu(); + } + } else if (keyBasedNavigation) { + expandDirectoryMenu(empty); + } + }); + + menu.addEventFilter(Menu.ON_HIDING, event -> { + if (closeBrowserActionMenuKey) { + menu.show(); + } + }); + } + + private void addHoverHandling(Menu m, MenuItem empty) { + var hover = new SimpleBooleanProperty(); + menu.addEventFilter(Menu.ON_SHOWING, event -> { + if (!keyBasedNavigation) { + hover.set(true); + } + }); + menu.addEventFilter(Menu.ON_HIDING, event -> { + if (!keyBasedNavigation) { + hover.set(false); + } + }); + new BooleanAnimationTimer(hover, 100, () -> { + expandDirectoryMenu(empty); + }).start(); + } + + private void addInputListeners() { + menu.parentPopupProperty().subscribe(contextMenu -> { + if (contextMenu != null) { + contextMenu.addEventFilter(KeyEvent.KEY_PRESSED, event -> { + keyBasedNavigation = true; + if (event.getCode().equals(KeyCode.ENTER)) { + expandBrowserActionMenuKey = true; + } else { + expandBrowserActionMenuKey = false; + } + if (event.getCode().equals(KeyCode.LEFT) && browserActionMenu != null && browserActionMenu.isShowing()) { + closeBrowserActionMenuKey = true; + } else { + closeBrowserActionMenuKey = false; + } + }); + contextMenu.addEventFilter(MouseEvent.ANY,event -> { + keyBasedNavigation = false; + }); + } + }); + } + + private void expandDirectoryMenu(MenuItem empty) { + if (menu.isShowing() && !menu.getItems().getFirst().equals(empty)) { + return; + } + + ThreadHelper.runFailableAsync(() -> { + var newItems = updateMenuItems(menu, browserEntry, false); + Platform.runLater(() -> { + var reshow = (browserActionMenu == null || !browserActionMenu.isShowing()) && menu.isShowing(); + if (reshow) { + menu.hide(); + } + menu.getItems().setAll(newItems); + if (reshow) { + menu.show(); + } + }); + }); + } + + private boolean hideBrowserActionsMenu() { + if (shownBrowserActionsMenu != null && shownBrowserActionsMenu.isShowing()) { + shownBrowserActionsMenu.hide(); + shownBrowserActionsMenu = null; + return true; + } + return false; + } + + private void showBrowserActionsMenu() { + if (browserActionMenu == null) { + this.browserActionMenu = new BrowserContextMenu(model, browserEntry); + this.browserActionMenu.setOnAction(e -> { + hide(); + }); + onLeft(this.browserActionMenu, true, keyEvent -> { + this.browserActionMenu.hide(); + keyEvent.consume(); + }); + } + + menu.hide(); + browserActionMenu.show(menu.getStyleableNode(), Side.RIGHT, 0, 0); + shownBrowserActionsMenu = browserActionMenu; + Platform.runLater(() -> { + browserActionMenu.getItems().getFirst().getStyleableNode().requestFocus(); + }); + } + } + + private final Supplier base; + private final OpenFileSystemModel model; + private ContextMenu shownBrowserActionsMenu; + + private boolean expandBrowserActionMenuKey; + private boolean keyBasedNavigation; + private boolean closeBrowserActionMenuKey; + + public BrowserQuickAccessContextMenu(Supplier base, OpenFileSystemModel model) { + this.base = base; + this.model = model; + + addEventFilter(Menu.ON_SHOWING, e -> { + Node content = getSkin().getNode(); + if (content instanceof Region r) { + r.setMaxWidth(500); + } + }); + addEventFilter(Menu.ON_SHOWN, e -> { + Platform.runLater(() -> { + getItems().getFirst().getStyleableNode().requestFocus(); + }); + }); + onLeft(this, false, e -> { + hide(); + e.consume(); + }); + setAutoHide(true); + getStyleClass().add("condensed"); + } + + public void showMenu(Node anchor) { + getItems().clear(); + ThreadHelper.runFailableAsync(() -> { + var entry = base.get(); + if (entry.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY) { + return; + } + + var actionsMenu = new AtomicReference(); + var r = new Menu(); + var newItems = updateMenuItems(r, entry, true); + Platform.runLater(() -> { + getItems().addAll(r.getItems()); + show(anchor, Side.RIGHT, 0, 0); + }); + }); + } + + private MenuItem createItem(BrowserEntry browserEntry) { + return new QuickAccessMenu(browserEntry).getMenu(); + } + + private List updateMenuItems( + Menu m, + BrowserEntry entry, + boolean updateInstantly) + throws Exception { + var newFiles = model.getFileSystem().listFiles(entry.getRawFileEntry().resolved().getPath()); + try (var s = newFiles) { + var list = s.map(fileEntry -> fileEntry.resolved()).toList(); + // Wait until all files are listed, i.e. do not skip the stream elements + list = list.subList(0, Math.min(list.size(), 150)); + + var newItems = new ArrayList(); + if (list.isEmpty()) { + var empty = new Menu(""); + empty.getStyleClass().add("leaf"); + newItems.add(empty); + } else { + var browserEntries = list.stream() + .map(fileEntry -> new BrowserEntry(fileEntry, model.getFileList(), false)) + .toList(); + var menus = browserEntries.stream() + .sorted(model.getFileList().order()) + .collect(Collectors.toMap( + e -> e, + e -> createItem(e), + (v1, v2) -> v2, + LinkedHashMap::new)); + var dirs = browserEntries.stream() + .filter(e -> e.getRawFileEntry().getKind() == FileKind.DIRECTORY) + .toList(); + if (dirs.size() == 1) { + updateMenuItems( + (Menu) menus.get(dirs.getFirst()), + dirs.getFirst(), + true); + } + newItems.addAll(menus.values()); + } + if (updateInstantly) { + m.getItems().setAll(newItems); + } + return newItems; + } + } +} diff --git a/app/src/main/java/io/xpipe/app/issue/ErrorHandlerComp.java b/app/src/main/java/io/xpipe/app/issue/ErrorHandlerComp.java index a094c199..b58eab9b 100644 --- a/app/src/main/java/io/xpipe/app/issue/ErrorHandlerComp.java +++ b/app/src/main/java/io/xpipe/app/issue/ErrorHandlerComp.java @@ -9,7 +9,6 @@ import io.xpipe.app.core.AppWindowHelper; import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.augment.GrowAugment; -import io.xpipe.app.util.JfxHelper; import io.xpipe.app.util.LicenseRequiredException; import io.xpipe.app.util.PlatformState; import javafx.application.Platform; @@ -151,8 +150,18 @@ public class ErrorHandlerComp extends SimpleComp { } } + private Region createActionButtonGraphic(String nameString, String descString) { + var header = new Label(nameString); + AppFont.header(header); + var desc = new Label(descString); + AppFont.small(desc); + var text = new VBox(header, desc); + text.setSpacing(2); + return text; + } + private Region createActionComp(ErrorAction a) { - var r = JfxHelper.createNamedEntry(a.getName(), a.getDescription()); + var r = createActionButtonGraphic(a.getName(), a.getDescription()); var b = new ButtonComp(null, r, () -> { takenAction.setValue(a); try { diff --git a/app/src/main/java/io/xpipe/app/util/JfxHelper.java b/app/src/main/java/io/xpipe/app/util/JfxHelper.java index 53e8a8d2..b704bf8c 100644 --- a/app/src/main/java/io/xpipe/app/util/JfxHelper.java +++ b/app/src/main/java/io/xpipe/app/util/JfxHelper.java @@ -3,55 +3,14 @@ package io.xpipe.app.util; import atlantafx.base.controls.Spacer; import io.xpipe.app.core.AppFont; import io.xpipe.app.fxcomps.impl.PrettyImageHelper; -import javafx.beans.binding.Bindings; import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; -import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; -import org.kordamp.ikonli.javafx.FontIcon; public class JfxHelper { - public static Region createNamedEntry(String nameString, String descString) { - var header = new Label(nameString); - AppFont.header(header); - var desc = new Label(descString); - AppFont.small(desc); - var text = new VBox(header, desc); - text.setSpacing(2); - return text; - } - - public static Region createNamedEntry(String nameString, String descString, FontIcon graphic) { - var header = new Label(nameString); - var desc = new Label(descString); - AppFont.small(desc); - desc.setOpacity(0.65); - var text = new VBox(header, desc); - text.setSpacing(2); - - var pane = new StackPane(graphic); - var hbox = new HBox(pane, text); - hbox.setSpacing(8); - pane.prefWidthProperty() - .bind(Bindings.createDoubleBinding( - () -> (header.getHeight() + desc.getHeight()) * 0.6, - header.heightProperty(), - desc.heightProperty())); - pane.prefHeightProperty() - .bind(Bindings.createDoubleBinding( - () -> header.getHeight() + desc.getHeight() + 2, - header.heightProperty(), - desc.heightProperty())); - pane.prefHeightProperty().addListener((c, o, n) -> { - var size = Math.min(n.intValue(), 100); - graphic.setIconSize((int) (size * 0.55)); - }); - return hbox; - } - public static Region createNamedEntry(String nameString, String descString, String image) { var header = new Label(nameString); AppFont.header(header); 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 16bbb034..94d29d7b 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 @@ -208,8 +208,13 @@ -fx-opacity: 1.0; } +.browser .quick-access-button { + -fx-border-radius: 0; + -fx-background-radius: 0; +} -.browser .quick-access-button .context-menu .leaf .arrow { +.browser .quick-access-button .context-menu .leaf > * > .arrow { + -fx-pref-width: 0; -fx-opacity: 0; }