1
0
Fork 0
mirror of https://github.com/beefytech/Beef.git synced 2025-06-10 20:42:21 +02:00
Beef/BeefLibs/Beefy2D/src/widgets/EditWidget.bf
2019-10-29 04:56:42 -07:00

3445 lines
No EOL
107 KiB
Beef

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Threading;
using Beefy.events;
using Beefy.gfx;
using Beefy.utils;
using System.IO;
//#define INCLUDE_CHARDATA_CHARID
namespace Beefy.widgets
{
public struct EditSelection
{
public int32 mStartPos;
public int32 mEndPos;
public this(int startPos = 0, int endPos = 0)
{
mStartPos = (int32)startPos;
mEndPos = (int32)endPos;
}
public EditSelection Clone()
{
EditSelection aSelection = EditSelection();
aSelection.mStartPos = mStartPos;
aSelection.mEndPos = mEndPos;
return aSelection;
}
public bool HasSelection
{
get { return (mStartPos != mEndPos); }
}
public bool IsForwardSelect
{
get { return (mStartPos < mEndPos); }
}
public int MinPos
{
get { return (IsForwardSelect) ? mStartPos : mEndPos; }
}
public int MaxPos
{
get { return (IsForwardSelect) ? mEndPos : mStartPos; }
}
public int Length
{
get { return Math.Abs(mEndPos - mStartPos); }
}
public void GetAsForwardSelect(out int startPos, out int endPos)
{
if (IsForwardSelect)
{
startPos = mStartPos;
endPos = mEndPos;
}
else
{
startPos = mEndPos;
endPos = mStartPos;
}
}
public void MakeForwardSelect() mut
{
if (mEndPos < mStartPos)
{
int32 swap = mStartPos;
mStartPos = mEndPos;
mEndPos = swap;
}
}
}
public delegate void EditWidgetEventHandler(EditEvent theEvent);
public class EditWidgetContent : Widget
{
public enum TextFlags
{
Wrap = 1
};
//[StructLayout(LayoutKind.Explicit) ]
public struct CharData
{
public char8 mChar;
public uint8 mDisplayPassId;
public uint8 mDisplayTypeId; // For colorization, for example
public uint8 mDisplayFlags;
}
public class TextAction : UndoAction
{
public EditWidgetContent.Data mEditWidgetContentData;
public EditSelection? mSelection;
public bool mRestoreSelectionOnUndo;
public bool mMoveCursor = true;
public int32 mPrevTextVersionId;
public int32 mCursorTextPos;
public LineAndColumn? mVirtualCursorPos;
// Return either the focused edit widget content, or the last one added
public EditWidgetContent EditWidgetContent
{
get
{
EditWidgetContent editWidgetContent = null;
for (var user in mEditWidgetContentData.mUsers)
{
editWidgetContent = user;
if (editWidgetContent.mEditWidget.mHasFocus)
break;
}
return editWidgetContent;
}
}
public this(EditWidgetContent editWidget)
{
mPrevTextVersionId = editWidget.mData.mCurTextVersionId;
mEditWidgetContentData = editWidget.mData;
if (editWidget.HasSelection())
mSelection = editWidget.mSelection;
mCursorTextPos = (int32)editWidget.CursorTextPos;
mVirtualCursorPos = editWidget.mVirtualCursorPos;
}
public void SetPreviousState(bool force)
{
var editWidgetContent = EditWidgetContent;
mEditWidgetContentData.mCurTextVersionId = mPrevTextVersionId;
if ((mRestoreSelectionOnUndo) || (force))
editWidgetContent.mSelection = mSelection;
else
editWidgetContent.mSelection = null;
editWidgetContent.mCursorTextPos = mCursorTextPos;
editWidgetContent.mVirtualCursorPos = mVirtualCursorPos;
if (mMoveCursor)
editWidgetContent.EnsureCursorVisible();
}
}
public class SetCursorAction : TextAction
{
public this(EditWidgetContent editWidget) : base(editWidget)
{
mRestoreSelectionOnUndo = true;
mMoveCursor = false;
}
public override bool Undo()
{
SetPreviousState(false);
return true;
}
public override bool Redo()
{
SetPreviousState(true);
return true;
}
}
public class TextUndoBatchStart : TextAction, IUndoBatchStart
{
public String mName;
public UndoBatchEnd mBatchEnd;
public String Name
{
get
{
return mName;
}
}
public this(EditWidgetContent editWidget, String name)
: base(editWidget)
{
mName = name;
mBatchEnd = new UndoBatchEnd();
mBatchEnd.mBatchStart = this;
}
public override bool Undo()
{
SetPreviousState(false);
return true;
}
public IUndoBatchEnd BatchEnd
{
get
{
return mBatchEnd;
}
}
}
public class DeleteSelectionAction : TextAction
{
public String mSelectionText;
public this(EditWidgetContent editWidgetContent)
: base(editWidgetContent)
{
if (mSelection != null)
{
mSelectionText = new String();
editWidgetContent.GetSelectionText(mSelectionText);
}
}
~this()
{
delete mSelectionText;
}
public override bool Undo()
{
var editWidgetContent = EditWidgetContent;
if (mSelection != null)
{
bool wantSnapScroll = mEditWidgetContentData.mTextLength == 0;
editWidgetContent.CursorTextPos = mSelection.Value.MinPos;
editWidgetContent.PhysInsertAtCursor(mSelectionText, mMoveCursor);
if (wantSnapScroll)
editWidgetContent.mEditWidget.FinishScroll();
}
SetPreviousState(false);
return true;
}
public override bool Redo()
{
SetPreviousState(true);
if (mSelection != null)
EditWidgetContent.PhysDeleteSelection(mMoveCursor);
return true;
}
}
public class IndentTextAction : TextAction
{
// InsertCharList is for block indent, RemoveCharList is for unindent (shift-tab)
public List<Tuple<int32, char8>> mRemoveCharList = new List<Tuple<int32, char8>>() ~ delete _;
public List<Tuple<int32, char8>> mInsertCharList = new List<Tuple<int32, char8>>() ~ delete _;
public EditSelection mNewSelection;
public this(EditWidgetContent editWidget)
: base(editWidget)
{
}
public override bool Undo()
{
var editWidgetContent = EditWidgetContent;
for (int idx = mRemoveCharList.Count - 1; idx >= 0; idx--)
{
var idxChar = mRemoveCharList[idx];
editWidgetContent.InsertText(idxChar.Item1, ToStackString!(idxChar.Item2));
}
for (int idx = mInsertCharList.Count - 1; idx >= 0; idx--)
{
var idxChar = mInsertCharList[idx];
editWidgetContent.RemoveText(idxChar.Item1, 1);
}
editWidgetContent.ContentChanged();
SetPreviousState(false);
return true;
}
public override bool Redo()
{
var editWidgetContent = EditWidgetContent;
SetPreviousState(true);
for (var idxChar in mRemoveCharList)
editWidgetContent.RemoveText(idxChar.Item1, 1);
var charStr = scope String(' ', 1);
for (var idxChar in mInsertCharList)
{
charStr[0] = idxChar.Item2;
editWidgetContent.InsertText(idxChar.Item1, charStr);
}
editWidgetContent.ContentChanged();
editWidgetContent.mSelection = mNewSelection;
editWidgetContent.CursorTextPos = mNewSelection.mEndPos;
return true;
}
}
public class InsertTextAction : DeleteSelectionAction
{
public String mText ~ delete _;
public this(EditWidgetContent editWidget, String text, InsertFlags insertFlags = .NoMoveCursor)
: base(editWidget)
{
mText = new String(text);
mRestoreSelectionOnUndo = !insertFlags.HasFlag(.NoRestoreSelectionOnUndo);
}
public override bool Merge(UndoAction nextAction)
{
InsertTextAction insertTextAction = nextAction as InsertTextAction;
if (insertTextAction == null)
return false;
if (insertTextAction.mSelection != null)
{
if (mSelection == null)
return false;
if (mSelection.Value.mEndPos != insertTextAction.mSelection.Value.mStartPos)
return false;
mSelection.ValueRef.mEndPos = insertTextAction.mSelection.Value.mEndPos;
mSelectionText.Append(insertTextAction.mSelectionText);
}
int curIdx = mCursorTextPos;
int nextIdx = insertTextAction.mCursorTextPos;
mRestoreSelectionOnUndo &= insertTextAction.mRestoreSelectionOnUndo;
if ((nextIdx != curIdx + mText.Length) ||
(mText.EndsWith("\n")) ||
(insertTextAction.mText == "\n"))
return false;
mText.Append(insertTextAction.mText);
return true;
}
public override bool Undo()
{
var editWidgetContent = EditWidgetContent;
int startIdx = (mSelection != null) ? mSelection.Value.MinPos : mCursorTextPos;
editWidgetContent.RemoveText(startIdx, (int32)mText.Length);
editWidgetContent.ContentChanged();
base.Undo();
editWidgetContent.ContentChanged();
editWidgetContent.mData.mCurTextVersionId = mPrevTextVersionId;
//mEditWidget.mVirtualCursorPos = mPrevVirtualCursorPos;
return true;
}
public override bool Redo()
{
var editWidgetContent = EditWidgetContent;
base.Redo();
bool wantSnapScroll = mEditWidgetContentData.mTextLength == 0;
editWidgetContent.PhysInsertAtCursor(mText, mMoveCursor);
if (wantSnapScroll)
editWidgetContent.mEditWidget.FinishScroll();
return true;
}
}
public class DeleteCharAction : TextAction
{
String mText ~ delete _;
int32 mOffset;
public this(EditWidgetContent editWidget, int offset, int count) :
base(editWidget)
{
var editWidgetContent = EditWidgetContent;
mOffset = (int32)offset;
int32 textPos = mCursorTextPos;
mText = new String(count);
editWidgetContent.ExtractString(textPos + offset, count, mText);
}
public override bool Merge(UndoAction nextAction)
{
DeleteCharAction deleteCharAction = nextAction as DeleteCharAction;
if (deleteCharAction == null)
return false;
if ((deleteCharAction.mOffset < 0) != (mOffset < 0))
return false;
int32 curIdx = mCursorTextPos;
int32 nextIdx = deleteCharAction.mCursorTextPos;
if (nextIdx != curIdx + mOffset)
return false;
mOffset += deleteCharAction.mOffset;
if (mOffset < 0)
{
//mText = deleteCharAction.mText + mText;
mText.Insert(0, deleteCharAction.mText);
}
else
{
//mText = mText + deleteCharAction.mText;
mText.Append(deleteCharAction.mText);
}
return true;
}
public override bool Undo()
{
int32 textPos = mCursorTextPos;
var editWidgetContent = EditWidgetContent;
editWidgetContent.CursorTextPos = textPos + mOffset;
editWidgetContent.PhysInsertAtCursor(mText, mMoveCursor);
editWidgetContent.ContentChanged();
SetPreviousState(false);
return true;
}
public override bool Redo()
{
var editWidgetContent = EditWidgetContent;
SetPreviousState(true);
editWidgetContent.PhysDeleteChars(mOffset, (int32)mText.Length);
editWidgetContent.ContentChanged();
return true;
}
}
public struct LineAndColumn
{
public int32 mLine;
public int32 mColumn;
public this(int line, int column)
{
mLine = (int32)line;
mColumn = (int32)column;
}
}
public class Data
{
public CharData[] mText = new CharData[1] ~ delete _;
public int32 mTextLength;
public IdSpan mTextIdData = IdSpan.Empty ~ _.Dispose();
public uint8[] mTextFlags ~ delete _;
public List<int32> mLineStarts ~ delete _;
public UndoManager mUndoManager = new UndoManager() ~ delete _;
public int32 mNextCharId = 1; //
public int32 mCurTextVersionId = 1; // Changes when text is modified
//public int mCurComplexChangeId = 1; // Changes when text is modified by more than a single-char8acter insertion or deletion
public List<EditWidgetContent> mUsers = new List<EditWidgetContent>() ~ delete _;
public ~this()
{
}
public void Ref(EditWidgetContent content)
{
mUsers.Add(content);
}
public void Deref(EditWidgetContent content)
{
mUsers.Remove(content);
if (mUsers.Count == 0)
delete this;
}
public char8 SafeGetChar(int idx)
{
if ((idx < 0) || (idx >= mTextLength))
return 0;
return mText[idx].mChar;
}
public char32 GetChar32(int idx)
{
int checkIdx = idx;
char32 c = (char32)SafeGetChar(checkIdx++);
if (c < (char32)0x80)
return c;
int8 trailingBytes = UTF8.sTrailingBytesForUTF8[c];
switch (trailingBytes)
{
case 3: c <<= 6; c += (int)SafeGetChar(checkIdx++); fallthrough;
case 2: c <<= 6; c += (int)SafeGetChar(checkIdx++); fallthrough;
case 1: c <<= 6; c += (int)SafeGetChar(checkIdx++); fallthrough;
}
return c - (int32)UTF8.sOffsetsFromUTF8[trailingBytes];
}
public char32 GetChar32(ref int idx)
{
char32 c = (char32)SafeGetChar(idx++);
if (c < (char32)0x80)
return c;
int8 trailingBytes = UTF8.sTrailingBytesForUTF8[c];
switch (trailingBytes)
{
case 3: c <<= 6; c += (int)SafeGetChar(idx++); fallthrough;
case 2: c <<= 6; c += (int)SafeGetChar(idx++); fallthrough;
case 1: c <<= 6; c += (int)SafeGetChar(idx++); fallthrough;
}
return c - (int32)UTF8.sOffsetsFromUTF8[trailingBytes];
}
}
public Data mData ~ _.Deref(this);
public Insets mTextInsets = new Insets() ~ delete _;
public float mHorzJumpSize = 1;
public bool mAutoHorzScroll = true;
public float mShowLineBottomPadding;
public bool mContentChanged;
public EditWidget mEditWidget;
public float mTabSize;
public float mCharWidth = -1;
public int32 mTabLength = 4;
public uint8 mExtendDisplayFlags;
public uint8 mInsertDisplayFlags;
public int32 mCursorBlinkTicks;
public bool mCursorImplicitlyMoved;
public bool mJustInsertedCharPair; // Pressing backspace will delete last char8, even though cursor is between char8 pairs (ie: for brace pairs 'speculatively' inserted)
public EditSelection? mSelection;
public EditSelection? mDragSelectionUnion; // For double-clicking a word and then "dragging" the selection
public bool mIsReadOnly = false;
public bool mWantsUndo = true;
public bool mIsMultiline = false;
public bool mWordWrap = false;
public float mCursorWantX; // For keyboard cursor selection, accounting for when we truncate to line end
public bool mOverTypeMode = false;
public int32 mCursorTextPos;
public bool mShowCursorAtLineEnd;
public LineAndColumn? mVirtualCursorPos;
public bool mEnsureCursorVisibleOnModify = true;
public bool mAllowVirtualCursor;
public bool mAllowMaximalScroll = true; // Allows us to scroll down such that edit widget is blank except for one line of content at the top
public float mMaximalScrollAddedHeight; // 0 is mAllowMaximalScroll is false
public int CursorTextPos
{
get
{
if (mCursorTextPos == -1)
{
float x;
float y;
GetTextCoordAtLineAndColumn(mVirtualCursorPos.Value.mLine, mVirtualCursorPos.Value.mColumn, out x, out y);
int line;
int lineChar;
float overflowX;
GetLineCharAtCoord(x, y, out line, out lineChar, out overflowX);
mCursorTextPos = (int32)GetTextIdx(line, lineChar);
}
return mCursorTextPos;
}
set
{
Debug.Assert(value >= 0);
mVirtualCursorPos = null;
mCursorTextPos = (int32)value;
}
}
public LineAndColumn CursorLineAndColumn
{
get
{
if (mVirtualCursorPos.HasValue)
return mVirtualCursorPos.Value;
LineAndColumn lineAndColumn;
int line;
int lineChar;
GetLineCharAtIdx(mCursorTextPos, out line, out lineChar);
float x;
float y;
GetTextCoordAtLineChar(line, lineChar, out x, out y);
int coordLine;
int coordLineColumn;
GetLineAndColumnAtCoord(x, y, out coordLine, out coordLineColumn);
lineAndColumn.mLine = (int32)coordLine;
lineAndColumn.mColumn = (int32)coordLineColumn;
return lineAndColumn;
}
set
{
mVirtualCursorPos = value;
mCursorTextPos = -1;
Debug.Assert(mAllowVirtualCursor);
Debug.Assert(mVirtualCursorPos.Value.mColumn >= 0);
}
}
public bool WantsUndo
{
get
{
return (!mIsReadOnly) && (mWantsUndo);
}
}
public this(EditWidgetContent refContent = null)
{
if (refContent != null)
mData = refContent.mData;
else
mData = CreateEditData();
mData.Ref(this);
mContentChanged = true;
}
protected virtual Data CreateEditData()
{
return new Data();
}
public uint8[] mTempCopyArr;
public virtual bool CheckReadOnly()
{
return mIsReadOnly;
}
public bool TryGetCursorTextPos(out int textPos, out float overflowX)
{
if (mVirtualCursorPos.HasValue)
{
float x;
float y;
GetTextCoordAtLineAndColumn(mVirtualCursorPos.Value.mLine, mVirtualCursorPos.Value.mColumn, out x, out y);
int line;
int lineChar;
bool success = GetLineCharAtCoord(x, y, out line, out lineChar, out overflowX);
textPos = GetTextIdx(line, lineChar);
return success;
}
textPos = mCursorTextPos;
overflowX = 0;
return true;
}
public bool TryGetCursorTextPos(out int textPos)
{
float overflowX;
return TryGetCursorTextPos(out textPos, out overflowX);
}
public (char32, int) GetChar32(int idx)
{
char32 c = mData.mText[idx].mChar;
if (c < '\x80')
return (c, 1);
EditWidgetContent.CharData* readPtr = &mData.mText[idx + 1];
int trailingBytes = UTF8.sTrailingBytesForUTF8[c];
switch (trailingBytes)
{
case 4: c <<= 6; c += (uint8)(readPtr++).mChar; fallthrough;
case 3: c <<= 6; c += (uint8)(readPtr++).mChar; fallthrough;
case 2:
c <<= 6;
c += (uint8)(readPtr++).mChar;
fallthrough;
case 1:
c <<= 6;
c += (uint8)(readPtr++).mChar;
fallthrough;
}
c -= (int32)UTF8.sOffsetsFromUTF8[trailingBytes];
return (c, trailingBytes + 1);
}
public void ExtractString(int startIdx, int length, String outStr)
{
Debug.Assert(length >= 0);
char8* ptr = outStr.PrepareBuffer(length);
CharData* char8DataPtr = mData.mText.CArray();
for (int32 idx = 0; idx < length; idx++)
ptr[idx] = char8DataPtr[idx + startIdx].mChar;
}
public void ExtractLine(int line, String outStr)
{
GetLinePosition(line, var lineStart, var lineEnd);
ExtractString(lineStart, lineEnd - lineStart, outStr);
}
public char8 SafeGetChar(int idx)
{
if ((idx < 0) || (idx >= mData.mTextLength))
return 0;
return mData.mText[idx].mChar;
}
static bool IsNonBreakingChar(char8 c)
{
// Assume any 'high' codepoints are non-breaking
return (c.IsLetterOrDigit) || (c == '_') || (c >= '\x80');
}
public override void MouseDown(float x, float y, int32 btn, int32 btnCount)
{
base.MouseDown(x, y, btn, btnCount);
mEditWidget.SetFocus();
if ((mSelection == null) && (mWidgetWindow.IsKeyDown(KeyCode.Shift)))
StartSelection();
MoveCursorToCoord(x, y);
//PrintF("~TestStruct() %d\n", mInner.mVal1);
if ((btnCount > 1) && (!mWidgetWindow.IsKeyDown(KeyCode.Shift)))
{
// Select word
StartSelection();
int cursorTextPos = CursorTextPos;
int textPos = cursorTextPos;
if (mData.mTextLength == 0)
{
//Nothing
}
else if ((textPos < mData.mTextLength) && (!IsNonBreakingChar((char8)mData.mText[textPos].mChar)))
{
mSelection.ValueRef.mEndPos++;
}
else
{
while ((textPos > 0) && (IsNonBreakingChar((char8)mData.mText[textPos - 1].mChar)))
{
mSelection.ValueRef.mStartPos--;
textPos--;
}
textPos = cursorTextPos + 1;
while ((textPos <= mData.mTextLength) && ((textPos == mData.mTextLength) || (mData.mText[textPos - 1].mChar != '\n')) &&
(IsNonBreakingChar((char8)mData.mText[textPos - 1].mChar)))
{
mSelection.ValueRef.mEndPos++;
textPos++;
}
}
mDragSelectionUnion = mSelection;
CursorTextPos = mSelection.Value.MaxPos;
}
else if (!mWidgetWindow.IsKeyDown(KeyCode.Shift))
{
if ((btn != 0) && (mSelection != null) && (CursorTextPos >= mSelection.Value.MinPos) && (CursorTextPos <= mSelection.Value.MaxPos))
{
// Leave selection
}
else
StartSelection();
}
else
SelectToCursor();
}
public override void MouseUp(float x, float y, int32 btn)
{
base.MouseUp(x, y, btn);
if (mMouseFlags == 0)
{
mDragSelectionUnion = null;
}
}
public override void MouseMove(float x, float y)
{
base.MouseMove(x, y);
if ((mMouseDown) && (mMouseFlags == .Left))
{
MoveCursorToCoord(x, y);
SelectToCursor();
}
}
public override void MouseEnter()
{
base.MouseEnter();
BFApp.sApp.SetCursor(Cursor.Text);
}
public override void MouseLeave()
{
base.MouseLeave();
BFApp.sApp.SetCursor(Cursor.Pointer);
}
public virtual void ClearText()
{
SelectAll();
DeleteSelection();
}
public void PhysDeleteSelection(bool moveCursor = true)
{
int startIdx = 0;
int endIdx = 0;
mSelection.Value.GetAsForwardSelect(out startIdx, out endIdx);
RemoveText(startIdx, endIdx - startIdx);
mSelection = null;
CursorTextPos = startIdx;
if (endIdx != startIdx)
ContentChanged();
if (moveCursor)
EnsureCursorVisible();
}
public virtual void DeleteSelection(bool moveCursor = true)
{
if (mSelection != null)
{
var action = new DeleteSelectionAction(this);
action.mMoveCursor = moveCursor;
mData.mUndoManager.Add(action);
PhysDeleteSelection();
}
}
public bool GetSelectionText(String outStr)
{
if (mSelection == null)
return false;
int startIdx = 0;
int endIdx = 0;
mSelection.Value.GetAsForwardSelect(out startIdx, out endIdx);
ExtractString(startIdx, endIdx - startIdx, outStr);
return true;
}
void VerifyTextIds()
{
#if INCLUDE_CHARDATA_CHARID
/*if (mUpdateCnt > 0)
Debug.WriteLine("IdLen: {0}", mTextIdDataLength);*/
int curTextIdx = 0;
int encodeIdx = 0;
int curTextId = 1;
while (true)
{
int cmd = Utils.DecodeInt(mTextIdData, ref encodeIdx); ;
if (cmd == 0)
{
Debug.Assert(encodeIdx == mTextIdDataLength);
break;
}
if (cmd > 0)
{
curTextId = cmd;
}
else
{
for (int i = 0; i < -cmd; i++)
{
Debug.Assert(mText[curTextIdx].mCharId == curTextId);
curTextIdx++;
curTextId++;
}
}
}
Debug.Assert(curTextIdx == mTextLength);
#endif
}
protected virtual void AdjustCursorsAfterExternalEdit(int index, int ofs)
{
#unwarn
int cursorPos = CursorTextPos;
if (mCursorTextPos >= index)
mCursorTextPos += (int32)ofs;
if (HasSelection())
{
if (((ofs > 0) && (mSelection.mValue.mStartPos >= index)) ||
((ofs < 0) && (mSelection.mValue.mStartPos > index)))
mSelection.mValue.mStartPos += (int32)ofs;
if (mSelection.mValue.mEndPos > index)
mSelection.mValue.mEndPos += (int32)ofs;
}
}
public virtual void ApplyTextFlags(int index, String text, uint8 typeNum, uint8 flags)
{
uint8 curTypeNum = typeNum;
for (int i = 0; i < text.Length; i++)
{
char8 c = text[i];
mData.mText[i + index].mChar = (char8)c;
#if INCLUDE_CHARDATA_CHARID
mText[i + index].mCharId = mNextCharId;
#endif
mData.mNextCharId++;
mData.mText[i + index].mDisplayTypeId = curTypeNum;
mData.mText[i + index].mDisplayFlags = flags;
if (c.IsWhiteSpace)
curTypeNum = 0;
}
}
public virtual void GetInsertFlags(int index, ref uint8 typeId, ref uint8 flags)
{
// Leave it
}
public virtual void InsertText(int index, String text)
{
scope AutoBeefPerf("EWC.InsertText");
int wantLen = mData.mTextLength + text.Length + 1;
while (wantLen > mData.mText.Count)
{
scope AutoBeefPerf("EWC.InsertText:Resize");
CharData[] oldText = mData.mText;
int allocSize = Math.Max(mData.mText.Count * 3 / 2, wantLen);
mData.mText = new CharData[allocSize];
oldText.CopyTo(mData.mText, 0, 0, mData.mTextLength);
delete oldText;
}
mData.mText.CopyTo(mData.mText, index, index + text.Length, mData.mTextLength - index);
int32 curId = mData.mNextCharId;
mData.mTextIdData.Insert(index, (int32)text.Length, ref curId);
// We break attribute copying on spaces and revert back to zero
uint8 curTypeNum = 0;
uint8 curFlags = mInsertDisplayFlags;
GetInsertFlags(index, ref curTypeNum, ref curFlags);
ApplyTextFlags(index, text, curTypeNum, curFlags);
mData.mTextLength += (int32)text.Length;
mData.mCurTextVersionId++;
TextChanged();
VerifyTextIds();
for (var user in mData.mUsers)
{
if (user != this)
{
user.AdjustCursorsAfterExternalEdit(index, (int32)text.Length);
user.ContentChanged();
}
}
}
public virtual void RemoveText(int index, int length)
{
for (var user in mData.mUsers)
{
if (user != this)
{
user.AdjustCursorsAfterExternalEdit(index, -length);
user.ContentChanged();
}
}
if (length == 0)
return;
mData.mText.CopyTo(mData.mText, index + length, index, mData.mTextLength - (index + length));
mData.mTextIdData.Remove(index, length);
mData.mTextLength -= (int32)length;
mData.mCurTextVersionId++;
TextChanged();
VerifyTextIds();
}
/*public void PhysInsertAtCursor(String theString, bool moveCursor = true)
{
int textPos = CursorTextPos;
bool atEnd = textPos == mData.mTextLength;
String str = theString;
int strStartIdx = 0;
while (true)
{
int crPos = str.IndexOf('\n', strStartIdx);
if (crPos == -1)
{
if (strStartIdx != 0)
str = stack String(theString, strStartIdx);
break;
}
if (mIsMultiline)
{
//string lineString = aString.Substring(strStartIdx, crPos - strStartIdx + 1);
String lineString = scope String();
str.Substring(strStartIdx, crPos - strStartIdx + 1, lineString);
strStartIdx = crPos + 1;
InsertText((int32)textPos, lineString);
textPos += lineString.Length;
}
else
{
// Only allow the first line in
//aString = aString.Substring(0, crPos);
//aString.RemoveToEnd(crPos);
str = stack String(theString, 0, crPos);
}
}
InsertText((int32)textPos, str);
//if (atEnd)
//ContentAppended(str);
//else
ContentChanged();
if (moveCursor)
{
textPos += str.Length;
MoveCursorToIdx((int32)textPos);
if (mEnsureCursorVisibleOnModify)
EnsureCursorVisible();
}
}*/
public void PhysInsertAtCursor(String text, bool moveCursor = true)
{
int textPos = CursorTextPos;
bool atEnd = textPos == mData.mTextLength;
String insertStr = text;
if (!mIsMultiline)
{
int crPos = insertStr.IndexOf('\n');
if (crPos != -1)
insertStr = scope:: String(text, 0, crPos);
}
InsertText((int32)textPos, insertStr);
if (atEnd)
TextAppended(insertStr);
else
ContentChanged();
if (moveCursor)
{
textPos += insertStr.Length;
MoveCursorToIdx((int32)textPos);
if (mEnsureCursorVisibleOnModify)
EnsureCursorVisible();
}
}
public void AppendText(String text)
{
scope AutoBeefPerf("EWC.AppendText");
if (mAllowVirtualCursor)
{
let prevCursorPos = CursorLineAndColumn;
CursorTextPos = mData.mTextLength;
PhysInsertAtCursor(text);
CursorLineAndColumn = prevCursorPos;
}
else
{
int prevCursorPos = CursorTextPos;
CursorTextPos = mData.mTextLength;
PhysInsertAtCursor(text);
CursorTextPos = prevCursorPos;
}
}
public enum InsertFlags
{
None,
NoRestoreSelectionOnUndo = 1,
NoMoveCursor = 2,
IsGroupPart = 4,
IsGroupStart = 8
}
public virtual void InsertAtCursor(String theString, InsertFlags insertFlags = .None)
{
scope AutoBeefPerf("EWC.InsertAtCursor");
//String insertText = scope String();
String insertString = theString;
bool doCheck = true;
//
{
scope AutoBeefPerf("FilterCheck");
if (theString.Length > 32*1024)
{
bool[128] hasChar = default;
doCheck = false;
for (char32 c in theString.DecodedChars)
{
if (c < (char32)128)
hasChar[(int)c] = true;
else if (!AllowChar(c))
{
doCheck = true;
break;
}
}
if (!doCheck)
{
for (int i < 128)
{
if ((hasChar[i]) && (!AllowChar((char32)i)))
{
doCheck = true;
break;
}
}
}
}
if (doCheck)
{
//TODO: Make this use DecodedChars
for (int32 i = 0; i < insertString.Length; i++)
{
if (!AllowChar(insertString[i]))
{
if (insertString == (Object)theString)
{
insertString = scope:: String();
insertString.Set(theString);
}
insertString.Remove(i, 1);
i--;
}
}
}
}
Debug.Assert(!theString.StartsWith("\x06"));
if ((!HasSelection()) && (mVirtualCursorPos.HasValue) && (theString != "\n"))
{
int textPos;
TryGetCursorTextPos(out textPos);
int line;
int lineChar;
GetLineCharAtIdx(textPos, out line, out lineChar);
float textX;
float textY;
GetTextCoordAtLineChar(line, lineChar, out textX, out textY);
float cursorX;
float cursorY;
GetTextCoordAtLineAndColumn(mVirtualCursorPos.Value.mLine, mVirtualCursorPos.Value.mColumn, out cursorX, out cursorY);
if (cursorX > textX)
{
int lineStart;
int lineEnd;
GetLinePosition(line, out lineStart, out lineEnd);
if (textPos == lineEnd)
{
int prevTabCount = (int)((textX + 0.5f) / mTabSize);
int wantTabCount = (int)((cursorX + 0.5f) / mTabSize);
int wantAddTabs = wantTabCount - prevTabCount;
if (wantAddTabs > 0)
textX = wantTabCount * mTabSize + mTextInsets.mLeft;
int wantSpaces = (int)((cursorX - textX + 0.5f) / mCharWidth);
//theString = new string(' ', wantSpaces) + theString;
let prevString = insertString;
insertString = scope:: String();
insertString.Append('\t', wantAddTabs);
insertString.Append(' ', wantSpaces);
insertString.Append(prevString);
}
}
//if (wantAddTabs > 0)
//theString = new string('\t', wantAddTabs) + theString;
}
if ((insertString.Length == 0) && (!HasSelection()))
return;
if (WantsUndo)
{
var action = new InsertTextAction(this, insertString, insertFlags);
action.mMoveCursor = !insertFlags.HasFlag(.NoMoveCursor);
mData.mUndoManager.Add(action);
}
if (HasSelection())
PhysDeleteSelection(!insertFlags.HasFlag(.NoMoveCursor));
//StringBuilder insertText = new StringBuilder(theString);
/*for (int32 i = 0; i < insertText.Length; i++)
{
if (!AllowChar(insertText[i]))
{
insertText.Remove(i, 1);
i--;
}
}*/
/*int curIdx = 0;
for (var c in insertText.DecodedChars)
{
if (!AllowChar(c))
{
insertText.Remove(curIdx, @c.NextIndex - curIdx);
@c.NextIndex = curIdx;
continue;
}
curIdx = @c.NextIndex;
}*/
//theString = insertText.ToString();
PhysInsertAtCursor(insertString, !insertFlags.HasFlag(.NoMoveCursor));
}
public virtual void ReplaceAtCursor(char8 c)
{
}
public virtual void ClampCursor()
{
if (mVirtualCursorPos.HasValue)
{
var cursorPos = mVirtualCursorPos.Value;
cursorPos.mLine = Math.Min(GetLineCount() - 1, cursorPos.mLine);
mVirtualCursorPos = cursorPos;
}
mCursorTextPos = Math.Min(mCursorTextPos, mData.mTextLength);
}
public virtual void ContentChanged()
{
//TODO:
//mCursorPos = GetTextIdx(mCursorLine, mCursorCharIdx);
MarkDirty();
mContentChanged = true;
delete mData.mTextFlags;
mData.mTextFlags = null;
delete mData.mLineStarts;
mData.mLineStarts = null;
mData.mCurTextVersionId++;
mDragSelectionUnion = null;
mEditWidget.ContentChanged();
TextChanged();
}
public virtual void TextAppended(String str)
{
if ((mWordWrap) || (mData.mLineStarts == null))
{
ContentChanged();
return;
}
scope AutoBeefPerf("EWC.TextAppended");
int textIdx = mData.mTextLength - str.Length;
mData.mLineStarts.PopBack();
for (char8 c in str.RawChars)
{
textIdx++;
if (c == '\n')
{
mData.mLineStarts.Add((int32)textIdx);
}
}
mData.mLineStarts.Add(mData.mTextLength);
mContentChanged = true;
mData.mCurTextVersionId++;
mDragSelectionUnion = null;
//Debugging
//ContentChanged();
//GetTextData();
mEditWidget.ContentChanged();
}
//Thread mCheckThread;
public virtual void GetTextData()
{
// Generate line starts and text flags if we need to
if (mData.mLineStarts == null)
{
scope AutoBeefPerf("EWC.GetTextData");
CharData* char8DataPtr = mData.mText.CArray();
uint8* textFlagsPtr = null;
if (mData.mTextFlags != null)
textFlagsPtr = mData.mTextFlags.CArray();
int32 lineIdx = 0;
if (textFlagsPtr != null)
{
for (int32 i < mData.mTextLength)
{
if ((char8DataPtr[i].mChar == '\n') || ((textFlagsPtr != null) && ((textFlagsPtr[i] & ((int32)TextFlags.Wrap)) != 0)))
lineIdx++;
}
}
else
{
for (int32 i < mData.mTextLength)
{
if (char8DataPtr[i].mChar == '\n')
lineIdx++;
}
}
mData.mLineStarts = new List<int32>();
mData.mLineStarts.GrowUnitialized(lineIdx + 2);
int32* lineStartsPtr = mData.mLineStarts.Ptr;
lineStartsPtr[0] = 0;
lineIdx = 0;
if (textFlagsPtr != null)
{
for (int32 i < mData.mTextLength)
{
if ((textFlagsPtr != null) && ((textFlagsPtr[i] & ((int32)TextFlags.Wrap)) != 0))
{
lineIdx++;
lineStartsPtr[lineIdx] = i;
}
else if ((char8)char8DataPtr[i].mChar == '\n')
{
lineIdx++;
lineStartsPtr[lineIdx] = i + 1;
}
}
}
else
{
for (int32 i < mData.mTextLength)
{
if ((char8)char8DataPtr[i].mChar == '\n')
{
lineIdx++;
lineStartsPtr[lineIdx] = i + 1;
}
}
}
mData.mLineStarts[lineIdx + 1] = mData.mTextLength;
}
}
public virtual void Backspace()
{
if (mJustInsertedCharPair)
{
CursorTextPos++;
mJustInsertedCharPair = false;
}
if (HasSelection())
{
DeleteSelection();
return;
}
mSelection = null;
int textPos = 0;
if (!TryGetCursorTextPos(out textPos))
{
var lineAndColumn = CursorLineAndColumn;
mCursorBlinkTicks = 0;
CursorLineAndColumn = LineAndColumn(lineAndColumn.mLine, lineAndColumn.mColumn - 1);
CursorMoved();
return;
}
if (textPos == 0)
return;
int32 removeNum = -1;
if (mData.mText[CursorTextPos - 1].mChar == '\n')
{
// Pulling up line - delete trailing whitespace if the line has no content
int lineIdx = 0;
int lineCharIdx = 0;
GetLineCharAtIdx(CursorTextPos, out lineIdx, out lineCharIdx);
String lineText = scope String();
GetLineText(lineIdx, lineText);
String trimmedLineText = scope String(lineText);
trimmedLineText.Trim();
if ((lineText.Length > 0) && (trimmedLineText.Length == 0))
{
CursorTextPos += (int32)lineText.Length;
removeNum = (int32)-lineText.Length - 1;
}
}
// Roll back past UTF8 data if necessary
while (true)
{
char8 c = mData.SafeGetChar(mCursorTextPos + removeNum);
if ((uint8)c & 0xC0 != 0x80)
break;
removeNum--;
}
mData.mUndoManager.Add(new DeleteCharAction(this, removeNum, -removeNum));
PhysDeleteChars(removeNum, -removeNum);
}
public virtual void PhysDeleteChars(int32 offset, int32 length = 1)
{
int textPos = CursorTextPos;
RemoveText(textPos + offset, length);
textPos += offset;
ContentChanged();
if (offset != 0)
{
MoveCursorToIdx(textPos);
EnsureCursorVisible();
}
}
public virtual void ClearLine()
{
int line;
int lineChar;
GetCursorLineChar(out line, out lineChar);
String lineText = scope String();
GetLineText(line, lineText);
var prevCursorLineAndCol = CursorLineAndColumn;
mSelection = EditSelection();
mSelection.ValueRef.mStartPos = (int32)(CursorTextPos - lineChar);
mSelection.ValueRef.mEndPos = mSelection.Value.mStartPos + (int32)lineText.Length;
DeleteSelection();
CursorLineAndColumn = prevCursorLineAndCol;
}
public virtual void DeleteLine()
{
int line;
int lineChar;
GetCursorLineChar(out line, out lineChar);
String lineText = scope String();
GetLineText(line, lineText);
var prevCursorLineAndCol = CursorLineAndColumn;
mSelection = EditSelection();
mSelection.ValueRef.mStartPos = (int32)(CursorTextPos - lineChar);
mSelection.ValueRef.mEndPos = mSelection.ValueRef.mStartPos + (int32)lineText.Length + 1;
DeleteSelection();
CursorLineAndColumn = prevCursorLineAndCol;
}
public virtual void DeleteChar()
{
if (HasSelection())
{
DeleteSelection();
return;
}
mSelection = null;
int textPos = CursorTextPos;
if (textPos >= mData.mTextLength)
return;
int line;
int lineChar;
GetLineCharAtIdx(textPos, out line, out lineChar);
bool wasLineEnd = false;
for (int i = textPos; i < mData.mTextLength; i++)
{
char8 c = mData.mText[i].mChar;
if (c == '\n')
{
wasLineEnd = true;
break;
}
else if (!c.IsWhiteSpace)
break;
}
String lineText = scope String();
GetLineText(line, lineText);
if (wasLineEnd)
{
if (lineText.IsWhiteSpace)
{
DeleteLine();
GetLineCharAtIdx(CursorTextPos, out line, out lineChar);
lineText.Clear();
GetLineText(line, lineText);
int32 contentIdx;
for (contentIdx = 0; contentIdx < lineText.Length; contentIdx++)
{
if (!lineText[contentIdx].IsWhiteSpace)
{
CursorTextPos = GetTextIdx(line, contentIdx);
break;
}
}
return;
}
UndoBatchStart undoBatchStart = new UndoBatchStart("PullUpLine");
mData.mUndoManager.Add(undoBatchStart);
// Add in any required virtual spaces
InsertAtCursor("");
textPos = CursorTextPos;
while ((textPos < mData.mTextLength) && (lineChar != 0))
{
mData.mUndoManager.Add(new DeleteCharAction(this, 0, 1));
PhysDeleteChars(0);
char8 c = (char8)mData.mText[textPos].mChar;
if ((c == '\n') || (!c.IsWhiteSpace))
break;
}
mData.mUndoManager.Add(undoBatchStart.mBatchEnd);
}
else
{
int32 char8Count = 1;
int checkIdx = textPos + 1;
while (true)
{
char8 c = mData.SafeGetChar(checkIdx);
if (((uint8)c & 0xC0) != 0x80)
{
// Delete all the combining marks after the char8
var checkChar = mData.GetChar32(checkIdx);
if (!checkChar.IsCombiningMark)
break;
}
char8Count++;
checkIdx++;
}
mData.mUndoManager.Add(new DeleteCharAction(this, 0, char8Count));
PhysDeleteChars(0, char8Count);
}
}
public virtual bool AllowChar(char32 c)
{
if ((c == '\t') && (!mIsMultiline))
return false;
return true;
}
public void InsertCharPair(String char8Pair)
{
InsertAtCursor(char8Pair);
MoveCursorToIdx(CursorTextPos - 1);
mJustInsertedCharPair = true;
}
public virtual bool WantsInsertCharPair(char8 theChar)
{
return false;
}
public virtual int32 GetTabSpaceCount()
{
return (int32)Math.Round(mTabSize / mCharWidth);
}
public override void KeyChar(char32 theChar)
{
base.KeyChar(theChar);
char32 useChar = theChar;
mCursorBlinkTicks = 0;
if (useChar == '\b')
{
if (!CheckReadOnly())
{
if (HasSelection())
{
DeleteSelection();
return;
}
int32 column = CursorLineAndColumn.mColumn;
int32 tabSpaceCount = GetTabSpaceCount();
if ((mAllowVirtualCursor) && (column != 0) && ((column % tabSpaceCount) == 0))
{
int lineStart;
int lineEnd;
GetLinePosition(CursorLineAndColumn.mLine, out lineStart, out lineEnd);
bool doBlockUnindent = false;
int cursorTextPos = 0;
float overflowX = 0;
if (!TryGetCursorTextPos(out cursorTextPos, out overflowX))
{
cursorTextPos += (int32)Math.Round(overflowX / mCharWidth);
for (int32 i = 0; i < tabSpaceCount; i++)
{
if (cursorTextPos - 1 < lineEnd)
{
if (!((char8)mData.mText[cursorTextPos - 1].mChar).IsWhiteSpace)
break;
Backspace();
}
else
{
var virtualCursorPos = CursorLineAndColumn;
virtualCursorPos.mColumn--;
CursorLineAndColumn = virtualCursorPos;
}
cursorTextPos--;
}
return;
}
else if (cursorTextPos > 0)
{
Debug.Assert((cursorTextPos >= lineStart) && (cursorTextPos <= lineEnd));
if (((char8)mData.mText[cursorTextPos - 1].mChar).IsWhiteSpace)
{
doBlockUnindent = true;
// Cursor has to be at line end or only have whitespace infront of it (line start)
if (cursorTextPos < lineEnd)
{
for (int i = lineStart; i < cursorTextPos; i++)
{
if (!((char8)mData.mText[i].mChar).IsWhiteSpace)
{
doBlockUnindent = false;
break;
}
}
}
}
}
if (doBlockUnindent)
{
BlockIndentSelection(true);
return;
}
}
Backspace();
}
return;
}
mJustInsertedCharPair = false;
switch (useChar)
{
case '\r':
useChar = '\n';
break;
case '\t':
if (AllowChar(useChar))
BlockIndentSelection(mWidgetWindow.IsKeyDown(KeyCode.Shift));
return;
}
if (useChar == '\n')
{
if ((!mIsMultiline) || (mWidgetWindow.IsKeyDown(KeyCode.Control)))
{
// Submit is handled in KeyDown
//mEditWidget.Submit();
return;
}
}
if ((AllowChar(useChar)) && (!CheckReadOnly()))
{
if ((useChar == '\n') && (!mAllowVirtualCursor))
{
int lineIdx;
int lineCharIdx;
GetLineCharAtIdx(CursorTextPos, out lineIdx, out lineCharIdx);
// Duplicate the tab start from the previous line
if (lineIdx > 0)
{
int32 indentCount = 0;
String aLine = scope String();
GetLineText(lineIdx, aLine);
for (int32 i = 0; i < Math.Min(aLine.Length, lineCharIdx); i++)
{
if (aLine[i] != '\t')
break;
indentCount++;
}
aLine.Trim();
UndoBatchStart undoBatchStart = new UndoBatchStart("newline");
mData.mUndoManager.Add(undoBatchStart);
if (aLine.Length == 0)
{
// Remove previous line's tabs (since that line was blank)
for (int32 i = 0; i < indentCount; i++)
Backspace();
}
InsertAtCursor(ToStackString!(useChar));
for (int32 i = 0; i < indentCount; i++)
InsertAtCursor("\t");
if (aLine.Length > 0)
{
char8 c = aLine[aLine.Length - 1];
if ((c == '(') || (c == '[') || (c == '{'))
InsertAtCursor("\t");
}
if (WantsUndo)
mData.mUndoManager.Add(undoBatchStart.mBatchEnd);
return;
}
}
bool restoreSelectionOnUndo = true;
if ((mOverTypeMode) && (!HasSelection()))
{
int cursorPos = CursorTextPos;
if (cursorPos < mData.mTextLength)
{
char8 c = (char8)mData.mText[cursorPos].mChar;
if ((c != '\n') && (c != '\r'))
{
mSelection = EditSelection(cursorPos, cursorPos + 1);
restoreSelectionOnUndo = false;
}
}
}
if ((useChar >= '\x20') || (useChar == '\n'))
{
InsertAtCursor(ToStackString!(useChar), restoreSelectionOnUndo ? .None : .NoRestoreSelectionOnUndo);
}
}
mCursorImplicitlyMoved = true;
}
public virtual float GetPageScrollTextHeight()
{
return mEditWidget.mScrollContentContainer.mHeight;
}
public virtual float AdjustPageScrollPos(float newY, int dir)
{
return Math.Max(0, Math.Min(newY, mHeight - mEditWidget.mScrollContentContainer.mHeight - mMaximalScrollAddedHeight));
}
int32 GetCharType(char8 theChar)
{
if (theChar == '\n')
return 2;
if (theChar.IsWhiteSpace)
return 0;
if (IsNonBreakingChar(theChar))
return 1;
// We break on each instance of these
switch (theChar)
{
case '<', '>', '(', ')', '[', ']', '{', '}':
return 3;
}
return 4;
}
public void GetTextCoordAtCursor(out float x, out float y)
{
if (mVirtualCursorPos.HasValue)
{
GetTextCoordAtLineAndColumn(mVirtualCursorPos.Value.mLine, mVirtualCursorPos.Value.mColumn, out x, out y);
}
else
{
int line;
int lineChar;
GetLineCharAtIdx(mCursorTextPos, out line, out lineChar);
GetTextCoordAtLineChar(line, lineChar, out x, out y);
}
}
public bool GetCursorLineChar(out int line, out int lineChar)
{
if (mCursorTextPos != -1)
{
GetLineCharAtIdx_Fast(mCursorTextPos, true, out line, out lineChar);
return true;
}
line = mVirtualCursorPos.Value.mLine;
float x;
float y;
GetTextCoordAtLineAndColumn(mVirtualCursorPos.Value.mLine, mVirtualCursorPos.Value.mColumn, out x, out y);
float overflowX;
return GetLineCharAtCoord(x, y, out line, out lineChar, out overflowX);
}
public virtual bool PrepareForCursorMove(int dir = 0)
{
if (mWidgetWindow.IsKeyDown(KeyCode.Shift))
return false;
if ((mSelection == null) || (!mSelection.Value.HasSelection))
return false;
if (dir < 0)
CursorTextPos = mSelection.Value.MinPos;
else if (dir > 0)
CursorTextPos = mSelection.Value.MaxPos;
mSelection = null;
return true;
}
public virtual void Undo()
{
scope AutoBeefPerf("EWC.Undo");
//Profiler.StartSampling();
if (WantsUndo)
mData.mUndoManager.Undo();
//Profiler.StopSampling();
}
public virtual void Redo()
{
scope AutoBeefPerf("EWC.Redo");
if (WantsUndo)
mData.mUndoManager.Redo();
}
public virtual void PasteText(String text)
{
InsertAtCursor(text);
}
public void CutText()
{
if (!CheckReadOnly())
{
String selText = scope String();
GetSelectionText(selText);
if (!selText.IsWhiteSpace)
{
BFApp.sApp.SetClipboardText(selText);
DeleteSelection();
}
}
}
public void CopyText()
{
String selText = scope String();
GetSelectionText(selText);
if (!selText.IsWhiteSpace)
BFApp.sApp.SetClipboardText(selText);
}
public void PasteText()
{
String aText = scope String();
BFApp.sApp.GetClipboardText(aText);
aText.Replace("\r", "");
if ((aText != null) && (!CheckReadOnly()))
PasteText(aText);
}
public override void KeyDown(KeyCode keyCode, bool isRepeat)
{
base.KeyDown(keyCode, isRepeat);
if (keyCode == KeyCode.Escape)
{
mEditWidget.Cancel();
if (mWidgetWindow == null)
return;
}
int lineIdx;
int lineChar;
GetCursorLineChar(out lineIdx, out lineChar);
bool wasMoveKey = false;
int prevCursorPos;
bool gotCursorPos = TryGetCursorTextPos(out prevCursorPos);
if (mWidgetWindow.GetKeyFlags() == KeyFlags.Ctrl)
{
switch (keyCode)
{
case (KeyCode)'A':
SelectAll();
case (KeyCode)'C':
CopyText();
case (KeyCode)'X':
CutText();
case (KeyCode)'V':
PasteText();
case (KeyCode)'Z':
Undo();
case (KeyCode)'Y':
Redo();
case .Return:
if (mIsMultiline)
mEditWidget.Submit();
default:
}
}
switch (keyCode)
{
case .Return:
if (!mIsMultiline)
mEditWidget.Submit();
case KeyCode.Left:
{
if (!PrepareForCursorMove(-1))
{
PrepareForCursorMove(-1);
if (mAllowVirtualCursor)
{
bool doVirtualMove = true;
if ((mWidgetWindow.IsKeyDown(KeyCode.Shift)) || (mWidgetWindow.IsKeyDown(KeyCode.Control)))
doVirtualMove = false;
else
{
int lineStart;
int lineEnd;
GetLinePosition(lineIdx, out lineStart, out lineEnd);
//if (gotCursorPos)
if (gotCursorPos)
doVirtualMove = false;
}
if (doVirtualMove)
{
var lineAndColumn = CursorLineAndColumn;
if (lineAndColumn.mColumn > 0)
{
/*mSelection = null;
mCursorBlinkTicks = 0;
CursorLineAndColumn = LineAndColumn(lineAndColumn.mLine, lineAndColumn.mColumn - 1);
EnsureCursorVisible(true, false, false);
CursorMoved();*/
float x;
float y;
GetTextCoordAtLineAndColumn(lineAndColumn.mLine, lineAndColumn.mColumn - 1, out x, out y);
MoveCursorToCoord(x, y);
break;
}
}
}
wasMoveKey = true;
int anIndex = GetTextIdx(lineIdx, lineChar);
int prevCharType = (anIndex > 0) ? GetCharType((char8)mData.mText[anIndex - 1].mChar) : -1;
while (true)
{
if (lineChar > 0)
MoveCursorTo(lineIdx, lineChar - 1);
else if (lineIdx > 0)
{
int cursorIdx = mCursorTextPos;
String lineText = scope String();
GetLineText(lineIdx - 1, lineText);
MoveCursorTo(lineIdx - 1, (int32)lineText.Length);
if ((!mAllowVirtualCursor) && (cursorIdx == mCursorTextPos))
MoveCursorTo(lineIdx - 1, (int32)lineText.Length - 1);
break;
}
if (!mWidgetWindow.IsKeyDown(KeyCode.Control))
break;
//mInner.mVal1 = inVal;
//PrintF("TestStruct() %d\n", inVal);
GetLineCharAtIdx(CursorTextPos, out lineIdx, out lineChar);
anIndex = CursorTextPos;
if (anIndex == 0)
break;
char8 aChar = (char8)mData.mText[anIndex - 1].mChar;
int32 char8Type = GetCharType(aChar);
if (prevCharType == 3)
break;
if (char8Type != prevCharType)
{
if ((char8Type == 0) && (prevCharType != 0))
break;
if ((prevCharType == 2) || (prevCharType == 1) || (prevCharType == 4))
break;
}
prevCharType = char8Type;
}
}
}
break;
case KeyCode.Right:
{
if (!PrepareForCursorMove(1))
{
if (mAllowVirtualCursor)
{
bool doVirtualMove = true;
if ((mWidgetWindow.IsKeyDown(KeyCode.Shift)) || (mWidgetWindow.IsKeyDown(KeyCode.Control)))
doVirtualMove = false;
else
{
int lineStart;
int lineEnd;
GetLinePosition(lineIdx, out lineStart, out lineEnd);
if (prevCursorPos < lineEnd)
doVirtualMove = false;
}
if (doVirtualMove)
{
mCursorBlinkTicks = 0;
mSelection = null;
var lineAndColumn = CursorLineAndColumn;
CursorLineAndColumn = LineAndColumn(lineAndColumn.mLine, lineAndColumn.mColumn + 1);
EnsureCursorVisible(true, false, false);
CursorMoved();
break;
}
}
wasMoveKey = true;
int anIndex = GetTextIdx(lineIdx, lineChar);
int prevCharType = (anIndex < mData.mTextLength) ? GetCharType((char8)mData.mText[anIndex].mChar) : -1;
while (true)
{
int lineStart;
int lineEnd;
GetLinePosition(lineIdx, out lineStart, out lineEnd);
int lineLen = lineEnd - lineStart;
int nextLineChar = lineChar + 1;
bool isWithinLine = nextLineChar < lineLen;
if (nextLineChar == lineLen)
{
GetTextData();
if ((mData.mTextFlags == null) || ((mData.mTextFlags[lineEnd] & (int32)TextFlags.Wrap) == 0))
{
isWithinLine = true;
}
}
if (isWithinLine)
MoveCursorTo(lineIdx, lineChar + 1, false, 1);
else if (lineIdx < GetLineCount() - 1)
MoveCursorTo(lineIdx + 1, 0);
if (!mWidgetWindow.IsKeyDown(KeyCode.Control))
break;
GetLineCharAtIdx(CursorTextPos, out lineIdx, out lineChar);
anIndex = GetTextIdx(lineIdx, lineChar);
if (anIndex == mData.mTextLength)
break;
char8 aChar = (char8)mData.mText[anIndex].mChar;
int32 char8Type = GetCharType(aChar);
if (char8Type == 3)
break;
if (char8Type != prevCharType)
{
if ((char8Type != 0) && (prevCharType == 0))
break;
if ((char8Type == 2) || (char8Type == 1) || (char8Type == 4))
break;
}
prevCharType = char8Type;
}
}
}
break;
case KeyCode.Up:
fallthrough;
case KeyCode.Down:
{
int32 aDir = (keyCode == KeyCode.Up) ? -1 : 1;
bool didSelectionMove = PrepareForCursorMove(aDir);
GetCursorLineChar(out lineIdx, out lineChar);
if (mWidgetWindow.IsKeyDown(KeyCode.Control))
{
//mEditWidget.Resize(mEditWidget.mX, mEditWidget.mY, mEditWidget.mWidth, mEditWidget.mHeight + aDir);
mEditWidget.VertScrollTo(mEditWidget.mVertPos.mDest + aDir * mEditWidget.mScrollContentContainer.mHeight * 0.25f);
EnsureCursorVisible(false);
return;
}
wasMoveKey = true;
if ((lineIdx + aDir >= 0) && (lineIdx + aDir < GetLineCount()))
{
if (mAllowVirtualCursor)
{
float cursorX;
float cursorY;
GetTextCoordAtCursor(out cursorX, out cursorY);
mCursorWantX = cursorX;
}
//if (!mAllowVirtualCursor)
{
lineIdx += aDir;
float wantedX = mCursorWantX;
float aX;
float aY;
GetTextCoordAtLineChar(lineIdx, 0, out aX, out aY);
MoveCursorToCoord(mCursorWantX, aY);
// Restore old desired X
mCursorWantX = wantedX;
}
/*else
{
mCursorBlinkTicks = 0;
var prevLineAndColumn = CursorLineAndColumn;
CursorLineAndColumn = LineAndColumn(Math.Max(0, prevLineAndColumn.mLine + aDir), prevLineAndColumn.mColumn);
CursorMoved();
}*/
}
if (didSelectionMove)
CursorToLineStart(false);
}
break;
case KeyCode.Home:
PrepareForCursorMove(-1);
wasMoveKey = true;
if (mWidgetWindow.IsKeyDown(KeyCode.Control))
CursorToStart();
else
CursorToLineStart(true);
//mCursorImplicitlyMoved = true;
break;
case KeyCode.End:
PrepareForCursorMove(1);
wasMoveKey = true;
if (mWidgetWindow.IsKeyDown(KeyCode.Control))
{
CursorToEnd();
}
else
{
CursorToLineEnd();
if ((!mAllowVirtualCursor) && (mWordWrap))
mShowCursorAtLineEnd = true;
}
break;
case KeyCode.PageUp:
fallthrough;
case KeyCode.PageDown:
{
if (!mIsMultiline)
break;
wasMoveKey = true;
int32 aDir = (keyCode == KeyCode.PageUp) ? -1 : 1;
PrepareForCursorMove(aDir);
float cursorX;
float cursorY;
if (mAllowVirtualCursor)
{
var lineAndCol = CursorLineAndColumn;
GetTextCoordAtLineAndColumn(lineAndCol.mLine, lineAndCol.mColumn, out cursorX, out cursorY);
mCursorWantX = cursorX;
}
else
{
GetTextCoordAtLineChar(lineIdx, lineChar, out cursorX, out cursorY);
}
cursorY -= (float)mEditWidget.mVertPos.mDest;
float pageScrollTextSize = GetPageScrollTextHeight();
float adjustY = pageScrollTextSize * aDir;
float scrollToVal = (float)(mEditWidget.mVertPos.mDest + adjustY);
float newScrollToVal = AdjustPageScrollPos(scrollToVal, aDir);
cursorY += scrollToVal - newScrollToVal;
scrollToVal = newScrollToVal;
if (mEditWidget.VertScrollTo(scrollToVal))
{
cursorY += (float)mEditWidget.mVertPos.mDest;
MoveCursorToCoord(cursorX, cursorY);
// Restore old desired X
mCursorWantX = cursorX;
}
else
{
if (aDir == -1)
{
lineIdx = 0;
CursorTextPos = 0;
}
else
{
lineIdx = GetLineCount() - 1;
CursorTextPos = GetTextIdx(lineIdx, lineChar);
}
float wantedX = mCursorWantX;
float aX;
float aY;
GetTextCoordAtLineChar(lineIdx, 0, out aX, out aY);
MoveCursorToCoord(mCursorWantX, aY);
// Restore old desired X
mCursorWantX = wantedX;
}
}
break;
case KeyCode.Insert:
mOverTypeMode = !mOverTypeMode;
break;
case KeyCode.Delete:
if (!CheckReadOnly())
DeleteChar();
mCursorImplicitlyMoved = true;
break;
default:
}
if (wasMoveKey)
{
if (mWidgetWindow.IsKeyDown(KeyCode.Shift))
{
if (!HasSelection())
{
StartSelection();
mSelection.ValueRef.mStartPos = (int32)prevCursorPos;
}
SelectToCursor();
}
else
mSelection = null;
EnsureCursorVisible();
}
}
public float GetCursorScreenRelY()
{
float cursorX;
float cursorY;
GetTextCoordAtCursor(out cursorX, out cursorY);
return (float)(cursorY - mEditWidget.mVertScrollbar.mContentPos);
}
public void SetCursorScreenRelY(float scrollTopDelta)
{
float cursorX;
float cursorY;
GetTextCoordAtCursor(out cursorX, out cursorY);
mEditWidget.mVertScrollbar.ScrollTo(cursorY - scrollTopDelta);
}
public override bool Contains(float x, float y)
{
return true;
}
public override void Update()
{
Debug.Assert((mCursorTextPos != -1) || (mVirtualCursorPos != null));
base.Update();
if (mContentChanged)
RecalcSize();
mCursorBlinkTicks++;
if (mEditWidget.mHasFocus)
MarkDirty();
}
public virtual void RecalcSize()
{
mContentChanged = false;
mEditWidget.UpdateScrollbars();
}
public virtual void TextChanged()
{
}
// Returns true if we have text at that location
// If we return 'false' then line/theChar are set to closest positions
public virtual bool GetLineCharAtCoord(float x, float y, out int line, out int theChar, out float overflowX)
{
line = -1;
theChar = -1;
overflowX = 0;
return false;
}
public virtual bool GetLineAndColumnAtCoord(float x, float y, out int line, out int column)
{
line = -1;
column = -1;
return false;
}
public void GetColumnAtLineChar(int line, int lineChar, out int column)
{
float x;
float y;
GetTextCoordAtLineChar(line, lineChar, out x, out y);
int line2;
GetLineAndColumnAtCoord(x, y, out line2, out column);
}
public virtual void GetLineCharAtIdx(int idx, out int line, out int theChar)
{
int lineA;
int char8A;
GetLineCharAtIdx_Fast(idx, false, out lineA, out char8A);
/*int32 lineB;
int32 char8B;
GetLineCharAtIdx_Slow(idx, out lineB, out char8B);
Debug.Assert((lineA == lineB) && (char8A == char8B));*/
line = lineA;
theChar = char8A;
}
public virtual void GetLineCharAtIdx_Fast(int idx, bool checkCursorAtLineEnd, out int line, out int theChar)
{
GetTextData();
//bool isCurCursor = true;
if (idx == mData.mTextLength)
{
int32 lastLine = GetLineCount() - 1;
line = (int32)lastLine;
theChar = idx - mData.mLineStarts[lastLine];
return;
}
int lo = 0;
int hi = mData.mLineStarts.Count - 2;
if (idx < 0)
{
line = 0;
theChar = 0;
}
while (lo <= hi)
{
int i = (lo + hi) / 2;
var midVal = mData.mLineStarts[i];
int c = midVal - idx;
if (c == 0)
{
if ((checkCursorAtLineEnd) && (mShowCursorAtLineEnd) && (i > 0))
{
i--;
midVal = mData.mLineStarts[i];
}
line = (int32)i;
theChar = idx - midVal;
return;
}
if (c < 0)
lo = i + 1;
else
hi = i - 1;
}
if (hi < GetLineCount())
{
line = (int32)hi;
theChar = idx - mData.mLineStarts[hi];
}
else
{
line = (int32)mData.mLineStarts.Count - 2;
theChar = mData.mLineStarts[mData.mLineStarts.Count - 1] - mData.mLineStarts[mData.mLineStarts.Count - 2];
}
}
public void GetLineCharAtIdx_Slow(int32 idx, out int32 line, out int32 theChar)
{
GetTextData();
for (int32 lineIdx = 0; lineIdx < mData.mLineStarts.Count; lineIdx++)
{
int32 lineStart = mData.mLineStarts[lineIdx];
if ((idx < lineStart) || ((idx == lineStart) && (lineIdx > 0) && (mCursorTextPos == lineStart) && (mShowCursorAtLineEnd)))
{
line = lineIdx - 1;
theChar = idx - mData.mLineStarts[lineIdx - 1];
return;
}
}
if (idx < 0)
{
line = 0;
theChar = 0;
}
else
{
line = (int32)mData.mLineStarts.Count - 2;
theChar = mData.mLineStarts[mData.mLineStarts.Count - 1] - mData.mLineStarts[mData.mLineStarts.Count - 2];
}
}
public virtual float GetLineHeight(int line)
{
return 0;
}
public virtual int32 GetLineCount()
{
GetTextData();
return (int32)mData.mLineStarts.Count - 1;
}
public virtual void GetLinePosition(int line, out int lineStart, out int lineEnd)
{
GetTextData();
if (line >= mData.mLineStarts.Count - 1)
{
lineStart = Math.Max(0, mData.mTextLength);
lineEnd = mData.mTextLength;
return;
}
lineStart = mData.mLineStarts[line];
lineEnd = mData.mLineStarts[line + 1];
if ((lineEnd > lineStart) && (mData.mText[lineEnd - 1].mChar == '\n'))
lineEnd--;
}
public virtual void GetLineText(int line, String outStr)
{
GetTextData();
int lineStart = mData.mLineStarts[line];
int lineEnd = mData.mLineStarts[line + 1];
if ((lineEnd > lineStart) && (mData.mText[lineEnd - 1].mChar == '\n'))
ExtractString(lineStart, lineEnd - lineStart - 1, outStr); // Remove last char8 (it's a '\n')
else
ExtractString(lineStart, lineEnd - lineStart, outStr); // Full line
}
public int GetTextIdx(int line, int char8Idx)
{
GetTextData();
int useLine = Math.Min(line, mData.mLineStarts.Count - 1);
return mData.mLineStarts[useLine] + char8Idx;
}
public int GetCharIdIdx(int32 findCharId)
{
mData.mTextIdData.Prepare();
return mData.mTextIdData.GetIndexFromId(findCharId);
}
public int32 GetSourceCharIdAtLineChar(int line, int column)
{
int encodeIdx = 0;
int spanLeft = 0;
int32 char8Id = 0;
int curLine = 0;
int curColumn = 0;
int char8Idx = 0;
mData.mTextIdData.Prepare();
while (true)
{
while (spanLeft == 0)
{
if (mData.mTextIdData.mData == null)
{
spanLeft = mData.mTextLength;
continue;
}
int32 cmd = Utils.DecodeInt(mData.mTextIdData.mData, ref encodeIdx);
if (cmd > 0)
char8Id = cmd;
else
spanLeft = -cmd;
if (cmd == 0)
return 0;
}
if ((curLine == line) && (curColumn == column))
return char8Id;
char8 c = (char8)mData.mText[char8Idx++].mChar;
if (c == '\n')
{
if (curLine == line)
return char8Id;
curLine++;
curColumn = 0;
}
else
curColumn++;
spanLeft--;
char8Id++;
}
}
public virtual void GetTextCoordAtLineChar(int line, int char8Idx, out float x, out float y)
{
x = 0;
y = 0;
}
public virtual void GetTextCoordAtLineAndColumn(int line, int column, out float x, out float y)
{
x = 0;
y = 0;
}
// We used to have a split between PhysCursorMoved and CursorMoved. CursorMoved has a "ResetWantX" and was non-virtual... uh-
// so what was taht for?
public virtual void PhysCursorMoved()
{
mJustInsertedCharPair = false;
mShowCursorAtLineEnd = false;
mCursorBlinkTicks = 0;
mCursorImplicitlyMoved = false;
//
ResetWantX();
}
public void CursorMoved()
{
PhysCursorMoved();
/*mJustInsertedCharPair = false;
mShowCursorAtLineEnd = false;
mCursorBlinkTicks = 0;
ResetWantX();*/
}
public virtual void CursorToStart()
{
MoveCursorTo(0, 0);
}
public virtual void CursorToEnd()
{
String lineStr = scope String();
GetLineText(GetLineCount() - 1, lineStr);
MoveCursorTo(GetLineCount() - 1, (int32)lineStr.Length);
//mCursorWantX = GetDrawPos;
}
public virtual void CursorToLineStart(bool allowToScreenStart)
{
int lineIdx;
int lineChar;
GetCursorLineChar(out lineIdx, out lineChar);
String lineText = scope String();
GetLineText(lineIdx, lineText);
bool hasNonWhitespace = false;
int32 contentIdx;
for (contentIdx = 0; contentIdx < lineText.Length; contentIdx++)
{
if (!lineText[contentIdx].IsWhiteSpace)
{
hasNonWhitespace = true;
break;
}
}
if ((mAllowVirtualCursor) && (!hasNonWhitespace))
{
var prevLineAndColumn = CursorLineAndColumn;
CursorToLineEnd();
if ((!allowToScreenStart) || (CursorLineAndColumn.mColumn < prevLineAndColumn.mColumn))
return;
}
if ((allowToScreenStart) && (lineChar <= contentIdx))
MoveCursorTo(lineIdx, 0);
else
MoveCursorTo(lineIdx, contentIdx);
}
public virtual void CursorToLineEnd()
{
int line;
int lineChar;
GetCursorLineChar(out line, out lineChar);
String curLineStr = scope String();
GetLineText(line, curLineStr);
int32 lineLen = (int32)curLineStr.Length;
MoveCursorTo(line, lineLen);
}
public void StartSelection()
{
mSelection = EditSelection();
int textPos;
TryGetCursorTextPos(out textPos);
mSelection.ValueRef.mEndPos = mSelection.ValueRef.mStartPos = (int32)textPos;
}
public void SelectToCursor()
{
/*int textPos;
TryGetCursorTextPos(out textPos);
if (mSelection != null)
{
mSelection.Value.mEndPos = textPos;
}*/
int textPos;
TryGetCursorTextPos(out textPos);
if (mDragSelectionUnion != null)
{
mSelection = EditSelection(mDragSelectionUnion.Value.MinPos, mDragSelectionUnion.Value.MaxPos);
if (textPos <= mSelection.Value.mStartPos)
{
mSelection.ValueRef.mStartPos = (int32)Math.Max(0, textPos - 1);
while ((textPos > 0) && (IsNonBreakingChar((char8)mData.mText[textPos - 1].mChar)))
{
textPos--;
mSelection.ValueRef.mStartPos = (int32)textPos;
}
CursorTextPos = mSelection.Value.mStartPos;
}
else
{
if (textPos > mSelection.Value.mEndPos)
{
mSelection.ValueRef.mEndPos = (int32)textPos;
while ((textPos <= mData.mTextLength) && ((textPos == mData.mTextLength) || (mData.mText[textPos - 1].mChar != '\n')) &&
(IsNonBreakingChar((char8)mData.mText[textPos - 1].mChar)))
{
mSelection.ValueRef.mEndPos = (int32)textPos;
textPos++;
}
}
CursorTextPos = mSelection.Value.mEndPos;
}
}
else if (mSelection != null)
{
mSelection.ValueRef.mEndPos = (int32)textPos;
}
}
public virtual bool IsLineVisible(int line, bool useDestScrollPostion = true)
{
if (!mIsMultiline)
return true;
float aX;
float aY;
GetTextCoordAtLineChar(line, 0, out aX, out aY);
float lineHeight = GetLineHeight(line);
double scrollPos = useDestScrollPostion ? mEditWidget.mVertPos.mDest : mEditWidget.mVertPos.mSrc;
if (aY + lineHeight*0.9f < scrollPos + mTextInsets.mTop)
{
return false;
}
else if (aY + lineHeight*0.1f + mShowLineBottomPadding > scrollPos + mEditWidget.mScrollContentContainer.mHeight)
{
return false;
}
return true;
}
public bool IsCursorVisible(bool useDestScrollPostion = true)
{
return IsLineVisible(CursorLineAndColumn.mLine, useDestScrollPostion);
}
public virtual void EnsureCursorVisible(bool scrollView = true, bool centerView = false, bool doHorzJump = true)
{
if (mEditWidget.mScrollContentContainer.mWidth <= 0)
return; // Not sized yet
if (mContentChanged)
RecalcSize();
float horzJumpSize = doHorzJump ? mHorzJumpSize : 0;
float aX;
float aY;
GetTextCoordAtCursor(out aX, out aY);
int line;
int lineChar;
GetCursorLineChar(out line, out lineChar);
float lineHeight = GetLineHeight(line);
if (mIsMultiline)
{
if (aY < mEditWidget.mVertPos.mDest + mTextInsets.mTop)
{
if (scrollView)
{
float scrollPos = aY - mTextInsets.mTop;
if (centerView)
{
scrollPos -= mEditWidget.mScrollContentContainer.mHeight * 0.50f;
scrollPos = (float)Math.Round(scrollPos / lineHeight) * lineHeight;
}
mEditWidget.VertScrollTo(scrollPos);
}
else
{
int aLine;
int aCharIdx;
float overflowX;
GetLineCharAtCoord(aX, (float)mEditWidget.mVertPos.mDest + mTextInsets.mTop, out aLine, out aCharIdx, out overflowX);
float newX;
float newY;
GetTextCoordAtLineChar(aLine, aCharIdx, out newX, out newY);
if (aY < mEditWidget.mVertPos.mDest + mTextInsets.mTop - 0.01f)
GetLineCharAtCoord(newX, newY + GetLineHeight(aLine), out aLine, out aCharIdx, out overflowX);
MoveCursorTo(aLine, aCharIdx);
}
}
else if (aY + lineHeight + mShowLineBottomPadding > mEditWidget.mVertPos.mDest + mEditWidget.mScrollContentContainer.mHeight)
{
if (scrollView)
{
float scrollPos = aY + lineHeight + mShowLineBottomPadding - mEditWidget.mScrollContentContainer.mHeight;
if (centerView)
{
// Show slightly more content on bottom
scrollPos += mEditWidget.mScrollContentContainer.mHeight * 0.50f;
scrollPos = (float)Math.Round(scrollPos / lineHeight) * lineHeight;
}
mEditWidget.VertScrollTo(scrollPos);
}
else
MoveCursorToCoord(aX, (float)mEditWidget.mVertPos.mDest + mEditWidget.mScrollContentContainer.mHeight - lineHeight);
}
}
if (mAutoHorzScroll)
{
if (aX < mEditWidget.mHorzPos.mDest + mTextInsets.mLeft)
mEditWidget.HorzScrollTo(Math.Max(0, aX - mTextInsets.mLeft - horzJumpSize));
else if (aX >= mEditWidget.mHorzPos.mDest + mEditWidget.mScrollContentContainer.mWidth - mTextInsets.mRight)
{
float wantScrollPos = aX - mEditWidget.mScrollContentContainer.mWidth + horzJumpSize + mTextInsets.mRight;
if (mAllowVirtualCursor)
TrySetHorzScroll(wantScrollPos);
mEditWidget.HorzScrollTo(Math.Min(mWidth - mEditWidget.mScrollContentContainer.mWidth, wantScrollPos));
}
}
}
protected virtual void TrySetHorzScroll(float horzScroll)
{
mWidth = Math.Max(mWidth, horzScroll + mEditWidget.mScrollContentContainer.mWidth);
mEditWidget.UpdateScrollbars();
}
public void BlockIndentSelection(bool unIndent = false)
{
if (CheckReadOnly())
return;
int minLineIdx = 0;
int minLineCharIdx = 0;
int maxLineIdx = 0;
int maxLineCharIdx = 0;
bool isMultilineSelection = HasSelection();
if (isMultilineSelection)
{
GetLineCharAtIdx(mSelection.Value.MinPos, out minLineIdx, out minLineCharIdx);
GetLineCharAtIdx(mSelection.Value.MaxPos, out maxLineIdx, out maxLineCharIdx);
isMultilineSelection = maxLineIdx != minLineIdx;
}
if (isMultilineSelection)
{
var indentTextAction = new EditWidgetContent.IndentTextAction(this);
EditSelection newSel = mSelection.Value;
mSelection.ValueRef.MakeForwardSelect();
mSelection.ValueRef.mStartPos -= (int32)minLineCharIdx;
//int32 minPos = newSel.MinPos;
int32 startAdjust = 0;
int32 endAdjust = 0;
if (unIndent)
{
//bool hadWsLeft = (minPos > 0) && (Char8.IsWhiteSpace((char8)mData.mText[minPos - 1].mChar));
for (int lineIdx = minLineIdx; lineIdx <= maxLineIdx; lineIdx++)
{
int lineStart;
int lineEnd;
GetLinePosition(lineIdx, out lineStart, out lineEnd);
//lineStart += endAdjust;
for (int32 i = 0; i < mTabLength; i++)
{
char8 c = (char8)mData.mText[lineStart + i].mChar;
if (((c == '\t') && (i == 0)) || (c == ' '))
{
indentTextAction.mRemoveCharList.Add(Tuple<int32, char8>((int32)lineStart + i + endAdjust, c));
//RemoveText(lineStart, 1);
endAdjust--;
}
if (c != ' ')
break;
}
if ((lineIdx == minLineIdx) && (endAdjust != 0))
startAdjust = endAdjust;
}
indentTextAction.Redo();
}
else
{
startAdjust++;
for (int lineIdx = minLineIdx; lineIdx <= maxLineIdx; lineIdx++)
{
int lineStart;
int lineEnd;
GetLinePosition(lineIdx, out lineStart, out lineEnd);
lineStart += endAdjust;
//InsertText(lineStart, "\t");
indentTextAction.mInsertCharList.Add(Tuple<int32, char8>((int32)lineStart, '\t'));
endAdjust++;
}
indentTextAction.Redo();
}
ContentChanged();
if (newSel.IsForwardSelect)
{
newSel.mStartPos = Math.Max(newSel.mStartPos + startAdjust, 0);
newSel.mEndPos += endAdjust;
CursorTextPos = newSel.mEndPos;
}
else
{
newSel.mEndPos = Math.Max(newSel.mEndPos + startAdjust, 0);
newSel.mStartPos += endAdjust;
CursorTextPos = newSel.mEndPos;
}
indentTextAction.mNewSelection = newSel;
if ((indentTextAction.mInsertCharList.Count > 0) ||
(indentTextAction.mRemoveCharList.Count > 0))
{
mData.mUndoManager.Add(indentTextAction);
}
else
delete indentTextAction;
mSelection = newSel;
return;
}
else // Non-block
{
int lineIdx;
int lineCharIdx;
GetLineCharAtIdx(CursorTextPos, out lineIdx, out lineCharIdx);
if (unIndent)
{
var prevSelection = mSelection;
mSelection = null;
if (lineCharIdx > 0)
{
String aLine = scope String();
GetLineText(lineIdx, aLine);
for (int32 i = 0; i < mTabLength; i++)
{
if (lineCharIdx == 0)
break;
char8 c = aLine[lineCharIdx - 1];
if (!c.IsWhiteSpace)
break;
lineCharIdx--;
if (prevSelection != null)
{
prevSelection.mValue.mStartPos--;
prevSelection.mValue.mEndPos--;
}
Backspace();
if (c == '\t')
break;
}
}
else if (mAllowVirtualCursor)
{
var cursorPos = CursorLineAndColumn;
cursorPos.mColumn = Math.Max(0, (cursorPos.mColumn - 1) / mTabLength * mTabLength);
CursorLineAndColumn = cursorPos;
}
mSelection = prevSelection;
return;
}
String curLineText = scope String();
GetLineText(lineIdx, curLineText);
int32 indentCount = 1;
String trimmedCurLineText = scope String(curLineText);
trimmedCurLineText.Trim();
if ((lineCharIdx == 0) && (trimmedCurLineText.Length == 0)) // Smart indent
{
if (mAllowVirtualCursor)
{
if (HasSelection())
DeleteSelection();
var prevCursorPos = CursorLineAndColumn;
CursorToLineEnd();
if (prevCursorPos.mColumn >= CursorLineAndColumn.mColumn)
{
var cursorPos = prevCursorPos;
cursorPos.mColumn = (prevCursorPos.mColumn + mTabLength) / mTabLength * mTabLength;
CursorLineAndColumn = cursorPos;
}
return;
}
int checkLineIdx = lineIdx - 1;
while (checkLineIdx >= 0)
{
String lineText = scope String();
GetLineText(checkLineIdx, lineText);
lineText.TrimEnd();
int32 checkIndentCount = 0;
for (; checkIndentCount < lineText.Length; checkIndentCount++)
{
if (lineText[checkIndentCount] != '\t')
break;
}
if ((checkIndentCount < lineText.Length) && (lineText[checkIndentCount] == '{'))
checkIndentCount++;
if (checkIndentCount > 0)
{
indentCount = checkIndentCount;
break;
}
checkLineIdx--;
}
}
if (HasSelection())
{
GetLinePosition(minLineIdx, var lineStart, var lineEnd);
String str = scope .();
ExtractString(lineStart, CursorTextPos - lineStart, str);
if (str.IsWhiteSpace)
{
let prevSelection = mSelection.Value;
mSelection = null;
for (int32 i = 0; i < indentCount; i++)
InsertAtCursor("\t");
GetLinePosition(minLineIdx, out lineStart, out lineEnd);
mSelection = EditSelection(prevSelection.mStartPos + indentCount, prevSelection.mEndPos + indentCount);
}
else
InsertAtCursor("\t");
}
else
{
for (int32 i = 0; i < indentCount; i++)
InsertAtCursor("\t");
}
}
}
public void SelectAll()
{
CursorToStart();
StartSelection();
CursorToEnd();
SelectToCursor();
Debug.Assert(mSelection.Value.mEndPos <= mData.mTextLength);
}
//public void MoveCursorTo
public void MoveCursorTo(int line, int char8Idx, bool centerCursor = false, int movingDir = 0)
{
int useCharIdx = char8Idx;
mShowCursorAtLineEnd = false;
CursorTextPos = GetTextIdx(line, char8Idx);
// Skip over UTF8 parts AND unicode combining marks (ie: when we have a letter with an accent mark following it)
while (true)
{
char8 c = mData.SafeGetChar(mCursorTextPos);
if (c < (char8)0x80)
break;
if ((uint8)c & 0xC0 != 0x80)
{
var checkChar = mData.GetChar32(mCursorTextPos);
if (!checkChar.IsCombiningMark)
break;
}
if (movingDir > 0)
{
useCharIdx++;
mCursorTextPos++;
}
else
{
if (mCursorTextPos == 0)
break;
useCharIdx--;
mCursorTextPos--;
}
}
float aX;
float aY;
GetTextCoordAtLineChar(line, useCharIdx, out aX, out aY);
mCursorWantX = aX;
mCursorBlinkTicks = 0;
if (mEnsureCursorVisibleOnModify)
EnsureCursorVisible(true, centerCursor);
PhysCursorMoved();
}
public void ResetWantX()
{
int line;
int col;
GetLineCharAtIdx(CursorTextPos, out line, out col);
float x;
float y;
GetTextCoordAtLineChar(line, col, out x, out y);
mCursorWantX = x;
}
public void MoveCursorToIdx(int index, bool centerCursor = false)
{
int aLine;
int aCharIdx;
GetLineCharAtIdx(index, out aLine, out aCharIdx);
MoveCursorTo(aLine, aCharIdx, centerCursor);
}
public void MoveCursorToCoord(float x, float y)
{
bool failed = false;
int aLine;
int aCharIdx;
float overflowX;
failed = !GetLineCharAtCoord(x, y, out aLine, out aCharIdx, out overflowX);
if ((mAllowVirtualCursor) && (failed))
{
int line;
int column;
GetLineAndColumnAtCoord(x, y, out line, out column);
CursorLineAndColumn = LineAndColumn(line, column);
mCursorBlinkTicks = 0;
PhysCursorMoved();
mShowCursorAtLineEnd = false;
}
else
{
if ((mWordWrap) && (failed))
mShowCursorAtLineEnd = true;
MoveCursorTo(aLine, aCharIdx);
int lineStart;
int lineEnd;
GetLinePosition(aLine, out lineStart, out lineEnd);
if ((mWordWrap) && (aCharIdx == lineEnd - lineStart) && (lineEnd != lineStart))
mShowCursorAtLineEnd = true;
}
}
public bool HasSelection()
{
return (mSelection != null) && (mSelection.Value.HasSelection);
}
public override void RehupScale(float oldScale, float newScale)
{
base.RehupScale(oldScale, newScale);
mTextInsets.Scale(newScale / oldScale);
//ContentChanged
}
//public virtual void DrawContent(
public void ClearUndoData()
{
if (mData.mUndoManager != null)
{
mData.mUndoManager.Clear();
}
}
public bool HasUndoData()
{
if (mData.mUndoManager != null)
{
return mData.mUndoManager.GetActionCount() > 0;
}
return false;
}
}
public abstract class EditWidget : ScrollableWidget
{
public EditWidgetContent mEditWidgetContent;
public Event<EditWidgetEventHandler> mOnSubmit ~ _.Dispose();
public Event<EditWidgetEventHandler> mOnCancel ~ _.Dispose();
public Event<EditWidgetEventHandler> mOnContentChanged ~ _.Dispose();
public EditWidgetContent Content
{
get { return mEditWidgetContent; }
}
public void GetText(String outStr)
{
mEditWidgetContent.ExtractString(0, mEditWidgetContent.mData.mTextLength, outStr);
}
public bool IsWhiteSpace()
{
for (int i < mEditWidgetContent.mData.mTextLength)
{
char8 c = mEditWidgetContent.mData.mText[i].mChar;
if (!c.IsWhiteSpace)
return false;
}
return true;
}
public virtual void Submit()
{
if (mOnSubmit.HasListeners)
{
EditEvent anEvent = scope EditEvent();
anEvent.mSender = this;
mOnSubmit(anEvent);
}
}
public void Cancel()
{
if (mOnCancel.HasListeners)
{
EditEvent editEvent = scope EditEvent();
editEvent.mSender = this;
mOnCancel(editEvent);
return;
}
}
public void ContentChanged()
{
if (mOnContentChanged.HasListeners)
{
EditEvent editEvent = scope EditEvent();
editEvent.mSender = this;
mOnContentChanged(editEvent);
return;
}
}
public virtual void SetText(String text)
{
scope AutoBeefPerf("EditWidget.SetText");
var prevLineAndColumn = mEditWidgetContent.CursorLineAndColumn;
float cursorPos = (float)mVertPos.mDest;
EditWidgetContent.TextUndoBatchStart undoBatchStart = null;
if (mEditWidgetContent.WantsUndo)
{
undoBatchStart = new EditWidgetContent.TextUndoBatchStart(mEditWidgetContent, "setText");
mEditWidgetContent.mData.mUndoManager.Add(undoBatchStart);
}
mEditWidgetContent.CursorToStart();
mEditWidgetContent.SelectAll();
mEditWidgetContent.InsertAtCursor(text);
if (mEditWidgetContent.mAllowVirtualCursor)
mEditWidgetContent.CursorLineAndColumn = prevLineAndColumn;
mEditWidgetContent.ClampCursor();
mVertPos.Set(cursorPos, true);
if (mEditWidgetContent.WantsUndo)
mEditWidgetContent.mData.mUndoManager.Add(undoBatchStart.mBatchEnd);
if ((!mHasFocus) && (mHorzPos != null))
mHorzPos.Set(0, true);
//TextChanged();
//mEditWidgetContent.mUndoManager.Clear();
}
public IdSpan DuplicateTextIdData()
{
return mEditWidgetContent.mData.mTextIdData.Duplicate();
}
public override void MouseDown(float x, float y, int32 btn, int32 btnCount)
{
base.MouseDown(x, y, btn, btnCount);
// If we get clicked on ourselves then just translate into editwidget space to get the focus
float childX;
float childY;
mEditWidgetContent.ParentToSelfTranslate(x, y, out childX, out childY);
mEditWidgetContent.MouseDown(childX, childY, btn, btnCount);
}
public override void SetFocus()
{
mWidgetWindow.SetFocus(this);
}
public override void GotFocus()
{
base.GotFocus();
MouseEventHandler handler = new => HandleWindowMouseDown;
//Debug.WriteLine("Adding handler {0}", handler);
mWidgetWindow.mOnMouseDown.Add(handler);
}
public override void LostFocus()
{
MarkDirty();
var widgetWindow = mWidgetWindow;
base.LostFocus();
widgetWindow.mOnMouseDown.Remove(scope => HandleWindowMouseDown, true);
//Debug.WriteLine("Removing handler (LostFocus)");
}
protected virtual bool WantsUnfocus()
{
return (!mMouseOver) && (mWidgetWindow != null) && (!ContainsWidget(mWidgetWindow.mOverWidget));
}
protected virtual void HandleWindowMouseDown(MouseEvent theEvent)
{
if (WantsUnfocus())
{
// Someone else got clicked on, remove focus
//mWidgetWindow.mMouseDownHandler.Remove(scope => HandleWindowMouseDown, true);
//Debug.WriteLine("Removing handler (HandleWindowMouseDown)");
mWidgetWindow.SetFocus(null);
}
}
public override void RemovedFromParent(Widget previousParent, WidgetWindow previousWidgetWindow)
{
base.RemovedFromParent(previousParent, previousWidgetWindow);
//TODO: Does this cause problems if we didn't have the handler added?
if (previousWidgetWindow != null)
{
//previousWidgetWindow.mMouseDownHandler.Remove(scope => HandleWindowMouseDown, true);
//Debug.WriteLine("Removing handler (RemovedFromParent)");
}
}
public override void KeyChar(char32 theChar)
{
base.KeyChar(theChar);
mEditWidgetContent.KeyChar(theChar);
}
public override void Resize(float x, float y, float width, float height)
{
base.Resize(x, y, width, height);
if (mEditWidgetContent.mWordWrap) // Need to recalculate word wrapping
mEditWidgetContent.ContentChanged();
}
public override void KeyDown(KeyCode keyCode, bool isRepeat)
{
base.KeyDown(keyCode, isRepeat);
if (mWidgetWindow != null)
mEditWidgetContent.KeyDown(keyCode, isRepeat);
}
public void FinishScroll()
{
mVertPos.mPct = 1.0f;
UpdateContentPosition();
}
}
}