TerminalUI can define rootView rect

- Add functions to calculate Rectangle for fullscreen/nonfullscreen modes.
- Fixes for rendering so that nonfullscreen works better with views.
- Add some basic samples
- isEmpty method for Rectangle
- Fixes #872
This commit is contained in:
Janne Valkealahti
2023-09-22 08:57:16 +01:00
parent a79ecbc069
commit 585125d2e3
4 changed files with 154 additions and 9 deletions

View File

@@ -18,6 +18,7 @@ package org.springframework.shell.component.view;
import java.io.IOError;
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;
import org.jline.keymap.BindingReader;
import org.jline.keymap.KeyMap;
@@ -148,36 +149,75 @@ public class TerminalUI implements ViewService {
}
}
private void render(int rows, int columns) {
private void render(Rectangle rect) {
if (rootView != null) {
rootView.setRect(0, 0, columns, rows);
rootView.setRect(rect.x(), rect.y(), rect.width(), rect.height());
rootView.draw(virtualDisplay);
}
if (modalView != null) {
modalView.setLayer(1);
modalView.setRect(0, 0, columns, rows);
modalView.setRect(rect.x(), rect.y(), rect.width(), rect.height());
modalView.draw(virtualDisplay);
}
}
private BiFunction<Terminal, View, Rectangle> fullScreenViewRect = (terminal, view) -> {
Size s = terminal.getSize();
return new Rectangle(0, 0, s.getColumns(), s.getRows());
};
private BiFunction<Terminal, View, Rectangle> nonfullScreenViewRect = (terminal, view) -> {
Size s = terminal.getSize();
Rectangle rect = view.getRect();
if (!rect.isEmpty()) {
return rect;
}
return new Rectangle(0, 0, s.getColumns(), 5);
};
/**
* Sets a view rect function for full screen mode. Default behaviour uses {@link Rectangle}
* area equal to terminal size.
*
* @param fullScreenViewRect the view rect function
*/
public void setFullScreenViewRect(BiFunction<Terminal, View, Rectangle> fullScreenViewRect) {
Assert.notNull(fullScreenViewRect, "view rect function must be set");
this.fullScreenViewRect = fullScreenViewRect;
}
/**
* Sets a view rect function for full screen mode. Default behaviour uses {@link Rectangle}
* from {@code rootView} if it's not empty, otherwise uses zero based area with
* width matching terminal columns and height matching 5.
*
* @param nonfullScreenViewRect the view rect function
*/
public void setNonfullScreenViewRect(BiFunction<Terminal, View, Rectangle> nonfullScreenViewRect) {
Assert.notNull(nonfullScreenViewRect, "view rect function must be set");
this.nonfullScreenViewRect = nonfullScreenViewRect;
}
private void display() {
log.trace("display()");
log.trace("display() start");
size.copy(terminal.getSize());
if (fullScreen) {
display.clear();
display.resize(size.getRows(), size.getColumns());
display.reset();
rootView.setRect(0, 0, size.getColumns(), size.getRows());
display.resize(size.getRows(), size.getColumns());
Rectangle rect = fullScreenViewRect.apply(terminal, rootView);
rootView.setRect(rect.x(), rect.y(), rect.width(), rect.height());
virtualDisplay.resize(size.getRows(), size.getColumns());
virtualDisplay.setShowCursor(false);
render(size.getRows(), size.getColumns());
render(rect);
}
else {
Rectangle rect = nonfullScreenViewRect.apply(terminal, rootView);
display.reset();
display.resize(size.getRows(), size.getColumns());
Rectangle rect = rootView.getRect();
virtualDisplay.resize(rect.height(), rect.width());
virtualDisplay.setShowCursor(false);
render(rect.height(), rect.width());
render(rect);
}
List<AttributedString> newLines = virtualDisplay.getScreenLines();
@@ -192,6 +232,7 @@ public class TerminalUI implements ViewService {
terminal.puts(Capability.cursor_invisible);
}
display.update(newLines, targetCursorPos);
log.trace("display() end");
}
private void dispatchWinch() {

View File

@@ -36,4 +36,15 @@ public record Rectangle(int x, int y, int width, int height) {
h += y;
return ((w < x || w > X) && (h < y || h > Y));
}
/**
* Determines whether the {@code Rectangle} is empty. When the {@code Rectangle}
* is empty, it encloses no area.
*
* @return {@code true} if the {@code Rectangle} is empty; {@code false}
* otherwise.
*/
public boolean isEmpty() {
return width <= 0 || height <= 0;
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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.geom;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class RectangleTests {
private Rectangle rect;
@Test
void isEmpty() {
rect = new Rectangle(0, 0, 0, 0);
assertThat(rect.isEmpty()).isTrue();
rect = new Rectangle(1, 0, 0, 0);
assertThat(rect.isEmpty()).isTrue();
rect = new Rectangle(0, 1, 0, 0);
assertThat(rect.isEmpty()).isTrue();
rect = new Rectangle(1, 1, 0, 0);
assertThat(rect.isEmpty()).isTrue();
rect = new Rectangle(0, 0, 1, 0);
assertThat(rect.isEmpty()).isTrue();
rect = new Rectangle(0, 0, 0, 1);
assertThat(rect.isEmpty()).isTrue();
rect = new Rectangle(1, 1, 1, 1);
assertThat(rect.isEmpty()).isFalse();
}
}

View File

@@ -17,12 +17,62 @@ 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.TerminalUI;
import org.springframework.shell.component.view.control.BoxView;
import org.springframework.shell.component.view.control.InputView;
import org.springframework.shell.component.view.geom.HorizontalAlign;
import org.springframework.shell.component.view.geom.VerticalAlign;
import org.springframework.shell.standard.AbstractShellComponent;
@Command
public class ComponentUiCommands extends AbstractShellComponent {
@Command(command = "componentui tui1")
public void tui1() {
TerminalUI ui = new TerminalUI(getTerminal());
BoxView view = new BoxView();
view.setShowBorder(true);
view.setDrawFunction((screen, rect) -> {
screen.writerBuilder()
.build()
.text("Hello World", rect, HorizontalAlign.CENTER, VerticalAlign.CENTER);
return rect;
});
ui.setRoot(view, true);
ui.run();
}
@Command(command = "componentui tui2")
public void tui2() {
TerminalUI ui = new TerminalUI(getTerminal());
BoxView view = new BoxView();
view.setRect(0, 0, 40, 5);
view.setShowBorder(true);
view.setDrawFunction((screen, rect) -> {
screen.writerBuilder()
.build()
.text("Hello World", rect, HorizontalAlign.CENTER, VerticalAlign.CENTER);
return rect;
});
ui.setRoot(view, false);
ui.run();
}
@Command(command = "componentui tui3")
public void tui3() {
TerminalUI ui = new TerminalUI(getTerminal());
BoxView view = new BoxView();
view.setShowBorder(true);
view.setDrawFunction((screen, rect) -> {
screen.writerBuilder()
.build()
.text("Hello World", rect, HorizontalAlign.CENTER, VerticalAlign.CENTER);
return rect;
});
ui.setRoot(view, false);
ui.run();
}
@Command(command = "componentui string")
public String stringInput() {
InputView view = new InputView();