Overhaul ListView to support checked states
- ListView can be defined to use nocheck, checked or radio - List can now scroll through up/down - Actual visual is handled in a DefaultListCell - Modify/add scenarios and catalog app - Relates #865
This commit is contained in:
@@ -16,9 +16,15 @@
|
||||
package org.springframework.shell.component.view.control;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.shell.component.view.control.cell.ListCell;
|
||||
import org.springframework.shell.component.view.event.KeyEvent.Key;
|
||||
import org.springframework.shell.component.view.event.MouseEvent;
|
||||
@@ -27,69 +33,63 @@ import org.springframework.shell.component.view.message.ShellMessageBuilder;
|
||||
import org.springframework.shell.component.view.screen.Screen;
|
||||
import org.springframework.shell.component.view.screen.ScreenItem;
|
||||
import org.springframework.shell.style.StyleSettings;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ListView} shows {@code list items} vertically.
|
||||
* {@code ListView} is a {@link View} showing items in a vertical list.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*/
|
||||
public class ListView<T> extends BoxView {
|
||||
|
||||
private final List<T> items = new ArrayList<>();
|
||||
private int selected = -1;
|
||||
|
||||
private final List<ListCell<T>> cells = new ArrayList<>();
|
||||
private Function<ListView<T>, ListCell<T>> factory = listView -> new ListCell<>();
|
||||
private final ItemStyle itemStyle;
|
||||
private int start = 0;
|
||||
private int pos = 0;
|
||||
private final Set<Integer> selected = new HashSet<>();
|
||||
private BiFunction<ListView<T>, T, ListCell<T>> factory = (listView, item) -> ListCell.of(item,
|
||||
listView.getItemStyle());
|
||||
|
||||
/**
|
||||
* Construct list view with no initial items.
|
||||
* Specifies how a item shows selection state.
|
||||
*/
|
||||
public enum ItemStyle {
|
||||
|
||||
/**
|
||||
* The item will be shown normally, with no check indicator. The default.
|
||||
*/
|
||||
NOCHECK,
|
||||
|
||||
/**
|
||||
* The item will indicate checked/un-checked state.
|
||||
*/
|
||||
CHECKED,
|
||||
|
||||
/**
|
||||
* The item is part of a radio group and will indicate selected state.
|
||||
*/
|
||||
RADIO
|
||||
}
|
||||
|
||||
public ListView() {
|
||||
this.itemStyle = ItemStyle.NOCHECK;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawInternal(Screen screen) {
|
||||
Rectangle rect = getInnerRect();
|
||||
int y = rect.y();
|
||||
|
||||
int selectedStyle = resolveThemeStyle(StyleSettings.TAG_HIGHLIGHT, ScreenItem.STYLE_BOLD);
|
||||
int i = 0;
|
||||
for (ListCell<T> c : cells) {
|
||||
c.setRect(rect.x(), y++, rect.width(), 1);
|
||||
if (i == selected) {
|
||||
c.updateSelected(true);
|
||||
c.setStyle(selectedStyle);
|
||||
}
|
||||
else {
|
||||
c.updateSelected(false);
|
||||
c.setBackgroundColor(-1);
|
||||
c.setStyle(-1);
|
||||
}
|
||||
c.updateSelected(i == selected);
|
||||
c.draw(screen);
|
||||
i++;
|
||||
}
|
||||
super.drawInternal(screen);
|
||||
public ListView(ItemStyle itemStyle) {
|
||||
Assert.notNull(itemStyle, "item style must be set");
|
||||
this.itemStyle = itemStyle;
|
||||
setItems(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a cell factory.
|
||||
*
|
||||
* @param factory the cell factory
|
||||
*/
|
||||
public void setCellFactory(Function<ListView<T>, ListCell<T>> factory) {
|
||||
this.factory = factory;
|
||||
public ListView(T[] items, ItemStyle itemStyle) {
|
||||
this(items != null ? Arrays.asList(items) : Collections.emptyList(), itemStyle);
|
||||
}
|
||||
|
||||
public void setItems(List<T> items) {
|
||||
this.items.clear();
|
||||
this.items.addAll(items);
|
||||
this.cells.clear();
|
||||
for (T i : items) {
|
||||
ListCell<T> c = factory.apply(this);
|
||||
cells.add(c);
|
||||
c.updateItem(i);
|
||||
}
|
||||
public ListView(@Nullable List<T> items, ItemStyle itemStyle) {
|
||||
Assert.notNull(itemStyle, "item style must be set");
|
||||
this.itemStyle = itemStyle;
|
||||
setItems(items);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -97,60 +97,187 @@ public class ListView<T> extends BoxView {
|
||||
registerKeyBinding(Key.CursorUp, () -> up());
|
||||
registerKeyBinding(Key.CursorDown, () -> down());
|
||||
registerKeyBinding(Key.Enter, () -> enter());
|
||||
registerKeyBinding(Key.Space, () -> space());
|
||||
|
||||
registerMouseBinding(MouseEvent.Type.Wheel | MouseEvent.Button.WheelUp, () -> up());
|
||||
registerMouseBinding(MouseEvent.Type.Wheel | MouseEvent.Button.WheelDown, () -> down());
|
||||
registerMouseBinding(MouseEvent.Type.Released | MouseEvent.Button.Button1, event -> click(event));
|
||||
}
|
||||
|
||||
private void up() {
|
||||
updateIndex(-1);
|
||||
dispatch(ShellMessageBuilder.ofView(this, ListViewSelectedItemChangedEvent.of(this, selectedItem())));
|
||||
public ItemStyle getItemStyle() {
|
||||
return itemStyle;
|
||||
}
|
||||
|
||||
private void down() {
|
||||
updateIndex(1);
|
||||
dispatch(ShellMessageBuilder.ofView(this, ListViewSelectedItemChangedEvent.of(this, selectedItem())));
|
||||
}
|
||||
|
||||
private void enter() {
|
||||
dispatch(ShellMessageBuilder.ofView(this, ListViewOpenSelectedItemEvent.of(this, selectedItem())));
|
||||
}
|
||||
|
||||
private void click(MouseEvent event) {
|
||||
int index = event.y() - getInnerRect().y();
|
||||
if (index > -1 && index < items.size()) {
|
||||
setSelected(index);
|
||||
private void updateCells() {
|
||||
cells.clear();
|
||||
for (T i : items) {
|
||||
ListCell<T> c = factory.apply(this, i);
|
||||
c.setItemStyle(getItemStyle());
|
||||
cells.add(c);
|
||||
}
|
||||
}
|
||||
|
||||
public void setSelected(int selected) {
|
||||
if (this.selected != selected) {
|
||||
this.selected = selected;
|
||||
public void setItems(@Nullable List<T> items) {
|
||||
this.items.clear();
|
||||
if (items != null) {
|
||||
this.items.addAll(items);
|
||||
}
|
||||
if (this.items.isEmpty()) {
|
||||
start = -1;
|
||||
pos = -1;
|
||||
}
|
||||
else {
|
||||
start = 0;
|
||||
pos = 0;
|
||||
}
|
||||
updateCells();
|
||||
}
|
||||
|
||||
private void updateSelectionStates() {
|
||||
int active = start + pos;
|
||||
if (itemStyle == ItemStyle.CHECKED) {
|
||||
boolean removed = selected.remove(active);
|
||||
if (!removed) {
|
||||
selected.add(active);
|
||||
}
|
||||
}
|
||||
else if (itemStyle == ItemStyle.RADIO) {
|
||||
selected.clear();
|
||||
selected.add(active);
|
||||
}
|
||||
ListIterator<ListCell<T>> iter = cells.listIterator();
|
||||
while (iter.hasNext()) {
|
||||
int index = iter.nextIndex();
|
||||
ListCell<T> c = iter.next();
|
||||
c.setSelected(selected.contains(index));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawInternal(Screen screen) {
|
||||
if (start > -1 && pos > -1) {
|
||||
Rectangle rect = getInnerRect();
|
||||
int y = rect.y();
|
||||
int selectedStyle = resolveThemeStyle(StyleSettings.TAG_HIGHLIGHT, ScreenItem.STYLE_BOLD);
|
||||
int i = 0;
|
||||
|
||||
for (ListCell<T> c : cells) {
|
||||
if (i < start) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
c.setRect(rect.x(), y++, rect.width(), 1);
|
||||
if (i == start + pos) {
|
||||
c.setStyle(selectedStyle);
|
||||
}
|
||||
else {
|
||||
c.setBackgroundColor(-1);
|
||||
c.setStyle(-1);
|
||||
}
|
||||
c.draw(screen);
|
||||
i++;
|
||||
if (i - start >= rect.height()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.drawInternal(screen);
|
||||
}
|
||||
|
||||
public void setCellFactory(BiFunction<ListView<T>, T, ListCell<T>> factory) {
|
||||
Assert.notNull(factory, "cell factory must be set");
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
private void scrollIndex(boolean up) {
|
||||
int size = items.size();
|
||||
int maxItems = getInnerRect().height();
|
||||
int active = start + pos;
|
||||
if (up) {
|
||||
if (start > 0 && pos == 0) {
|
||||
start--;
|
||||
}
|
||||
else if (start + pos <= 0) {
|
||||
start = size - Math.min(maxItems, size);
|
||||
pos = Math.min(maxItems, size) - 1;
|
||||
}
|
||||
else {
|
||||
pos--;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (start + pos + 1 < Math.min(maxItems, size)) {
|
||||
pos++;
|
||||
}
|
||||
else if (start + pos + 1 >= size) {
|
||||
start = 0;
|
||||
pos = 0;
|
||||
}
|
||||
else {
|
||||
start++;
|
||||
}
|
||||
}
|
||||
if (active != start + pos) {
|
||||
dispatch(ShellMessageBuilder.ofView(this, ListViewSelectedItemChangedEvent.of(this, selectedItem())));
|
||||
}
|
||||
}
|
||||
|
||||
private void scrollIndex(int step) {
|
||||
if (start < 0 && pos < 0) {
|
||||
return;
|
||||
}
|
||||
if (step < 0) {
|
||||
for (int i = step; i < 0; i++) {
|
||||
scrollIndex(true);
|
||||
}
|
||||
}
|
||||
else if (step > 0) {
|
||||
for (int i = step; i > 0; i--) {
|
||||
scrollIndex(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private T selectedItem() {
|
||||
T selectedItem = null;
|
||||
if (selected >= 0 && selected < items.size()) {
|
||||
selectedItem = items.get(selected);
|
||||
int active = start + pos;
|
||||
if (active >= 0 && active < items.size()) {
|
||||
selectedItem = items.get(active);
|
||||
}
|
||||
return selectedItem;
|
||||
}
|
||||
|
||||
private void updateIndex(int step) {
|
||||
int size = items.size();
|
||||
if (step > 0) {
|
||||
if (selected + step < size) {
|
||||
selected += step;
|
||||
}
|
||||
}
|
||||
else if (step < 0) {
|
||||
if (selected - step > 0) {
|
||||
selected += step;
|
||||
private void up() {
|
||||
scrollIndex(-1);
|
||||
}
|
||||
|
||||
private void down() {
|
||||
scrollIndex(1);
|
||||
}
|
||||
|
||||
private void enter() {
|
||||
if (itemStyle == ItemStyle.NOCHECK) {
|
||||
dispatch(ShellMessageBuilder.ofView(this, ListViewOpenSelectedItemEvent.of(this, selectedItem())));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void space() {
|
||||
updateSelectionStates();
|
||||
}
|
||||
|
||||
private void click(MouseEvent event) {
|
||||
int index = event.y() - getInnerRect().y();
|
||||
int active = start + index;
|
||||
if (active >= 0 && active < items.size()) {
|
||||
pos = index;
|
||||
if (itemStyle == ItemStyle.NOCHECK) {
|
||||
dispatch(ShellMessageBuilder.ofView(this, ListViewSelectedItemChangedEvent.of(this, selectedItem())));
|
||||
return;
|
||||
}
|
||||
}
|
||||
updateSelectionStates();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,16 +16,23 @@
|
||||
package org.springframework.shell.component.view.control.cell;
|
||||
|
||||
import org.springframework.shell.component.view.control.AbstractControl;
|
||||
import org.springframework.shell.component.view.control.Cell;
|
||||
|
||||
/**
|
||||
* Base implementation of a {@link Cell}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*/
|
||||
public abstract class AbstractCell<T> extends AbstractControl implements Cell<T> {
|
||||
|
||||
private boolean selected;
|
||||
private T item;
|
||||
private int style = -1;
|
||||
private int foregroundColor = -1;
|
||||
private int backgroundColor = -1;
|
||||
|
||||
public AbstractCell(T item) {
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getItem() {
|
||||
return item;
|
||||
@@ -36,16 +43,6 @@ public abstract class AbstractCell<T> extends AbstractControl implements Cell<T>
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelected() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSelected(boolean selected) {
|
||||
this.selected = selected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStyle(int style) {
|
||||
this.style = style;
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.cell;
|
||||
|
||||
import org.springframework.shell.component.view.control.ListView.ItemStyle;
|
||||
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.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Base implementation of a {@link ListCell}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*/
|
||||
public abstract class AbstractListCell<T> extends AbstractCell<T> implements ListCell<T> {
|
||||
|
||||
private ItemStyle itemStyle;
|
||||
private boolean selected;
|
||||
|
||||
public AbstractListCell(T item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
public AbstractListCell(T item, ItemStyle itemStyle) {
|
||||
super(item);
|
||||
this.itemStyle = itemStyle;
|
||||
}
|
||||
|
||||
protected String getText() {
|
||||
return getItem().toString();
|
||||
}
|
||||
|
||||
protected String getIndicator() {
|
||||
if (getItemStyle() == ItemStyle.CHECKED || getItemStyle() == ItemStyle.RADIO) {
|
||||
return selected ? "[x]" : "[ ]";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Screen screen) {
|
||||
String indicator = getIndicator();
|
||||
String text = null;
|
||||
if (StringUtils.hasText(indicator)) {
|
||||
text = String.format("%s %s", indicator, getText());
|
||||
}
|
||||
else {
|
||||
text = getText();
|
||||
}
|
||||
Rectangle rect = getRect();
|
||||
Writer writer = screen.writerBuilder().style(getStyle()).color(getForegroundColor()).build();
|
||||
writer.text(text, rect.x(), rect.y());
|
||||
writer.background(rect, getBackgroundColor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemStyle(ItemStyle itemStyle) {
|
||||
this.itemStyle = itemStyle;
|
||||
}
|
||||
|
||||
public ItemStyle getItemStyle() {
|
||||
return itemStyle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelected(boolean selected) {
|
||||
this.selected = selected;
|
||||
}
|
||||
|
||||
public boolean isSelected() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.cell;
|
||||
|
||||
import org.springframework.shell.component.view.control.Control;
|
||||
import org.springframework.shell.component.view.screen.Screen;
|
||||
|
||||
/**
|
||||
* Base interface for all cells. Typically a {@link Cell} is a building block in
|
||||
* a {@link View} not needing to be aware of how it is drawn into a {@link Screen}
|
||||
* but needs to aware of its "item", bounds via {@link Control} and other
|
||||
* properties like {@code background}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*/
|
||||
public interface Cell<T> extends Control {
|
||||
|
||||
/**
|
||||
* Get item bound to a cell.
|
||||
*
|
||||
* @return item bound to a cell
|
||||
*/
|
||||
T getItem();
|
||||
|
||||
/**
|
||||
* Sets an item to bound into a cell.
|
||||
*
|
||||
* @param item item to bound into a cell
|
||||
*/
|
||||
void setItem(T item);
|
||||
|
||||
/**
|
||||
* Sets a style.
|
||||
*
|
||||
* @param style the style
|
||||
*/
|
||||
void setStyle(int style);
|
||||
|
||||
/**
|
||||
* Sets a foreground color.
|
||||
*
|
||||
* @param foregroundColor the background color
|
||||
*/
|
||||
void setForegroundColor(int foregroundColor);
|
||||
|
||||
/**
|
||||
* Sets a background color.
|
||||
*
|
||||
* @param backgroundColor the background color
|
||||
*/
|
||||
void setBackgroundColor(int backgroundColor);
|
||||
|
||||
}
|
||||
@@ -15,32 +15,45 @@
|
||||
*/
|
||||
package org.springframework.shell.component.view.control.cell;
|
||||
|
||||
import org.springframework.shell.component.view.control.Cell;
|
||||
import org.springframework.shell.component.view.control.ListView;
|
||||
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.component.view.control.ListView.ItemStyle;
|
||||
|
||||
/**
|
||||
* The {@link Cell} type used within {@link ListView} instances
|
||||
* Extension of a {@link Cell} to make it aware of an item style and selection state.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*/
|
||||
public class ListCell<T> extends AbstractCell<T> {
|
||||
public interface ListCell<T> extends Cell<T> {
|
||||
|
||||
protected String text;
|
||||
/**
|
||||
* Set {@link ItemStyle}.
|
||||
*
|
||||
* @param itemStyle the item style
|
||||
*/
|
||||
void setItemStyle(ItemStyle itemStyle);
|
||||
|
||||
@Override
|
||||
public void draw(Screen screen) {
|
||||
Rectangle rect = getRect();
|
||||
Writer writer = screen.writerBuilder().style(getStyle()).color(getForegroundColor()).build();
|
||||
writer.text(text, rect.x(), rect.y());
|
||||
writer.background(rect, getBackgroundColor());
|
||||
/**
|
||||
* Set selection state.
|
||||
*
|
||||
* @param selected the selection state
|
||||
*/
|
||||
void setSelected(boolean selected);
|
||||
|
||||
/**
|
||||
* Helper method to build a {@code ListCell}.
|
||||
*
|
||||
* @param item the item
|
||||
* @param itemStyle the item style
|
||||
* @return a default list cell
|
||||
*/
|
||||
static <T> ListCell<T> of(T item, ItemStyle itemStyle) {
|
||||
return new DefaultListCell<T>(item, itemStyle);
|
||||
}
|
||||
|
||||
public void updateItem(T item) {
|
||||
setItem(item);
|
||||
this.text = item.toString();
|
||||
}
|
||||
static class DefaultListCell<T> extends AbstractListCell<T> {
|
||||
|
||||
DefaultListCell(T item, ItemStyle itemStyle) {
|
||||
super(item, itemStyle);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.shell.component.view.control;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.assertj.core.api.AssertProvider;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -56,6 +58,13 @@ public class AbstractViewTests {
|
||||
eventLoop = null;
|
||||
}
|
||||
|
||||
protected void clearScreens() {
|
||||
screen24x80.resize(24, 80);
|
||||
screen7x10.resize(7, 10);
|
||||
screen0x0.resize(0, 0);
|
||||
screen10x10.resize(10, 10);
|
||||
}
|
||||
|
||||
protected void configure(View view) {
|
||||
if (eventLoop != null) {
|
||||
if (view instanceof AbstractView v) {
|
||||
@@ -136,6 +145,11 @@ public class AbstractViewTests {
|
||||
return getField(object, field, int.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected static Set<Integer> getIntSetField(Object object, String field) {
|
||||
return getField(object, field, Set.class);
|
||||
}
|
||||
|
||||
protected static Runnable getRunnableField(Object object, String field) {
|
||||
return getField(object, field, Runnable.class);
|
||||
}
|
||||
@@ -154,4 +168,12 @@ public class AbstractViewTests {
|
||||
return (T) ReflectionTestUtils.invokeMethod(object, method, args);
|
||||
}
|
||||
|
||||
protected static void callVoidIntMethod(Object target, String method, int value) {
|
||||
ReflectionTestUtils.invokeSetterMethod(target, method, value, int.class);
|
||||
}
|
||||
|
||||
protected static void callVoidMethod(Object target, String method) {
|
||||
ReflectionTestUtils.invokeSetterMethod(target, method, null, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,12 +15,18 @@
|
||||
*/
|
||||
package org.springframework.shell.component.view.control;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.shell.component.view.control.cell.ListCell;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.shell.component.view.control.ListView.ItemStyle;
|
||||
import org.springframework.shell.component.view.control.ListView.ListViewSelectedItemChangedEvent;
|
||||
import org.springframework.shell.component.view.control.cell.AbstractListCell;
|
||||
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;
|
||||
@@ -31,104 +37,431 @@ import org.springframework.shell.component.view.event.MouseHandler.MouseHandlerR
|
||||
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.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class ListViewTests extends AbstractViewTests {
|
||||
|
||||
@Test
|
||||
void hasBorder() {
|
||||
ListView<?> view = new ListView<>();
|
||||
view.setShowBorder(true);
|
||||
view.setRect(0, 0, 80, 24);
|
||||
view.draw(screen24x80);
|
||||
assertThat(forScreen(screen24x80)).hasBorder(0, 0, 80, 24);
|
||||
private final static ParameterizedTypeReference<ListViewSelectedItemChangedEvent<String>> LISTVIEW_STRING_TYPEREF
|
||||
= new ParameterizedTypeReference<ListViewSelectedItemChangedEvent<String>>() {};
|
||||
|
||||
private static final String SELECTED_FIELD = "selected";
|
||||
private static final String START_FIELD = "start";
|
||||
private static final String POSITION_FIELD = "pos";
|
||||
private static final String SCROLL_METHOD = "scrollIndex";
|
||||
ListView<String> view;
|
||||
|
||||
@Nested
|
||||
class Events {
|
||||
|
||||
@Test
|
||||
void arrowKeysMoveActive() {
|
||||
view = new ListView<>();
|
||||
configure(view);
|
||||
view.setRect(0, 0, 80, 24);
|
||||
view.setItems(Arrays.asList("item1", "item2"));
|
||||
|
||||
Flux<ListViewSelectedItemChangedEvent<String>> actions = eventLoop
|
||||
.viewEvents(LISTVIEW_STRING_TYPEREF);
|
||||
StepVerifier verifier = StepVerifier.create(actions)
|
||||
.expectNextCount(1)
|
||||
.thenCancel()
|
||||
.verifyLater();
|
||||
|
||||
KeyEvent eventDown = KeyEvent.of(Key.CursorDown);
|
||||
KeyHandlerResult result = view.getKeyHandler().handle(KeyHandler.argsOf(eventDown));
|
||||
assertThat(result).isNotNull().satisfies(r -> {
|
||||
assertThat(r.event()).isEqualTo(eventDown);
|
||||
assertThat(r.consumed()).isTrue();
|
||||
});
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(0);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(1);
|
||||
verifier.verify(Duration.ofSeconds(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void mouseWheelMoveActive() {
|
||||
view = new ListView<>();
|
||||
configure(view);
|
||||
view.setRect(0, 0, 80, 24);
|
||||
view.setItems(Arrays.asList("item1", "item2"));
|
||||
|
||||
Flux<ListViewSelectedItemChangedEvent<String>> actions = eventLoop
|
||||
.viewEvents(LISTVIEW_STRING_TYPEREF);
|
||||
StepVerifier verifier = StepVerifier.create(actions)
|
||||
.expectNextCount(1)
|
||||
.thenCancel()
|
||||
.verifyLater();
|
||||
|
||||
MouseEvent eventDown = mouseWheelDown(0, 0);
|
||||
MouseHandlerResult result = view.getMouseHandler().handle(MouseHandler.argsOf(eventDown));
|
||||
assertThat(result).isNotNull().satisfies(r -> {
|
||||
assertThat(r.event()).isEqualTo(eventDown);
|
||||
assertThat(r.capture()).isEqualTo(view);
|
||||
});
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(0);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(1);
|
||||
verifier.verify(Duration.ofSeconds(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void mouseClickMoveActive() {
|
||||
view = new ListView<>();
|
||||
configure(view);
|
||||
view.setRect(0, 0, 80, 24);
|
||||
view.setItems(Arrays.asList("item1", "item2"));
|
||||
|
||||
Flux<ListViewSelectedItemChangedEvent<String>> actions = eventLoop
|
||||
.viewEvents(LISTVIEW_STRING_TYPEREF);
|
||||
StepVerifier verifier = StepVerifier.create(actions)
|
||||
.expectNextCount(1)
|
||||
.thenCancel()
|
||||
.verifyLater();
|
||||
|
||||
MouseEvent event01 = mouseClick(0, 1);
|
||||
view.getMouseHandler().handle(MouseHandler.argsOf(event01));
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(0);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(1);
|
||||
verifier.verify(Duration.ofSeconds(1));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void arrowKeysMoveSelection() {
|
||||
ListView<String> view = new ListView<>();
|
||||
configure(view);
|
||||
view.setRect(0, 0, 80, 24);
|
||||
view.setItems(Arrays.asList("item1", "item2"));
|
||||
assertThat(ReflectionTestUtils.getField(view, "selected")).isEqualTo(-1);
|
||||
@Nested
|
||||
class Navigation {
|
||||
|
||||
KeyEvent eventDown = KeyEvent.of(Key.CursorDown);
|
||||
KeyEvent eventUp = KeyEvent.of(Key.CursorUp);
|
||||
KeyHandlerResult result = view.getKeyHandler().handle(KeyHandler.argsOf(eventDown));
|
||||
assertThat(result).isNotNull().satisfies(r -> {
|
||||
assertThat(r.event()).isEqualTo(eventDown);
|
||||
assertThat(r.consumed()).isTrue();
|
||||
// assertThat(r.focus()).isEqualTo(view);
|
||||
// assertThat(r.capture()).isEqualTo(view);
|
||||
});
|
||||
assertThat(selected(view)).isEqualTo(0);
|
||||
result = view.getKeyHandler().handle(KeyHandler.argsOf(eventDown));
|
||||
assertThat(selected(view)).isEqualTo(1);
|
||||
result = view.getKeyHandler().handle(KeyHandler.argsOf(eventUp));
|
||||
assertThat(selected(view)).isEqualTo(0);
|
||||
}
|
||||
@Test
|
||||
void initialActiveIndexZeroWhenItemsSet() {
|
||||
view = new ListView<>();
|
||||
configure(view);
|
||||
view.setRect(0, 0, 80, 24);
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(0);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(0);
|
||||
view.setItems(Arrays.asList("item1", "item2"));
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(0);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void mouseWheelMoveSelection() {
|
||||
ListView<String> view = new ListView<>();
|
||||
configure(view);
|
||||
view.setRect(0, 0, 80, 24);
|
||||
view.setItems(Arrays.asList("item1", "item2"));
|
||||
assertThat(selected(view)).isEqualTo(-1);
|
||||
@Test
|
||||
void arrowMovesActiveFromFirstToLast() {
|
||||
view = new ListView<>();
|
||||
configure(view);
|
||||
view.setRect(0, 0, 80, 24);
|
||||
view.setItems(Arrays.asList("item1", "item2"));
|
||||
|
||||
MouseEvent eventDown = mouseWheelDown(0, 0);
|
||||
MouseEvent eventUp = mouseWheelUp(0, 0);
|
||||
MouseHandlerResult result = view.getMouseHandler().handle(MouseHandler.argsOf(eventDown));
|
||||
assertThat(result).isNotNull().satisfies(r -> {
|
||||
assertThat(r.event()).isEqualTo(eventDown);
|
||||
// assertThat(r.consumed()).isFalse();
|
||||
// assertThat(r.focus()).isNull();
|
||||
assertThat(r.capture()).isEqualTo(view);
|
||||
});
|
||||
assertThat(selected(view)).isEqualTo(0);
|
||||
result = view.getMouseHandler().handle(MouseHandler.argsOf(eventDown));
|
||||
assertThat(selected(view)).isEqualTo(1);
|
||||
result = view.getMouseHandler().handle(MouseHandler.argsOf(eventUp));
|
||||
assertThat(selected(view)).isEqualTo(0);
|
||||
}
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(0);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(0);
|
||||
view.getKeyHandler().handle(KeyHandler.argsOf(KeyEvent.of(Key.CursorUp)));
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(0);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void mouseClickMoveSelection() {
|
||||
ListView<String> view = new ListView<>();
|
||||
configure(view);
|
||||
view.setRect(0, 0, 80, 24);
|
||||
view.setItems(Arrays.asList("item1", "item2"));
|
||||
assertThat(selected(view)).isEqualTo(-1);
|
||||
@Test
|
||||
void arrowMovesActiveFromLastToFirst() {
|
||||
view = new ListView<>();
|
||||
configure(view);
|
||||
view.setRect(0, 0, 80, 24);
|
||||
view.setItems(Arrays.asList("item1", "item2"));
|
||||
|
||||
MouseEvent event00 = mouseClick(0, 0);
|
||||
MouseEvent event01 = mouseClick(0, 1);
|
||||
view.getMouseHandler().handle(MouseHandler.argsOf(event00));
|
||||
assertThat(selected(view)).isEqualTo(0);
|
||||
view.getMouseHandler().handle(MouseHandler.argsOf(event01));
|
||||
assertThat(selected(view)).isEqualTo(1);
|
||||
}
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(0);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(0);
|
||||
view.getKeyHandler().handle(KeyHandler.argsOf(KeyEvent.of(Key.CursorDown)));
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(0);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(1);
|
||||
|
||||
view.getKeyHandler().handle(KeyHandler.argsOf(KeyEvent.of(Key.CursorDown)));
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(0);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(0);
|
||||
}
|
||||
|
||||
static int selected(ListView<?> view) {
|
||||
return (int) ReflectionTestUtils.getField(view, "selected");
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Visual {
|
||||
|
||||
@Test
|
||||
void customCellFactory() {
|
||||
ListView<String> view = new ListView<>();
|
||||
void hasBorder() {
|
||||
view = new ListView<>();
|
||||
view.setShowBorder(true);
|
||||
view.setCellFactory(list -> new TestListCell());
|
||||
view.setRect(0, 0, 80, 24);
|
||||
view.draw(screen24x80);
|
||||
assertThat(forScreen(screen24x80)).hasBorder(0, 0, 80, 24);
|
||||
}
|
||||
|
||||
@Test
|
||||
void showingAllCells() {
|
||||
view = new ListView<>();
|
||||
view.setShowBorder(true);
|
||||
view.setRect(0, 0, 10, 7);
|
||||
view.setItems(Arrays.asList("item1", "item2", "item3"));
|
||||
view.draw(screen7x10);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item1", 1, 1, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item2", 1, 2, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item3", 1, 3, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void showingCellsWhichFitVertically() {
|
||||
view = new ListView<>();
|
||||
view.setShowBorder(true);
|
||||
view.setRect(0, 0, 10, 7);
|
||||
view.setItems(Arrays.asList("item1", "item2", "item3", "item4", "item5", "item6", "item7"));
|
||||
view.draw(screen7x10);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item1", 1, 1, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item2", 1, 2, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item3", 1, 3, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item4", 1, 4, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item5", 1, 5, 5);
|
||||
assertThat(forScreen(screen7x10)).hasNoHorizontalText("item6", 1, 6, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void scrollUpThrough() {
|
||||
view = new ListView<>();
|
||||
view.setShowBorder(true);
|
||||
view.setRect(0, 0, 10, 7);
|
||||
view.setItems(Arrays.asList("item1", "item2", "item3", "item4", "item5", "item6", "item7"));
|
||||
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(0);
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(0);
|
||||
view.draw(screen7x10);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item1", 1, 1, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item2", 1, 2, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item3", 1, 3, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item4", 1, 4, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item5", 1, 5, 5);
|
||||
clearScreens();
|
||||
|
||||
callVoidIntMethod(view, SCROLL_METHOD, -1);
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(2);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(4);
|
||||
view.draw(screen7x10);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item3", 1, 1, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item4", 1, 2, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item5", 1, 3, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item6", 1, 4, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item7", 1, 5, 5);
|
||||
clearScreens();
|
||||
|
||||
callVoidIntMethod(view, SCROLL_METHOD, -1);
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(2);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(3);
|
||||
view.draw(screen7x10);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item3", 1, 1, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item4", 1, 2, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item5", 1, 3, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item6", 1, 4, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item7", 1, 5, 5);
|
||||
clearScreens();
|
||||
|
||||
callVoidIntMethod(view, SCROLL_METHOD, -1);
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(2);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(2);
|
||||
view.draw(screen7x10);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item3", 1, 1, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item4", 1, 2, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item5", 1, 3, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item6", 1, 4, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item7", 1, 5, 5);
|
||||
clearScreens();
|
||||
|
||||
callVoidIntMethod(view, SCROLL_METHOD, -1);
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(2);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(1);
|
||||
view.draw(screen7x10);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item3", 1, 1, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item4", 1, 2, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item5", 1, 3, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item6", 1, 4, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item7", 1, 5, 5);
|
||||
clearScreens();
|
||||
|
||||
callVoidIntMethod(view, SCROLL_METHOD, -1);
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(2);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(0);
|
||||
view.draw(screen7x10);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item3", 1, 1, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item4", 1, 2, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item5", 1, 3, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item6", 1, 4, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item7", 1, 5, 5);
|
||||
clearScreens();
|
||||
|
||||
callVoidIntMethod(view, SCROLL_METHOD, -1);
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(1);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(0);
|
||||
view.draw(screen7x10);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item2", 1, 1, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item3", 1, 2, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item4", 1, 3, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item5", 1, 4, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item6", 1, 5, 5);
|
||||
clearScreens();
|
||||
|
||||
callVoidIntMethod(view, SCROLL_METHOD, -1);
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(0);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(0);
|
||||
view.draw(screen7x10);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item1", 1, 1, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item2", 1, 2, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item3", 1, 3, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item4", 1, 4, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item5", 1, 5, 5);
|
||||
clearScreens();
|
||||
}
|
||||
|
||||
@Test
|
||||
void scrollDownThrough() {
|
||||
view = new ListView<>();
|
||||
view.setShowBorder(true);
|
||||
view.setRect(0, 0, 10, 7);
|
||||
|
||||
view.setItems(Arrays.asList("item1", "item2", "item3", "item4", "item5", "item6", "item7"));
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(0);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(0);
|
||||
view.draw(screen7x10);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item1", 1, 1, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item2", 1, 2, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item3", 1, 3, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item4", 1, 4, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item5", 1, 5, 5);
|
||||
clearScreens();
|
||||
|
||||
callVoidIntMethod(view, SCROLL_METHOD, 1);
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(0);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(1);
|
||||
view.draw(screen7x10);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item1", 1, 1, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item2", 1, 2, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item3", 1, 3, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item4", 1, 4, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item5", 1, 5, 5);
|
||||
clearScreens();
|
||||
|
||||
callVoidIntMethod(view, SCROLL_METHOD, 1);
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(0);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(2);
|
||||
view.draw(screen7x10);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item1", 1, 1, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item2", 1, 2, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item3", 1, 3, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item4", 1, 4, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item5", 1, 5, 5);
|
||||
clearScreens();
|
||||
|
||||
callVoidIntMethod(view, SCROLL_METHOD, 1);
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(0);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(3);
|
||||
view.draw(screen7x10);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item1", 1, 1, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item2", 1, 2, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item3", 1, 3, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item4", 1, 4, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item5", 1, 5, 5);
|
||||
clearScreens();
|
||||
|
||||
callVoidIntMethod(view, SCROLL_METHOD, 1);
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(0);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(4);
|
||||
view.draw(screen7x10);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item1", 1, 1, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item2", 1, 2, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item3", 1, 3, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item4", 1, 4, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item5", 1, 5, 5);
|
||||
clearScreens();
|
||||
|
||||
callVoidIntMethod(view, SCROLL_METHOD, 1);
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(1);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(4);
|
||||
view.draw(screen7x10);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item2", 1, 1, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item3", 1, 2, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item4", 1, 3, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item5", 1, 4, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item6", 1, 5, 5);
|
||||
clearScreens();
|
||||
|
||||
callVoidIntMethod(view, SCROLL_METHOD, 1);
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(2);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(4);
|
||||
view.draw(screen7x10);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item3", 1, 1, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item4", 1, 2, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item5", 1, 3, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item6", 1, 4, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item7", 1, 5, 5);
|
||||
clearScreens();
|
||||
|
||||
callVoidIntMethod(view, SCROLL_METHOD, 1);
|
||||
assertThat(getIntField(view, START_FIELD)).isEqualTo(0);
|
||||
assertThat(getIntField(view, POSITION_FIELD)).isEqualTo(0);
|
||||
view.draw(screen7x10);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item1", 1, 1, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item2", 1, 2, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item3", 1, 3, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item4", 1, 4, 5);
|
||||
assertThat(forScreen(screen7x10)).hasHorizontalText("item5", 1, 5, 5);
|
||||
clearScreens();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Radio {
|
||||
|
||||
@Test
|
||||
void showRadio() {
|
||||
view = new ListView<>(ItemStyle.RADIO);
|
||||
configure(view);
|
||||
view.setRect(0, 0, 80, 24);
|
||||
view.setItems(Arrays.asList("item1", "item2"));
|
||||
view.getKeyHandler().handle(KeyHandler.argsOf(KeyEvent.of(Key.Space)));
|
||||
assertThat(getIntSetField(view, SELECTED_FIELD)).containsExactly(0);
|
||||
view.getKeyHandler().handle(KeyHandler.argsOf(KeyEvent.of(Key.CursorDown)));
|
||||
view.getKeyHandler().handle(KeyHandler.argsOf(KeyEvent.of(Key.Space)));
|
||||
assertThat(getIntSetField(view, SELECTED_FIELD)).containsExactly(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Checked {
|
||||
|
||||
@Test
|
||||
void showChecked() {
|
||||
view = new ListView<>(ItemStyle.CHECKED);
|
||||
configure(view);
|
||||
view.setRect(0, 0, 80, 24);
|
||||
view.setItems(Arrays.asList("item1", "item2"));
|
||||
view.getKeyHandler().handle(KeyHandler.argsOf(KeyEvent.of(Key.Space)));
|
||||
assertThat(getIntSetField(view, SELECTED_FIELD)).containsExactly(0);
|
||||
view.getKeyHandler().handle(KeyHandler.argsOf(KeyEvent.of(Key.CursorDown)));
|
||||
view.getKeyHandler().handle(KeyHandler.argsOf(KeyEvent.of(Key.Space)));
|
||||
assertThat(getIntSetField(view, SELECTED_FIELD)).containsExactly(0, 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Factory {
|
||||
|
||||
@Test
|
||||
void customCellFactory() {
|
||||
view = new ListView<>();
|
||||
view.setShowBorder(true);
|
||||
view.setCellFactory((list, item) -> new TestListCell(item));
|
||||
view.setRect(0, 0, 80, 24);
|
||||
view.setItems(Arrays.asList("item1"));
|
||||
view.draw(screen24x80);
|
||||
assertThat(forScreen(screen24x80)).hasHorizontalText("pre-item1-post", 0, 1, 16);
|
||||
}
|
||||
|
||||
static class TestListCell extends ListCell<String> {
|
||||
static class TestListCell extends AbstractListCell<String> {
|
||||
|
||||
TestListCell(String item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Screen screen) {
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.shell.component.view.control.cell;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.shell.component.view.control.AbstractViewTests;
|
||||
import org.springframework.shell.component.view.control.ListView.ItemStyle;
|
||||
import org.springframework.shell.component.view.screen.Color;
|
||||
import org.springframework.shell.component.view.screen.ScreenItem;
|
||||
|
||||
@@ -26,40 +27,49 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
class ListCellTests extends AbstractViewTests {
|
||||
|
||||
@Test
|
||||
void simpleTextWrites() {
|
||||
ListCell<String> cell = new ListCell<>();
|
||||
void simpleTextWritesNoCheck() {
|
||||
ListCell<String> cell = ListCell.of("item", ItemStyle.NOCHECK);
|
||||
cell.setRect(0, 0, 10, 1);
|
||||
cell.updateItem("item");
|
||||
cell.draw(screen10x10);
|
||||
assertThat(forScreen(screen10x10)).hasHorizontalText("item", 0, 0, 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasBackgroundColor() {
|
||||
ListCell<String> cell = new ListCell<>();
|
||||
cell.setBackgroundColor(Color.BLUE);
|
||||
void simpleTextWritesChecked() {
|
||||
ListCell<String> cell = ListCell.of("item", ItemStyle.CHECKED);
|
||||
cell.setRect(0, 0, 10, 1);
|
||||
cell.updateItem("item");
|
||||
cell.draw(screen10x10);
|
||||
assertThat(forScreen(screen10x10)).hasHorizontalText("item", 0, 0, 4).hasBackgroundColor(0, 0, Color.BLUE);
|
||||
assertThat(forScreen(screen10x10)).hasHorizontalText("[ ] item", 0, 0, 8);
|
||||
cell.setSelected(true);
|
||||
cell.draw(screen10x10);
|
||||
assertThat(forScreen(screen10x10)).hasHorizontalText("[x] item", 0, 0, 8);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasBackgroundColor() {
|
||||
ListCell<String> cell = ListCell.of("item", ItemStyle.RADIO);
|
||||
cell.setRect(0, 0, 10, 1);
|
||||
cell.draw(screen10x10);
|
||||
assertThat(forScreen(screen10x10)).hasHorizontalText("[ ] item", 0, 0, 8);
|
||||
cell.setSelected(true);
|
||||
cell.draw(screen10x10);
|
||||
assertThat(forScreen(screen10x10)).hasHorizontalText("[x] item", 0, 0, 8);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasForegroundColor() {
|
||||
ListCell<String> cell = new ListCell<>();
|
||||
ListCell<String> cell = ListCell.of("item", ItemStyle.NOCHECK);
|
||||
cell.setForegroundColor(Color.BLUE);
|
||||
cell.setRect(0, 0, 10, 1);
|
||||
cell.updateItem("item");
|
||||
cell.draw(screen10x10);
|
||||
assertThat(forScreen(screen10x10)).hasHorizontalText("item", 0, 0, 4).hasForegroundColor(0, 0, Color.BLUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasStyle() {
|
||||
ListCell<String> cell = new ListCell<>();
|
||||
ListCell<String> cell = ListCell.of("item", ItemStyle.NOCHECK);
|
||||
cell.setStyle(ScreenItem.STYLE_BOLD);
|
||||
cell.setRect(0, 0, 10, 1);
|
||||
cell.updateItem("item");
|
||||
cell.draw(screen10x10);
|
||||
assertThat(forScreen(screen10x10)).hasHorizontalText("item", 0, 0, 4).hasStyle(0, 0, ScreenItem.STYLE_BOLD);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ import org.springframework.shell.component.view.control.MenuView.MenuItemCheckSt
|
||||
import org.springframework.shell.component.view.control.StatusBarView;
|
||||
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.control.cell.AbstractListCell;
|
||||
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;
|
||||
@@ -148,7 +148,7 @@ public class Catalog {
|
||||
// start main scenario browser
|
||||
ui.setRoot(app, true);
|
||||
ui.setFocus(categories);
|
||||
categories.setSelected(0);
|
||||
// categories.setSelected(0);
|
||||
ui.run();
|
||||
}
|
||||
|
||||
@@ -238,7 +238,11 @@ public class Catalog {
|
||||
return categories;
|
||||
}
|
||||
|
||||
private static class ScenarioListCell extends ListCell<ScenarioData> {
|
||||
private static class ScenarioListCell extends AbstractListCell<ScenarioData> {
|
||||
|
||||
public ScenarioListCell(ScenarioData item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Screen screen) {
|
||||
@@ -257,7 +261,7 @@ public class Catalog {
|
||||
scenarios.setTitle("Scenarios");
|
||||
scenarios.setFocusedTitleStyle(ScreenItem.STYLE_BOLD);
|
||||
scenarios.setShowBorder(true);
|
||||
scenarios.setCellFactory(list -> new ScenarioListCell());
|
||||
scenarios.setCellFactory((list, item) -> new ScenarioListCell(item));
|
||||
return scenarios;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.samples.catalog.scenario.listview;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.shell.component.view.control.ListView;
|
||||
import org.springframework.shell.component.view.control.View;
|
||||
import org.springframework.shell.component.view.control.ListView.ItemStyle;
|
||||
import org.springframework.shell.samples.catalog.scenario.AbstractScenario;
|
||||
import org.springframework.shell.samples.catalog.scenario.Scenario;
|
||||
import org.springframework.shell.samples.catalog.scenario.ScenarioComponent;
|
||||
|
||||
@ScenarioComponent(name = "Radio List", description = "Items with radio states", category = { Scenario.CATEGORY_LISTVIEW })
|
||||
public class CheckedListViewScenario extends AbstractScenario {
|
||||
|
||||
@Override
|
||||
public View build() {
|
||||
ListView<String> view = new ListView<>(ItemStyle.RADIO);
|
||||
view.setEventLoop(getEventloop());
|
||||
view.setItems(Arrays.asList("item1", "item2", "item3"));
|
||||
return view;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.samples.catalog.scenario.listview;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.springframework.shell.component.view.control.ListView;
|
||||
import org.springframework.shell.component.view.control.View;
|
||||
import org.springframework.shell.samples.catalog.scenario.AbstractScenario;
|
||||
import org.springframework.shell.samples.catalog.scenario.Scenario;
|
||||
import org.springframework.shell.samples.catalog.scenario.ScenarioComponent;
|
||||
|
||||
@ScenarioComponent(name = "Long List", description = "Show many items", category = { Scenario.CATEGORY_LISTVIEW })
|
||||
public class LongListViewScenario extends AbstractScenario {
|
||||
|
||||
@Override
|
||||
public View build() {
|
||||
ListView<String> view = new ListView<>();
|
||||
view.setEventLoop(getEventloop());
|
||||
List<String> items = IntStream.of(20).mapToObj(i -> "item" + i).collect(Collectors.toList());
|
||||
view.setItems(items);
|
||||
return view;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.samples.catalog.scenario.listview;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.shell.component.view.control.ListView;
|
||||
import org.springframework.shell.component.view.control.View;
|
||||
import org.springframework.shell.component.view.control.ListView.ItemStyle;
|
||||
import org.springframework.shell.samples.catalog.scenario.AbstractScenario;
|
||||
import org.springframework.shell.samples.catalog.scenario.Scenario;
|
||||
import org.springframework.shell.samples.catalog.scenario.ScenarioComponent;
|
||||
|
||||
@ScenarioComponent(name = "Checked List", description = "Items with checked states", category = { Scenario.CATEGORY_LISTVIEW })
|
||||
public class RadioListViewScenario extends AbstractScenario {
|
||||
|
||||
@Override
|
||||
public View build() {
|
||||
ListView<String> view = new ListView<>(ItemStyle.CHECKED);
|
||||
view.setEventLoop(getEventloop());
|
||||
view.setItems(Arrays.asList("item1", "item2", "item3"));
|
||||
return view;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user