MultilineTextField.java
net.minecraft.client.gui.components.MultilineTextField
信息
- 全限定名:net.minecraft.client.gui.components.MultilineTextField
- 类型:public class
- 包:net.minecraft.client.gui.components
- 源码路径:src/main/java/net/minecraft/client/gui/components/MultilineTextField.java
- 起始行号:L19
- 职责:
TODO
字段/常量
-
LOGGER- 类型:
Logger - 修饰符:
private static final - 源码定位:
L20 - 说明:
TODO
- 类型:
-
NO_LIMIT- 类型:
int - 修饰符:
public static final - 源码定位:
L21 - 说明:
TODO
- 类型:
-
LINE_SEEK_PIXEL_BIAS- 类型:
int - 修饰符:
private static final - 源码定位:
L22 - 说明:
TODO
- 类型:
-
font- 类型:
Font - 修饰符:
private final - 源码定位:
L23 - 说明:
TODO
- 类型:
-
displayLines- 类型:
List<MultilineTextField.StringView> - 修饰符:
private final - 源码定位:
L24 - 说明:
TODO
- 类型:
-
value- 类型:
String - 修饰符:
private - 源码定位:
L25 - 说明:
TODO
- 类型:
-
cursor- 类型:
int - 修饰符:
private - 源码定位:
L26 - 说明:
TODO
- 类型:
-
selectCursor- 类型:
int - 修饰符:
private - 源码定位:
L27 - 说明:
TODO
- 类型:
-
selecting- 类型:
boolean - 修饰符:
private - 源码定位:
L28 - 说明:
TODO
- 类型:
-
characterLimit- 类型:
int - 修饰符:
private - 源码定位:
L29 - 说明:
TODO
- 类型:
-
lineLimit- 类型:
int - 修饰符:
private - 源码定位:
L30 - 说明:
TODO
- 类型:
-
width- 类型:
int - 修饰符:
private final - 源码定位:
L31 - 说明:
TODO
- 类型:
-
valueListener- 类型:
Consumer<String> - 修饰符:
private - 源码定位:
L32 - 说明:
TODO
- 类型:
-
cursorListener- 类型:
Runnable - 修饰符:
private - 源码定位:
L33 - 说明:
TODO
- 类型:
内部类/嵌套类型
net.minecraft.client.gui.components.MultilineTextField.StringView- 类型:
record - 修饰符:
protected - 源码定位:
L407 - 说明:
TODO
- 类型:
构造器
public MultilineTextField(Font font, int width) @ L35
- 构造器名:MultilineTextField
- 源码定位:L35
- 修饰符:public
参数:
- font: Font
- width: int
说明:
TODO
方法
下面的方法块按源码顺序生成。
public int characterLimit() @ L41
- 方法名:characterLimit
- 源码定位:L41
- 返回类型:int
- 修饰符:public
参数:
- 无
说明:
TODO
public void setCharacterLimit(int characterLimit) @ L45
- 方法名:setCharacterLimit
- 源码定位:L45
- 返回类型:void
- 修饰符:public
参数:
- characterLimit: int
说明:
TODO
public void setLineLimit(int lineLimit) @ L53
- 方法名:setLineLimit
- 源码定位:L53
- 返回类型:void
- 修饰符:public
参数:
- lineLimit: int
说明:
TODO
public boolean hasCharacterLimit() @ L61
- 方法名:hasCharacterLimit
- 源码定位:L61
- 返回类型:boolean
- 修饰符:public
参数:
- 无
说明:
TODO
public boolean hasLineLimit() @ L65
- 方法名:hasLineLimit
- 源码定位:L65
- 返回类型:boolean
- 修饰符:public
参数:
- 无
说明:
TODO
public void setValueListener(Consumer<String> valueListener) @ L69
- 方法名:setValueListener
- 源码定位:L69
- 返回类型:void
- 修饰符:public
参数:
- valueListener: Consumer
说明:
TODO
public void setCursorListener(Runnable cursorListener) @ L73
- 方法名:setCursorListener
- 源码定位:L73
- 返回类型:void
- 修饰符:public
参数:
- cursorListener: Runnable
说明:
TODO
public void setValue(String value) @ L77
- 方法名:setValue
- 源码定位:L77
- 返回类型:void
- 修饰符:public
参数:
- value: String
说明:
TODO
public void setValue(String value, boolean allowOverflowLineLimit) @ L81
- 方法名:setValue
- 源码定位:L81
- 返回类型:void
- 修饰符:public
参数:
- value: String
- allowOverflowLineLimit: boolean
说明:
TODO
public String value() @ L91
- 方法名:value
- 源码定位:L91
- 返回类型:String
- 修饰符:public
参数:
- 无
说明:
TODO
public void insertText(String input) @ L95
- 方法名:insertText
- 源码定位:L95
- 返回类型:void
- 修饰符:public
参数:
- input: String
说明:
TODO
public void deleteText(int dir) @ L109
- 方法名:deleteText
- 源码定位:L109
- 返回类型:void
- 修饰符:public
参数:
- dir: int
说明:
TODO
public int cursor() @ L117
- 方法名:cursor
- 源码定位:L117
- 返回类型:int
- 修饰符:public
参数:
- 无
说明:
TODO
public void setSelecting(boolean selecting) @ L121
- 方法名:setSelecting
- 源码定位:L121
- 返回类型:void
- 修饰符:public
参数:
- selecting: boolean
说明:
TODO
public MultilineTextField.StringView getSelected() @ L125
- 方法名:getSelected
- 源码定位:L125
- 返回类型:MultilineTextField.StringView
- 修饰符:public
参数:
- 无
说明:
TODO
public int getLineCount() @ L129
- 方法名:getLineCount
- 源码定位:L129
- 返回类型:int
- 修饰符:public
参数:
- 无
说明:
TODO
public int getLineAtCursor() @ L133
- 方法名:getLineAtCursor
- 源码定位:L133
- 返回类型:int
- 修饰符:public
参数:
- 无
说明:
TODO
public MultilineTextField.StringView getLineView(int lineIndex) @ L144
- 方法名:getLineView
- 源码定位:L144
- 返回类型:MultilineTextField.StringView
- 修饰符:public
参数:
- lineIndex: int
说明:
TODO
public void seekCursor(Whence whence, int cursor) @ L148
- 方法名:seekCursor
- 源码定位:L148
- 返回类型:void
- 修饰符:public
参数:
- whence: Whence
- cursor: int
说明:
TODO
public void seekCursorLine(int lineOffset) @ L167
- 方法名:seekCursorLine
- 源码定位:L167
- 返回类型:void
- 修饰符:public
参数:
- lineOffset: int
说明:
TODO
public void seekCursorToPoint(double x, double y) @ L176
- 方法名:seekCursorToPoint
- 源码定位:L176
- 返回类型:void
- 修饰符:public
参数:
- x: double
- y: double
说明:
TODO
public void selectWordAtCursor() @ L184
- 方法名:selectWordAtCursor
- 源码定位:L184
- 返回类型:void
- 修饰符:public
参数:
- 无
说明:
TODO
public boolean keyPressed(KeyEvent event) @ L191
- 方法名:keyPressed
- 源码定位:L191
- 返回类型:boolean
- 修饰符:public
参数:
- event: KeyEvent
说明:
TODO
public Iterable<MultilineTextField.StringView> iterateLines() @ L289
- 方法名:iterateLines
- 源码定位:L289
- 返回类型:Iterable<MultilineTextField.StringView>
- 修饰符:public
参数:
- 无
说明:
TODO
public boolean hasSelection() @ L293
- 方法名:hasSelection
- 源码定位:L293
- 返回类型:boolean
- 修饰符:public
参数:
- 无
说明:
TODO
public String getSelectedText() @ L297
- 方法名:getSelectedText
- 源码定位:L297
- 返回类型:String
- 修饰符:public
参数:
- 无
说明:
TODO
private MultilineTextField.StringView getCursorLineView() @ L303
- 方法名:getCursorLineView
- 源码定位:L303
- 返回类型:MultilineTextField.StringView
- 修饰符:private
参数:
- 无
说明:
TODO
private MultilineTextField.StringView getCursorLineView(int lineOffset) @ L307
- 方法名:getCursorLineView
- 源码定位:L307
- 返回类型:MultilineTextField.StringView
- 修饰符:private
参数:
- lineOffset: int
说明:
TODO
public MultilineTextField.StringView getPreviousWord() @ L317
- 方法名:getPreviousWord
- 源码定位:L317
- 返回类型:MultilineTextField.StringView
- 修饰符:public
参数:
- 无
说明:
TODO
public MultilineTextField.StringView getNextWord() @ L336
- 方法名:getNextWord
- 源码定位:L336
- 返回类型:MultilineTextField.StringView
- 修饰符:public
参数:
- 无
说明:
TODO
private int getWordEndPosition(int from) @ L355
- 方法名:getWordEndPosition
- 源码定位:L355
- 返回类型:int
- 修饰符:private
参数:
- from: int
说明:
TODO
private void onValueChange() @ L365
- 方法名:onValueChange
- 源码定位:L365
- 返回类型:void
- 修饰符:private
参数:
- 无
说明:
TODO
private void reflowDisplayLines() @ L371
- 方法名:reflowDisplayLines
- 源码定位:L371
- 返回类型:void
- 修饰符:private
参数:
- 无
说明:
TODO
private String truncateFullText(String input) @ L387
- 方法名:truncateFullText
- 源码定位:L387
- 返回类型:String
- 修饰符:private
参数:
- input: String
说明:
TODO
private String truncateInsertionText(String input) @ L391
- 方法名:truncateInsertionText
- 源码定位:L391
- 返回类型:String
- 修饰符:private
参数:
- input: String
说明:
TODO
private boolean overflowsLineLimit(String newValue) @ L401
- 方法名:overflowsLineLimit
- 源码定位:L401
- 返回类型:boolean
- 修饰符:private
参数:
- newValue: String
说明:
TODO
代码
@OnlyIn(Dist.CLIENT)
public class MultilineTextField {
private static final Logger LOGGER = LogUtils.getLogger();
public static final int NO_LIMIT = Integer.MAX_VALUE;
private static final int LINE_SEEK_PIXEL_BIAS = 2;
private final Font font;
private final List<MultilineTextField.StringView> displayLines = Lists.newArrayList();
private String value;
private int cursor;
private int selectCursor;
private boolean selecting;
private int characterLimit = Integer.MAX_VALUE;
private int lineLimit = Integer.MAX_VALUE;
private final int width;
private Consumer<String> valueListener = s -> {};
private Runnable cursorListener = () -> {};
public MultilineTextField(Font font, int width) {
this.font = font;
this.width = width;
this.setValue("");
}
public int characterLimit() {
return this.characterLimit;
}
public void setCharacterLimit(int characterLimit) {
if (characterLimit < 0) {
throw new IllegalArgumentException("Character limit cannot be negative");
} else {
this.characterLimit = characterLimit;
}
}
public void setLineLimit(int lineLimit) {
if (lineLimit < 0) {
throw new IllegalArgumentException("Character limit cannot be negative");
} else {
this.lineLimit = lineLimit;
}
}
public boolean hasCharacterLimit() {
return this.characterLimit != Integer.MAX_VALUE;
}
public boolean hasLineLimit() {
return this.lineLimit != Integer.MAX_VALUE;
}
public void setValueListener(Consumer<String> valueListener) {
this.valueListener = valueListener;
}
public void setCursorListener(Runnable cursorListener) {
this.cursorListener = cursorListener;
}
public void setValue(String value) {
this.setValue(value, false);
}
public void setValue(String value, boolean allowOverflowLineLimit) {
String newValue = this.truncateFullText(value);
if (allowOverflowLineLimit || !this.overflowsLineLimit(newValue)) {
this.value = newValue;
this.cursor = this.value.length();
this.selectCursor = this.cursor;
this.onValueChange();
}
}
public String value() {
return this.value;
}
public void insertText(String input) {
if (!input.isEmpty() || this.hasSelection()) {
String text = this.truncateInsertionText(StringUtil.filterText(input, true));
MultilineTextField.StringView selected = this.getSelected();
String newValue = new StringBuilder(this.value).replace(selected.beginIndex, selected.endIndex, text).toString();
if (!this.overflowsLineLimit(newValue)) {
this.value = newValue;
this.cursor = selected.beginIndex + text.length();
this.selectCursor = this.cursor;
this.onValueChange();
}
}
}
public void deleteText(int dir) {
if (!this.hasSelection()) {
this.selectCursor = Mth.clamp(this.cursor + dir, 0, this.value.length());
}
this.insertText("");
}
public int cursor() {
return this.cursor;
}
public void setSelecting(boolean selecting) {
this.selecting = selecting;
}
public MultilineTextField.StringView getSelected() {
return new MultilineTextField.StringView(Math.min(this.selectCursor, this.cursor), Math.max(this.selectCursor, this.cursor));
}
public int getLineCount() {
return this.displayLines.size();
}
public int getLineAtCursor() {
for (int i = 0; i < this.displayLines.size(); i++) {
MultilineTextField.StringView view = this.displayLines.get(i);
if (this.cursor >= view.beginIndex && this.cursor <= view.endIndex) {
return i;
}
}
return -1;
}
public MultilineTextField.StringView getLineView(int lineIndex) {
return this.displayLines.get(Mth.clamp(lineIndex, 0, this.displayLines.size() - 1));
}
public void seekCursor(Whence whence, int cursor) {
switch (whence) {
case ABSOLUTE:
this.cursor = cursor;
break;
case RELATIVE:
this.cursor += cursor;
break;
case END:
this.cursor = this.value.length() + cursor;
}
this.cursor = Mth.clamp(this.cursor, 0, this.value.length());
this.cursorListener.run();
if (!this.selecting) {
this.selectCursor = this.cursor;
}
}
public void seekCursorLine(int lineOffset) {
if (lineOffset != 0) {
int oldCursorLeft = this.font.width(this.value.substring(this.getCursorLineView().beginIndex, this.cursor)) + 2;
MultilineTextField.StringView lineView = this.getCursorLineView(lineOffset);
int newCursor = this.font.plainSubstrByWidth(this.value.substring(lineView.beginIndex, lineView.endIndex), oldCursorLeft).length();
this.seekCursor(Whence.ABSOLUTE, lineView.beginIndex + newCursor);
}
}
public void seekCursorToPoint(double x, double y) {
int left = Mth.floor(x);
int top = Mth.floor(y / 9.0);
MultilineTextField.StringView lineView = this.displayLines.get(Mth.clamp(top, 0, this.displayLines.size() - 1));
int clickedColumn = this.font.plainSubstrByWidth(this.value.substring(lineView.beginIndex, lineView.endIndex), left).length();
this.seekCursor(Whence.ABSOLUTE, lineView.beginIndex + clickedColumn);
}
public void selectWordAtCursor() {
MultilineTextField.StringView wordView = this.getPreviousWord();
this.seekCursor(Whence.ABSOLUTE, wordView.beginIndex);
this.setSelecting(true);
this.seekCursor(Whence.ABSOLUTE, wordView.endIndex);
}
public boolean keyPressed(KeyEvent event) {
this.selecting = event.hasShiftDown();
if (event.isSelectAll()) {
this.cursor = this.value.length();
this.selectCursor = 0;
return true;
} else if (event.isCopy()) {
Minecraft.getInstance().keyboardHandler.setClipboard(this.getSelectedText());
return true;
} else if (event.isPaste()) {
this.insertText(Minecraft.getInstance().keyboardHandler.getClipboard());
return true;
} else if (event.isCut()) {
Minecraft.getInstance().keyboardHandler.setClipboard(this.getSelectedText());
this.insertText("");
return true;
} else {
switch (event.key()) {
case 257:
case 335:
this.insertText("\n");
return true;
case 259:
if (event.hasControlDownWithQuirk()) {
MultilineTextField.StringView wordView = this.getPreviousWord();
this.deleteText(wordView.beginIndex - this.cursor);
} else {
this.deleteText(-1);
}
return true;
case 261:
if (event.hasControlDownWithQuirk()) {
MultilineTextField.StringView wordView = this.getNextWord();
this.deleteText(wordView.beginIndex - this.cursor);
} else {
this.deleteText(1);
}
return true;
case 262:
if (event.hasControlDownWithQuirk()) {
MultilineTextField.StringView wordView = this.getNextWord();
this.seekCursor(Whence.ABSOLUTE, wordView.beginIndex);
} else {
this.seekCursor(Whence.RELATIVE, 1);
}
return true;
case 263:
if (event.hasControlDownWithQuirk()) {
MultilineTextField.StringView wordView = this.getPreviousWord();
this.seekCursor(Whence.ABSOLUTE, wordView.beginIndex);
} else {
this.seekCursor(Whence.RELATIVE, -1);
}
return true;
case 264:
if (!event.hasControlDownWithQuirk()) {
this.seekCursorLine(1);
}
return true;
case 265:
if (!event.hasControlDownWithQuirk()) {
this.seekCursorLine(-1);
}
return true;
case 266:
this.seekCursor(Whence.ABSOLUTE, 0);
return true;
case 267:
this.seekCursor(Whence.END, 0);
return true;
case 268:
if (event.hasControlDownWithQuirk()) {
this.seekCursor(Whence.ABSOLUTE, 0);
} else {
this.seekCursor(Whence.ABSOLUTE, this.getCursorLineView().beginIndex);
}
return true;
case 269:
if (event.hasControlDownWithQuirk()) {
this.seekCursor(Whence.END, 0);
} else {
this.seekCursor(Whence.ABSOLUTE, this.getCursorLineView().endIndex);
}
return true;
default:
return false;
}
}
}
public Iterable<MultilineTextField.StringView> iterateLines() {
return this.displayLines;
}
public boolean hasSelection() {
return this.selectCursor != this.cursor;
}
@VisibleForTesting
public String getSelectedText() {
MultilineTextField.StringView selected = this.getSelected();
return this.value.substring(selected.beginIndex, selected.endIndex);
}
private MultilineTextField.StringView getCursorLineView() {
return this.getCursorLineView(0);
}
private MultilineTextField.StringView getCursorLineView(int lineOffset) {
int lineIndex = this.getLineAtCursor();
if (lineIndex < 0) {
LOGGER.error("Cursor is not within text (cursor = {}, length = {})", this.cursor, this.value.length());
return this.displayLines.getLast();
} else {
return this.displayLines.get(Mth.clamp(lineIndex + lineOffset, 0, this.displayLines.size() - 1));
}
}
@VisibleForTesting
public MultilineTextField.StringView getPreviousWord() {
if (this.value.isEmpty()) {
return MultilineTextField.StringView.EMPTY;
} else {
int startPosition = Mth.clamp(this.cursor, 0, this.value.length() - 1);
while (startPosition > 0 && Character.isWhitespace(this.value.charAt(startPosition - 1))) {
startPosition--;
}
while (startPosition > 0 && !Character.isWhitespace(this.value.charAt(startPosition - 1))) {
startPosition--;
}
return new MultilineTextField.StringView(startPosition, this.getWordEndPosition(startPosition));
}
}
@VisibleForTesting
public MultilineTextField.StringView getNextWord() {
if (this.value.isEmpty()) {
return MultilineTextField.StringView.EMPTY;
} else {
int startPosition = Mth.clamp(this.cursor, 0, this.value.length() - 1);
while (startPosition < this.value.length() && !Character.isWhitespace(this.value.charAt(startPosition))) {
startPosition++;
}
while (startPosition < this.value.length() && Character.isWhitespace(this.value.charAt(startPosition))) {
startPosition++;
}
return new MultilineTextField.StringView(startPosition, this.getWordEndPosition(startPosition));
}
}
private int getWordEndPosition(int from) {
int end = from;
while (end < this.value.length() && !Character.isWhitespace(this.value.charAt(end))) {
end++;
}
return end;
}
private void onValueChange() {
this.reflowDisplayLines();
this.valueListener.accept(this.value);
this.cursorListener.run();
}
private void reflowDisplayLines() {
this.displayLines.clear();
if (this.value.isEmpty()) {
this.displayLines.add(MultilineTextField.StringView.EMPTY);
} else {
this.font
.getSplitter()
.splitLines(
this.value, this.width, Style.EMPTY, false, (style, start, end) -> this.displayLines.add(new MultilineTextField.StringView(start, end))
);
if (this.value.charAt(this.value.length() - 1) == '\n') {
this.displayLines.add(new MultilineTextField.StringView(this.value.length(), this.value.length()));
}
}
}
private String truncateFullText(String input) {
return this.hasCharacterLimit() ? StringUtil.truncateStringIfNecessary(input, this.characterLimit, false) : input;
}
private String truncateInsertionText(String input) {
String truncatedInput = input;
if (this.hasCharacterLimit()) {
int remainingCharacters = this.characterLimit - this.value.length();
truncatedInput = StringUtil.truncateStringIfNecessary(input, remainingCharacters, false);
}
return truncatedInput;
}
private boolean overflowsLineLimit(String newValue) {
return this.hasLineLimit()
&& this.font.getSplitter().splitLines(newValue, this.width, Style.EMPTY).size() + (StringUtil.endsWithNewLine(newValue) ? 1 : 0) > this.lineLimit;
}
@OnlyIn(Dist.CLIENT)
protected record StringView(int beginIndex, int endIndex) {
private static final MultilineTextField.StringView EMPTY = new MultilineTextField.StringView(0, 0);
}
}