Browser shortcut rework

This commit is contained in:
crschnick 2024-05-08 09:56:52 +00:00
parent 9e95c6b5b4
commit 26823e4728
23 changed files with 302 additions and 189 deletions

View file

@ -1,19 +1,21 @@
package io.xpipe.app.browser;
import atlantafx.base.theme.Styles;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.impl.TextFieldComp;
import io.xpipe.app.fxcomps.impl.TooltipAugment;
import io.xpipe.app.util.InputHelper;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.HBox;
import atlantafx.base.theme.Styles;
import org.kordamp.ikonli.javafx.FontIcon;
public class BrowserFilterComp extends Comp<BrowserFilterComp.Structure> {
@ -29,9 +31,15 @@ public class BrowserFilterComp extends Comp<BrowserFilterComp.Structure> {
@Override
public Structure createBase() {
var expanded = new SimpleBooleanProperty();
var text = new TextFieldComp(filterString, false).createRegion();
var text = new TextFieldComp(filterString, false).createStructure().get();
var button = new Button();
new TooltipAugment<>("app.search").augment(button);
button.setFocusTraversable(true);
InputHelper.onExactKeyCode(text, KeyCode.ESCAPE, true, keyEvent -> {
text.clear();
button.fire();
keyEvent.consume();
});
new TooltipAugment<>("app.search", new KeyCodeCombination(KeyCode.F, KeyCombination.CONTROL_DOWN)).augment(button);
text.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue && filterString.getValue() == null) {
if (button.isFocused()) {

View file

@ -1,19 +1,17 @@
package io.xpipe.app.browser;
import atlantafx.base.theme.Styles;
import io.xpipe.app.browser.file.BrowserContextMenu;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.browser.icon.FileIconManager;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import io.xpipe.app.fxcomps.impl.StackComp;
import io.xpipe.app.fxcomps.impl.TextFieldComp;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.ThreadHelper;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
@ -25,25 +23,16 @@ import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import atlantafx.base.theme.Styles;
import javafx.scene.layout.StackPane;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
public class BrowserNavBar extends SimpleComp {
private static final PseudoClass INVISIBLE = PseudoClass.getPseudoClass("invisible");
private final OpenFileSystemModel model;
public BrowserNavBar(OpenFileSystemModel model) {
this.model = model;
}
public class BrowserNavBar extends Comp<BrowserNavBar.Structure> {
@Override
protected Region createSimple() {
public Structure createBase() {
var path = new SimpleStringProperty(model.getCurrentPath().get());
model.getCurrentPath().subscribe((newValue) -> {
path.set(newValue);
@ -114,55 +103,54 @@ public class BrowserNavBar extends SimpleComp {
var historyButton = new Button(null, new FontIcon("mdi2h-history"));
historyButton.setAccessibleText("History");
historyButton.getStyleClass().add(Styles.RIGHT_PILL);
// historyButton.getStyleClass().add("path-graphic-button");
new ContextMenuAugment<>(event -> event.getButton() == MouseButton.PRIMARY, null, this::createContextMenu)
.augment(new SimpleCompStructure<>(historyButton));
var breadcrumbs = new BrowserBreadcrumbBar(model).grow(false, true);
var stack = new StackComp(List.of(pathBar, breadcrumbs))
.apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT))
.hgrow()
.apply(struc -> {
var t = struc.get().getChildren().get(0);
var b = struc.get().getChildren().get(1);
b.setOnMouseClicked(event -> {
t.requestFocus();
var pathRegion = pathBar.createStructure().get();
var breadcrumbsRegion = breadcrumbs.createRegion();
breadcrumbsRegion.setOnMouseClicked(event -> {
pathRegion.requestFocus();
event.consume();
});
b.visibleProperty()
breadcrumbsRegion.visibleProperty()
.bind(Bindings.createBooleanBinding(
() -> {
return !t.isFocused()
return !pathRegion.isFocused()
&& !model.getInOverview().get();
},
t.focusedProperty(),
pathRegion.focusedProperty(),
model.getInOverview()));
})
.grow(false, true);
var stack = new StackPane(pathRegion, breadcrumbsRegion);
stack.setAlignment(Pos.CENTER_LEFT);
HBox.setHgrow(stack, Priority.ALWAYS);
var topBox = new HorizontalComp(List.of(Comp.of(() -> homeButton), stack, Comp.of(() -> historyButton)))
.apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT))
.apply(struc -> {
((Region) struc.get().getChildren().get(0))
.minHeightProperty()
.bind(((Region) struc.get().getChildren().get(1)).heightProperty());
((Region) struc.get().getChildren().get(0))
.maxHeightProperty()
.bind(((Region) struc.get().getChildren().get(1)).heightProperty());
var topBox = new HBox(homeButton, stack, historyButton);
homeButton.minHeightProperty().bind(stack.heightProperty());
homeButton.maxHeightProperty().bind(stack.heightProperty());
historyButton.minHeightProperty().bind(stack.heightProperty());
historyButton.maxHeightProperty().bind(stack.heightProperty());
topBox.setPickOnBounds(false);
HBox.setHgrow(topBox, Priority.ALWAYS);
((Region) struc.get().getChildren().get(2))
.minHeightProperty()
.bind(((Region) struc.get().getChildren().get(1)).heightProperty());
((Region) struc.get().getChildren().get(2))
.maxHeightProperty()
.bind(((Region) struc.get().getChildren().get(1)).heightProperty());
})
.apply(struc -> {
struc.get().setPickOnBounds(false);
})
.hgrow();
return new Structure(topBox,pathRegion, historyButton);
}
return topBox.createRegion();
public record Structure(HBox box, TextField textField, Button historyButton) implements CompStructure<HBox> {
@Override
public HBox get() {
return box;
}
}
private static final PseudoClass INVISIBLE = PseudoClass.getPseudoClass("invisible");
private final OpenFileSystemModel model;
public BrowserNavBar(OpenFileSystemModel model) {
this.model = model;
}
private ContextMenu createContextMenu() {

View file

@ -78,7 +78,7 @@ public class BrowserTransferComp extends SimpleComp {
})
.hide(Bindings.isEmpty(syncItems))
.disable(syncAllDownloaded)
.apply(new TooltipAugment<>("downloadStageDescription"));
.tooltipKey("downloadStageDescription");
var clearButton = new IconButtonComp("mdi2c-close", () -> {
model.clear();
})

View file

@ -4,14 +4,13 @@ import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.fxcomps.impl.TooltipAugment;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.Shortcuts;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.LicenseProvider;
import io.xpipe.app.util.ThreadHelper;
import javafx.scene.control.Button;
import javafx.scene.control.MenuItem;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Region;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
@ -20,7 +19,7 @@ public interface LeafAction extends BrowserAction {
void execute(OpenFileSystemModel model, List<BrowserEntry> entries) throws Exception;
default Button toButton(OpenFileSystemModel model, List<BrowserEntry> selected) {
default Button toButton(Region root, OpenFileSystemModel model, List<BrowserEntry> selected) {
var b = new Button();
b.setOnAction(event -> {
// Only accept shortcut actions in the current tab
@ -37,17 +36,20 @@ public interface LeafAction extends BrowserAction {
});
event.consume();
});
if (getShortcut() != null) {
Shortcuts.addShortcut(b, getShortcut());
}
var name = getName(model, selected);
new TooltipAugment<>(name).augment(b);
new TooltipAugment<>(name, getShortcut()).augment(b);
var graphic = getIcon(model, selected);
if (graphic != null) {
b.setGraphic(graphic);
}
b.setMnemonicParsing(false);
b.accessibleTextProperty().bind(name);
root.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (getShortcut() != null && getShortcut().match(event)) {
b.fire();
event.consume();
}
});
b.setDisable(!isActive(model, selected));
model.getCurrentPath().addListener((observable, oldValue, newValue) -> {

View file

@ -5,6 +5,7 @@ import io.xpipe.app.browser.action.BrowserAction;
import io.xpipe.app.browser.action.LeafAction;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.util.InputHelper;
import io.xpipe.app.util.LicenseProvider;
import javafx.scene.control.ContextMenu;
@ -38,6 +39,11 @@ public final class BrowserContextMenu extends ContextMenu {
}
private void createMenu() {
InputHelper.onLeft(this, false, e -> {
hide();
e.consume();
});
AppFont.normal(this.getStyleableNode());
var empty = source == null;

View file

@ -10,9 +10,7 @@ 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.PlatformThread;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.HumanReadableFormat;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.app.util.*;
import io.xpipe.core.process.OsType;
import io.xpipe.core.store.FileKind;
import io.xpipe.core.store.FileNames;
@ -26,12 +24,16 @@ import javafx.collections.ListChangeListener;
import javafx.css.PseudoClass;
import javafx.geometry.Bounds;
import javafx.geometry.Pos;
import javafx.geometry.Side;
import javafx.scene.AccessibleRole;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.control.skin.TableViewSkin;
import javafx.scene.control.skin.VirtualFlow;
import javafx.scene.input.*;
import javafx.scene.input.DragEvent;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
@ -128,7 +130,6 @@ public final class BrowserFileListComp extends SimpleComp {
} else {
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
}
table.getSelectionModel().setCellSelectionEnabled(false);
table.getSelectionModel().getSelectedItems().addListener((ListChangeListener<? super BrowserEntry>) c -> {
@ -158,18 +159,24 @@ public final class BrowserFileListComp extends SimpleComp {
private void prepareTableShortcuts(TableView<BrowserEntry> table) {
table.setOnKeyPressed(event -> {
var selected = fileList.getSelection();
BrowserAction.getFlattened(fileList.getFileSystemModel(), selected).stream()
.filter(browserAction -> browserAction.isApplicable(fileList.getFileSystemModel(), selected)
&& browserAction.isActive(fileList.getFileSystemModel(), selected))
.filter(browserAction -> browserAction.getShortcut() != null)
.filter(browserAction -> browserAction.getShortcut().match(event))
.findAny()
.ifPresent(browserAction -> {
var action = BrowserAction.getFlattened(fileList.getFileSystemModel(), selected).stream().filter(
browserAction -> browserAction.isApplicable(fileList.getFileSystemModel(), selected) &&
browserAction.isActive(fileList.getFileSystemModel(), selected)).filter(
browserAction -> browserAction.getShortcut() != null).filter(browserAction -> browserAction.getShortcut().match(event)).findAny();
action.ifPresent(browserAction -> {
ThreadHelper.runFailableAsync(() -> {
browserAction.execute(fileList.getFileSystemModel(), selected);
});
event.consume();
});
if (action.isPresent()) {
return;
}
if (event.getCode() == KeyCode.ESCAPE) {
table.getSelectionModel().clearSelection();
event.consume();
}
});
}
@ -344,7 +351,6 @@ public final class BrowserFileListComp extends SimpleComp {
// Sort the list ourselves as sorting the table would incur a lot of cell updates
var obs = FXCollections.observableList(newItems);
table.getItems().setAll(obs);
// table.sort();
}
var currentDirectory = fileList.getFileSystemModel().getCurrentDirectory();
@ -488,9 +494,6 @@ public final class BrowserFileListComp extends SimpleComp {
.not()
.not())
.focusTraversable(false)
.apply(struc -> struc.get().focusedProperty().addListener((observable, oldValue, newValue) -> {
getTableRow().requestFocus();
}))
.createRegion();
editing.addListener((observable, oldValue, newValue) -> {
@ -520,13 +523,20 @@ public final class BrowserFileListComp extends SimpleComp {
graphic.setAlignment(Pos.CENTER_LEFT);
setGraphic(graphic);
tableView.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (event.getCode() == KeyCode.RIGHT) {
InputHelper.onExactKeyCode(tableView, KeyCode.RIGHT, true, event -> {
var selected = fileList.getSelection();
if (selected.size() == 1 && selected.getFirst() == getTableRow().getItem()) {
((ButtonBase) quickAccess).fire();
event.consume();
}
});
InputHelper.onExactKeyCode(tableView, KeyCode.SPACE, true, event -> {
var selected = fileList.getSelection();
// Only show one menu across all selected entries
if (selected.size() > 0 && selected.getLast() == getTableRow().getItem()) {
var cm = new BrowserContextMenu(fileList.getFileSystemModel(), getTableRow().getItem());
ContextMenuHelper.toggleShow(cm, this, Side.RIGHT);
event.consume();
}
});
}

View file

@ -1,19 +1,16 @@
package io.xpipe.app.browser.file;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.store.FileKind;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.FileSystem;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import lombok.Getter;
import java.util.ArrayList;
@ -37,8 +34,6 @@ public final class BrowserFileListModel {
private final Property<List<BrowserEntry>> shown = new SimpleObjectProperty<>(new ArrayList<>());
private final ObservableList<BrowserEntry> previousSelection = FXCollections.observableArrayList();
private final ObservableList<BrowserEntry> selection = FXCollections.observableArrayList();
private final ObservableList<FileSystem.FileEntry> selectedRaw =
ListBindingsHelper.mappedContentBinding(selection, entry -> entry.getRawFileEntry());
private final Property<BrowserEntry> draggedOverDirectory = new SimpleObjectProperty<>();
private final Property<Boolean> draggedOverEmpty = new SimpleBooleanProperty();

View file

@ -4,9 +4,6 @@ import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.util.InputHelper;
import javafx.application.Platform;
import javafx.scene.control.Menu;
import javafx.scene.layout.Region;
import java.util.function.Supplier;
@ -34,11 +31,6 @@ public class BrowserQuickAccessButtonComp extends SimpleComp {
}
event.consume();
});
cm.addEventFilter(Menu.ON_HIDDEN, e -> {
Platform.runLater(() -> {
struc.get().requestFocus();
});
});
InputHelper.onRight(struc.get(), false, keyEvent -> {
cm.showMenu(struc.get());
keyEvent.consume();

View file

@ -1,5 +1,6 @@
package io.xpipe.app.browser.fs;
import atlantafx.base.controls.Spacer;
import io.xpipe.app.browser.BrowserFilterComp;
import io.xpipe.app.browser.BrowserNavBar;
import io.xpipe.app.browser.BrowserOverviewComp;
@ -13,9 +14,9 @@ import io.xpipe.app.fxcomps.Comp;
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.TooltipAugment;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.Shortcuts;
import io.xpipe.app.util.InputHelper;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
@ -28,8 +29,6 @@ import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import atlantafx.base.controls.Spacer;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.ArrayList;
@ -53,17 +52,24 @@ public class OpenFileSystemComp extends SimpleComp {
}
private Region createContent() {
var root = new VBox();
var overview = new Button(null, new FontIcon("mdi2m-monitor"));
overview.setOnAction(e -> model.cdAsync(null));
new TooltipAugment<>("overview", new KeyCodeCombination(KeyCode.HOME, KeyCombination.ALT_DOWN)).augment(overview);
overview.disableProperty().bind(model.getInOverview());
overview.setAccessibleText("System overview");
InputHelper.onKeyCombination(root, new KeyCodeCombination(KeyCode.HOME, KeyCombination.ALT_DOWN), true, keyEvent -> {
overview.fire();
keyEvent.consume();
});
var backBtn = BrowserAction.byId("back", model, List.of()).toButton(model, List.of());
var forthBtn = BrowserAction.byId("forward", model, List.of()).toButton(model, List.of());
var refreshBtn = BrowserAction.byId("refresh", model, List.of()).toButton(model, List.of());
var terminalBtn = BrowserAction.byId("openTerminal", model, List.of()).toButton(model, List.of());
var backBtn = BrowserAction.byId("back", model, List.of()).toButton(root, model, List.of());
var forthBtn = BrowserAction.byId("forward", model, List.of()).toButton(root, model, List.of());
var refreshBtn = BrowserAction.byId("refresh", model, List.of()).toButton(root, model, List.of());
var terminalBtn = BrowserAction.byId("openTerminal", model, List.of()).toButton(root, model, List.of());
var menuButton = new MenuButton(null, new FontIcon("mdral-folder_open"));
var menuButton = new MenuButton(null, new FontIcon
("mdral-folder_open"));
new ContextMenuAugment<>(
event -> event.getButton() == MouseButton.PRIMARY,
null,
@ -73,18 +79,18 @@ public class OpenFileSystemComp extends SimpleComp {
menuButton.setAccessibleText("Directory options");
var filter = new BrowserFilterComp(model, model.getFilter()).createStructure();
Shortcuts.addShortcut(filter.toggleButton(), new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN));
var topBar = new HBox();
topBar.setAlignment(Pos.CENTER);
topBar.getStyleClass().add("top-bar");
var navBar = new BrowserNavBar(model).createStructure();
topBar.getChildren()
.setAll(
overview,
backBtn,
forthBtn,
new Spacer(10),
new BrowserNavBar(model).hgrow().createRegion(),
navBar.get(),
new Spacer(5),
filter.get(),
refreshBtn,
@ -92,9 +98,18 @@ public class OpenFileSystemComp extends SimpleComp {
menuButton);
var content = createFileListContent();
var root = new VBox(topBar, content);
root.getChildren().addAll(topBar, content);
VBox.setVgrow(content, Priority.ALWAYS);
root.setPadding(Insets.EMPTY);
InputHelper.onCtrlKeyCode(root, KeyCode.F, true, keyEvent -> {
filter.toggleButton().fire();
filter.textField().requestFocus();
keyEvent.consume();
});
InputHelper.onCtrlKeyCode(root, KeyCode.L, true, keyEvent -> {
navBar.textField().requestFocus();
keyEvent.consume();
});
return root;
}

View file

@ -10,6 +10,7 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.InputHelper;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty;
@ -21,6 +22,7 @@ import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.input.DragEvent;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
@ -164,6 +166,24 @@ public class BrowserSessionTabsComp extends SimpleComp {
}
}
});
InputHelper.onInput(tabs, true, keyEvent -> {
var current = tabs.getSelectionModel().getSelectedItem();
if (current == null) {
return;
}
if (keyEvent.getCode() == KeyCode.W && keyEvent.isShortcutDown()) {
tabs.getTabs().remove(current);
keyEvent.consume();
}
if (keyEvent.getCode() == KeyCode.W && keyEvent.isShortcutDown() && keyEvent.isShiftDown()) {
tabs.getTabs().clear();
keyEvent.consume();
}
});
return tabs;
}
@ -216,7 +236,7 @@ public class BrowserSessionTabsComp extends SimpleComp {
if (color != null) {
c.getStyleClass().add(color.getId());
}
new TooltipAugment<>(new SimpleStringProperty(model.getTooltip())).augment(c);
new TooltipAugment<>(new SimpleStringProperty(model.getTooltip()), null).augment(c);
c.addEventHandler(
DragEvent.DRAG_ENTERED,
mouseEvent -> Platform.runLater(

View file

@ -70,8 +70,8 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
for (int i = 0; i < entries.size(); i++) {
var e = entries.get(i);
var b = new IconButtonComp(e.icon(), () -> value.setValue(e));
b.shortcut(new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + i]));
b.apply(new TooltipAugment<>(e.name()));
var shortcut = new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + i]);
b.apply(new TooltipAugment<>(e.name(), shortcut));
b.apply(struc -> {
AppFont.setSize(struc.get(), 2);
struc.get().pseudoClassStateChanged(selected, value.getValue().equals(e));
@ -123,9 +123,9 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
};
{
var shortcut = new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + entries.size()]);
var b = new IconButtonComp("mdi2g-github", () -> Hyperlinks.open(Hyperlinks.GITHUB))
.shortcut(new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + entries.size()]))
.apply(new TooltipAugment<>("visitGithubRepository"))
.tooltipKey("visitGithubRepository", shortcut)
.apply(simpleBorders)
.accessibleTextKey("visitGithubRepository");
b.apply(struc -> {
@ -135,9 +135,9 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
}
{
var shortcut = new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + entries.size() + 1]);
var b = new IconButtonComp("mdi2d-discord", () -> Hyperlinks.open(Hyperlinks.DISCORD))
.shortcut(new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + entries.size() + 1]))
.apply(new TooltipAugment<>("discord"))
.tooltipKey("discord", shortcut)
.apply(simpleBorders)
.accessibleTextKey("discord");
b.apply(struc -> {
@ -147,9 +147,9 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
}
{
var shortcut = new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + entries.size() + 2]);
var b = new IconButtonComp("mdi2t-translate", () -> Hyperlinks.open(Hyperlinks.TRANSLATE))
.shortcut(new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + entries.size() + 2]))
.apply(new TooltipAugment<>("translate"))
.tooltipKey("translate", shortcut)
.apply(simpleBorders)
.accessibleTextKey("translate");
b.apply(struc -> {
@ -160,7 +160,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
{
var b = new IconButtonComp("mdi2u-update", () -> UpdateAvailableAlert.showIfNeeded())
.apply(new TooltipAugment<>("updateAvailableTooltip"))
.tooltipKey("updateAvailableTooltip")
.accessibleTextKey("updateAvailableTooltip");
b.apply(struc -> {
AppFont.setSize(struc.get(), 2);

View file

@ -190,7 +190,7 @@ public abstract class StoreEntryComp extends SimpleComp {
var imageComp = PrettyImageHelper.ofFixedSize(img, w, h);
var storeIcon = imageComp.createRegion();
if (wrapper.getValidity().getValue().isUsable()) {
new TooltipAugment<>(wrapper.getEntry().getProvider().displayName()).augment(storeIcon);
new TooltipAugment<>(wrapper.getEntry().getProvider().displayName(), null).augment(storeIcon);
}
var stack = new StackPane(storeIcon);
@ -227,7 +227,7 @@ public abstract class StoreEntryComp extends SimpleComp {
button.accessibleText(
actionProvider.getName(wrapper.getEntry().ref()).getValue());
button.apply(new TooltipAugment<>(
actionProvider.getName(wrapper.getEntry().ref())));
actionProvider.getName(wrapper.getEntry().ref()), null));
if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ONLY_SHOW_IF_ENABLED) {
button.hide(Bindings.not(p.getValue()));
} else if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ALWAYS_SHOW) {
@ -266,7 +266,7 @@ public abstract class StoreEntryComp extends SimpleComp {
event -> event.getButton() == MouseButton.PRIMARY,
null,
() -> StoreEntryComp.this.createContextMenu()));
settingsButton.apply(new TooltipAugment<>("more"));
settingsButton.tooltipKey("more");
return settingsButton;
}

View file

@ -7,12 +7,10 @@ import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FilterComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.impl.TooltipAugment;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.OsType;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
@ -29,7 +27,6 @@ import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.text.TextAlignment;
import org.kordamp.ikonli.javafx.FontIcon;
public class StoreEntryListStatusComp extends SimpleComp {
@ -186,7 +183,7 @@ public class StoreEntryListStatusComp extends SimpleComp {
sortMode));
});
alphabetical.accessibleTextKey("sortAlphabetical");
alphabetical.apply(new TooltipAugment<>("sortAlphabetical"));
alphabetical.tooltipKey("sortAlphabetical");
return alphabetical;
}
@ -225,7 +222,7 @@ public class StoreEntryListStatusComp extends SimpleComp {
sortMode));
});
date.accessibleTextKey("sortLastUsed");
date.apply(new TooltipAugment<>("sortLastUsed"));
date.tooltipKey("sortLastUsed");
return date;
}

View file

@ -7,7 +7,6 @@ import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.prefs.CloseBehaviourAlert;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.OsType;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Rectangle2D;
@ -17,18 +16,17 @@ import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Region;
import javafx.stage.Screen;
import javafx.stage.Stage;
import lombok.Builder;
import lombok.Getter;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import javax.imageio.ImageIO;
import java.io.IOException;
import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import javax.imageio.ImageIO;
public class AppMainWindow {
@ -157,6 +155,13 @@ public class AppMainWindow {
e.consume();
});
stage.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (event.getCode().equals(KeyCode.Q) && event.isShortcutDown()) {
stage.close();
event.consume();
}
});
TrackEvent.debug("Window listeners added");
}

View file

@ -161,13 +161,11 @@ public class AppWindowHelper {
event.consume();
});
a.getDialogPane().getScene().addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (OsType.getLocal().equals(OsType.LINUX) || OsType.getLocal().equals(OsType.MACOS)) {
if (event.getCode().equals(KeyCode.W) && event.isShortcutDown()) {
s.close();
event.consume();
return;
}
}
if (event.getCode().equals(KeyCode.ESCAPE)) {
s.close();
@ -274,12 +272,10 @@ public class AppWindowHelper {
});
scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (OsType.getLocal().equals(OsType.LINUX) || OsType.getLocal().equals(OsType.MACOS)) {
if (event.getCode().equals(KeyCode.W) && event.isShortcutDown()) {
stage.close();
event.consume();
}
}
});
}

