feat(renderer): replace G2DTextfieldControlImpl2 with G2DSingleLineTextfieldControlImpl and enhance CustomMultiLineTextArea with cachedRawFontHeight
This commit is contained in:
@@ -20,7 +20,7 @@ import de.extio.game_engine.renderer.g2d.control.impl.G2DSetFocusControl;
|
|||||||
import de.extio.game_engine.renderer.g2d.control.impl.G2DSliderControlImpl;
|
import de.extio.game_engine.renderer.g2d.control.impl.G2DSliderControlImpl;
|
||||||
import de.extio.game_engine.renderer.g2d.control.impl.G2DSwitchControlImpl;
|
import de.extio.game_engine.renderer.g2d.control.impl.G2DSwitchControlImpl;
|
||||||
import de.extio.game_engine.renderer.g2d.control.impl.G2DTableControlImpl;
|
import de.extio.game_engine.renderer.g2d.control.impl.G2DTableControlImpl;
|
||||||
import de.extio.game_engine.renderer.g2d.control.impl.G2DTextfieldControlImpl2;
|
import de.extio.game_engine.renderer.g2d.control.impl.G2DSingleLineTextfieldControlImpl;
|
||||||
import de.extio.game_engine.renderer.g2d.control.impl.G2DToggleButtonControlImpl;
|
import de.extio.game_engine.renderer.g2d.control.impl.G2DToggleButtonControlImpl;
|
||||||
import de.extio.game_engine.renderer.g2d.control.impl.G2DTooltipControl;
|
import de.extio.game_engine.renderer.g2d.control.impl.G2DTooltipControl;
|
||||||
import de.extio.game_engine.renderer.g2d.control.impl.G2DWindowCloseButtonControlImpl;
|
import de.extio.game_engine.renderer.g2d.control.impl.G2DWindowCloseButtonControlImpl;
|
||||||
@@ -235,7 +235,7 @@ public class G2DDrawControl extends G2DAbstractRenderingBo implements ControlRen
|
|||||||
control = new G2DLabelControlImpl();
|
control = new G2DLabelControlImpl();
|
||||||
}
|
}
|
||||||
else if (this.clazz == TextfieldControl.class) {
|
else if (this.clazz == TextfieldControl.class) {
|
||||||
control = new G2DTextfieldControlImpl2();
|
control = new G2DSingleLineTextfieldControlImpl();
|
||||||
}
|
}
|
||||||
else if (this.clazz == MultiLineTextAreaControl.class) {
|
else if (this.clazz == MultiLineTextAreaControl.class) {
|
||||||
control = new G2DMultiLineTextAreaControlImpl();
|
control = new G2DMultiLineTextAreaControlImpl();
|
||||||
@@ -366,8 +366,8 @@ public class G2DDrawControl extends G2DAbstractRenderingBo implements ControlRen
|
|||||||
}
|
}
|
||||||
else if (this.clazz == TextfieldControl.class && this.textfieldData != null) {
|
else if (this.clazz == TextfieldControl.class && this.textfieldData != null) {
|
||||||
((TextfieldControl) control).setCustomData(this.textfieldData);
|
((TextfieldControl) control).setCustomData(this.textfieldData);
|
||||||
if (control instanceof final G2DTextfieldControlImpl2 tfc2) {
|
if (control instanceof final G2DSingleLineTextfieldControlImpl tfc) {
|
||||||
tfc2.setLastCaptionUpdateTime(this.lastCaptionUpdateTime);
|
tfc.setLastCaptionUpdateTime(this.lastCaptionUpdateTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (this.clazz == MultiLineTextAreaControl.class && this.multiLineTextAreaData != null) {
|
else if (this.clazz == MultiLineTextAreaControl.class && this.multiLineTextAreaData != null) {
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ public class CustomMultiLineTextArea extends Component {
|
|||||||
|
|
||||||
private int cachedFontSize = 0;
|
private int cachedFontSize = 0;
|
||||||
|
|
||||||
|
private int cachedRawFontHeight = 0;
|
||||||
|
|
||||||
private int cachedSpaceWidth = 0;
|
private int cachedSpaceWidth = 0;
|
||||||
|
|
||||||
private List<String> cachedWrappedLines = null;
|
private List<String> cachedWrappedLines = null;
|
||||||
@@ -108,6 +110,7 @@ public class CustomMultiLineTextArea extends Component {
|
|||||||
this.cachedLineHeight = 0;
|
this.cachedLineHeight = 0;
|
||||||
this.cachedSpaceWidth = 0;
|
this.cachedSpaceWidth = 0;
|
||||||
this.cachedFontSize = 0;
|
this.cachedFontSize = 0;
|
||||||
|
this.cachedRawFontHeight = 0;
|
||||||
this.cachedWrappedLines = null;
|
this.cachedWrappedLines = null;
|
||||||
}
|
}
|
||||||
this.fontSize = fontSize;
|
this.fontSize = fontSize;
|
||||||
@@ -934,6 +937,10 @@ public class CustomMultiLineTextArea extends Component {
|
|||||||
|
|
||||||
final List<String> wrappedLines = getWrappedLines();
|
final List<String> wrappedLines = getWrappedLines();
|
||||||
final int lineHeight = getLineHeight();
|
final int lineHeight = getLineHeight();
|
||||||
|
if (this.cachedRawFontHeight == 0) {
|
||||||
|
this.cachedRawFontHeight = G2DDrawFont.getTextDimensions("ÄÖÜABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzgjpqy", g2d, this.fontSize, 1.0).getY();
|
||||||
|
}
|
||||||
|
final int rawFontHeight = this.cachedRawFontHeight;
|
||||||
|
|
||||||
final Color fgColor = this.foregroundColor != null ? this.foregroundColor : theme.getTextNormal().toColor();
|
final Color fgColor = this.foregroundColor != null ? this.foregroundColor : theme.getTextNormal().toColor();
|
||||||
|
|
||||||
@@ -1008,15 +1015,15 @@ public class CustomMultiLineTextArea extends Component {
|
|||||||
final String beforeCaret = caretPosInWrappedLine > 0 && caretPosInWrappedLine <= caretWrappedLine.length()
|
final String beforeCaret = caretPosInWrappedLine > 0 && caretPosInWrappedLine <= caretWrappedLine.length()
|
||||||
? caretWrappedLine.substring(0, caretPosInWrappedLine)
|
? caretWrappedLine.substring(0, caretPosInWrappedLine)
|
||||||
: "";
|
: "";
|
||||||
final var textDim = beforeCaret.isEmpty() ? ImmutableCoordI2.create(0, (int) (this.fontSize * this.scaleFactor)) : G2DDrawFont.getTextDimensions(beforeCaret, g2d, this.fontSize, this.scaleFactor);
|
final var textDim = beforeCaret.isEmpty() ? ImmutableCoordI2.create(0, 0) : G2DDrawFont.getTextDimensions(beforeCaret, g2d, this.fontSize, this.scaleFactor);
|
||||||
final int spaceWidth = beforeCaret.endsWith(" ") ? getSpaceWidth() : 0;
|
final int spaceWidth = beforeCaret.endsWith(" ") ? getSpaceWidth() : 0;
|
||||||
final int textHeight = textDim.getY();
|
|
||||||
final int caretX = textDim.getX() + TEXT_PADDING + (int) (spaceWidth * this.scaleFactor);
|
final int caretX = textDim.getX() + TEXT_PADDING + (int) (spaceWidth * this.scaleFactor);
|
||||||
final int caretY = (int) ((caretWrappedLineIndex * lineHeight - this.scrollOffsetY) * this.scaleFactor);
|
final int caretY = (int) ((caretWrappedLineIndex * lineHeight - this.scrollOffsetY) * this.scaleFactor);
|
||||||
|
final int caretHeight = (int) (rawFontHeight * this.scaleFactor);
|
||||||
|
|
||||||
if (caretY >= 0 && caretY < this.getHeight()) {
|
if (caretY >= 0 && caretY < this.getHeight()) {
|
||||||
g2d.setColor((currentTime % 1000 < 500) ? theme.getSelectionPrimary().toColor() : theme.getSelectionSecondary().toColor());
|
g2d.setColor((currentTime % 1000 < 500) ? theme.getSelectionPrimary().toColor() : theme.getSelectionSecondary().toColor());
|
||||||
g2d.fillRect(caretX, caretY, 2, textHeight);
|
g2d.fillRect(caretX, caretY, 2, caretHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,602 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.components;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Toolkit;
|
||||||
|
import java.awt.datatransfer.Clipboard;
|
||||||
|
import java.awt.datatransfer.DataFlavor;
|
||||||
|
import java.awt.datatransfer.StringSelection;
|
||||||
|
import java.awt.event.FocusEvent;
|
||||||
|
import java.awt.event.FocusListener;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.awt.event.KeyListener;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.event.MouseMotionListener;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.ThemeManager;
|
||||||
|
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont;
|
||||||
|
import de.extio.game_engine.renderer.g2d.theme.Theme;
|
||||||
|
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class CustomSingleLineTextField extends Component {
|
||||||
|
|
||||||
|
private static final int CARET_BLINK_INTERVAL = 500;
|
||||||
|
|
||||||
|
private static final int TEXT_PADDING = 4;
|
||||||
|
|
||||||
|
private String text = "";
|
||||||
|
|
||||||
|
private int caretPosition = 0;
|
||||||
|
|
||||||
|
private int selectionAnchor = 0;
|
||||||
|
|
||||||
|
private int scrollOffsetX = 0;
|
||||||
|
|
||||||
|
private int fontSize;
|
||||||
|
|
||||||
|
private double scaleFactor;
|
||||||
|
|
||||||
|
private boolean enabled = true;
|
||||||
|
|
||||||
|
private Color backgroundColor;
|
||||||
|
|
||||||
|
private Color foregroundColor;
|
||||||
|
|
||||||
|
private boolean dirty = true;
|
||||||
|
|
||||||
|
private boolean readonly = false;
|
||||||
|
|
||||||
|
private long lastBlinkTime = 0;
|
||||||
|
|
||||||
|
private boolean caretVisible = true;
|
||||||
|
|
||||||
|
private int cachedRawFontHeight = 0;
|
||||||
|
|
||||||
|
private final ThemeManager themeManager;
|
||||||
|
|
||||||
|
private final Consumer<String> onTextChanged;
|
||||||
|
|
||||||
|
private final Runnable onSubmit;
|
||||||
|
|
||||||
|
public void setText(final String text) {
|
||||||
|
this.text = text == null ? "" : text;
|
||||||
|
this.caretPosition = Math.min(this.caretPosition, this.text.length());
|
||||||
|
this.selectionAnchor = this.caretPosition;
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText() {
|
||||||
|
return this.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFontSize(final int fontSize) {
|
||||||
|
if (this.fontSize != fontSize) {
|
||||||
|
this.cachedRawFontHeight = 0;
|
||||||
|
}
|
||||||
|
this.fontSize = fontSize;
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScaleFactor(final double scaleFactor) {
|
||||||
|
this.scaleFactor = scaleFactor;
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabled(final boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReadonly(final boolean readonly) {
|
||||||
|
this.readonly = readonly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackgroundColor(final Color color) {
|
||||||
|
this.backgroundColor = color;
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setForegroundColor(final Color color) {
|
||||||
|
this.foregroundColor = color;
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDirty() {
|
||||||
|
return this.dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDirty(final boolean dirty) {
|
||||||
|
this.dirty = dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomSingleLineTextField(final ThemeManager themeManager, final Consumer<String> onTextChanged, final Runnable onSubmit) {
|
||||||
|
this.themeManager = themeManager;
|
||||||
|
this.onTextChanged = onTextChanged;
|
||||||
|
this.onSubmit = onSubmit;
|
||||||
|
this.setIgnoreRepaint(true);
|
||||||
|
this.setFocusable(true);
|
||||||
|
|
||||||
|
this.addKeyListener(new KeyListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void keyTyped(final KeyEvent e) {
|
||||||
|
if (!CustomSingleLineTextField.this.enabled || CustomSingleLineTextField.this.readonly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final char ch = e.getKeyChar();
|
||||||
|
if (!Character.isISOControl(ch)) {
|
||||||
|
deleteSelection();
|
||||||
|
insertChar(ch);
|
||||||
|
}
|
||||||
|
else if (ch == '\b') {
|
||||||
|
backspace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void keyPressed(final KeyEvent e) {
|
||||||
|
if (!CustomSingleLineTextField.this.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean shiftPressed = e.isShiftDown();
|
||||||
|
final boolean ctrlPressed = e.isControlDown();
|
||||||
|
|
||||||
|
switch (e.getKeyCode()) {
|
||||||
|
case KeyEvent.VK_ENTER:
|
||||||
|
if (!CustomSingleLineTextField.this.readonly) {
|
||||||
|
if (CustomSingleLineTextField.this.onSubmit != null) {
|
||||||
|
CustomSingleLineTextField.this.onSubmit.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case KeyEvent.VK_LEFT:
|
||||||
|
if (CustomSingleLineTextField.this.caretPosition > 0) {
|
||||||
|
CustomSingleLineTextField.this.caretPosition--;
|
||||||
|
if (!shiftPressed) {
|
||||||
|
CustomSingleLineTextField.this.selectionAnchor = CustomSingleLineTextField.this.caretPosition;
|
||||||
|
}
|
||||||
|
ensureCaretVisible();
|
||||||
|
CustomSingleLineTextField.this.dirty = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case KeyEvent.VK_RIGHT:
|
||||||
|
if (CustomSingleLineTextField.this.caretPosition < CustomSingleLineTextField.this.text.length()) {
|
||||||
|
CustomSingleLineTextField.this.caretPosition++;
|
||||||
|
if (!shiftPressed) {
|
||||||
|
CustomSingleLineTextField.this.selectionAnchor = CustomSingleLineTextField.this.caretPosition;
|
||||||
|
}
|
||||||
|
ensureCaretVisible();
|
||||||
|
CustomSingleLineTextField.this.dirty = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case KeyEvent.VK_HOME:
|
||||||
|
CustomSingleLineTextField.this.caretPosition = 0;
|
||||||
|
if (!shiftPressed) {
|
||||||
|
CustomSingleLineTextField.this.selectionAnchor = CustomSingleLineTextField.this.caretPosition;
|
||||||
|
}
|
||||||
|
ensureCaretVisible();
|
||||||
|
CustomSingleLineTextField.this.dirty = true;
|
||||||
|
break;
|
||||||
|
case KeyEvent.VK_END:
|
||||||
|
CustomSingleLineTextField.this.caretPosition = CustomSingleLineTextField.this.text.length();
|
||||||
|
if (!shiftPressed) {
|
||||||
|
CustomSingleLineTextField.this.selectionAnchor = CustomSingleLineTextField.this.caretPosition;
|
||||||
|
}
|
||||||
|
ensureCaretVisible();
|
||||||
|
CustomSingleLineTextField.this.dirty = true;
|
||||||
|
break;
|
||||||
|
case KeyEvent.VK_DELETE:
|
||||||
|
if (!CustomSingleLineTextField.this.readonly) {
|
||||||
|
if (hasSelection()) {
|
||||||
|
if (e.isShiftDown()) {
|
||||||
|
cut();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
deleteSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (CustomSingleLineTextField.this.caretPosition < CustomSingleLineTextField.this.text.length()) {
|
||||||
|
CustomSingleLineTextField.this.text = CustomSingleLineTextField.this.text.substring(0, CustomSingleLineTextField.this.caretPosition) +
|
||||||
|
CustomSingleLineTextField.this.text.substring(CustomSingleLineTextField.this.caretPosition + 1);
|
||||||
|
notifyTextChanged();
|
||||||
|
CustomSingleLineTextField.this.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case KeyEvent.VK_C:
|
||||||
|
if (ctrlPressed) {
|
||||||
|
copy();
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case KeyEvent.VK_X:
|
||||||
|
if (ctrlPressed && !CustomSingleLineTextField.this.readonly) {
|
||||||
|
cut();
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case KeyEvent.VK_V:
|
||||||
|
if (ctrlPressed && !CustomSingleLineTextField.this.readonly) {
|
||||||
|
paste();
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case KeyEvent.VK_INSERT:
|
||||||
|
if (ctrlPressed) {
|
||||||
|
copy();
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
else if (shiftPressed && !CustomSingleLineTextField.this.readonly) {
|
||||||
|
paste();
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case KeyEvent.VK_A:
|
||||||
|
if (ctrlPressed) {
|
||||||
|
selectAll();
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void keyReleased(final KeyEvent e) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addMouseListener(new MouseAdapter() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mousePressed(final MouseEvent e) {
|
||||||
|
CustomSingleLineTextField.this.requestFocus();
|
||||||
|
if (!CustomSingleLineTextField.this.enabled) {
|
||||||
|
e.consume();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateCaretPosition(e.getX());
|
||||||
|
if (!e.isShiftDown()) {
|
||||||
|
CustomSingleLineTextField.this.selectionAnchor = CustomSingleLineTextField.this.caretPosition;
|
||||||
|
}
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(final MouseEvent e) {
|
||||||
|
if (!CustomSingleLineTextField.this.enabled) {
|
||||||
|
e.consume();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.getClickCount() == 2) {
|
||||||
|
selectWordAtCaret();
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseReleased(final MouseEvent e) {
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addMouseMotionListener(new MouseMotionListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseMoved(final MouseEvent e) {
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseDragged(final MouseEvent e) {
|
||||||
|
if (!CustomSingleLineTextField.this.enabled) {
|
||||||
|
e.consume();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateCaretPosition(e.getX());
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addFocusListener(new FocusListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void focusGained(final FocusEvent e) {
|
||||||
|
CustomSingleLineTextField.this.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void focusLost(final FocusEvent e) {
|
||||||
|
CustomSingleLineTextField.this.dirty = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertChar(final char ch) {
|
||||||
|
this.text = this.text.substring(0, this.caretPosition) + ch + this.text.substring(this.caretPosition);
|
||||||
|
this.caretPosition++;
|
||||||
|
this.selectionAnchor = this.caretPosition;
|
||||||
|
ensureCaretVisible();
|
||||||
|
notifyTextChanged();
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void backspace() {
|
||||||
|
if (hasSelection()) {
|
||||||
|
deleteSelection();
|
||||||
|
}
|
||||||
|
else if (this.caretPosition > 0) {
|
||||||
|
this.text = this.text.substring(0, this.caretPosition - 1) + this.text.substring(this.caretPosition);
|
||||||
|
this.caretPosition--;
|
||||||
|
this.selectionAnchor = this.caretPosition;
|
||||||
|
ensureCaretVisible();
|
||||||
|
notifyTextChanged();
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyTextChanged() {
|
||||||
|
if (this.onTextChanged != null) {
|
||||||
|
this.onTextChanged.accept(this.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCaretPosition(final int mouseX) {
|
||||||
|
if (this.getGraphics() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final int logicalMouseX = toLogicalCoord(mouseX);
|
||||||
|
final int textX = logicalMouseX - TEXT_PADDING + this.scrollOffsetX;
|
||||||
|
|
||||||
|
int bestPos = 0;
|
||||||
|
int bestDist = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
for (int j = 0; j <= this.text.length(); j++) {
|
||||||
|
final String substr = this.text.substring(0, j);
|
||||||
|
final int textWidth = G2DDrawFont.getTextDimensions(substr, this.getGraphics(), this.fontSize, 1.0).getX();
|
||||||
|
final int dist = Math.abs(textWidth - textX);
|
||||||
|
if (dist < bestDist) {
|
||||||
|
bestDist = dist;
|
||||||
|
bestPos = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.caretPosition = bestPos;
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureCaretVisible() {
|
||||||
|
if (this.getGraphics() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String beforeCaret = this.text.substring(0, this.caretPosition);
|
||||||
|
final int caretX = G2DDrawFont.getTextDimensions(beforeCaret, this.getGraphics(), this.fontSize, 1.0).getX();
|
||||||
|
final int visibleWidth = getVisibleWidth();
|
||||||
|
|
||||||
|
if (caretX < this.scrollOffsetX) {
|
||||||
|
this.scrollOffsetX = Math.max(0, caretX - TEXT_PADDING);
|
||||||
|
}
|
||||||
|
else if (caretX > this.scrollOffsetX + visibleWidth - TEXT_PADDING * 2) {
|
||||||
|
this.scrollOffsetX = caretX - visibleWidth + TEXT_PADDING * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scrollOffsetX = Math.max(0, this.scrollOffsetX);
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getVisibleWidth() {
|
||||||
|
return (int) (this.getWidth() / this.scaleFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int toLogicalCoord(final int screenCoord) {
|
||||||
|
return (int) (screenCoord / this.scaleFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasSelection() {
|
||||||
|
return this.caretPosition != this.selectionAnchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getSelectionStart() {
|
||||||
|
return Math.min(this.caretPosition, this.selectionAnchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getSelectionEnd() {
|
||||||
|
return Math.max(this.caretPosition, this.selectionAnchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getSelectedText() {
|
||||||
|
if (!hasSelection()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.text.substring(getSelectionStart(), getSelectionEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteSelection() {
|
||||||
|
if (!hasSelection()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int start = getSelectionStart();
|
||||||
|
final int end = getSelectionEnd();
|
||||||
|
this.text = this.text.substring(0, start) + this.text.substring(end);
|
||||||
|
this.caretPosition = start;
|
||||||
|
this.selectionAnchor = start;
|
||||||
|
notifyTextChanged();
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copy() {
|
||||||
|
if (!hasSelection()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String selectedText = getSelectedText();
|
||||||
|
final StringSelection selection = new StringSelection(selectedText);
|
||||||
|
final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||||
|
clipboard.setContents(selection, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cut() {
|
||||||
|
if (!hasSelection() || this.readonly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
copy();
|
||||||
|
deleteSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void paste() {
|
||||||
|
if (this.readonly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||||
|
if (clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) {
|
||||||
|
String clipboardText = (String) clipboard.getData(DataFlavor.stringFlavor);
|
||||||
|
if (clipboardText != null && !clipboardText.isEmpty()) {
|
||||||
|
clipboardText = clipboardText.replace("\r\n", " ").replace('\n', ' ').replace('\r', ' ');
|
||||||
|
deleteSelection();
|
||||||
|
this.text = this.text.substring(0, this.caretPosition) + clipboardText + this.text.substring(this.caretPosition);
|
||||||
|
this.caretPosition += clipboardText.length();
|
||||||
|
this.selectionAnchor = this.caretPosition;
|
||||||
|
ensureCaretVisible();
|
||||||
|
notifyTextChanged();
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (final Exception ex) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectAll() {
|
||||||
|
this.selectionAnchor = 0;
|
||||||
|
this.caretPosition = this.text.length();
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectWordAtCaret() {
|
||||||
|
if (this.text.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int start = this.caretPosition;
|
||||||
|
int end = this.caretPosition;
|
||||||
|
|
||||||
|
while (start > 0 && isWordCharacter(this.text.charAt(start - 1))) {
|
||||||
|
start--;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (end < this.text.length() && isWordCharacter(this.text.charAt(end))) {
|
||||||
|
end++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start < end) {
|
||||||
|
this.selectionAnchor = start;
|
||||||
|
this.caretPosition = end;
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isWordCharacter(final char ch) {
|
||||||
|
return Character.isLetterOrDigit(ch) || ch == '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(final Graphics g) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paint(final Graphics g) {
|
||||||
|
if (this.themeManager == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var g2d = (Graphics2D) g;
|
||||||
|
final Theme theme = this.themeManager.getCurrentTheme();
|
||||||
|
|
||||||
|
final Color bgColor = this.backgroundColor != null ? this.backgroundColor : theme.getBackgroundNormal().toColor();
|
||||||
|
g2d.setColor(bgColor);
|
||||||
|
g2d.fillRect(0, 0, this.getWidth(), this.getHeight());
|
||||||
|
|
||||||
|
if (this.cachedRawFontHeight == 0) {
|
||||||
|
this.cachedRawFontHeight = G2DDrawFont.getTextDimensions("ÄÖÜABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzgjpqy", g2d, this.fontSize, 1.0).getY();
|
||||||
|
}
|
||||||
|
final int rawFontHeight = this.cachedRawFontHeight > 0 ? this.cachedRawFontHeight : this.fontSize;
|
||||||
|
final int textY = (int) ((this.getHeight() / this.scaleFactor - rawFontHeight) / 2.0);
|
||||||
|
final Color fgColor = this.foregroundColor != null ? this.foregroundColor : theme.getTextNormal().toColor();
|
||||||
|
|
||||||
|
final var oldClip = g2d.getClip();
|
||||||
|
g2d.setClip(0, 0, this.getWidth(), this.getHeight());
|
||||||
|
|
||||||
|
final boolean hasSelection = hasSelection();
|
||||||
|
final int selStart = hasSelection ? getSelectionStart() : 0;
|
||||||
|
final int selEnd = hasSelection ? getSelectionEnd() : 0;
|
||||||
|
|
||||||
|
if (hasSelection && !this.text.isEmpty()) {
|
||||||
|
final String beforeSel = this.text.substring(0, selStart);
|
||||||
|
final String selected = this.text.substring(selStart, selEnd);
|
||||||
|
final String afterSel = this.text.substring(selEnd);
|
||||||
|
|
||||||
|
final int beforeSelWidth = G2DDrawFont.getTextDimensions(beforeSel, g2d, this.fontSize, this.scaleFactor).getX();
|
||||||
|
final int selWidth = G2DDrawFont.getTextDimensions(selected, g2d, this.fontSize, this.scaleFactor).getX();
|
||||||
|
final int drawOffsetX = TEXT_PADDING - (int) (this.scrollOffsetX * this.scaleFactor);
|
||||||
|
|
||||||
|
if (!beforeSel.isEmpty()) {
|
||||||
|
g2d.setColor(fgColor);
|
||||||
|
G2DDrawFont.renderText(g2d, null, this.scaleFactor, (int) (TEXT_PADDING / this.scaleFactor) - this.scrollOffsetX, textY, this.fontSize, beforeSel);
|
||||||
|
}
|
||||||
|
|
||||||
|
g2d.setColor(theme.getSelectionPrimary().toColor());
|
||||||
|
g2d.fillRect(drawOffsetX + beforeSelWidth, (int) (textY * this.scaleFactor), selWidth, (int) (rawFontHeight * this.scaleFactor));
|
||||||
|
|
||||||
|
g2d.setColor(theme.getBackgroundNormal().toColor());
|
||||||
|
G2DDrawFont.renderText(g2d, null, this.scaleFactor,
|
||||||
|
(int) ((drawOffsetX + beforeSelWidth) / this.scaleFactor), textY, this.fontSize, selected);
|
||||||
|
|
||||||
|
if (!afterSel.isEmpty()) {
|
||||||
|
g2d.setColor(fgColor);
|
||||||
|
G2DDrawFont.renderText(g2d, null, this.scaleFactor,
|
||||||
|
(int) ((drawOffsetX + beforeSelWidth + selWidth) / this.scaleFactor), textY, this.fontSize, afterSel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
final var textDim = G2DDrawFont.getTextDimensions(this.text, g2d, this.fontSize, 1.0);
|
||||||
|
final var textDimScaled = ImmutableCoordI2.create((int) (textDim.getX() * this.scaleFactor), (int) (textDim.getY() * this.scaleFactor));
|
||||||
|
g2d.setColor(fgColor);
|
||||||
|
G2DDrawFont.renderText(g2d, textDimScaled, this.scaleFactor, TEXT_PADDING - this.scrollOffsetX, textY, this.fontSize, this.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isFocusOwner() && this.enabled) {
|
||||||
|
final long currentTime = System.currentTimeMillis();
|
||||||
|
if (currentTime - this.lastBlinkTime > CARET_BLINK_INTERVAL) {
|
||||||
|
this.caretVisible = !this.caretVisible;
|
||||||
|
this.lastBlinkTime = currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.caretVisible) {
|
||||||
|
final String beforeCaret = this.text.substring(0, this.caretPosition);
|
||||||
|
final var textDim = beforeCaret.isEmpty()
|
||||||
|
? ImmutableCoordI2.create(0, 0)
|
||||||
|
: G2DDrawFont.getTextDimensions(beforeCaret, g2d, this.fontSize, this.scaleFactor);
|
||||||
|
final int caretX = textDim.getX() + TEXT_PADDING - (int) (this.scrollOffsetX * this.scaleFactor);
|
||||||
|
final int caretY = (int) (textY * this.scaleFactor);
|
||||||
|
final int caretHeight = (int) (rawFontHeight * this.scaleFactor);
|
||||||
|
|
||||||
|
g2d.setColor((currentTime % 1000 < 500) ? theme.getSelectionPrimary().toColor() : theme.getSelectionSecondary().toColor());
|
||||||
|
g2d.fillRect(caretX, caretY, 2, caretHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g2d.setClip(oldClip);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.impl;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.G2DRenderer;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.components.CustomSingleLineTextField;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.BaseControl;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.TextfieldControl;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.TextfieldData;
|
||||||
|
import de.extio.game_engine.renderer.model.color.RgbaColor;
|
||||||
|
import de.extio.game_engine.renderer.model.event.UiControlEvent;
|
||||||
|
|
||||||
|
public class G2DSingleLineTextfieldControlImpl extends G2DBaseControlImpl implements TextfieldControl {
|
||||||
|
|
||||||
|
private CustomSingleLineTextField textField;
|
||||||
|
|
||||||
|
private boolean readonly;
|
||||||
|
|
||||||
|
private long lastCaptionUpdateTime;
|
||||||
|
|
||||||
|
private long lastCaptionUpdateTimeInternal;
|
||||||
|
|
||||||
|
private String lastText = "";
|
||||||
|
|
||||||
|
private RgbaColor backgroundColor;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCustomData(final TextfieldData data) {
|
||||||
|
if (data != null) {
|
||||||
|
this.setReadonly(data.readonly());
|
||||||
|
this.setBackgroundColor(data.backgroundColor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public G2DSingleLineTextfieldControlImpl() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBackgroundColor(final RgbaColor color) {
|
||||||
|
this.modified |= (color == null) ? this.backgroundColor != null : !color.equals(this.backgroundColor);
|
||||||
|
this.backgroundColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setReadonly(final boolean readonly) {
|
||||||
|
this.modified |= readonly != this.readonly;
|
||||||
|
this.readonly = readonly;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setCaption(final String caption) {
|
||||||
|
this.caption = caption;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastCaptionUpdateTime(final long lastCaptionUpdateTime) {
|
||||||
|
this.modified |= this.lastCaptionUpdateTime != lastCaptionUpdateTime;
|
||||||
|
this.lastCaptionUpdateTime = lastCaptionUpdateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void build() {
|
||||||
|
super.build();
|
||||||
|
|
||||||
|
this.textField = new CustomSingleLineTextField(
|
||||||
|
this.rendererData.getThemeManager(),
|
||||||
|
text -> {
|
||||||
|
this.lastText = text;
|
||||||
|
this.lastCaptionUpdateTimeInternal = System.currentTimeMillis();
|
||||||
|
try {
|
||||||
|
Thread.sleep(2);
|
||||||
|
}
|
||||||
|
catch (final InterruptedException e) {
|
||||||
|
}
|
||||||
|
this.rendererData.getEventService().fire(new UiControlEvent(this.id, text));
|
||||||
|
},
|
||||||
|
() -> {
|
||||||
|
final String submitText = (this.lastText == null ? "" : this.lastText) + "\n";
|
||||||
|
this.textField.setText("");
|
||||||
|
this.lastText = "";
|
||||||
|
this.caption = null;
|
||||||
|
this.lastCaptionUpdateTimeInternal = System.currentTimeMillis();
|
||||||
|
this.rendererData.getEventService().fire(new UiControlEvent(this.id, submitText));
|
||||||
|
});
|
||||||
|
|
||||||
|
this.textField.setName(this.id);
|
||||||
|
this.textField.setFocusable(true);
|
||||||
|
this.initControl();
|
||||||
|
|
||||||
|
final var mainFrame = ((G2DRenderer) this.rendererData.getRenderer()).getMainFrame();
|
||||||
|
mainFrame.add(this.textField);
|
||||||
|
this.updateAllComponentZOrder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void performAction() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render() {
|
||||||
|
if (this.lastCaptionUpdateTime > this.lastCaptionUpdateTimeInternal && this.caption != null && !this.caption.equals(this.lastText)) {
|
||||||
|
this.textField.setText(this.caption);
|
||||||
|
this.lastText = this.caption;
|
||||||
|
this.lastCaptionUpdateTimeInternal = this.lastCaptionUpdateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean componentDirty = this.textField.isDirty();
|
||||||
|
if (this.modified || componentDirty) {
|
||||||
|
this.rebuildBufferedImage();
|
||||||
|
this.bufferedImageGraphics.setClip(0, 0, this.bufferedImage.getWidth(), this.bufferedImage.getHeight());
|
||||||
|
this.textField.setSize(this.bufferedImage.getWidth(), this.bufferedImage.getHeight());
|
||||||
|
if (this.modified || componentDirty) {
|
||||||
|
this.initControl();
|
||||||
|
}
|
||||||
|
this.textField.paint(this.bufferedImageGraphics);
|
||||||
|
this.textField.setDirty(false);
|
||||||
|
this.updateAllComponentZOrder();
|
||||||
|
}
|
||||||
|
else if (this.positionModified) {
|
||||||
|
this.textField.setLocation(this.x, this.y);
|
||||||
|
this.positionModified = false;
|
||||||
|
this.updateAllComponentZOrder();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.textField.paint(this.bufferedImageGraphics);
|
||||||
|
|
||||||
|
super.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (this.textField.isFocusOwner()) {
|
||||||
|
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().requestFocus();
|
||||||
|
}
|
||||||
|
this.textField.setVisible(false);
|
||||||
|
this.textField.invalidate();
|
||||||
|
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().remove(this.textField);
|
||||||
|
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initControl() {
|
||||||
|
final var theme = this.rendererData.getThemeManager().getCurrentTheme();
|
||||||
|
final var bgColor = this.backgroundColor != null ? this.backgroundColor.toAwtColor() : theme.getBackgroundNormal().toColor();
|
||||||
|
final var fgColor = theme.getTextNormal().toColor();
|
||||||
|
|
||||||
|
this.textField.setBackgroundColor(bgColor);
|
||||||
|
this.textField.setForegroundColor(fgColor);
|
||||||
|
this.textField.setFontSize(this.fontSize);
|
||||||
|
this.textField.setScaleFactor(this.scaleFactor);
|
||||||
|
this.textField.setLocation(this.x, this.y);
|
||||||
|
this.textField.setSize(this.width, this.height);
|
||||||
|
this.positionModified = false;
|
||||||
|
this.textField.setVisible(this.visible);
|
||||||
|
this.textField.setEnabled(this.enabled);
|
||||||
|
this.textField.setReadonly(this.readonly);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user