TextInput.hx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775
  1. package h2d;
  2. import hxd.Key in K;
  3. private typedef TextHistoryElement = { t : String, c : Int, sel : { start : Int, length : Int } };
  4. /**
  5. A skinnable text input handler.
  6. Supports text selection, keyboard cursor navigation, as well as basic hotkeys: `Ctrl + Z`, `Ctrl + Y` for undo and redo and `Ctrl + A` to select all text.
  7. **/
  8. class TextInput extends Text {
  9. /**
  10. Current position of the input cursor.
  11. When TextInput is not focused value is -1.
  12. **/
  13. public var cursorIndex : Int = -1;
  14. /**
  15. The Tile used to render the input cursor.
  16. **/
  17. public var cursorTile : h2d.Tile;
  18. /**
  19. The Tile used to render the background for selected text.
  20. When rendering, this Tile is stretched horizontally to fill entire selection area.
  21. **/
  22. public var selectionTile : h2d.Tile;
  23. /**
  24. The blinking interval of the cursor in seconds.
  25. **/
  26. public var cursorBlinkTime = 0.5;
  27. /**
  28. Maximum input width.
  29. Contrary to `Text.maxWidth` does not cause a word-wrap, but also masks out contents that are outside the max width.
  30. **/
  31. public var inputWidth : Null<Int>;
  32. /**
  33. Whether the text input allows multiple lines.
  34. **/
  35. public var multiline: Bool = false;
  36. /**
  37. If not null, represents current text selection range.
  38. **/
  39. public var selectionRange : { start : Int, length : Int };
  40. /**
  41. When disabled, user would not be able to edit the input text (selection is still available).
  42. **/
  43. public var canEdit = true;
  44. /**
  45. If set, TextInput will render provided color as a background to text interactive area.
  46. **/
  47. public var backgroundColor(get, set) : Null<Int>;
  48. /**
  49. When disabled, showSoftwareKeyboard will not be called.
  50. **/
  51. public var useSoftwareKeyboard : Bool = true;
  52. public static dynamic function showSoftwareKeyboard(target:TextInput) {}
  53. public static dynamic function hideSoftwareKeyboard(target:TextInput) {}
  54. var interactive : h2d.Interactive;
  55. var cursorText : String;
  56. var cursorX : Float;
  57. var cursorXIndex : Int;
  58. var cursorY : Float;
  59. var cursorBlink = 0.;
  60. var scrollX = 0.;
  61. var selectionPos : Float;
  62. var selectionSize : Float;
  63. var undo : Array<TextHistoryElement> = [];
  64. var redo : Array<TextHistoryElement> = [];
  65. var lastChange = 0.;
  66. var lastClick = 0.;
  67. var maxHistorySize = 100;
  68. /**
  69. Create a new TextInput instance.
  70. @param font The font used to render the text.
  71. @param parent An optional parent `h2d.Object` instance to which TextInput adds itself if set.
  72. **/
  73. public function new(font, ?parent) {
  74. super(font, parent);
  75. interactive = new h2d.Interactive(0, 0);
  76. interactive.cursor = TextInput;
  77. interactive.onPush = function(e:hxd.Event) {
  78. onPush(e);
  79. if( !e.cancel && e.button == 0 ) {
  80. if( !interactive.hasFocus() ) {
  81. e.kind = EFocus;
  82. onFocus(e);
  83. e.kind = EPush;
  84. if( e.cancel ) return;
  85. interactive.focus();
  86. }
  87. cursorBlink = 0;
  88. var startIndex = textPos(e.relX, e.relY);
  89. cursorIndex = startIndex;
  90. selectionRange = null;
  91. var pt = new h2d.col.Point();
  92. var scene = getScene();
  93. if( scene == null ) return; // was removed
  94. scene.startCapture(function(e) {
  95. pt.x = e.relX;
  96. pt.y = e.relY;
  97. globalToLocal(pt);
  98. var index = textPos(pt.x, pt.y);
  99. if( index == startIndex )
  100. selectionRange = null;
  101. else if( index < startIndex )
  102. selectionRange = { start : index, length : startIndex - index };
  103. else
  104. selectionRange = { start : startIndex, length : index - startIndex };
  105. selectionSize = 0;
  106. cursorIndex = index;
  107. if( e.kind == ERelease || getScene() != scene )
  108. scene.stopCapture();
  109. });
  110. }
  111. };
  112. interactive.onKeyDown = function(e:hxd.Event) {
  113. onKeyDown(e);
  114. handleKey(e);
  115. };
  116. interactive.onTextInput = function(e:hxd.Event) {
  117. onTextInput(e);
  118. handleKey(e);
  119. };
  120. interactive.onFocus = function(e) {
  121. onFocus(e);
  122. if ( useSoftwareKeyboard && canEdit )
  123. showSoftwareKeyboard(this);
  124. }
  125. interactive.onFocusLost = function(e) {
  126. cursorIndex = -1;
  127. selectionRange = null;
  128. hideSoftwareKeyboard(this);
  129. onFocusLost(e);
  130. };
  131. interactive.onClick = function(e) {
  132. onClick(e);
  133. if( e.cancel ) return;
  134. var t = haxe.Timer.stamp();
  135. // double click to select all
  136. if( t - lastClick < 0.3 && text.length != 0 ) {
  137. selectionRange = { start : 0, length : text.length };
  138. selectionSize = 0;
  139. cursorIndex = text.length;
  140. }
  141. lastClick = t;
  142. };
  143. interactive.onKeyUp = function(e) onKeyUp(e);
  144. interactive.onRelease = function(e) onRelease(e);
  145. interactive.onKeyUp = function(e) onKeyUp(e);
  146. interactive.onMove = function(e) onMove(e);
  147. interactive.onOver = function(e) onOver(e);
  148. interactive.onOut = function(e) onOut(e);
  149. interactive.cursor = TextInput;
  150. addChildAt(interactive, 0);
  151. }
  152. override function constraintSize(width:Float, height:Float) {
  153. // disable (don't allow multiline textinput for now)
  154. }
  155. function handleKey( e : hxd.Event ) {
  156. if( e.cancel || cursorIndex < 0 )
  157. return;
  158. var oldIndex = cursorIndex;
  159. var oldText = text;
  160. switch( e.keyCode ) {
  161. case K.UP if( multiline ):
  162. moveCursorVertically(-1);
  163. case K.DOWN if( multiline ):
  164. moveCursorVertically(1);
  165. case K.LEFT if (K.isDown(K.CTRL)):
  166. cursorIndex = getWordStart();
  167. case K.LEFT:
  168. if( cursorIndex > 0 )
  169. cursorIndex--;
  170. case K.RIGHT if (K.isDown(K.CTRL)):
  171. cursorIndex = getWordEnd();
  172. case K.RIGHT:
  173. if( cursorIndex < text.length )
  174. cursorIndex++;
  175. case K.HOME:
  176. if( multiline ) {
  177. var currentLine = getCurrentLine();
  178. cursorIndex = currentLine.startIndex;
  179. } else cursorIndex = 0;
  180. case K.END:
  181. if( multiline ) {
  182. var currentLine = getCurrentLine();
  183. cursorIndex = currentLine.startIndex + currentLine.value.length - 1;
  184. } else cursorIndex = text.length;
  185. case K.BACKSPACE, K.DELETE if( selectionRange != null ):
  186. if( !canEdit ) return;
  187. beforeChange();
  188. cutSelection();
  189. onChange();
  190. case K.DELETE:
  191. if( cursorIndex < text.length && canEdit ) {
  192. beforeChange();
  193. var end = K.isDown(K.CTRL) ? getWordEnd() : cursorIndex + 1;
  194. text = text.substr(0, cursorIndex) + text.substr(end);
  195. onChange();
  196. }
  197. case K.BACKSPACE:
  198. if( cursorIndex > 0 && canEdit ) {
  199. beforeChange();
  200. var end = cursorIndex;
  201. cursorIndex = K.isDown(K.CTRL) ? getWordStart() : cursorIndex - 1;
  202. text = text.substr(0, cursorIndex) + text.substr(end);
  203. onChange();
  204. }
  205. case K.ESCAPE:
  206. cursorIndex = -1;
  207. interactive.blur();
  208. return;
  209. case K.ENTER, K.NUMPAD_ENTER:
  210. if(!multiline) {
  211. cursorIndex = -1;
  212. interactive.blur();
  213. return;
  214. } else {
  215. beforeChange();
  216. if( selectionRange != null )
  217. cutSelection();
  218. text = text.substr(0, cursorIndex) + '\n' + text.substr(cursorIndex);
  219. cursorIndex++;
  220. onChange();
  221. }
  222. case K.Z if( K.isDown(K.CTRL) ):
  223. if( undo.length > 0 && canEdit ) {
  224. redo.push(curHistoryState());
  225. setState(undo.pop());
  226. onChange();
  227. }
  228. return;
  229. case K.Y if( K.isDown(K.CTRL) ):
  230. if( redo.length > 0 && canEdit ) {
  231. undo.push(curHistoryState());
  232. setState(redo.pop());
  233. onChange();
  234. }
  235. return;
  236. case K.A if (K.isDown(K.CTRL)):
  237. if (text != "") {
  238. cursorIndex = text.length;
  239. selectionRange = {start: 0, length: text.length};
  240. selectionSize = 0;
  241. }
  242. return;
  243. case K.C if (K.isDown(K.CTRL)):
  244. if( text != "" && selectionRange != null ) {
  245. hxd.System.setClipboardText(text.substr(selectionRange.start, selectionRange.length));
  246. }
  247. case K.X if (K.isDown(K.CTRL)):
  248. if( text != "" && selectionRange != null ) {
  249. if(hxd.System.setClipboardText(text.substr(selectionRange.start, selectionRange.length))) {
  250. if( !canEdit ) return;
  251. beforeChange();
  252. cutSelection();
  253. onChange();
  254. }
  255. }
  256. case K.V if (K.isDown(K.CTRL)):
  257. if( !canEdit ) return;
  258. var t = hxd.System.getClipboardText();
  259. if( t != null && t.length > 0 ) {
  260. beforeChange();
  261. if( selectionRange != null )
  262. cutSelection();
  263. text = text.substr(0, cursorIndex) + t + text.substr(cursorIndex);
  264. cursorIndex += t.length;
  265. onChange();
  266. }
  267. default:
  268. if( e.kind == EKeyDown )
  269. return;
  270. if( e.charCode != 0 && canEdit ) {
  271. if( !font.hasChar(e.charCode) ) return; // don't allow chars not supported by font
  272. beforeChange();
  273. if( selectionRange != null )
  274. cutSelection();
  275. text = text.substr(0, cursorIndex) + String.fromCharCode(e.charCode) + text.substr(cursorIndex);
  276. cursorIndex++;
  277. onChange();
  278. }
  279. }
  280. cursorBlink = 0.;
  281. if( K.isDown(K.SHIFT) && text == oldText ) {
  282. if( cursorIndex == oldIndex ) return;
  283. if( selectionRange == null )
  284. selectionRange = oldIndex < cursorIndex ? { start : oldIndex, length : cursorIndex - oldIndex } : { start : cursorIndex, length : oldIndex - cursorIndex };
  285. else if( oldIndex == selectionRange.start ) {
  286. selectionRange.length += oldIndex - cursorIndex;
  287. selectionRange.start = cursorIndex;
  288. } else
  289. selectionRange.length += cursorIndex - oldIndex;
  290. if( selectionRange.length == 0 )
  291. selectionRange = null;
  292. else if( selectionRange.length < 0 ) {
  293. selectionRange.start += selectionRange.length;
  294. selectionRange.length = -selectionRange.length;
  295. }
  296. selectionSize = 0;
  297. } else
  298. selectionRange = null;
  299. }
  300. function cutSelection() {
  301. if(selectionRange == null) return false;
  302. cursorIndex = selectionRange.start;
  303. var end = cursorIndex + selectionRange.length;
  304. text = text.substr(0, cursorIndex) + text.substr(end);
  305. selectionRange = null;
  306. return true;
  307. }
  308. function getWordEnd() {
  309. var len = text.length;
  310. if (cursorIndex >= len) {
  311. return cursorIndex;
  312. }
  313. var charset = hxd.Charset.getDefault();
  314. var ret = cursorIndex;
  315. while (ret < len && charset.isSpace(StringTools.fastCodeAt(text, ret))) ret++;
  316. while (ret < len && !charset.isSpace(StringTools.fastCodeAt(text, ret))) ret++;
  317. return ret;
  318. }
  319. function getWordStart() {
  320. if (cursorIndex <= 0) {
  321. return cursorIndex;
  322. }
  323. var charset = hxd.Charset.getDefault();
  324. var ret = cursorIndex;
  325. while (ret > 0 && charset.isSpace(StringTools.fastCodeAt(text, ret - 1))) ret--;
  326. while (ret > 0 && !charset.isSpace(StringTools.fastCodeAt(text, ret - 1))) ret--;
  327. return ret;
  328. }
  329. function moveCursorVertically(yDiff: Int){
  330. if( !multiline || yDiff == 0)
  331. return;
  332. var lines = [];
  333. var cursorLineIndex = -1, currLineIndex = 0, currIndex = 0;
  334. for( line in getAllLines() ) {
  335. lines.push( { line: line, startIndex: currIndex } );
  336. var prevIndex = currIndex;
  337. currIndex += line.length;
  338. if( cursorIndex >= prevIndex && cursorIndex < currIndex )
  339. cursorLineIndex = currLineIndex;
  340. currLineIndex++;
  341. }
  342. if (cursorLineIndex == -1)
  343. return;
  344. var destinationIndex = hxd.Math.iclamp(cursorLineIndex + yDiff, -1, lines.length);
  345. if (destinationIndex == cursorLineIndex)
  346. return;
  347. // We're moving down from the last line, move to the end of the line
  348. if( destinationIndex == lines.length) {
  349. cursorIndex = text.length;
  350. return;
  351. }
  352. // We're moving up from the first line, snap to beginning
  353. if( destinationIndex == -1 ) {
  354. cursorIndex = 0;
  355. return;
  356. }
  357. var current = lines[cursorLineIndex];
  358. var xOffset = 0.;
  359. var prevCC: Null<Int> = null;
  360. var cI = 0;
  361. while( current.startIndex + cI < cursorIndex) {
  362. var cc = current.line.charCodeAt(cI);
  363. var c = font.getChar(cc);
  364. xOffset += c.width + c.getKerningOffset(prevCC) + letterSpacing;
  365. prevCC = cc;
  366. cI++;
  367. }
  368. var destination = lines[destinationIndex];
  369. var currOffset = 0.;
  370. prevCC = null;
  371. for( cI in 0...destination.line.length ) {
  372. var cc = StringTools.fastCodeAt(destination.line, cI);
  373. var c = font.getChar(cc);
  374. var newCurrOffset = currOffset + c.width + c.getKerningOffset(prevCC) + letterSpacing;
  375. if( newCurrOffset > xOffset ) {
  376. cursorIndex = destination.startIndex + cI + 1;
  377. if( xOffset - currOffset < newCurrOffset - xOffset )
  378. cursorIndex--;
  379. return;
  380. }
  381. currOffset = newCurrOffset;
  382. prevCC = cc;
  383. }
  384. cursorIndex = destination.startIndex + destination.line.length;
  385. // The last character in this line may be the \n, check for this and move back by one.
  386. // we can't just assume this because the last line typically won't end with a newline.
  387. if( destination.line.charAt(destination.line.length-1) == "\n")
  388. cursorIndex--;
  389. }
  390. function setState(h:TextHistoryElement) {
  391. text = h.t;
  392. cursorIndex = h.c;
  393. selectionRange = h.sel;
  394. if( selectionRange != null )
  395. cursorIndex = selectionRange.start + selectionRange.length;
  396. }
  397. function curHistoryState() : TextHistoryElement {
  398. return { t : text, c : cursorIndex, sel : selectionRange == null ? null : { start : selectionRange.start, length : selectionRange.length } };
  399. }
  400. /**
  401. Load the state from a previous input, copy the current text, cursor position, selection etc.
  402. This allows to continue uninterrupted input experience while the input component has been reset/rebuild
  403. **/
  404. public function loadState( from : TextInput, focus=false ) {
  405. if( from == null )
  406. return;
  407. this.undo = from.undo;
  408. this.redo = from.redo;
  409. this.text = from.text;
  410. this.cursorIndex = from.cursorIndex;
  411. this.scrollX = from.scrollX;
  412. this.selectionRange = from.selectionRange;
  413. this.cursorBlinkTime = from.cursorBlinkTime;
  414. if( focus ) this.focus();
  415. }
  416. function beforeChange() {
  417. var t = haxe.Timer.stamp();
  418. if( t - lastChange < 1 ) {
  419. lastChange = t;
  420. return;
  421. }
  422. lastChange = t;
  423. undo.push(curHistoryState());
  424. redo = [];
  425. while( undo.length > maxHistorySize ) undo.shift();
  426. }
  427. function getAllLines() {
  428. var lines = this.text.split('\n');
  429. var finalLines : Array<String> = [];
  430. for(l in lines) {
  431. var splitText = splitText(l).split('\n');
  432. finalLines = finalLines.concat(splitText);
  433. }
  434. for(i in 0...finalLines.length) {
  435. finalLines[i] += '\n';
  436. }
  437. return finalLines;
  438. }
  439. function getCurrentLine() : {value: String, startIndex: Int} {
  440. var lines = getAllLines();
  441. var currIndex = 0;
  442. for( i in 0...lines.length ) {
  443. var newCurrIndex = currIndex + lines[i].length;
  444. if( cursorIndex < newCurrIndex )
  445. return { value: lines[i], startIndex: currIndex };
  446. currIndex = newCurrIndex;
  447. }
  448. return { value: '', startIndex: -1 };
  449. }
  450. function getCursorXOffset() {
  451. var lines = getAllLines();
  452. var offset = cursorIndex;
  453. var currLine = getCurrentLine().value;
  454. var currIndex = 0;
  455. for(i in 0...lines.length) {
  456. currIndex += lines[i].length;
  457. if(cursorIndex < currIndex) {
  458. break;
  459. } else {
  460. offset -= lines[i].length;
  461. }
  462. }
  463. return calcTextWidth(currLine.substr(0, offset));
  464. }
  465. function getCursorYOffset() {
  466. // return 0.0;
  467. var lines = getAllLines();
  468. var currIndex = 0;
  469. var lineNum = 0;
  470. for(i in 0...lines.length) {
  471. currIndex += lines[i].length;
  472. if(cursorIndex < currIndex) {
  473. lineNum = i;
  474. break;
  475. }
  476. }
  477. return lineNum * font.lineHeight;
  478. }
  479. /**
  480. Returns a String representing currently selected text area or `null` if no text is selected.
  481. **/
  482. public function getSelectedText() : String {
  483. return selectionRange == null ? null : text.substr(selectionRange.start, selectionRange.length);
  484. }
  485. override function set_text(t:String) {
  486. super.set_text(t);
  487. if( cursorIndex > t.length ) cursorIndex = t.length;
  488. return t;
  489. }
  490. override function set_font(f) {
  491. super.set_font(f);
  492. cursorTile = h2d.Tile.fromColor(0xFFFFFF, 1, font.size);
  493. cursorTile.dy = 2;
  494. selectionTile = h2d.Tile.fromColor(0x3399FF, 0, hxd.Math.ceil(font.lineHeight));
  495. return f;
  496. }
  497. override function initGlyphs(text:String, rebuild = true):Void {
  498. super.initGlyphs(text, rebuild);
  499. if( rebuild ) {
  500. this.calcWidth += cursorTile.width; // cursor end pos
  501. if( inputWidth != null && this.calcWidth > inputWidth ) this.calcWidth = inputWidth;
  502. }
  503. }
  504. function textPos( x : Float, y : Float ) {
  505. x += scrollX;
  506. var lineIndex = Math.floor(y / font.lineHeight);
  507. var lines = getAllLines();
  508. lineIndex = hxd.Math.iclamp(lineIndex, 0, lines.length - 1);
  509. var selectedLine = lines[lineIndex];
  510. var pos = 0;
  511. for(i in 0...lineIndex) {
  512. pos += lines[i].length;
  513. }
  514. var linePos = 0;
  515. while( linePos < selectedLine.length ) {
  516. if( calcTextWidth(selectedLine.substr(0,linePos+1)) > x ) {
  517. pos++;
  518. break;
  519. }
  520. pos++;
  521. linePos++;
  522. }
  523. return pos - 1;
  524. }
  525. function syncInteract() {
  526. var lines = getAllLines();
  527. interactive.width = (inputWidth != null ? inputWidth : maxWidth != null ? Math.ceil(maxWidth) : textWidth);
  528. interactive.height = font.lineHeight * (lines.length == 0 ? 1 : lines.length);
  529. }
  530. override function getBoundsRec(relativeTo:Object, out:h2d.col.Bounds, forSize:Bool) {
  531. syncInteract();
  532. super.getBoundsRec(relativeTo, out, forSize);
  533. }
  534. override function sync(ctx) {
  535. syncInteract();
  536. super.sync(ctx);
  537. }
  538. override function draw(ctx:RenderContext) {
  539. if( inputWidth != null ) {
  540. var h = localToGlobal(new h2d.col.Point(inputWidth, font.lineHeight));
  541. ctx.clipRenderZone(absX, absY, h.x - absX, h.y - absY);
  542. }
  543. if( cursorIndex >= 0 && (text != cursorText || cursorIndex != cursorXIndex) ) {
  544. if( cursorIndex > text.length ) cursorIndex = text.length;
  545. cursorText = text;
  546. cursorXIndex = cursorIndex;
  547. cursorX = getCursorXOffset();
  548. cursorY = getCursorYOffset();
  549. if( inputWidth != null && cursorX - scrollX >= inputWidth )
  550. scrollX = cursorX - inputWidth + 1;
  551. else if( cursorX < scrollX && cursorIndex > 0 )
  552. scrollX = cursorX - hxd.Math.imin(inputWidth, Std.int(cursorX));
  553. else if( cursorX < scrollX )
  554. scrollX = cursorX;
  555. }
  556. absX -= scrollX * matA;
  557. absY -= scrollX * matC;
  558. if( selectionRange != null ) {
  559. var lines = getAllLines();
  560. var lineOffset = 0;
  561. for(i in 0...lines.length) {
  562. var line = lines[i];
  563. var selEnd = line.length;
  564. if(selectionRange.start > lineOffset + line.length || selectionRange.start + selectionRange.length < lineOffset) {
  565. lineOffset += line.length;
  566. continue;
  567. }
  568. var selStart = Math.floor(Math.max(0, selectionRange.start - lineOffset));
  569. var selEnd = Math.floor(Math.min(line.length - selStart, selectionRange.length + selectionRange.start - lineOffset - selStart));
  570. selectionPos = calcTextWidth(line.substr(0, selStart));
  571. selectionSize = calcTextWidth(line.substr(selStart, selEnd));
  572. if( selectionRange.start + selectionRange.length == text.length ) selectionSize += cursorTile.width; // last pixel
  573. selectionTile.dx += selectionPos;
  574. selectionTile.dy += i * font.lineHeight;
  575. selectionTile.width += selectionSize;
  576. emitTile(ctx, selectionTile);
  577. selectionTile.dx -= selectionPos;
  578. selectionTile.dy -= i * font.lineHeight;
  579. selectionTile.width -= selectionSize;
  580. lineOffset += line.length;
  581. }
  582. }
  583. super.draw(ctx);
  584. absX += scrollX * matA;
  585. absY += scrollX * matC;
  586. if( cursorIndex >= 0 ) {
  587. cursorBlink += ctx.elapsedTime;
  588. if( cursorBlink % (cursorBlinkTime * 2) < cursorBlinkTime ) {
  589. cursorTile.dx += cursorX - scrollX;
  590. cursorTile.dy += cursorY;
  591. emitTile(ctx, cursorTile);
  592. cursorTile.dx -= cursorX - scrollX;
  593. cursorTile.dy -= cursorY;
  594. }
  595. }
  596. if( inputWidth != null )
  597. ctx.popRenderZone();
  598. }
  599. /**
  600. Sets focus on this `TextInput`.
  601. **/
  602. public function focus() {
  603. interactive.focus();
  604. if( cursorIndex < 0 ) {
  605. cursorIndex = 0;
  606. if( text != "" ) selectionRange = { start : 0, length : text.length };
  607. }
  608. }
  609. /**
  610. Checks if TextInput is currently focused.
  611. **/
  612. public function hasFocus() {
  613. return interactive.hasFocus();
  614. }
  615. /**
  616. Delegate of underlying `Interactive.onOut`.
  617. **/
  618. public dynamic function onOut(e:hxd.Event) {
  619. }
  620. /**
  621. Delegate of underlying `Interactive.onOver`.
  622. **/
  623. public dynamic function onOver(e:hxd.Event) {
  624. }
  625. /**
  626. Delegate of underlying `Interactive.onMove`.
  627. **/
  628. public dynamic function onMove(e:hxd.Event) {
  629. }
  630. /**
  631. Delegate of underlying `Interactive.onClick`.
  632. **/
  633. public dynamic function onClick(e:hxd.Event) {
  634. }
  635. /**
  636. Delegate of underlying `Interactive.onPush`.
  637. **/
  638. public dynamic function onPush(e:hxd.Event) {
  639. }
  640. /**
  641. Delegate of underlying `Interactive.onRelease`.
  642. **/
  643. public dynamic function onRelease(e:hxd.Event) {
  644. }
  645. /**
  646. Delegate of underlying `Interactive.onKeyDown`.
  647. **/
  648. public dynamic function onKeyDown(e:hxd.Event) {
  649. }
  650. /**
  651. Delegate of underlying `Interactive.onKeyUp`.
  652. **/
  653. public dynamic function onKeyUp(e:hxd.Event) {
  654. }
  655. /**
  656. Delegate of underlying `Interactive.onTextInput`.
  657. **/
  658. public dynamic function onTextInput(e:hxd.Event) {
  659. }
  660. /**
  661. Delegate of underlying `Interactive.onFocus`.
  662. **/
  663. public dynamic function onFocus(e:hxd.Event) {
  664. }
  665. /**
  666. Delegate of underlying `Interactive.onFocusLost`.
  667. **/
  668. public dynamic function onFocusLost(e:hxd.Event) {
  669. }
  670. /**
  671. Sent when user modifies TextInput contents.
  672. **/
  673. public dynamic function onChange() {
  674. }
  675. override function drawRec(ctx:RenderContext) {
  676. var old = interactive.visible;
  677. var oldC = interactive.parentContainer;
  678. // workaround @:bypassAccessor not working by setting parentContainer=null
  679. // prevent domkit style to be updated
  680. interactive.parentContainer = null;
  681. interactive.visible = false;
  682. interactive.parentContainer = oldC;
  683. interactive.draw(ctx);
  684. super.drawRec(ctx);
  685. interactive.parentContainer = null;
  686. interactive.visible = old;
  687. interactive.parentContainer = oldC;
  688. }
  689. function get_backgroundColor() return interactive.backgroundColor;
  690. function set_backgroundColor(v) return interactive.backgroundColor = v;
  691. }