Esanders has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/342814 )

Change subject: Refactor rect-from-element computation in FocusableNode into 
static method
......................................................................

Refactor rect-from-element computation in FocusableNode into static method

So that this can be re-used by other users who want to draw
rects around complex elements. We may want to move this up
to ve.utils at a later date.

Change-Id: Idba5a181360658dd2dd0cdc0a070e70b1069f5e4
---
M src/ce/ve.ce.FocusableNode.js
1 file changed, 137 insertions(+), 122 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/VisualEditor/VisualEditor 
refs/changes/14/342814/1

diff --git a/src/ce/ve.ce.FocusableNode.js b/src/ce/ve.ce.FocusableNode.js
index 8c4349a..ad32496 100644
--- a/src/ce/ve.ce.FocusableNode.js
+++ b/src/ce/ve.ce.FocusableNode.js
@@ -87,6 +87,137 @@
  */
 ve.ce.FocusableNode.static.iconWhenInvisible = null;
 
+/* Static methods */
+
+/**
+ * Get rects for an element
+ *
+ * @param {jQuery} $element Element to get highlights
+ * @param {Object} [relativeRect] Rect with top & left to get position 
relative to
+ * @return {Object} Object containing rects and boundingRect
+ */
+ve.ce.FocusableNode.static.getRectsForElement = function ( $element, 
relativeRect ) {
+       var i, l, $set, columnCount, columnWidth,
+               boundingRect = null,
+               rects = [],
+               filteredRects = [],
+               webkitColumns = 'webkitColumnCount' in document.createElement( 
'div' ).style;
+
+       function contains( rect1, rect2 ) {
+               return rect2.left >= rect1.left &&
+                       rect2.top >= rect1.top &&
+                       rect2.right <= rect1.right &&
+                       rect2.bottom <= rect1.bottom;
+       }
+
+       function process( el ) {
+               var i, j, il, jl, contained, clientRects, overflow, $el;
+
+               if ( el.classList.contains( 've-ce-noHighlight' ) ) {
+                       return;
+               }
+
+               $el = $( el );
+
+               if ( webkitColumns ) {
+                       columnCount = $el.css( '-webkit-column-count' );
+                       columnWidth = $el.css( '-webkit-column-width' );
+                       if ( ( columnCount && columnCount !== 'auto' ) || ( 
columnWidth && columnWidth !== 'auto' ) ) {
+                               // Support: Chrome
+                               // Chrome incorrectly measures children of 
nodes with columns [1], let's
+                               // just ignore them rather than render a 
possibly bizarre highlight. They
+                               // will usually not be positioned, because 
Chrome also doesn't position
+                               // them correctly [2] and so people avoid doing 
it.
+                               //
+                               // Of course there are other ways to render a 
node outside the bounding
+                               // box of its parent, like negative margin. We 
do not handle these cases,
+                               // and the highlight may not correctly cover 
the entire node if that
+                               // happens. This can't be worked around without 
implementing CSS
+                               // layouting logic ourselves, which is not 
worth it.
+                               //
+                               // [1] 
https://code.google.com/p/chromium/issues/detail?id=391271
+                               // [2] 
https://code.google.com/p/chromium/issues/detail?id=291616
+
+                               // jQuery keeps nodes in its collections in 
document order, so the
+                               // children have not been processed yet and can 
be safely removed.
+                               $set = $set.not( $el.find( '*' ) );
+                       }
+               }
+
+               // Don't descend if overflow is anything but visible as this 
prevents
+               // child elements appearing beyond the bounding box of the 
parent
+               overflow = $el.css( 'overflow' );
+               if ( overflow && overflow !== 'visible' ) {
+                       $set = $set.not( $el.find( '*' ) );
+               }
+
+               clientRects = el.getClientRects();
+
+               for ( i = 0, il = clientRects.length; i < il; i++ ) {
+                       contained = false;
+                       for ( j = 0, jl = rects.length; j < jl; j++ ) {
+                               // This rect is contained by an existing rect, 
discard
+                               if ( contains( rects[ j ], clientRects[ i ] ) ) 
{
+                                       contained = true;
+                                       break;
+                               }
+                               // An existing rect is contained by this rect, 
discard the existing rect
+                               if ( contains( clientRects[ i ], rects[ j ] ) ) 
{
+                                       rects.splice( j, 1 );
+                                       j--;
+                                       jl--;
+                               }
+                       }
+                       if ( !contained ) {
+                               rects.push( clientRects[ i ] );
+                       }
+               }
+       }
+
+       $set = $element.find( '*' ).addBack();
+       // Calling process() may change $set.length
+       for ( i = 0; i < $set.length; i++ ) {
+               process( $set[ i ] );
+       }
+
+       // Elements with a width/height of 0 return a clientRect with a 
width/height of 1
+       // As elements with an actual width/height of 1 aren't that useful 
anyway, just
+       // throw away anything that is <=1
+       filteredRects = rects.filter( function ( rect ) {
+               return rect.width > 1 && rect.height > 1;
+       } );
+       // But if this filtering doesn't leave any rects at all, then we do 
want to use the 1px rects
+       if ( filteredRects.length > 0 ) {
+               rects = filteredRects;
+       }
+
+       boundingRect = null;
+
+       for ( i = 0, l = rects.length; i < l; i++ ) {
+               // Translate to relative
+               if ( relativeRect ) {
+                       rects[ i ] = ve.translateRect( rects[ i ], 
-relativeRect.left, -relativeRect.top );
+               }
+               if ( !boundingRect ) {
+                       boundingRect = ve.copy( rects[ i ] );
+               } else {
+                       boundingRect.top = Math.min( boundingRect.top, rects[ i 
].top );
+                       boundingRect.left = Math.min( boundingRect.left, rects[ 
i ].left );
+                       boundingRect.bottom = Math.max( boundingRect.bottom, 
rects[ i ].bottom );
+                       boundingRect.right = Math.max( boundingRect.right, 
rects[ i ].right );
+               }
+       }
+       if ( boundingRect ) {
+               boundingRect.width = boundingRect.right - boundingRect.left;
+               boundingRect.height = boundingRect.bottom - boundingRect.top;
+       }
+
+       return {
+               rects: rects,
+               boundingRect: boundingRect
+       };
+};
+
 /* Methods */
 
 /**
@@ -540,143 +671,27 @@
 };
 
 /**
- * Calculate position of highlights
+ * Re-calculate position of highlights
  */
 ve.ce.FocusableNode.prototype.calculateHighlights = function () {
-       var i, l, $set, columnCount, columnWidth, surfaceOffset,
-               rects = [],
-               filteredRects = [],
-               webkitColumns = 'webkitColumnCount' in document.createElement( 
'div' ).style;
+       var allRects, surfaceOffset;
 
        // Protect against calling before/after surface setup/teardown
        if ( !this.focusableSurface ) {
+               this.rects = [];
                this.boundingRect = null;
                this.startAndEndRects = null;
-               this.rects = [];
                return;
        }
 
        surfaceOffset = 
this.focusableSurface.getSurface().getBoundingClientRect();
 
-       function contains( rect1, rect2 ) {
-               return rect2.left >= rect1.left &&
-                       rect2.top >= rect1.top &&
-                       rect2.right <= rect1.right &&
-                       rect2.bottom <= rect1.bottom;
-       }
+       allRects = this.constructor.static.getRectsForElement( this.$focusable, 
surfaceOffset );
 
-       function process( el ) {
-               var i, j, il, jl, contained, clientRects, overflow, $el;
-
-               if ( el.classList.contains( 've-ce-noHighlight' ) ) {
-                       return;
-               }
-
-               $el = $( el );
-
-               if ( webkitColumns ) {
-                       columnCount = $el.css( '-webkit-column-count' );
-                       columnWidth = $el.css( '-webkit-column-width' );
-                       if ( ( columnCount && columnCount !== 'auto' ) || ( 
columnWidth && columnWidth !== 'auto' ) ) {
-                               // Support: Chrome
-                               // Chrome incorrectly measures children of 
nodes with columns [1], let's
-                               // just ignore them rather than render a 
possibly bizarre highlight. They
-                               // will usually not be positioned, because 
Chrome also doesn't position
-                               // them correctly [2] and so people avoid doing 
it.
-                               //
-                               // Of course there are other ways to render a 
node outside the bounding
-                               // box of its parent, like negative margin. We 
do not handle these cases,
-                               // and the highlight may not correctly cover 
the entire node if that
-                               // happens. This can't be worked around without 
implementing CSS
-                               // layouting logic ourselves, which is not 
worth it.
-                               //
-                               // [1] 
https://code.google.com/p/chromium/issues/detail?id=391271
-                               // [2] 
https://code.google.com/p/chromium/issues/detail?id=291616
-
-                               // jQuery keeps nodes in its collections in 
document order, so the
-                               // children have not been processed yet and can 
be safely removed.
-                               $set = $set.not( $el.find( '*' ) );
-                       }
-               }
-
-               // Don't descend if overflow is anything but visible as this 
prevents
-               // child elements appearing beyond the bounding box of the 
parent
-               overflow = $el.css( 'overflow' );
-               if ( overflow && overflow !== 'visible' ) {
-                       $set = $set.not( $el.find( '*' ) );
-               }
-
-               clientRects = el.getClientRects();
-
-               for ( i = 0, il = clientRects.length; i < il; i++ ) {
-                       contained = false;
-                       for ( j = 0, jl = rects.length; j < jl; j++ ) {
-                               // This rect is contained by an existing rect, 
discard
-                               if ( contains( rects[ j ], clientRects[ i ] ) ) 
{
-                                       contained = true;
-                                       break;
-                               }
-                               // An existing rect is contained by this rect, 
discard the existing rect
-                               if ( contains( clientRects[ i ], rects[ j ] ) ) 
{
-                                       rects.splice( j, 1 );
-                                       j--;
-                                       jl--;
-                               }
-                       }
-                       if ( !contained ) {
-                               rects.push( clientRects[ i ] );
-                       }
-               }
-       }
-
-       $set = this.$focusable.find( '*' ).addBack();
-       // Calling process() may change $set.length
-       for ( i = 0; i < $set.length; i++ ) {
-               process( $set[ i ] );
-       }
-
-       // Elements with a width/height of 0 return a clientRect with a 
width/height of 1
-       // As elements with an actual width/height of 1 aren't that useful 
anyway, just
-       // throw away anything that is <=1
-       filteredRects = rects.filter( function ( rect ) {
-               return rect.width > 1 && rect.height > 1;
-       } );
-       // But if this filtering doesn't leave any rects at all, then we do 
want to use the 1px rects
-       if ( filteredRects.length > 0 ) {
-               rects = filteredRects;
-       }
-
-       this.boundingRect = null;
+       this.rects = allRects.rects;
+       this.boundingRect = allRects.boundingRect;
        // startAndEndRects is lazily evaluated in getStartAndEndRects from 
rects
        this.startAndEndRects = null;
-
-       for ( i = 0, l = rects.length; i < l; i++ ) {
-               // Translate to relative
-               rects[ i ] = ve.translateRect( rects[ i ], -surfaceOffset.left, 
-surfaceOffset.top );
-               this.$highlights.append(
-                       this.createHighlight().css( {
-                               top: rects[ i ].top,
-                               left: rects[ i ].left,
-                               width: rects[ i ].width,
-                               height: rects[ i ].height
-                       } )
-               );
-
-               if ( !this.boundingRect ) {
-                       this.boundingRect = ve.copy( rects[ i ] );
-               } else {
-                       this.boundingRect.top = Math.min( 
this.boundingRect.top, rects[ i ].top );
-                       this.boundingRect.left = Math.min( 
this.boundingRect.left, rects[ i ].left );
-                       this.boundingRect.bottom = Math.max( 
this.boundingRect.bottom, rects[ i ].bottom );
-                       this.boundingRect.right = Math.max( 
this.boundingRect.right, rects[ i ].right );
-               }
-       }
-       if ( this.boundingRect ) {
-               this.boundingRect.width = this.boundingRect.right - 
this.boundingRect.left;
-               this.boundingRect.height = this.boundingRect.bottom - 
this.boundingRect.top;
-       }
-
-       this.rects = rects;
 };
 
 /**

-- 
To view, visit https://gerrit.wikimedia.org/r/342814
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Idba5a181360658dd2dd0cdc0a070e70b1069f5e4
Gerrit-PatchSet: 1
Gerrit-Project: VisualEditor/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Esanders <esand...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to