Skip to content

Commit

Permalink
Revisit ProgressView
Browse files Browse the repository at this point in the history
- Polish a lot of things
- Do updates/redraw internally with eventloop as it'd be super
  inconvenient asking user to handle this externally
- Rewrite sample to be more unified for various cases
- Set eventloop from ViewComponent
- Relates #995
  • Loading branch information
jvalkeal committed Feb 15, 2024
1 parent 27c2717 commit afb8bc7
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public ViewComponent(TerminalUI terminalUI, Terminal terminal, ViewComponentExec
this.view = view;
this.viewComponentExecutor = viewComponentExecutor;
this.eventLoop = terminalUI.getEventLoop();
view.setEventLoop(this.eventLoop);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,33 @@
*/
package org.springframework.shell.component.view.control;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.function.Function;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.Disposable;
import reactor.core.Disposables;
import reactor.core.publisher.Flux;

import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.shell.component.message.ShellMessageBuilder;
import org.springframework.shell.component.message.ShellMessageHeaderAccessor;
import org.springframework.shell.component.message.StaticShellMessageHeaderAccessor;
import org.springframework.shell.component.view.control.cell.TextCell;
import org.springframework.shell.component.view.event.EventLoop;
import org.springframework.shell.component.view.screen.Screen;
import org.springframework.shell.geom.HorizontalAlign;
import org.springframework.shell.geom.Rectangle;
import org.springframework.shell.style.SpinnerSettings;
import org.springframework.shell.style.ThemeResolver;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

/**
* {@code ProgressView} is used to show a progress indicator.
Expand Down Expand Up @@ -100,26 +111,36 @@ public class ProgressView extends BoxView {
};

/**
* Construct view with {@code tickStart 0} and {@code tickEnd 100}.
* Construct view with {@code tickStart 0} and {@code tickEnd 100}. Uses default
* {@link ProgressViewItem}s.
*/
public ProgressView() {
this(0, 100);
}

/**
* Construct view with given bounds for {@code tickStart} and {@code tickEnd}.
* {@code tickStart} needs to be equal or more than zero. {@code tickEnd} needs
* to be higher than {@code tickStart}. Defines default items for {@code text},
* {@code spinner} and {@code percent}.
* Construct view with {@code tickStart 0} and {@code tickEnd 100}. Uses default
* {@link ProgressViewItem}s.
*
* @param tickStart the tick start
* @param tickEnd the tick end
* @param tickEnd the tick end
*/
public ProgressView(int tickStart, int tickEnd) {
this(tickStart, tickEnd, new ProgressViewItem[] { ProgressViewItem.ofText(), ProgressViewItem.ofSpinner(),
ProgressViewItem.ofPercent() });
}

/**
* Construct view with given {@link ProgressViewItem}s using {@code tickStart 0}
* and {@code tickEnd 100}.
*
* @param items the progress view items
*/
public ProgressView(ProgressViewItem... items) {
this(0, 100, new ProgressViewItem[] { ProgressViewItem.ofText(), ProgressViewItem.ofSpinner(),
ProgressViewItem.ofPercent() });
}

/**
* Construct view with given bounds for {@code tickStart} and {@code tickEnd}.
* {@code tickStart} needs to be equal or more than zero. {@code tickEnd} needs
Expand Down Expand Up @@ -208,20 +229,72 @@ public void setSpinner(Spinner spinner) {
this.spinner = spinner;
}

/**
* Starts a runtime logic. Call to already started progress has no effect.
*/
public void start() {
if (running) {
return;
}
running = true;
startTime = System.currentTimeMillis();
ProgressState state = getState();
scheduleTicks();
dispatch(ShellMessageBuilder.ofView(this, ProgressViewStartEvent.of(this, state)));
}

private Disposable.Composite disposables;
private final String TAG_KEY = "ProgressView";
private final String TAG_VALUE = UUID.randomUUID().toString();

private void scheduleTicks() {
if (disposables != null) {
return;
}
EventLoop eventLoop = getEventLoop();
if (eventLoop == null) {
return;
}
Flux<Message<?>> ticks = Flux.interval(Duration.ofMillis(50)).map(l -> {
Message<Long> message = MessageBuilder
.withPayload(l)
.setHeader(ShellMessageHeaderAccessor.EVENT_TYPE, EventLoop.Type.USER)
.setHeader(TAG_KEY, TAG_VALUE)
.build();
return message;
});
Disposable ticksDisposable = ticks.subscribe(m -> {
eventLoop.dispatch(m);
});
Disposable eventsDisposable = eventLoop.events()
.filter(m -> EventLoop.Type.USER.equals(StaticShellMessageHeaderAccessor.getEventType(m)))
.filter(m -> ObjectUtils.nullSafeEquals(m.getHeaders().get(TAG_KEY), TAG_VALUE))
.subscribe(m -> {
requestRedraw();
});
disposables = Disposables.composite();
disposables.add(eventsDisposable);
disposables.add(ticksDisposable);
}

private void requestRedraw() {
EventLoop eventLoop = getEventLoop();
if (eventLoop != null) {
eventLoop.dispatch(ShellMessageBuilder.ofRedraw());
}
}

/**
* Stops a runtime logic.
*/
public void stop() {
if (!running) {
return;
}
if (disposables != null) {
disposables.dispose();
disposables = null;
}
running = false;
ProgressState state = getState();
dispatch(ShellMessageBuilder.ofView(this, ProgressViewEndEvent.of(this, state)));
Expand Down Expand Up @@ -309,6 +382,7 @@ else if (value < tickStart) {
if (changed) {
ProgressState state = getState();
dispatch(ShellMessageBuilder.ofView(this, ProgressViewStateChangeEvent.of(this, state)));
requestRedraw();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.springframework.shell.component.view.control;

import java.time.Duration;
import java.util.List;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
Expand All @@ -27,6 +28,7 @@
import org.springframework.shell.component.view.control.ProgressView.ProgressViewItem;
import org.springframework.shell.component.view.control.ProgressView.ProgressViewStartEvent;
import org.springframework.shell.component.view.control.ProgressView.ProgressViewStateChangeEvent;
import org.springframework.test.util.ReflectionTestUtils;

import static org.assertj.core.api.Assertions.assertThat;

Expand All @@ -40,20 +42,23 @@ class Construction {
@Test
void constructDefault() {
view = new ProgressView();
assertThat(getViewItems(view)).hasSize(3);
assertThat(view.getState().tickValue()).isEqualTo(0);
}

@Test
void constructBounds() {
view = new ProgressView(10, 30);
assertThat(getViewItems(view)).hasSize(3);
assertThat(view.getState().tickValue()).isEqualTo(10);
assertThat(view.getState().tickStart()).isEqualTo(10);
assertThat(view.getState().tickEnd()).isEqualTo(30);
}

void xxx() {
@Test
void constructJustText() {
view = new ProgressView(10, 30, ProgressViewItem.ofText());
ProgressViewItem.ofText();
assertThat(getViewItems(view)).hasSize(1);
}

}
Expand Down Expand Up @@ -179,4 +184,10 @@ void hasBorder() {

}

private static List<ProgressViewItem> getViewItems(ProgressView view) {

@SuppressWarnings("unchecked")
List<ProgressViewItem> items = (List<ProgressViewItem>) ReflectionTestUtils.getField(view, "items");
return items;
}
}
Loading

0 comments on commit afb8bc7

Please sign in to comment.