418 lines
15 KiB
Java
418 lines
15 KiB
Java
/*
|
|
* This file is part of ComputerCraft - http://www.computercraft.info
|
|
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
|
|
* Send enquiries to dratcliffe@gmail.com
|
|
*/
|
|
|
|
package dan200.computercraft.client.gui.widgets;
|
|
|
|
import dan200.computercraft.client.FrameInfo;
|
|
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
|
|
import dan200.computercraft.core.terminal.Terminal;
|
|
import dan200.computercraft.core.terminal.TextBuffer;
|
|
import dan200.computercraft.shared.computer.core.ClientComputer;
|
|
import dan200.computercraft.shared.computer.core.IComputer;
|
|
import dan200.computercraft.shared.util.Colour;
|
|
import dan200.computercraft.shared.util.Palette;
|
|
import net.minecraft.client.Minecraft;
|
|
import net.minecraft.client.gui.IGuiEventListener;
|
|
import net.minecraft.client.renderer.BufferBuilder;
|
|
import net.minecraft.client.renderer.GlStateManager;
|
|
import net.minecraft.client.renderer.Tessellator;
|
|
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
|
|
import net.minecraft.util.SharedConstants;
|
|
import org.lwjgl.glfw.GLFW;
|
|
import org.lwjgl.opengl.GL11;
|
|
|
|
import java.util.BitSet;
|
|
import java.util.function.Supplier;
|
|
|
|
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.BACKGROUND;
|
|
|
|
public class WidgetTerminal implements IGuiEventListener
|
|
{
|
|
private static final float TERMINATE_TIME = 0.5f;
|
|
|
|
private final Minecraft client;
|
|
|
|
private final Supplier<ClientComputer> computer;
|
|
private final int termWidth;
|
|
private final int termHeight;
|
|
|
|
private float terminateTimer = -1;
|
|
private float rebootTimer = -1;
|
|
private float shutdownTimer = -1;
|
|
|
|
private int lastMouseButton = -1;
|
|
private int lastMouseX = -1;
|
|
private int lastMouseY = -1;
|
|
|
|
private final int leftMargin;
|
|
private final int rightMargin;
|
|
private final int topMargin;
|
|
private final int bottomMargin;
|
|
|
|
private final BitSet keysDown = new BitSet( 256 );
|
|
|
|
public WidgetTerminal( Minecraft client, Supplier<ClientComputer> computer, int termWidth, int termHeight, int leftMargin, int rightMargin, int topMargin, int bottomMargin )
|
|
{
|
|
this.client = client;
|
|
this.computer = computer;
|
|
this.termWidth = termWidth;
|
|
this.termHeight = termHeight;
|
|
this.leftMargin = leftMargin;
|
|
this.rightMargin = rightMargin;
|
|
this.topMargin = topMargin;
|
|
this.bottomMargin = bottomMargin;
|
|
}
|
|
|
|
@Override
|
|
public boolean charTyped( char ch, int modifiers )
|
|
{
|
|
if( ch >= 32 && ch <= 126 || ch >= 160 && ch <= 255 ) // printable chars in byte range
|
|
{
|
|
// Queue the "char" event
|
|
queueEvent( "char", Character.toString( ch ) );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean keyPressed( int key, int scancode, int modifiers )
|
|
{
|
|
if( key == GLFW.GLFW_KEY_ESCAPE ) return false;
|
|
if( (modifiers & GLFW.GLFW_MOD_CONTROL) != 0 )
|
|
{
|
|
switch( key )
|
|
{
|
|
case GLFW.GLFW_KEY_T:
|
|
if( terminateTimer < 0 ) terminateTimer = 0;
|
|
return true;
|
|
case GLFW.GLFW_KEY_S:
|
|
if( shutdownTimer < 0 ) shutdownTimer = 0;
|
|
return true;
|
|
case GLFW.GLFW_KEY_R:
|
|
if( rebootTimer < 0 ) rebootTimer = 0;
|
|
return true;
|
|
|
|
case GLFW.GLFW_KEY_V:
|
|
// Ctrl+V for paste
|
|
String clipboard = client.keyboardListener.getClipboardString();
|
|
if( clipboard != null )
|
|
{
|
|
// Clip to the first occurrence of \r or \n
|
|
int newLineIndex1 = clipboard.indexOf( "\r" );
|
|
int newLineIndex2 = clipboard.indexOf( "\n" );
|
|
if( newLineIndex1 >= 0 && newLineIndex2 >= 0 )
|
|
{
|
|
clipboard = clipboard.substring( 0, Math.min( newLineIndex1, newLineIndex2 ) );
|
|
}
|
|
else if( newLineIndex1 >= 0 )
|
|
{
|
|
clipboard = clipboard.substring( 0, newLineIndex1 );
|
|
}
|
|
else if( newLineIndex2 >= 0 )
|
|
{
|
|
clipboard = clipboard.substring( 0, newLineIndex2 );
|
|
}
|
|
|
|
// Filter the string
|
|
clipboard = SharedConstants.filterAllowedCharacters( clipboard );
|
|
if( !clipboard.isEmpty() )
|
|
{
|
|
// Clip to 512 characters and queue the event
|
|
if( clipboard.length() > 512 ) clipboard = clipboard.substring( 0, 512 );
|
|
queueEvent( "paste", clipboard );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( key >= 0 && terminateTimer < 0 && rebootTimer < 0 && shutdownTimer < 0 )
|
|
{
|
|
// Queue the "key" event and add to the down set
|
|
boolean repeat = keysDown.get( key );
|
|
keysDown.set( key );
|
|
IComputer computer = this.computer.get();
|
|
if( computer != null ) computer.keyDown( key, repeat );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean keyReleased( int key, int scancode, int modifiers )
|
|
{
|
|
// Queue the "key_up" event and remove from the down set
|
|
if( key >= 0 && keysDown.get( key ) )
|
|
{
|
|
keysDown.set( key, false );
|
|
IComputer computer = this.computer.get();
|
|
if( computer != null ) computer.keyUp( key );
|
|
}
|
|
|
|
switch( key )
|
|
{
|
|
case GLFW.GLFW_KEY_T:
|
|
terminateTimer = -1;
|
|
break;
|
|
case GLFW.GLFW_KEY_R:
|
|
rebootTimer = -1;
|
|
break;
|
|
case GLFW.GLFW_KEY_S:
|
|
shutdownTimer = -1;
|
|
break;
|
|
case GLFW.GLFW_KEY_LEFT_CONTROL:
|
|
case GLFW.GLFW_KEY_RIGHT_CONTROL:
|
|
terminateTimer = rebootTimer = shutdownTimer = -1;
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean mouseClicked( double mouseX, double mouseY, int button )
|
|
{
|
|
ClientComputer computer = this.computer.get();
|
|
if( computer == null || !computer.isColour() || button < 0 || button > 2 ) return false;
|
|
|
|
Terminal term = computer.getTerminal();
|
|
if( term != null )
|
|
{
|
|
int charX = (int) (mouseX / FixedWidthFontRenderer.FONT_WIDTH);
|
|
int charY = (int) (mouseY / FixedWidthFontRenderer.FONT_HEIGHT);
|
|
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
|
|
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
|
|
|
|
computer.mouseClick( button + 1, charX + 1, charY + 1 );
|
|
|
|
lastMouseButton = button;
|
|
lastMouseX = charX;
|
|
lastMouseY = charY;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean mouseReleased( double mouseX, double mouseY, int button )
|
|
{
|
|
ClientComputer computer = this.computer.get();
|
|
if( computer == null || !computer.isColour() || button < 0 || button > 2 ) return false;
|
|
|
|
Terminal term = computer.getTerminal();
|
|
if( term != null )
|
|
{
|
|
int charX = (int) (mouseX / FixedWidthFontRenderer.FONT_WIDTH);
|
|
int charY = (int) (mouseY / FixedWidthFontRenderer.FONT_HEIGHT);
|
|
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
|
|
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
|
|
|
|
if( lastMouseButton == button )
|
|
{
|
|
computer.mouseUp( lastMouseButton + 1, charX + 1, charY + 1 );
|
|
lastMouseButton = -1;
|
|
}
|
|
|
|
lastMouseX = charX;
|
|
lastMouseY = charY;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean mouseDragged( double mouseX, double mouseY, int button, double v2, double v3 )
|
|
{
|
|
ClientComputer computer = this.computer.get();
|
|
if( computer == null || !computer.isColour() || button < 0 || button > 2 ) return false;
|
|
|
|
Terminal term = computer.getTerminal();
|
|
if( term != null )
|
|
{
|
|
int charX = (int) (mouseX / FixedWidthFontRenderer.FONT_WIDTH);
|
|
int charY = (int) (mouseY / FixedWidthFontRenderer.FONT_HEIGHT);
|
|
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
|
|
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
|
|
|
|
computer.mouseDrag( button + 1, charX + 1, charY + 1 );
|
|
|
|
lastMouseX = charX;
|
|
lastMouseY = charY;
|
|
lastMouseButton = button;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean mouseScrolled( double delta )
|
|
{
|
|
ClientComputer computer = this.computer.get();
|
|
if( computer == null || !computer.isColour() ) return false;
|
|
|
|
if( lastMouseX >= 0 && lastMouseY >= 0 && delta != 0 )
|
|
{
|
|
queueEvent( "mouse_scroll", delta < 0 ? 1 : -1, lastMouseX + 1, lastMouseY + 1 );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void update()
|
|
{
|
|
if( terminateTimer >= 0 && terminateTimer < TERMINATE_TIME && (terminateTimer += 0.05f) > TERMINATE_TIME )
|
|
{
|
|
queueEvent( "terminate" );
|
|
}
|
|
|
|
if( shutdownTimer >= 0 && shutdownTimer < TERMINATE_TIME && (shutdownTimer += 0.05f) > TERMINATE_TIME )
|
|
{
|
|
ClientComputer computer = this.computer.get();
|
|
if( computer != null ) computer.shutdown();
|
|
}
|
|
|
|
if( rebootTimer >= 0 && rebootTimer < TERMINATE_TIME && (rebootTimer += 0.05f) > TERMINATE_TIME )
|
|
{
|
|
ClientComputer computer = this.computer.get();
|
|
if( computer != null ) computer.reboot();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void focusChanged( boolean focused )
|
|
{
|
|
if( !focused )
|
|
{
|
|
// When blurring, we should make all keys go up
|
|
for( int key = 0; key < keysDown.size(); key++ )
|
|
{
|
|
if( keysDown.get( key ) ) queueEvent( "key_up", key );
|
|
}
|
|
keysDown.clear();
|
|
|
|
// When blurring, we should make the last mouse button go up
|
|
if( lastMouseButton > 0 )
|
|
{
|
|
IComputer computer = this.computer.get();
|
|
if( computer != null ) computer.mouseUp( lastMouseButton + 1, lastMouseX + 1, lastMouseY + 1 );
|
|
lastMouseButton = -1;
|
|
}
|
|
|
|
shutdownTimer = terminateTimer = rebootTimer = -1;
|
|
}
|
|
}
|
|
|
|
public void draw( int originX, int originY )
|
|
{
|
|
synchronized( computer )
|
|
{
|
|
// Draw the screen contents
|
|
ClientComputer computer = this.computer.get();
|
|
Terminal terminal = computer != null ? computer.getTerminal() : null;
|
|
if( terminal != null )
|
|
{
|
|
// Draw the terminal
|
|
boolean greyscale = !computer.isColour();
|
|
Palette palette = terminal.getPalette();
|
|
|
|
// Get the data from the terminal first
|
|
// Unfortunately we have to keep the lock for the whole of drawing, so the text doesn't change under us.
|
|
FixedWidthFontRenderer fontRenderer = FixedWidthFontRenderer.instance();
|
|
boolean tblink = terminal.getCursorBlink() && FrameInfo.getGlobalCursorBlink();
|
|
int tw = terminal.getWidth();
|
|
int th = terminal.getHeight();
|
|
int tx = terminal.getCursorX();
|
|
int ty = terminal.getCursorY();
|
|
|
|
// Draw margins
|
|
TextBuffer emptyLine = new TextBuffer( ' ', tw );
|
|
if( topMargin > 0 )
|
|
{
|
|
fontRenderer.drawString( emptyLine, originX, originY - topMargin,
|
|
terminal.getTextColourLine( 0 ), terminal.getBackgroundColourLine( 0 ),
|
|
leftMargin, rightMargin, greyscale, palette );
|
|
}
|
|
|
|
if( bottomMargin > 0 )
|
|
{
|
|
fontRenderer.drawString( emptyLine, originX, originY + bottomMargin + (th - 1) * FixedWidthFontRenderer.FONT_HEIGHT,
|
|
terminal.getTextColourLine( th - 1 ), terminal.getBackgroundColourLine( th - 1 ),
|
|
leftMargin, rightMargin, greyscale, palette );
|
|
}
|
|
|
|
// Draw lines
|
|
int y = originY;
|
|
for( int line = 0; line < th; line++ )
|
|
{
|
|
TextBuffer text = terminal.getLine( line );
|
|
TextBuffer colour = terminal.getTextColourLine( line );
|
|
TextBuffer backgroundColour = terminal.getBackgroundColourLine( line );
|
|
fontRenderer.drawString( text, originX, y, colour, backgroundColour, leftMargin, rightMargin, greyscale, palette );
|
|
y += FixedWidthFontRenderer.FONT_HEIGHT;
|
|
}
|
|
|
|
if( tblink && tx >= 0 && ty >= 0 && tx < tw && ty < th )
|
|
{
|
|
TextBuffer cursor = new TextBuffer( '_', 1 );
|
|
TextBuffer cursorColour = new TextBuffer( "0123456789abcdef".charAt( terminal.getTextColour() ), 1 );
|
|
|
|
fontRenderer.drawString(
|
|
cursor,
|
|
originX + FixedWidthFontRenderer.FONT_WIDTH * tx,
|
|
originY + FixedWidthFontRenderer.FONT_HEIGHT * ty,
|
|
cursorColour, null,
|
|
0, 0,
|
|
greyscale,
|
|
palette
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Draw a black background
|
|
Colour black = Colour.Black;
|
|
GlStateManager.color4f( black.getR(), black.getG(), black.getB(), 1.0f );
|
|
try
|
|
{
|
|
int x = originX - leftMargin;
|
|
int y = originY - rightMargin;
|
|
int width = termWidth * FixedWidthFontRenderer.FONT_WIDTH + leftMargin + rightMargin;
|
|
int height = termHeight * FixedWidthFontRenderer.FONT_HEIGHT + topMargin + bottomMargin;
|
|
|
|
client.getTextureManager().bindTexture( BACKGROUND );
|
|
|
|
Tessellator tesslector = Tessellator.getInstance();
|
|
BufferBuilder buffer = tesslector.getBuffer();
|
|
buffer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX );
|
|
buffer.pos( x, y + height, 0 ).tex( 0 / 256.0, height / 256.0 ).endVertex();
|
|
buffer.pos( x + width, y + height, 0 ).tex( width / 256.0, height / 256.0 ).endVertex();
|
|
buffer.pos( x + width, y, 0 ).tex( width / 256.0, 0 / 256.0 ).endVertex();
|
|
buffer.pos( x, y, 0 ).tex( 0 / 256.0, 0 / 256.0 ).endVertex();
|
|
tesslector.draw();
|
|
}
|
|
finally
|
|
{
|
|
GlStateManager.color4f( 1.0f, 1.0f, 1.0f, 1.0f );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void queueEvent( String event )
|
|
{
|
|
ClientComputer computer = this.computer.get();
|
|
if( computer != null ) computer.queueEvent( event );
|
|
}
|
|
|
|
private void queueEvent( String event, Object... args )
|
|
{
|
|
ClientComputer computer = this.computer.get();
|
|
if( computer != null ) computer.queueEvent( event, args );
|
|
}
|
|
}
|