Skip to content

Commit

Permalink
Make ViewComponent api better for expected use
Browse files Browse the repository at this point in the history
- Exposing eventloop if user need to interact with it
- Add api/feature to set used view rect expand to
  full terminal width
- Add plain ProgressView samples
- Relates #995
- Fixes #997
  • Loading branch information
jvalkeal committed Feb 2, 2024
1 parent 10d9940 commit 9446afe
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -15,13 +15,15 @@
*/
package org.springframework.shell.component;

import org.jline.terminal.Size;
import org.jline.terminal.Terminal;

import org.springframework.shell.component.message.ShellMessageBuilder;
import org.springframework.shell.component.view.TerminalUI;
import org.springframework.shell.component.view.control.View;
import org.springframework.shell.component.view.control.ViewDoneEvent;
import org.springframework.shell.component.view.event.EventLoop;
import org.springframework.shell.geom.Rectangle;
import org.springframework.util.Assert;

/**
Expand All @@ -34,37 +36,65 @@ public class ViewComponent {
private final Terminal terminal;
private final View view;
private EventLoop eventLoop;
private TerminalUI ui;
private boolean useTerminalWidth = true;

/**
* Construct view component with a given {@link Terminal} and {@link View}.
*
* @param terminal the terminal
* @param view the main view
*/
public ViewComponent(Terminal terminal, View view) {
Assert.notNull(terminal, "terminal must be set");
Assert.notNull(view, "view must be set");
this.terminal = terminal;
this.view = view;
this.ui = new TerminalUI(terminal);
this.eventLoop = ui.getEventLoop();
}

/**
* Run a view execution loop.
*/
public void run() {
TerminalUI ui = new TerminalUI(terminal);
eventLoop = ui.getEventLoop();
eventLoop.onDestroy(eventLoop.viewEvents(ViewDoneEvent.class, view)
.subscribe(event -> {
exit();
}
));
view.setEventLoop(eventLoop);
Size terminalSize = terminal.getSize();
Rectangle rect = view.getRect();
if (useTerminalWidth) {
view.setRect(rect.x(), rect.y(), terminalSize.getColumns() - rect.x(), rect.height());
}
ui.setRoot(view, false);
ui.run();
}

/**
* Sets if full terminal width should be used for a view. Defaults to {@code true}.
*
* @param useTerminalWidth the use terminal width flag
*/
public void setUseTerminalWidth(boolean useTerminalWidth) {
this.useTerminalWidth = useTerminalWidth;
}

/**
* Gets an {@link EventLoop} associated with this view component.
*
* @return event loop with this view component
*/
public EventLoop getEventLoop() {
return eventLoop;
}

/**
* Request exit from an execution loop.
*/
public void exit() {
if (eventLoop == null) {
return;
}
eventLoop.dispatch(ShellMessageBuilder.ofInterrupt());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -15,11 +15,23 @@
*/
package org.springframework.shell.samples.standard;

import java.time.Duration;

import reactor.core.publisher.Flux;

import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.shell.command.annotation.Command;
import org.springframework.shell.component.ViewComponent;
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.TerminalUI;
import org.springframework.shell.component.view.control.BoxView;
import org.springframework.shell.component.view.control.InputView;
import org.springframework.shell.component.view.control.ProgressView;
import org.springframework.shell.component.view.control.ProgressView.ProgressViewItem;
import org.springframework.shell.component.view.event.EventLoop;
import org.springframework.shell.geom.HorizontalAlign;
import org.springframework.shell.geom.VerticalAlign;
import org.springframework.shell.standard.AbstractShellComponent;
Expand Down Expand Up @@ -83,4 +95,70 @@ public String stringInput() {
return String.format("Input was '%s'", input);
}

@Command(command = "componentui progress1")
public void progress1() {
ProgressView view = new ProgressView();
view.setDescription("name");
view.setRect(0, 0, 20, 1);


ViewComponent component = new ViewComponent(getTerminal(), view);
EventLoop eventLoop = component.getEventLoop();

Flux<Message<?>> ticks = Flux.interval(Duration.ofMillis(100)).map(l -> {
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();
}

@Command(command = "componentui progress2")
public void progress2() {
ProgressView view = new ProgressView(0, 100, ProgressViewItem.ofText(10, HorizontalAlign.LEFT),
ProgressViewItem.ofSpinner(3, HorizontalAlign.LEFT),
ProgressViewItem.ofPercent(0, HorizontalAlign.RIGHT));
view.setDescription("name");
view.setRect(0, 0, 20, 1);


ViewComponent component = new ViewComponent(getTerminal(), view);
EventLoop eventLoop = component.getEventLoop();

Flux<Message<?>> ticks = Flux.interval(Duration.ofMillis(100)).map(l -> {
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();
}

}

0 comments on commit 9446afe

Please sign in to comment.