package gminers.glasspane.component.text; import gminers.glasspane.VertAlignment; import gminers.glasspane.component.Focusable; import gminers.glasspane.component.PaneImage; import gminers.glasspane.component.button.PaneButton; import gminers.glasspane.event.KeyTypedEvent; import gminers.glasspane.listener.PaneEventHandler; import gminers.kitchensink.Rendering; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.IOException; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import lombok.ToString; import lombok.experimental.FieldDefaults; import net.minecraft.client.Minecraft; import net.minecraft.util.ResourceLocation; import org.lwjgl.input.Keyboard; import org.lwjgl.opengl.Display; import org.lwjgl.opengl.GL11; @FieldDefaults(level = AccessLevel.PROTECTED) @ToString public class PaneTextField extends PaneLabel implements Focusable { int counter = 0; /** * The current position of the carat. */ @Getter @Setter int cursorPos = 0; /** * The current opacity of the visual bell. */ @Getter @Setter float blink = 0.0f; /** * The current color of the visual bell. */ @Getter @Setter int blinkColor = 0xFFFFFF; /** * The current offset of the view of the text. */ @Getter @Setter int viewPos = 0; final StringBuilder str = new StringBuilder(); int trimmedLength = 0; /** * The text to show when the TextField is empty. */ @Getter @Setter String blankText = ""; /** * The color to use when showing blankText. */ @Getter @Setter int blankColor = 0x888888; /** * Whether or not to blink the text field when something happens.<br/> * The following colors are used: * <ul> * <li>Green - Copy succeeded</li> * <li>Yellow - Cut succeeded</li> * <li>Red - Paste succeeded</li> * <li>White - Cannot currently do that</li> * </ul> * Other, custom, colors can also be used, but these four are the only ones used by default. */ @Getter @Setter boolean visualBellEnabled = true; /** * An icon to put to the left of the text. Can be used for identification purposes or decoration.<br/> * Null means 'do not show'. */ @Getter @Setter ResourceLocation icon = null; /** * The U (X texture offset) to use when rendering the icon */ @Getter @Setter int iconU = 0; /** * The V (Y texture offset) to use when rendering the icon */ @Getter @Setter int iconV = 0; /** * The width of the portion of the icon's image to use - 256 for the entire image */ @Getter @Setter int iconImageWidth = 256; /** * The height of the portion of the icon's image to use - 256 for the entire image. */ @Getter @Setter int iconImageHeight = 256; /** * A 24-bit packed color to use for the icon. (first 8 bits are ignored, see {@link #alpha}) */ @Getter @Setter int iconColor = 0xFFFFFF; /** * The alpha transparency of the icon - 0.0 is completely transparent, 1.0 is opaque */ @Getter @Setter float alpha = 1.0f; /** * Whether or not to use one-bit transparency for the icon. One-bit transparency is faster, but if your image is partially * transparent, it will render as fully opaque. */ @Getter @Setter boolean oneBitTransparency = true; public PaneTextField() { alignmentY = VertAlignment.MIDDLE; width = 200; height = 20; setActivatedOnClick(false); } public PaneTextField(final String text) { this(); setText(text); } @Override protected void doTick() { // add to the counter counter++; // if we're allowing visual bells, and we need to decrement the bell opacity, do so if (visualBellEnabled && blink > 0) { // make sure we don't go negative blink -= Math.min(blink, 0.1); } } @Override protected void doRender(final int mouseX, final int mouseY, final float partialTicks) { // clamp cursor pos if (cursorPos < 0) { cursorPos = 0; } else if (cursorPos > str.length()) { cursorPos = str.length() - 1; } // bind the widgets Minecraft.getMinecraft().renderEngine.bindTexture(RESOURCE); // u and v, for convenient changing final int u = 0; final int v = 0; // set color GL11.glColor3f(0.6f, 0.6f, 0.6f); PaneButton.renderStretchyTexturedRect(0, 0, u, v, width, height, 220, 40); // translate to the right to make text less stupid looking GL11.glTranslatef(4f, 0f, 0f); if (icon != null) { GL11.glTranslatef(getHeight() - 4, 0, 0); } // trim the text to the component width final String oldText = text; final int oldColor = color; if (text.isEmpty() || text == blankText) { text = blankText; color = blankColor; } String trimmedText; if (viewPos > text.length()) { viewPos = text.length() - trimmedLength; } text = trimmedText = renderer.trimStringToWidth(text.substring(viewPos), getWidth() - 8 - (icon != null ? getHeight() - 4 : 0)); trimmedLength = text.length(); // shift the view pos if needed if (cursorPos > viewPos + trimmedLength) { viewPos = cursorPos - trimmedLength; } else if (cursorPos < viewPos) { viewPos = cursorPos; } // render the label super.doRender(mouseX, mouseY, partialTicks); text = oldText; color = oldColor; // if we're focused, draw focus-y things if (getParent() != null && getParent().getFocusedComponent() == this) { final int llw = renderer.getStringWidth(trimmedText.substring(0, Math.min(trimmedLength, Math.max(0, cursorPos - viewPos)))); final int opacity = 255 - ((int) ((counter + partialTicks) * 15) % 255); int hHeight = height / 2; // such as a carat (if the window is also focused) if (Display.isActive()) { Rendering.drawRect(llw - 1, hHeight - ((renderer.FONT_HEIGHT / 2) + 1), llw, hHeight + (renderer.FONT_HEIGHT / 2), 0x00FFFFFF | opacity << 24); } if (icon != null) { GL11.glTranslatef(-(getHeight() - 4), 0, 0); } GL11.glTranslatef(-4f, 0f, 0f); GL11.glColor3f(1.0f, 1.0f, 1.0f); // and ye olde blue outline Minecraft.getMinecraft().renderEngine.bindTexture(RESOURCE); final int fv = 200; PaneButton.renderStretchyTexturedRect(0, 0, u, fv, width, height, 220, 40); } else { // if we aren't focused, just undo the translate GL11.glTranslatef(-4f, 0f, 0f); if (icon != null) { GL11.glTranslatef(-(getHeight() - 4), 0, 0); } } // draw some indications there's more text if we're not showing it all if (viewPos > 0) { Rendering.drawHorzGradientRect(1, 1, 2 + (icon != null ? getHeight() - 4 : 0), height - 1, 0x99FFFFFF, 0x33FFFFFF, 0); } if (viewPos + trimmedLength < str.length()) { Rendering.drawHorzGradientRect(width - 1, height - 1, width - 2, 1, 0x99FFFFFF, 0x33FFFFFF, 0); } // if we are allowing visual bell, draw the current visual bell values if (visualBellEnabled) { final int blinkOpacity = (int) ((blink - (Math.min(blink, 0.1) * partialTicks)) * 255); Rendering.drawRect(1, 1, width - 1, height - 1, blinkColor | (blinkOpacity << 24)); } // render the icon if (icon != null) { PaneImage.render(icon, 2, 2, iconU, iconV, getHeight() - 4, getHeight() - 4, iconImageWidth, iconImageHeight, iconColor, 1.0f, false); } } @Override public void setText(final String text) { viewPos = 0; cursorPos = 0; str.delete(0, str.length()); str.append(text); this.text = text; } @PaneEventHandler public void onKeyType(final KeyTypedEvent e) { if (getParent() == null || getParent().getFocusedComponent() != this) return; // precalc the ctrl and shift values final boolean ctrl = Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL); // shift is included for IBM-style shortcuts instead of Windows-style final boolean shift = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT); if ((ctrl && e.getKeyCode() == Keyboard.KEY_C) || (ctrl && e.getKeyCode() == Keyboard.KEY_INSERT)) { // if they're holding ^C or ^Ins... // copy to the clipboard final Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard(); clip.setContents(new StringSelection(text), null); // 'sound' the visual bell in a green color for feedback blinkColor = 0x55FF55; blink = 0.5f; } else if ((ctrl && e.getKeyCode() == Keyboard.KEY_X) || (shift && e.getKeyCode() == Keyboard.KEY_DELETE)) { // if they're holding ^X or _Del... // copy to the clipboard final Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard(); clip.setContents(new StringSelection(text), null); // clear the text str.delete(0, str.length()); text = ""; // reset the cursor and view positions cursorPos = 0; viewPos = 0; // 'sound' the visual bell in a yellow color for feedback blinkColor = 0xFFFF55; blink = 0.75f; } else if ((ctrl && e.getKeyCode() == Keyboard.KEY_V) || (shift && e.getKeyCode() == Keyboard.KEY_INSERT)) { // if they're holding ^V or _Ins... // paste from the clipboard final Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard(); // cache the contents as it's recreated every time this is called - with large payloads, this is very costly final Transferable cont = clip.getContents(null); // make sure it can be represented by a string if (cont.isDataFlavorSupported(DataFlavor.stringFlavor)) { try { final String data = String.valueOf(cont.getTransferData(DataFlavor.stringFlavor)); // finally, put it into the string buffer str.insert(cursorPos, data); text = str.toString(); cursorPos += data.length(); // and 'sound' a red visual bell for feedback. blinkColor = 0xFF5555; blink = 0.5f; } catch (final UnsupportedFlavorException e1) { e1.printStackTrace(); } catch (final IOException e1) { e1.printStackTrace(); } } } else if (Character.isLetterOrDigit(e.getKeyChar()) || e.getKeyChar() > 31 && e.getKeyChar() < 127) { // if it's a letter/digit, or a printable ASCII character, insert it // this won't fit all cases for international text input, but should be adequate for US and Euro layouts (for the most part) str.insert(cursorPos, e.getKeyChar()); text = str.toString(); cursorPos++; } else if (e.getKeyCode() == Keyboard.KEY_BACK) { // if it's backspace, and we actually have something to backspace if (str.length() > 0 && cursorPos > 0) { // then delete it str.deleteCharAt(cursorPos - 1); text = str.toString(); cursorPos--; } else { // otherwise, 'sound' a quick white visual bell blink = 0.45f; blinkColor = 0xFFFFFF; } } else if (e.getKeyCode() == Keyboard.KEY_DELETE) { // if it's delete, and we actually have something to delete if (cursorPos < str.length()) { // then delete it str.deleteCharAt(cursorPos); text = str.toString(); } else { // otherwise, 'sound' a quick white visual bell blink = 0.45f; blinkColor = 0xFFFFFF; } } else if (e.getKeyCode() == Keyboard.KEY_LEFT) { // if we're pressing left and actually have space to go left if (cursorPos > 0) { // then go left cursorPos--; } else { // otherwise, 'sound' a quick white visual bell blink = 0.45f; blinkColor = 0xFFFFFF; } } else if (e.getKeyCode() == Keyboard.KEY_RIGHT) { // if we're pressing right and actually have space to go right if (cursorPos < text.length()) { // then go right cursorPos++; } else { // otherwise, 'sound' a quick white visual bell blink = 0.45f; blinkColor = 0xFFFFFF; } } else if (e.getKeyCode() == Keyboard.KEY_END) { // just jump straight to the ending char when End is pressed cursorPos = text.length(); } else if (e.getKeyCode() == Keyboard.KEY_HOME) { // just jump straight to the beginning char when Home is pressed cursorPos = 0; } } }