DarkEditWidget.bf 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922
  1. using System;
  2. using System.Collections.Generic;
  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;
  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((char32)' ');
  249. //Debug.Assert(mFont.GetWidth((char32)'W') == mCharWidth);
  250. if (mTabSize == 0)
  251. mTabSize = mTabLength * mCharWidth;
  252. else
  253. mTabSize = (float)Math.Round(mTabSize / mCharWidth) * mCharWidth;
  254. }
  255. else
  256. mCharWidth = -1;
  257. if (virtualCursor)
  258. Debug.Assert(isMonospace);
  259. mAllowVirtualCursor = virtualCursor;
  260. }
  261. public override void RehupScale(float oldScale, float newScale)
  262. {
  263. base.RehupScale(oldScale, newScale);
  264. Utils.RoundScale(ref mTabSize, newScale / oldScale);
  265. SetFont(mFont, mCharWidth != -1, mAllowVirtualCursor);
  266. mContentChanged = true; // Defer calling of RecalcSize
  267. }
  268. public virtual float DrawText(Graphics g, String str, float x, float y, uint16 typeIdAndFlags)
  269. {
  270. using (g.PushColor(mTextColors[typeIdAndFlags & 0xFF]))
  271. return DoDrawText(g, str, x, y);
  272. }
  273. public virtual uint32 GetSelectionColor(uint8 flags)
  274. {
  275. return mEditWidget.mHasFocus ? mHiliteColor : mUnfocusedHiliteColor;
  276. }
  277. public override void Draw(Graphics g)
  278. {
  279. base.Draw(g);
  280. #unwarn
  281. int lineCount = GetLineCount();
  282. float lineSpacing = GetLineHeight(0);
  283. g.SetFont(mFont);
  284. float offsetY = mTextInsets.mTop;
  285. if (mHeight < lineSpacing)
  286. offsetY = (mHeight - lineSpacing) * 0.75f;
  287. g.PushTranslate(mTextInsets.mLeft, offsetY);
  288. int selStartLine = -1;
  289. int selStartCharIdx = -1;
  290. int selEndLine = -1;
  291. int selEndCharIdx = -1;
  292. int selStartIdx = -1;
  293. int selEndIdx = -1;
  294. if (mSelection != null)
  295. {
  296. mSelection.Value.GetAsForwardSelect(out selStartIdx, out selEndIdx);
  297. GetLineCharAtIdx(selStartIdx, out selStartLine, out selStartCharIdx);
  298. GetLineCharAtIdx(selEndIdx, out selEndLine, out selEndCharIdx);
  299. }
  300. int firstLine;
  301. int firstCharIdx;
  302. float overflowX;
  303. GetLineCharAtCoord(0, -mY, out firstLine, out firstCharIdx, out overflowX);
  304. int lastLine;
  305. int lastCharIdx;
  306. float lastOverflowX;
  307. GetLineCharAtCoord(0, -mY + mEditWidget.mScrollContentContainer.mHeight, out lastLine, out lastCharIdx, out lastOverflowX);
  308. bool drewCursor = false;
  309. String sectionText = scope String(256);
  310. for (int lineIdx = firstLine; lineIdx <= lastLine; lineIdx++)
  311. {
  312. //string lineText = GetLineText(lineIdx);
  313. int lineStart;
  314. int lineEnd;
  315. GetLinePosition(lineIdx, out lineStart, out lineEnd);
  316. int lineDrawStart = lineStart;
  317. float curX = 0;
  318. float curY = lineIdx * lineSpacing;
  319. while (true)
  320. {
  321. int lineDrawEnd = lineDrawStart;
  322. uint16 curTypeIdAndFlags = *(uint16*)&mData.mText[lineDrawStart].mDisplayTypeId;
  323. // Check for transition of curTypeIdAndFlags - colors ignore whitespace, but if flags are set then we need
  324. // to be exact
  325. /*while ((lineDrawEnd < lineEnd) && ((*(uint16*)&mData.mText[lineDrawEnd].mDisplayTypeId == curTypeIdAndFlags) ||
  326. ((curTypeIdAndFlags < 0x100) && (((char8)mData.mText[lineDrawEnd].mChar).IsWhiteSpace))))
  327. lineDrawEnd++;*/
  328. while (true)
  329. {
  330. var checkEnd = ref mData.mText[lineDrawEnd];
  331. if ((lineDrawEnd < lineEnd) && ((*(uint16*)&checkEnd.mDisplayTypeId == curTypeIdAndFlags) ||
  332. ((curTypeIdAndFlags < 0x100) && (checkEnd.mChar.IsWhiteSpace) && (checkEnd.mDisplayFlags == 0))))
  333. lineDrawEnd++;
  334. else
  335. break;
  336. }
  337. sectionText.Clear();
  338. ExtractString(lineDrawStart, lineDrawEnd - lineDrawStart, sectionText);
  339. int selStart = Math.Max(0, selStartIdx - lineDrawStart);
  340. int selEnd = Math.Min(lineDrawEnd - lineDrawStart, selEndIdx - lineDrawStart);
  341. uint8 flags = (uint8)(curTypeIdAndFlags >> 8);
  342. if ((lineDrawStart >= selStartIdx) && (lineDrawEnd < selEndIdx) && (lineDrawEnd == lineDrawStart))
  343. {
  344. // Blank line selected
  345. using (g.PushColor(GetSelectionColor(flags)))
  346. g.FillRect(curX, curY, 4, lineSpacing);
  347. }
  348. if (selEnd > selStart)
  349. {
  350. String selPrevString = scope String(selStart);
  351. selPrevString.Append(sectionText, 0, selStart);
  352. String selIncludeString = scope String(selEnd);
  353. selIncludeString.Append(sectionText, 0, selEnd);
  354. float selStartX = GetTabbedWidth(selPrevString, curX);
  355. float selEndX = GetTabbedWidth(selIncludeString, curX);
  356. if (lineIdx != selEndLine)
  357. selEndX += mFont.GetWidth((char32)' ');
  358. using (g.PushColor(GetSelectionColor(flags)))
  359. g.FillRect(selStartX, curY, selEndX - selStartX, lineSpacing);
  360. }
  361. float nextX = curX;
  362. nextX = DrawText(g, sectionText, curX, curY, curTypeIdAndFlags);
  363. DrawSectionFlagsOver(g, curX, curY, nextX - curX, flags);
  364. //int32 lineDrawStartColumn = lineDrawStart - lineStart;
  365. //int32 lineDrawEndColumn = lineDrawEnd - lineStart;
  366. if ((mEditWidget.mHasFocus) && (!drewCursor))
  367. {
  368. float aX = -1;
  369. if (mVirtualCursorPos != null)
  370. {
  371. if ((lineIdx == mVirtualCursorPos.Value.mLine) && (lineDrawEnd == lineEnd))
  372. {
  373. aX = mVirtualCursorPos.Value.mColumn * mCharWidth;
  374. }
  375. }
  376. else if (mCursorTextPos >= lineDrawStart)
  377. {
  378. bool isInside = mCursorTextPos < lineDrawEnd;
  379. if ((mCursorTextPos == lineDrawEnd) && (lineDrawEnd == lineEnd))
  380. {
  381. if (lineDrawEnd == mData.mTextLength)
  382. isInside = true;
  383. if (mWordWrap)
  384. {
  385. if ((mShowCursorAtLineEnd) || (lineEnd >= mData.mTextFlags.Count) || (mData.mTextFlags[lineEnd] & (int32)TextFlags.Wrap) == 0)
  386. isInside = true;
  387. }
  388. else
  389. isInside = true;
  390. }
  391. if (isInside)
  392. {
  393. String subText = scope String(mCursorTextPos - lineDrawStart);
  394. subText.Append(sectionText, 0, mCursorTextPos - lineDrawStart);
  395. aX = GetTabbedWidth(subText, curX);
  396. }
  397. }
  398. if (aX != -1)
  399. {
  400. float brightness = (float)Math.Cos(Math.Max(0.0f, mCursorBlinkTicks - 20) / 9.0f);
  401. brightness = Math.Max(0, Math.Min(1.0f, brightness * 2.0f + 1.6f));
  402. if (mEditWidget.mVertPos.IsMoving)
  403. brightness = 0; // When we animate a pgup or pgdn, it's weird seeing the cursor scrolling around
  404. if (mOverTypeMode)
  405. {
  406. if (mCharWidth <= 2)
  407. {
  408. using (g.PushColor(Color.Get(brightness * 0.75f)))
  409. g.FillRect(aX, curY, GS!(2), lineSpacing);
  410. }
  411. else
  412. {
  413. using (g.PushColor(Color.Get(brightness * 0.30f)))
  414. g.FillRect(aX, curY, mCharWidth, lineSpacing);
  415. }
  416. }
  417. else
  418. {
  419. using (g.PushColor(Color.Get(brightness)))
  420. g.FillRect(aX, curY, Math.Max(1.0f, GS!(1)), lineSpacing);
  421. }
  422. drewCursor = true;
  423. }
  424. }
  425. lineDrawStart = lineDrawEnd;
  426. curX = nextX;
  427. if (lineDrawStart >= lineEnd)
  428. break;
  429. }
  430. }
  431. g.PopMatrix();
  432. /*using (g.PushColor(0x4000FF00))
  433. g.FillRect(-8, -8, mWidth + 16, mHeight + 16);*/
  434. /*if (mDbgX != -1)
  435. g.FillRect(mDbgX - 1, mDbgY - 1, 3, 3);*/
  436. }
  437. public override void AddWidget(Widget widget)
  438. {
  439. base.AddWidget(widget);
  440. }
  441. public override bool AllowChar(char32 theChar)
  442. {
  443. if ((int)theChar < 32)
  444. return (theChar == '\n') || (mIsMultiline && (theChar == '\t'));
  445. return mFont.HasChar(theChar);
  446. }
  447. public override void InsertAtCursor(String theString, InsertFlags insertFlags)
  448. {
  449. scope AutoBeefPerf("DarkEditWidgetContent.InsertAtCursor");
  450. base.InsertAtCursor(theString, insertFlags);
  451. }
  452. public override void GetTextCoordAtLineChar(int line, int lineChar, out float x, out float y)
  453. {
  454. String lineText = scope String(256);
  455. GetLineText(line, lineText);
  456. if (lineChar > lineText.Length)
  457. x = GetTabbedWidth(lineText, 0) + (mFont.GetWidth((char32)' ') * (lineChar - (int32)lineText.Length)) + mTextInsets.mLeft;
  458. else
  459. {
  460. String subText = scope String(Math.Min(lineChar, 256));
  461. subText.Append(lineText, 0, lineChar);
  462. x = GetTabbedWidth(subText, 0, true) + mTextInsets.mLeft;
  463. }
  464. y = mTextInsets.mTop + line * mFont.GetLineSpacing();
  465. }
  466. public override void GetTextCoordAtLineAndColumn(int line, int column, out float x, out float y)
  467. {
  468. Debug.Assert((mCharWidth != -1) || (column == 0));
  469. String lineText = scope String(256);
  470. GetLineText(line, lineText);
  471. x = mTextInsets.mLeft + column * mCharWidth;
  472. y = mTextInsets.mTop + line * mFont.GetLineSpacing();
  473. }
  474. public override bool GetLineCharAtCoord(float x, float y, out int line, out int char8Idx, out float overflowX)
  475. {
  476. line = (int) ((y - mTextInsets.mTop) / mFont.GetLineSpacing() + 0.001f);
  477. int lineCount = GetLineCount();
  478. if (line < 0)
  479. line = 0;
  480. if (line >= lineCount)
  481. line = lineCount - 1;
  482. String lineText = scope String(256);
  483. GetLineText(line, lineText);
  484. int32 char8Count = GetTabbedCharCountToLength(lineText, x - mTextInsets.mLeft);
  485. char8Idx = char8Count;
  486. if (char8Count < lineText.Length)
  487. {
  488. String subString = scope String(char8Count);
  489. subString.Append(lineText, 0, char8Count);
  490. float subWidth = GetTabbedWidth(subString, 0);
  491. var utf8enumerator = lineText.DecodedChars(char8Count);
  492. if (utf8enumerator.MoveNext())
  493. {
  494. char32 c = utf8enumerator.Current;
  495. float checkCharWidth = 0;
  496. if (c == '\t')
  497. checkCharWidth = mTabSize * 0.5f;
  498. else
  499. {
  500. checkCharWidth = mFont.GetWidth(c) * 0.5f;
  501. }
  502. if (x >= subWidth + mTextInsets.mLeft + checkCharWidth)
  503. char8Idx = (int32)utf8enumerator.NextIndex;
  504. }
  505. }
  506. else
  507. {
  508. overflowX = (x - mTextInsets.mLeft) - (GetTabbedWidth(lineText, 0) + 0.001f);
  509. return overflowX <= 0;
  510. }
  511. overflowX = 0;
  512. return true;
  513. }
  514. public override bool GetLineAndColumnAtCoord(float x, float y, out int line, out int column)
  515. {
  516. line = (int32)((y - mTextInsets.mTop) / mFont.GetLineSpacing() + 0.001f);
  517. if (line >= GetLineCount())
  518. line = GetLineCount() - 1;
  519. line = Math.Max(0, line);
  520. column = Math.Max(0, (int32)((x - mTextInsets.mLeft + 1) / mCharWidth + 0.6f));
  521. return mCharWidth != -1;
  522. }
  523. void RecalcSize(int32 startLineNum, int32 endLineNum, bool forceAccurate = false)
  524. {
  525. scope AutoBeefPerf("DEWC.RecalcSize");
  526. String line = scope String();
  527. for (int32 lineIdx = startLineNum; lineIdx < endLineNum; lineIdx++)
  528. {
  529. line.Clear();
  530. GetLineText(lineIdx, line);
  531. mRecalcSizeCurMaxWidth = Math.Max(mRecalcSizeCurMaxWidth, GetTabbedWidth(line, 0, forceAccurate) + mHorzJumpSize);
  532. Debug.Assert(!mRecalcSizeCurMaxWidth.IsNaN);
  533. }
  534. }
  535. public override void CursorToLineEnd()
  536. {
  537. //int32 line;
  538. //int32 lineChar;
  539. //GetCursorLineChar(out line, out lineChar);
  540. /*RecalcSize(line, line + 1, true);
  541. if (mRecalcSizeCurMaxWidth > mWidth)
  542. {
  543. mRecalcSizeLineNum = -1;
  544. }*/
  545. mRecalcSizeLineNum = -1;
  546. RecalcSize(true);
  547. base.CursorToLineEnd();
  548. }
  549. public void RecalcSize(bool forceAccurate = false)
  550. {
  551. mMaximalScrollAddedHeight = 0;
  552. if (mRecalcSizeLineNum == -1)
  553. {
  554. mRecalcSizeCurMaxWidth = 0;
  555. mHasQueuedRecalcSize = false;
  556. }
  557. else // We need to recalc again after our current pass
  558. mHasQueuedRecalcSize = true;
  559. if (!mIsReadOnly)
  560. {
  561. float cursorX;
  562. float cursorY;
  563. GetTextCoordAtCursor(out cursorX, out cursorY);
  564. mRecalcSizeCurMaxWidth = Math.Max(mRecalcSizeCurMaxWidth, cursorX + mHorzJumpSize);
  565. }
  566. if (mUpdateCnt == 0)
  567. {
  568. RecalcSize(0, GetLineCount());
  569. mWidth = mRecalcSizeCurMaxWidth + mTextInsets.mLeft + mTextInsets.mRight;
  570. Debug.Assert(!mWidth.IsNaN);
  571. }
  572. else if (mRecalcSizeLineNum == -1)
  573. {
  574. mRecalcSizeLineNum = 0;
  575. // The actual recalculation will take 16 ticks so just make sure we have enough width for
  576. // the current line for now
  577. var lineAndCol = CursorLineAndColumn;
  578. RecalcSize(lineAndCol.mLine, lineAndCol.mLine + 1, forceAccurate);
  579. mWidth = Math.Max(mWidth, mRecalcSizeCurMaxWidth + mTextInsets.mLeft + mTextInsets.mRight);
  580. Debug.Assert(!mWidth.IsNaN);
  581. }
  582. mHeight = GetLineCount() * mFont.GetLineSpacing() + mTextInsets.mTop + mTextInsets.mBottom;
  583. UpdateMaximalScroll();
  584. base.RecalcSize();
  585. }
  586. public override void RecalcSize()
  587. {
  588. RecalcSize(false);
  589. }
  590. public override void ContentChanged()
  591. {
  592. base.ContentChanged();
  593. mRecalcSizeLineNum = -1;
  594. }
  595. public override void TextAppended(String str)
  596. {
  597. if ((mData.mLineStarts != null) && (mIsReadOnly))
  598. {
  599. int32 recalcSizeLineNum = Math.Max((int32)mData.mLineStarts.Count - 2, 0);
  600. if ((mRecalcSizeLineNum == -1) || (recalcSizeLineNum < mRecalcSizeLineNum))
  601. mRecalcSizeLineNum = recalcSizeLineNum;
  602. }
  603. base.TextAppended(str);
  604. }
  605. void UpdateMaximalScroll()
  606. {
  607. if (mAllowMaximalScroll)
  608. {
  609. let prevHeight = mHeight;
  610. mHeight -= mMaximalScrollAddedHeight;
  611. mMaximalScrollAddedHeight = mEditWidget.mScrollContentContainer.mHeight - mFont.GetLineSpacing();
  612. mHeight += mMaximalScrollAddedHeight;
  613. if (mHeight != prevHeight)
  614. mEditWidget.UpdateScrollbars();
  615. }
  616. }
  617. public override void Resize(float x, float y, float width, float height)
  618. {
  619. base.Resize(x, y, width, height);
  620. UpdateMaximalScroll();
  621. }
  622. public override float GetLineHeight(int line)
  623. {
  624. return mFont.GetLineSpacing();
  625. }
  626. public override float GetPageScrollTextHeight()
  627. {
  628. float numLinesVisible = mEditWidget.mScrollContentContainer.mHeight / mFont.GetLineSpacing();
  629. if (numLinesVisible - (int32)numLinesVisible < 0.90f)
  630. numLinesVisible = (int32) numLinesVisible;
  631. float val = numLinesVisible * mFont.GetLineSpacing();
  632. if (val <= 0)
  633. return base.GetPageScrollTextHeight();
  634. return val;
  635. }
  636. public void CheckRecordScrollTop()
  637. {
  638. if (mWantsCheckScrollPosition)
  639. {
  640. if (mTopCharId != -1)
  641. {
  642. int textIdx = mData.mTextIdData.GetPrepared().GetIndexFromId(mTopCharId);
  643. if (textIdx != -1)
  644. {
  645. int line;
  646. int lineChar;
  647. GetLineCharAtIdx(textIdx, out line, out lineChar);
  648. var vertPos = mEditWidget.mVertPos.mDest;
  649. var offset = vertPos % mFont.GetLineSpacing();
  650. mEditWidget.mVertScrollbar.ScrollTo(line * mFont.GetLineSpacing() + offset);
  651. }
  652. else
  653. {
  654. mTopCharId = -1;
  655. }
  656. }
  657. mWantsCheckScrollPosition = false;
  658. }
  659. if (mEditWidget.mHasFocus)
  660. {
  661. mTopCharId = -1;
  662. }
  663. else
  664. {
  665. var vertPos = mEditWidget.mVertPos.mDest;
  666. if ((mTopCharId == -1) || (mTopCharIdVertPos != vertPos))
  667. {
  668. float lineNum = (float)(vertPos / mFont.GetLineSpacing());
  669. int lineStart;
  670. int lineEnd;
  671. GetLinePosition((int32)lineNum, out lineStart, out lineEnd);
  672. int idAtStart = mData.mTextIdData.GetIdAtIndex((int32)lineStart);
  673. if (idAtStart == -1)
  674. idAtStart = 0;
  675. mTopCharId = (int32)idAtStart;
  676. mTopCharIdVertPos = vertPos;
  677. }
  678. }
  679. }
  680. public override void Update()
  681. {
  682. base.Update();
  683. if ((mRecalcSizeLineNum != -1) && (BFApp.sApp.mIsUpdateBatchStart))
  684. {
  685. int32 lineCount = GetLineCount();
  686. int32 toLine = Math.Min(lineCount, mRecalcSizeLineNum + Math.Max(1, lineCount / 16) + 80);
  687. RecalcSize(mRecalcSizeLineNum, toLine);
  688. if (toLine == lineCount)
  689. {
  690. mRecalcSizeLineNum = -1;
  691. mWidth = mRecalcSizeCurMaxWidth + mTextInsets.mLeft + mTextInsets.mRight;
  692. base.RecalcSize();
  693. }
  694. else
  695. mRecalcSizeLineNum = toLine;
  696. }
  697. if ((mRecalcSizeLineNum == -1) && (mHasQueuedRecalcSize))
  698. RecalcSize();
  699. CheckRecordScrollTop();
  700. }
  701. }
  702. public class DarkEditWidget : EditWidget
  703. {
  704. public bool mDrawBox = true;
  705. public this(DarkEditWidgetContent content = null)
  706. {
  707. mEditWidgetContent = content;
  708. if (mEditWidgetContent == null)
  709. mEditWidgetContent = new DarkEditWidgetContent();
  710. mEditWidgetContent.mEditWidget = this;
  711. mScrollContent = mEditWidgetContent;
  712. mScrollContentContainer.AddWidget(mEditWidgetContent);
  713. SetupInsets();
  714. //mScrollbarInsets.Set(18, 1, 0, 0);
  715. mHorzPos.mSpeed = 0.2f;
  716. mVertPos.mSpeed = 0.2f;
  717. mScrollbarBaseContentSizeOffset = GS!(3);
  718. }
  719. protected virtual void SetupInsets()
  720. {
  721. mScrollContentInsets.Set(GS!(3), GS!(3), GS!(3), GS!(3));
  722. }
  723. protected override void HandleWindowMouseDown(Beefy.events.MouseEvent event)
  724. {
  725. base.HandleWindowMouseDown(event);
  726. // If we got closed as part of this click, don't propagate the click through
  727. if (mParent == null)
  728. {
  729. event.mHandled = true;
  730. }
  731. }
  732. public override void RehupScale(float oldScale, float newScale)
  733. {
  734. base.RehupScale(oldScale, newScale);
  735. SetupInsets();
  736. }
  737. public override void DefaultDesignInit()
  738. {
  739. base.DefaultDesignInit();
  740. mWidth = GS!(80);
  741. mHeight = GS!(20);
  742. SetText("Edit Text");
  743. }
  744. public override void InitScrollbars(bool wantHorz, bool wantVert)
  745. {
  746. SetupInsets();
  747. base.InitScrollbars(wantHorz, wantVert);
  748. float scrollIncrement = ((DarkEditWidgetContent) mEditWidgetContent).mFont.GetLineSpacing() * GS!(3);
  749. if (mHorzScrollbar != null)
  750. mHorzScrollbar.mScrollIncrement = scrollIncrement;
  751. if (mVertScrollbar != null)
  752. mVertScrollbar.mScrollIncrement = scrollIncrement;
  753. }
  754. public override void Draw(Graphics g)
  755. {
  756. base.Draw(g);
  757. if (mDrawBox)
  758. {
  759. g.DrawBox(DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.EditBox), 0, 0, mWidth, mHeight);
  760. if (mHasFocus)
  761. {
  762. using (g.PushColor(DarkTheme.COLOR_SELECTED_OUTLINE))
  763. g.DrawBox(DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.Outline), 0, 0, mWidth, mHeight);
  764. }
  765. }
  766. /*using (g.PushColor(0x40FF0000))
  767. g.FillRect(0, 0, mWidth, mHeight);*/
  768. }
  769. public override void LostFocus()
  770. {
  771. base.LostFocus();
  772. var darkEditWidgetContent = (DarkEditWidgetContent)mEditWidgetContent;
  773. darkEditWidgetContent.CheckRecordScrollTop();
  774. if (darkEditWidgetContent.mScrollToStartOnLostFocus)
  775. HorzScrollTo(0);
  776. }
  777. }
  778. }