using System; using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; using UnityEngine; using FairyGUI.Utils; namespace FairyGUI { /// /// 接收用户输入的文本控件。因为支持直接输入表情,所以从RichTextField派生。 /// public class InputTextField : RichTextField { /// /// /// public int maxLength { get; set; } /// /// 如果是true,则当文本获得焦点时,弹出键盘进行输入,如果是false则不会。 /// 默认是使用Stage.keyboardInput的值。 /// public bool keyboardInput { get; set; } /// /// /// public int keyboardType { get; set; } /// /// /// public bool hideInput { get; set; } /// /// /// public bool disableIME { get; set; } /// /// /// public bool mouseWheelEnabled { get; set; } /// /// /// public static Action onCopy; /// /// /// public static Action onPaste; /// /// /// public static PopupMenu contextMenu; string _text; string _restrict; Regex _restrictPattern; bool _displayAsPassword; string _promptText; string _decodedPromptText; int _border; int _corner; Color _borderColor; Color _backgroundColor; bool _editable; bool _editing; int _caretPosition; int _selectionStart; int _composing; char _highSurrogateChar; string _textBeforeEdit; EventListener _onChanged; EventListener _onSubmit; Shape _caret; SelectionShape _selectionShape; float _nextBlink; const int GUTTER_X = 2; const int GUTTER_Y = 2; public InputTextField() { gameObject.name = "InputTextField"; _text = string.Empty; maxLength = 0; _editable = true; _composing = 0; keyboardInput = Stage.keyboardInput; _borderColor = Color.black; _backgroundColor = Color.clear; mouseWheelEnabled = true; this.tabStop = true; cursor = "text-ibeam"; /* 因为InputTextField定义了ClipRect,而ClipRect是四周缩进了2个像素的(GUTTER),默认的点击测试 * 是使用ClipRect的,那会造成无法点击四周的空白区域。所以这里自定义了一个HitArea */ this.hitArea = new RectHitTest(); this.touchChildren = false; onFocusIn.Add(__focusIn); onFocusOut.AddCapture(__focusOut); onKeyDown.Add(__keydown); onTouchBegin.AddCapture(__touchBegin); onTouchMove.AddCapture(__touchMove); onMouseWheel.Add(__mouseWheel); onClick.Add(__click); onRightClick.Add(__rightClick); } /// /// /// public EventListener onChanged { get { return _onChanged ?? (_onChanged = new EventListener(this, "onChanged")); } } /// /// /// public EventListener onSubmit { get { return _onSubmit ?? (_onSubmit = new EventListener(this, "onSubmit")); } } /// /// /// public override string text { get { return _text; } set { _text = value; ClearSelection(); UpdateText(); } } /// /// /// public override TextFormat textFormat { get { return base.textFormat; } set { base.textFormat = value; if (_editing) { _caret.height = textField.textFormat.size; _caret.DrawRect(0, Color.clear, textField.textFormat.color); } } } /// /// /// public string restrict { get { return _restrict; } set { _restrict = value; if (string.IsNullOrEmpty(_restrict)) _restrictPattern = null; else _restrictPattern = new Regex(value); } } /// /// /// public int caretPosition { get { textField.Redraw(); return _caretPosition; } set { SetSelection(value, 0); } } public int selectionBeginIndex { get { return _selectionStart < _caretPosition ? _selectionStart : _caretPosition; } } public int selectionEndIndex { get { return _selectionStart < _caretPosition ? _caretPosition : _selectionStart; } } /// /// /// public string promptText { get { return _promptText; } set { _promptText = value; if (!string.IsNullOrEmpty(_promptText)) _decodedPromptText = UBBParser.inst.Parse(XMLUtils.EncodeString(_promptText)); else _decodedPromptText = null; UpdateText(); } } /// /// /// public bool displayAsPassword { get { return _displayAsPassword; } set { if (_displayAsPassword != value) { _displayAsPassword = value; UpdateText(); } } } /// /// /// public bool editable { get { return _editable; } set { _editable = value; if (_caret != null) _caret.visible = _editable; } } /// /// /// public int border { get { return _border; } set { _border = value; UpdateShape(); } } /// /// /// public int corner { get { return _corner; } set { _corner = value; UpdateShape(); } } /// /// /// public Color borderColor { get { return _borderColor; } set { _borderColor = value; UpdateShape(); } } /// /// /// public Color backgroundColor { get { return _backgroundColor; } set { _backgroundColor = value; UpdateShape(); } } void UpdateShape() { if (_border > 0 || _backgroundColor.a > 0) { CreateGraphics(); graphics.enabled = true; RoundedRectMesh mesh = graphics.GetMeshFactory(); mesh.lineWidth = _border; mesh.lineColor = _borderColor; mesh.fillColor = _backgroundColor; mesh.topLeftRadius = mesh.topRightRadius = mesh.bottomLeftRadius = mesh.bottomRightRadius = corner; graphics.SetMeshDirty(); } else { if (graphics != null) graphics.enabled = false; } } /// /// /// /// /// -1 means the rest count from start public void SetSelection(int start, int length) { if (!_editing) Stage.inst.focus = this; _selectionStart = start; _caretPosition = length < 0 ? int.MaxValue : (start + length); if (!textField.Redraw()) { int cnt = textField.charPositions.Count; if (_caretPosition >= cnt) _caretPosition = cnt - 1; if (_selectionStart >= cnt) _selectionStart = cnt - 1; UpdateCaret(); } } /// /// /// /// public void ReplaceSelection(string value) { if (keyboardInput && Stage.keyboardInput && !Stage.inst.keyboard.supportsCaret) { this.text = _text + value; OnChanged(); return; } if (!_editing) Stage.inst.focus = this; textField.Redraw(); int t0, t1; if (_selectionStart != _caretPosition) { if (_selectionStart < _caretPosition) { t0 = _selectionStart; t1 = _caretPosition; _caretPosition = _selectionStart; } else { t0 = _caretPosition; t1 = _selectionStart; _selectionStart = _caretPosition; } } else { if (string.IsNullOrEmpty(value)) return; t0 = t1 = _caretPosition; } StringBuilder buffer = new StringBuilder(); GetPartialText(0, t0, buffer); if (!string.IsNullOrEmpty(value)) { value = ValidateInput(value); buffer.Append(value); _caretPosition += GetTextlength(value); } GetPartialText(t1 + _composing, -1, buffer); string newText = buffer.ToString(); if (maxLength > 0) { string newText2 = TruncateText(newText, maxLength); if (newText2.Length != newText.Length) _caretPosition += (newText2.Length - newText.Length); newText = newText2; } this.text = newText; OnChanged(); } /// /// /// /// public void ReplaceText(string value) { if (value == _text) return; if (value == null) value = string.Empty; value = ValidateInput(value); if (maxLength > 0) value = TruncateText(value, maxLength); _caretPosition = value.Length; this.text = value; OnChanged(); } void GetPartialText(int startIndex, int endIndex, StringBuilder buffer) { int elementCount = textField.htmlElements.Count; int lastIndex = startIndex; string tt; if (_displayAsPassword) tt = _text; else tt = textField.parsedText; if (endIndex < 0) endIndex = tt.Length; for (int i = 0; i < elementCount; i++) { HtmlElement element = textField.htmlElements[i]; if (element.htmlObject != null && element.text != null) { if (element.charIndex >= startIndex && element.charIndex < endIndex) { buffer.Append(tt.Substring(lastIndex, element.charIndex - lastIndex)); buffer.Append(element.text); lastIndex = element.charIndex + 1; } } } if (lastIndex < tt.Length) buffer.Append(tt.Substring(lastIndex, endIndex - lastIndex)); } int GetTextlength(string value) { int textLen = value.Length; int ret = textLen; for (int i = 0; i < textLen; i++) { if (char.IsHighSurrogate(value[i])) ret--; } return ret; } string TruncateText(string value, int length) { int textLen = value.Length; int len = 0; int i = 0; while (i < textLen) { if (len == length) return value.Substring(0, i); if (char.IsHighSurrogate(value[i])) i++; i++; len++; } return value; } string ValidateInput(string source) { if (_restrict != null) { StringBuilder sb = new StringBuilder(); Match mc = _restrictPattern.Match(source); int lastPos = 0; string s; while (mc != Match.Empty) { if (mc.Index != lastPos) { //保留tab和回车 for (int i = lastPos; i < mc.Index; i++) { if (source[i] == '\n' || source[i] == '\t') sb.Append(source[i]); } } s = mc.ToString(); lastPos = mc.Index + s.Length; sb.Append(s); mc = mc.NextMatch(); } for (int i = lastPos; i < source.Length; i++) { if (source[i] == '\n' || source[i] == '\t') sb.Append(source[i]); } return sb.ToString(); } else return source; } void UpdateText() { if (!_editing && _text.Length == 0 && !string.IsNullOrEmpty(_decodedPromptText)) { textField.htmlText = _decodedPromptText; return; } if (_displayAsPassword) textField.text = EncodePasswordText(_text); else textField.text = _text; _composing = Input.compositionString.Length; if (_composing > 0) { StringBuilder buffer = new StringBuilder(); GetPartialText(0, _caretPosition, buffer); buffer.Append(Input.compositionString); GetPartialText(_caretPosition, -1, buffer); textField.text = buffer.ToString(); } } string EncodePasswordText(string value) { int textLen = value.Length; StringBuilder tmp = new StringBuilder(textLen); int i = 0; while (i < textLen) { char c = value[i]; if (c == '\n') tmp.Append(c); else { if (char.IsHighSurrogate(c)) i++; tmp.Append("*"); } i++; } return tmp.ToString(); } void ClearSelection() { if (_selectionStart != _caretPosition) { if (_selectionShape != null) _selectionShape.Clear(); _selectionStart = _caretPosition; } } public string GetSelection() { if (_selectionStart == _caretPosition) return string.Empty; StringBuilder buffer = new StringBuilder(); if (_selectionStart < _caretPosition) GetPartialText(_selectionStart, _caretPosition, buffer); else GetPartialText(_caretPosition, _selectionStart, buffer); return buffer.ToString(); } void Scroll(int hScroll, int vScroll) { vScroll = Mathf.Clamp(vScroll, 0, textField.lines.Count - 1); TextField.LineInfo line = textField.lines[vScroll]; hScroll = Mathf.Clamp(hScroll, 0, line.charCount - 1); TextField.CharPosition cp = GetCharPosition(line.charIndex + hScroll); Vector2 pt = GetCharLocation(cp); MoveContent(new Vector2(GUTTER_X - pt.x, GUTTER_Y - pt.y), false); } void AdjustCaret(TextField.CharPosition cp, bool moveSelectionHeader = false) { _caretPosition = cp.charIndex; if (moveSelectionHeader) _selectionStart = _caretPosition; UpdateCaret(); } void UpdateCaret(bool forceUpdate = false) { TextField.CharPosition cp; if (_editing) cp = GetCharPosition(_caretPosition + Input.compositionString.Length); else cp = GetCharPosition(_caretPosition); Vector2 pos = GetCharLocation(cp); TextField.LineInfo line = textField.lines[cp.lineIndex]; Vector2 offset = pos + textField.xy; if (offset.x < textField.textFormat.size) offset.x += Mathf.Min(50, _contentRect.width * 0.5f); else if (offset.x > _contentRect.width - GUTTER_X - textField.textFormat.size) offset.x -= Mathf.Min(50, _contentRect.width * 0.5f); if (offset.x < GUTTER_X) offset.x = GUTTER_X; else if (offset.x > _contentRect.width - GUTTER_X) offset.x = Mathf.Max(GUTTER_X, _contentRect.width - GUTTER_X); if (offset.y < GUTTER_Y) offset.y = GUTTER_Y; else if (offset.y + line.height >= _contentRect.height - GUTTER_Y) offset.y = Mathf.Max(GUTTER_Y, _contentRect.height - line.height - GUTTER_Y); MoveContent(offset - pos, forceUpdate); if (_editing) { _caret.position = textField.xy + pos; _caret.height = line.height > 0 ? line.height : textField.textFormat.size; if (_editable) { Vector2 cursorPos = _caret.LocalToWorld(new Vector2(0, _caret.height)); cursorPos = StageCamera.main.WorldToScreenPoint(cursorPos); #if !UNITY_2019_OR_NEWER if (Stage.devicePixelRatio == 1) { #endif cursorPos.y = Screen.height - cursorPos.y; cursorPos = cursorPos / Stage.devicePixelRatio; Input.compositionCursorPos = cursorPos + new Vector2(0, 20); #if !UNITY_2019_OR_NEWER } else Input.compositionCursorPos = cursorPos - new Vector2(0, 20); #endif } _nextBlink = Time.time + 0.5f; _caret.graphics.enabled = true; UpdateSelection(cp); } } void MoveContent(Vector2 pos, bool forceUpdate) { float ox = textField.x; float oy = textField.y; float nx = pos.x; float ny = pos.y; float rectWidth = _contentRect.width - 1; //-1 to avoid cursor be clipped if (rectWidth - nx > textField.textWidth) nx = rectWidth - textField.textWidth; if (_contentRect.height - ny > textField.textHeight) ny = _contentRect.height - textField.textHeight; if (nx > 0) nx = 0; if (ny > 0) ny = 0; nx = (int)nx; ny = (int)ny; if (nx != ox || ny != oy || forceUpdate) { if (_caret != null) { _caret.SetXY(nx + _caret.x - ox, ny + _caret.y - oy); _selectionShape.SetXY(nx, ny); } textField.SetXY(nx, ny); List elements = textField.htmlElements; int count = elements.Count; for (int i = 0; i < count; i++) { HtmlElement element = elements[i]; if (element.htmlObject != null) element.htmlObject.SetPosition(element.position.x + nx, element.position.y + ny); } } } void UpdateSelection(TextField.CharPosition cp) { if (_selectionStart == _caretPosition) { _selectionShape.Clear(); return; } TextField.CharPosition start; if (_editing && Input.compositionString.Length > 0) { if (_selectionStart < _caretPosition) { cp = GetCharPosition(_caretPosition); start = GetCharPosition(_selectionStart); } else start = GetCharPosition(_selectionStart + Input.compositionString.Length); } else start = GetCharPosition(_selectionStart); if (start.charIndex > cp.charIndex) { TextField.CharPosition tmp = start; start = cp; cp = tmp; } Vector2 v1 = GetCharLocation(start); Vector2 v2 = GetCharLocation(cp); _selectionShape.rects.Clear(); textField.GetLinesShape(start.lineIndex, v1.x, cp.lineIndex, v2.x, false, _selectionShape.rects); _selectionShape.Refresh(); } TextField.CharPosition GetCharPosition(int caretIndex) { if (caretIndex < 0) caretIndex = 0; else if (caretIndex >= textField.charPositions.Count) caretIndex = textField.charPositions.Count - 1; return textField.charPositions[caretIndex]; } /// /// 通过本地坐标获得字符索引位置 /// /// 本地坐标 /// TextField.CharPosition GetCharPosition(Vector2 location) { if (textField.charPositions.Count <= 1) return textField.charPositions[0]; location.x -= textField.x; location.y -= textField.y; List lines = textField.lines; int len = lines.Count; TextField.LineInfo line; int i; for (i = 0; i < len; i++) { line = lines[i]; if (line.y + line.height > location.y) break; } if (i == len) i = len - 1; int lineIndex = i; len = textField.charPositions.Count; TextField.CharPosition v; int firstInLine = -1; for (i = 0; i < len; i++) { v = textField.charPositions[i]; if (v.lineIndex == lineIndex) { if (firstInLine == -1) firstInLine = i; if (v.offsetX + v.width * 0.5f > location.x) return v; } else if (firstInLine != -1) return v; } return textField.charPositions[i - 1]; } /// /// 获得字符的坐标。 /// /// /// Vector2 GetCharLocation(TextField.CharPosition cp) { TextField.LineInfo line = textField.lines[cp.lineIndex]; Vector2 pos; if (line.charCount == 0 || textField.charPositions.Count == 0) { if (textField.align == AlignType.Center) pos.x = (int)(_contentRect.width / 2); else pos.x = GUTTER_X; } else { TextField.CharPosition v = textField.charPositions[Math.Min(cp.charIndex, textField.charPositions.Count - 1)]; pos.x = v.offsetX; } pos.y = line.y; return pos; } override internal void RefreshObjects() { base.RefreshObjects(); if (_editing) { SetChildIndex(_selectionShape, 0); SetChildIndex(_caret, this.numChildren - 1); } int cnt = textField.charPositions.Count; if (_caretPosition >= cnt) _caretPosition = cnt - 1; if (_selectionStart >= cnt) _selectionStart = cnt - 1; UpdateCaret(true); } protected void OnChanged() { DispatchEvent("onChanged", null); TextInputHistory.inst.MarkChanged(this); } protected override void OnSizeChanged() { base.OnSizeChanged(); Rect rect = _contentRect; rect.x += GUTTER_X; rect.y += GUTTER_Y; rect.width -= GUTTER_X * 2; rect.height -= GUTTER_Y * 2; this.clipRect = rect; ((RectHitTest)this.hitArea).rect = _contentRect; } public override void Update(UpdateContext context) { base.Update(context); if (_editing) { if (_nextBlink < Time.time) { _nextBlink = Time.time + 0.5f; _caret.graphics.enabled = !_caret.graphics.enabled; } } } public override void Dispose() { if ((_flags & Flags.Disposed) != 0) return; _editing = false; if (_caret != null) { _caret.Dispose(); _selectionShape.Dispose(); } base.Dispose(); } void DoCopy(string value) { if (onCopy != null) { onCopy(this, value); return; } #if UNITY_WEBPLAYER || UNITY_WEBGL || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_EDITOR TextEditor textEditor = new TextEditor(); #if UNITY_5_3_OR_NEWER textEditor.text = value; #else textEditor.content = new GUIContent(value); #endif textEditor.OnFocus(); textEditor.Copy(); #endif } void DoPaste() { if (onPaste != null) { onPaste(this); return; } #if UNITY_WEBPLAYER || UNITY_WEBGL || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_EDITOR TextEditor textEditor = new TextEditor(); #if UNITY_5_3_OR_NEWER textEditor.text = string.Empty; #else textEditor.content = new GUIContent(string.Empty); #endif textEditor.multiline = !textField.singleLine; textEditor.Paste(); #if UNITY_5_3_OR_NEWER string value = textEditor.text; #else string value = textEditor.content.text; #endif if (!string.IsNullOrEmpty(value)) ReplaceSelection(value); #endif } void CreateCaret() { _caret = new Shape(); _caret.gameObject.name = "Caret"; _caret.touchable = false; _caret._flags |= Flags.SkipBatching; _caret.xy = textField.xy; _selectionShape = new SelectionShape(); _selectionShape.gameObject.name = "Selection"; _selectionShape.color = UIConfig.inputHighlightColor; _selectionShape._flags |= Flags.SkipBatching; _selectionShape.touchable = false; _selectionShape.xy = textField.xy; } void __touchBegin(EventContext context) { if (!_editing || textField.charPositions.Count <= 1 || keyboardInput && Stage.keyboardInput && !Stage.inst.keyboard.supportsCaret || context.inputEvent.button != 0) return; ClearSelection(); Vector3 v = Stage.inst.touchPosition; v = this.GlobalToLocal(v); TextField.CharPosition cp = GetCharPosition(v); AdjustCaret(cp, true); context.CaptureTouch(); } void __touchMove(EventContext context) { if (!_editing) return; Vector3 v = Stage.inst.touchPosition; v = this.GlobalToLocal(v); if (float.IsNaN(v.x)) return; TextField.CharPosition cp = GetCharPosition(v); if (cp.charIndex != _caretPosition) AdjustCaret(cp); } void __mouseWheel(EventContext context) { if (_editing && mouseWheelEnabled) { context.StopPropagation(); TextField.CharPosition cp = GetCharPosition(new Vector2(GUTTER_X, GUTTER_Y)); int vScroll = cp.lineIndex; int hScroll = cp.charIndex - textField.lines[cp.lineIndex].charIndex; if (context.inputEvent.mouseWheelDelta < 0) vScroll--; else vScroll++; Scroll(hScroll, vScroll); } } void __focusIn(EventContext context) { if (!Application.isPlaying) return; _editing = true; _textBeforeEdit = _text; if (_caret == null) CreateCaret(); if (!string.IsNullOrEmpty(_promptText)) UpdateText(); float caretSize; //如果界面缩小过,光标很容易看不见,这里放大一下 if (UIConfig.inputCaretSize == 1 && UIContentScaler.scaleFactor < 1) caretSize = UIConfig.inputCaretSize / UIContentScaler.scaleFactor; else caretSize = UIConfig.inputCaretSize; _caret.SetSize(caretSize, textField.textFormat.size); _caret.DrawRect(0, Color.clear, textField.textFormat.color); _caret.visible = _editable; AddChild(_caret); _selectionShape.Clear(); AddChildAt(_selectionShape, 0); if (!textField.Redraw()) { TextField.CharPosition cp = GetCharPosition(_caretPosition); AdjustCaret(cp); } if (Stage.keyboardInput) { if (keyboardInput) { if (_editable) Stage.inst.OpenKeyboard(_text, false, _displayAsPassword ? false : !textField.singleLine, _displayAsPassword, false, null, keyboardType, hideInput); SetSelection(0, -1); } } else { if (!disableIME && !_displayAsPassword) Input.imeCompositionMode = IMECompositionMode.On; else Input.imeCompositionMode = IMECompositionMode.Off; _composing = 0; if ((string)context.data == "key") //select all if got focus by tab key SetSelection(0, -1); TextInputHistory.inst.StartRecord(this); } } void __focusOut(EventContext contxt) { if (!_editing) return; _editing = false; if (Stage.keyboardInput) { if (keyboardInput) Stage.inst.CloseKeyboard(); } else { Input.imeCompositionMode = IMECompositionMode.Auto; TextInputHistory.inst.StopRecord(this); } if (!string.IsNullOrEmpty(_promptText)) UpdateText(); _caret.RemoveFromParent(); _selectionShape.RemoveFromParent(); if (contextMenu != null && contextMenu.contentPane.onStage) contextMenu.Hide(); } void __keydown(EventContext context) { if (!_editing) return; if (HandleKey(context.inputEvent)) context.StopPropagation(); } bool HandleKey(InputEvent evt) { bool keyCodeHandled = true; switch (evt.keyCode) { case KeyCode.Backspace: { if (evt.command) { //for mac:CMD+Backspace=Delete if (_selectionStart == _caretPosition && _caretPosition < textField.charPositions.Count - 1) _selectionStart = _caretPosition + 1; } else { if (_selectionStart == _caretPosition && _caretPosition > 0) _selectionStart = _caretPosition - 1; } if (_editable) ReplaceSelection(null); break; } case KeyCode.Delete: { if (_selectionStart == _caretPosition && _caretPosition < textField.charPositions.Count - 1) _selectionStart = _caretPosition + 1; if (_editable) ReplaceSelection(null); break; } case KeyCode.LeftArrow: { if (!evt.shift) ClearSelection(); if (_caretPosition > 0) { if (evt.command) //mac keyboard { TextField.CharPosition cp = GetCharPosition(_caretPosition); TextField.LineInfo line = textField.lines[cp.lineIndex]; cp = GetCharPosition(new Vector2(int.MinValue, line.y + textField.y)); AdjustCaret(cp, !evt.shift); } else { TextField.CharPosition cp = GetCharPosition(_caretPosition - 1); AdjustCaret(cp, !evt.shift); } } break; } case KeyCode.RightArrow: { if (!evt.shift) ClearSelection(); if (_caretPosition < textField.charPositions.Count - 1) { if (evt.command) { TextField.CharPosition cp = GetCharPosition(_caretPosition); TextField.LineInfo line = textField.lines[cp.lineIndex]; cp = GetCharPosition(new Vector2(int.MaxValue, line.y + textField.y)); AdjustCaret(cp, !evt.shift); } else { TextField.CharPosition cp = GetCharPosition(_caretPosition + 1); AdjustCaret(cp, !evt.shift); } } break; } case KeyCode.UpArrow: { if (!evt.shift) ClearSelection(); TextField.CharPosition cp = GetCharPosition(_caretPosition); if (cp.lineIndex > 0) { TextField.LineInfo line = textField.lines[cp.lineIndex - 1]; cp = GetCharPosition(new Vector2(_caret.x, line.y + textField.y)); AdjustCaret(cp, !evt.shift); } break; } case KeyCode.DownArrow: { if (!evt.shift) ClearSelection(); TextField.CharPosition cp = GetCharPosition(_caretPosition); if (cp.lineIndex == textField.lines.Count - 1) cp.charIndex = textField.charPositions.Count - 1; else { TextField.LineInfo line = textField.lines[cp.lineIndex + 1]; cp = GetCharPosition(new Vector2(_caret.x, line.y + textField.y)); } AdjustCaret(cp, !evt.shift); break; } case KeyCode.PageUp: { ClearSelection(); break; } case KeyCode.PageDown: { ClearSelection(); break; } case KeyCode.Home: { if (!evt.shift) ClearSelection(); TextField.CharPosition cp = GetCharPosition(_caretPosition); TextField.LineInfo line = textField.lines[cp.lineIndex]; cp = GetCharPosition(new Vector2(int.MinValue, line.y + textField.y)); AdjustCaret(cp, !evt.shift); break; } case KeyCode.End: { if (!evt.shift) ClearSelection(); TextField.CharPosition cp = GetCharPosition(_caretPosition); TextField.LineInfo line = textField.lines[cp.lineIndex]; cp = GetCharPosition(new Vector2(int.MaxValue, line.y + textField.y)); AdjustCaret(cp, !evt.shift); break; } //Select All case KeyCode.A: { if (evt.ctrlOrCmd) { _selectionStart = 0; AdjustCaret(GetCharPosition(int.MaxValue)); } break; } //Copy case KeyCode.C: { if (evt.ctrlOrCmd && !_displayAsPassword) { string s = GetSelection(); if (!string.IsNullOrEmpty(s)) DoCopy(s); } break; } //Paste case KeyCode.V: { if (evt.ctrlOrCmd && _editable) DoPaste(); break; } //Cut case KeyCode.X: { if (evt.ctrlOrCmd && !_displayAsPassword) { string s = GetSelection(); if (!string.IsNullOrEmpty(s)) { DoCopy(s); if (_editable) ReplaceSelection(null); } } break; } case KeyCode.Z: { if (evt.ctrlOrCmd && _editable) { if (evt.shift) TextInputHistory.inst.Redo(this); else TextInputHistory.inst.Undo(this); } break; } case KeyCode.Y: { if (evt.ctrlOrCmd && _editable) TextInputHistory.inst.Redo(this); break; } case KeyCode.Return: case KeyCode.KeypadEnter: { if (textField.singleLine) { Stage.inst.focus = parent; DispatchEvent("onSubmit", null); DispatchEvent("onKeyDown", null); //for backward compatibility } break; } case KeyCode.Tab: { if (textField.singleLine) { Stage.inst.DoKeyNavigate(evt.shift); keyCodeHandled = false; } break; } case KeyCode.Escape: { this.text = _textBeforeEdit; Stage.inst.focus = parent; break; } default: keyCodeHandled = (int)evt.keyCode <= 272 && !evt.ctrlOrCmd; break; } char c = evt.character; if (c != 0) { if (evt.ctrlOrCmd) return true; if (c == '\r' || c == 3) c = '\n'; if (c == 25)/*shift+tab*/ c = '\t'; if (c == 27/*escape*/ || textField.singleLine && (c == '\n' || c == '\t')) return true; if (char.IsHighSurrogate(c)) { _highSurrogateChar = c; return true; } if (_editable) { if (char.IsLowSurrogate(c)) ReplaceSelection(char.ConvertFromUtf32(((int)c & 0x03FF) + ((((int)_highSurrogateChar & 0x03FF) + 0x40) << 10))); else ReplaceSelection(c.ToString()); } return true; } else { if (Input.compositionString.Length > 0 && _editable) { int composing = _composing; _composing = Input.compositionString.Length; StringBuilder buffer = new StringBuilder(); GetPartialText(0, _caretPosition, buffer); buffer.Append(Input.compositionString); GetPartialText(_caretPosition + composing, -1, buffer); textField.text = buffer.ToString(); } return keyCodeHandled; } } internal void CheckComposition() { if (_composing != 0 && Input.compositionString.Length == 0) UpdateText(); } void __click(EventContext context) { if (_editing && context.inputEvent.isDoubleClick) { context.StopPropagation(); _selectionStart = 0; AdjustCaret(GetCharPosition(int.MaxValue)); } } void __rightClick(EventContext context) { if (contextMenu != null) { context.StopPropagation(); contextMenu.Show(); } } } class TextInputHistory { static TextInputHistory _inst; public static TextInputHistory inst { get { if (_inst == null) _inst = new TextInputHistory(); return _inst; } } List _undoBuffer; List _redoBuffer; string _currentText; InputTextField _textField; bool _lock; int _changedFrame; public const int maxHistoryLength = 5; public TextInputHistory() { _undoBuffer = new List(); _redoBuffer = new List(); } public void StartRecord(InputTextField textField) { _undoBuffer.Clear(); _redoBuffer.Clear(); _textField = textField; _lock = false; _currentText = textField.text; _changedFrame = 0; } public void MarkChanged(InputTextField textField) { if (_textField != textField) return; if (_lock) return; string newText = _textField.text; if (_currentText == newText) return; if (_changedFrame != Time.frameCount) { _changedFrame = Time.frameCount; _undoBuffer.Add(_currentText); if (_undoBuffer.Count > maxHistoryLength) _undoBuffer.RemoveAt(0); } else { int cnt = _undoBuffer.Count; if (cnt > 0 && newText == _undoBuffer[cnt - 1]) _undoBuffer.RemoveAt(cnt - 1); } _currentText = newText; } public void StopRecord(InputTextField textField) { if (_textField != textField) return; _undoBuffer.Clear(); _redoBuffer.Clear(); _textField = null; _currentText = null; } public void Undo(InputTextField textField) { if (_textField != textField) return; if (_undoBuffer.Count == 0) return; string text = _undoBuffer[_undoBuffer.Count - 1]; _undoBuffer.RemoveAt(_undoBuffer.Count - 1); _redoBuffer.Add(_currentText); _lock = true; int caretPos = _textField.caretPosition; _textField.text = text; int dlen = text.Length - _currentText.Length; if (dlen < 0) _textField.caretPosition = caretPos + dlen; _currentText = text; _lock = false; } public void Redo(InputTextField textField) { if (_textField != textField) return; if (_redoBuffer.Count == 0) return; string text = _redoBuffer[_redoBuffer.Count - 1]; _redoBuffer.RemoveAt(_redoBuffer.Count - 1); _undoBuffer.Add(_currentText); _lock = true; int caretPos = _textField.caretPosition; _textField.text = text; int dlen = text.Length - _currentText.Length; if (dlen > 0) _textField.caretPosition = caretPos + dlen; _currentText = text; _lock = false; } } }