diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/ListView.java b/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/ListView.java index bf0a3973..583fa831 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/ListView.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/ListView.java @@ -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 extends BoxView { private final List items = new ArrayList<>(); - private int selected = -1; - private final List> cells = new ArrayList<>(); - private Function, ListCell> factory = listView -> new ListCell<>(); + private final ItemStyle itemStyle; + private int start = 0; + private int pos = 0; + private final Set selected = new HashSet<>(); + private BiFunction, T, ListCell> 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 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, ListCell> factory) { - this.factory = factory; + public ListView(T[] items, ItemStyle itemStyle) { + this(items != null ? Arrays.asList(items) : Collections.emptyList(), itemStyle); } - public void setItems(List items) { - this.items.clear(); - this.items.addAll(items); - this.cells.clear(); - for (T i : items) { - ListCell c = factory.apply(this); - cells.add(c); - c.updateItem(i); - } + public ListView(@Nullable List items, ItemStyle itemStyle) { + Assert.notNull(itemStyle, "item style must be set"); + this.itemStyle = itemStyle; + setItems(items); } @Override @@ -97,60 +97,187 @@ public class ListView 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 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 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> iter = cells.listIterator(); + while (iter.hasNext()) { + int index = iter.nextIndex(); + ListCell 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 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, T, ListCell> 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(); } /** diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/cell/AbstractCell.java b/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/cell/AbstractCell.java index 3e9f3eca..e831c8a8 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/cell/AbstractCell.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/cell/AbstractCell.java @@ -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 extends AbstractControl implements Cell { - 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 extends AbstractControl implements Cell 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; diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/cell/AbstractListCell.java b/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/cell/AbstractListCell.java new file mode 100644 index 00000000..834ee9db --- /dev/null +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/cell/AbstractListCell.java @@ -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 extends AbstractCell implements ListCell { + + 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; + } + +} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/cell/Cell.java b/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/cell/Cell.java new file mode 100644 index 00000000..f1a5fd15 --- /dev/null +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/cell/Cell.java @@ -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 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); + +} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/cell/ListCell.java b/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/cell/ListCell.java index 08344f58..48a142f8 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/cell/ListCell.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/cell/ListCell.java @@ -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 extends AbstractCell { +public interface ListCell extends Cell { - 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 ListCell of(T item, ItemStyle itemStyle) { + return new DefaultListCell(item, itemStyle); } - public void updateItem(T item) { - setItem(item); - this.text = item.toString(); - } + static class DefaultListCell extends AbstractListCell { + DefaultListCell(T item, ItemStyle itemStyle) { + super(item, itemStyle); + } + + } } diff --git a/spring-shell-core/src/test/java/org/springframework/shell/component/view/control/AbstractViewTests.java b/spring-shell-core/src/test/java/org/springframework/shell/component/view/control/AbstractViewTests.java index d156cffe..9f9f02ab 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/component/view/control/AbstractViewTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/component/view/control/AbstractViewTests.java @@ -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 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); + } + } diff --git a/spring-shell-core/src/test/java/org/springframework/shell/component/view/control/ListViewTests.java b/spring-shell-core/src/test/java/org/springframework/shell/component/view/control/ListViewTests.java index 5fed7a46..7abc9450 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/component/view/control/ListViewTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/component/view/control/ListViewTests.java @@ -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> LISTVIEW_STRING_TYPEREF + = new ParameterizedTypeReference>() {}; + + 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 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> 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> 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> 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 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 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 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 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 { + static class TestListCell extends AbstractListCell { + + TestListCell(String item) { + super(item); + } @Override public void draw(Screen screen) { diff --git a/spring-shell-core/src/test/java/org/springframework/shell/component/view/control/cell/ListCellTests.java b/spring-shell-core/src/test/java/org/springframework/shell/component/view/control/cell/ListCellTests.java index 8fea15f5..87a65046 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/component/view/control/cell/ListCellTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/component/view/control/cell/ListCellTests.java @@ -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 cell = new ListCell<>(); + void simpleTextWritesNoCheck() { + ListCell 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 cell = new ListCell<>(); - cell.setBackgroundColor(Color.BLUE); + void simpleTextWritesChecked() { + ListCell 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 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 cell = new ListCell<>(); + ListCell 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 cell = new ListCell<>(); + ListCell 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); } diff --git a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/Catalog.java b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/Catalog.java index 16377966..3145df81 100644 --- a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/Catalog.java +++ b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/Catalog.java @@ -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 { + private static class ScenarioListCell extends AbstractListCell { + + 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; } diff --git a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/CheckedListViewScenario.java b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/CheckedListViewScenario.java new file mode 100644 index 00000000..a7ab1432 --- /dev/null +++ b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/CheckedListViewScenario.java @@ -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 view = new ListView<>(ItemStyle.RADIO); + view.setEventLoop(getEventloop()); + view.setItems(Arrays.asList("item1", "item2", "item3")); + return view; + } + +} diff --git a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/LongListViewScenario.java b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/LongListViewScenario.java new file mode 100644 index 00000000..03fdc303 --- /dev/null +++ b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/LongListViewScenario.java @@ -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 view = new ListView<>(); + view.setEventLoop(getEventloop()); + List items = IntStream.of(20).mapToObj(i -> "item" + i).collect(Collectors.toList()); + view.setItems(items); + return view; + } + +} diff --git a/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/RadioListViewScenario.java b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/RadioListViewScenario.java new file mode 100644 index 00000000..0c1cd152 --- /dev/null +++ b/spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/scenario/listview/RadioListViewScenario.java @@ -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 view = new ListView<>(ItemStyle.CHECKED); + view.setEventLoop(getEventloop()); + view.setItems(Arrays.asList("item1", "item2", "item3")); + return view; + } + +}