Input.lua 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424
  1. --[[
  2. MIT License
  3. Copyright (c) 2019 Mitchell Davis <coding.jackalope@gmail.com>
  4. Permission is hereby granted, free of charge, to any person obtaining a copy
  5. of this software and associated documentation files (the "Software"), to deal
  6. in the Software without restriction, including without limitation the rights
  7. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the Software is
  9. furnished to do so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in all
  11. copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  18. SOFTWARE.
  19. --]]
  20. local Cursor = require(SLAB_PATH .. '.Internal.Core.Cursor')
  21. local DrawCommands = require(SLAB_PATH .. '.Internal.Core.DrawCommands')
  22. local Keyboard = require(SLAB_PATH .. '.Internal.Input.Keyboard')
  23. local LayoutManager = require(SLAB_PATH .. '.Internal.UI.LayoutManager')
  24. local Mouse = require(SLAB_PATH .. '.Internal.Input.Mouse')
  25. local Region = require(SLAB_PATH .. '.Internal.UI.Region')
  26. local Stats = require(SLAB_PATH .. '.Internal.Core.Stats')
  27. local Style = require(SLAB_PATH .. '.Style')
  28. local Text = require(SLAB_PATH .. '.Internal.UI.Text')
  29. local Tooltip = require(SLAB_PATH .. '.Internal.UI.Tooltip')
  30. local UTF8 = require('utf8')
  31. local Utility = require(SLAB_PATH .. '.Internal.Core.Utility')
  32. local Window = require(SLAB_PATH .. '.Internal.UI.Window')
  33. local Input = {}
  34. local Instances = {}
  35. local Focused = nil
  36. local LastFocused = nil
  37. local TextCursorPos = 0
  38. local TextCursorPosLine = 0
  39. local TextCursorPosLineMax = 0
  40. local TextCursorPosLineNumber = 1
  41. local TextCursorAnchor = -1
  42. local TextCursorAlpha = 0.0
  43. local FadeIn = true
  44. local DragSelect = false
  45. local FocusToNext = false
  46. local LastText = ""
  47. local Pad = Region.GetScrollPad() + Region.GetScrollBarSize()
  48. local PendingFocus = nil
  49. local PendingCursorPos = -1
  50. local PendingCursorColumn = -1
  51. local PendingCursorLine = -1
  52. local MIN_WIDTH = 150.0
  53. local function SanitizeText(Data)
  54. local Result = false
  55. if Data ~= nil then
  56. local Count = 0
  57. Data, Count = string.gsub(Data, "\r", "")
  58. Result = Count > 0
  59. end
  60. return Data, Result
  61. end
  62. local function GetDisplayCharacter(Data, Pos)
  63. local Result = ''
  64. if Data ~= nil and Pos > 0 then
  65. local Offset = UTF8.offset(Data, -1, Pos + 1)
  66. Result = string.sub(Data, Offset, Pos)
  67. if Result == nil then
  68. Result = 'nil'
  69. end
  70. end
  71. if Result == '\n' then
  72. Result = "\\n"
  73. end
  74. return Result
  75. end
  76. local function GetCharacter(Data, Index, Forward)
  77. local Result = ""
  78. if Forward then
  79. local Sub = string.sub(Data, Index + 1)
  80. Result = string.match(Sub, "[%z\1-\127\194-\244%s\n][\128-\191]*")
  81. else
  82. local Sub = string.sub(Data, 1, Index)
  83. Result = string.match(Sub, "[%z\1-\127\194-\244%s\n][\128-\191]*$")
  84. end
  85. return Result
  86. end
  87. local function UpdateMultiLinePosition(Instance)
  88. if Instance ~= nil then
  89. if Instance.Lines ~= nil then
  90. local Count = 0
  91. local Start = 0
  92. local Found = false
  93. for I, V in ipairs(Instance.Lines) do
  94. local Length = string.len(V)
  95. Count = Count + Length
  96. if TextCursorPos < Count then
  97. TextCursorPosLine = TextCursorPos - Start
  98. TextCursorPosLineNumber = I
  99. Found = true
  100. break
  101. end
  102. Start = Start + Length
  103. end
  104. if not Found then
  105. TextCursorPosLine = string.len(Instance.Lines[#Instance.Lines])
  106. TextCursorPosLineNumber = #Instance.Lines
  107. end
  108. else
  109. TextCursorPosLine = TextCursorPos
  110. TextCursorPosLineNumber = 1
  111. end
  112. TextCursorPosLineMax = TextCursorPosLine
  113. end
  114. end
  115. local function ValidateTextCursorPos(Instance)
  116. if Instance ~= nil then
  117. local OldPos = TextCursorPos
  118. local Byte = string.byte(string.sub(Instance.Text, TextCursorPos, TextCursorPos))
  119. -- This is a continuation byte. Check next byte to see if it is an ASCII character or
  120. -- the beginning of a UTF8 character.
  121. if Byte ~= nil and Byte > 127 then
  122. local NextByte = string.byte(string.sub(Instance.Text, TextCursorPos + 1, TextCursorPos + 1))
  123. if NextByte ~= nil and NextByte > 127 and NextByte < 191 then
  124. while Byte > 127 and Byte < 191 do
  125. TextCursorPos = TextCursorPos - 1
  126. Byte = string.byte(string.sub(Instance.Text, TextCursorPos, TextCursorPos))
  127. end
  128. if TextCursorPos < OldPos or Byte >= 191 then
  129. TextCursorPos = TextCursorPos - 1
  130. UpdateMultiLinePosition(Instance)
  131. end
  132. end
  133. end
  134. end
  135. end
  136. local function MoveToHome(Instance)
  137. if Instance ~= nil then
  138. if Instance.Lines ~= nil and TextCursorPosLineNumber > 1 then
  139. TextCursorPosLine = 0
  140. local Count = 0
  141. local Start = 0
  142. for I, V in ipairs(Instance.Lines) do
  143. Count = Count + string.len(V)
  144. if I == TextCursorPosLineNumber then
  145. TextCursorPos = Start
  146. break
  147. end
  148. Start = Start + string.len(V)
  149. end
  150. else
  151. TextCursorPos = 0
  152. end
  153. UpdateMultiLinePosition(Instance)
  154. end
  155. end
  156. local function MoveToEnd(Instance)
  157. if Instance ~= nil then
  158. if Instance.Lines ~= nil then
  159. local Count = 0
  160. for I, V in ipairs(Instance.Lines) do
  161. Count = Count + string.len(V)
  162. if I == TextCursorPosLineNumber then
  163. TextCursorPos = Count - 1
  164. if I == #Instance.Lines then
  165. TextCursorPos = Count
  166. end
  167. break
  168. end
  169. end
  170. else
  171. TextCursorPos = #Instance.Text
  172. end
  173. UpdateMultiLinePosition(Instance)
  174. end
  175. end
  176. local function ValidateNumber(Instance)
  177. local Result = false
  178. if Instance ~= nil and Instance.NumbersOnly and Instance.Text ~= "" then
  179. if string.sub(Instance.Text, #Instance.Text, #Instance.Text) == "." then
  180. return
  181. end
  182. local Value = tonumber(Instance.Text)
  183. if Value == nil then
  184. Value = 0.0
  185. end
  186. local OldValue = Value
  187. if Instance.MinNumber ~= nil then
  188. Value = math.max(Value, Instance.MinNumber)
  189. end
  190. if Instance.MaxNumber ~= nil then
  191. Value = math.min(Value, Instance.MaxNumber)
  192. end
  193. Result = OldValue ~= Value
  194. Instance.Text = tostring(Value)
  195. end
  196. return Result
  197. end
  198. local function GetAlignmentOffset(Instance)
  199. local Offset = 6.0
  200. if Instance ~= nil then
  201. if Instance.Align == 'center' then
  202. local TextW = Text.GetWidth(Instance.Text)
  203. Offset = (Instance.W * 0.5) - (TextW * 0.5)
  204. end
  205. end
  206. return Offset
  207. end
  208. local function GetSelection(Instance)
  209. if Instance ~= nil and TextCursorAnchor >= 0 and TextCursorAnchor ~= TextCursorPos then
  210. local Min = math.min(TextCursorAnchor, TextCursorPos) + 1
  211. local Max = math.max(TextCursorAnchor, TextCursorPos)
  212. return string.sub(Instance.Text, Min, Max)
  213. end
  214. return ""
  215. end
  216. local function MoveCursorVertical(Instance, MoveDown)
  217. if Instance ~= nil and Instance.Lines ~= nil then
  218. local OldLineNumber = TextCursorPosLineNumber
  219. if MoveDown then
  220. TextCursorPosLineNumber = math.min(TextCursorPosLineNumber + 1, #Instance.Lines)
  221. else
  222. TextCursorPosLineNumber = math.max(1, TextCursorPosLineNumber - 1)
  223. end
  224. local Line = Instance.Lines[TextCursorPosLineNumber]
  225. if OldLineNumber == TextCursorPosLineNumber then
  226. TextCursorPosLine = MoveDown and string.len(Line) or 0
  227. else
  228. if TextCursorPosLineNumber == #Instance.Lines and TextCursorPosLine >= string.len(Line) then
  229. TextCursorPosLine = string.len(Line)
  230. else
  231. TextCursorPosLine = math.min(string.len(Line), TextCursorPosLineMax + 1)
  232. local Ch = GetCharacter(Line, TextCursorPosLine)
  233. if Ch ~= nil then
  234. TextCursorPosLine = TextCursorPosLine - string.len(Ch)
  235. end
  236. end
  237. end
  238. local Start = 0
  239. for I, V in ipairs(Instance.Lines) do
  240. if I == TextCursorPosLineNumber then
  241. TextCursorPos = Start + TextCursorPosLine
  242. break
  243. end
  244. Start = Start + string.len(V)
  245. end
  246. end
  247. end
  248. local function IsValidDigit(Instance, Ch)
  249. if Instance ~= nil then
  250. if Instance.NumbersOnly then
  251. if string.match(Ch, "%d") ~= nil then
  252. return true
  253. end
  254. if Ch == "-" then
  255. if TextCursorAnchor == 0 or TextCursorPos == 0 or #Instance.Text == 0 then
  256. return true
  257. end
  258. end
  259. if Ch == "." then
  260. local Selected = GetSelection(Instance)
  261. if Selected ~= nil and string.find(Selected, ".", 1, true) ~= nil then
  262. return true
  263. end
  264. if string.find(Instance.Text, ".", 1, true) == nil then
  265. return true
  266. end
  267. end
  268. else
  269. return true
  270. end
  271. end
  272. return false
  273. end
  274. local function IsCommandKeyDown()
  275. local LKey, RKey = 'lctrl', 'rctrl'
  276. if Utility.IsOSX() then
  277. LKey, RKey = 'lgui', 'rgui'
  278. end
  279. return Keyboard.IsDown(LKey) or Keyboard.IsDown(RKey)
  280. end
  281. local function IsHomePressed()
  282. local Result = false
  283. if Utility.IsOSX() then
  284. Result = IsCommandKeyDown() and Keyboard.IsPressed('left')
  285. else
  286. Result = Keyboard.IsPressed('home')
  287. end
  288. return Result
  289. end
  290. local function IsEndPressed()
  291. local Result = false
  292. if Utility.IsOSX() then
  293. Result = IsCommandKeyDown() and Keyboard.IsPressed('right')
  294. else
  295. Result = Keyboard.IsPressed('end')
  296. end
  297. return Result
  298. end
  299. local function IsNextSpaceDown()
  300. local Result = false
  301. if Utility.IsOSX() then
  302. Result = Keyboard.IsDown('lalt') or Keyboard.IsDown('ralt')
  303. else
  304. Result = Keyboard.IsDown('lctrl') or Keyboard.IsDown('rctrl')
  305. end
  306. return Result
  307. end
  308. local function GetCursorXOffset(Instance)
  309. local Result = GetAlignmentOffset(Instance)
  310. if Instance ~= nil then
  311. if TextCursorPos > 0 then
  312. local Sub = string.sub(Instance.Text, 1, TextCursorPos)
  313. Result = Text.GetWidth(Sub) + GetAlignmentOffset(Instance)
  314. end
  315. end
  316. return Result
  317. end
  318. local function GetCursorPos(Instance)
  319. local X, Y = GetAlignmentOffset(Instance), 0.0
  320. if Instance ~= nil then
  321. local Data = Instance.Text
  322. if Instance.Lines ~= nil then
  323. Data = Instance.Lines[TextCursorPosLineNumber]
  324. Y = Text.GetHeight() * (TextCursorPosLineNumber - 1)
  325. end
  326. local CursorPos = math.min(TextCursorPosLine, string.len(Data))
  327. if CursorPos > 0 then
  328. local Sub = string.sub(Data, 0, CursorPos)
  329. X = X + Text.GetWidth(Sub)
  330. end
  331. end
  332. return X, Y
  333. end
  334. local function SelectWord(Instance)
  335. if Instance ~= nil then
  336. local Filter = "%s"
  337. if GetCharacter(Instance.Text, TextCursorPos) == " " then
  338. if GetCharacter(Instance.Text, TextCursorPos + 1) == " " then
  339. Filter = "%S"
  340. else
  341. TextCursorPos = TextCursorPos + 1
  342. end
  343. end
  344. TextCursorAnchor = 0
  345. local I = 0
  346. while I ~= nil and I + 1 < TextCursorPos do
  347. I = string.find(Instance.Text, Filter, I + 1)
  348. if I ~= nil and I < TextCursorPos then
  349. TextCursorAnchor = I
  350. else
  351. break
  352. end
  353. end
  354. I = string.find(Instance.Text, Filter, TextCursorPos + 1)
  355. if I ~= nil then
  356. TextCursorPos = I - 1
  357. else
  358. TextCursorPos = #Instance.Text
  359. end
  360. UpdateMultiLinePosition(Instance)
  361. end
  362. end
  363. local function GetNextCursorPos(Instance, Left)
  364. local Result = 0
  365. if Instance ~= nil then
  366. local NextSpace = IsNextSpaceDown()
  367. if NextSpace then
  368. if Left then
  369. Result = 0
  370. local I = 0
  371. while I ~= nil and I + 1 < TextCursorPos do
  372. I = string.find(Instance.Text, "%s", I + 1)
  373. if I ~= nil and I < TextCursorPos then
  374. Result = I
  375. else
  376. break
  377. end
  378. end
  379. else
  380. local I = string.find(Instance.Text, "%s", TextCursorPos + 1)
  381. if I ~= nil then
  382. Result = I
  383. else
  384. Result = #Instance.Text
  385. end
  386. end
  387. else
  388. if Left then
  389. local Ch = GetCharacter(Instance.Text, TextCursorPos)
  390. if Ch ~= nil then
  391. Result = TextCursorPos - string.len(Ch)
  392. end
  393. else
  394. local Ch = GetCharacter(Instance.Text, TextCursorPos, true)
  395. if Ch ~= nil then
  396. Result = TextCursorPos + string.len(Ch)
  397. else
  398. Result = TextCursorPos
  399. end
  400. end
  401. end
  402. Result = math.max(0, Result)
  403. Result = math.min(Result, string.len(Instance.Text))
  404. end
  405. return Result
  406. end
  407. local function GetCursorPosLine(Instance, Line, X)
  408. local Result = 0
  409. if Instance ~= nil and Line ~= "" then
  410. if Text.GetWidth(Line) < X then
  411. Result = string.len(Line)
  412. if string.find(Line, "\n") ~= nil then
  413. Result = string.len(Line) - 1
  414. end
  415. else
  416. X = X - GetAlignmentOffset(Instance)
  417. local PosX = X
  418. local Index = 0
  419. local Sub = ""
  420. while Index <= string.len(Line) do
  421. local Ch = GetCharacter(Line, Index, true)
  422. if Ch == nil then
  423. break
  424. end
  425. Index = Index + string.len(Ch)
  426. Sub = Sub .. Ch
  427. local PosX = Text.GetWidth(Sub)
  428. if PosX > X then
  429. local CharX = PosX - X
  430. local CharW = Text.GetWidth(Ch)
  431. if CharX < CharW * 0.65 then
  432. Result = Result + string.len(Ch)
  433. end
  434. break
  435. end
  436. Result = Index
  437. end
  438. end
  439. end
  440. return Result
  441. end
  442. local function GetTextCursorPos(Instance, X, Y)
  443. local Result = 0
  444. if Instance ~= nil then
  445. local Line = Instance.Text
  446. local Start = 0
  447. if Instance.Lines ~= nil and #Instance.Lines > 0 then
  448. local H = Text.GetHeight()
  449. local LineNumber = 1
  450. local Found = false
  451. for I, V in ipairs(Instance.Lines) do
  452. if Y <= H then
  453. Line = V
  454. Found = true
  455. break
  456. end
  457. H = H + Text.GetHeight()
  458. Start = Start + #V
  459. end
  460. if not Found then
  461. Line = Instance.Lines[#Instance.Lines]
  462. end
  463. end
  464. Result = math.min(Start + GetCursorPosLine(Instance, Line, X), #Instance.Text)
  465. end
  466. return Result
  467. end
  468. local function MoveCursorPage(Instance, PageDown)
  469. if Instance ~= nil then
  470. local PageH = Instance.H - Text.GetHeight()
  471. local PageY = PageDown and PageH or 0.0
  472. local X, Y = GetCursorPos(Instance)
  473. local TX, TY = Region.InverseTransform(Instance.Id, 0.0, PageY)
  474. local NextY = 0.0
  475. if PageDown then
  476. NextY = TY + PageH
  477. else
  478. NextY = math.max(TY - PageH, 0.0)
  479. end
  480. TextCursorPos = GetTextCursorPos(Instance, 0.0, NextY)
  481. UpdateMultiLinePosition(Instance)
  482. end
  483. end
  484. local function UpdateTransform(Instance)
  485. if Instance ~= nil then
  486. local X, Y = GetCursorPos(Instance)
  487. local TX, TY = Region.InverseTransform(Instance.Id, 0.0, 0.0)
  488. local W = TX + Instance.W - Region.GetScrollPad() - Region.GetScrollBarSize()
  489. local H = TY + Instance.H
  490. if Instance.H > Text.GetHeight() then
  491. H = H - Region.GetScrollPad() - Region.GetScrollBarSize()
  492. end
  493. local NewX = 0.0
  494. if TextCursorPosLine == 0 then
  495. NewX = TX
  496. elseif X > W then
  497. NewX = -(X - W)
  498. elseif X < TX then
  499. NewX = TX - X
  500. end
  501. local NewY = 0.0
  502. if TextCursorPosLineNumber == 1 then
  503. NewY = TY
  504. elseif Y > H then
  505. NewY = -(Y - H)
  506. elseif Y < TY then
  507. NewY = TY - Y
  508. end
  509. Region.Translate(Instance.Id, NewX, NewY)
  510. end
  511. end
  512. local function DeleteSelection(Instance)
  513. if Instance ~= nil and Instance.Text ~= "" and not Instance.ReadOnly then
  514. local Start = 0
  515. local Min = 0
  516. local Max = 0
  517. if TextCursorAnchor ~= -1 then
  518. Min = math.min(TextCursorAnchor, TextCursorPos)
  519. Max = math.max(TextCursorAnchor, TextCursorPos) + 1
  520. else
  521. if TextCursorPos == 0 then
  522. return false
  523. end
  524. local NewTextCursorPos = TextCursorPos
  525. local Ch = GetCharacter(Instance.Text, TextCursorPos)
  526. if Ch ~= nil then
  527. Min = TextCursorPos - string.len(Ch)
  528. NewTextCursorPos = Min
  529. end
  530. Ch = GetCharacter(Instance.Text, TextCursorPos, true)
  531. if Ch ~= nil then
  532. Max = TextCursorPos + 1
  533. else
  534. Max = string.len(Instance.Text) + 1
  535. end
  536. TextCursorPos = NewTextCursorPos
  537. end
  538. local Left = string.sub(Instance.Text, 1, Min)
  539. local Right = string.sub(Instance.Text, Max)
  540. Instance.Text = Left .. Right
  541. TextCursorPos = string.len(Left)
  542. if TextCursorAnchor ~= -1 then
  543. TextCursorPos = math.min(TextCursorAnchor, TextCursorPos)
  544. end
  545. TextCursorPos = math.max(0, TextCursorPos)
  546. TextCursorPos = math.min(TextCursorPos, string.len(Instance.Text))
  547. TextCursorAnchor = -1
  548. UpdateMultiLinePosition(Instance)
  549. end
  550. return true
  551. end
  552. local function DrawSelection(Instance, X, Y, W, H, Color)
  553. if Instance ~= nil and TextCursorAnchor >= 0 and TextCursorAnchor ~= TextCursorPos then
  554. local Min = math.min(TextCursorAnchor, TextCursorPos)
  555. local Max = math.max(TextCursorAnchor, TextCursorPos)
  556. H = Text.GetHeight()
  557. if Instance.Lines ~= nil then
  558. local Count = 0
  559. local Start = 0
  560. local OffsetMin = 0
  561. local OffsetMax = 0
  562. local OffsetY = 0
  563. for I, V in ipairs(Instance.Lines) do
  564. Count = Count + string.len(V)
  565. if Min < Count then
  566. if Min > Start then
  567. OffsetMin = math.max(Min - Start, 1)
  568. else
  569. OffsetMin = 0
  570. end
  571. if Max < Count then
  572. OffsetMax = math.max(Max - Start, 1)
  573. else
  574. OffsetMax = string.len(V)
  575. end
  576. local SubMin = string.sub(V, 1, OffsetMin)
  577. local SubMax = string.sub(V, 1, OffsetMax)
  578. local MinX = Text.GetWidth(SubMin) - 1.0 + GetAlignmentOffset(Instance)
  579. local MaxX = Text.GetWidth(SubMax) + 1.0 + GetAlignmentOffset(Instance)
  580. DrawCommands.Rectangle('fill', X + MinX, Y + OffsetY, MaxX - MinX, H, Color)
  581. end
  582. if Max <= Count then
  583. break
  584. end
  585. Start = Start + string.len(V)
  586. OffsetY = OffsetY + H
  587. end
  588. else
  589. local SubMin = string.sub(Instance.Text, 1, Min)
  590. local SubMax = string.sub(Instance.Text, 1, Max)
  591. local MinX = Text.GetWidth(SubMin) - 1.0 + GetAlignmentOffset(Instance)
  592. local MaxX = Text.GetWidth(SubMax) + 1.0 + GetAlignmentOffset(Instance)
  593. DrawCommands.Rectangle('fill', X + MinX, Y, MaxX - MinX, H, Color)
  594. end
  595. end
  596. end
  597. local function DrawCursor(Instance, X, Y, W, H)
  598. if Instance ~= nil then
  599. local CX, CY = GetCursorPos(Instance)
  600. local CX = X + CX
  601. local CY = Y + CY
  602. H = Text.GetHeight()
  603. DrawCommands.Line(CX, CY, CX, CY + H, 1.0, {0.0, 0.0, 0.0, TextCursorAlpha})
  604. end
  605. end
  606. local function IsHighlightTerminator(Ch)
  607. if Ch ~= nil then
  608. return string.match(Ch, "%w") == nil
  609. end
  610. return true
  611. end
  612. local function UpdateTextObject(Instance, Width, Align, Highlight, BaseColor)
  613. if Instance ~= nil and Instance.TextObject ~= nil then
  614. local ColoredText = {}
  615. if Highlight == nil then
  616. ColoredText = {BaseColor, Instance.Text}
  617. else
  618. --local StartTime = love.timer.getTime()
  619. local TX, TY = Region.InverseTransform(Instance.Id, 0, 0)
  620. local TextH = Text.GetHeight()
  621. local Top = TY - TextH * 2
  622. local Bottom = TY + Instance.H + TextH * 2
  623. local H = #Instance.Lines * TextH
  624. local TopLineNo = math.max(math.floor((Top / H) * #Instance.Lines), 1)
  625. local BottomLineNo = math.min(math.floor((Bottom / H) * #Instance.Lines), #Instance.Lines)
  626. local Index = 1
  627. local EndIndex = 1
  628. for I = 1, BottomLineNo, 1 do
  629. local Count = string.len(Instance.Lines[I])
  630. if I < TopLineNo then
  631. Index = Index + Count
  632. end
  633. EndIndex = EndIndex + Count
  634. end
  635. if Index > 1 then
  636. table.insert(ColoredText, BaseColor)
  637. table.insert(ColoredText, string.sub(Instance.Text, 1, Index - 1))
  638. end
  639. while Index < EndIndex do
  640. local MatchIndex = nil
  641. local Key = nil
  642. for K, V in pairs(Highlight) do
  643. local Found = nil
  644. local Anchor = Index
  645. repeat
  646. Found = string.find(Instance.Text, K, Anchor, true)
  647. if Found ~= nil then
  648. local FoundEnd = Found + string.len(K)
  649. local Prev = string.sub(Instance.Text, Found - 1, Found - 1)
  650. local Next = string.sub(Instance.Text, FoundEnd, FoundEnd)
  651. if Found == 1 then
  652. Prev = nil
  653. end
  654. if FoundEnd > string.len(Instance.Text) then
  655. Next = nil
  656. end
  657. if not (IsHighlightTerminator(Prev) and IsHighlightTerminator(Next)) then
  658. Anchor = Found + 1
  659. Found = nil
  660. end
  661. else
  662. break
  663. end
  664. until Found ~= nil
  665. if Found ~= nil then
  666. if MatchIndex == nil then
  667. MatchIndex = Found
  668. Key = K
  669. elseif Found < MatchIndex then
  670. MatchIndex = Found
  671. Key = K
  672. end
  673. end
  674. end
  675. if Key ~= nil then
  676. table.insert(ColoredText, BaseColor)
  677. table.insert(ColoredText, string.sub(Instance.Text, Index, MatchIndex - 1))
  678. table.insert(ColoredText, Highlight[Key])
  679. table.insert(ColoredText, Key)
  680. Index = MatchIndex + string.len(Key)
  681. else
  682. table.insert(ColoredText, BaseColor)
  683. table.insert(ColoredText, string.sub(Instance.Text, Index, EndIndex))
  684. Index = EndIndex
  685. break
  686. end
  687. end
  688. if Index < string.len(Instance.Text) then
  689. table.insert(ColoredText, BaseColor)
  690. table.insert(ColoredText, string.sub(Instance.Text, Index))
  691. end
  692. --print(string.format("UpdateTextObject Time: %f", (love.timer.getTime() - StartTime)))
  693. end
  694. if #ColoredText == 0 then
  695. ColoredText = {BaseColor, Instance.Text}
  696. end
  697. Instance.TextObject:setf(ColoredText, Width, Align)
  698. end
  699. end
  700. local function GetInstance(Id)
  701. for I, V in ipairs(Instances) do
  702. if V.Id == Id then
  703. return V
  704. end
  705. end
  706. local Instance = {}
  707. Instance.Id = Id
  708. Instance.Text = ""
  709. Instance.TextChanged = false
  710. Instance.NumbersOnly = true
  711. Instance.ReadOnly = false
  712. Instance.Align = 'left'
  713. Instance.MinNumber = nil
  714. Instance.MaxNumber = nil
  715. Instance.Lines = nil
  716. Instance.TextObject = nil
  717. Instance.Highlight = nil
  718. Instance.ShouldUpdateTextObject = false
  719. table.insert(Instances, Instance)
  720. return Instance
  721. end
  722. function Input.Begin(Id, Options)
  723. assert(Id ~= nil, "Please pass a valid Id into Slab.Input.")
  724. local StatHandle = Stats.Begin('Input', 'Slab')
  725. Options = Options == nil and {} or Options
  726. Options.Tooltip = Options.Tooltip == nil and "" or Options.Tooltip
  727. Options.ReturnOnText = Options.ReturnOnText == nil and true or Options.ReturnOnText
  728. Options.Text = Options.Text == nil and nil or Options.Text
  729. Options.TextColor = Options.TextColor == nil and nil or Options.TextColor
  730. Options.BgColor = Options.BgColor == nil and Style.InputBgColor or Options.BgColor
  731. Options.SelectColor = Options.SelectColor == nil and Style.InputSelectColor or Options.SelectColor
  732. Options.SelectOnFocus = Options.SelectOnFocus == nil and true or Options.SelectOnFocus
  733. Options.W = Options.W == nil and nil or Options.W
  734. Options.H = Options.H == nil and nil or Options.H
  735. Options.ReadOnly = Options.ReadOnly == nil and false or Options.ReadOnly
  736. Options.Align = Options.Align == nil and nil or Options.Align
  737. Options.Rounding = Options.Rounding == nil and Style.InputBgRounding or Options.Rounding
  738. Options.MinNumber = Options.MinNumber == nil and nil or Options.MinNumber
  739. Options.MaxNumber = Options.MaxNumber == nil and nil or Options.MaxNumber
  740. Options.MultiLine = Options.MultiLine == nil and false or Options.MultiLine
  741. Options.MultiLineW = Options.MultiLineW == nil and math.huge or Options.MultiLineW
  742. Options.Highlight = Options.Highlight == nil and nil or Options.Highlight
  743. if type(Options.MinNumber) ~= "number" then
  744. Options.MinNumber = nil
  745. end
  746. if type(Options.MaxNumber) ~= "number" then
  747. Options.MaxNumber = nil
  748. end
  749. if Options.MultiLine then
  750. Options.TextColor = Style.MultilineTextColor
  751. end
  752. local Instance = GetInstance(Window.GetId() .. "." .. Id)
  753. Instance.NumbersOnly = Options.NumbersOnly
  754. Instance.ReadOnly = Options.ReadOnly
  755. Instance.Align = Options.Align
  756. Instance.MinNumber = Options.MinNumber
  757. Instance.MaxNumber = Options.MaxNumber
  758. Instance.MultiLine = Options.MultiLine
  759. if Instance.MultiLineW ~= Options.MultiLineW then
  760. Instance.Lines = nil
  761. end
  762. Instance.MultiLineW = Options.MultiLineW
  763. local WinItemId = Window.GetItemId(Id)
  764. if Instance.Align == nil then
  765. Instance.Align = Instance == Focused and 'left' or 'center'
  766. if Instance.ReadOnly then
  767. Instance.Align = 'center'
  768. end
  769. if Options.MultiLine then
  770. Instance.Align = 'left'
  771. end
  772. end
  773. if Focused ~= Instance then
  774. if Options.MultiLine and #Options.Text ~= #Instance.Text then
  775. Instance.Lines = nil
  776. end
  777. Instance.Text = Options.Text == nil and Instance.Text or Options.Text
  778. end
  779. if Instance.MinNumber ~= nil and Instance.MaxNumber ~= nil then
  780. assert(Instance.MinNumber < Instance.MaxNumber,
  781. "Invalid MinNumber and MaxNumber passed to Input control '" .. Instance.Id .. "'. MinNumber: " .. Instance.MinNumber .. " MaxNumber: " .. Instance.MaxNumber)
  782. end
  783. local H = Options.H == nil and Text.GetHeight() or Options.H
  784. local W = Options.W == nil and MIN_WIDTH or Options.W
  785. local ContentW, ContentH = 0.0, 0.0
  786. local Result = false
  787. W, H = LayoutManager.ComputeSize(W, H)
  788. LayoutManager.AddControl(W, H)
  789. Instance.W = W
  790. Instance.H = H
  791. local X, Y = Cursor.GetPosition()
  792. if Options.MultiLine then
  793. Options.SelectOnFocus = false
  794. local WasSanitized = false
  795. Options.Text, WasSanitized = SanitizeText(Options.Text)
  796. if WasSanitized then
  797. Result = true
  798. LastText = Options.Text
  799. end
  800. ContentW, ContentH = Text.GetSizeWrap(Instance.Text, Options.MultiLineW)
  801. end
  802. local ShouldUpdateTextObject = Instance.ShouldUpdateTextObject
  803. Instance.ShouldUpdateTextObject = false
  804. if Instance.Lines == nil and Instance.Text ~= "" then
  805. if Options.MultiLine then
  806. if Instance.TextObject == nil then
  807. Instance.TextObject = love.graphics.newText(Style.Font)
  808. end
  809. Instance.Lines = Text.GetLines(Instance.Text, Options.MultiLineW)
  810. ContentH = #Instance.Lines * Text.GetHeight()
  811. ShouldUpdateTextObject = true
  812. end
  813. end
  814. if Options.Highlight ~= nil then
  815. if Instance.Highlight == nil or Utility.TableCount(Options.Highlight) ~= Utility.TableCount(Instance.Highlight) then
  816. Instance.Highlight = Utility.Copy(Options.Highlight)
  817. ShouldUpdateTextObject = true
  818. else
  819. for K, V in pairs(Options.Highlight) do
  820. local HighlightColor = Instance.Highlight[K]
  821. if HighlightColor ~= nil then
  822. if V[1] ~= HighlightColor[1] or V[2] ~= HighlightColor[2] or V[3] ~= HighlightColor[3] or V[4] ~= HighlightColor[4] then
  823. ShouldUpdateTextObject = true
  824. break
  825. end
  826. else
  827. Instance.Highlight = Utility.Copy(Options.Highlight)
  828. ShouldUpdateTextObject = true
  829. break
  830. end
  831. end
  832. end
  833. else
  834. if Instance.Highlight ~= nil then
  835. Instance.Highlight = nil
  836. ShouldUpdateTextObject = true
  837. end
  838. end
  839. if ShouldUpdateTextObject then
  840. UpdateTextObject(Instance, Options.MultiLineW, Instance.Align, Options.Highlight, Options.TextColor)
  841. end
  842. local IsObstructed = Window.IsObstructedAtMouse()
  843. local MouseX, MouseY = Window.GetMousePosition()
  844. local Hovered = not IsObstructed and X <= MouseX and MouseX <= X + W and Y <= MouseY and MouseY <= Y + H
  845. local HoveredScrollBar = Region.IsHoverScrollBar(Instance.Id) or Region.IsScrolling()
  846. if Hovered and not HoveredScrollBar then
  847. Mouse.SetCursor('ibeam')
  848. Tooltip.Begin(Options.Tooltip)
  849. Window.SetHotItem(WinItemId)
  850. end
  851. local CheckFocus = Mouse.IsClicked(1) and not HoveredScrollBar
  852. local FocusedThisFrame = false
  853. local ClearFocus = false
  854. if CheckFocus then
  855. if Hovered then
  856. FocusedThisFrame = Focused ~= Instance
  857. Focused = Instance
  858. elseif Instance == Focused then
  859. ClearFocus = true
  860. Focused = nil
  861. end
  862. end
  863. if FocusToNext and LastFocused == nil then
  864. FocusedThisFrame = true
  865. Focused = Instance
  866. CheckFocus = true
  867. FocusToNext = false
  868. TextCursorAnchor = -1
  869. TextCursorPos = 0
  870. TextCursorPosLine = 0
  871. TextCursorPosLineNumber = 1
  872. end
  873. if LastFocused == Instance then
  874. LastFocused = nil
  875. end
  876. if Instance == Focused then
  877. local Back = false
  878. local IgnoreBack = false
  879. local ShouldDelete = false
  880. local ShouldUpdateTransform = false
  881. local PreviousTextCursorPos = TextCursorPos
  882. if IsCommandKeyDown() then
  883. if Keyboard.IsPressed('x') or Keyboard.IsPressed('c') then
  884. local Selected = GetSelection(Instance)
  885. if Selected ~= "" then
  886. love.system.setClipboardText(Selected)
  887. ShouldDelete = Keyboard.IsPressed('x')
  888. end
  889. end
  890. if Keyboard.IsPressed('v') then
  891. local Text = love.system.getClipboardText()
  892. Input.Text(Text)
  893. TextCursorPos = math.min(TextCursorPos + #Text - 1, #Instance.Text)
  894. end
  895. end
  896. if Keyboard.IsPressed('tab') then
  897. if Options.MultiLine then
  898. Input.Text('\t')
  899. else
  900. LastFocused = Instance
  901. FocusToNext = true
  902. end
  903. end
  904. if Keyboard.IsPressed('backspace') then
  905. ShouldDelete = true
  906. IgnoreBack = TextCursorAnchor ~= -1
  907. end
  908. if Keyboard.IsPressed('delete') then
  909. if TextCursorAnchor == -1 then
  910. local Ch = GetCharacter(Instance.Text, TextCursorPos, true)
  911. if Ch ~= nil then
  912. TextCursorPos = TextCursorPos + string.len(Ch)
  913. ShouldDelete = true
  914. end
  915. else
  916. IgnoreBack = true
  917. ShouldDelete = true
  918. end
  919. end
  920. if ShouldDelete then
  921. if DeleteSelection(Instance) then
  922. Instance.TextChanged = true
  923. end
  924. end
  925. local ClearAnchor = false
  926. local IsShiftDown = Keyboard.IsDown('lshift') or Keyboard.IsDown('rshift')
  927. if Keyboard.IsPressed('lshift') or Keyboard.IsPressed('rshift') then
  928. if TextCursorAnchor == -1 then
  929. TextCursorAnchor = TextCursorPos
  930. end
  931. end
  932. local HomePressed, EndPressed = false, false
  933. if IsHomePressed() then
  934. MoveToHome(Instance)
  935. ShouldUpdateTransform = true
  936. HomePressed = true
  937. end
  938. if IsEndPressed() then
  939. MoveToEnd(Instance)
  940. ShouldUpdateTransform = true
  941. EndPressed = true
  942. end
  943. if not HomePressed and (Keyboard.IsPressed('left') or Back) then
  944. TextCursorPos = GetNextCursorPos(Instance, true)
  945. ShouldUpdateTransform = true
  946. UpdateMultiLinePosition(Instance)
  947. end
  948. if not EndPressed and Keyboard.IsPressed('right') then
  949. TextCursorPos = GetNextCursorPos(Instance, false)
  950. ShouldUpdateTransform = true
  951. UpdateMultiLinePosition(Instance)
  952. end
  953. if Keyboard.IsPressed('up') then
  954. MoveCursorVertical(Instance, false)
  955. ShouldUpdateTransform = true
  956. end
  957. if Keyboard.IsPressed('down') then
  958. MoveCursorVertical(Instance, true)
  959. ShouldUpdateTransform = true
  960. end
  961. if Keyboard.IsPressed('pageup') then
  962. MoveCursorPage(Instance, false)
  963. ShouldUpdateTransform = true
  964. end
  965. if Keyboard.IsPressed('pagedown') then
  966. MoveCursorPage(Instance, true)
  967. ShouldUpdateTransform = true
  968. end
  969. if CheckFocus or DragSelect then
  970. if FocusedThisFrame and Options.SelectOnFocus and Instance.Text ~= "" then
  971. TextCursorAnchor = 0
  972. TextCursorPos = #Instance.Text
  973. else
  974. local MouseInputX, MouseInputY = MouseX - X, MouseY - Y
  975. local CX, CY = Region.InverseTransform(Instance.Id, MouseInputX, MouseInputY)
  976. TextCursorPos = GetTextCursorPos(Instance, CX, CY)
  977. if Mouse.IsClicked(1) then
  978. TextCursorAnchor = TextCursorPos
  979. DragSelect = true
  980. end
  981. ShouldUpdateTransform = true
  982. IsShiftDown = true
  983. end
  984. UpdateMultiLinePosition(Instance)
  985. end
  986. if Mouse.IsReleased(1) then
  987. DragSelect = false
  988. if TextCursorAnchor == TextCursorPos then
  989. TextCursorAnchor = -1
  990. end
  991. end
  992. if Mouse.IsDoubleClicked(1) then
  993. local MouseInputX, MouseInputY = MouseX - X, MouseY - Y
  994. local CX, CY = Region.InverseTransform(Instance.Id, MouseInputX, MouseInputY)
  995. TextCursorPos = GetTextCursorPos(Instance, CX, CY)
  996. SelectWord(Instance)
  997. DragSelect = false
  998. end
  999. if Keyboard.IsPressed('return') then
  1000. Result = true
  1001. if Options.MultiLine then
  1002. Input.Text('\n')
  1003. else
  1004. ClearFocus = true
  1005. end
  1006. end
  1007. if Instance.TextChanged or Back then
  1008. if Options.ReturnOnText then
  1009. Result = true
  1010. end
  1011. if Options.MultiLine then
  1012. Instance.Lines = Text.GetLines(Instance.Text, Options.MultiLineW)
  1013. UpdateTextObject(Instance, Options.MultiLineW, Instance.Align, Options.Highlight, Options.TextColor)
  1014. end
  1015. UpdateMultiLinePosition(Instance)
  1016. Instance.TextChanged = false
  1017. PreviousTextCursorPos = -1
  1018. end
  1019. if ShouldUpdateTransform then
  1020. ClearAnchor = not IsShiftDown
  1021. UpdateTransform(Instance)
  1022. end
  1023. if ClearAnchor then
  1024. TextCursorAnchor = -1
  1025. end
  1026. else
  1027. local WasValidated = ValidateNumber(Instance)
  1028. if WasValidated then
  1029. Result = true
  1030. LastText = Instance.Text
  1031. end
  1032. end
  1033. if Region.IsScrolling(Instance.Id) then
  1034. local DeltaX, DeltaY = Mouse.GetDelta()
  1035. local WheelX, WheelY = Region.GetWheelDelta()
  1036. if DeltaY ~= 0.0 or WheelY ~= 0.0 then
  1037. Instance.ShouldUpdateTextObject = true
  1038. end
  1039. end
  1040. if (Instance == Focused and not Instance.ReadOnly) or Options.MultiLine then
  1041. Options.BgColor = Style.InputEditBgColor
  1042. end
  1043. local TX, TY = Window.TransformPoint(X, Y)
  1044. Region.Begin(Instance.Id, {
  1045. X = X,
  1046. Y = Y,
  1047. W = W,
  1048. H = H,
  1049. ContentW = ContentW + Pad,
  1050. ContentH = ContentH + Pad,
  1051. BgColor = Options.BgColor,
  1052. SX = TX,
  1053. SY = TY,
  1054. MouseX = MouseX,
  1055. MouseY = MouseY,
  1056. Intersect = true,
  1057. IgnoreScroll = not Options.MultiLine,
  1058. Rounding = Options.Rounding,
  1059. IsObstructed = IsObstructed,
  1060. AutoSizeContent = false
  1061. })
  1062. if Instance == Focused then
  1063. DrawSelection(Instance, X, Y, W, H, Options.SelectColor)
  1064. DrawCursor(Instance, X, Y, W, H)
  1065. end
  1066. if Instance.Text ~= "" then
  1067. Cursor.SetPosition(X + GetAlignmentOffset(Instance), Y)
  1068. LayoutManager.Begin('Ignore', {Ignore = true})
  1069. if Instance.TextObject ~= nil then
  1070. Text.BeginObject(Instance.TextObject)
  1071. else
  1072. Text.Begin(Instance.Text, {AddItem = false, Color = Options.TextColor})
  1073. end
  1074. LayoutManager.End()
  1075. end
  1076. Region.End()
  1077. Region.ApplyScissor()
  1078. Cursor.SetItemBounds(X, Y, W, H)
  1079. Cursor.SetPosition(X, Y)
  1080. Cursor.AdvanceX(W)
  1081. Cursor.AdvanceY(H)
  1082. Window.AddItem(X, Y, W, H, WinItemId)
  1083. if ClearFocus then
  1084. ValidateNumber(Instance)
  1085. LastText = Instance.Text
  1086. Focused = nil
  1087. if not Options.MultiLine then
  1088. Region.ResetTransform(Instance.Id)
  1089. end
  1090. end
  1091. Stats.End(StatHandle)
  1092. return Result
  1093. end
  1094. function Input.Text(Ch)
  1095. if Focused ~= nil and not Focused.ReadOnly then
  1096. if not IsValidDigit(Focused, Ch) then
  1097. return
  1098. end
  1099. if TextCursorAnchor ~= -1 then
  1100. DeleteSelection(Focused)
  1101. end
  1102. if TextCursorPos == 0 then
  1103. Focused.Text = Ch .. Focused.Text
  1104. else
  1105. local Temp = Focused.Text
  1106. local Left = string.sub(Temp, 0, TextCursorPos)
  1107. local Right = string.sub(Temp, TextCursorPos + 1)
  1108. Focused.Text = Left .. Ch .. Right
  1109. end
  1110. TextCursorPos = math.min(TextCursorPos + string.len(Ch), string.len(Focused.Text))
  1111. TextCursorAnchor = -1
  1112. UpdateTransform(Focused)
  1113. Focused.TextChanged = true
  1114. end
  1115. end
  1116. function Input.Update(dt)
  1117. local Delta = dt * 2.0
  1118. if FadeIn then
  1119. TextCursorAlpha = math.min(TextCursorAlpha + Delta, 1.0)
  1120. FadeIn = TextCursorAlpha < 1.0
  1121. else
  1122. TextCursorAlpha = math.max(TextCursorAlpha - Delta, 0.0)
  1123. FadeIn = TextCursorAlpha == 0.0
  1124. end
  1125. if PendingFocus ~= nil then
  1126. LastFocused = Focused
  1127. Focused = PendingFocus
  1128. PendingFocus = nil
  1129. end
  1130. if Focused ~= nil then
  1131. if PendingCursorPos >= 0 then
  1132. TextCursorPos = math.min(PendingCursorPos, #Focused.Text)
  1133. ValidateTextCursorPos(Focused)
  1134. UpdateMultiLinePosition(Focused)
  1135. PendingCursorPos = -1
  1136. end
  1137. local MultiLineChanged = false
  1138. if PendingCursorColumn >= 0 then
  1139. if Focused.Lines ~= nil then
  1140. TextCursorPosLine = PendingCursorColumn
  1141. MultiLineChanged = true
  1142. end
  1143. PendingCursorColumn = -1
  1144. end
  1145. if PendingCursorLine > 0 then
  1146. if Focused.Lines ~= nil then
  1147. TextCursorPosLineNumber = math.min(PendingCursorLine, #Focused.Lines)
  1148. MultiLineChanged = true
  1149. end
  1150. PendingCursorLine = 0
  1151. end
  1152. if MultiLineChanged then
  1153. local Line = Focused.Lines[TextCursorPosLineNumber]
  1154. TextCursorPosLine = math.min(TextCursorPosLine, string.len(Line))
  1155. local Start = 0
  1156. for I, V in ipairs(Focused.Lines) do
  1157. if I == TextCursorPosLineNumber then
  1158. TextCursorPos = Start + TextCursorPosLine
  1159. break
  1160. end
  1161. Start = Start + string.len(V)
  1162. end
  1163. ValidateTextCursorPos(Focused)
  1164. end
  1165. else
  1166. PendingCursorPos = -1
  1167. PendingCursorColumn = -1
  1168. PendingCursorLine = 0
  1169. end
  1170. end
  1171. function Input.GetText()
  1172. if Focused ~= nil then
  1173. if Focused.NumbersOnly and (Focused.Text == "" or Focused.Text == ".") then
  1174. return "0"
  1175. end
  1176. return Focused.Text
  1177. end
  1178. return LastText
  1179. end
  1180. function Input.GetCursorPos()
  1181. if Focused ~= nil then
  1182. return TextCursorPos, TextCursorPosLine, TextCursorPosLineNumber
  1183. end
  1184. return 0, 0, 0
  1185. end
  1186. function Input.IsFocused(Id)
  1187. local Instance = GetInstance(Window.GetId() .. '.' .. Id)
  1188. return Instance == Focused
  1189. end
  1190. function Input.SetFocused(Id)
  1191. local Instance = GetInstance(Window.GetId() .. '.' .. Id)
  1192. PendingFocus = Instance
  1193. end
  1194. function Input.SetCursorPos(Pos)
  1195. PendingCursorPos = math.max(Pos, 0)
  1196. end
  1197. function Input.SetCursorPosLine(Column, Line)
  1198. if Column ~= nil then
  1199. PendingCursorColumn = math.max(Column, 0)
  1200. end
  1201. if Line ~= nil then
  1202. PendingCursorLine = math.max(Line, 1)
  1203. end
  1204. end
  1205. function Input.GetDebugInfo()
  1206. local Info = {}
  1207. local X, Y = GetCursorPos(Focused)
  1208. if Focused ~= nil then
  1209. Region.InverseTransform(Focused.Id, X, Y)
  1210. end
  1211. Info['Focused'] = Focused ~= nil and Focused.Id or 'nil'
  1212. Info['Width'] = Focused ~= nil and Focused.W or 0
  1213. Info['Height'] = Focused ~= nil and Focused.H or 0
  1214. Info['CursorX'] = X
  1215. Info['CursorY'] = Y
  1216. Info['CursorPos'] = TextCursorPos
  1217. Info['Character'] = Focused ~= nil and GetDisplayCharacter(Focused.Text, TextCursorPos) or ''
  1218. Info['LineCursorPos'] = TextCursorPosLine
  1219. Info['LineCursorPosMax'] = TextCursorPosLineMax
  1220. Info['LineNumber'] = TextCursorPosLineNumber
  1221. Info['LineLength'] = (Focused ~= nil and Focused.Lines ~= nil) and string.len(Focused.Lines[TextCursorPosLineNumber]) or 0
  1222. Info['Lines'] = Focused ~= nil and Focused.Lines or nil
  1223. return Info
  1224. end
  1225. return Input