1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | "use strict" ;
|
28 |
|
29 |
|
30 |
|
31 | const Element = require( './Element.js' ) ;
|
32 | const Slider = require( './Slider.js' ) ;
|
33 |
|
34 | const ScreenBuffer = require( '../ScreenBuffer.js' ) ;
|
35 | const TextBuffer = require( '../TextBuffer.js' ) ;
|
36 | const Rect = require( '../Rect.js' ) ;
|
37 |
|
38 | const string = require( 'string-kit' ) ;
|
39 |
|
40 |
|
41 |
|
42 | function TextBox( options ) {
|
43 |
|
44 | options = ! options ? {} : options.internal ? options : Object.create( options ) ;
|
45 | options.internal = true ;
|
46 |
|
47 | Element.call( this , options ) ;
|
48 |
|
49 | this.onKey = this.onKey.bind( this ) ;
|
50 | this.onClick = this.onClick.bind( this ) ;
|
51 | this.onDrag = this.onDrag.bind( this ) ;
|
52 | this.onWheel = this.onWheel.bind( this ) ;
|
53 |
|
54 | if ( options.keyBindings ) { this.keyBindings = options.keyBindings ; }
|
55 |
|
56 | this.textAttr = options.textAttr || options.attr || { bgColor: 'default' } ;
|
57 | this.altTextAttr = options.altTextAttr || Object.assign( {} , this.textAttr , { color: 'gray' , italic: true } ) ;
|
58 | this.voidAttr = options.voidAttr || options.emptyAttr || options.attr || { bgColor: 'default' } ;
|
59 |
|
60 | this.scrollable = !! options.scrollable ;
|
61 | this.hasVScrollBar = this.scrollable && !! options.vScrollBar ;
|
62 | this.hasHScrollBar = this.scrollable && !! options.hScrollBar ;
|
63 | this.scrollX = options.scrollX || 0 ;
|
64 | this.scrollY = options.scrollY || 0 ;
|
65 |
|
66 |
|
67 |
|
68 | this.extraScrolling = !! options.extraScrolling ;
|
69 |
|
70 |
|
71 | this.firstLineRightShift = options.firstLineRightShift || 0 ;
|
72 |
|
73 | this.wordWrap = !! ( options.wordWrap || options.wordwrap ) ;
|
74 | this.lineWrap = !! ( options.lineWrap || this.wordWrap ) ;
|
75 |
|
76 | this.hiddenContent = options.hiddenContent ;
|
77 |
|
78 | this.stateMachine = options.stateMachine ;
|
79 |
|
80 | this.textAreaWidth = this.hasVScrollBar ? this.outputWidth - 1 : this.outputWidth ;
|
81 | this.textAreaHeight = this.hasHScrollBar ? this.outputHeight - 1 : this.outputHeight ;
|
82 |
|
83 | this.textBuffer = null ;
|
84 | this.altTextBuffer = null ;
|
85 | this.vScrollBarSlider = null ;
|
86 | this.hScrollBarSlider = null ;
|
87 |
|
88 | this.on( 'key' , this.onKey ) ;
|
89 | this.on( 'click' , this.onClick ) ;
|
90 | this.on( 'drag' , this.onDrag ) ;
|
91 | this.on( 'wheel' , this.onWheel ) ;
|
92 |
|
93 | this.initChildren() ;
|
94 |
|
95 | if ( this.setContent === TextBox.prototype.setContent ) {
|
96 | this.setContent( options.content , options.contentHasMarkup , true ) ;
|
97 | }
|
98 |
|
99 |
|
100 | if ( this.elementType === 'TextBox' && ! options.noDraw ) { this.draw() ; }
|
101 | }
|
102 |
|
103 | module.exports = TextBox ;
|
104 |
|
105 | TextBox.prototype = Object.create( Element.prototype ) ;
|
106 | TextBox.prototype.constructor = TextBox ;
|
107 | TextBox.prototype.elementType = 'TextBox' ;
|
108 |
|
109 |
|
110 |
|
111 |
|
112 | TextBox.prototype.strictInlineSupport = true ;
|
113 |
|
114 |
|
115 |
|
116 | TextBox.prototype.destroy = function( isSubDestroy ) {
|
117 | this.off( 'key' , this.onKey ) ;
|
118 | this.off( 'click' , this.onClick ) ;
|
119 | this.off( 'drag' , this.onDrag ) ;
|
120 | this.off( 'wheel' , this.onWheel ) ;
|
121 |
|
122 | Element.prototype.destroy.call( this , isSubDestroy ) ;
|
123 | } ;
|
124 |
|
125 |
|
126 |
|
127 | TextBox.prototype.initChildren = function() {
|
128 | this.textBuffer = new TextBuffer( {
|
129 | dst: this.outputDst ,
|
130 |
|
131 | x: this.outputX ,
|
132 | y: this.outputY ,
|
133 |
|
134 |
|
135 | firstLineRightShift: this.firstLineRightShift ,
|
136 | lineWrapWidth: this.lineWrap ? this.textAreaWidth : null ,
|
137 | wordWrap: this.wordWrap ,
|
138 | dstClipRect: {
|
139 | x: this.outputX ,
|
140 | y: this.outputY ,
|
141 | width: this.textAreaWidth ,
|
142 | height: this.textAreaHeight
|
143 | } ,
|
144 | hidden: this.hiddenContent ,
|
145 | forceInBound: true ,
|
146 | stateMachine: this.stateMachine
|
147 | } ) ;
|
148 |
|
149 | this.textBuffer.setDefaultAttr( this.textAttr ) ;
|
150 | this.textBuffer.setVoidAttr( this.voidAttr ) ;
|
151 |
|
152 |
|
153 | if ( this.useAltTextBuffer ) {
|
154 | this.altTextBuffer = new TextBuffer( {
|
155 | firstLineRightShift: this.firstLineRightShift ,
|
156 | lineWrapWidth: this.lineWrap ? this.textAreaWidth : null ,
|
157 | wordWrap: this.wordWrap ,
|
158 | dstClipRect: {
|
159 | x: this.outputX ,
|
160 | y: this.outputY ,
|
161 | width: this.textAreaWidth ,
|
162 | height: this.textAreaHeight
|
163 | }
|
164 |
|
165 | } ) ;
|
166 |
|
167 | this.altTextBuffer.setDefaultAttr( this.altTextAttr ) ;
|
168 | this.altTextBuffer.setVoidAttr( this.voidAttr ) ;
|
169 | this.textBuffer.setVoidTextBuffer( this.altTextBuffer ) ;
|
170 | }
|
171 |
|
172 |
|
173 | if ( this.hasVScrollBar ) {
|
174 | this.vScrollBarSlider = new Slider( {
|
175 | internal: true ,
|
176 | parent: this ,
|
177 | x: this.outputX + this.outputWidth - 1 ,
|
178 | y: this.outputY ,
|
179 | height: this.outputHeight ,
|
180 | isVertical: true ,
|
181 | valueToRate: scrollY => -scrollY / Math.max( 1 , this.textBuffer.buffer.length - this.textAreaHeight ) ,
|
182 | rateToValue: rate => -rate * Math.max( 1 , this.textBuffer.buffer.length - this.textAreaHeight ) ,
|
183 | noDraw: true
|
184 | } ) ;
|
185 |
|
186 | this.vScrollBarSlider.on( 'slideStep' , d => this.scroll( 0 , -d * Math.ceil( this.textAreaHeight / 2 ) ) ) ;
|
187 | this.vScrollBarSlider.on( 'slide' , value => {
|
188 | this.scrollTo( 0 , value ) ;
|
189 | this.draw() ;
|
190 | } ) ;
|
191 | }
|
192 |
|
193 | if ( this.hasHScrollBar ) {
|
194 | this.hScrollBarSlider = new Slider( {
|
195 | internal: true ,
|
196 | parent: this ,
|
197 | x: this.outputX ,
|
198 | y: this.outputY + this.outputHeight - 1 ,
|
199 | width: this.hasVScrollBar ? this.outputWidth - 1 : this.outputWidth ,
|
200 | valueToRate: scrollX => {
|
201 | var lineWidth = this.textBuffer.getContentSize().width ;
|
202 | return -scrollX / Math.max( 1 , lineWidth - this.textAreaWidth ) ;
|
203 | } ,
|
204 | rateToValue: rate => {
|
205 | var lineWidth = this.textBuffer.getContentSize().width ;
|
206 | return -rate * Math.max( 1 , lineWidth - this.textAreaWidth ) ;
|
207 | } ,
|
208 | noDraw: true
|
209 | } ) ;
|
210 |
|
211 | this.hScrollBarSlider.on( 'slideStep' , d => this.scroll( -d * Math.ceil( this.textAreaWidth / 2 ) , 0 ) ) ;
|
212 | this.hScrollBarSlider.on( 'slide' , value => {
|
213 | this.scrollTo( value , 0 ) ;
|
214 | this.draw() ;
|
215 | } ) ;
|
216 | }
|
217 | } ;
|
218 |
|
219 |
|
220 |
|
221 | TextBox.prototype.setSizeAndPosition = function( options ) {
|
222 | this.outputX = options.outputX || options.x || this.outputX || 0 ;
|
223 | this.outputY = options.outputY || options.y || this.outputY || 0 ;
|
224 | this.outputWidth = options.outputWidth || options.width || this.outputWidth || 1 ;
|
225 | this.outputHeight = options.outputHeight || options.height || this.outputHeight || 1 ;
|
226 |
|
227 | this.textAreaWidth = this.hasVScrollBar ? this.outputWidth - 1 : this.outputWidth ;
|
228 | this.textAreaHeight = this.hasHScrollBar ? this.outputHeight - 1 : this.outputHeight ;
|
229 |
|
230 | this.textBuffer.lineWrapWidth = this.lineWrap ? this.textAreaWidth : null ;
|
231 | if ( this.altTextBuffer ) { this.altTextBuffer.lineWrapWidth = this.lineWrap ? this.textAreaWidth : null ; }
|
232 |
|
233 | this.textBuffer.x = this.outputX ;
|
234 | this.textBuffer.y = this.outputY ;
|
235 |
|
236 | this.textBuffer.dstClipRect = new Rect( {
|
237 | x: this.outputX ,
|
238 | y: this.outputY ,
|
239 | width: this.textAreaWidth ,
|
240 | height: this.textAreaHeight
|
241 | } ) ;
|
242 |
|
243 |
|
244 | if ( this.lineWrap ) {
|
245 | this.textBuffer.wrapAllLines() ;
|
246 | if ( this.altTextBuffer ) { this.altTextBuffer.wrapAllLines() ; }
|
247 | }
|
248 |
|
249 | if ( this.vScrollBarSlider ) {
|
250 | this.vScrollBarSlider.x = this.outputX + this.outputWidth - 1 ;
|
251 | this.vScrollBarSlider.y = this.outputY ;
|
252 | this.vScrollBarSlider.height = this.outputHeight ;
|
253 | }
|
254 |
|
255 | if ( this.hScrollBarSlider ) {
|
256 | this.hScrollBarSlider.x = this.outputX ;
|
257 | this.hScrollBarSlider.y = this.outputY + this.outputHeight - 1 ;
|
258 | this.hScrollBarSlider.width = this.hasVScrollBar ? this.outputWidth - 1 : this.outputWidth ;
|
259 | }
|
260 | } ;
|
261 |
|
262 |
|
263 |
|
264 | TextBox.prototype.keyBindings = {
|
265 | UP: 'tinyScrollUp' ,
|
266 | DOWN: 'tinyScrollDown' ,
|
267 | PAGE_UP: 'scrollUp' ,
|
268 | PAGE_DOWN: 'scrollDown' ,
|
269 | ' ': 'scrollDown' ,
|
270 | HOME: 'scrollTop' ,
|
271 | END: 'scrollBottom' ,
|
272 | LEFT: 'scrollLeft' ,
|
273 | RIGHT: 'scrollRight' ,
|
274 | CTRL_O: 'copyClipboard'
|
275 | } ;
|
276 |
|
277 |
|
278 |
|
279 | TextBox.prototype.preDrawSelf = function() {
|
280 |
|
281 |
|
282 | this.textBuffer.draw( { dst: this.outputDst } ) ;
|
283 | } ;
|
284 |
|
285 |
|
286 |
|
287 | TextBox.prototype.scroll = function( dx , dy ) {
|
288 | return this.scrollTo( dx ? this.scrollX + dx : null , dy ? this.scrollY + dy : null ) ;
|
289 | } ;
|
290 |
|
291 |
|
292 |
|
293 | TextBox.prototype.scrollTo = function( x , y , internalAndNoDraw = false ) {
|
294 | if ( ! this.scrollable ) { return ; }
|
295 |
|
296 | if ( x !== undefined && x !== null ) {
|
297 |
|
298 | this.scrollX = Math.min( 0 , Math.max( Math.round( x ) ,
|
299 | ( this.extraScrolling ? 1 : this.textAreaWidth ) - this.textBuffer.getContentSize().width + 1 )
|
300 | ) ;
|
301 |
|
302 | this.textBuffer.x = this.outputX + this.scrollX ;
|
303 | }
|
304 |
|
305 | if ( y !== undefined && y !== null ) {
|
306 | this.scrollY = Math.min( 0 , Math.max( Math.round( y ) ,
|
307 | ( this.extraScrolling ? 1 : this.textAreaHeight ) - this.textBuffer.buffer.length )
|
308 | ) ;
|
309 |
|
310 | this.textBuffer.y = this.outputY + this.scrollY ;
|
311 | }
|
312 |
|
313 | if ( ! internalAndNoDraw ) {
|
314 | if ( this.vScrollBarSlider ) {
|
315 | this.vScrollBarSlider.setValue( this.scrollY , true ) ;
|
316 | }
|
317 |
|
318 | if ( this.hScrollBarSlider ) {
|
319 | this.hScrollBarSlider.setValue( this.scrollX , true ) ;
|
320 | }
|
321 |
|
322 | this.draw() ;
|
323 | }
|
324 | } ;
|
325 |
|
326 |
|
327 |
|
328 | TextBox.prototype.autoScrollAndDraw = function( onlyDrawCursor = false ) {
|
329 | var x , y ;
|
330 |
|
331 |
|
332 |
|
333 | if ( this.textBuffer.cx - 1 < -this.scrollX && this.scrollX !== 0 ) {
|
334 | x = -Math.max( 0 , this.textBuffer.cx - 1 ) ;
|
335 | }
|
336 | else if ( this.textBuffer.cx > this.textAreaWidth - this.scrollX - 1 ) {
|
337 | x = this.textAreaWidth - 1 - this.textBuffer.cx ;
|
338 | }
|
339 |
|
340 | if ( this.textBuffer.cy < -this.scrollY ) {
|
341 | y = -this.textBuffer.cy ;
|
342 | }
|
343 | else if ( this.textBuffer.cy > this.textAreaHeight - this.scrollY - 1 ) {
|
344 | y = this.textAreaHeight - 1 - this.textBuffer.cy ;
|
345 | }
|
346 |
|
347 | if ( x !== undefined || y !== undefined ) {
|
348 |
|
349 | this.scrollTo( x , y ) ;
|
350 | }
|
351 | else if ( ! onlyDrawCursor ) {
|
352 | this.draw() ;
|
353 | }
|
354 | else {
|
355 | this.drawCursor() ;
|
356 | }
|
357 | } ;
|
358 |
|
359 |
|
360 |
|
361 | TextBox.prototype.autoScrollAndDrawCursor = function() {
|
362 | return this.autoScrollAndDraw( true ) ;
|
363 | } ;
|
364 |
|
365 |
|
366 |
|
367 | TextBox.prototype.getContentSize = function() {
|
368 | return this.textBuffer.getContentSize() ;
|
369 | } ;
|
370 |
|
371 |
|
372 |
|
373 | TextBox.prototype.getContent = function() {
|
374 | return this.textBuffer.getText() ;
|
375 | } ;
|
376 |
|
377 |
|
378 |
|
379 | TextBox.prototype.setContent = function( content , hasMarkup , dontDraw ) {
|
380 | var contentSize ;
|
381 |
|
382 | if ( typeof content !== 'string' ) {
|
383 | if ( content === null || content === undefined ) { content = '' ; }
|
384 | else { content = '' + content ; }
|
385 | }
|
386 |
|
387 | this.content = content ;
|
388 | this.contentHasMarkup = !! hasMarkup ;
|
389 |
|
390 | this.textBuffer.setText( this.content , this.contentHasMarkup , this.textAttr ) ;
|
391 |
|
392 | if ( this.stateMachine ) {
|
393 | this.textBuffer.runStateMachine() ;
|
394 | }
|
395 |
|
396 |
|
397 | this.textBuffer.moveToEndOfLine() ;
|
398 |
|
399 | if ( ! dontDraw ) {
|
400 | this.drawCursor() ;
|
401 | this.redraw() ;
|
402 | }
|
403 | } ;
|
404 |
|
405 |
|
406 |
|
407 |
|
408 | TextBox.prototype.getAltContent = function() {
|
409 | if ( ! this.altTextBuffer ) { return null ; }
|
410 | return this.altTextBuffer.getText() ;
|
411 | } ;
|
412 |
|
413 |
|
414 |
|
415 |
|
416 | TextBox.prototype.setAltContent = function( content , hasMarkup , dontDraw ) {
|
417 | if ( ! this.altTextBuffer ) { return ; }
|
418 |
|
419 | var contentSize ;
|
420 |
|
421 | if ( typeof content !== 'string' ) {
|
422 | if ( content === null || content === undefined ) { content = '' ; }
|
423 | else { content = '' + content ; }
|
424 | }
|
425 |
|
426 |
|
427 |
|
428 |
|
429 | this.altTextBuffer.setText( content , hasMarkup , this.altTextAttr ) ;
|
430 |
|
431 |
|
432 |
|
433 | if ( ! dontDraw ) {
|
434 | this.drawCursor() ;
|
435 | this.redraw() ;
|
436 | }
|
437 | } ;
|
438 |
|
439 |
|
440 |
|
441 | TextBox.prototype.onKey = function( key , trash , data ) {
|
442 | switch( this.keyBindings[ key ] ) {
|
443 | case 'tinyScrollUp' :
|
444 | this.scroll( 0 , Math.ceil( this.textAreaHeight / 5 ) ) ;
|
445 | break ;
|
446 |
|
447 | case 'tinyScrollDown' :
|
448 | this.scroll( 0 , -Math.ceil( this.textAreaHeight / 5 ) ) ;
|
449 | break ;
|
450 |
|
451 | case 'scrollUp' :
|
452 | this.scroll( 0 , Math.ceil( this.textAreaHeight / 2 ) ) ;
|
453 | break ;
|
454 |
|
455 | case 'scrollDown' :
|
456 | this.scroll( 0 , -Math.ceil( this.textAreaHeight / 2 ) ) ;
|
457 | break ;
|
458 |
|
459 | case 'scrollLeft' :
|
460 | this.scroll( Math.ceil( this.textAreaWidth / 2 ) , 0 ) ;
|
461 | break ;
|
462 |
|
463 | case 'scrollRight' :
|
464 | this.scroll( -Math.ceil( this.textAreaWidth / 2 ) , 0 ) ;
|
465 | break ;
|
466 |
|
467 | case 'scrollTop' :
|
468 | this.scroll( 0 , 0 ) ;
|
469 | break ;
|
470 |
|
471 | case 'scrollBottom' :
|
472 |
|
473 | this.scroll( 0 , this.textAreaHeight - this.textBuffer.buffer.length ) ;
|
474 | break ;
|
475 |
|
476 | case 'copyClipboard' :
|
477 | if ( this.document ) {
|
478 | this.document.setClipboard( this.textBuffer.getSelectionText() ).catch( () => undefined ) ;
|
479 | }
|
480 | break ;
|
481 |
|
482 | default :
|
483 | return ;
|
484 | }
|
485 |
|
486 | return true ;
|
487 | } ;
|
488 |
|
489 |
|
490 |
|
491 | TextBox.prototype.onClick = function( data ) {
|
492 |
|
493 | if ( this.scrollable && ! this.hasFocus ) {
|
494 | this.document.giveFocusTo( this , 'select' ) ;
|
495 | }
|
496 | } ;
|
497 |
|
498 |
|
499 |
|
500 | TextBox.prototype.onWheel = function( data ) {
|
501 |
|
502 | if ( ! this.hasFocus ) {
|
503 | this.document.giveFocusTo( this , 'select' ) ;
|
504 | }
|
505 |
|
506 | if ( this.scrollable ) {
|
507 | this.scroll( 0 , -data.yDirection * Math.ceil( this.textAreaHeight / 5 ) ) ;
|
508 | }
|
509 | } ;
|
510 |
|
511 |
|
512 |
|
513 | TextBox.prototype.onDrag = function( data ) {
|
514 | var xmin , ymin , xmax , ymax ;
|
515 |
|
516 | if ( ! this.hasFocus ) {
|
517 | this.document.giveFocusTo( this , 'select' ) ;
|
518 | }
|
519 |
|
520 | if ( data.yFrom < data.y || ( data.yFrom === data.y && data.xFrom <= data.x ) ) {
|
521 | ymin = data.yFrom ;
|
522 | ymax = data.y ;
|
523 | xmin = data.xFrom ;
|
524 | xmax = data.x ;
|
525 | }
|
526 | else {
|
527 | ymin = data.y ;
|
528 | ymax = data.yFrom ;
|
529 | xmin = data.x ;
|
530 | xmax = data.xFrom ;
|
531 | }
|
532 |
|
533 | this.textBuffer.setSelectionRegion( {
|
534 | xmin: xmin - this.scrollX ,
|
535 | xmax: xmax - this.scrollX ,
|
536 | ymin: ymin - this.scrollY ,
|
537 | ymax: ymax - this.scrollY
|
538 | } ) ;
|
539 |
|
540 | if ( this.document ) {
|
541 | this.document.setClipboard( this.textBuffer.getSelectionText() , 'primary' ).catch( () => undefined ) ;
|
542 | }
|
543 |
|
544 | this.draw() ;
|
545 | } ;
|
546 |
|