Make ViewComponent api better for expected use
- 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
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.
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.shell.component;
|
||||
|
||||
import org.jline.terminal.Size;
|
||||
import org.jline.terminal.Terminal;
|
||||
|
||||
import org.springframework.shell.component.message.ShellMessageBuilder;
|
||||
@@ -22,6 +23,7 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
@@ -83,4 +95,70 @@ public class ComponentUiCommands extends AbstractShellComponent {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user