Hide statusbar in a catalog app
- Can clear items from a GridView - Full refactor for AppView now using GridView internally and can hide/show menu/status. - Add function key bindings f1-f10 - Modify catalog app to use new features in an AppView, listen F10 key to toggle statusbar visibility. - Relates #805 - Relates #807 - Relates #811
This commit is contained in:
@@ -15,9 +15,6 @@
|
||||
*/
|
||||
package org.springframework.shell.component.view.control;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.shell.component.view.event.KeyEvent;
|
||||
import org.springframework.shell.component.view.event.KeyEvent.Key;
|
||||
import org.springframework.shell.component.view.event.KeyHandler;
|
||||
@@ -25,6 +22,7 @@ import org.springframework.shell.component.view.event.MouseHandler;
|
||||
import org.springframework.shell.component.view.geom.Rectangle;
|
||||
import org.springframework.shell.component.view.message.ShellMessageBuilder;
|
||||
import org.springframework.shell.component.view.screen.Screen;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link AppView} provides an opinionated terminal UI application view
|
||||
@@ -34,40 +32,69 @@ import org.springframework.shell.component.view.screen.Screen;
|
||||
*/
|
||||
public class AppView extends BoxView {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(AppView.class);
|
||||
private GridView grid;
|
||||
private View main;
|
||||
private View modal;
|
||||
private View menu;
|
||||
private View status;
|
||||
private boolean menuVisible = true;
|
||||
private boolean statusVisible = true;
|
||||
|
||||
public AppView(View main, View menuBar, View statusBar) {
|
||||
Assert.notNull(main, "Main view must be set");
|
||||
Assert.notNull(menuBar, "Menubar view must be set");
|
||||
Assert.notNull(statusBar, "Statusbar view must be set");
|
||||
this.main = main;
|
||||
this.menu = menuBar;
|
||||
this.status = statusBar;
|
||||
initLayout();
|
||||
}
|
||||
|
||||
private void initLayout() {
|
||||
grid = new GridView();
|
||||
grid.setRowSize(1, 0, 1);
|
||||
grid.setColumnSize(0);
|
||||
grid.clearItems();
|
||||
if (menuVisible && statusVisible) {
|
||||
grid.addItem(menu, 0, 0, 1, 1, 0, 0);
|
||||
grid.addItem(main, 1, 0, 1, 1, 0, 0);
|
||||
grid.addItem(status, 2, 0, 1, 1, 0, 0);
|
||||
}
|
||||
else if (!menuVisible && !statusVisible) {
|
||||
grid.addItem(menu, 0, 0, 0, 0, 0, 0);
|
||||
grid.addItem(main, 0, 0, 3, 1, 0, 0);
|
||||
grid.addItem(status, 2, 0, 0, 0, 0, 0);
|
||||
}
|
||||
else if (menuVisible && !statusVisible) {
|
||||
grid.addItem(menu, 0, 0, 1, 1, 0, 0);
|
||||
grid.addItem(main, 1, 0, 2, 1, 0, 0);
|
||||
grid.addItem(status, 2, 0, 0, 1, 0, 0);
|
||||
}
|
||||
else if (!menuVisible && statusVisible) {
|
||||
grid.addItem(menu, 0, 0, 0, 1, 0, 0);
|
||||
grid.addItem(main, 0, 0, 2, 1, 0, 0);
|
||||
grid.addItem(status, 2, 0, 1, 1, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawInternal(Screen screen) {
|
||||
Rectangle rect = getInnerRect();
|
||||
if (main != null) {
|
||||
main.setRect(rect.x(), rect.y(), rect.width(), rect.height());
|
||||
main.draw(screen);
|
||||
}
|
||||
if (modal != null) {
|
||||
modal.setLayer(1);
|
||||
modal.setRect(rect.x() + 5, rect.y() + 5, rect.width() - 10, rect.height() - 10);
|
||||
modal.draw(screen);
|
||||
if (grid != null) {
|
||||
grid.setRect(rect.x(), rect.y(), rect.width(), rect.height());
|
||||
grid.draw(screen);
|
||||
}
|
||||
super.drawInternal(screen);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MouseHandler getMouseHandler() {
|
||||
log.trace("getMouseHandler()");
|
||||
if (main != null) {
|
||||
MouseHandler handler = main.getMouseHandler();
|
||||
if (handler != null) {
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
return super.getMouseHandler();
|
||||
MouseHandler handler = grid.getMouseHandler();
|
||||
return handler.thenIfNotConsumed(super.getMouseHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyHandler getKeyHandler() {
|
||||
KeyHandler handler1 = args -> {
|
||||
KeyHandler handler = args -> {
|
||||
KeyEvent event = args.event();
|
||||
boolean consumed = false;
|
||||
if (event.isKey(Key.CursorLeft)) {
|
||||
@@ -80,25 +107,50 @@ public class AppView extends BoxView {
|
||||
}
|
||||
return KeyHandler.resultOf(event, consumed, null);
|
||||
};
|
||||
|
||||
KeyHandler handler2 = main != null ? main.getKeyHandler() : super.getKeyHandler();
|
||||
return handler2.thenIfNotConsumed(handler1);
|
||||
KeyHandler otherHandler = main != null ? main.getKeyHandler() : super.getKeyHandler();
|
||||
return otherHandler.thenIfNotConsumed(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFocus() {
|
||||
if (main != null) {
|
||||
return main.hasFocus();
|
||||
if (grid != null) {
|
||||
return grid.hasFocus();
|
||||
}
|
||||
return super.hasFocus();
|
||||
}
|
||||
|
||||
public void setMain(View main) {
|
||||
this.main = main;
|
||||
/**
|
||||
* Sets visibility for a {@code menubar}.
|
||||
*
|
||||
* @param visible the menubar visibility
|
||||
*/
|
||||
public void setMenuBarVisible(boolean visible) {
|
||||
menuVisible = visible;
|
||||
initLayout();
|
||||
}
|
||||
|
||||
public void setModal(View modal) {
|
||||
this.modal = modal;
|
||||
/**
|
||||
* Sets visibility for a {@code statusbar}.
|
||||
*
|
||||
* @param visible the statusbar visibility
|
||||
*/
|
||||
public void setStatusBarVisible(boolean visible) {
|
||||
statusVisible = visible;
|
||||
initLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles a {@code menubar} visibility.
|
||||
*/
|
||||
public void toggleMenuBarVisibility() {
|
||||
setMenuBarVisible(!menuVisible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles a {@code statusbar} visibility.
|
||||
*/
|
||||
public void toggleStatusBarVisibility() {
|
||||
setStatusBarVisible(!statusVisible);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -173,6 +173,13 @@ public class GridView extends BoxView {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all items.
|
||||
*/
|
||||
public void clearItems() {
|
||||
this.gridItems.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines if borders is shown.
|
||||
*
|
||||
|
||||
@@ -61,6 +61,17 @@ public class KeyBinder {
|
||||
keyMap.bind(KeyEvent.Key.CursorUp, key(terminal, Capability.key_up));
|
||||
keyMap.bind(KeyEvent.Key.CursorDown, key(terminal, Capability.key_down));
|
||||
|
||||
keyMap.bind(KeyEvent.Key.f1, key(terminal, Capability.key_f1));
|
||||
keyMap.bind(KeyEvent.Key.f2, key(terminal, Capability.key_f2));
|
||||
keyMap.bind(KeyEvent.Key.f3, key(terminal, Capability.key_f3));
|
||||
keyMap.bind(KeyEvent.Key.f4, key(terminal, Capability.key_f4));
|
||||
keyMap.bind(KeyEvent.Key.f5, key(terminal, Capability.key_f5));
|
||||
keyMap.bind(KeyEvent.Key.f6, key(terminal, Capability.key_f6));
|
||||
keyMap.bind(KeyEvent.Key.f7, key(terminal, Capability.key_f7));
|
||||
keyMap.bind(KeyEvent.Key.f8, key(terminal, Capability.key_f8));
|
||||
keyMap.bind(KeyEvent.Key.f9, key(terminal, Capability.key_f9));
|
||||
keyMap.bind(KeyEvent.Key.f10, key(terminal, Capability.key_f10));
|
||||
|
||||
keyMap.bind(KeyEvent.Key.CursorLeft | KeyEvent.KeyMask.AltMask, alt(key(terminal, Capability.key_left)));
|
||||
keyMap.bind(KeyEvent.Key.CursorRight | KeyEvent.KeyMask.AltMask, alt(key(terminal, Capability.key_right)));
|
||||
keyMap.bind(KeyEvent.Key.CursorUp | KeyEvent.KeyMask.AltMask, alt(key(terminal, Capability.key_up)));
|
||||
|
||||
@@ -112,6 +112,19 @@ public record KeyEvent(int key) {
|
||||
public static final int Tab = 0x100007;
|
||||
public static final int Backtab = 0x100008;
|
||||
|
||||
public static final int f1 = 0x100009;
|
||||
public static final int f2 = 0x10000a;
|
||||
public static final int f3 = 0x10000b;
|
||||
public static final int f4 = 0x10000c;
|
||||
public static final int f5 = 0x10000d;
|
||||
public static final int f6 = 0x10000e;
|
||||
public static final int f7 = 0x10000f;
|
||||
public static final int f8 = 0x100010;
|
||||
public static final int f9 = 0x100011;
|
||||
public static final int f10 = 0x100012;
|
||||
public static final int f11 = 0x100013;
|
||||
public static final int f12 = 0x100014;
|
||||
|
||||
public static final int Char = 0x1000000;
|
||||
public static final int Mouse = 0x1000001;
|
||||
public static final int Unicode = 0x1000002;
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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.component.view.control;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
class AppViewTests extends AbstractViewTests {
|
||||
|
||||
@Nested
|
||||
class ItemPositions {
|
||||
|
||||
@Test
|
||||
void simpleSetup() {
|
||||
BoxView menu = spy(new BoxView());
|
||||
BoxView main = spy(new BoxView());
|
||||
BoxView status = spy(new BoxView());
|
||||
AppView view = new AppView(main, menu, status);
|
||||
|
||||
view.setRect(0, 0, 80, 24);
|
||||
view.draw(screen24x80);
|
||||
|
||||
verify(menu).setRect(0, 0, 80, 1);
|
||||
verify(main).setRect(0, 1, 80, 22);
|
||||
verify(status).setRect(0, 23, 80, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void simpleSetupWithInnerBorder() {
|
||||
BoxView menu = spy(new BoxView());
|
||||
BoxView main = spy(new BoxView());
|
||||
main.setShowBorder(true);
|
||||
BoxView status = spy(new BoxView());
|
||||
AppView view = new AppView(main, menu, status);
|
||||
|
||||
view.setRect(0, 0, 80, 24);
|
||||
view.draw(screen24x80);
|
||||
|
||||
verify(menu).setRect(0, 0, 80, 1);
|
||||
verify(main).setRect(0, 1, 80, 22);
|
||||
verify(status).setRect(0, 23, 80, 1);
|
||||
assertThat(forScreen(screen24x80)).hasBorder(0, 1, 80, 22);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Visibility {
|
||||
|
||||
@Test
|
||||
void menuAndStatusVisible() {
|
||||
BoxView menu = spy(new BoxView());
|
||||
BoxView main = spy(new BoxView());
|
||||
BoxView status = spy(new BoxView());
|
||||
AppView view = new AppView(main, menu, status);
|
||||
view.setMenuBarVisible(true);
|
||||
view.setStatusBarVisible(true);
|
||||
|
||||
view.setRect(0, 0, 80, 24);
|
||||
view.draw(screen24x80);
|
||||
|
||||
verify(menu).setRect(0, 0, 80, 1);
|
||||
verify(main).setRect(0, 1, 80, 22);
|
||||
verify(status).setRect(0, 23, 80, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void menuAndStatusInvisible() {
|
||||
BoxView menu = spy(new BoxView());
|
||||
BoxView main = spy(new BoxView());
|
||||
BoxView status = spy(new BoxView());
|
||||
AppView view = new AppView(main, menu, status);
|
||||
view.setMenuBarVisible(false);
|
||||
view.setStatusBarVisible(false);
|
||||
|
||||
view.setRect(0, 0, 80, 24);
|
||||
view.draw(screen24x80);
|
||||
|
||||
verify(menu, never()).setRect(anyInt(), anyInt(), anyInt(), anyInt());
|
||||
verify(main).setRect(0, 0, 80, 24);
|
||||
verify(status, never()).setRect(anyInt(), anyInt(), anyInt(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void onlyMenuVisible() {
|
||||
BoxView menu = spy(new BoxView());
|
||||
BoxView main = spy(new BoxView());
|
||||
BoxView status = spy(new BoxView());
|
||||
AppView view = new AppView(main, menu, status);
|
||||
view.setMenuBarVisible(true);
|
||||
view.setStatusBarVisible(false);
|
||||
|
||||
view.setRect(0, 0, 80, 24);
|
||||
view.draw(screen24x80);
|
||||
|
||||
verify(menu).setRect(0, 0, 80, 1);
|
||||
verify(main).setRect(0, 1, 80, 23);
|
||||
verify(status, never()).setRect(anyInt(), anyInt(), anyInt(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void onlyStatusVisible() {
|
||||
BoxView menu = spy(new BoxView());
|
||||
BoxView main = spy(new BoxView());
|
||||
BoxView status = spy(new BoxView());
|
||||
AppView view = new AppView(main, menu, status);
|
||||
view.setMenuBarVisible(false);
|
||||
view.setStatusBarVisible(true);
|
||||
|
||||
view.setRect(0, 0, 80, 24);
|
||||
view.draw(screen24x80);
|
||||
|
||||
verify(menu, never()).setRect(anyInt(), anyInt(), anyInt(), anyInt());
|
||||
verify(main).setRect(0, 0, 80, 23);
|
||||
verify(status).setRect(0, 23, 80, 1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -512,5 +512,55 @@ class GridViewTests extends AbstractViewTests {
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class NestedGrids {
|
||||
|
||||
@Test
|
||||
void simpleNestedGrid() {
|
||||
BoxView box = spy(new BoxView());
|
||||
|
||||
GridView grid2 = spy(new GridView());
|
||||
grid2.setColumnSize(0);
|
||||
grid2.setRowSize(0);
|
||||
grid2.addItem(box, 0, 0, 1, 1, 0, 0);
|
||||
|
||||
GridView grid1 = spy(new GridView());
|
||||
grid1.setColumnSize(0);
|
||||
grid1.setRowSize(0);
|
||||
grid1.addItem(grid2, 0, 0, 1, 1, 0, 0);
|
||||
|
||||
grid1.setRect(0, 0, 80, 24);
|
||||
grid1.draw(screen24x80);
|
||||
|
||||
verify(grid1).setRect(0, 0, 80, 24);
|
||||
verify(grid2).setRect(0, 0, 80, 24);
|
||||
verify(box).setRect(0, 0, 80, 24);
|
||||
}
|
||||
|
||||
@Test
|
||||
void simpleNestedGridWithBorders() {
|
||||
BoxView box = spy(new BoxView());
|
||||
box.setShowBorder(true);
|
||||
|
||||
GridView grid2 = spy(new GridView());
|
||||
grid2.setColumnSize(0);
|
||||
grid2.setRowSize(0);
|
||||
grid2.setShowBorder(true);
|
||||
grid2.addItem(box, 0, 0, 1, 1, 0, 0);
|
||||
|
||||
GridView grid1 = spy(new GridView());
|
||||
grid1.setColumnSize(0);
|
||||
grid1.setRowSize(0);
|
||||
grid1.setShowBorder(true);
|
||||
grid1.addItem(grid2, 0, 0, 1, 1, 0, 0);
|
||||
|
||||
grid1.setRect(0, 0, 80, 24);
|
||||
grid1.draw(screen24x80);
|
||||
|
||||
verify(grid1).setRect(0, 0, 80, 24);
|
||||
verify(grid2).setRect(1, 1, 78, 22);
|
||||
verify(box).setRect(2, 2, 76, 20);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
@@ -42,6 +44,7 @@ import org.springframework.shell.component.view.control.StatusBarView.StatusItem
|
||||
import org.springframework.shell.component.view.control.View;
|
||||
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.geom.Rectangle;
|
||||
import org.springframework.shell.component.view.message.ShellMessageBuilder;
|
||||
@@ -66,6 +69,7 @@ public class Catalog {
|
||||
= new ParameterizedTypeReference<ListViewOpenSelectedItemEvent<ScenarioData>>() {};
|
||||
private final static ParameterizedTypeReference<ListViewSelectedItemChangedEvent<String>> LISTVIEW_STRING_TYPEREF
|
||||
= new ParameterizedTypeReference<ListViewSelectedItemChangedEvent<String>>() {};
|
||||
private final static Logger log = LoggerFactory.getLogger(Catalog.class);
|
||||
|
||||
// mapping from category name to scenarios(can belong to multiple categories)
|
||||
private final Map<String, List<ScenarioData>> categoryMap = new TreeMap<>();
|
||||
@@ -139,18 +143,25 @@ public class Catalog {
|
||||
}
|
||||
|
||||
private AppView buildScenarioBrowser(EventLoop eventLoop, TerminalUI component) {
|
||||
// we use main app view to represent scenario browser
|
||||
AppView app = new AppView();
|
||||
app.setEventLoop(eventLoop);
|
||||
|
||||
// category selector on left, scenario selector on right
|
||||
GridView grid = new GridView();
|
||||
grid.setRowSize(1, 0, 1);
|
||||
grid.setEventLoop(eventLoop);
|
||||
grid.setRowSize(0);
|
||||
grid.setColumnSize(30, 0);
|
||||
|
||||
categories = buildCategorySelector(eventLoop);
|
||||
ListView<ScenarioData> scenarios = buildScenarioSelector(eventLoop);
|
||||
|
||||
grid.addItem(categories, 0, 0, 1, 1, 0, 0);
|
||||
grid.addItem(scenarios, 0, 1, 1, 1, 0, 0);
|
||||
|
||||
MenuBarView menuBar = buildMenuBar(eventLoop);
|
||||
StatusBarView statusBar = buildStatusBar(eventLoop);
|
||||
|
||||
// we use main app view to represent scenario browser
|
||||
AppView app = new AppView(grid, menuBar, statusBar);
|
||||
app.setEventLoop(eventLoop);
|
||||
|
||||
// handle event when scenario is chosen
|
||||
eventLoop.onDestroy(eventLoop.viewEvents(LISTVIEW_SCENARIO_TYPEREF, scenarios)
|
||||
.subscribe(event -> {
|
||||
@@ -180,14 +191,21 @@ public class Catalog {
|
||||
}
|
||||
));
|
||||
|
||||
// We place statusbar below categories and scenarios
|
||||
MenuBarView menuBar = buildMenuBar(eventLoop);
|
||||
StatusBarView statusBar = buildStatusBar(eventLoop);
|
||||
grid.addItem(menuBar, 0, 0, 1, 2, 0, 0);
|
||||
grid.addItem(categories, 1, 0, 1, 1, 0, 0);
|
||||
grid.addItem(scenarios, 1, 1, 1, 1, 0, 0);
|
||||
grid.addItem(statusBar, 2, 0, 1, 2, 0, 0);
|
||||
app.setMain(grid);
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user