Преглед на файлове

separate OffsetCoordCache from PositionCoordCache

Adam Shaw преди 7 години
родител
ревизия
e51aad5106
променени са 7 файла, в които са добавени 185 реда и са изтрити 118 реда
  1. 33 16
      src/agenda/TimeGrid.ts
  2. 34 15
      src/basic/DayGrid.ts
  3. 20 84
      src/common/CoordCache.ts
  4. 55 0
      src/common/OffsetCoordCache.ts
  5. 23 0
      src/component/DateComponent.ts
  6. 12 3
      src/interactions/HitDragging.ts
  7. 8 0
      src/util/geom.ts

+ 33 - 16
src/agenda/TimeGrid.ts

@@ -10,6 +10,7 @@ import { Duration, createDuration, addDurations, multiplyDuration, wholeDivideDu
 import { startOfDay, DateMarker, addMs } from '../datelib/marker'
 import { DateFormatter, createFormatter, formatIsoTimeString } from '../datelib/formatting'
 import DateComponent, { Seg } from '../component/DateComponent'
+import OffsetCoordCache from '../common/OffsetCoordCache'
 import { DateSpan } from '../structs/date-span'
 import { EventStore } from '../structs/event-store'
 import { Hit } from '../interactions/HitDragging'
@@ -61,6 +62,8 @@ export default class TimeGrid extends DateComponent {
 
   colCoordCache: CoordCache
   slatCoordCache: CoordCache
+  colOffsets: OffsetCoordCache
+  slatOffsets: OffsetCoordCache
 
   rootBgContainerEl: HTMLElement
   bottomRuleEl: HTMLElement // hidden by default
@@ -514,7 +517,7 @@ export default class TimeGrid extends DateComponent {
     // could be 1.0 if slatCoverage is covering *all* the slots
     slatRemainder = slatCoverage - slatIndex
 
-    return this.slatCoordCache.getTopPosition(slatIndex) +
+    return this.slatCoordCache.indexToTopPosition(slatIndex) +
       this.slatCoordCache.getHeight(slatIndex) * slatRemainder
   }
 
@@ -561,21 +564,41 @@ export default class TimeGrid extends DateComponent {
   }
 
 
+  /* Sizing
+  ------------------------------------------------------------------------------------------------------------------*/
+
+
+  buildCoordCaches() {
+    this.colCoordCache.build()
+    this.slatCoordCache.build()
+  }
+
+
   /* Hit System
   ------------------------------------------------------------------------------------------------------------------*/
 
 
+  prepareHits() {
+    this.colOffsets = new OffsetCoordCache(this.colCoordCache)
+    this.slatOffsets = new OffsetCoordCache(this.slatCoordCache)
+  }
+
+
+  releaseHits() {
+    this.colOffsets.destroy()
+    this.slatOffsets.destroy()
+  }
+
+
   queryHit(leftOffset, topOffset): Hit {
-    let snapsPerSlot = this.snapsPerSlot
-    let colCoordCache = this.colCoordCache
-    let slatCoordCache = this.slatCoordCache
+    let { snapsPerSlot, slatCoordCache, colOffsets, slatOffsets } = this
 
-    if (colCoordCache.isLeftInBounds(leftOffset) && slatCoordCache.isTopInBounds(topOffset)) {
-      let colIndex = colCoordCache.getHorizontalIndex(leftOffset)
-      let slatIndex = slatCoordCache.getVerticalIndex(topOffset)
+    if (colOffsets.isInBounds(leftOffset, topOffset)) {
+      let colIndex = colOffsets.leftOffsetToIndex(leftOffset)
+      let slatIndex = slatOffsets.topOffsetToIndex(topOffset)
 
       if (colIndex != null && slatIndex != null) {
-        let slatTop = slatCoordCache.getTopOffset(slatIndex)
+        let slatTop = slatOffsets.indexToTopOffset(slatIndex)
         let slatHeight = slatCoordCache.getHeight(slatIndex)
         let partial = (topOffset - slatTop) / slatHeight // floating point number between 0 and 1
         let localSnapIndex = Math.floor(partial * snapsPerSlot) // the snap # relative to start of slat
@@ -599,8 +622,8 @@ export default class TimeGrid extends DateComponent {
           },
           dayEl: this.colEls[colIndex],
           rect: {
-            left: colCoordCache.getLeftOffset(colIndex),
-            right: colCoordCache.getRightOffset(colIndex),
+            left: colOffsets.indexToLeftOffset(colIndex),
+            right: colOffsets.indexToRightOffset(colIndex),
             top: slatTop,
             bottom: slatTop + slatHeight
           }
@@ -610,12 +633,6 @@ export default class TimeGrid extends DateComponent {
   }
 
 
-  buildCoordCaches() {
-    this.colCoordCache.build()
-    this.slatCoordCache.build()
-  }
-
-
   /* Event Resize Visualization
   ------------------------------------------------------------------------------------------------------------------*/
 

+ 34 - 15
src/basic/DayGrid.ts

@@ -21,6 +21,7 @@ import { EventStore } from '../structs/event-store'
 import DayTile from './DayTile'
 import { Hit } from '../interactions/HitDragging'
 import { DateRange, rangeContainsMarker, intersectRanges } from '../datelib/date-range'
+import OffsetCoordCache from '../common/OffsetCoordCache'
 
 const DAY_NUM_FORMAT = createFormatter({ day: 'numeric' })
 const WEEK_NUM_FORMAT = createFormatter({ week: 'numeric' })
@@ -62,6 +63,8 @@ export default class DayGrid extends DateComponent {
 
   rowCoordCache: CoordCache
   colCoordCache: CoordCache
+  colOffsets: OffsetCoordCache
+  rowOffsets: OffsetCoordCache
 
   // isRigid determines whether the individual rows should ignore the contents and be a constant height.
   // Relies on the view's colCnt and rowCnt. In the future, this component should probably be self-sufficient.
@@ -289,16 +292,39 @@ export default class DayGrid extends DateComponent {
   }
 
 
+  /* Sizing
+  ------------------------------------------------------------------------------------------------------------------*/
+
+
+  buildCoordCaches() {
+    this.colCoordCache.build()
+    this.rowCoordCache.build()
+    this.rowCoordCache.bottoms[this.rowCnt - 1] += this.bottomCoordPadding // hack
+  }
+
+
   /* Hit System
   ------------------------------------------------------------------------------------------------------------------*/
 
 
+  prepareHits() {
+    this.colOffsets = new OffsetCoordCache(this.colCoordCache)
+    this.rowOffsets = new OffsetCoordCache(this.rowCoordCache)
+  }
+
+
+  releaseHits() {
+    this.colOffsets.destroy()
+    this.rowOffsets.destroy()
+  }
+
+
   queryHit(leftOffset, topOffset): Hit {
-    let { colCoordCache, rowCoordCache } = this
+    let { colOffsets, rowOffsets } = this
 
-    if (colCoordCache.isLeftInBounds(leftOffset) && rowCoordCache.isTopInBounds(topOffset)) {
-      let col = colCoordCache.getHorizontalIndex(leftOffset)
-      let row = rowCoordCache.getVerticalIndex(topOffset)
+    if (colOffsets.isInBounds(leftOffset, topOffset)) {
+      let col = colOffsets.leftOffsetToIndex(leftOffset)
+      let row = rowOffsets.topOffsetToIndex(topOffset)
 
       if (row != null && col != null) {
         return {
@@ -309,10 +335,10 @@ export default class DayGrid extends DateComponent {
           },
           dayEl: this.getCellEl(row, col),
           rect: {
-            left: colCoordCache.getLeftOffset(col),
-            right: colCoordCache.getRightOffset(col),
-            top: rowCoordCache.getTopOffset(row),
-            bottom: rowCoordCache.getBottomOffset(row)
+            left: colOffsets.indexToLeftOffset(col),
+            right: colOffsets.indexToRightOffset(col),
+            top: rowOffsets.indexToTopOffset(row),
+            bottom: rowOffsets.indexToBottomOffset(row)
           }
         }
       }
@@ -320,13 +346,6 @@ export default class DayGrid extends DateComponent {
   }
 
 
-  buildCoordCaches() {
-    this.colCoordCache.build()
-    this.rowCoordCache.build()
-    this.rowCoordCache.bottoms[this.rowCnt - 1] += this.bottomCoordPadding // hack
-  }
-
-
   /* Cell System
   ------------------------------------------------------------------------------------------------------------------*/
   // FYI: the first column is the leftmost column, regardless of date

+ 20 - 84
src/common/CoordCache.ts

@@ -1,6 +1,5 @@
-import { computeInnerRect, getScrollParent } from '../util/dom-geom'
 
-export interface CoordCacheOptions {
+export interface CoordCacheOptions { // TODO: set these props directly
   originEl: HTMLElement
   els: HTMLElement[]
   isHorizontal?: boolean
@@ -16,12 +15,10 @@ options:
 - isHorizontal
 - isVertical
 */
-export default class CoordCache {
+export default class CoordCache { // TODO: rename to PositionCoordCache
 
   els: HTMLElement[] // assumed to be siblings
   originEl: HTMLElement // options can override the natural originEl
-  origin: any // {left,top} position of originEl of els
-  boundingRect: any // constrain cordinates to this rectangle. {left,right,top,bottom} or null
   isHorizontal: boolean = false // whether to query for left/right/width
   isVertical: boolean = false // whether to query for top/bottom/height
 
@@ -46,11 +43,6 @@ export default class CoordCache {
     let originEl = this.originEl
     let originClientRect = originEl.getBoundingClientRect() // relative to viewport top-left
 
-    this.origin = {
-      top: originClientRect.top + window.scrollY,
-      left: originClientRect.left + window.scrollX
-    }
-
     if (this.isHorizontal) {
       this.buildElHorizontals(originClientRect.left)
     }
@@ -58,8 +50,6 @@ export default class CoordCache {
     if (this.isVertical) {
       this.buildElVerticals(originClientRect.top)
     }
-
-    this.boundingRect = this.queryBoundingRect()
   }
 
 
@@ -68,11 +58,11 @@ export default class CoordCache {
     let lefts = []
     let rights = []
 
-    this.els.forEach(function(node) {
-      let rect = node.getBoundingClientRect()
+    for (let el of this.els) {
+      let rect = el.getBoundingClientRect()
       lefts.push(rect.left - originClientLeft)
       rights.push(rect.right - originClientLeft)
-    })
+    }
 
     this.lefts = lefts
     this.rights = rights
@@ -84,11 +74,11 @@ export default class CoordCache {
     let tops = []
     let bottoms = []
 
-    this.els.forEach(function(node) {
-      let rect = node.getBoundingClientRect()
+    for (let el of this.els) {
+      let rect = el.getBoundingClientRect()
       tops.push(rect.top - originClientTop)
       bottoms.push(rect.bottom - originClientTop)
-    })
+    }
 
     this.tops = tops
     this.bottoms = bottoms
@@ -97,8 +87,7 @@ export default class CoordCache {
 
   // Given a left offset (from document left), returns the index of the el that it horizontally intersects.
   // If no intersection is made, returns undefined.
-  getHorizontalIndex(leftOffset) {
-    let leftPosition = leftOffset - this.origin.left
+  leftPositionToIndex(leftPosition) {
     let lefts = this.lefts
     let rights = this.rights
     let len = lefts.length
@@ -114,8 +103,7 @@ export default class CoordCache {
 
   // Given a top offset (from document top), returns the index of the el that it vertically intersects.
   // If no intersection is made, returns undefined.
-  getVerticalIndex(topOffset) {
-    let topPosition = topOffset - this.origin.top
+  topPositionToIndex(topPosition) {
     let tops = this.tops
     let bottoms = this.bottoms
     let len = tops.length
@@ -129,93 +117,41 @@ export default class CoordCache {
   }
 
 
-  // Gets the left offset (from document left) of the element at the given index
-  getLeftOffset(leftIndex) {
-    return this.lefts[leftIndex] + this.origin.left
-  }
-
-
   // Gets the left position (from originEl left) of the element at the given index
-  getLeftPosition(leftIndex) {
+  indexToLeftPosition(leftIndex) {
     return this.lefts[leftIndex]
   }
 
 
-  // Gets the right offset (from document left) of the element at the given index.
-  // This value is NOT relative to the document's right edge, like the CSS concept of "right" would be.
-  getRightOffset(leftIndex) {
-    return this.rights[leftIndex] + this.origin.left
-  }
-
-
   // Gets the right position (from originEl left) of the element at the given index.
   // This value is NOT relative to the originEl's right edge, like the CSS concept of "right" would be.
-  getRightPosition(leftIndex) {
+  indexToRightPosition(leftIndex) {
     return this.rights[leftIndex]
   }
 
 
-  // Gets the width of the element at the given index
-  getWidth(leftIndex) {
-    return this.rights[leftIndex] - this.lefts[leftIndex]
-  }
-
-
-  // Gets the top offset (from document top) of the element at the given index
-  getTopOffset(topIndex) {
-    return this.tops[topIndex] + this.origin.top
-  }
-
-
   // Gets the top position (from originEl top) of the element at the given position
-  getTopPosition(topIndex) {
+  indexToTopPosition(topIndex) {
     return this.tops[topIndex]
   }
 
-  // Gets the bottom offset (from the document top) of the element at the given index.
-  // This value is NOT relative to the originEl's bottom edge, like the CSS concept of "bottom" would be.
-  getBottomOffset(topIndex) {
-    return this.bottoms[topIndex] + this.origin.top
-  }
-
 
   // Gets the bottom position (from the originEl top) of the element at the given index.
   // This value is NOT relative to the originEl's bottom edge, like the CSS concept of "bottom" would be.
-  getBottomPosition(topIndex) {
+  indexToBottomPosition(topIndex) {
     return this.bottoms[topIndex]
   }
 
 
-  // Gets the height of the element at the given index
-  getHeight(topIndex) {
-    return this.bottoms[topIndex] - this.tops[topIndex]
-  }
-
-
-  // Bounding Rect
-  // TODO: decouple this from CoordCache
-
-  // Compute and return what the elements' bounding rectangle is, from the user's perspective.
-  // Right now, only returns a rectangle if constrained by an overflow:scroll element.
-  // Returns null if there are no elements
-  queryBoundingRect() {
-    let scrollParentEl: HTMLElement = getScrollParent(this.els[0] || this.originEl)
-
-    if (scrollParentEl) {
-      return computeInnerRect(scrollParentEl)
-    }
-  }
-
-  isPointInBounds(leftOffset, topOffset) {
-    return this.isLeftInBounds(leftOffset) && this.isTopInBounds(topOffset)
+  // Gets the width of the element at the given index
+  getWidth(leftIndex) {
+    return this.rights[leftIndex] - this.lefts[leftIndex]
   }
 
-  isLeftInBounds(leftOffset) {
-    return !this.boundingRect || (leftOffset >= this.boundingRect.left && leftOffset < this.boundingRect.right)
-  }
 
-  isTopInBounds(topOffset) {
-    return !this.boundingRect || (topOffset >= this.boundingRect.top && topOffset < this.boundingRect.bottom)
+  // Gets the height of the element at the given index
+  getHeight(topIndex) {
+    return this.bottoms[topIndex] - this.tops[topIndex]
   }
 
 }

+ 55 - 0
src/common/OffsetCoordCache.ts

@@ -0,0 +1,55 @@
+import CoordCache from './CoordCache'
+import { computeInnerRect, getScrollParent, computeRect } from '../util/dom-geom'
+import { Rect, pointInsideRect } from '../util/geom'
+
+export default class OffsetCoordCache {
+
+  coordCache: CoordCache
+  boundingRect: Rect
+  originOffsetLeft: number
+  originOffsetTop: number
+
+  constructor(coordCache: CoordCache) {
+    this.coordCache = coordCache
+
+    let scrollParent = getScrollParent(coordCache.originEl)
+    this.boundingRect = scrollParent ? computeInnerRect(scrollParent) : null
+
+    let rect = computeRect(coordCache.originEl)
+    this.originOffsetLeft = rect.left
+    this.originOffsetTop = rect.top
+  }
+
+  destroy() {
+    // console.log('OffsetCoordCache::destroy')
+  }
+
+  isInBounds(pageX, pageY): boolean {
+    return !this.boundingRect || pointInsideRect({ left: pageX, top: pageY }, this.boundingRect)
+  }
+
+  leftOffsetToIndex(leftOffset): number {
+    return this.coordCache.leftPositionToIndex(leftOffset - this.originOffsetLeft)
+  }
+
+  topOffsetToIndex(topOffset): number {
+    return this.coordCache.topPositionToIndex(topOffset - this.originOffsetTop)
+  }
+
+  indexToLeftOffset(index): number {
+    return this.coordCache.indexToLeftPosition(index) + this.originOffsetLeft
+  }
+
+  indexToTopOffset(index): number {
+    return this.coordCache.indexToTopPosition(index) + this.originOffsetTop
+  }
+
+  indexToRightOffset(index): number {
+    return this.coordCache.indexToRightPosition(index) + this.originOffsetLeft
+  }
+
+  indexToBottomOffset(index): number {
+    return this.coordCache.indexToBottomPosition(index) + this.originOffsetTop
+  }
+
+}

+ 23 - 0
src/component/DateComponent.ts

@@ -77,6 +77,7 @@ export default abstract class DateComponent extends Component {
 
   renderedFlags: any = {}
   dirtySizeFlags: any = {}
+  needHitsDepth: number = 0
 
   dateProfile: DateProfile = null
   businessHoursDef: BusinessHoursDef = false
@@ -194,6 +195,28 @@ export default abstract class DateComponent extends Component {
   }
 
 
+  requestPrepareHits() {
+    if (!(this.needHitsDepth++)) {
+      this.prepareHits()
+    }
+  }
+
+
+  requestReleaseHits() {
+    if (!(--this.needHitsDepth)) {
+      this.releaseHits()
+    }
+  }
+
+
+  protected prepareHits() {
+  }
+
+
+  protected releaseHits() {
+  }
+
+
   queryHit(leftOffset, topOffset): Hit {
     return null // this should be abstract
   }

+ 12 - 3
src/interactions/HitDragging.ts

@@ -67,7 +67,7 @@ export default class HitDragging {
     this.movingHit = null
     this.finalHit = null
 
-    this.prepareComponents()
+    this.prepareHits()
     this.processFirstCoord(ev)
 
     if (this.initialHit || !this.requireInitial) {
@@ -118,6 +118,7 @@ export default class HitDragging {
   }
 
   handlePointerUp = (ev: PointerDragEvent) => {
+    this.releaseHits()
     this.emitter.trigger('pointerup', ev)
   }
 
@@ -143,11 +144,19 @@ export default class HitDragging {
     }
   }
 
-  prepareComponents() {
+  prepareHits() {
     let { droppableHash } = this
 
     for (let id in droppableHash) {
-      droppableHash[id].buildCoordCaches()
+      droppableHash[id].requestPrepareHits()
+    }
+  }
+
+  releaseHits() {
+    let { droppableHash } = this
+
+    for (let id in droppableHash) {
+      droppableHash[id].requestReleaseHits()
     }
   }
 

+ 8 - 0
src/util/geom.ts

@@ -12,6 +12,14 @@ export interface Rect {
 }
 
 
+export function pointInsideRect(point: Point, rect: Rect): boolean {
+  return point.left >= rect.left &&
+    point.left < rect.right &&
+    point.top >= rect.top &&
+    point.top < rect.bottom
+}
+
+
 // Returns a new rectangle that is the intersection of the two rectangles. If they don't intersect, returns false
 export function intersectRects(rect1: Rect, rect2: Rect): Rect | false {
   let res = {