UNPKG

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