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 BaseMenu = require( './BaseMenu.js' ) ;
|
33 | const Button = require( './Button.js' ) ;
|
34 | const ToggleButton = require( './ToggleButton.js' ) ;
|
35 |
|
36 | const misc = require( '../misc.js' ) ;
|
37 | const string = require( 'string-kit' ) ;
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 | function RowMenu( options ) {
|
44 |
|
45 | options = ! options ? {} : options.internal ? options : Object.create( options ) ;
|
46 | options.internal = true ;
|
47 |
|
48 | if ( ! options.outputWidth && ! options.width ) {
|
49 | options.outputWidth = Math.min( options.parent.inputWidth , options.parent.outputWidth ) ;
|
50 | }
|
51 |
|
52 | this.buttonPaddingWidth = 0 ;
|
53 | this.buttonSymbolWidth = 0 ;
|
54 | this.pageMaxWidth = options.pageMaxWidth || null ;
|
55 | this.pageItemsDef = [] ;
|
56 |
|
57 | BaseMenu.call( this , options ) ;
|
58 |
|
59 | this.justify = !! options.justify ;
|
60 |
|
61 | this.separator = options.separator || options.buttonSeparator || ' ' ;
|
62 | this.separatorHasMarkup = !! ( options.separatorHasMarkup || options.buttonSeparatorHasMarkup ) ;
|
63 | this.separatorAttr = Object.assign( {} , this.backgroundAttr , options.separatorAttr || options.buttonSeparatorAttr ) ;
|
64 | this.separatorWidth = 0 ;
|
65 |
|
66 | this.initChildren() ;
|
67 |
|
68 |
|
69 | if ( this.elementType === 'RowMenu' && ! options.noDraw ) { this.draw() ; }
|
70 | }
|
71 |
|
72 | module.exports = RowMenu ;
|
73 |
|
74 | RowMenu.prototype = Object.create( BaseMenu.prototype ) ;
|
75 | RowMenu.prototype.constructor = RowMenu ;
|
76 | RowMenu.prototype.elementType = 'RowMenu' ;
|
77 |
|
78 |
|
79 |
|
80 | RowMenu.prototype.inlineNewLine = true ;
|
81 | RowMenu.prototype.ButtonClass = Button ;
|
82 |
|
83 |
|
84 |
|
85 | RowMenu.prototype.defaultOptions = {
|
86 | buttonBlurAttr: { bgColor: 'white' , color: 'black' } ,
|
87 | buttonFocusAttr: { bgColor: 'green' , color: 'blue' , dim: true } ,
|
88 | buttonDisabledAttr: { bgColor: 'white' , color: 'brightBlack' } ,
|
89 | buttonSubmittedAttr: { bgColor: 'brightWhite' , color: 'brightBlack' }
|
90 | } ;
|
91 |
|
92 |
|
93 |
|
94 | RowMenu.prototype.destroy = function( isSubDestroy ) {
|
95 | this.off( 'key' , this.onKey ) ;
|
96 | this.off( 'focus' , this.onFocus ) ;
|
97 |
|
98 | Element.prototype.destroy.call( this , isSubDestroy ) ;
|
99 | } ;
|
100 |
|
101 |
|
102 |
|
103 | RowMenu.prototype.keyBindings = {
|
104 | LEFT: 'previous' ,
|
105 | RIGHT: 'next' ,
|
106 | PAGE_UP: 'previousPage' ,
|
107 | PAGE_DOWN: 'nextPage' ,
|
108 | HOME: 'firstPage' ,
|
109 | END: 'lastPage' ,
|
110 |
|
111 |
|
112 | ALT_ENTER: 'submit'
|
113 | } ;
|
114 |
|
115 |
|
116 |
|
117 | RowMenu.prototype.buttonKeyBindings = {
|
118 | ENTER: 'submit' ,
|
119 | KP_ENTER: 'submit'
|
120 | } ;
|
121 |
|
122 |
|
123 |
|
124 | RowMenu.prototype.toggleButtonKeyBindings = {
|
125 | ENTER: 'toggle' ,
|
126 | KP_ENTER: 'toggle'
|
127 | } ;
|
128 |
|
129 |
|
130 |
|
131 |
|
132 | RowMenu.prototype.initChildren = function() {
|
133 | if ( ! this.itemsDef.length ) { return ; }
|
134 |
|
135 | this.buttonPaddingWidth =
|
136 | Math.max(
|
137 | Element.computeContentWidth( this.blurLeftPadding , this.paddingHasMarkup ) ,
|
138 | Element.computeContentWidth( this.focusLeftPadding , this.paddingHasMarkup ) ,
|
139 | Element.computeContentWidth( this.disabledLeftPadding , this.paddingHasMarkup ) ,
|
140 | Element.computeContentWidth( this.submittedLeftPadding , this.paddingHasMarkup ) ,
|
141 | Element.computeContentWidth( this.turnedOnFocusLeftPadding , this.paddingHasMarkup ) ,
|
142 | Element.computeContentWidth( this.turnedOffFocusLeftPadding , this.paddingHasMarkup ) ,
|
143 | Element.computeContentWidth( this.turnedOnBlurLeftPadding , this.paddingHasMarkup ) ,
|
144 | Element.computeContentWidth( this.turnedOffBlurLeftPadding , this.paddingHasMarkup )
|
145 | ) + Math.max(
|
146 | Element.computeContentWidth( this.blurRightPadding , this.paddingHasMarkup ) ,
|
147 | Element.computeContentWidth( this.focusRightPadding , this.paddingHasMarkup ) ,
|
148 | Element.computeContentWidth( this.disabledRightPadding , this.paddingHasMarkup ) ,
|
149 | Element.computeContentWidth( this.submittedRightPadding , this.paddingHasMarkup ) ,
|
150 | Element.computeContentWidth( this.turnedOnFocusRightPadding , this.paddingHasMarkup ) ,
|
151 | Element.computeContentWidth( this.turnedOffFocusRightPadding , this.paddingHasMarkup ) ,
|
152 | Element.computeContentWidth( this.turnedOnBlurRightPadding , this.paddingHasMarkup ) ,
|
153 | Element.computeContentWidth( this.turnedOffBlurRightPadding , this.paddingHasMarkup )
|
154 | ) ;
|
155 |
|
156 | if ( this.buttonPaddingWidth > this.outputWidth ) {
|
157 |
|
158 | return ;
|
159 | }
|
160 |
|
161 | var ellipsisWidth = Element.computeContentWidth( this.contentEllipsis , false ) ;
|
162 | this.separatorWidth = Element.computeContentWidth( this.separator , this.separatorHasMarkup ) ;
|
163 |
|
164 |
|
165 | this.previousPageDef = Object.assign( { content: '◀' , internalRole: 'previousPage' } , this.previousPageDef ) ;
|
166 | this.previousPageDef.contentHasMarkup = this.previousPageDef.contentHasMarkup || this.previousPageDef.markup ;
|
167 | this.previousPageDef.width = this.buttonPaddingWidth + Element.computeContentWidth( this.previousPageDef.content , this.previousPageDef.contentHasMarkup ) ;
|
168 | this.previousPageDef.buttonContent = this.previousPageDef.content ;
|
169 |
|
170 | this.nextPageDef = Object.assign( { content: '▶' , internalRole: 'nextPage' } , this.nextPageDef ) ;
|
171 | this.nextPageDef.contentHasMarkup = this.nextPageDef.contentHasMarkup || this.nextPageDef.markup ;
|
172 | this.nextPageDef.width = this.buttonPaddingWidth + Element.computeContentWidth( this.nextPageDef.content , this.nextPageDef.contentHasMarkup ) ;
|
173 | this.nextPageDef.buttonContent = this.nextPageDef.content ;
|
174 |
|
175 |
|
176 | var page = 0 , pageWidth = 0 , pageItemCount = 0 ;
|
177 |
|
178 | this.itemsDef.forEach( ( def , index ) => {
|
179 | def.buttonContent = def.content ;
|
180 | def.contentHasMarkup = def.contentHasMarkup || def.markup ;
|
181 |
|
182 | var contentWidth = Element.computeContentWidth( def.content , def.contentHasMarkup ) ,
|
183 | isLastItem = index === this.itemsDef.length - 1 ;
|
184 |
|
185 | def.width = contentWidth + this.buttonPaddingWidth + this.buttonSymbolWidth ;
|
186 |
|
187 | var overflow = pageWidth + def.width
|
188 | + ( pageItemCount ? this.separatorWidth : 0 )
|
189 | + ( isLastItem ? 0 : this.nextPageDef.width + this.separatorWidth )
|
190 | - this.outputWidth ;
|
191 |
|
192 |
|
193 | if ( overflow > 0 ) {
|
194 | if ( pageItemCount ) {
|
195 | page ++ ;
|
196 | pageItemCount = 0 ;
|
197 | pageWidth = this.previousPageDef.width + this.separatorWidth ;
|
198 |
|
199 | overflow = pageWidth + def.width
|
200 | + ( isLastItem ? 0 : this.nextPageDef.width + this.separatorWidth )
|
201 | - this.outputWidth ;
|
202 | }
|
203 |
|
204 | if ( overflow > 0 ) {
|
205 | def.buttonContent = Element.truncateContent( def.content , contentWidth - overflow - ellipsisWidth , def.contentHasMarkup ) + this.contentEllipsis ;
|
206 | contentWidth = Element.computeContentWidth( def.buttonContent , def.contentHasMarkup ) ;
|
207 | }
|
208 | }
|
209 |
|
210 | def.page = page ;
|
211 | pageWidth += def.width + ( pageItemCount ? this.separatorWidth : 0 ) ;
|
212 | pageItemCount ++ ;
|
213 |
|
214 | if ( ! this.pageItemsDef[ page ] ) { this.pageItemsDef[ page ] = [] ; }
|
215 | this.pageItemsDef[ page ].push( def ) ;
|
216 | } ) ;
|
217 |
|
218 | this.maxPage = page ;
|
219 |
|
220 |
|
221 | if ( ! this.pageItemsDef.length ) { this.pageItemsDef.push( [] ) ; }
|
222 |
|
223 | this.pageItemsDef.forEach( ( pageDef , index ) => {
|
224 | if ( index ) { pageDef.unshift( this.previousPageDef ) ; }
|
225 | if ( index < this.pageItemsDef.length - 1 ) { pageDef.push( this.nextPageDef ) ; }
|
226 | pageDef.buttonsWidth = pageDef.reduce( ( acc , def ) => acc + def.width , 0 ) ;
|
227 | pageDef.buttonsAndSeparatorsWidth = pageDef.buttonsWidth + ( pageDef.length - 1 ) * this.separatorWidth ;
|
228 | pageDef.justifyWidth = Math.max( 0 ,
|
229 | this.justify ? ( this.outputWidth - pageDef.buttonsAndSeparatorsWidth ) / ( pageDef.length - 1 )
|
230 | : 0
|
231 | ) ;
|
232 |
|
233 | } ) ;
|
234 |
|
235 |
|
236 | if ( this.elementType === 'RowMenu' ) { this.initPage() ; }
|
237 | } ;
|
238 |
|
239 |
|
240 |
|
241 | RowMenu.prototype.initPage = function( page = this.page ) {
|
242 | var pageDef = this.pageItemsDef[ page ] ,
|
243 | justifyWidthError = 0 ,
|
244 | buttonOffsetX = 0 ,
|
245 | buttonOffsetY = 0 ;
|
246 |
|
247 | if ( ! pageDef ) { return ; }
|
248 |
|
249 | this.buttons.forEach( button => button.destroy( false , true ) ) ;
|
250 | this.buttons.length = 0 ;
|
251 |
|
252 |
|
253 |
|
254 | pageDef.forEach( ( def , index ) => {
|
255 | var ButtonConstructor , isToggle , key , value , blurAttr ;
|
256 |
|
257 | ButtonConstructor = def.internalRole ? Button : this.ButtonClass ;
|
258 | isToggle = ButtonConstructor === ToggleButton || ButtonConstructor.prototype instanceof ToggleButton ;
|
259 |
|
260 | key = def.key ;
|
261 | value = this.childUseParentKeyValue && key && this.value && typeof this.value === 'object' ? this.value[ key ] : def.value ;
|
262 |
|
263 | if ( index % 2 ) {
|
264 |
|
265 | blurAttr = def.blurAttr || this.buttonBlurAttr ;
|
266 | }
|
267 | else {
|
268 |
|
269 | blurAttr = def.evenBlurAttr || def.blurAttr || this.buttonEvenBlurAttr || this.buttonBlurAttr ;
|
270 | }
|
271 |
|
272 | this.buttons[ index ] = new ButtonConstructor( {
|
273 | internal: true ,
|
274 | parent: this ,
|
275 | childId: index ,
|
276 | internalRole: def.internalRole ,
|
277 | content: def.buttonContent ,
|
278 | contentHasMarkup: def.contentHasMarkup ,
|
279 | disabled: def.disabled ,
|
280 | def ,
|
281 | key ,
|
282 | value ,
|
283 | outputX: this.outputX + buttonOffsetX ,
|
284 | outputY: this.outputY + buttonOffsetY ,
|
285 |
|
286 | blurAttr ,
|
287 | focusAttr: def.focusAttr || this.buttonFocusAttr ,
|
288 | disabledAttr: def.disabledAttr || this.buttonDisabledAttr ,
|
289 | submittedAttr: def.submittedAttr || this.buttonSubmittedAttr ,
|
290 | turnedOnFocusAttr: def.turnedOnFocusAttr || this.turnedOnFocusAttr ,
|
291 | turnedOffFocusAttr: def.turnedOffFocusAttr || this.turnedOffFocusAttr ,
|
292 | turnedOnBlurAttr: def.turnedOnBlurAttr || this.turnedOnBlurAttr ,
|
293 | turnedOffBlurAttr: def.turnedOffBlurAttr || this.turnedOffBlurAttr ,
|
294 |
|
295 | blurLeftPadding: this.blurLeftPadding ,
|
296 | blurRightPadding: this.blurRightPadding ,
|
297 | focusLeftPadding: this.focusLeftPadding ,
|
298 | focusRightPadding: this.focusRightPadding ,
|
299 | disabledLeftPadding: this.disabledLeftPadding ,
|
300 | disabledRightPadding: this.disabledRightPadding ,
|
301 | submittedLeftPadding: this.submittedLeftPadding ,
|
302 | submittedRightPadding: this.submittedRightPadding ,
|
303 |
|
304 | turnedOnFocusLeftPadding: this.turnedOnFocusLeftPadding ,
|
305 | turnedOnFocusRightPadding: this.turnedOnFocusRightPadding ,
|
306 | turnedOffFocusLeftPadding: this.turnedOffFocusLeftPadding ,
|
307 | turnedOffFocusRightPadding: this.turnedOffFocusRightPadding ,
|
308 | turnedOnBlurLeftPadding: this.turnedOnBlurLeftPadding ,
|
309 | turnedOnBlurRightPadding: this.turnedOnBlurRightPadding ,
|
310 | turnedOffBlurLeftPadding: this.turnedOffBlurLeftPadding ,
|
311 | turnedOffBlurRightPadding: this.turnedOffBlurRightPadding ,
|
312 |
|
313 | paddingHasMarkup: this.paddingHasMarkup ,
|
314 |
|
315 | keyBindings: isToggle ? this.toggleButtonKeyBindings : this.buttonKeyBindings ,
|
316 | actionKeyBindings: isToggle ? this.toggleButtonActionKeyBindings : this.buttonActionKeyBindings ,
|
317 | shortcuts: def.shortcuts ,
|
318 |
|
319 | noDraw: true
|
320 | } ) ;
|
321 |
|
322 | this.buttons[ index ].on( 'submit' , this.onButtonSubmit ) ;
|
323 |
|
324 | if ( isToggle ) {
|
325 | this.buttons[ index ].on( 'toggle' , this.onButtonToggle ) ;
|
326 | }
|
327 |
|
328 | var justifyWidthFloat = pageDef.justifyWidth + justifyWidthError ;
|
329 | var justifyWidth = Math.round( justifyWidthFloat ) ;
|
330 | justifyWidthError = justifyWidthFloat - justifyWidth ;
|
331 |
|
332 | buttonOffsetX += this.buttons[ index ].outputWidth + this.separatorWidth + justifyWidth ;
|
333 | } ) ;
|
334 |
|
335 |
|
336 |
|
337 |
|
338 | } ;
|
339 |
|
340 |
|
341 |
|
342 | RowMenu.prototype.preDrawSelf = function() {
|
343 |
|
344 | this.outputDst.put( { x: this.outputX , y: this.outputY , attr: this.backgroundAttr } , ' '.repeat( this.outputWidth ) ) ;
|
345 |
|
346 | if ( this.separator ) {
|
347 | let index , button , nextButton ;
|
348 | for ( index = 0 ; index < this.buttons.length - 1 ; index ++ ) {
|
349 | button = this.buttons[ index ] ;
|
350 | nextButton = this.buttons[ index + 1 ] ;
|
351 | this.outputDst.put( {
|
352 | x: Math.round( button.outputX + button.outputWidth + nextButton.outputX ) / 2 ,
|
353 | y: this.outputY ,
|
354 | attr: this.separatorAttr
|
355 | } ,
|
356 | this.separator
|
357 | ) ;
|
358 |
|
359 | }
|
360 |
|
361 | }
|
362 | } ;
|
363 |
|