Skip to content

Commit 95aaa86

Browse files
committed
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
1 parent 9446afe commit 95aaa86

File tree

2 files changed

+78
-56
lines changed
  • spring-shell-core/src/main/java/org/springframework/shell/component/view/control
  • spring-shell-samples/spring-shell-sample-commands/src/main/java/org/springframework/shell/samples/standard

2 files changed

+78
-56
lines changed

spring-shell-core/src/main/java/org/springframework/shell/component/view/control/ProgressView.java

Lines changed: 55 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,14 @@
1515
*/
1616
package org.springframework.shell.component.view.control;
1717

18+
import java.util.ArrayList;
1819
import java.util.Arrays;
1920
import java.util.List;
2021
import java.util.function.Function;
2122

23+
import org.slf4j.Logger;
24+
import org.slf4j.LoggerFactory;
25+
2226
import org.springframework.shell.component.message.ShellMessageBuilder;
2327
import org.springframework.shell.component.view.control.cell.TextCell;
2428
import org.springframework.shell.component.view.screen.Screen;
@@ -36,15 +40,18 @@
3640
*/
3741
public class ProgressView extends BoxView {
3842

43+
private final static Logger log = LoggerFactory.getLogger(ProgressView.class);
3944
private final int tickStart;
4045
private final int tickEnd;
4146
private int tickValue;
4247
private boolean running = false;
4348
private String description;
44-
private Spinner spinner = Spinner.of(Spinner.LINE1, 130);
45-
private int spinnerFrame;
49+
private Spinner spinner;
4650
private List<ProgressViewItem> items;
4751
private GridView grid;
52+
private long startTime;
53+
private long updateTime;
54+
private List<TextCell<Context>> cells = new ArrayList<>();
4855

4956
private final static Function<Context, TextCell<Context>> DEFAULT_DESCRIPTION_FACTORY =
5057
(item) -> TextCell.of(item, ctx -> {
@@ -66,9 +73,27 @@ public class ProgressView extends BoxView {
6673
private final static Function<Context, TextCell<Context>> DEFAULT_SPINNER_FACTORY =
6774
item -> {
6875
TextCell<Context> cell = TextCell.of(item, ctx -> {
69-
Spinner spin = ctx.spinner();
70-
int spinState = ctx.getState().sprinnerFrame();
71-
return String.format("%s", spin.getFrames()[spinState]);
76+
int frame = 0;
77+
78+
Spinner spin = ctx.getSpinner();
79+
if (spin == null) {
80+
spin = Spinner.of(Spinner.LINE1, 130);
81+
}
82+
if (ctx.getState().running()) {
83+
// we know start time and current update time,
84+
// calculate elapsed time "frame" to pick rolling
85+
// spinner frame.
86+
int interval = spin.getInterval();
87+
long startTime = ctx.getState().startTime();
88+
long updateTime = ctx.getState().updateTime();
89+
long elapsedTime = updateTime - startTime;
90+
long elapsedFrame = elapsedTime / interval;
91+
frame = (int) elapsedFrame % spin.getFrames().length;
92+
log.debug("Calculate frame {} {} {}", interval, elapsedTime, elapsedFrame);
93+
}
94+
log.debug("Drawing frame {}", frame);
95+
96+
return String.format("%s", spin.getFrames()[frame]);
7297
});
7398
return cell;
7499
};
@@ -173,11 +198,21 @@ public void setDescription(String description) {
173198
this.description = description;
174199
}
175200

201+
/**
202+
* Sets an explicit spinner to use.
203+
*
204+
* @param spinner the spinner to use
205+
*/
206+
public void setSpinner(Spinner spinner) {
207+
this.spinner = spinner;
208+
}
209+
176210
public void start() {
177211
if (running) {
178212
return;
179213
}
180214
running = true;
215+
startTime = System.currentTimeMillis();
181216
ProgressState state = getState();
182217
dispatch(ShellMessageBuilder.ofView(this, ProgressViewStartEvent.of(this, state)));
183218
}
@@ -212,6 +247,7 @@ private void initLayout() {
212247
for (ProgressViewItem item : items) {
213248
columnSizes[index] = item.size;
214249
TextCell<Context> cell = item.factory.apply(buildContext());
250+
cells.add(cell);
215251
cell.setHorizontalAlign(item.align);
216252
grid.addItem(new BoxWrapper(cell), 0, index, 1, 1, 0, 0);
217253
index++;
@@ -221,27 +257,17 @@ private void initLayout() {
221257

222258
}
223259

224-
boolean needLastMillisInit = true;
225-
private long lastMillis;
226-
227260
@Override
228261
protected void drawInternal(Screen screen) {
229262
long current = System.currentTimeMillis();
230-
if (needLastMillisInit) {
231-
needLastMillisInit = false;
232-
lastMillis = current;
233-
}
234263

235-
long elapsedFromLast = current - lastMillis;
236-
if (elapsedFromLast > spinner.getInterval()) {
237-
spinnerFrame = (spinnerFrame + 1) % spinner.getFrames().length;
238-
lastMillis = current;
264+
updateTime = current;
265+
Context context = buildContext();
266+
for (TextCell<Context> cell : cells) {
267+
cell.setItem(context);
239268
}
240269

241270
Rectangle rect = getRect();
242-
int width = rect.width();
243-
width = width / 3;
244-
245271
grid.setRect(rect.x(), rect.y(), rect.width(), rect.height());
246272
grid.draw(screen);
247273

@@ -291,7 +317,7 @@ else if (value < tickStart) {
291317
* @return a view progress state
292318
*/
293319
public ProgressState getState() {
294-
return ProgressState.of(tickStart, tickEnd, tickValue, running, spinnerFrame);
320+
return ProgressState.of(tickStart, tickEnd, tickValue, running, startTime, updateTime);
295321
}
296322

297323
private Context buildContext() {
@@ -318,7 +344,7 @@ public int resolveThemeStyle(String tag, int defaultStyle) {
318344
}
319345

320346
@Override
321-
public Spinner spinner() {
347+
public Spinner getSpinner() {
322348
return ProgressView.this.spinner;
323349
}
324350
};
@@ -355,7 +381,7 @@ public interface Context {
355381
*
356382
* @return spinner frames
357383
*/
358-
Spinner spinner();
384+
Spinner getSpinner();
359385

360386
/**
361387
* Resolve style using existing {@link ThemeResolver} and {@code theme name}.
@@ -376,12 +402,15 @@ public interface Context {
376402
* @param tickEnd the tick end value, positive and more than tick start
377403
* @param tickValue the current tick value, within inclusive bounds of tick start/end
378404
* @param running the running state
379-
* @param spinnerFrame the current spinner frame index
405+
* @param startTime the running start time
406+
* @param updateTime the last running update time
380407
*/
381-
public record ProgressState(int tickStart, int tickEnd, int tickValue, boolean running, int sprinnerFrame) {
408+
public record ProgressState(int tickStart, int tickEnd, int tickValue, boolean running, long startTime,
409+
long updateTime) {
382410

383-
public static ProgressState of(int tickStart, int tickEnd, int tickValue, boolean running, int spinnerFrame) {
384-
return new ProgressState(tickStart, tickEnd, tickValue, running, spinnerFrame);
411+
public static ProgressState of(int tickStart, int tickEnd, int tickValue, boolean running, long startTime,
412+
long updateTime) {
413+
return new ProgressState(tickStart, tickEnd, tickValue, running, startTime, updateTime);
385414
}
386415
}
387416

spring-shell-samples/spring-shell-sample-commands/src/main/java/org/springframework/shell/samples/standard/ComponentUiCommands.java

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.shell.component.view.control.InputView;
3232
import org.springframework.shell.component.view.control.ProgressView;
3333
import org.springframework.shell.component.view.control.ProgressView.ProgressViewItem;
34+
import org.springframework.shell.component.view.control.Spinner;
3435
import org.springframework.shell.component.view.event.EventLoop;
3536
import org.springframework.shell.geom.HorizontalAlign;
3637
import org.springframework.shell.geom.VerticalAlign;
@@ -95,13 +96,7 @@ public String stringInput() {
9596
return String.format("Input was '%s'", input);
9697
}
9798

98-
@Command(command = "componentui progress1")
99-
public void progress1() {
100-
ProgressView view = new ProgressView();
101-
view.setDescription("name");
102-
view.setRect(0, 0, 20, 1);
103-
104-
99+
private void runProgress(ProgressView view) {
105100
ViewComponent component = new ViewComponent(getTerminal(), view);
106101
EventLoop eventLoop = component.getEventLoop();
107102

@@ -123,42 +118,40 @@ public void progress1() {
123118
}
124119
}));
125120

126-
127121
component.run();
128122
}
129123

124+
@Command(command = "componentui progress1")
125+
public void progress1() {
126+
ProgressView view = new ProgressView();
127+
view.setDescription("name");
128+
view.setRect(0, 0, 20, 1);
129+
view.start();
130+
131+
runProgress(view);
132+
}
133+
130134
@Command(command = "componentui progress2")
131135
public void progress2() {
132136
ProgressView view = new ProgressView(0, 100, ProgressViewItem.ofText(10, HorizontalAlign.LEFT),
133137
ProgressViewItem.ofSpinner(3, HorizontalAlign.LEFT),
134138
ProgressViewItem.ofPercent(0, HorizontalAlign.RIGHT));
135139
view.setDescription("name");
136140
view.setRect(0, 0, 20, 1);
141+
view.start();
137142

143+
runProgress(view);
144+
}
138145

139-
ViewComponent component = new ViewComponent(getTerminal(), view);
140-
EventLoop eventLoop = component.getEventLoop();
141-
142-
Flux<Message<?>> ticks = Flux.interval(Duration.ofMillis(100)).map(l -> {
143-
Message<Long> message = MessageBuilder
144-
.withPayload(l)
145-
.setHeader(ShellMessageHeaderAccessor.EVENT_TYPE, EventLoop.Type.USER)
146-
.build();
147-
return message;
148-
});
149-
eventLoop.dispatch(ticks);
150-
151-
eventLoop.onDestroy(eventLoop.events()
152-
.filter(m -> EventLoop.Type.USER.equals(StaticShellMessageHeaderAccessor.getEventType(m)))
153-
.subscribe(m -> {
154-
if (m.getPayload() instanceof Long) {
155-
view.tickAdvance(5);
156-
eventLoop.dispatch(ShellMessageBuilder.ofRedraw());
157-
}
158-
}));
159-
146+
@Command(command = "componentui progress3")
147+
public void progress3() {
148+
ProgressView view = new ProgressView();
149+
view.setDescription("name");
150+
view.setRect(0, 0, 20, 1);
151+
view.setSpinner(Spinner.of(Spinner.DOTS1, 80));
152+
view.start();
160153

161-
component.run();
154+
runProgress(view);
162155
}
163156

164157
}

0 commit comments

Comments
 (0)