Skip to content

Commit

Permalink
Non-fullscreen view
Browse files Browse the repository at this point in the history
- ViewComponent can take view and drive it as non-fullscreen
- ViewDoneEvent which InputView now uses
- ComponentUiCommands is a sample where we add ideas for
  views in flow components
- Allow View to set eventloop
- Relates #850
  • Loading branch information
jvalkeal committed Aug 30, 2023
1 parent ba691de commit a3ca69b
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright 2023 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.component;

import org.jline.terminal.Terminal;

import org.springframework.messaging.Message;
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.component.view.message.ShellMessageBuilder;
import org.springframework.util.Assert;

/**
* Handles view execution in a non-fullscreen setup.
*
* @author Janne Valkealahti
*/
public class ViewComponent {

private final Terminal terminal;
private final View view;
private EventLoop eventLoop;

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;
}

/**
* 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);
ui.setRoot(view, false);
ui.run();
}

/**
* Request exit from an execution loop.
*/
public void exit() {
if (eventLoop == null) {
return;
}
Message<String> msg = ShellMessageBuilder.withPayload("int")
.setEventType(EventLoop.Type.SYSTEM)
.setPriority(0)
.build();
eventLoop.dispatch(msg);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.springframework.shell.component.view.event.KeyHandler;
import org.springframework.shell.component.view.geom.Position;
import org.springframework.shell.component.view.geom.Rectangle;
import org.springframework.shell.component.view.message.ShellMessageBuilder;
import org.springframework.shell.component.view.screen.Screen;

/**
Expand All @@ -41,6 +42,7 @@ protected void initInternal() {
registerKeyBinding(Key.CursorRight, event -> right());
registerKeyBinding(Key.Delete, () -> delete());
registerKeyBinding(Key.Backspace, () -> backspace());
registerKeyBinding(Key.Enter, () -> done());
}

@Override
Expand Down Expand Up @@ -117,4 +119,9 @@ private void left() {
private void right() {
moveCursor(1);
}

private void done() {
dispatch(ShellMessageBuilder.ofView(this, ViewDoneEvent.of(this)));
}

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

import org.springframework.lang.Nullable;
import org.springframework.shell.component.view.event.EventLoop;
import org.springframework.shell.component.view.event.KeyHandler;
import org.springframework.shell.component.view.event.MouseHandler;

Expand Down Expand Up @@ -79,4 +80,11 @@ public interface View extends Control {
@Nullable
KeyHandler getHotKeyHandler();

/**
* Sets an {@link EventLoop}.
*
* @param eventLoop the event loop
*/
void setEventLoop(@Nullable EventLoop eventLoop);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2023 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.component.view.control;

/**
* Spesialisation of a {@link ViewEvent} indicating that a {@link View} is done.
*
* @author Janne Valkealahti
*/
public interface ViewDoneEvent extends ViewEvent {

/**
* Create a generic event with empty view args.
*
* @param view the view
* @return a generic view done event
*/
static ViewDoneEvent of(View view) {
return new GenericViewDoneEvent(view, ViewEventArgs.EMPTY);
}

record GenericViewDoneEvent(View view, ViewEventArgs args) implements ViewDoneEvent {
}

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

import java.time.Duration;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;

import org.springframework.shell.component.view.event.KeyEvent;
import org.springframework.shell.component.view.event.KeyEvent.Key;
import org.springframework.shell.component.view.event.KeyHandler.KeyHandlerResult;
import org.springframework.test.util.ReflectionTestUtils;

import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -30,11 +35,11 @@ class InputViewTests extends AbstractViewTests {
private static final String CURSOR_INDEX_FIELD = "cursorIndex";
private static final String CURSOR_POSITION_METHOD = "cursorPosition";

InputView view;

@Nested
class Input {

InputView view;

@BeforeEach
void setup() {
view = new InputView();
Expand Down Expand Up @@ -82,8 +87,6 @@ void shouldShowUnicodeEmoji() {
@Nested
class CursorPositions {

InputView view;

@BeforeEach
void setup() {
view = new InputView();
Expand Down Expand Up @@ -128,8 +131,6 @@ void shouldMoveWithInputKeysWide() {
@Nested
class MoveAndDeletions {

InputView view;

@BeforeEach
void setup() {
view = new InputView();
Expand Down Expand Up @@ -157,8 +158,6 @@ void deleteFromLastPosition() {
@Nested
class MoveAndMods {

InputView view;

@BeforeEach
void setup() {
view = new InputView();
Expand All @@ -183,4 +182,33 @@ void shouldAddToCursorPosition() {

}

@Nested
class Events {

@BeforeEach
void setup() {
view = new InputView();
view.setRect(0, 0, 10, 1);
configure(view);
}

@Test
void handlesKeyEnter() {
Flux<ViewDoneEvent> actions = eventLoop
.viewEvents(ViewDoneEvent.class);
StepVerifier verifier = StepVerifier.create(actions)
.expectNextCount(1)
.thenCancel()
.verifyLater();

KeyHandlerResult result = handleKey(view, KeyEvent.Key.Enter);

assertThat(result).isNotNull().satisfies(r -> {
assertThat(r.consumed()).isTrue();
});
verifier.verify(Duration.ofSeconds(1));
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2023 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.samples.standard;

import org.springframework.shell.command.annotation.Command;
import org.springframework.shell.component.ViewComponent;
import org.springframework.shell.component.view.control.InputView;
import org.springframework.shell.standard.AbstractShellComponent;

@Command
public class ComponentUiCommands extends AbstractShellComponent {

@Command(command = "componentui string")
public String stringInput() {
InputView view = new InputView();
view.setRect(0, 0, 10, 1);
ViewComponent component = new ViewComponent(getTerminal(), view);
component.run();
String input = view.getInputText();
return String.format("Input was '%s'", input);
}

}

0 comments on commit a3ca69b

Please sign in to comment.