Enhance TerminalUI eventing

- New methods in an eventloop and message builder
- Generic polish to use these methods
- Relates #761
This commit is contained in:
Janne Valkealahti
2023-10-07 14:56:54 +01:00
parent 9a5f189783
commit 8716774236
5 changed files with 138 additions and 37 deletions

View File

@@ -33,8 +33,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.shell.component.view.control.View;
import org.springframework.shell.component.view.control.ViewService;
import org.springframework.shell.component.view.event.DefaultEventLoop;
@@ -48,10 +46,8 @@ import org.springframework.shell.component.view.event.MouseHandler;
import org.springframework.shell.component.view.event.MouseHandler.MouseHandlerResult;
import org.springframework.shell.component.view.geom.Rectangle;
import org.springframework.shell.component.view.message.ShellMessageBuilder;
import org.springframework.shell.component.view.message.ShellMessageHeaderAccessor;
import org.springframework.shell.component.view.screen.DefaultScreen;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
@@ -236,40 +232,24 @@ public class TerminalUI implements ViewService {
}
private void dispatchWinch() {
Message<String> message = MessageBuilder.withPayload("WINCH")
.setHeader(ShellMessageHeaderAccessor.EVENT_TYPE, EventLoop.Type.SIGNAL)
.build();
eventLoop.dispatch(message);
eventLoop.dispatch(ShellMessageBuilder.ofSignal("WINCH"));
}
private void registerEventHandling() {
// XXX: think this again
eventLoop.onDestroy(eventLoop.events()
.filter(m -> {
return ObjectUtils.nullSafeEquals(m.getHeaders().get(ShellMessageHeaderAccessor.EVENT_TYPE), EventLoop.Type.SIGNAL);
})
.doOnNext(m -> {
eventLoop.onDestroy(eventLoop.signalEvents()
.subscribe(event -> {
display();
})
.subscribe());
}));
// XXX: think this again
eventLoop.onDestroy(eventLoop.events()
.filter(m -> {
return ObjectUtils.nullSafeEquals(m.getHeaders().get(ShellMessageHeaderAccessor.EVENT_TYPE), EventLoop.Type.SYSTEM);
})
.doOnNext(m -> {
Object payload = m.getPayload();
if (payload instanceof String s) {
if ("redraw".equals(s)) {
display();
}
else if ("int".equals(s)) {
this.terminal.raise(Signal.INT);
}
eventLoop.onDestroy(eventLoop.systemEvents()
.subscribe(event -> {
if ("redraw".equals(event)) {
display();
}
})
.subscribe());
else if ("int".equals(event)) {
this.terminal.raise(Signal.INT);
}
}));
eventLoop.onDestroy(eventLoop.keyEvents()
.doOnNext(m -> {
@@ -417,11 +397,8 @@ public class TerminalUI implements ViewService {
}
private void dispatchKeyEvent(KeyEvent event) {
Message<KeyEvent> message = MessageBuilder
.withPayload(event)
.setHeader(ShellMessageHeaderAccessor.EVENT_TYPE, EventLoop.Type.KEY)
.build();
eventLoop.dispatch(message);
log.debug("Dispatch key event: {}", event);
eventLoop.dispatch(ShellMessageBuilder.ofKeyEvent(event));
}
private void dispatchMouse(MouseEvent event) {

View File

@@ -150,6 +150,22 @@ public class DefaultEventLoop implements EventLoop {
.ofType(MouseEvent.class);
}
@Override
public Flux<String> systemEvents() {
return events()
.filter(m -> EventLoop.Type.SYSTEM.equals(StaticShellMessageHeaderAccessor.getEventType(m)))
.map(m -> m.getPayload())
.ofType(String.class);
}
@Override
public Flux<String> signalEvents() {
return events()
.filter(m -> EventLoop.Type.SIGNAL.equals(StaticShellMessageHeaderAccessor.getEventType(m)))
.map(m -> m.getPayload())
.ofType(String.class);
}
@Override
public <T extends ViewEvent> Flux<T> viewEvents(Class<T> clazz) {
return events(EventLoop.Type.VIEW, clazz);

View File

@@ -61,6 +61,22 @@ public interface EventLoop {
*/
Flux<MouseEvent> mouseEvents();
/**
* Specialisation of {@link #events()} which returns type safe
* {code system} events.
*
* @return the system events from an event loop
*/
Flux<String> systemEvents();
/**
* Specialisation of {@link #events()} which returns type safe
* {code signal} events.
*
* @return the signal events from an event loop
*/
Flux<String> signalEvents();
/**
* Specialisation of {@link #events()} which returns type safe {@link ViewEvent}s.
*

View File

@@ -20,6 +20,7 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.shell.component.view.control.View;
import org.springframework.shell.component.view.event.EventLoop;
import org.springframework.shell.component.view.event.KeyEvent;
import org.springframework.shell.component.view.event.MouseEvent;
import org.springframework.util.Assert;
@@ -83,6 +84,30 @@ public final class ShellMessageBuilder<T> {
.build();
}
/**
* Create a {@code signal} message.
*
* @return a signal message
*/
public static Message<String> ofSignal(String signal) {
return new ShellMessageBuilder<>(signal, null)
.setEventType(EventLoop.Type.SIGNAL)
.setPriority(0)
.build();
}
/**
* Create a message of a {@link KeyEvent}.
*
* @param event the event type
* @return a message with {@link KeyEvent} as a payload
*/
public static Message<KeyEvent> ofKeyEvent(KeyEvent event) {
return new ShellMessageBuilder<>(event, null)
.setEventType(EventLoop.Type.KEY)
.build();
}
/**
* Create a message of a {@link MouseEvent}.
*

View File

@@ -17,6 +17,7 @@ package org.springframework.shell.component.view.event;
import java.time.Duration;
import java.util.Arrays;
import java.util.EnumSet;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
@@ -190,4 +191,70 @@ class DefaultEventLoopTests {
}
}
@Test
void keyEvents() {
initDefault();
KeyEvent event = KeyEvent.of(KeyEvent.Key.a);
Message<KeyEvent> message = ShellMessageBuilder.ofKeyEvent(event);
StepVerifier verifier1 = StepVerifier.create(loop.keyEvents())
.expectNextCount(1)
.thenCancel()
.verifyLater();
loop.dispatch(message);
verifier1.verify(Duration.ofSeconds(1));
}
@Test
void mouseEvents() {
initDefault();
org.jline.terminal.MouseEvent jlineMouseEvent = new org.jline.terminal.MouseEvent(
org.jline.terminal.MouseEvent.Type.Released,
org.jline.terminal.MouseEvent.Button.Button1,
EnumSet.noneOf(org.jline.terminal.MouseEvent.Modifier.class), 0, 0);
MouseEvent event = MouseEvent.of(jlineMouseEvent);
Message<MouseEvent> message = ShellMessageBuilder.ofMouseEvent(event);
StepVerifier verifier1 = StepVerifier.create(loop.mouseEvents())
.expectNextCount(1)
.thenCancel()
.verifyLater();
loop.dispatch(message);
verifier1.verify(Duration.ofSeconds(1));
}
@Test
void systemEvents() {
initDefault();
Message<String> message = ShellMessageBuilder.ofRedraw();
StepVerifier verifier1 = StepVerifier.create(loop.systemEvents())
.expectNextCount(1)
.thenCancel()
.verifyLater();
loop.dispatch(message);
verifier1.verify(Duration.ofSeconds(1));
}
@Test
void signalEvents() {
initDefault();
Message<String> message = ShellMessageBuilder.ofSignal("WINCH");
StepVerifier verifier1 = StepVerifier.create(loop.signalEvents())
.expectNextCount(1)
.thenCancel()
.verifyLater();
loop.dispatch(message);
verifier1.verify(Duration.ofSeconds(1));
}
}