Added scroll up/down for windows
This commit is contained in:
parent
aca4c493b0
commit
6f367aec7e
@ -31,14 +31,22 @@
|
|||||||
package dorkbox.console.output;
|
package dorkbox.console.output;
|
||||||
|
|
||||||
import static dorkbox.console.util.windows.Kernel32.ASSERT;
|
import static dorkbox.console.util.windows.Kernel32.ASSERT;
|
||||||
|
import static dorkbox.console.util.windows.Kernel32.FillConsoleOutputAttribute;
|
||||||
|
import static dorkbox.console.util.windows.Kernel32.FillConsoleOutputCharacterW;
|
||||||
|
import static dorkbox.console.util.windows.Kernel32.GetConsoleScreenBufferInfo;
|
||||||
|
import static dorkbox.console.util.windows.Kernel32.SetConsoleCursorPosition;
|
||||||
|
import static dorkbox.console.util.windows.Kernel32.SetConsoleTextAttribute;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import com.sun.jna.ptr.IntByReference;
|
||||||
|
|
||||||
import dorkbox.console.util.windows.CONSOLE_SCREEN_BUFFER_INFO;
|
import dorkbox.console.util.windows.CONSOLE_SCREEN_BUFFER_INFO;
|
||||||
import dorkbox.console.util.windows.COORD;
|
import dorkbox.console.util.windows.COORD;
|
||||||
import dorkbox.console.util.windows.HANDLE;
|
import dorkbox.console.util.windows.HANDLE;
|
||||||
import dorkbox.console.util.windows.Kernel32;
|
import dorkbox.console.util.windows.Kernel32;
|
||||||
|
import dorkbox.console.util.windows.SMALL_RECT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Windows ANSI escape processor, uses JNA direct-mapping to access native platform API's to change the console attributes.
|
* A Windows ANSI escape processor, uses JNA direct-mapping to access native platform API's to change the console attributes.
|
||||||
@ -77,7 +85,7 @@ final class WindowsAnsiOutputStream extends AnsiOutputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final HANDLE console;
|
private final HANDLE console;
|
||||||
private final CONSOLE_SCREEN_BUFFER_INFO originalInfo = new CONSOLE_SCREEN_BUFFER_INFO();
|
private CONSOLE_SCREEN_BUFFER_INFO originalInfo = new CONSOLE_SCREEN_BUFFER_INFO();
|
||||||
private volatile CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO();
|
private volatile CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO();
|
||||||
|
|
||||||
private volatile boolean negative;
|
private volatile boolean negative;
|
||||||
@ -101,13 +109,13 @@ final class WindowsAnsiOutputStream extends AnsiOutputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
out.flush();
|
out.flush();
|
||||||
ASSERT(Kernel32.GetConsoleScreenBufferInfo(console, originalInfo), "Could not get the screen info");
|
ASSERT(GetConsoleScreenBufferInfo(console, originalInfo), "Could not get the screen info");
|
||||||
}
|
}
|
||||||
|
|
||||||
private
|
private
|
||||||
void getConsoleInfo() throws IOException {
|
void getConsoleInfo() throws IOException {
|
||||||
out.flush();
|
out.flush();
|
||||||
ASSERT(Kernel32.GetConsoleScreenBufferInfo(console, info), "Could not get the screen info:");
|
ASSERT(GetConsoleScreenBufferInfo(console, info), "Could not get the screen info:");
|
||||||
}
|
}
|
||||||
|
|
||||||
private
|
private
|
||||||
@ -124,12 +132,12 @@ final class WindowsAnsiOutputStream extends AnsiOutputStream {
|
|||||||
attributes = (short) (attributes & 0xFF00 | fg | bg);
|
attributes = (short) (attributes & 0xFF00 | fg | bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
ASSERT(Kernel32.SetConsoleTextAttribute(console, attributes), "Could not set text attributes");
|
ASSERT(SetConsoleTextAttribute(console, attributes), "Could not set text attributes");
|
||||||
}
|
}
|
||||||
|
|
||||||
private
|
private
|
||||||
void applyCursorPosition() throws IOException {
|
void applyCursorPosition() throws IOException {
|
||||||
ASSERT(Kernel32.SetConsoleCursorPosition(console, info.cursorPosition.asValue()), "Could not set cursor position");
|
ASSERT(SetConsoleCursorPosition(console, info.cursorPosition.asValue()), "Could not set cursor position");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -164,8 +172,8 @@ final class WindowsAnsiOutputStream extends AnsiOutputStream {
|
|||||||
topLeft.y = info.window.top;
|
topLeft.y = info.window.top;
|
||||||
int screenLength = info.window.height() * info.size.x;
|
int screenLength = info.window.height() * info.size.x;
|
||||||
|
|
||||||
ASSERT(Kernel32.FillConsoleOutputAttribute(console, originalInfo.attributes, screenLength, topLeft.asValue(), written), "Could not fill console");
|
ASSERT(FillConsoleOutputAttribute(console, originalInfo.attributes, screenLength, topLeft.asValue(), written), "Could not fill console");
|
||||||
ASSERT(Kernel32.FillConsoleOutputCharacterW(console, ' ', screenLength, topLeft.asValue(), written), "Could not fill console");
|
ASSERT(FillConsoleOutputCharacterW(console, ' ', screenLength, topLeft.asValue(), written), "Could not fill console");
|
||||||
break;
|
break;
|
||||||
case ERASE_TO_BEGINNING:
|
case ERASE_TO_BEGINNING:
|
||||||
COORD topLeft2 = new COORD();
|
COORD topLeft2 = new COORD();
|
||||||
@ -173,14 +181,14 @@ final class WindowsAnsiOutputStream extends AnsiOutputStream {
|
|||||||
topLeft2.y = info.window.top;
|
topLeft2.y = info.window.top;
|
||||||
int lengthToCursor = (info.cursorPosition.y - info.window.top) * info.size.x + info.cursorPosition.x;
|
int lengthToCursor = (info.cursorPosition.y - info.window.top) * info.size.x + info.cursorPosition.x;
|
||||||
|
|
||||||
ASSERT(Kernel32.FillConsoleOutputAttribute(console, originalInfo.attributes, lengthToCursor, topLeft2.asValue(), written), "Could not fill console");
|
ASSERT(FillConsoleOutputAttribute(console, originalInfo.attributes, lengthToCursor, topLeft2.asValue(), written), "Could not fill console");
|
||||||
ASSERT(Kernel32.FillConsoleOutputCharacterW(console, ' ', lengthToCursor, topLeft2.asValue(), written), "Could not fill console");
|
ASSERT(FillConsoleOutputCharacterW(console, ' ', lengthToCursor, topLeft2.asValue(), written), "Could not fill console");
|
||||||
break;
|
break;
|
||||||
case ERASE_TO_END:
|
case ERASE_TO_END:
|
||||||
int lengthToEnd = (info.window.bottom - info.cursorPosition.y) * info.size.x + info.size.x - info.cursorPosition.x;
|
int lengthToEnd = (info.window.bottom - info.cursorPosition.y) * info.size.x + info.size.x - info.cursorPosition.x;
|
||||||
|
|
||||||
ASSERT(Kernel32.FillConsoleOutputAttribute(console, originalInfo.attributes, lengthToEnd, info.cursorPosition.asValue(), written), "Could not fill console");
|
ASSERT(FillConsoleOutputAttribute(console, originalInfo.attributes, lengthToEnd, info.cursorPosition.asValue(), written), "Could not fill console");
|
||||||
ASSERT(Kernel32.FillConsoleOutputCharacterW(console, ' ', lengthToEnd, info.cursorPosition.asValue(), written), "Could not fill console");
|
ASSERT(FillConsoleOutputCharacterW(console, ' ', lengthToEnd, info.cursorPosition.asValue(), written), "Could not fill console");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,21 +202,21 @@ final class WindowsAnsiOutputStream extends AnsiOutputStream {
|
|||||||
COORD currentRow = info.cursorPosition.asValue();
|
COORD currentRow = info.cursorPosition.asValue();
|
||||||
currentRow.x = (short) 0;
|
currentRow.x = (short) 0;
|
||||||
|
|
||||||
ASSERT(Kernel32.FillConsoleOutputAttribute(console, originalInfo.attributes, info.size.x, currentRow.asValue(), written), "Could not fill console");
|
ASSERT(FillConsoleOutputAttribute(console, originalInfo.attributes, info.size.x, currentRow.asValue(), written), "Could not fill console");
|
||||||
ASSERT(Kernel32.FillConsoleOutputCharacterW(console, ' ', info.size.x, currentRow.asValue(), written), "Could not fill console");
|
ASSERT(FillConsoleOutputCharacterW(console, ' ', info.size.x, currentRow.asValue(), written), "Could not fill console");
|
||||||
break;
|
break;
|
||||||
case ERASE_TO_BEGINNING:
|
case ERASE_TO_BEGINNING:
|
||||||
COORD leftColCurrRow2 = info.cursorPosition.asValue();
|
COORD leftColCurrRow2 = info.cursorPosition.asValue();
|
||||||
leftColCurrRow2.x = (short) 0;
|
leftColCurrRow2.x = (short) 0;
|
||||||
|
|
||||||
ASSERT(Kernel32.FillConsoleOutputAttribute(console, originalInfo.attributes, info.cursorPosition.x, leftColCurrRow2.asValue(), written), "Could not fill console");
|
ASSERT(FillConsoleOutputAttribute(console, originalInfo.attributes, info.cursorPosition.x, leftColCurrRow2.asValue(), written), "Could not fill console");
|
||||||
ASSERT(Kernel32.FillConsoleOutputCharacterW(console, ' ', info.cursorPosition.x, leftColCurrRow2.asValue(), written), "Could not fill console");
|
ASSERT(FillConsoleOutputCharacterW(console, ' ', info.cursorPosition.x, leftColCurrRow2.asValue(), written), "Could not fill console");
|
||||||
break;
|
break;
|
||||||
case ERASE_TO_END:
|
case ERASE_TO_END:
|
||||||
int lengthToLastCol = info.size.x - info.cursorPosition.x;
|
int lengthToLastCol = info.size.x - info.cursorPosition.x;
|
||||||
|
|
||||||
ASSERT(Kernel32.FillConsoleOutputAttribute(console, originalInfo.attributes, lengthToLastCol, info.cursorPosition.asValue(), written), "Could not fill console");
|
ASSERT(FillConsoleOutputAttribute(console, originalInfo.attributes, lengthToLastCol, info.cursorPosition.asValue(), written), "Could not fill console");
|
||||||
ASSERT(Kernel32.FillConsoleOutputCharacterW(console, ' ', lengthToLastCol, info.cursorPosition.asValue(), written), "Could not fill console");
|
ASSERT(FillConsoleOutputCharacterW(console, ' ', lengthToLastCol, info.cursorPosition.asValue(), written), "Could not fill console");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,20 +307,30 @@ final class WindowsAnsiOutputStream extends AnsiOutputStream {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected
|
protected
|
||||||
void processScrollDown(final int optionInt) throws IOException {
|
void processScrollDown(final int count) throws IOException {
|
||||||
|
scroll((short) -count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected
|
protected
|
||||||
void processScrollUp(final int optionInt) throws IOException {
|
void processScrollUp(final int count) throws IOException {
|
||||||
|
scroll((short) count);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected
|
protected
|
||||||
void processCursorUpLine(final int count) throws IOException {
|
void processCursorUpLine(final int count) throws IOException {
|
||||||
|
getConsoleInfo();
|
||||||
|
info.cursorPosition.y = (short) Math.max(info.window.top, info.cursorPosition.y - count);
|
||||||
|
info.cursorPosition.x = 0;
|
||||||
|
applyCursorPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected
|
protected
|
||||||
void processCursorDownLine(final int count) throws IOException {
|
void processCursorDownLine(final int count) throws IOException {
|
||||||
|
getConsoleInfo();
|
||||||
|
info.cursorPosition.y = (short) Math.max(info.window.top, info.cursorPosition.y + count);
|
||||||
|
info.cursorPosition.x = 0;
|
||||||
|
applyCursorPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -364,6 +382,45 @@ final class WindowsAnsiOutputStream extends AnsiOutputStream {
|
|||||||
applyCursorPosition();
|
applyCursorPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param rowsToScroll negative to go down, positive to go up.
|
||||||
|
*
|
||||||
|
* Scroll up and new lines are added at the bottom, scroll down and new lines are added at the
|
||||||
|
* top (per the definition).
|
||||||
|
*
|
||||||
|
* Windows doesn't EXACTLY do this, since it will use whatever content is still on the buffer
|
||||||
|
* and show THAT instead of blank lines. If the content is moved enough so that it runs OFF the
|
||||||
|
* buffer, blank lines will be shown.
|
||||||
|
*/
|
||||||
|
private void scroll(short rowsToScroll) throws IOException {
|
||||||
|
if (rowsToScroll == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current screen buffer window position.
|
||||||
|
getConsoleInfo();
|
||||||
|
|
||||||
|
SMALL_RECT.ByReference scrollRect = new SMALL_RECT.ByReference();
|
||||||
|
COORD.ByValue coordDest = new COORD.ByValue();
|
||||||
|
|
||||||
|
// the content that will be scrolled
|
||||||
|
scrollRect.top = (short) (0);
|
||||||
|
scrollRect.bottom = (short) (Short.MAX_VALUE);
|
||||||
|
scrollRect.left = 0;
|
||||||
|
scrollRect.right = (short) (info.size.x - 1);
|
||||||
|
|
||||||
|
// The destination for the scroll rectangle is xxx row up/down.
|
||||||
|
coordDest.x = 0;
|
||||||
|
coordDest.y = (short) (-rowsToScroll);
|
||||||
|
|
||||||
|
// fill the space with whatever color was already there with spaces
|
||||||
|
IntByReference attribs = new IntByReference();
|
||||||
|
attribs.setValue(info.attributes);
|
||||||
|
|
||||||
|
// The clipping rectangle is the same as the scrolling rectangle, so we pass NULL
|
||||||
|
ASSERT(Kernel32.ScrollConsoleScreenBufferW(console, scrollRect, null, coordDest, attribs), "Could not scroll console");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
super.close();
|
super.close();
|
||||||
|
@ -85,7 +85,7 @@ public class Kernel32 {
|
|||||||
* https://msdn.microsoft.com/en-us/library/windows/desktop/ms679351(v=vs.85).aspx
|
* https://msdn.microsoft.com/en-us/library/windows/desktop/ms679351(v=vs.85).aspx
|
||||||
*/
|
*/
|
||||||
public static native
|
public static native
|
||||||
int FormatMessageA(int flags, Pointer source, int messageId, int languageId, byte[] buffer, int size, long[] args);
|
int FormatMessageW(int flags, Pointer source, int messageId, int languageId, byte[] buffer, int size, long[] args);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -95,11 +95,19 @@ public class Kernel32 {
|
|||||||
int GetConsoleScreenBufferInfo(HANDLE consoleOutput, CONSOLE_SCREEN_BUFFER_INFO consoleScreenBufferInfo);
|
int GetConsoleScreenBufferInfo(HANDLE consoleOutput, CONSOLE_SCREEN_BUFFER_INFO consoleScreenBufferInfo);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://msdn.microsoft.com/en-us/library/ms686025%28VS.85%29.aspx
|
* https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx
|
||||||
*/
|
*/
|
||||||
public static native
|
public static native
|
||||||
int SetConsoleCursorPosition(HANDLE consoleOutput, COORD.ByValue cursorPosition);
|
int SetConsoleCursorPosition(HANDLE consoleOutput, COORD.ByValue cursorPosition);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://msdn.microsoft.com/en-us/library/windows/desktop/ms685107(v=vs.85).aspx
|
||||||
|
*/
|
||||||
|
public static native
|
||||||
|
int ScrollConsoleScreenBufferW(HANDLE consoleOutput, SMALL_RECT.ByReference scrollRect, SMALL_RECT.ByReference clipRect, COORD.ByValue destinationOrigin, IntByReference fillAttributes);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://msdn.microsoft.com/en-us/library/ms682662%28VS.85%29.aspx
|
* https://msdn.microsoft.com/en-us/library/ms682662%28VS.85%29.aspx
|
||||||
@ -148,7 +156,7 @@ public class Kernel32 {
|
|||||||
int errorCode = Native.getLastError();
|
int errorCode = Native.getLastError();
|
||||||
int bufferSize = 160;
|
int bufferSize = 160;
|
||||||
byte data[] = new byte[bufferSize];
|
byte data[] = new byte[bufferSize];
|
||||||
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, Pointer.NULL, errorCode, 0, data, bufferSize, null);
|
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, Pointer.NULL, errorCode, 0, data, bufferSize, null);
|
||||||
return new String(data);
|
return new String(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ import com.sun.jna.Structure;
|
|||||||
* https://msdn.microsoft.com/en-us/library/ms686311%28VS.85%29.aspx
|
* https://msdn.microsoft.com/en-us/library/ms686311%28VS.85%29.aspx
|
||||||
*/
|
*/
|
||||||
public class SMALL_RECT extends Structure {
|
public class SMALL_RECT extends Structure {
|
||||||
|
static public class ByReference extends SMALL_RECT implements Structure.ByReference { }
|
||||||
|
|
||||||
public short left;
|
public short left;
|
||||||
public short top;
|
public short top;
|
||||||
|
Loading…
Reference in New Issue
Block a user