ProgressView spinner calculation in item function
- Move spinner frame calculation to item itself so that - Remove spinnerFrame from state and add start/update times - view don't have hard dependency to it, as we'd need its interval - Change spinner to run only when view is running - Relates #995
This commit is contained in:
@@ -15,10 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.shell.component.view.control;
|
package org.springframework.shell.component.view.control;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import org.springframework.shell.component.message.ShellMessageBuilder;
|
import org.springframework.shell.component.message.ShellMessageBuilder;
|
||||||
import org.springframework.shell.component.view.control.cell.TextCell;
|
import org.springframework.shell.component.view.control.cell.TextCell;
|
||||||
import org.springframework.shell.component.view.screen.Screen;
|
import org.springframework.shell.component.view.screen.Screen;
|
||||||
@@ -36,15 +40,18 @@ import org.springframework.util.Assert;
|
|||||||
*/
|
*/
|
||||||
public class ProgressView extends BoxView {
|
public class ProgressView extends BoxView {
|
||||||
|
|
||||||
|
private final static Logger log = LoggerFactory.getLogger(ProgressView.class);
|
||||||
private final int tickStart;
|
private final int tickStart;
|
||||||
private final int tickEnd;
|
private final int tickEnd;
|
||||||
private int tickValue;
|
private int tickValue;
|
||||||
private boolean running = false;
|
private boolean running = false;
|
||||||
private String description;
|
private String description;
|
||||||
private Spinner spinner = Spinner.of(Spinner.LINE1, 130);
|
private Spinner spinner;
|
||||||
private int spinnerFrame;
|
|
||||||
private List<ProgressViewItem> items;
|
private List<ProgressViewItem> items;
|
||||||
private GridView grid;
|
private GridView grid;
|
||||||
|
private long startTime;
|
||||||
|
private long updateTime;
|
||||||
|
private List<TextCell<Context>> cells = new ArrayList<>();
|
||||||
|
|
||||||
private final static Function<Context, TextCell<Context>> DEFAULT_DESCRIPTION_FACTORY =
|
private final static Function<Context, TextCell<Context>> DEFAULT_DESCRIPTION_FACTORY =
|
||||||
(item) -> TextCell.of(item, ctx -> {
|
(item) -> TextCell.of(item, ctx -> {
|
||||||
@@ -66,9 +73,27 @@ public class ProgressView extends BoxView {
|
|||||||
private final static Function<Context, TextCell<Context>> DEFAULT_SPINNER_FACTORY =
|
private final static Function<Context, TextCell<Context>> DEFAULT_SPINNER_FACTORY =
|
||||||
item -> {
|
item -> {
|
||||||
TextCell<Context> cell = TextCell.of(item, ctx -> {
|
TextCell<Context> cell = TextCell.of(item, ctx -> {
|
||||||
Spinner spin = ctx.spinner();
|
int frame = 0;
|
||||||
int spinState = ctx.getState().sprinnerFrame();
|
|
||||||
return String.format("%s", spin.getFrames()[spinState]);
|
Spinner spin = ctx.getSpinner();
|
||||||
|
if (spin == null) {
|
||||||
|
spin = Spinner.of(Spinner.LINE1, 130);
|
||||||
|
}
|
||||||
|
if (ctx.getState().running()) {
|
||||||
|
// we know start time and current update time,
|
||||||
|
// calculate elapsed time "frame" to pick rolling
|
||||||
|
// spinner frame.
|
||||||
|
int interval = spin.getInterval();
|
||||||
|
long startTime = ctx.getState().startTime();
|
||||||
|
long updateTime = ctx.getState().updateTime();
|
||||||
|
long elapsedTime = updateTime - startTime;
|
||||||
|
long elapsedFrame = elapsedTime / interval;
|
||||||
|
frame = (int) elapsedFrame % spin.getFrames().length;
|
||||||
|
log.debug("Calculate frame {} {} {}", interval, elapsedTime, elapsedFrame);
|
||||||
|
}
|
||||||
|
log.debug("Drawing frame {}", frame);
|
||||||
|
|
||||||
|
return String.format("%s", spin.getFrames()[frame]);
|
||||||
});
|
});
|
||||||
return cell;
|
return cell;
|
||||||
};
|
};
|
||||||
@@ -173,11 +198,21 @@ public class ProgressView extends BoxView {
|
|||||||
this.description = description;
|
this.description = description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an explicit spinner to use.
|
||||||
|
*
|
||||||
|
* @param spinner the spinner to use
|
||||||
|
*/
|
||||||
|
public void setSpinner(Spinner spinner) {
|
||||||
|
this.spinner = spinner;
|
||||||
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
if (running) {
|
if (running) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
running = true;
|
running = true;
|
||||||
|
startTime = System.currentTimeMillis();
|
||||||
ProgressState state = getState();
|
ProgressState state = getState();
|
||||||
dispatch(ShellMessageBuilder.ofView(this, ProgressViewStartEvent.of(this, state)));
|
dispatch(ShellMessageBuilder.ofView(this, ProgressViewStartEvent.of(this, state)));
|
||||||
}
|
}
|
||||||
@@ -212,6 +247,7 @@ public class ProgressView extends BoxView {
|
|||||||
for (ProgressViewItem item : items) {
|
for (ProgressViewItem item : items) {
|
||||||
columnSizes[index] = item.size;
|
columnSizes[index] = item.size;
|
||||||
TextCell<Context> cell = item.factory.apply(buildContext());
|
TextCell<Context> cell = item.factory.apply(buildContext());
|
||||||
|
cells.add(cell);
|
||||||
cell.setHorizontalAlign(item.align);
|
cell.setHorizontalAlign(item.align);
|
||||||
grid.addItem(new BoxWrapper(cell), 0, index, 1, 1, 0, 0);
|
grid.addItem(new BoxWrapper(cell), 0, index, 1, 1, 0, 0);
|
||||||
index++;
|
index++;
|
||||||
@@ -221,27 +257,17 @@ public class ProgressView extends BoxView {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean needLastMillisInit = true;
|
|
||||||
private long lastMillis;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void drawInternal(Screen screen) {
|
protected void drawInternal(Screen screen) {
|
||||||
long current = System.currentTimeMillis();
|
long current = System.currentTimeMillis();
|
||||||
if (needLastMillisInit) {
|
|
||||||
needLastMillisInit = false;
|
|
||||||
lastMillis = current;
|
|
||||||
}
|
|
||||||
|
|
||||||
long elapsedFromLast = current - lastMillis;
|
updateTime = current;
|
||||||
if (elapsedFromLast > spinner.getInterval()) {
|
Context context = buildContext();
|
||||||
spinnerFrame = (spinnerFrame + 1) % spinner.getFrames().length;
|
for (TextCell<Context> cell : cells) {
|
||||||
lastMillis = current;
|
cell.setItem(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle rect = getRect();
|
Rectangle rect = getRect();
|
||||||
int width = rect.width();
|
|
||||||
width = width / 3;
|
|
||||||
|
|
||||||
grid.setRect(rect.x(), rect.y(), rect.width(), rect.height());
|
grid.setRect(rect.x(), rect.y(), rect.width(), rect.height());
|
||||||
grid.draw(screen);
|
grid.draw(screen);
|
||||||
|
|
||||||
@@ -291,7 +317,7 @@ public class ProgressView extends BoxView {
|
|||||||
* @return a view progress state
|
* @return a view progress state
|
||||||
*/
|
*/
|
||||||
public ProgressState getState() {
|
public ProgressState getState() {
|
||||||
return ProgressState.of(tickStart, tickEnd, tickValue, running, spinnerFrame);
|
return ProgressState.of(tickStart, tickEnd, tickValue, running, startTime, updateTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Context buildContext() {
|
private Context buildContext() {
|
||||||
@@ -318,7 +344,7 @@ public class ProgressView extends BoxView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Spinner spinner() {
|
public Spinner getSpinner() {
|
||||||
return ProgressView.this.spinner;
|
return ProgressView.this.spinner;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -355,7 +381,7 @@ public class ProgressView extends BoxView {
|
|||||||
*
|
*
|
||||||
* @return spinner frames
|
* @return spinner frames
|
||||||
*/
|
*/
|
||||||
Spinner spinner();
|
Spinner getSpinner();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve style using existing {@link ThemeResolver} and {@code theme name}.
|
* Resolve style using existing {@link ThemeResolver} and {@code theme name}.
|
||||||
@@ -376,12 +402,15 @@ public class ProgressView extends BoxView {
|
|||||||
* @param tickEnd the tick end value, positive and more than tick start
|
* @param tickEnd the tick end value, positive and more than tick start
|
||||||
* @param tickValue the current tick value, within inclusive bounds of tick start/end
|
* @param tickValue the current tick value, within inclusive bounds of tick start/end
|
||||||
* @param running the running state
|
* @param running the running state
|
||||||
* @param spinnerFrame the current spinner frame index
|
* @param startTime the running start time
|
||||||
|
* @param updateTime the last running update time
|
||||||
*/
|
*/
|
||||||
public record ProgressState(int tickStart, int tickEnd, int tickValue, boolean running, int sprinnerFrame) {
|
public record ProgressState(int tickStart, int tickEnd, int tickValue, boolean running, long startTime,
|
||||||
|
long updateTime) {
|
||||||
|
|
||||||
public static ProgressState of(int tickStart, int tickEnd, int tickValue, boolean running, int spinnerFrame) {
|
public static ProgressState of(int tickStart, int tickEnd, int tickValue, boolean running, long startTime,
|
||||||
return new ProgressState(tickStart, tickEnd, tickValue, running, spinnerFrame);
|
long updateTime) {
|
||||||
|
return new ProgressState(tickStart, tickEnd, tickValue, running, startTime, updateTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import org.springframework.shell.component.view.control.BoxView;
|
|||||||
import org.springframework.shell.component.view.control.InputView;
|
import org.springframework.shell.component.view.control.InputView;
|
||||||
import org.springframework.shell.component.view.control.ProgressView;
|
import org.springframework.shell.component.view.control.ProgressView;
|
||||||
import org.springframework.shell.component.view.control.ProgressView.ProgressViewItem;
|
import org.springframework.shell.component.view.control.ProgressView.ProgressViewItem;
|
||||||
|
import org.springframework.shell.component.view.control.Spinner;
|
||||||
import org.springframework.shell.component.view.event.EventLoop;
|
import org.springframework.shell.component.view.event.EventLoop;
|
||||||
import org.springframework.shell.geom.HorizontalAlign;
|
import org.springframework.shell.geom.HorizontalAlign;
|
||||||
import org.springframework.shell.geom.VerticalAlign;
|
import org.springframework.shell.geom.VerticalAlign;
|
||||||
@@ -95,13 +96,7 @@ public class ComponentUiCommands extends AbstractShellComponent {
|
|||||||
return String.format("Input was '%s'", input);
|
return String.format("Input was '%s'", input);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(command = "componentui progress1")
|
private void runProgress(ProgressView view) {
|
||||||
public void progress1() {
|
|
||||||
ProgressView view = new ProgressView();
|
|
||||||
view.setDescription("name");
|
|
||||||
view.setRect(0, 0, 20, 1);
|
|
||||||
|
|
||||||
|
|
||||||
ViewComponent component = new ViewComponent(getTerminal(), view);
|
ViewComponent component = new ViewComponent(getTerminal(), view);
|
||||||
EventLoop eventLoop = component.getEventLoop();
|
EventLoop eventLoop = component.getEventLoop();
|
||||||
|
|
||||||
@@ -123,10 +118,19 @@ public class ComponentUiCommands extends AbstractShellComponent {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
component.run();
|
component.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Command(command = "componentui progress1")
|
||||||
|
public void progress1() {
|
||||||
|
ProgressView view = new ProgressView();
|
||||||
|
view.setDescription("name");
|
||||||
|
view.setRect(0, 0, 20, 1);
|
||||||
|
view.start();
|
||||||
|
|
||||||
|
runProgress(view);
|
||||||
|
}
|
||||||
|
|
||||||
@Command(command = "componentui progress2")
|
@Command(command = "componentui progress2")
|
||||||
public void progress2() {
|
public void progress2() {
|
||||||
ProgressView view = new ProgressView(0, 100, ProgressViewItem.ofText(10, HorizontalAlign.LEFT),
|
ProgressView view = new ProgressView(0, 100, ProgressViewItem.ofText(10, HorizontalAlign.LEFT),
|
||||||
@@ -134,31 +138,20 @@ public class ComponentUiCommands extends AbstractShellComponent {
|
|||||||
ProgressViewItem.ofPercent(0, HorizontalAlign.RIGHT));
|
ProgressViewItem.ofPercent(0, HorizontalAlign.RIGHT));
|
||||||
view.setDescription("name");
|
view.setDescription("name");
|
||||||
view.setRect(0, 0, 20, 1);
|
view.setRect(0, 0, 20, 1);
|
||||||
|
view.start();
|
||||||
|
|
||||||
|
runProgress(view);
|
||||||
|
}
|
||||||
|
|
||||||
ViewComponent component = new ViewComponent(getTerminal(), view);
|
@Command(command = "componentui progress3")
|
||||||
EventLoop eventLoop = component.getEventLoop();
|
public void progress3() {
|
||||||
|
ProgressView view = new ProgressView();
|
||||||
|
view.setDescription("name");
|
||||||
|
view.setRect(0, 0, 20, 1);
|
||||||
|
view.setSpinner(Spinner.of(Spinner.DOTS1, 80));
|
||||||
|
view.start();
|
||||||
|
|
||||||
Flux<Message<?>> ticks = Flux.interval(Duration.ofMillis(100)).map(l -> {
|
runProgress(view);
|
||||||
Message<Long> message = MessageBuilder
|
|
||||||
.withPayload(l)
|
|
||||||
.setHeader(ShellMessageHeaderAccessor.EVENT_TYPE, EventLoop.Type.USER)
|
|
||||||
.build();
|
|
||||||
return message;
|
|
||||||
});
|
|
||||||
eventLoop.dispatch(ticks);
|
|
||||||
|
|
||||||
eventLoop.onDestroy(eventLoop.events()
|
|
||||||
.filter(m -> EventLoop.Type.USER.equals(StaticShellMessageHeaderAccessor.getEventType(m)))
|
|
||||||
.subscribe(m -> {
|
|
||||||
if (m.getPayload() instanceof Long) {
|
|
||||||
view.tickAdvance(5);
|
|
||||||
eventLoop.dispatch(ShellMessageBuilder.ofRedraw());
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
component.run();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user