UNPKG

39 kBJavaScriptView Raw
1'use strict'
2
3var hasOwn = require('hasown')
4var copyUtils = require('copy-utils')
5var copyList = copyUtils.copyList
6var F = require('functionally')
7var isObject = require('i-s').object
8
9/**
10 * @class Region
11 *
12 * # z.region
13 *
14 * The Region class is an abstraction that allows the developer to refer to rectangles on the screen,
15 * and move them around, make diffs and unions, detect intersections, compute areas, etc.
16 *
17 * ## Creating a region
18 *
19 *
20 *
21 * var region = require('region')({
22 * top : 10,
23 * left : 10,
24 * bottom: 100,
25 * right : 100
26 * })
27 * //this region is a square, 90x90, starting from (10,10) to (100,100)
28 *
29 * var second = require('region')({ top: 10, left: 100, right: 200, bottom: 60})
30 * var union = region.getUnion(second)
31 *
32 * //the "union" region is a union between "region" and "second"
33 *
34 * ## Element regions
35 *
36 * The {@link Element} class has {@link Element#getRegion} and {@link Element#setRegion} methods, so you can easily
37 * retrieve and set element size and position.
38 *
39 * var bodyElement = Element.select('body'),
40 * bodyRegion = bodyElement.getRegion()
41 *
42 * bodyRegion.setWidth(100).setHeight(200)
43 *
44 * //this makes the body just 100px in width and 200px in height
45 * bodyElement.setRegion(bodyRegion)
46 *
47 * //you can even bind an element to a region
48 *
49 * var reg = bodyElement.getRegion({bound: true})
50 *
51 * reg.setWidth(200) //also sets the width of the bodyElement
52 *
53 */
54
55var classy = require('classy')
56var EventEmitter = require('zemitter').mixin
57
58var MAX = Math.max,
59 MIN = Math.min,
60 POINT_POSITIONS = {
61 cy: 'YCenter',
62 cx: 'XCenter',
63 t : 'Top',
64 tc: 'TopCenter',
65 tl: 'TopLeft',
66 tr: 'TopRight',
67 b : 'Bottom',
68 bc: 'BottomCenter',
69 bl: 'BottomLeft',
70 br: 'BottomRight',
71 l : 'Left',
72 lc: 'LeftCenter',
73 r : 'Right',
74 rc: 'RightCenter',
75 c : 'Center'
76 }
77
78var REGION = classy.define({
79
80 forceInstance: true,
81
82 mixins: [
83 EventEmitter
84 // ,
85 // 'z.alignable'
86 ],
87
88 statics: {
89 init: function(){
90 var exportAsNonStatic = {
91 getIntersection : true,
92 getIntersectionArea : true,
93 getIntersectionHeight: true,
94 getIntersectionWidth : true,
95 getUnion : true
96 }
97 var thisProto = this.prototype
98 var newName
99
100 var exportHasOwn = hasOwn(exportAsNonStatic)
101 var methodName
102
103 for (methodName in exportAsNonStatic) if (exportHasOwn(methodName)) {
104 newName = exportAsNonStatic[methodName]
105 if (typeof newName != 'string'){
106 newName = methodName
107 }
108
109 (function(proto, methodName, protoMethodName){
110
111 proto[methodName] = function(region){
112 //<debug>
113 if (!this.$ownClass[protoMethodName]){
114 console.warn('cannot find method ', protoMethodName,' on ', this.$ownClass)
115 }
116 //</debug>
117 return this.$ownClass[protoMethodName](this, region)
118 }
119
120 })(thisProto, newName, methodName)
121 }
122 },
123
124 /**
125 * @static
126 * Returns true if the given region is valid, false otherwise.
127 * @param {Region} region The region to check
128 * @return {Boolean} True, if the region is valid, false otherwise.
129 * A region is valid if
130 * * left <= right &&
131 * * top <= bottom
132 */
133 validate: function(region){
134
135 var isValid = true
136
137 if (region.right < region.left){
138 isValid = false
139 region.right = region.left
140 }
141
142 if (region.bottom < region.top){
143 isValid = false
144 region.bottom = region.top
145 }
146
147 return isValid
148 },
149
150 /**
151 * Returns the region corresponding to the documentElement
152 * @return {Region} The region corresponding to the documentElement. This region is the maximum region visible on the screen.
153 */
154 getDocRegion: function(){
155 return REGION.fromDOM(document.documentElement)
156 },
157
158 fromDOM: function(dom){
159 var rect = dom.getBoundingClientRect()
160 var docElem = document.documentElement
161
162 rect.top += win.pageYOffset - docElem.clientTop
163 rect.left+= win.pageXOffset - docElem.clientLeft
164
165 return new REGION(rect)
166 },
167
168 /**
169 * @static
170 * Returns a region that is the intersection of the given two regions
171 * @param {Region} first The first region
172 * @param {Region} second The second region
173 * @return {Region/Boolean} The intersection region or false if no intersection found
174 */
175 getIntersection: function(first, second){
176
177 var area = this.getIntersectionArea(first, second)
178
179 if (area){
180 return new REGION(area)
181 }
182
183 return false
184 },
185
186 getIntersectionWidth: function(first, second){
187 var minRight = MIN(first.right, second.right)
188 var maxLeft = MAX(first.left, second.left)
189
190 if (maxLeft < minRight){
191 return minRight - maxLeft
192 }
193
194 return 0
195 },
196
197 getIntersectionHeight: function(first, second){
198 var maxTop = MAX(first.top, second.top)
199 var minBottom = MIN(first.bottom,second.bottom)
200
201 if (maxTop < minBottom){
202 return minBottom - maxTop
203 }
204
205 return 0
206 },
207
208 getIntersectionArea: function(first, second){
209 var maxTop = MAX(first.top, second.top)
210 var minRight = MIN(first.right, second.right)
211 var minBottom = MIN(first.bottom,second.bottom)
212 var maxLeft = MAX(first.left, second.left)
213
214 if (
215 maxTop < minBottom &&
216 maxLeft < minRight
217 ){
218 return {
219 top : maxTop,
220 right : minRight,
221 bottom : minBottom,
222 left : maxLeft,
223
224 width : minRight - maxLeft,
225 height : minBottom - maxTop
226 }
227 }
228
229 return false
230 },
231
232 /**
233 * @static
234 * Returns a region that is the union of the given two regions
235 * @param {Region} first The first region
236 * @param {Region} second The second region
237 * @return {Region} The union region. The smallest region that contains both given regions.
238 */
239 getUnion: function(first, second){
240 var top = MIN(first.top, second.top)
241 var right = MAX(first.right, second.right)
242 var bottom = MAX(first.bottom,second.bottom)
243 var left = MIN(first.left, second.left)
244
245 return new REGION(top, right, bottom, left)
246 },
247
248 /**
249 * @static
250 * Returns a region. If the reg argument is a region, returns it, otherwise return a new region built from the reg object.
251 *
252 * @param {Region} reg A region or an object with either top, left, bottom, right or
253 * with top, left, width, height
254 * @return {Region} A region
255 */
256 getRegion: function(reg){
257 return reg instanceof REGION?
258 reg :
259 new REGION(reg)
260 },
261
262 /**
263 * @static
264 * Aligns the source region to the target region, so as to correspond to the given alignment.
265 *
266 * NOTE that this method makes changes on the sourceRegion in order for it to be aligned as specified.
267 *
268 * @param {Region} sourceRegion
269 * @param {Region} targetRegion
270 *
271 * @param {String} align A string with 2 valid align positions, eg: 'tr-bl'.
272 * For valid positions, see {@link Region#getPoint}
273 *
274 * Having 2 regions, we need to be able to align them as we wish:
275 *
276 * for example, if we have
277 *
278 * source target
279 * ________________
280 * ____
281 * | | ________
282 * |____| | |
283 * | |
284 * |________|
285 *
286 * and we align 't-t', we get:
287 *
288 * source target
289 * _________________
290 *
291 * ____ ________
292 * | | | |
293 * |____| | |
294 * |________|
295 *
296 * In this case, the source was moved down to be aligned to the top of the target
297 *
298 *
299 * and if we align 'tc-tc' we get
300 *
301 * source target
302 * __________________
303 *
304 * ________
305 * | | | |
306 * | |____| |
307 * |________|
308 *
309 * Since the source was moved to have the top-center point to be the same with target top-center
310 *
311 *
312 *
313 * @return {RegionClass} The Region class
314 */
315 // align: function(sourceRegion, targetRegion, align){
316 // align = (align || 'c-c').split('-')
317
318 // //<debug>
319 // if (align.length != 2){
320 // console.warn('Incorrect region alignment! The align parameter need to be in the form \'br-c\', that is, a - separated string!', align)
321 // }
322 // //</debug>
323
324 // this.alignToPoint(sourceRegion, targetRegion.getPoint(align[1]), align[0])
325
326 // return REGION
327 // },
328
329 /**
330 * Modifies the given region to be aligned to the point, as specified by anchor
331 *
332 * @param {Region} region The region to align to the point
333 * @param {Object} point The point to be used as a reference
334 * @param {Number} point.x
335 * @param {Number} point.y
336 * @param {String} anchor The position where to anchor the region to the point. See {@link #getPoint} for available options/
337 * @return {RegionClass} The Region class
338 */
339 // alignToPoint: function(region, point, anchor){
340 // var sourcePoint = region.getPoint(anchor),
341 // count = 0,
342 // shiftObj = {}
343
344 // if (
345 // sourcePoint.x != null &&
346 // point.x != null
347 // ){
348
349 // count++
350 // shiftObj.left = point.x - sourcePoint.x
351 // }
352
353 // if (
354 // sourcePoint.y != null &&
355 // point.y != null
356 // ){
357 // count++
358 // shiftObj.top = point.y - sourcePoint.y
359 // }
360
361 // if (count){
362
363 // region.shift(shiftObj)
364
365 // }
366
367 // return REGION
368 // },
369
370 /**
371 * Creates a region that corresponds to a point.
372 *
373 * @param {Object} xy The point
374 * @param {Number} xy.x
375 * @param {Number} xy.y
376 *
377 * @return {Region} The new region, with top==xy.y, bottom = xy.y and left==xy.x, right==xy.x
378 */
379 fromPoint: function(xy){
380 return new REGION({
381 top : xy.y,
382 bottom : xy.y,
383 left : xy.x,
384 right : xy.x
385 })
386 }
387 },
388
389 /**
390 * @cfg {Boolean} emitChangeEvents If this is set to true, the region
391 * will emit 'changesize' and 'changeposition' whenever the size or the position changs
392 */
393 emitChangeEvents: false,
394
395 /**
396 * @cfg {Number} changeEventsBuffer If {@link #emitChangeEvents} is true, the change events will be emitted in a buffered manner,
397 * if this value is greater than 0
398 */
399 changeEventsBuffer: 0,
400
401 /**
402 * Returns this region, or a clone of this region
403 * @param {Boolean} [clone] If true, this method will return a clone of this region
404 * @return {Region} This region, or a clone of this
405 */
406 getRegion: function(clone){
407 return clone?
408 this.clone():
409 this
410 },
411
412 /**
413 * Sets the properties of this region to those of the given region
414 * @param {Region/Object} reg The region or object to use for setting properties of this region
415 * @return {Region} this
416 */
417 setRegion: function(reg){
418
419 if (reg instanceof REGION){
420 this.set(reg.get())
421 } else {
422 this.set(reg)
423 }
424
425 return this
426 },
427
428 /**
429 * Returns true if this region is valid, false otherwise
430 *
431 * @param {Region} region The region to check
432 * @return {Boolean} True, if the region is valid, false otherwise.
433 * A region is valid if
434 * * left <= right &&
435 * * top <= bottom
436 */
437 validate: function(){
438 return REGION.validate(this)
439 },
440
441 _before: function(){
442 if (this.emitChangeEvents){
443 return copyList(this, {}, ['left','top','bottom','right'])
444 }
445 },
446
447 _after: function(before){
448 if (this.emitChangeEvents){
449
450 if(this.top != before.top || this.left != before.left) {
451 this.emitPositionChange()
452 }
453
454 if(this.right != before.right || this.bottom != before.bottom) {
455 this.emitSizeChange()
456 }
457 }
458 },
459
460 notifyPositionChange: function(){
461 this.emit('changeposition', this)
462 },
463
464 emitPositionChange: function(){
465 if (this.changeEventsBuffer){
466 if (!this.emitPositionChangeBuffered){
467 this.emitPositionChangeBuffered = F.buffer(this.notifyPositionChange, changeEventsBuffer)
468 }
469 this.emitPositionChangeBuffered()
470 }
471
472 this.notifyPositionChange()
473 },
474
475 notifySizeChange: function(){
476 this.emit('changesize', this)
477 },
478
479 emitSizeChange: function(){
480 if (this.changeEventsBuffer){
481 if (!this.emitSizeChangeBuffered){
482 this.emitSizeChangeBuffered = F.buffer(this.notifySizeChange, changeEventsBuffer)
483 }
484 this.emitSizeChangeBuffered()
485 }
486
487 this.notifySizeChange()
488 },
489
490 /**
491 * Add the given amounts to each specified side. Example
492 *
493 * region.add({
494 * top: 50, //add 50 px to the top side
495 * bottom: -100 //substract 100 px from the bottom side
496 * })
497 *
498 * @param {Object} directions
499 * @param {Number} [directions.top]
500 * @param {Number} [directions.left]
501 * @param {Number} [directions.bottom]
502 * @param {Number} [directions.right]
503 *
504 * @return {Region} this
505 */
506 add: function(directions){
507
508 var before = this._before()
509 var direction
510
511 for (direction in directions) if ( hasOwn(directions, direction) ) {
512 this[direction] += directions[direction]
513 }
514
515 this[0] = this.left
516 this[1] = this.top
517
518 this._after(before)
519
520 return this
521 },
522
523 /**
524 * The same as {@link #add}, but substracts the given values
525 * @param {Object} directions
526 * @param {Number} [directions.top]
527 * @param {Number} [directions.left]
528 * @param {Number} [directions.bottom]
529 * @param {Number} [directions.right]
530 *
531 * @return {Region} this
532 */
533 substract: function(directions){
534
535 var before = this._before()
536 var direction
537
538 for (direction in directions) if (hasOwn(directions, direction) ) {
539 this[direction] -= directions[direction]
540 }
541
542 this[0] = this.left
543 this[1] = this.top
544
545 this._after(before)
546
547 return this
548 },
549
550 /**
551 * Retrieves the size of the region.
552 * @return {Object} An object with {width, height}, corresponding to the width and height of the region
553 */
554 getSize: function(){
555 return {
556 width : this.getWidth(),
557 height : this.getHeight()
558 }
559 },
560
561 /**
562 * Move the region to the given position and keeps the region width and height.
563 *
564 * @param {Object} position An object with {top, left} properties. The values in {top,left} are used to move the region by the given amounts.
565 * @param {Number} [position.left]
566 * @param {Number} [position.top]
567 *
568 * @return {Region} this
569 */
570 setPosition: function(position){
571 var width = this.getWidth(),
572 height = this.getHeight()
573
574 if (position.left){
575 position.right = position.left + width
576 }
577
578 if (position.top){
579 position.bottom = position.top + height
580 }
581
582 return this.set(position)
583 },
584
585 /**
586 * Sets both the height and the width of this region to the given size.
587 *
588 * @param {Number} size The new size for the region
589 * @return {Region} this
590 */
591 setSize: function(size){
592 if (size.height && size.width){
593 return this.set({
594 right : this.left + size.width,
595 bottom : this.top + size.height
596 })
597 }
598
599 if (size.width){
600 this.setWidth(size.width)
601 }
602
603 if (size.height){
604 this.setHeight(size.height)
605 }
606
607 return this
608 },
609
610 /**
611 * @chainable
612 *
613 * Sets the width of this region
614 * @param {Number} width The new width for this region
615 * @return {Region} this
616 */
617 setWidth: function(width){
618 return this.set({
619 right: this.left + width
620 })
621 },
622
623 /**
624 * @chainable
625 *
626 * Sets the height of this region
627 * @param {Number} height The new height for this region
628 * @return {Region} this
629 */
630 setHeight: function(height){
631 return this.set({
632 bottom: this.top + height
633 })
634 },
635
636 /**
637 * Sets the given properties on this region
638 *
639 * @param {Object} directions an object containing top, left, and EITHER bottom, right OR width, height
640 * @param {Number} [directions.top]
641 * @param {Number} [directions.left]
642 *
643 * @param {Number} [directions.bottom]
644 * @param {Number} [directions.right]
645 *
646 * @param {Number} [directions.width]
647 * @param {Number} [directions.height]
648 *
649 *
650 * @return {Region} this
651 */
652 set: function(directions){
653 var before = this._before()
654
655 copyList(directions, this, ['left','top','bottom','right'])
656
657 if (directions.bottom == null && directions.height != null){
658 this.bottom = this.top + directions.height
659 }
660 if (directions.right == null && directions.width != null){
661 this.right = this.left + directions.width
662 }
663
664 this[0] = this.left
665 this[1] = this.top
666
667 this._after(before)
668
669 return this
670 },
671
672 /**
673 * Retrieves the given property from this region. If no property is given, return an object
674 * with {left, top, right, bottom}
675 *
676 * @param {String} [dir] the property to retrieve from this region
677 * @return {Number/Object}
678 */
679 get: function(dir){
680 return dir? this[dir]:
681 copyList(this, {}, ['left','right','top','bottom'])
682 },
683
684 /**
685 * Shifts this region to either top, or left or both.
686 * Shift is similar to {@link #add} by the fact that it adds the given dimensions to top/left sides, but also adds the given dimensions
687 * to bottom and right
688 *
689 * @param {Object} directions
690 * @param {Number} [directions.top]
691 * @param {Number} [directions.left]
692 *
693 * @return {Region} this
694 */
695 shift: function(directions){
696
697 var before = this._before()
698
699 if (directions.top){
700 this.top += directions.top
701 this.bottom += directions.top
702 }
703
704 if (directions.left){
705 this.left += directions.left
706 this.right += directions.left
707 }
708
709 this[0] = this.left
710 this[1] = this.top
711
712 this._after(before)
713
714 return this
715 },
716
717 /**
718 * Same as {@link #shift}, but substracts the given values
719 * @chainable
720 *
721 * @param {Object} directions
722 * @param {Number} [directions.top]
723 * @param {Number} [directions.left]
724 *
725 * @return {Region} this
726 */
727 unshift: function(directions){
728
729 if (directions.top){
730 directions.top *= -1
731 }
732
733 if (directions.left){
734 directions.left *= -1
735 }
736
737 return this.shift(directions)
738 },
739
740 /**
741 * Compare this region and the given region. Return true if they have all the same size and position
742 * @param {Region} region The region to compare with
743 * @return {Boolean} True if this and region have same size and position
744 */
745 equals: function(region){
746 return this.equalsPosition(region) && this.equalsSize(region)
747 },
748
749 /**
750 * Returns true if this region has the same bottom,right properties as the given region
751 * @param {Region/Object} size The region to compare against
752 * @return {Boolean} true if this region is the same size as the given size
753 */
754 equalsSize: function(size){
755 var isInstance = size instanceof REGION
756
757 var s = {
758 width: size.width == null && isInstance?
759 size.getWidth():
760 size.width,
761
762 height: size.height == null && isInstance?
763 size.getHeight():
764 size.height
765 }
766 return this.getWidth() == s.width && this.getHeight() == s.height
767 },
768
769 /**
770 * Returns true if this region has the same top,left properties as the given region
771 * @param {Region} region The region to compare against
772 * @return {Boolean} true if this.top == region.top and this.left == region.left
773 */
774 equalsPosition: function(region){
775 return this.top == region.top && this.left == region.left
776 },
777
778 /**
779 * Adds the given ammount to the left side of this region
780 * @param {Number} left The ammount to add
781 * @return {Region} this
782 */
783 addLeft: function(left){
784 var before = this._before()
785
786 this.left = this[0] = this.left + left
787
788 this._after(before)
789
790 return this
791 },
792
793 /**
794 * Adds the given ammount to the top side of this region
795 * @param {Number} top The ammount to add
796 * @return {Region} this
797 */
798 addTop: function(top){
799 var before = this._before()
800
801 this.top = this[1] = this.top + top
802
803 this._after(before)
804
805 return this
806 },
807
808 /**
809 * Adds the given ammount to the bottom side of this region
810 * @param {Number} bottom The ammount to add
811 * @return {Region} this
812 */
813 addBottom: function(bottom){
814 var before = this._before()
815
816 this.bottom += bottom
817
818 this._after(before)
819
820 return this
821 },
822
823 /**
824 * Adds the given ammount to the right side of this region
825 * @param {Number} right The ammount to add
826 * @return {Region} this
827 */
828 addRight: function(right){
829 var before = this._before()
830
831 this.right += right
832
833 this._after(before)
834
835 return this
836 },
837
838 /**
839 * Minimize the top side.
840 * @return {Region} this
841 */
842 minTop: function(){
843 return this.expand({top: 1})
844 },
845 /**
846 * Minimize the bottom side.
847 * @return {Region} this
848 */
849 maxBottom: function(){
850 return this.expand({bottom: 1})
851 },
852 /**
853 * Minimize the left side.
854 * @return {Region} this
855 */
856 minLeft: function(){
857 return this.expand({left: 1})
858 },
859 /**
860 * Maximize the right side.
861 * @return {Region} this
862 */
863 maxRight: function(){
864 return this.expand({right: 1})
865 },
866
867 /**
868 * Expands this region to the dimensions of the given region, or the document region, if no region is expanded.
869 * But only expand the given sides (any of the four can be expanded).
870 *
871 * @param {Object} directions
872 * @param {Boolean} [directions.top]
873 * @param {Boolean} [directions.bottom]
874 * @param {Boolean} [directions.left]
875 * @param {Boolean} [directions.right]
876 *
877 * @param {Region} [region] the region to expand to, defaults to the document region
878 * @return {Region} this region
879 */
880 expand: function(directions, region){
881 var docRegion = region || REGION.getDocRegion(),
882 list = [],
883 direction,
884 before = this._before()
885
886 for (direction in directions) if ( hasOwn(directions, direction) ) {
887 list.push(direction)
888 }
889
890 copyList(docRegion, this, list)
891
892 this[0] = this.left
893 this[1] = this.top
894
895 this._after(before)
896
897 return this
898 },
899
900 /**
901 * Returns a clone of this region
902 * @return {Region} A new region, with the same position and dimension as this region
903 */
904 clone: function(){
905 return new REGION({
906 top : this.top,
907 left : this.left,
908 right : this.right,
909 bottom : this.bottom
910 })
911 },
912
913 /**
914 * Returns true if this region contains the given point
915 * @param {Number/Object} x the x coordinate of the point
916 * @param {Number} [y] the y coordinate of the point
917 *
918 * @return {Boolean} true if this region constains the given point, false otherwise
919 */
920 containsPoint: function(x, y){
921 if (arguments.length == 1){
922 y = x.y
923 x = x.x
924 }
925
926 return this.left <= x &&
927 x <= this.right &&
928 this.top <= y &&
929 y <= this.bottom
930 },
931
932 /**
933 *
934 * @param region
935 *
936 * @return {Boolean} true if this region contains the given region, false otherwise
937 */
938 containsRegion: function(region){
939 return this.containsPoint(region.left, region.top) &&
940 this.containsPoint(region.right, region.bottom)
941 },
942
943 /**
944 * Returns an object with the difference for {top, bottom} positions betwen this and the given region,
945 *
946 * See {@link #diff}
947 * @param {Region} region The region to use for diff
948 * @return {Object} {top,bottom}
949 */
950 diffHeight: function(region){
951 return this.diff(region, {top: true, bottom: true})
952 },
953
954 /**
955 * Returns an object with the difference for {left, right} positions betwen this and the given region,
956 *
957 * See {@link #diff}
958 * @param {Region} region The region to use for diff
959 * @return {Object} {left,right}
960 */
961 diffWidth: function(region){
962 return this.diff(region, {left: true, right: true})
963 },
964
965 /**
966 * Returns an object with the difference in sizes for the given directions, between this and region
967 *
968 * @param {Region} region The region to use for diff
969 * @param {Object} directions An object with the directions to diff. Can have any of the following keys:
970 * * left
971 * * right
972 * * top
973 * * bottom
974 *
975 * @return {Object} and object with the same keys as the directions object, but the values being the
976 * differences between this region and the given region
977 */
978 diff: function(region, directions){
979 var result = {},
980 dirName
981
982 for (dirName in directions) if ( hasOwn(directions, dirName) ) {
983 result[dirName] = this[dirName] - region[dirName]
984 }
985
986 return result
987 },
988
989 /**
990 * Returns the position, in {left,top} properties, of this region
991 *
992 * @return {Object} {left,top}
993 */
994 getPosition: function(){
995 return {
996 left: this.left,
997 top : this.top
998 }
999 },
1000
1001 /**
1002 * Returns the point at the given position from this region.
1003 *
1004 * @param {String} position Any of:
1005 *
1006 * * 'cx' - See {@link #getPointXCenter}
1007 * * 'cy' - See {@link #getPointYCenter}
1008 * * 'b' - See {@link #getPointBottom}
1009 * * 'bc' - See {@link #getPointBottomCenter}
1010 * * 'l' - See {@link #getPointLeft}
1011 * * 'lc' - See {@link #getPointLeftCenter}
1012 * * 't' - See {@link #getPointTop}
1013 * * 'tc' - See {@link #getPointTopCenter}
1014 * * 'r' - See {@link #getPointRight}
1015 * * 'rc' - See {@link #getPointRightCenter}
1016 * * 'c' - See {@link #getPointCenter}
1017 * * 'tl' - See {@link #getPointTopLeft}
1018 * * 'bl' - See {@link #getPointBottomLeft}
1019 * * 'br' - See {@link #getPointBottomRight}
1020 * * 'tr' - See {@link #getPointTopRight}
1021 *
1022 * @param {Boolean} asLeftTop
1023 *
1024 * @return {Object} either an object with {x,y} or {left,top} if asLeftTop is true
1025 */
1026 getPoint: function(position, asLeftTop){
1027
1028 //<debug>
1029 if (!POINT_POSITIONS[position]) {
1030 console.warn('The position ', position, ' could not be found! Available options are tl, bl, tr, br, l, r, t, b.');
1031 }
1032 //</debug>
1033
1034 var method = 'getPoint' + POINT_POSITIONS[position],
1035 result = this[method]()
1036
1037 if (asLeftTop){
1038 return {
1039 left : result.x,
1040 top : result.y
1041 }
1042 }
1043
1044 return result
1045 },
1046
1047 /**
1048 * Returns a point with x = null and y being the middle of the left region segment
1049 * @return {Object} {x,y}
1050 */
1051 getPointYCenter: function(){
1052 return { x: null, y: this.top + this.getHeight() / 2 }
1053 },
1054
1055 /**
1056 * Returns a point with y = null and x being the middle of the top region segment
1057 * @return {Object} {x,y}
1058 */
1059 getPointXCenter: function(){
1060 return { x: this.left + this.getWidth() / 2, y: null }
1061 },
1062
1063 /**
1064 * Returns a point with x = null and y the region top position on the y axis
1065 * @return {Object} {x,y}
1066 */
1067 getPointTop: function(){
1068 return { x: null, y: this.top }
1069 },
1070
1071 /**
1072 * Returns a point that is the middle point of the region top segment
1073 * @return {Object} {x,y}
1074 */
1075 getPointTopCenter: function(){
1076 return { x: this.left + this.getWidth() / 2, y: this.top }
1077 },
1078
1079 /**
1080 * Returns a point that is the top-left point of the region
1081 * @return {Object} {x,y}
1082 */
1083 getPointTopLeft: function(){
1084 return { x: this.left, y: this.top}
1085 },
1086
1087 /**
1088 * Returns a point that is the top-right point of the region
1089 * @return {Object} {x,y}
1090 */
1091 getPointTopRight: function(){
1092 return { x: this.right, y: this.top}
1093 },
1094
1095 /**
1096 * Returns a point with x = null and y the region bottom position on the y axis
1097 * @return {Object} {x,y}
1098 */
1099 getPointBottom: function(){
1100 return { x: null, y: this.bottom }
1101 },
1102
1103 /**
1104 * Returns a point that is the middle point of the region bottom segment
1105 * @return {Object} {x,y}
1106 */
1107 getPointBottomCenter: function(){
1108 return { x: this.left + this.getWidth() / 2, y: this.bottom }
1109 },
1110
1111 /**
1112 * Returns a point that is the bottom-left point of the region
1113 * @return {Object} {x,y}
1114 */
1115 getPointBottomLeft: function(){
1116 return { x: this.left, y: this.bottom}
1117 },
1118
1119 /**
1120 * Returns a point that is the bottom-right point of the region
1121 * @return {Object} {x,y}
1122 */
1123 getPointBottomRight: function(){
1124 return { x: this.right, y: this.bottom}
1125 },
1126
1127 /**
1128 * Returns a point with y = null and x the region left position on the x axis
1129 * @return {Object} {x,y}
1130 */
1131 getPointLeft: function(){
1132 return { x: this.left, y: null }
1133 },
1134
1135 /**
1136 * Returns a point that is the middle point of the region left segment
1137 * @return {Object} {x,y}
1138 */
1139 getPointLeftCenter: function(){
1140 return { x: this.left, y: this.top + this.getHeight() / 2 }
1141 },
1142
1143 /**
1144 * Returns a point with y = null and x the region right position on the x axis
1145 * @return {Object} {x,y}
1146 */
1147 getPointRight: function(){
1148 return { x: this.right, y: null }
1149 },
1150
1151 /**
1152 * Returns a point that is the middle point of the region right segment
1153 * @return {Object} {x,y}
1154 */
1155 getPointRightCenter: function(){
1156 return { x: this.right, y: this.top + this.getHeight() / 2 }
1157 },
1158
1159 /**
1160 * Returns a point that is the center of the region
1161 * @return {Object} {x,y}
1162 */
1163 getPointCenter: function(){
1164 return { x: this.left + this.getWidth() / 2, y: this.top + this.getHeight() / 2 }
1165 },
1166
1167 /**
1168 * @return {Number} returns the height of the region
1169 */
1170 getHeight: function(){
1171 return this.bottom - this.top
1172 },
1173
1174 /**
1175 * @return {Number} returns the width of the region
1176 */
1177 getWidth: function(){
1178 return this.right - this.left
1179 },
1180
1181 /**
1182 * @return {Number} returns the top property of the region
1183 */
1184 getTop: function(){
1185 return this.top
1186 },
1187
1188 /**
1189 * @return {Number} returns the left property of the region
1190 */
1191 getLeft: function(){
1192 return this.left
1193 },
1194
1195 /**
1196 * @return {Number} returns the bottom property of the region
1197 */
1198 getBottom: function(){
1199 return this.bottom
1200 },
1201
1202 /**
1203 * @return {Number} returns the right property of the region
1204 */
1205 getRight: function(){
1206 return this.right
1207 },
1208
1209 /**
1210 * Returns the area of the region
1211 * @return {Number} the computed area
1212 */
1213 getArea: function(){
1214 return this.getWidth() * this.getHeight()
1215 },
1216
1217 constrainTo: function(contrain){
1218 var intersect = this.getIntersection(contrain),
1219 shift
1220
1221 if (!intersect || !intersect.equals(this)){
1222
1223 var contrainWidth = contrain.getWidth(),
1224 contrainHeight = contrain.getHeight(),
1225
1226 shift = {}
1227
1228 if (this.getWidth() > contrainWidth){
1229 this.left = contrain.left
1230 this.setWidth(contrainWidth)
1231 }
1232
1233 if (this.getHeight() > contrainHeight){
1234 this.top = contrain.top
1235 this.setHeight(contrainHeight)
1236 }
1237
1238 shift = {}
1239
1240 if (this.right > contrain.right){
1241 shift.left = contrain.right - this.right
1242 }
1243
1244 if (this.bottom > contrain.bottom){
1245 shift.top = contrain.bottom - this.bottom
1246 }
1247
1248 if (this.left < contrain.left){
1249 shift.left = contrain.left - this.left
1250 }
1251
1252 if (this.top < contrain.top){
1253 shift.top = contrain.top - this.top
1254 }
1255
1256 this.shift(shift)
1257
1258 return true
1259 }
1260
1261 return false
1262 },
1263
1264 /**
1265 * @constructor
1266 *
1267 * Construct a new Region.
1268 *
1269 * Example:
1270 *
1271 * var r = root.create('z.region', { top: 10, left: 20, bottom: 100, right: 200 })
1272 *
1273 * //or, the same, but with numbers
1274 *
1275 * r = root.create('z.region', 10, 200, 100, 20)
1276 *
1277 * //or, with width and height
1278 *
1279 * r = root.create('z.region', { top: 10, left: 20, width: 180, height: 90})
1280 *
1281 * @param {Number|Object} top The top pixel position, or an object with top, left, bottom, right properties. If an object is passed,
1282 * instead of having bottom and right, it can have width and height.
1283 *
1284 * @param {Number} right The right pixel position
1285 * @param {Number} bottom The bottom pixel position
1286 * @param {Number} left The left pixel position
1287 *
1288 * @return {Region} this
1289 */
1290 init: function(top, right, bottom, left){
1291
1292 if (isObject(top)){
1293 copyList(top, this, ['top','right','bottom','left'])
1294
1295 if (top.bottom == null && top.height != null){
1296 this.bottom = this.top + top.height
1297 }
1298 if (top.right == null && top.width != null){
1299 this.right = this.left + top.width
1300 }
1301
1302 if (top.emitChangeEvents){
1303 this.emitChangeEvents = top.emitChangeEvents
1304 }
1305 } else {
1306 this.top = top
1307 this.right = right
1308 this.bottom = bottom
1309 this.left = left
1310 }
1311
1312 this[0] = this.left
1313 this[1] = this.top
1314
1315 REGION.validate(this)
1316 },
1317
1318 /**
1319 * @property {Number} top
1320 */
1321
1322 /**
1323 * @property {Number} right
1324 */
1325
1326 /**
1327 * @property {Number} bottom
1328 */
1329
1330 /**
1331 * @property {Number} left
1332 */
1333
1334 /**
1335 * @property {Number} [0] the top property
1336 */
1337
1338 /**
1339 * @property {Number} [1] the left property
1340 */
1341
1342 /**
1343 *
1344 * Aligns this region to the given region
1345 * @param {Region} region
1346 * @param {String} alignPositions For available positions, see {@link #getPoint}
1347 *
1348 * eg: 'tr-bl'
1349 *
1350 * @return this
1351 */
1352 // alignToRegion: function(region, alignPositions){
1353 // REGION.align(this, region, alignPositions)
1354
1355 // return this
1356 // },
1357
1358 /**
1359 * Aligns this region to the given point, in the anchor position
1360 * @param {Object} point eg: {x: 20, y: 600}
1361 * @param {Number} point.x
1362 * @param {Number} point.y
1363 *
1364 * @param {String} anchor For available positions, see {@link #getPoint}
1365 *
1366 * eg: 'bl'
1367 *
1368 * @return this
1369 */
1370 // alignToPoint: function(point, anchor){
1371 // REGION.alignToPoint(this, point, anchor)
1372
1373 // return this
1374 // }
1375
1376 /**
1377 * @method getIntersection
1378 * Returns a region that is the intersection of this region and the given region
1379 * @param {Region} region The region to intersect with
1380 * @return {Region} The intersection region
1381 */
1382
1383 /**
1384 * @method getUnion
1385 * Returns a region that is the union of this region with the given region
1386 * @param {Region} region The region to make union with
1387 * @return {Region} The union region. The smallest region that contains both this and the given region.
1388 */
1389
1390})
1391
1392module.exports = REGION
\No newline at end of file