DarkEditWidget.bf 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926
  1. using System;
  2. using System.Collections;
  3. using System.Diagnostics;
  4. using System.Text;
  5. using Beefy.widgets;
  6. using Beefy.gfx;
  7. using Beefy.utils;
  8. namespace Beefy.theme.dark
  9. {
  10. public class DarkEditWidgetContent : EditWidgetContent
  11. {
  12. public Font mFont;
  13. public uint32[] mTextColors = sDefaultColors;
  14. public uint32 mHiliteColor = 0xFF2f5c88;
  15. public uint32 mUnfocusedHiliteColor = 0x00000000;
  16. public int32 mRecalcSizeLineNum = -1;
  17. public float mRecalcSizeCurMaxWidth = 0;
  18. public bool mHasQueuedRecalcSize;
  19. public int32 mTopCharId = -1;
  20. public double mTopCharIdVertPos = -1;
  21. public bool mWantsCheckScrollPosition;
  22. public uint32 mViewWhiteSpaceColor;
  23. public bool mScrollToStartOnLostFocus;
  24. protected static uint32[] sDefaultColors = new uint32[] { Color.White } ~ delete _;
  25. public this(EditWidgetContent refContent = null) : base(refContent)
  26. {
  27. //mTextInsets.Set(-3, 2, 0, 2);
  28. //mTextInsets.Set(GS!(-3), GS!(2), 0, GS!(2));
  29. mTextInsets.Set(GS!(0), GS!(2), 0, GS!(2));
  30. mWidth = GS!(100);
  31. mHeight = GS!(24);
  32. mHorzJumpSize = GS!(40);
  33. mFont = DarkTheme.sDarkTheme?.mSmallFont;
  34. }
  35. public override void GetTextData()
  36. {
  37. // Generate text flags if we need to...
  38. if ((mData.mTextFlags == null) && (mWordWrap))
  39. {
  40. scope AutoBeefPerf("DEWC.GetTextData");
  41. mData.mTextFlags = new uint8[mData.mTextLength + 1];
  42. int32 lineIdx = 0;
  43. int32 lineStartIdx = 0;
  44. String lineCheck = scope String();
  45. for (int32 i = 0; i < mData.mTextLength; i++)
  46. {
  47. char8 c = (char8)mData.mText[i].mChar;
  48. lineCheck.Clear();
  49. if (c == '\n')
  50. ExtractString(lineStartIdx, i - lineStartIdx, lineCheck);
  51. else if (i == mData.mTextLength - 1)
  52. ExtractString(lineStartIdx, i - lineStartIdx + 1, lineCheck);
  53. if (lineCheck.Length > 0)
  54. {
  55. String lineCheckLeft = scope String();
  56. lineCheckLeft.Reference(lineCheck);
  57. while (true)
  58. {
  59. int32 maxChars = GetTabbedCharCountToLength(lineCheckLeft, mEditWidget.mScrollContentContainer.mWidth - mTextInsets.mLeft - mTextInsets.mRight);
  60. if (maxChars == 0)
  61. maxChars = 1;
  62. if (maxChars >= lineCheckLeft.Length)
  63. break;
  64. int32 checkIdx = maxChars;
  65. while ((checkIdx > 0) && (!lineCheckLeft[checkIdx].IsWhiteSpace))
  66. checkIdx--;
  67. if (checkIdx == 0)
  68. checkIdx = maxChars - 1;
  69. mData.mTextFlags[lineStartIdx + checkIdx + 1] |= (int32)TextFlags.Wrap;
  70. lineStartIdx += checkIdx + 1;
  71. //lineCheck.Remove(0, checkIdx + 1);
  72. lineCheckLeft.AdjustPtr(checkIdx + 1);
  73. }
  74. }
  75. if (c == '\n')
  76. {
  77. lineStartIdx = i + 1;
  78. lineIdx++;
  79. }
  80. }
  81. }
  82. base.GetTextData();
  83. }
  84. protected override void AdjustCursorsAfterExternalEdit(int index, int ofs)
  85. {
  86. base.AdjustCursorsAfterExternalEdit(index, ofs);
  87. mWantsCheckScrollPosition = true;
  88. }
  89. public float GetTabbedPos(float startX)
  90. {
  91. float spaceWidth = mFont.GetWidth((char32)' ');
  92. if (mTabSize == 0)
  93. return startX + spaceWidth*4;
  94. return (float)Math.Truncate((startX + spaceWidth) / mTabSize + 0.999f) * mTabSize;
  95. }
  96. static mixin GetTabSection(var origString, var stringLeft, var subStr)
  97. {
  98. int32 tabIdx = (int32)stringLeft.IndexOf('\t');
  99. if (tabIdx == -1)
  100. break;
  101. if (subStr == null)
  102. {
  103. subStr = scope:: String(stringLeft, 0, tabIdx);
  104. stringLeft = scope:: String(origString, tabIdx + 1);
  105. }
  106. else
  107. {
  108. subStr.Clear();
  109. subStr.Append(stringLeft, 0, tabIdx);
  110. stringLeft.Remove(0, tabIdx + 1);
  111. }
  112. tabIdx
  113. }
  114. public float DoDrawText(Graphics g, String origString, float x, float y)
  115. {
  116. String stringLeft = origString;
  117. float aX = x;
  118. float aY = y;
  119. void DrawString(String str, float x, float y)
  120. {
  121. if (str.Length == 0)
  122. return;
  123. g.DrawString(str, x, y);
  124. if (mViewWhiteSpaceColor != 0)
  125. {
  126. let prevColor = g.mColor;
  127. g.PopColor();
  128. g.PushColor(mViewWhiteSpaceColor);
  129. float curX = x;
  130. int lastNonSpace = 0;
  131. for (int i < str.Length)
  132. {
  133. char8 c = str[i];
  134. if (c == ' ')
  135. {
  136. // Flush length
  137. if (lastNonSpace < i)
  138. {
  139. var contentStr = scope String();
  140. contentStr.Reference(str.Ptr + lastNonSpace, i - lastNonSpace);
  141. curX += mFont.GetWidth(contentStr);
  142. }
  143. g.DrawString("·", curX, y);
  144. curX += mFont.GetWidth(' ');
  145. lastNonSpace = i + 1;
  146. }
  147. }
  148. g.PopColor();
  149. g.PushColorOverride(prevColor);
  150. }
  151. }
  152. String subStr = null;
  153. while (true)
  154. {
  155. GetTabSection!(origString, stringLeft, subStr);
  156. if (g != null)
  157. DrawString(subStr, aX, aY);
  158. aX += mFont.GetWidth(subStr);
  159. if ((mViewWhiteSpaceColor != 0) && (g != null))
  160. {
  161. let prevColor = g.mColor;
  162. g.PopColor();
  163. g.PushColor(mViewWhiteSpaceColor);
  164. g.DrawString("→", aX, y);
  165. g.PopColor();
  166. g.PushColorOverride(prevColor);
  167. }
  168. aX = GetTabbedPos(aX);
  169. }
  170. if (g != null)
  171. DrawString(stringLeft, aX, aY);
  172. //TODO: This is just an "emergency dropout", remove when we optimize more?
  173. /*if ((mX + x >= 0) && (stringLeft.Length > 1000))
  174. {
  175. return aX + 10000;
  176. }*/
  177. aX += mFont.GetWidth(stringLeft);
  178. return aX;
  179. }
  180. /*public int GetTabbedCharCountToLength(String origString, float len)
  181. {
  182. String stringLeft = origString;
  183. float aX = 0;
  184. int idx = 0;
  185. String subStr = null;
  186. while (true)
  187. {
  188. int tabIdx = GetTabSection!(origString, stringLeft, subStr);
  189. int char8Count = mFont.GetCharCountToLength(subStr, len - aX);
  190. if (char8Count < subStr.Length)
  191. return idx + char8Count;
  192. idx += tabIdx + 1;
  193. aX += mFont.GetWidth(subStr);
  194. float prevX = aX;
  195. aX = GetTabbedPos(aX);
  196. if (len < aX)
  197. return idx - 1;
  198. }
  199. return idx + mFont.GetCharCountToLength(stringLeft, len - aX);
  200. }*/
  201. public int32 GetTabbedCharCountToLength(String origString, float len)
  202. {
  203. float aX = 0;
  204. int32 idx = 0;
  205. String subStr = scope String();
  206. subStr.Reference(origString);
  207. while (true)
  208. {
  209. bool hitTabStop = false;
  210. int32 char8Count = (int32)mFont.GetCharCountToLength(subStr, len - aX, &hitTabStop);
  211. if (!hitTabStop)
  212. return idx + char8Count;
  213. aX += mFont.GetWidth(StringView(subStr, 0, char8Count));
  214. aX = GetTabbedPos(aX);
  215. if (aX > len + 0.001f)
  216. return idx + char8Count;
  217. idx += char8Count + 1;
  218. subStr.AdjustPtr(char8Count + 1);
  219. }
  220. }
  221. public virtual void DrawSectionFlagsOver(Graphics g, float x, float y, float width, uint8 flags)
  222. {
  223. }
  224. public float GetTabbedWidth(String origString, float x, bool forceAccurate = false)
  225. {
  226. String stringLeft = origString;
  227. float aX = x;
  228. String subStr = null;
  229. while (true)
  230. {
  231. #unwarn
  232. int32 tabIdx = GetTabSection!(origString, stringLeft, subStr);
  233. aX += mFont.GetWidth(subStr);
  234. aX = GetTabbedPos(aX);
  235. }
  236. //TODO: This is just an "emergency dropout", remove when we optimize more?
  237. /*if ((!forceAccurate) && (mX + x >= 0) && (stringLeft.Length > 1000))
  238. {
  239. return aX + 10000;
  240. }*/
  241. return aX + mFont.GetWidth(stringLeft);
  242. }
  243. public void SetFont(Font font, bool isMonospace, bool virtualCursor)
  244. {
  245. mFont = font;
  246. if (isMonospace)
  247. {
  248. mCharWidth = mFont.GetWidth(' ');
  249. if (mTabSize == 0)
  250. mTabSize = mTabLength * mCharWidth;
  251. else
  252. mTabSize = (float)Math.Round(mTabSize / mCharWidth) * mCharWidth;
  253. }
  254. else
  255. {
  256. mCharWidth = -1;
  257. mTabSize = mTabLength * mFont.GetWidth('W');
  258. }
  259. if (virtualCursor)
  260. Debug.Assert(isMonospace);
  261. mAllowVirtualCursor = virtualCursor;
  262. }
  263. public override void RehupScale(float oldScale, float newScale)
  264. {
  265. base.RehupScale(oldScale, newScale);
  266. Utils.RoundScale(ref mTabSize, newScale / oldScale);
  267. SetFont(mFont, mCharWidth != -1, mAllowVirtualCursor);
  268. mContentChanged = true; // Defer calling of RecalcSize
  269. }
  270. public virtual float DrawText(Graphics g, String str, float x, float y, uint16 typeIdAndFlags)
  271. {
  272. using (g.PushColor(mTextColors[typeIdAndFlags & 0xFF]))
  273. return DoDrawText(g, str, x, y);
  274. }
  275. public virtual uint32 GetSelectionColor(uint8 flags)
  276. {
  277. return mEditWidget.mHasFocus ? mHiliteColor : mUnfocusedHiliteColor;
  278. }
  279. public override void Draw(Graphics g)
  280. {
  281. base.Draw(g);
  282. #unwarn
  283. int lineCount = GetLineCount();
  284. float lineSpacing = GetLineHeight(0);
  285. g.SetFont(mFont);
  286. float offsetY = mTextInsets.mTop;
  287. if (mHeight < lineSpacing)
  288. offsetY = (mHeight - lineSpacing) * 0.75f;
  289. g.PushTranslate(mTextInsets.mLeft, offsetY);
  290. int selStartLine = -1;
  291. int selStartCharIdx = -1;
  292. int selEndLine = -1;
  293. int selEndCharIdx = -1;
  294. int selStartIdx = -1;
  295. int selEndIdx = -1;
  296. if (mSelection != null)
  297. {
  298. mSelection.Value.GetAsForwardSelect(out selStartIdx, out selEndIdx);
  299. GetLineCharAtIdx(selStartIdx, out selStartLine, out selStartCharIdx);
  300. GetLineCharAtIdx(selEndIdx, out selEndLine, out selEndCharIdx);
  301. }
  302. int firstLine;
  303. int firstCharIdx;
  304. float overflowX;
  305. GetLineCharAtCoord(0, -mY, out firstLine, out firstCharIdx, out overflowX);
  306. int lastLine;
  307. int lastCharIdx;
  308. float lastOverflowX;
  309. GetLineCharAtCoord(0, -mY + mEditWidget.mScrollContentContainer.mHeight, out lastLine, out lastCharIdx, out lastOverflowX);
  310. bool drewCursor = false;
  311. String sectionText = scope String(256);
  312. for (int lineIdx = firstLine; lineIdx <= lastLine; lineIdx++)
  313. {
  314. //string lineText = GetLineText(lineIdx);
  315. int lineStart;
  316. int lineEnd;
  317. GetLinePosition(lineIdx, out lineStart, out lineEnd);
  318. int lineDrawStart = lineStart;
  319. float curX = 0;
  320. float curY = lineIdx * lineSpacing;
  321. while (true)
  322. {
  323. int lineDrawEnd = lineDrawStart;
  324. uint16 curTypeIdAndFlags = *(uint16*)&mData.mText[lineDrawStart].mDisplayTypeId;
  325. // Check for transition of curTypeIdAndFlags - colors ignore whitespace, but if flags are set then we need
  326. // to be exact
  327. /*while ((lineDrawEnd < lineEnd) && ((*(uint16*)&mData.mText[lineDrawEnd].mDisplayTypeId == curTypeIdAndFlags) ||
  328. ((curTypeIdAndFlags < 0x100) && (((char8)mData.mText[lineDrawEnd].mChar).IsWhiteSpace))))
  329. lineDrawEnd++;*/
  330. while (true)
  331. {
  332. var checkEnd = ref mData.mText[lineDrawEnd];
  333. if ((lineDrawEnd < lineEnd) && ((*(uint16*)&checkEnd.mDisplayTypeId == curTypeIdAndFlags) ||
  334. ((curTypeIdAndFlags < 0x100) && (checkEnd.mChar.IsWhiteSpace) && (checkEnd.mDisplayFlags == 0))))
  335. lineDrawEnd++;
  336. else
  337. break;
  338. }
  339. sectionText.Clear();
  340. ExtractString(lineDrawStart, lineDrawEnd - lineDrawStart, sectionText);
  341. int selStart = Math.Max(0, selStartIdx - lineDrawStart);
  342. int selEnd = Math.Min(lineDrawEnd - lineDrawStart, selEndIdx - lineDrawStart);
  343. uint8 flags = (uint8)(curTypeIdAndFlags >> 8);
  344. if ((lineDrawStart >= selStartIdx) && (lineDrawEnd < selEndIdx) && (lineDrawEnd == lineDrawStart))
  345. {
  346. // Blank line selected
  347. using (g.PushColor(GetSelectionColor(flags)))
  348. g.FillRect(curX, curY, 4, lineSpacing);
  349. }
  350. if (selEnd > selStart)
  351. {
  352. String selPrevString = scope String(selStart);
  353. selPrevString.Append(sectionText, 0, selStart);
  354. String selIncludeString = scope String(selEnd);
  355. selIncludeString.Append(sectionText, 0, selEnd);
  356. float selStartX = GetTabbedWidth(selPrevString, curX);
  357. float selEndX = GetTabbedWidth(selIncludeString, curX);
  358. if (lineIdx != selEndLine)
  359. selEndX += mFont.GetWidth((char32)' ');
  360. using (g.PushColor(GetSelectionColor(flags)))
  361. g.FillRect(selStartX, curY, selEndX - selStartX, lineSpacing);
  362. }
  363. float nextX = curX;
  364. nextX = DrawText(g, sectionText, curX, curY, curTypeIdAndFlags);
  365. DrawSectionFlagsOver(g, curX, curY, nextX - curX, flags);
  366. //int32 lineDrawStartColumn = lineDrawStart - lineStart;
  367. //int32 lineDrawEndColumn = lineDrawEnd - lineStart;
  368. if ((mEditWidget.mHasFocus) && (!drewCursor))
  369. {
  370. float aX = -1;
  371. if (mVirtualCursorPos != null)
  372. {
  373. if ((lineIdx == mVirtualCursorPos.Value.mLine) && (lineDrawEnd == lineEnd))
  374. {
  375. aX = mVirtualCursorPos.Value.mColumn * mCharWidth;
  376. }
  377. }
  378. else if (mCursorTextPos >= lineDrawStart)
  379. {
  380. bool isInside = mCursorTextPos < lineDrawEnd;
  381. if ((mCursorTextPos == lineDrawEnd) && (lineDrawEnd == lineEnd))
  382. {
  383. if (lineDrawEnd == mData.mTextLength)
  384. isInside = true;
  385. if (mWordWrap)
  386. {
  387. if ((mShowCursorAtLineEnd) || (lineEnd >= mData.mTextFlags.Count) || (mData.mTextFlags[lineEnd] & (int32)TextFlags.Wrap) == 0)
  388. isInside = true;
  389. }
  390. else
  391. isInside = true;
  392. }
  393. if (isInside)
  394. {
  395. String subText = scope String(mCursorTextPos - lineDrawStart);
  396. subText.Append(sectionText, 0, mCursorTextPos - lineDrawStart);
  397. aX = GetTabbedWidth(subText, curX);
  398. }
  399. }
  400. if (aX != -1)
  401. {
  402. float brightness = (float)Math.Cos(Math.Max(0.0f, mCursorBlinkTicks - 20) / 9.0f);
  403. brightness = Math.Max(0, Math.Min(1.0f, brightness * 2.0f + 1.6f));
  404. if (mEditWidget.mVertPos.IsMoving)
  405. brightness = 0; // When we animate a pgup or pgdn, it's weird seeing the cursor scrolling around
  406. Color cursorColor = mTextColors[0];
  407. if (mOverTypeMode)
  408. {
  409. if (mCharWidth <= 2)
  410. {
  411. using (g.PushColor(Color.Mult(cursorColor, Color.Get(brightness * 0.75f))))
  412. g.FillRect(aX, curY, GS!(2), lineSpacing);
  413. }
  414. else
  415. {
  416. using (g.PushColor(Color.Mult(cursorColor, Color.Get(brightness * 0.30f))))
  417. g.FillRect(aX, curY, mCharWidth, lineSpacing);
  418. }
  419. }
  420. else
  421. {
  422. using (g.PushColor(Color.Mult(cursorColor, Color.Get(brightness))))
  423. g.FillRect(aX, curY, Math.Max(1.0f, GS!(1)), lineSpacing);
  424. }
  425. drewCursor = true;
  426. }
  427. }
  428. lineDrawStart = lineDrawEnd;
  429. curX = nextX;
  430. if (lineDrawStart >= lineEnd)
  431. break;
  432. }
  433. }
  434. g.PopMatrix();
  435. /*using (g.PushColor(0x4000FF00))
  436. g.FillRect(-8, -8, mWidth + 16, mHeight + 16);*/
  437. /*if (mDbgX != -1)
  438. g.FillRect(mDbgX - 1, mDbgY - 1, 3, 3);*/
  439. }
  440. public override void AddWidget(Widget widget)
  441. {
  442. base.AddWidget(widget);
  443. }
  444. public override bool AllowChar(char32 theChar)
  445. {
  446. if ((int)theChar < 32)
  447. return (theChar == '\n') || (mIsMultiline && (theChar == '\t'));
  448. return mFont.HasChar(theChar);
  449. }
  450. public override void InsertAtCursor(String theString, InsertFlags insertFlags)
  451. {
  452. scope AutoBeefPerf("DarkEditWidgetContent.InsertAtCursor");
  453. base.InsertAtCursor(theString, insertFlags);
  454. }
  455. public override void GetTextCoordAtLineChar(int line, int lineChar, out float x, out float y)
  456. {
  457. String lineText = scope String(256);
  458. GetLineText(line, lineText);
  459. if (lineChar > lineText.Length)
  460. x = GetTabbedWidth(lineText, 0) + (mFont.GetWidth((char32)' ') * (lineChar - (int32)lineText.Length)) + mTextInsets.mLeft;
  461. else
  462. {
  463. String subText = scope String(Math.Min(lineChar, 256));
  464. subText.Append(lineText, 0, lineChar);
  465. x = GetTabbedWidth(subText, 0, true) + mTextInsets.mLeft;
  466. }
  467. y = mTextInsets.mTop + line * mFont.GetLineSpacing();
  468. }
  469. public override void GetTextCoordAtLineAndColumn(int line, int column, out float x, out float y)
  470. {
  471. Debug.Assert((mCharWidth != -1) || (column == 0));
  472. String lineText = scope String(256);
  473. GetLineText(line, lineText);
  474. x = mTextInsets.mLeft + column * mCharWidth;
  475. y = mTextInsets.mTop + line * mFont.GetLineSpacing();
  476. }
  477. public override bool GetLineCharAtCoord(float x, float y, out int line, out int char8Idx, out float overflowX)
  478. {
  479. line = (int) ((y - mTextInsets.mTop) / mFont.GetLineSpacing() + 0.001f);
  480. int lineCount = GetLineCount();
  481. if (line < 0)
  482. line = 0;
  483. if (line >= lineCount)
  484. line = lineCount - 1;
  485. String lineText = scope String(256);
  486. GetLineText(line, lineText);
  487. int32 char8Count = GetTabbedCharCountToLength(lineText, x - mTextInsets.mLeft);
  488. char8Idx = char8Count;
  489. if (char8Count < lineText.Length)
  490. {
  491. String subString = scope String(char8Count);
  492. subString.Append(lineText, 0, char8Count);
  493. float subWidth = GetTabbedWidth(subString, 0);
  494. var utf8enumerator = lineText.DecodedChars(char8Count);
  495. if (utf8enumerator.MoveNext())
  496. {
  497. char32 c = utf8enumerator.Current;
  498. float checkCharWidth = 0;
  499. if (c == '\t')
  500. checkCharWidth = mTabSize * 0.5f;
  501. else
  502. {
  503. checkCharWidth = mFont.GetWidth(c) * 0.5f;
  504. }
  505. if (x >= subWidth + mTextInsets.mLeft + checkCharWidth)
  506. char8Idx = (int32)utf8enumerator.NextIndex;
  507. }
  508. }
  509. else
  510. {
  511. overflowX = (x - mTextInsets.mLeft) - (GetTabbedWidth(lineText, 0) + 0.001f);
  512. return overflowX <= 0;
  513. }
  514. overflowX = 0;
  515. return true;
  516. }
  517. public override bool GetLineAndColumnAtCoord(float x, float y, out int line, out int column)
  518. {
  519. line = (int32)((y - mTextInsets.mTop) / mFont.GetLineSpacing() + 0.001f);
  520. if (line >= GetLineCount())
  521. line = GetLineCount() - 1;
  522. line = Math.Max(0, line);
  523. column = Math.Max(0, (int32)((x - mTextInsets.mLeft + 1) / mCharWidth + 0.6f));
  524. return mCharWidth != -1;
  525. }
  526. void RecalcSize(int32 startLineNum, int32 endLineNum, bool forceAccurate = false)
  527. {
  528. scope AutoBeefPerf("DEWC.RecalcSize");
  529. String line = scope String();
  530. for (int32 lineIdx = startLineNum; lineIdx < endLineNum; lineIdx++)
  531. {
  532. line.Clear();
  533. GetLineText(lineIdx, line);
  534. mRecalcSizeCurMaxWidth = Math.Max(mRecalcSizeCurMaxWidth, GetTabbedWidth(line, 0, forceAccurate) + mHorzJumpSize);
  535. Debug.Assert(!mRecalcSizeCurMaxWidth.IsNaN);
  536. }
  537. }
  538. public override void CursorToLineEnd()
  539. {
  540. //int32 line;
  541. //int32 lineChar;
  542. //GetCursorLineChar(out line, out lineChar);
  543. /*RecalcSize(line, line + 1, true);
  544. if (mRecalcSizeCurMaxWidth > mWidth)
  545. {
  546. mRecalcSizeLineNum = -1;
  547. }*/
  548. mRecalcSizeLineNum = -1;
  549. RecalcSize(true);
  550. base.CursorToLineEnd();
  551. }
  552. public void RecalcSize(bool forceAccurate = false)
  553. {
  554. mMaximalScrollAddedHeight = 0;
  555. if (mRecalcSizeLineNum == -1)
  556. {
  557. mRecalcSizeCurMaxWidth = 0;
  558. mHasQueuedRecalcSize = false;
  559. }
  560. else // We need to recalc again after our current pass
  561. mHasQueuedRecalcSize = true;
  562. if (!mIsReadOnly)
  563. {
  564. float cursorX;
  565. float cursorY;
  566. GetTextCoordAtCursor(out cursorX, out cursorY);
  567. mRecalcSizeCurMaxWidth = Math.Max(mRecalcSizeCurMaxWidth, cursorX + mHorzJumpSize);
  568. }
  569. if (mUpdateCnt == 0)
  570. {
  571. RecalcSize(0, GetLineCount());
  572. mWidth = mRecalcSizeCurMaxWidth + mTextInsets.mLeft + mTextInsets.mRight;
  573. Debug.Assert(!mWidth.IsNaN);
  574. }
  575. else if (mRecalcSizeLineNum == -1)
  576. {
  577. mRecalcSizeLineNum = 0;
  578. // The actual recalculation will take 16 ticks so just make sure we have enough width for
  579. // the current line for now
  580. var lineAndCol = CursorLineAndColumn;
  581. RecalcSize(lineAndCol.mLine, lineAndCol.mLine + 1, forceAccurate);
  582. mWidth = Math.Max(mWidth, mRecalcSizeCurMaxWidth + mTextInsets.mLeft + mTextInsets.mRight);
  583. Debug.Assert(!mWidth.IsNaN);
  584. }
  585. mHeight = GetLineCount() * mFont.GetLineSpacing() + mTextInsets.mTop + mTextInsets.mBottom;
  586. UpdateMaximalScroll();
  587. base.RecalcSize();
  588. }
  589. public override void RecalcSize()
  590. {
  591. RecalcSize(false);
  592. }
  593. public override void ContentChanged()
  594. {
  595. base.ContentChanged();
  596. mRecalcSizeLineNum = -1;
  597. }
  598. public override void TextAppended(String str)
  599. {
  600. if ((mData.mLineStarts != null) && (mIsReadOnly))
  601. {
  602. int32 recalcSizeLineNum = Math.Max((int32)mData.mLineStarts.Count - 2, 0);
  603. if ((mRecalcSizeLineNum == -1) || (recalcSizeLineNum < mRecalcSizeLineNum))
  604. mRecalcSizeLineNum = recalcSizeLineNum;
  605. }
  606. base.TextAppended(str);
  607. }
  608. void UpdateMaximalScroll()
  609. {
  610. if (mAllowMaximalScroll)
  611. {
  612. let prevHeight = mHeight;
  613. mHeight -= mMaximalScrollAddedHeight;
  614. mMaximalScrollAddedHeight = mEditWidget.mScrollContentContainer.mHeight - mFont.GetLineSpacing();
  615. mHeight += mMaximalScrollAddedHeight;
  616. if (mHeight != prevHeight)
  617. mEditWidget.UpdateScrollbars();
  618. }
  619. }
  620. public override void Resize(float x, float y, float width, float height)
  621. {
  622. base.Resize(x, y, width, height);
  623. UpdateMaximalScroll();
  624. }
  625. public override float GetLineHeight(int line)
  626. {
  627. return mFont.GetLineSpacing();
  628. }
  629. public override float GetPageScrollTextHeight()
  630. {
  631. float numLinesVisible = mEditWidget.mScrollContentContainer.mHeight / mFont.GetLineSpacing();
  632. if (numLinesVisible - (int32)numLinesVisible < 0.90f)
  633. numLinesVisible = (int32) numLinesVisible;
  634. float val = numLinesVisible * mFont.GetLineSpacing();
  635. if (val <= 0)
  636. return base.GetPageScrollTextHeight();
  637. return val;
  638. }
  639. public void CheckRecordScrollTop()
  640. {
  641. if (mWantsCheckScrollPosition)
  642. {
  643. if (mTopCharId != -1)
  644. {
  645. int textIdx = mData.mTextIdData.GetPrepared().GetIndexFromId(mTopCharId);
  646. if (textIdx != -1)
  647. {
  648. int line;
  649. int lineChar;
  650. GetLineCharAtIdx(textIdx, out line, out lineChar);
  651. var vertPos = mEditWidget.mVertPos.mDest;
  652. var offset = vertPos % mFont.GetLineSpacing();
  653. mEditWidget.mVertScrollbar.ScrollTo(line * mFont.GetLineSpacing() + offset);
  654. }
  655. else
  656. {
  657. mTopCharId = -1;
  658. }
  659. }
  660. mWantsCheckScrollPosition = false;
  661. }
  662. if (mEditWidget.mHasFocus)
  663. {
  664. mTopCharId = -1;
  665. }
  666. else
  667. {
  668. var vertPos = mEditWidget.mVertPos.mDest;
  669. if ((mTopCharId == -1) || (mTopCharIdVertPos != vertPos))
  670. {
  671. float lineNum = (float)(vertPos / mFont.GetLineSpacing());
  672. int lineStart;
  673. int lineEnd;
  674. GetLinePosition((int32)lineNum, out lineStart, out lineEnd);
  675. int idAtStart = mData.mTextIdData.GetIdAtIndex((int32)lineStart);
  676. if (idAtStart == -1)
  677. idAtStart = 0;
  678. mTopCharId = (int32)idAtStart;
  679. mTopCharIdVertPos = vertPos;
  680. }
  681. }
  682. }
  683. public override void Update()
  684. {
  685. base.Update();
  686. if ((mRecalcSizeLineNum != -1) && (BFApp.sApp.mIsUpdateBatchStart))
  687. {
  688. int32 lineCount = GetLineCount();
  689. int32 toLine = Math.Min(lineCount, mRecalcSizeLineNum + Math.Max(1, lineCount / 16) + 80);
  690. RecalcSize(mRecalcSizeLineNum, toLine);
  691. if (toLine == lineCount)
  692. {
  693. mRecalcSizeLineNum = -1;
  694. mWidth = mRecalcSizeCurMaxWidth + mTextInsets.mLeft + mTextInsets.mRight;
  695. base.RecalcSize();
  696. }
  697. else
  698. mRecalcSizeLineNum = toLine;
  699. }
  700. if ((mRecalcSizeLineNum == -1) && (mHasQueuedRecalcSize))
  701. RecalcSize();
  702. CheckRecordScrollTop();
  703. }
  704. }
  705. public class DarkEditWidget : EditWidget
  706. {
  707. public bool mDrawBox = true;
  708. public this(DarkEditWidgetContent content = null)
  709. {
  710. mEditWidgetContent = content;
  711. if (mEditWidgetContent == null)
  712. mEditWidgetContent = new DarkEditWidgetContent();
  713. mEditWidgetContent.mEditWidget = this;
  714. mScrollContent = mEditWidgetContent;
  715. mScrollContentContainer.AddWidget(mEditWidgetContent);
  716. SetupInsets();
  717. //mScrollbarInsets.Set(18, 1, 0, 0);
  718. mHorzPos.mSpeed = 0.2f;
  719. mVertPos.mSpeed = 0.2f;
  720. mScrollbarBaseContentSizeOffset = GS!(3);
  721. }
  722. protected virtual void SetupInsets()
  723. {
  724. mScrollContentInsets.Set(GS!(3), GS!(3), GS!(3), GS!(3));
  725. }
  726. protected override void HandleWindowMouseDown(Beefy.events.MouseEvent event)
  727. {
  728. base.HandleWindowMouseDown(event);
  729. // If we got closed as part of this click, don't propagate the click through
  730. if (mParent == null)
  731. {
  732. event.mHandled = true;
  733. }
  734. }
  735. public override void RehupScale(float oldScale, float newScale)
  736. {
  737. base.RehupScale(oldScale, newScale);
  738. SetupInsets();
  739. }
  740. public override void DefaultDesignInit()
  741. {
  742. base.DefaultDesignInit();
  743. mWidth = GS!(80);
  744. mHeight = GS!(20);
  745. SetText("Edit Text");
  746. }
  747. public override void InitScrollbars(bool wantHorz, bool wantVert)
  748. {
  749. SetupInsets();
  750. base.InitScrollbars(wantHorz, wantVert);
  751. float scrollIncrement = ((DarkEditWidgetContent) mEditWidgetContent).mFont.GetLineSpacing() * GS!(3);
  752. if (mHorzScrollbar != null)
  753. mHorzScrollbar.mScrollIncrement = scrollIncrement;
  754. if (mVertScrollbar != null)
  755. mVertScrollbar.mScrollIncrement = scrollIncrement;
  756. }
  757. public override void Draw(Graphics g)
  758. {
  759. base.Draw(g);
  760. if (mDrawBox)
  761. {
  762. g.DrawBox(DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.EditBox), 0, 0, mWidth, mHeight);
  763. if (mHasFocus)
  764. {
  765. using (g.PushColor(DarkTheme.COLOR_SELECTED_OUTLINE))
  766. g.DrawBox(DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.Outline), 0, 0, mWidth, mHeight);
  767. }
  768. }
  769. /*using (g.PushColor(0x40FF0000))
  770. g.FillRect(0, 0, mWidth, mHeight);*/
  771. }
  772. public override void LostFocus()
  773. {
  774. base.LostFocus();
  775. var darkEditWidgetContent = (DarkEditWidgetContent)mEditWidgetContent;
  776. darkEditWidgetContent.CheckRecordScrollTop();
  777. if (darkEditWidgetContent.mScrollToStartOnLostFocus)
  778. HorzScrollTo(0);
  779. }
  780. }
  781. }