Skip to content

Commit

Permalink
Make StatusBarView work with hotkeys
Browse files Browse the repository at this point in the history
- StatusItem can now define a hotkey which is then
  bound to its action.
- In catalog app replace use of raw key event to
  item's hotkey for status bar visibility.
- Various doc updates.
- Relates spring-projects#826
  • Loading branch information
jvalkeal committed Oct 30, 2023
1 parent db3f677 commit bba7a82
Show file tree
Hide file tree
Showing 9 changed files with 227 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,10 @@ else if (event.isKey(Key.CursorRight)) {

@Override
public KeyHandler getHotKeyHandler() {
return menu != null ? menu.getHotKeyHandler() : super.getHotKeyHandler();
KeyHandler mainHandler = main != null ? main.getHotKeyHandler() : super.getHotKeyHandler();
KeyHandler menuHandler = menu != null ? menu.getHotKeyHandler() : super.getHotKeyHandler();
KeyHandler statusHandler = status != null ? status.getHotKeyHandler() : super.getHotKeyHandler();
return mainHandler.thenIfNotConsumed(menuHandler).thenIfNotConsumed(statusHandler);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ private StatusItem itemAt(int x, int y) {
public void setItems(List<StatusItem> items) {
this.items.clear();
this.items.addAll(items);
registerHotKeys();
}

/**
Expand All @@ -132,21 +133,50 @@ public List<StatusItem> getItems() {
return items;
}

private void registerHotKeys() {
getItems().stream()
.filter(item -> item.getHotKey() != null)
.forEach(item -> {
Runnable action = item.getAction();
if (action != null) {
registerHotKeyBinding(item.getHotKey(), action);
}
});
}

/**
* {@link StatusItem} represents an item in a {@link StatusBarView}.
*/
public static class StatusItem {

private String title;
private Runnable action;
private Integer hotKey;

public StatusItem(String title) {
this(title, null);
}

public StatusItem(String title, Runnable action) {
this(title, action, null);
}

public StatusItem(String title, Runnable action, Integer hotKey) {
this.title = title;
this.action = action;
this.hotKey = hotKey;
}

public static StatusItem of(String title) {
return new StatusItem(title);
}

public static StatusItem of(String title, Runnable action) {
return new StatusItem(title, action);
}

public static StatusItem of(String title, Runnable action, Integer hotKey) {
return new StatusItem(title, action, hotKey);
}

public String getTitle() {
Expand All @@ -157,8 +187,18 @@ public Runnable getAction() {
return action;
}

public void setAction(Runnable action) {
public StatusItem setAction(Runnable action) {
this.action = action;
return this;
}

public Integer getHotKey() {
return hotKey;
}

public StatusItem setHotKey(Integer hotKey) {
this.hotKey = hotKey;
return this;
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

import org.springframework.shell.component.view.control.StatusBarView.StatusBarViewOpenSelectedItemEvent;
import org.springframework.shell.component.view.control.StatusBarView.StatusItem;
import org.springframework.shell.component.view.event.KeyEvent.Key;
import org.springframework.shell.component.view.event.MouseEvent;
import org.springframework.shell.component.view.event.MouseHandler.MouseHandlerResult;
import org.springframework.test.util.ReflectionTestUtils;
Expand All @@ -51,8 +52,23 @@ void constructView() {

view = new StatusBarView(Arrays.asList(new StatusItem("item1")));
assertThat(view.getItems()).hasSize(1);

view = new StatusBarView(Arrays.asList(StatusItem.of("item1")));
assertThat(view.getItems()).hasSize(1);
}

@Test
void hotkeys() {
StatusItem item;

item = StatusItem.of("title");
assertThat(item.getHotKey()).isNull();
item.setHotKey(Key.f);
assertThat(item.getHotKey()).isEqualTo(Key.f);

item = StatusItem.of("title").setHotKey(Key.f);
assertThat(item.getHotKey()).isEqualTo(Key.f);
}
}

@Nested
Expand Down
2 changes: 2 additions & 0 deletions spring-shell-docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,15 @@
** xref:tui/intro/index.adoc[]
*** xref:tui/intro/terminalui.adoc[]
** xref:tui/views/index.adoc[]
*** xref:tui/views/app.adoc[]
*** xref:tui/views/box.adoc[]
*** xref:tui/views/button.adoc[]
*** xref:tui/views/dialog.adoc[]
*** xref:tui/views/grid.adoc[]
*** xref:tui/views/list.adoc[]
*** xref:tui/views/menu.adoc[]
*** xref:tui/views/menubar.adoc[]
*** xref:tui/views/statusbar.adoc[]
** xref:tui/events/index.adoc[]
*** xref:tui/events/eventloop.adoc[]
*** xref:tui/events/key.adoc[]
Expand Down
41 changes: 41 additions & 0 deletions spring-shell-docs/modules/ROOT/pages/tui/views/app.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
= AppView
:page-section-summary-toc: 1

ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/shell/docs]

_AppView_ is a base implementation providing functionality to draw opinionated _application view_.
Inherits xref:tui/views/box.adoc[].

Generic idea is to have menu and status views which typically are xref:tui/views/menubar.adoc[] and
xref:tui/views/statusbar.adoc[] respectively. Main content view is then whatever user want to show
in it.

[source, text]
----
┌──────────────────────────┐
│ Menu │
├──────────────────────────┤
│ │
│ Main │
│ │
├──────────────────────────┤
│ Status │
└──────────────────────────┘
----

== Key Handling
If menu has a focus key handling is processed there, then main is consulted for handling.
Lastly cursor left/right are processed to dispatch _AppViewEvent_.

== HotKey Handling
Hotkeys are processed in order of _main_, _menu_ and _status_.

== Events
.AppView Events
|===
|Event |Description

|AppViewEvent
|Direction for a next selection.

|===
7 changes: 7 additions & 0 deletions spring-shell-docs/modules/ROOT/pages/tui/views/menubar.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/she
_MenuBarView_ is a base implementation providing functionality to draw a menu bar.
Inherits xref:tui/views/box.adoc[].

[source, text]
----
┌─────────────────────────────┐
│ File Help │
└─────────────────────────────┘
----

== Default Bindings
Default _key bindigs_ are:

Expand Down
48 changes: 48 additions & 0 deletions spring-shell-docs/modules/ROOT/pages/tui/views/statusbar.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
= StatusBarView
:page-section-summary-toc: 1

ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/shell/docs]

_StatusBarView_ is a base implementation providing functionality to draw a status bar.
Inherits xref:tui/views/box.adoc[].

[source, text]
----
┌─────────────────────────────┐
│ Item1 | Item2 | Item3 │
└─────────────────────────────┘
----

You can create a simple status bar with an item:

[source, java, indent=0]
----
include::{snippets}/StatusBarViewSnippets.java[tag=simple]
----

Constructor can take array form which allows to lay out simple
item definitions in a _dsl_ style.

[source, java, indent=0]
----
include::{snippets}/StatusBarViewSnippets.java[tag=viaarray]
----

Items support runnable actions which generally as executed when
item is selected. It can also get attached to a hot key.

[source, java, indent=0]
----
include::{snippets}/StatusBarViewSnippets.java[tag=items]
----


== Events
.StatusBarView Events
|===
|Event |Description

|StatusBarViewOpenSelectedItemEvent
|StatusItem is selected.

|===
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.docs;

import java.util.List;

import org.springframework.shell.component.view.control.StatusBarView;
import org.springframework.shell.component.view.control.StatusBarView.StatusItem;
import org.springframework.shell.component.view.event.KeyEvent;
import org.springframework.shell.component.view.event.KeyEvent.Key;

class StatusBarViewSnippets {

@SuppressWarnings("unused")
void simple() {
// tag::simple[]
StatusItem item1 = new StatusBarView.StatusItem("Item1");
StatusBarView statusBar = new StatusBarView(List.of(item1));
// end::simple[]
}

void items() {
// tag::items[]
StatusItem item1 = StatusBarView.StatusItem.of("Item1");

Runnable action1 = () -> {};
StatusItem item2 = StatusBarView.StatusItem.of("Item2", action1);

Runnable action2 = () -> {};
StatusItem item3 = StatusBarView.StatusItem.of("Item3", action2, KeyEvent.Key.f10);

StatusBarView statusBar = new StatusBarView();
statusBar.setItems(List.of(item1, item2, item3));
// end::items[]
}

void viaArray() {
// tag::viaarray[]
new StatusBarView(new StatusItem[] {
StatusItem.of("Item1"),
StatusItem.of("Item2")
.setAction(() -> {}),
StatusItem.of("Item3")
.setAction(() -> {})
.setHotKey(Key.f10)
});
// end::viaarray[]
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
package org.springframework.shell.samples.catalog;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
Expand Down Expand Up @@ -203,21 +202,6 @@ private AppView buildScenarioBrowser(EventLoop eventLoop, TerminalUI component)
}
));

// we could potentially do keybinding somewhere else
// but at least this shows how to do it in low level.
// essentially we now just handle F10 to toggle
// menubar visibility
// TODO: when we get support for hotkeys we should do
// binding there
eventLoop.onDestroy(eventLoop.keyEvents()
.subscribe(event -> {
log.debug("Raw keyevent {}", event);
if (event.isKey(KeyEvent.Key.f10)) {
app.toggleStatusBarVisibility();
}
}
));

return app;
}

Expand Down Expand Up @@ -322,11 +306,12 @@ private MenuBarView buildMenuBar(EventLoop eventLoop) {

private StatusBarView buildStatusBar(EventLoop eventLoop) {
Runnable quitAction = () -> requestQuit();
StatusBarView statusBar = new StatusBarView();
Runnable visibilyAction = () -> app.toggleStatusBarVisibility();
StatusBarView statusBar = new StatusBarView(new StatusItem[] {
StatusItem.of("CTRL-Q Quit", quitAction),
StatusItem.of("F10 Status Bar", visibilyAction, KeyEvent.Key.f10)
});
ui.configure(statusBar);
StatusItem item1 = new StatusBarView.StatusItem("CTRL-Q Quit", quitAction);
StatusItem item2 = new StatusBarView.StatusItem("F10 Status Bar");
statusBar.setItems(Arrays.asList(item1, item2));
return statusBar;
}

Expand Down

0 comments on commit bba7a82

Please sign in to comment.