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