Overhaul StatusBar for better text use
- Can change item text - Can position items by priority(true left, false right) and primary(sort for draw attempt) - Fixes #1112
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
* Copyright 2023-2024 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.
|
||||
@@ -17,12 +17,14 @@ package org.springframework.shell.component.view.control;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.shell.component.message.ShellMessageBuilder;
|
||||
import org.springframework.shell.component.view.event.MouseEvent;
|
||||
import org.springframework.shell.component.view.event.MouseHandler;
|
||||
@@ -30,17 +32,24 @@ import org.springframework.shell.component.view.screen.Screen;
|
||||
import org.springframework.shell.component.view.screen.Screen.Writer;
|
||||
import org.springframework.shell.geom.Rectangle;
|
||||
import org.springframework.shell.style.StyleSettings;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link StatusBarView} shows {@link StatusItem items} horizontally and is
|
||||
* typically used in layouts which builds complete terminal UI's.
|
||||
*
|
||||
* {@link StatusItem item} {@code primary} denotes if item is drawn to left
|
||||
* or right, {@code priority} on which order items are drawn until bar runs
|
||||
* out of space. Default {@code primary} is {@code true} and {@code priority}
|
||||
* is {@code 0}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*/
|
||||
public class StatusBarView extends BoxView {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(StatusBarView.class);
|
||||
private final List<StatusItem> items = new ArrayList<>();
|
||||
private String itemSeparator = " | ";
|
||||
|
||||
public StatusBarView() {
|
||||
this(new StatusItem[0]);
|
||||
@@ -59,18 +68,64 @@ public class StatusBarView extends BoxView {
|
||||
return StyleSettings.TAG_STATUSBAR_BACKGROUND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item separator.
|
||||
*
|
||||
* @return a separator
|
||||
*/
|
||||
@Nullable
|
||||
public String getItemSeparator() {
|
||||
return itemSeparator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the item separator. Separator can be {@code null} or empty which
|
||||
* essentially disables it.
|
||||
*
|
||||
* @param itemSeparator the item separator
|
||||
*/
|
||||
public void setItemSeparator(@Nullable String itemSeparator) {
|
||||
this.itemSeparator = itemSeparator;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawInternal(Screen screen) {
|
||||
Rectangle rect = getInnerRect();
|
||||
log.debug("Drawing status bar to {}", rect);
|
||||
Writer writer = screen.writerBuilder().build();
|
||||
int x = rect.x();
|
||||
|
||||
int primaryX = rect.x();
|
||||
int nonprimaryX = rect.x() + rect.width();
|
||||
boolean primaryWritten = false;
|
||||
boolean nonprimaryWritten = false;
|
||||
|
||||
ListIterator<StatusItem> iter = items.listIterator();
|
||||
while (iter.hasNext()) {
|
||||
StatusItem item = iter.next();
|
||||
String text = String.format(" %s%s", item.getTitle(), iter.hasNext() ? " |" : "");
|
||||
writer.text(text, x, rect.y());
|
||||
x += text.length();
|
||||
String text = item.getTitle();
|
||||
if (text == null) {
|
||||
continue;
|
||||
}
|
||||
String sep = getItemSeparator();
|
||||
if (nonprimaryX - primaryX < (text.length() + (sep != null ? sep.length() : 0))) {
|
||||
break;
|
||||
}
|
||||
if (item.primary) {
|
||||
if (primaryWritten && StringUtils.hasText(sep)) {
|
||||
text = sep + text;
|
||||
}
|
||||
writer.text(text, primaryX, rect.y());
|
||||
primaryX += text.length();
|
||||
primaryWritten = true;
|
||||
}
|
||||
else {
|
||||
if (nonprimaryWritten && StringUtils.hasText(sep)) {
|
||||
text = text + sep;
|
||||
}
|
||||
writer.text(text, nonprimaryX - text.length(), rect.y());
|
||||
nonprimaryX -= text.length();
|
||||
nonprimaryWritten = true;
|
||||
}
|
||||
}
|
||||
super.drawInternal(screen);
|
||||
}
|
||||
@@ -121,6 +176,19 @@ public class StatusBarView extends BoxView {
|
||||
public void setItems(List<StatusItem> items) {
|
||||
this.items.clear();
|
||||
this.items.addAll(items);
|
||||
Collections.sort(this.items, (o1, o2) -> {
|
||||
int ret = o1.priority - o2.priority;
|
||||
if (ret == 0) {
|
||||
if (o1.primary && !o2.primary) {
|
||||
ret = -1;
|
||||
}
|
||||
else if (!o1.primary && o2.primary) {
|
||||
ret = 1;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
// this.items.sort(null);
|
||||
registerHotKeys();
|
||||
}
|
||||
|
||||
@@ -152,6 +220,8 @@ public class StatusBarView extends BoxView {
|
||||
private String title;
|
||||
private Runnable action;
|
||||
private Integer hotKey;
|
||||
private boolean primary = true;
|
||||
private int priority = 0;
|
||||
|
||||
public StatusItem(String title) {
|
||||
this(title, null);
|
||||
@@ -167,6 +237,14 @@ public class StatusBarView extends BoxView {
|
||||
this.hotKey = hotKey;
|
||||
}
|
||||
|
||||
public StatusItem(String title, Runnable action, Integer hotKey, boolean primary, int priority) {
|
||||
this.title = title;
|
||||
this.action = action;
|
||||
this.hotKey = hotKey;
|
||||
this.primary = primary;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public static StatusItem of(String title) {
|
||||
return new StatusItem(title);
|
||||
}
|
||||
@@ -179,10 +257,18 @@ public class StatusBarView extends BoxView {
|
||||
return new StatusItem(title, action, hotKey);
|
||||
}
|
||||
|
||||
public static StatusItem of(String title, Runnable action, Integer hotKey, boolean primary, int priority) {
|
||||
return new StatusItem(title, action, hotKey, primary, priority);
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public Runnable getAction() {
|
||||
return action;
|
||||
}
|
||||
@@ -201,6 +287,22 @@ public class StatusBarView extends BoxView {
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
public void setPriority(int priority) {
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public boolean isPrimary() {
|
||||
return primary;
|
||||
}
|
||||
|
||||
public void setPrimary(boolean primary) {
|
||||
this.primary = primary;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
* Copyright 2023-2024 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.
|
||||
@@ -17,6 +17,8 @@ package org.springframework.shell.component.view.control;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
@@ -55,6 +57,10 @@ class StatusBarViewTests extends AbstractViewTests {
|
||||
|
||||
view = new StatusBarView(Arrays.asList(StatusItem.of("item1")));
|
||||
assertThat(view.getItems()).hasSize(1);
|
||||
|
||||
view = new StatusBarView(Arrays.asList(StatusItem.of("item1", null, null, false, 1)));
|
||||
assertThat(view.getItems()).hasSize(1);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -96,6 +102,66 @@ class StatusBarViewTests extends AbstractViewTests {
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Sorting {
|
||||
|
||||
StatusItem p_0_1;
|
||||
StatusItem p_0_2;
|
||||
StatusItem p_0_3;
|
||||
StatusItem p_1_1;
|
||||
StatusItem p_2_1;
|
||||
StatusItem n_0_1;
|
||||
StatusItem n_0_2;
|
||||
StatusItem n_0_3;
|
||||
StatusBarView view;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
p_0_1 = new StatusItem("p_0_1");
|
||||
p_0_2 = new StatusItem("p_0_2");
|
||||
p_0_3 = new StatusItem("p_0_3");
|
||||
|
||||
p_1_1 = new StatusItem("p_1_1");
|
||||
p_1_1.setPriority(1);
|
||||
p_2_1 = new StatusItem("p_2_1");
|
||||
p_2_1.setPriority(2);
|
||||
|
||||
n_0_1 = new StatusItem("n_0_1");
|
||||
n_0_1.setPrimary(false);
|
||||
n_0_2 = new StatusItem("n_0_2");
|
||||
n_0_2.setPrimary(false);
|
||||
n_0_3 = new StatusItem("n_0_3");
|
||||
n_0_3.setPrimary(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultsOrderNotChanged() {
|
||||
view = new StatusBarView(Arrays.asList(p_0_1, p_0_2, p_0_3));
|
||||
assertThat(extractTitles()).containsExactly("p_0_1", "p_0_2", "p_0_3");
|
||||
}
|
||||
|
||||
@Test
|
||||
void primaryBeforeNonprimary() {
|
||||
view = new StatusBarView(Arrays.asList(p_0_1, n_0_1));
|
||||
assertThat(extractTitles()).containsExactly("p_0_1", "n_0_1");
|
||||
view = new StatusBarView(Arrays.asList(n_0_1, p_0_1));
|
||||
assertThat(extractTitles()).containsExactly("p_0_1", "n_0_1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void priorityTakesOrder() {
|
||||
view = new StatusBarView(Arrays.asList(p_0_1, p_1_1, p_2_1));
|
||||
assertThat(extractTitles()).containsExactly("p_0_1", "p_1_1", "p_2_1");
|
||||
view = new StatusBarView(Arrays.asList(p_2_1, p_0_1, p_1_1));
|
||||
assertThat(extractTitles()).containsExactly("p_0_1", "p_1_1", "p_2_1");
|
||||
}
|
||||
|
||||
private List<String> extractTitles() {
|
||||
return view.getItems().stream().map(StatusItem::getTitle).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Styling {
|
||||
|
||||
@@ -107,6 +173,108 @@ class StatusBarViewTests extends AbstractViewTests {
|
||||
view.draw(screen24x80);
|
||||
assertThat(forScreen(screen24x80)).hasBorder(0, 0, 80, 24);
|
||||
}
|
||||
|
||||
@Test
|
||||
void primaryItems() {
|
||||
StatusItem item1 = new StatusItem("item1");
|
||||
StatusItem item2 = new StatusItem("item2");
|
||||
StatusBarView view = new StatusBarView(Arrays.asList(item1, item2));
|
||||
view.setItemSeparator(null);
|
||||
view.setRect(0, 0, 80, 1);
|
||||
view.draw(screen1x80);
|
||||
assertThat(forScreen(screen1x80)).hasHorizontalText("item1", 0, 0, 5);
|
||||
assertThat(forScreen(screen1x80)).hasHorizontalText("item2", 5, 0, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonprimaryItems() {
|
||||
StatusItem item1 = new StatusItem("item1");
|
||||
StatusItem item2 = new StatusItem("item2");
|
||||
item1.setPrimary(false);
|
||||
item2.setPrimary(false);
|
||||
StatusBarView view = new StatusBarView(Arrays.asList(item1, item2));
|
||||
view.setItemSeparator(null);
|
||||
view.setRect(0, 0, 80, 1);
|
||||
view.draw(screen1x80);
|
||||
assertThat(forScreen(screen1x80)).hasHorizontalText("item1", 75, 0, 5);
|
||||
assertThat(forScreen(screen1x80)).hasHorizontalText("item2", 70, 0, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void primaryAndNonprimaryItems() {
|
||||
StatusItem item1 = new StatusItem("item1");
|
||||
StatusItem item2 = new StatusItem("item2");
|
||||
item2.setPrimary(false);
|
||||
StatusBarView view = new StatusBarView(Arrays.asList(item1, item2));
|
||||
view.setItemSeparator(null);
|
||||
view.setRect(0, 0, 80, 1);
|
||||
view.draw(screen1x80);
|
||||
assertThat(forScreen(screen1x80)).hasHorizontalText("item1", 0, 0, 5);
|
||||
assertThat(forScreen(screen1x80)).hasHorizontalText("item2", 75, 0, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void canChangeText() {
|
||||
StatusItem item1 = new StatusItem("item1");
|
||||
StatusBarView view = new StatusBarView(Arrays.asList(item1));
|
||||
view.setItemSeparator(null);
|
||||
view.setRect(0, 0, 80, 1);
|
||||
view.draw(screen1x80);
|
||||
assertThat(forScreen(screen1x80)).hasHorizontalText("item1", 0, 0, 5);
|
||||
item1.setTitle("fake");
|
||||
view.draw(screen1x80);
|
||||
assertThat(forScreen(screen1x80)).hasHorizontalText("fake", 0, 0, 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void itemSeparator() {
|
||||
StatusItem item1 = new StatusItem("item1");
|
||||
StatusItem item2 = new StatusItem("item2");
|
||||
StatusItem item3 = new StatusItem("item3");
|
||||
item3.setPrimary(false);
|
||||
StatusItem item4 = new StatusItem("item4");
|
||||
item4.setPrimary(false);
|
||||
StatusBarView view = new StatusBarView(Arrays.asList(item1, item2, item3, item4));
|
||||
view.setRect(0, 0, 80, 1);
|
||||
view.draw(screen1x80);
|
||||
assertThat(forScreen(screen1x80)).hasHorizontalText("item1 | ", 0, 0, 8);
|
||||
assertThat(forScreen(screen1x80)).hasHorizontalText("item2", 8, 0, 5);
|
||||
assertThat(forScreen(screen1x80)).hasHorizontalText(" | item3", 72, 0, 8);
|
||||
assertThat(forScreen(screen1x80)).hasHorizontalText("item4", 67, 0, 5);
|
||||
view.setItemSeparator(null);
|
||||
view.draw(screen1x80);
|
||||
assertThat(forScreen(screen1x80)).hasHorizontalText("item1", 0, 0, 5);
|
||||
assertThat(forScreen(screen1x80)).hasHorizontalText("item2", 5, 0, 5);
|
||||
assertThat(forScreen(screen1x80)).hasHorizontalText("item3", 75, 0, 5);
|
||||
assertThat(forScreen(screen1x80)).hasHorizontalText("item4", 70, 0, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void skipItemsWhenOverflow() {
|
||||
StatusItem item1 = new StatusItem("item11111111111111111111111111");
|
||||
StatusItem item2 = new StatusItem("item22222222222222222222222222");
|
||||
StatusItem item3 = new StatusItem("item33333333333333333333333333");
|
||||
StatusBarView view = new StatusBarView(Arrays.asList(item1, item2, item3));
|
||||
view.setItemSeparator(null);
|
||||
view.setRect(0, 0, 80, 1);
|
||||
view.draw(screen1x80);
|
||||
assertThat(forScreen(screen1x80)).hasHorizontalText("item11111111111111111111111111", 0, 0, 30);
|
||||
assertThat(forScreen(screen1x80)).hasHorizontalText("item22222222222222222222222222", 30, 0, 30);
|
||||
assertThat(forScreen(screen1x80)).hasHorizontalText("", 60, 0, 20);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullTitleDontDraw() {
|
||||
StatusItem item1 = new StatusItem("item1");
|
||||
StatusItem item2 = new StatusItem(null);
|
||||
StatusBarView view = new StatusBarView(Arrays.asList(item1, item2));
|
||||
view.setItemSeparator(null);
|
||||
view.setRect(0, 0, 80, 1);
|
||||
view.draw(screen1x80);
|
||||
assertThat(forScreen(screen1x80)).hasHorizontalText("item1", 0, 0, 5);
|
||||
assertThat(forScreen(screen1x80)).hasHorizontalText("", 5, 0, 5);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
||||
Reference in New Issue
Block a user