Make Catalog browsers focusable with shortcuts

- Category and scenario list views can now be focused with
  ctrl+a/ctrl+s.
- Bind all keys with ctrl modifier
- Add "shortcut" concept to views which currently hooks to
  hot keys.
- In a catalog app by using a "shortcut" then takes the key
  event as a hot key, as it consumes resulting behaviour
  is to focus.
- Move view initInternal away from constructor call to
  require user to call init() which is not in a
  View interface
- Relates #826
This commit is contained in:
Janne Valkealahti
2023-11-12 16:43:11 +00:00
parent a9b1675595
commit 2d29050c34
10 changed files with 53 additions and 7 deletions

View File

@@ -190,6 +190,7 @@ public class TerminalUI implements ViewService {
* @param view the view to configure * @param view the view to configure
*/ */
public void configure(View view) { public void configure(View view) {
view.init();
view.setEventLoop(eventLoop); view.setEventLoop(eventLoop);
view.setThemeResolver(themeResolver); view.setThemeResolver(themeResolver);
view.setThemeName(themeName); view.setThemeName(themeName);

View File

@@ -60,10 +60,7 @@ public abstract class AbstractView extends AbstractControl implements View {
private Map<Integer, KeyBindingValue> keyBindings = new HashMap<>(); private Map<Integer, KeyBindingValue> keyBindings = new HashMap<>();
private Map<Integer, KeyBindingValue> hotKeyBindings = new HashMap<>(); private Map<Integer, KeyBindingValue> hotKeyBindings = new HashMap<>();
private Map<Integer, MouseBindingValue> mouseBindings = new HashMap<>(); private Map<Integer, MouseBindingValue> mouseBindings = new HashMap<>();
private boolean init = false;
public AbstractView() {
init();
}
/** /**
* Register {@link Disposable} to get disposed when view terminates. * Register {@link Disposable} to get disposed when view terminates.
@@ -88,8 +85,20 @@ public abstract class AbstractView extends AbstractControl implements View {
* *
* @see #initInternal() * @see #initInternal()
*/ */
protected final void init() { @Override
public final void init() {
if (init) {
return;
}
initInternal(); initInternal();
init = true;
}
private Integer shortcutKey;
private Runnable shortcutAction;
public void shortcut(Integer key, Runnable runnable) {
this.shortcutKey = key;
this.shortcutAction = runnable;
} }
/** /**
@@ -97,6 +106,9 @@ public abstract class AbstractView extends AbstractControl implements View {
* usefull. Typically key and mousebindings are registered from this method. * usefull. Typically key and mousebindings are registered from this method.
*/ */
protected void initInternal() { protected void initInternal() {
if (shortcutKey != null && shortcutAction != null) {
registerHotKeyBinding(shortcutKey, shortcutAction);
}
} }
@Override @Override

View File

@@ -261,6 +261,28 @@ public class GridView extends BoxView {
return super.getKeyHandler(); return super.getKeyHandler();
} }
@Override
public KeyHandler getHotKeyHandler() {
log.trace("getHotKeyHandler()");
KeyHandler handler = null;
for (GridItem i : gridItems) {
if (handler == null) {
handler = i.view.getHotKeyHandler();
}
else {
handler = handler.thenIfNotConsumed(i.view.getHotKeyHandler());
}
// handler = i.view.getHotKeyHandler();
// if (i.view.hasFocus()) {
// handler = i.view.getHotKeyHandler();
// break;
// }
}
if (handler != null) {
return handler.thenIfNotConsumed(super.getHotKeyHandler());
}
return super.getHotKeyHandler();
}
@Override @Override
public boolean hasFocus() { public boolean hasFocus() {

View File

@@ -100,6 +100,7 @@ public class ListView<T> extends BoxView {
@Override @Override
protected void initInternal() { protected void initInternal() {
super.initInternal();
registerViewCommand(ViewCommand.LINE_UP, () -> up()); registerViewCommand(ViewCommand.LINE_UP, () -> up());
registerViewCommand(ViewCommand.LINE_DOWN, () -> down()); registerViewCommand(ViewCommand.LINE_DOWN, () -> down());

View File

@@ -247,9 +247,11 @@ public class MenuBarView extends BoxView {
private MenuView buildMenuView(MenuBarItem item) { private MenuView buildMenuView(MenuBarItem item) {
MenuView menuView = new MenuView(item.getItems()); MenuView menuView = new MenuView(item.getItems());
menuView.init();
menuView.setEventLoop(getEventLoop()); menuView.setEventLoop(getEventLoop());
menuView.setThemeResolver(getThemeResolver()); menuView.setThemeResolver(getThemeResolver());
menuView.setThemeName(getThemeName()); menuView.setThemeName(getThemeName());
menuView.setViewService(getViewService());
menuView.setShowBorder(true); menuView.setShowBorder(true);
menuView.setLayer(1); menuView.setLayer(1);
Rectangle rect = getInnerRect(); Rectangle rect = getInnerRect();

View File

@@ -30,6 +30,8 @@ import org.springframework.shell.component.view.event.MouseHandler;
*/ */
public interface View extends Control { public interface View extends Control {
void init();
/** /**
* Sets a layer index this {@code View} operates on. * Sets a layer index this {@code View} operates on.
* *

View File

@@ -47,6 +47,7 @@ public class KeyBinder {
for (char i = KeyEvent.Key.a; i <= KeyEvent.Key.z; i++) { for (char i = KeyEvent.Key.a; i <= KeyEvent.Key.z; i++) {
keyMap.bind(i | KeyEvent.KeyMask.AltMask, alt(i)); keyMap.bind(i | KeyEvent.KeyMask.AltMask, alt(i));
keyMap.bind(i | KeyEvent.KeyMask.CtrlMask, ctrl(i));
} }
keyMap.bind(KeyEvent.Key.q | KeyEvent.KeyMask.CtrlMask, ctrl('q')); keyMap.bind(KeyEvent.Key.q | KeyEvent.KeyMask.CtrlMask, ctrl('q'));

View File

@@ -66,6 +66,7 @@ public class AbstractViewTests {
} }
protected void configure(View view) { protected void configure(View view) {
view.init();
if (eventLoop != null) { if (eventLoop != null) {
if (view instanceof AbstractView v) { if (view instanceof AbstractView v) {
v.setEventLoop(eventLoop); v.setEventLoop(eventLoop);

View File

@@ -38,6 +38,7 @@ class TerminalUiSnippets {
void sample() { void sample() {
TerminalUI ui = builder.build(); TerminalUI ui = builder.build();
BoxView view = new BoxView(); BoxView view = new BoxView();
ui.configure(view);
view.setDrawFunction((screen, rect) -> { view.setDrawFunction((screen, rect) -> {
screen.writerBuilder() screen.writerBuilder()
.build() .build()
@@ -59,6 +60,7 @@ class TerminalUiSnippets {
void sample() { void sample() {
TerminalUI ui = new TerminalUI(terminal); TerminalUI ui = new TerminalUI(terminal);
BoxView view = new BoxView(); BoxView view = new BoxView();
ui.configure(view);
ui.setRoot(view, true); ui.setRoot(view, true);
EventLoop eventLoop = ui.getEventLoop(); EventLoop eventLoop = ui.getEventLoop();
eventLoop.keyEvents() eventLoop.keyEvents()

View File

@@ -207,11 +207,12 @@ public class Catalog {
private ListView<String> buildCategorySelector() { private ListView<String> buildCategorySelector() {
ListView<String> categories = new ListView<>(); ListView<String> categories = new ListView<>();
categories.shortcut(Key.a | KeyMask.CtrlMask, () -> {});
ui.configure(categories); ui.configure(categories);
List<String> items = List.copyOf(categoryMap.keySet()); List<String> items = List.copyOf(categoryMap.keySet());
categories.setItems(items); categories.setItems(items);
categories.setTitle("Categories"); categories.setTitle("Categories (CTRL+A)");
categories.setFocusedTitleStyle(ScreenItem.STYLE_BOLD); categories.setFocusedTitleStyle(ScreenItem.STYLE_BOLD);
categories.setShowBorder(true); categories.setShowBorder(true);
return categories; return categories;
@@ -242,8 +243,9 @@ public class Catalog {
private ListView<ScenarioData> buildScenarioSelector() { private ListView<ScenarioData> buildScenarioSelector() {
ListView<ScenarioData> scenarios = new ListView<>(); ListView<ScenarioData> scenarios = new ListView<>();
scenarios.shortcut(Key.s | KeyMask.CtrlMask, () -> {});
ui.configure(scenarios); ui.configure(scenarios);
scenarios.setTitle("Scenarios"); scenarios.setTitle("Scenarios (CTRL+S)");
scenarios.setFocusedTitleStyle(ScreenItem.STYLE_BOLD); scenarios.setFocusedTitleStyle(ScreenItem.STYLE_BOLD);
scenarios.setShowBorder(true); scenarios.setShowBorder(true);
scenarios.setCellFactory((list, item) -> new ScenarioListCell(item)); scenarios.setCellFactory((list, item) -> new ScenarioListCell(item));