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 |
|
32 |
|
33 | const Element = require( './Element.js' ) ;
|
34 | const Container = require( './Container.js' ) ;
|
35 | const boxesChars = require( '../spChars.js' ).box ;
|
36 |
|
37 |
|
38 |
|
39 | function Layout( options ) {
|
40 |
|
41 | options = ! options ? {} : options.internal ? options : Object.create( options ) ;
|
42 | options.internal = true ;
|
43 |
|
44 | Element.call( this , options ) ;
|
45 |
|
46 | this.layoutDef = options.layout ;
|
47 | this.computed = {} ;
|
48 | this.boxesContainer = {} ;
|
49 | this.boxChars = boxesChars.light ;
|
50 |
|
51 | if ( options.boxChars ) {
|
52 | if ( typeof options.boxChars === 'object' ) {
|
53 | this.boxChars = options.boxChars ;
|
54 | }
|
55 | else if ( typeof options.boxChars === 'string' && boxesChars[ options.boxChars ] ) {
|
56 | this.boxChars = boxesChars[ options.boxChars ] ;
|
57 | }
|
58 | }
|
59 |
|
60 |
|
61 | if ( this.elementType === 'Layout' && ! options.noDraw ) { this.draw() ; }
|
62 | }
|
63 |
|
64 | module.exports = Layout ;
|
65 |
|
66 | Layout.prototype = Object.create( Element.prototype ) ;
|
67 | Layout.prototype.constructor = Layout ;
|
68 | Layout.prototype.elementType = 'Layout' ;
|
69 |
|
70 |
|
71 |
|
72 | Layout.prototype.destroy = function( isSubDestroy ) {
|
73 | Element.prototype.destroy.call( this , isSubDestroy ) ;
|
74 | } ;
|
75 |
|
76 |
|
77 |
|
78 | Layout.prototype.preDrawSelf = function() {
|
79 | var y , tees = {} ;
|
80 |
|
81 | this.computeBoundingBoxes() ;
|
82 |
|
83 |
|
84 | this.outputDst.put(
|
85 | { x: this.computed.xmin , y: this.computed.ymin } ,
|
86 | this.boxChars.topLeft + this.boxChars.horizontal.repeat( this.computed.dx - 1 ) + this.boxChars.topRight
|
87 | ) ;
|
88 |
|
89 |
|
90 | this.outputDst.put(
|
91 | { x: this.computed.xmin , y: this.computed.ymax } ,
|
92 | this.boxChars.bottomLeft + this.boxChars.horizontal.repeat( this.computed.dx - 1 ) + this.boxChars.bottomRight
|
93 | ) ;
|
94 |
|
95 |
|
96 | for ( y = this.computed.ymin + 1 ; y < this.computed.ymax ; y ++ ) {
|
97 | this.outputDst.put( { x: this.computed.xmin , y: y } , this.boxChars.vertical ) ;
|
98 | this.outputDst.put( { x: this.computed.xmax , y: y } , this.boxChars.vertical ) ;
|
99 | }
|
100 |
|
101 | this.drawRecursive( this.computed , tees ) ;
|
102 | } ;
|
103 |
|
104 |
|
105 |
|
106 | Layout.prototype.computeBoundingBoxes = function() {
|
107 | var computed = this.computed = {} ;
|
108 |
|
109 | var layoutDef = this.layoutDef ;
|
110 |
|
111 | var parent = {
|
112 | width_: this.outputDst.width ,
|
113 | height_: this.outputDst.height ,
|
114 | dx_: this.outputDst.width - 1 ,
|
115 | dy_: this.outputDst.height - 1 ,
|
116 | xmin_: 0 ,
|
117 | ymin_: 0
|
118 | } ;
|
119 |
|
120 | var inProgress = {
|
121 | offsetX: ( this.layoutDef.x ) || 0 ,
|
122 | offsetY: ( this.layoutDef.y ) || 0 ,
|
123 | remainingDx: parent.dx_ ,
|
124 | remainingDy: parent.dy_
|
125 | } ;
|
126 |
|
127 | this.computeBoundingBoxes_( layoutDef , computed , parent , inProgress ) ;
|
128 | } ;
|
129 |
|
130 |
|
131 |
|
132 | Layout.prototype.computeBoundingBoxes_ = function( layoutDef , computed , parent , inProgress ) {
|
133 | var i , nextInProgress , hasChild = false ;
|
134 |
|
135 |
|
136 |
|
137 | this.computeDxDy( layoutDef , computed , parent , inProgress ) ;
|
138 |
|
139 |
|
140 |
|
141 | computed.xmin_ = parent.xmin_ + inProgress.offsetX ;
|
142 | computed.xmax_ = computed.xmin_ + computed.dx_ ;
|
143 | computed.ymin_ = parent.ymin_ + inProgress.offsetY ;
|
144 | computed.ymax_ = computed.ymin_ + computed.dy_ ;
|
145 |
|
146 |
|
147 |
|
148 |
|
149 | if ( computed.xmax_ > parent.xmax_ ) {
|
150 | computed.xmax_ = parent.xmax_ ;
|
151 | computed.dx_ = computed.xmax_ - computed.xmin_ ;
|
152 | }
|
153 |
|
154 | if ( computed.ymax_ > parent.ymax_ ) {
|
155 | computed.ymax_ = parent.ymax_ ;
|
156 | computed.dy_ = computed.ymax_ - computed.ymin_ ;
|
157 | }
|
158 |
|
159 |
|
160 | computed.width_ = computed.dx_ + 1 ;
|
161 | computed.height_ = computed.dy_ + 1 ;
|
162 |
|
163 | computed.columns = [] ;
|
164 | computed.rows = [] ;
|
165 |
|
166 |
|
167 |
|
168 | nextInProgress = {
|
169 | offsetX: 0 ,
|
170 | offsetY: 0 ,
|
171 | remainingDx: computed.dx_ ,
|
172 | remainingDy: computed.dy_ ,
|
173 | autoDxCount: 0 ,
|
174 | autoDyCount: 0
|
175 | } ;
|
176 |
|
177 | if ( layoutDef.columns && layoutDef.columns.length ) {
|
178 |
|
179 | for ( i = 0 ; i < layoutDef.columns.length ; i ++ ) {
|
180 | computed.columns[ i ] = {} ;
|
181 | this.computeDxDy( layoutDef.columns[ i ] , computed.columns[ i ] , computed , nextInProgress , true ) ;
|
182 |
|
183 | if ( computed.columns[ i ].dx_ !== undefined ) { nextInProgress.remainingDx -= computed.columns[ i ].dx_ ; }
|
184 | else { nextInProgress.autoDxCount ++ ; }
|
185 | }
|
186 |
|
187 | for ( i = 0 ; i < layoutDef.columns.length ; i ++ ) {
|
188 | this.computeBoundingBoxes_( layoutDef.columns[ i ] , computed.columns[ i ] , computed , nextInProgress ) ;
|
189 | nextInProgress.offsetX = computed.columns[ i ].xmax_ - computed.xmin_ ;
|
190 | }
|
191 |
|
192 | hasChild = true ;
|
193 | }
|
194 | else if ( layoutDef.rows && layoutDef.rows.length ) {
|
195 |
|
196 | for ( i = 0 ; i < layoutDef.rows.length ; i ++ ) {
|
197 | computed.rows[ i ] = {} ;
|
198 | this.computeDxDy( layoutDef.rows[ i ] , computed.rows[ i ] , computed , nextInProgress , true ) ;
|
199 |
|
200 | if ( computed.rows[ i ].dy_ !== undefined ) { nextInProgress.remainingDy -= computed.rows[ i ].dy_ ; }
|
201 | else { nextInProgress.autoDyCount ++ ; }
|
202 | }
|
203 |
|
204 | for ( i = 0 ; i < layoutDef.rows.length ; i ++ ) {
|
205 | this.computeBoundingBoxes_( layoutDef.rows[ i ] , computed.rows[ i ] , computed , nextInProgress ) ;
|
206 | nextInProgress.offsetY = computed.rows[ i ].ymax_ - computed.ymin_ ;
|
207 | }
|
208 |
|
209 | hasChild = true ;
|
210 | }
|
211 |
|
212 | computed.width_ = computed.dx_ + 1 ;
|
213 | computed.height_ = computed.dy_ + 1 ;
|
214 |
|
215 | this.round( computed ) ;
|
216 |
|
217 |
|
218 |
|
219 | if ( ! hasChild ) {
|
220 | if ( this.boxesContainer[ layoutDef.id ] ) {
|
221 | if ( this.boxesContainer[ layoutDef.id ].width !== computed.width - 2 || this.boxesContainer[ layoutDef.id ].height !== computed.height - 2 ) {
|
222 | this.boxesContainer[ layoutDef.id ].resize( {
|
223 | x: 0 ,
|
224 | y: 0 ,
|
225 | width: computed.width - 2 ,
|
226 | height: computed.height - 2
|
227 | } ) ;
|
228 | }
|
229 |
|
230 | this.boxesContainer[ layoutDef.id ].outputX = computed.xmin + 1 ;
|
231 | this.boxesContainer[ layoutDef.id ].outputY = computed.ymin + 1 ;
|
232 |
|
233 | this.boxesContainer[ layoutDef.id ].moveTo(
|
234 | this.boxesContainer[ layoutDef.id ].outputX ,
|
235 | this.boxesContainer[ layoutDef.id ].outputY
|
236 | ) ;
|
237 | }
|
238 | else {
|
239 | var container = new Container( {
|
240 | internal: true ,
|
241 | id: layoutDef.id ,
|
242 | parent: this ,
|
243 | outputDst: this.outputDst ,
|
244 | outputX: computed.xmin + 1 ,
|
245 | outputY: computed.ymin + 1 ,
|
246 | outputWidth: computed.width - 2 ,
|
247 | outputHeight: computed.height - 2
|
248 | } ) ;
|
249 |
|
250 | layoutDef.id = container.id ;
|
251 | this.boxesContainer[ layoutDef.id ] = container ;
|
252 | }
|
253 | }
|
254 | } ;
|
255 |
|
256 |
|
257 |
|
258 | Layout.prototype.computeDxDy = function( layoutDef , computed , parent , inProgress , firstPass ) {
|
259 |
|
260 |
|
261 |
|
262 | if ( firstPass || computed.dx_ === undefined ) {
|
263 | if ( layoutDef.width !== undefined ) {
|
264 | computed.dx_ = Math.max( 0 , Math.min( parent.dx_ , layoutDef.width - 1 ) ) ;
|
265 | }
|
266 | else if ( layoutDef.widthPercent !== undefined ) {
|
267 | computed.dx_ = Math.max( 0 , Math.min( parent.dx_ , parent.dx_ * layoutDef.widthPercent / 100 ) ) ;
|
268 | }
|
269 | else if ( ! firstPass ) {
|
270 |
|
271 | computed.dx_ = Math.max( 0 , inProgress.remainingDx / ( inProgress.autoDxCount || 1 ) ) ;
|
272 |
|
273 | }
|
274 | }
|
275 |
|
276 |
|
277 | if ( firstPass || computed.dy_ === undefined ) {
|
278 | if ( layoutDef.height !== undefined ) {
|
279 | computed.dy_ = Math.max( 0 , Math.min( parent.dy_ , layoutDef.height - 1 ) ) ;
|
280 | }
|
281 | else if ( layoutDef.heightPercent !== undefined ) {
|
282 | computed.dy_ = Math.max( 0 , Math.min( parent.dy_ , parent.dy_ * layoutDef.heightPercent / 100 ) ) ;
|
283 | }
|
284 | else if ( ! firstPass ) {
|
285 | computed.dy_ = Math.max( 0 , inProgress.remainingDy / ( inProgress.autoDyCount || 1 ) ) ;
|
286 | }
|
287 | }
|
288 | } ;
|
289 |
|
290 |
|
291 |
|
292 | Layout.prototype.round = function( computed ) {
|
293 | computed.xmin = Math.round( computed.xmin_ ) ;
|
294 | computed.xmax = Math.round( computed.xmax_ ) ;
|
295 | computed.ymin = Math.round( computed.ymin_ ) ;
|
296 | computed.ymax = Math.round( computed.ymax_ ) ;
|
297 |
|
298 | computed.dx = computed.xmax - computed.xmin ;
|
299 | computed.dy = computed.ymax - computed.ymin ;
|
300 | computed.width = computed.dx + 1 ;
|
301 | computed.height = computed.dy + 1 ;
|
302 | } ;
|
303 |
|
304 |
|
305 |
|
306 | Layout.prototype.drawRecursive = function( computed , tees ) {
|
307 | var i ;
|
308 |
|
309 | if ( computed.columns.length ) {
|
310 | for ( i = 0 ; i < computed.columns.length ; i ++ ) {
|
311 | this.drawColumn( computed.columns[ i ] , tees , i === computed.columns.length - 1 ) ;
|
312 | }
|
313 | }
|
314 | else if ( computed.rows.length ) {
|
315 | for ( i = 0 ; i < computed.rows.length ; i ++ ) {
|
316 | this.drawRow( computed.rows[ i ] , tees , i === computed.rows.length - 1 ) ;
|
317 | }
|
318 | }
|
319 | } ;
|
320 |
|
321 |
|
322 |
|
323 | Layout.prototype.drawColumn = function( computed , tees , last ) {
|
324 | var y ;
|
325 |
|
326 | if ( ! last ) {
|
327 |
|
328 | this.drawTee( computed.xmax , computed.ymin , 'top' , tees ) ;
|
329 | this.drawTee( computed.xmax , computed.ymax , 'bottom' , tees ) ;
|
330 |
|
331 |
|
332 | for ( y = computed.ymin + 1 ; y < computed.ymax ; y ++ ) {
|
333 | this.outputDst.put( { x: computed.xmax , y: y } , this.boxChars.vertical ) ;
|
334 | }
|
335 | }
|
336 |
|
337 | this.drawRecursive( computed , tees ) ;
|
338 | } ;
|
339 |
|
340 |
|
341 |
|
342 | Layout.prototype.drawTee = function( x , y , type , tees ) {
|
343 | var key = x + ':' + y ;
|
344 |
|
345 | if ( ! tees[ key ] ) {
|
346 | this.outputDst.put( { x: x , y: y } , this.boxChars[ type + 'Tee' ] ) ;
|
347 | tees[ key ] = type ;
|
348 | }
|
349 | else if ( tees[ key ] !== type ) {
|
350 | this.outputDst.put( { x: x , y: y } , this.boxChars.cross ) ;
|
351 | }
|
352 | } ;
|
353 |
|
354 |
|
355 |
|
356 | Layout.prototype.drawRow = function( computed , tees , last ) {
|
357 | if ( ! last ) {
|
358 |
|
359 | this.drawTee( computed.xmin , computed.ymax , 'left' , tees ) ;
|
360 | this.drawTee( computed.xmax , computed.ymax , 'right' , tees ) ;
|
361 |
|
362 |
|
363 | this.outputDst.put( { x: computed.xmin + 1 , y: computed.ymax } , this.boxChars.horizontal.repeat( computed.dx - 1 ) ) ;
|
364 | }
|
365 |
|
366 | this.drawRecursive( computed , tees ) ;
|
367 | } ;
|
368 |
|