Initial hotkey infra

- Duplicate keyhandler into hotkeyhandler
- Define some view to handle hot keys, like AppView and MenuBarView
- Catalog app binds some menus to hot keys
- Bind all normal keys a-z with alt
- Relates #823
- Relates #826
This commit is contained in:
Janne Valkealahti
2023-08-10 08:27:49 +01:00
parent 9e6da01351
commit 7391d0d24f
8 changed files with 141 additions and 10 deletions

View File

@@ -229,12 +229,24 @@ public class TerminalUI {
private void handleKeyEvent(KeyEvent event) {
log.trace("handleKeyEvent {}", event);
if (rootView != null && rootView.hasFocus()) {
KeyHandler handler = rootView.getKeyHandler();
if (rootView != null) {
// if hotkeys consume, we're done
KeyHandler handler = rootView.getHotKeyHandler();
if (handler != null) {
KeyHandlerResult result = handler.handle(KeyHandler.argsOf(event));
if (result.focus() != null) {
setFocus(result.focus());
if (result.consumed()) {
return;
}
}
// continue with one having focus
if (rootView.hasFocus()) {
handler = rootView.getKeyHandler();
if (handler != null) {
KeyHandlerResult result = handler.handle(KeyHandler.argsOf(event));
if (result.focus() != null) {
setFocus(result.focus());
}
}
}
}

View File

@@ -55,6 +55,7 @@ public abstract class AbstractView extends AbstractControl implements View {
private int layer;
private EventLoop eventLoop;
private Map<Integer, KeyBindingValue> keyBindings = new HashMap<>();
private Map<Integer, KeyBindingValue> hotKeyBindings = new HashMap<>();
private Map<Integer, MouseBindingValue> mouseBindings = new HashMap<>();
public AbstractView() {
@@ -180,6 +181,25 @@ public abstract class AbstractView extends AbstractControl implements View {
return handler;
}
@Override
public KeyHandler getHotKeyHandler() {
log.trace("getHotKeyHandler() {}", this);
KeyHandler handler = args -> {
KeyEvent event = args.event();
boolean consumed = false;
Integer key = event.key();
if (key != null) {
KeyBindingValue keyBindingValue = getHotKeyBindings().get(key);
if (keyBindingValue != null) {
consumed = dispatchRunCommand(event, keyBindingValue);
}
}
return KeyHandler.resultOf(event, consumed, null);
};
return handler;
}
/**
* Sets a callback function which is invoked after a {@link View} has been
* drawn.
@@ -236,6 +256,24 @@ public abstract class AbstractView extends AbstractControl implements View {
});
}
protected void registerHotKeyBinding(Integer keyType, String keyCommand) {
registerHotKeyBinding(keyType, keyCommand, null, null);
}
protected void registerHotKeyBinding(Integer keyType, KeyBindingConsumer keyConsumer) {
registerHotKeyBinding(keyType, null, keyConsumer, null);
}
protected void registerHotKeyBinding(Integer keyType, Runnable keyRunnable) {
registerHotKeyBinding(keyType, null, null, keyRunnable);
}
private void registerHotKeyBinding(Integer keyType, String keyCommand, KeyBindingConsumer keyConsumer, Runnable keyRunnable) {
hotKeyBindings.compute(keyType, (key, old) -> {
return KeyBindingValue.of(old, keyCommand, keyConsumer, keyRunnable);
});
}
record KeyBindingValue(String keyCommand, KeyBindingConsumer keyConsumer, Runnable keyRunnable) {
static KeyBindingValue of(KeyBindingValue old, String keyCommand, KeyBindingConsumer keyConsumer,
Runnable keyRunnable) {
@@ -257,6 +295,15 @@ public abstract class AbstractView extends AbstractControl implements View {
return keyBindings;
}
/**
* Get hotkey bindings.
*
* @return hotkey bindings
*/
protected Map<Integer, KeyBindingValue> getHotKeyBindings() {
return hotKeyBindings;
}
record MouseBindingValue(String mouseCommand, MouseBindingConsumer mouseConsumer, Runnable mouseRunnable,
Predicate<MouseEvent> mousePredicate) {
static MouseBindingValue of(MouseBindingValue old, String mouseCommand, MouseBindingConsumer mouseConsumer,

View File

@@ -133,6 +133,11 @@ public class AppView extends BoxView {
return otherHandler.thenIfNotConsumed(handler);
}
@Override
public KeyHandler getHotKeyHandler() {
return menu != null ? menu.getHotKeyHandler() : super.getHotKeyHandler();
}
@Override
public boolean hasFocus() {
if (grid != null) {

View File

@@ -35,8 +35,8 @@ import org.springframework.shell.component.view.geom.Dimension;
import org.springframework.shell.component.view.geom.Rectangle;
import org.springframework.shell.component.view.screen.Screen;
import org.springframework.shell.component.view.screen.Screen.Writer;
import org.springframework.shell.style.ThemeResolver;
import org.springframework.shell.component.view.screen.ScreenItem;
import org.springframework.shell.style.ThemeResolver;
/**
* {@link MenuBarView} shows {@link MenuBarItem items} horizontally and is
@@ -187,6 +187,17 @@ public class MenuBarView extends BoxView {
return x;
}
private int itemIndex(MenuBarItem item) {
int index = 0;
for (MenuBarItem i : items) {
if (i == item) {
return index;
}
index++;
}
return -1;
}
private void select(MouseEvent event) {
int x = event.x();
int y = event.y();
@@ -256,6 +267,23 @@ public class MenuBarView extends BoxView {
public void setItems(List<MenuBarItem> items) {
this.items.clear();
this.items.addAll(items);
registerHotKeys();
}
private void selectItem(MenuBarItem item) {
int index = itemIndex(item);
if (index > -1) {
setSelected(index);
checkMenuView();
}
}
private void registerHotKeys() {
getItems().stream()
.filter(item -> item.getHotKey() != null)
.forEach(item -> {
registerHotKeyBinding(item.getHotKey(), () -> selectItem(item));
});
}
/**
@@ -265,6 +293,7 @@ public class MenuBarView extends BoxView {
private String title;
private List<MenuItem> items;
private Integer hotKey;
public MenuBarItem(String title) {
this(title, null);
@@ -286,6 +315,15 @@ public class MenuBarView extends BoxView {
public List<MenuItem> getItems() {
return items;
}
public Integer getHotKey() {
return hotKey;
}
public MenuBarItem setHotKey(Integer hotKey) {
this.hotKey = hotKey;
return this;
}
}
}

View File

@@ -60,13 +60,23 @@ public interface View extends Control {
MouseHandler getMouseHandler();
/**
* Gets a {@link View} mouse {@link KeyHandler}. Can be {@code null} which
* Gets a {@link View} key {@link KeyHandler}. Can be {@code null} which
* indicates view will not handle any key events.
*
* @return a view mouse handler
* @return a view key handler
* @see KeyHandler
*/
@Nullable
KeyHandler getKeyHandler();
/**
* Gets a {@link View} hotkey {@link KeyHandler}. Can be {@code null} which
* indicates view will not handle any key events.
*
* @return a view hotkey handler
* @see KeyHandler
*/
@Nullable
KeyHandler getHotKeyHandler();
}

View File

@@ -45,6 +45,10 @@ public class KeyBinder {
keyMap.bind(KeyEvent.Key.Char, Character.toString(i));
}
for (char i = KeyEvent.Key.a; i <= KeyEvent.Key.z; i++) {
keyMap.bind(i | KeyEvent.KeyMask.AltMask, alt(i));
}
keyMap.bind(KeyEvent.Key.q | KeyEvent.KeyMask.CtrlMask, ctrl('q'));
keyMap.bind(KeyEvent.Key.Mouse, key(terminal, Capability.key_mouse));

View File

@@ -44,6 +44,18 @@ class MenuBarViewTests extends AbstractViewTests {
assertThat(view.getItems()).hasSize(1);
}
@Test
void hotkeys() {
MenuBarItem item;
item = MenuBarItem.of("title");
assertThat(item.getHotKey()).isNull();
item.setHotKey(Key.f);
assertThat(item.getHotKey()).isEqualTo(Key.f);
item = MenuBarItem.of("title").setHotKey(Key.f);
assertThat(item.getHotKey()).isEqualTo(Key.f);
}
}
@Nested

View File

@@ -46,6 +46,7 @@ import org.springframework.shell.component.view.control.cell.ListCell;
import org.springframework.shell.component.view.event.EventLoop;
import org.springframework.shell.component.view.event.KeyEvent;
import org.springframework.shell.component.view.event.KeyEvent.Key;
import org.springframework.shell.component.view.event.KeyEvent.KeyMask;
import org.springframework.shell.component.view.geom.Rectangle;
import org.springframework.shell.component.view.message.ShellMessageBuilder;
import org.springframework.shell.component.view.screen.Screen;
@@ -281,11 +282,13 @@ public class Catalog {
.toArray(MenuItem[]::new);
MenuBarView menuBar = MenuBarView.of(
MenuBarItem.of("File",
MenuItem.of("Quit", MenuItemCheckStyle.NOCHECK, quitAction)),
MenuItem.of("Quit", MenuItemCheckStyle.NOCHECK, quitAction))
.setHotKey(Key.f | KeyMask.AltMask),
MenuBarItem.of("Theme",
themeItems),
themeItems)
.setHotKey(Key.t | KeyMask.AltMask),
MenuBarItem.of("Help",
MenuItem.of("About"))
MenuItem.of("About"))
);
menuBar.setThemeResolver(themeResolver);