View file

@ -1,5 +1,6 @@
package io.xpipe.app.fxcomps;
import atlantafx.base.controls.Spacer;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.augment.Augment;
import io.xpipe.app.fxcomps.augment.GrowAugment;
@ -7,12 +8,10 @@ import io.xpipe.app.fxcomps.impl.TooltipAugment;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.Shortcuts;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.control.ButtonBase;
import javafx.scene.control.Separator;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.HBox;
@ -20,8 +19,6 @@ import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import atlantafx.base.controls.Spacer;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@ -186,20 +183,20 @@ public abstract class Comp<S extends CompStructure<?>> {
return apply(struc -> Shortcuts.addShortcut(struc.get(), shortcut, r -> con.accept(struc)));
}
public Comp<S> shortcut(KeyCombination shortcut) {
return apply(struc -> Shortcuts.addShortcut((ButtonBase) struc.get(), shortcut));
}
public Comp<S> displayOnlyShortcut(KeyCombination shortcut) {
return apply(struc -> Shortcuts.addDisplayShortcut(struc.get(), shortcut));
}
public Comp<S> tooltip(ObservableValue<String> text) {
return apply(new TooltipAugment<>(text));
return apply(new TooltipAugment<>(text, null));
}
public Comp<S> tooltipKey(String key) {
return apply(new TooltipAugment<>(key));
return apply(new TooltipAugment<>(key, null));
}
public Comp<S> tooltipKey(String key, KeyCombination shortcut) {
return apply(new TooltipAugment<>(key, shortcut));
}
public Region createRegion() {

View file

@ -42,7 +42,7 @@ public class ContextMenuAugment<S extends CompStructure<?>> implements Augment<S
};
var r = struc.get();
r.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
r.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
if (mouseEventCheck != null && mouseEventCheck.test(event)) {
if (!hide.get()) {
var cm = contextMenu.get();
@ -55,18 +55,18 @@ public class ContextMenuAugment<S extends CompStructure<?>> implements Augment<S
event.consume();
}
});
r.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
r.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
if (mouseEventCheck != null && mouseEventCheck.test(event)) {
event.consume();
}
});
r.addEventFilter(KeyEvent.KEY_RELEASED, event -> {
r.addEventHandler(KeyEvent.KEY_RELEASED, event -> {
if (keyEventCheck != null && keyEventCheck.test(event)) {
event.consume();
}
});
r.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
r.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
if (keyEventCheck != null && keyEventCheck.test(event)) {
if (!hide.get()) {
var cm = contextMenu.get();
@ -80,7 +80,7 @@ public class ContextMenuAugment<S extends CompStructure<?>> implements Augment<S
});
if (r instanceof ButtonBase buttonBase && keyEventCheck == null) {
buttonBase.addEventFilter(ActionEvent.ACTION, event -> {
buttonBase.addEventHandler(ActionEvent.ACTION, event -> {
if (buttonBase.getOnAction() != null) {
return;
}

View file

@ -124,7 +124,7 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
ErrorEvent.fromThrowable(e).handle();
}
});
gitShareButton.apply(new TooltipAugment<>("gitShareFileTooltip"));
gitShareButton.tooltipKey("gitShareFileTooltip");
gitShareButton.styleClass(Styles.RIGHT_PILL).grow(false, true);
var layout = new HorizontalComp(List.of(fileNameComp, fileBrowseButton, gitShareButton))

View file

@ -4,47 +4,46 @@ import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.augment.Augment;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.Shortcuts;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Tooltip;
import javafx.scene.input.KeyCombination;
import javafx.stage.Window;
public class TooltipAugment<S extends CompStructure<?>> implements Augment<S> {
private final ObservableValue<String> text;
private final KeyCombination shortcut;
public TooltipAugment(ObservableValue<String> text) {
this.text = PlatformThread.sync(text);
public TooltipAugment(ObservableValue<String> text, KeyCombination shortcut) {
this.text = text;
this.shortcut = shortcut;
}
public TooltipAugment(String key) {
public TooltipAugment(String key, KeyCombination shortcut) {
this.text = AppI18n.observable(key);
this.shortcut = shortcut;
}
@Override
public void augment(S struc) {
var region = struc.get();
var tt = new FixedTooltip();
if (Shortcuts.getDisplayShortcut(region) != null) {
if (shortcut != null) {
var s = AppI18n.observable("shortcut");
var binding = Bindings.createStringBinding(
() -> {
return text.getValue() + "\n\n" + s.getValue() + ": "
+ Shortcuts.getDisplayShortcut(region).getDisplayText();
return text.getValue() + "\n\n" + s.getValue() + ": " + shortcut.getDisplayText();
},
text,
s);
tt.textProperty().bind(binding);
tt.textProperty().bind(PlatformThread.sync(binding));
} else {
tt.textProperty().bind(text);
tt.textProperty().bind(PlatformThread.sync(text));
}
tt.setStyle("-fx-font-size: 11pt;");
tt.setWrapText(true);
tt.setMaxWidth(400);
tt.getStyleClass().add("fancy-tooltip");
Tooltip.install(struc.get(), tt);
}

View file

@ -3,6 +3,7 @@ package io.xpipe.app.util;
import javafx.event.EventHandler;
import javafx.event.EventTarget;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
@ -11,9 +12,17 @@ import java.util.function.Consumer;
public class InputHelper {
public static void onLeft(EventTarget target, boolean filter, Consumer<KeyEvent> r) {
public static void onCtrlKeyCode(EventTarget target, KeyCode code, boolean filter, Consumer<KeyEvent> r) {
EventHandler<KeyEvent> keyEventEventHandler = event -> {
if (event.getCode() == KeyCode.LEFT || event.getCode() == KeyCode.NUMPAD4) {
if (event.isAltDown() || event.isShiftDown()) {
return;
}
if (!event.isShortcutDown()) {
return;
}
if (code == event.getCode()) {
r.accept(event);
}
};
@ -24,9 +33,9 @@ public class InputHelper {
}
}
public static void onRight(EventTarget target, boolean filter, Consumer<KeyEvent> r) {
public static void onKeyCombination(EventTarget target, KeyCombination c, boolean filter, Consumer<KeyEvent> r) {
EventHandler<KeyEvent> keyEventEventHandler = event -> {
if (event.getCode() == KeyCode.RIGHT || event.getCode() == KeyCode.NUMPAD6) {
if (c.match(event)) {
r.accept(event);
}
};
@ -37,6 +46,60 @@ public class InputHelper {
}
}
public static void onExactKeyCode(EventTarget target, KeyCode code, boolean filter, Consumer<KeyEvent> r) {
EventHandler<KeyEvent> keyEventEventHandler = event -> {
if (event.isAltDown() || event.isShiftDown() || event.isShortcutDown()) {
return;
}
if (code == event.getCode()) {
r.accept(event);
}
};
if (filter) {
target.addEventFilter(KeyEvent.KEY_PRESSED, keyEventEventHandler);
} else {
target.addEventHandler(KeyEvent.KEY_PRESSED, keyEventEventHandler);
}
}
public static void onInput(EventTarget target, boolean filter, Consumer<KeyEvent> r) {
EventHandler<KeyEvent> keyEventEventHandler = event -> {
r.accept(event);
};
if (filter) {
target.addEventFilter(KeyEvent.KEY_PRESSED, keyEventEventHandler);
} else {
target.addEventHandler(KeyEvent.KEY_PRESSED, keyEventEventHandler);
}
}
public static void onLeft(EventTarget target, boolean filter, Consumer<KeyEvent> r) {
EventHandler<KeyEvent> e = event -> {
if (event.getCode() == KeyCode.LEFT || event.getCode() == KeyCode.NUMPAD4) {
r.accept(event);
}
};
if (filter) {
target.addEventFilter(KeyEvent.KEY_PRESSED, e);
} else {
target.addEventHandler(KeyEvent.KEY_PRESSED, e);
}
}
public static void onRight(EventTarget target, boolean filter, Consumer<KeyEvent> r) {
EventHandler<KeyEvent> e = event -> {
if (event.getCode() == KeyCode.RIGHT || event.getCode() == KeyCode.NUMPAD6) {
r.accept(event);
}
};
if (filter) {
target.addEventFilter(KeyEvent.KEY_PRESSED, e);
} else {
target.addEventHandler(KeyEvent.KEY_PRESSED, e);
}
}
public static void onNavigationInput(EventTarget target, Consumer<Boolean> r) {
target.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
var c = event.getCode();

View file

@ -182,10 +182,14 @@
-fx-background-color: -color-accent-subtle;
}
.browser .table-row-cell:selected, .browser .table-row-cell:hover:selected {
.browser .table-row-cell:selected, .browser .table-row-cell:hover:selected, .root:key-navigation .browser .table-row-cell:focused:selected {
-fx-background-color: -color-success-subtle;
}
.root:key-navigation .browser .table-row-cell:focused {
-fx-background-color: -color-warning-subtle;
}
.root.nord .browser .table-row-cell:selected, .root.nord .browser .table-row-cell:hover:selected {
-fx-background-color: -color-success-7;
}

View file

@ -10,6 +10,9 @@ import io.xpipe.core.store.FileKind;
import javafx.beans.value.ObservableValue;
import javafx.scene.Node;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
@ -34,6 +37,11 @@ public class OpenDirectoryInNewTabAction implements LeafAction {
return Category.OPEN;
}
@Override
public KeyCombination getShortcut() {
return new KeyCodeCombination(KeyCode.ENTER, KeyCombination.SHIFT_DOWN);
}
@Override
public boolean acceptsEmptySelection() {
return true;

View file

@ -10,6 +10,9 @@ import io.xpipe.core.process.ShellControl;
import io.xpipe.core.store.FileNames;
import javafx.beans.value.ObservableValue;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import java.util.List;
@ -62,6 +65,11 @@ public class OpenNativeFileDetailsAction implements LeafAction {
}
}
@Override
public KeyCombination getShortcut() {
return new KeyCodeCombination(KeyCode.ENTER,KeyCombination.ALT_DOWN);
}
@Override
public Category getCategory() {
return Category.NATIVE;