bm-core-ui 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/CHANGELOG.md +1624 -0
  2. package/LICENSE +21 -0
  3. package/README.md +145 -0
  4. package/build/@types/index.d.ts +14078 -0
  5. package/build/BMCodeEditor/BMCodeEditor.js +707 -0
  6. package/build/BMCollectionView/BMCollectionView.js +5861 -0
  7. package/build/BMCollectionView/BMCollectionViewCell.js +688 -0
  8. package/build/BMCollectionView/BMCollectionViewFlowLayout.js +4467 -0
  9. package/build/BMCollectionView/BMCollectionViewLayout.js +830 -0
  10. package/build/BMCollectionView/BMCollectionViewLayoutAttributes.js +673 -0
  11. package/build/BMCollectionView/BMCollectionViewMasonryLayout.js +491 -0
  12. package/build/BMCollectionView/BMCollectionViewStackLayout.js +634 -0
  13. package/build/BMCollectionView/BMCollectionViewTileLayout.js +1121 -0
  14. package/build/BMCoreUI.css +2833 -0
  15. package/build/BMView/BMAttributedLabelView.js +310 -0
  16. package/build/BMView/BMLayoutConstraint_v2.5.js +1813 -0
  17. package/build/BMView/BMLayoutGuide.js +192 -0
  18. package/build/BMView/BMLayoutSizeClass.js +466 -0
  19. package/build/BMView/BMMenu.js +574 -0
  20. package/build/BMView/BMScrollView.js +247 -0
  21. package/build/BMView/BMTextField.js +512 -0
  22. package/build/BMView/BMTextFieldDelegate.js +71 -0
  23. package/build/BMView/BMView_v2.5.js +3572 -0
  24. package/build/BMView/BMViewport.js +221 -0
  25. package/build/BMViewLayoutEditor/BMLayoutEditor.js +6117 -0
  26. package/build/BMViewLayoutEditor/BMLayoutEditorConstraintSettings.js +417 -0
  27. package/build/BMViewLayoutEditor/BMLayoutEditorDelegate.js +59 -0
  28. package/build/BMViewLayoutEditor/BMLayoutEditorSettingCells.js +1653 -0
  29. package/build/BMViewLayoutEditor/BMLayoutEditorSettings.js +1480 -0
  30. package/build/BMViewLayoutEditor/BMLayoutEditorSettingsComplexCells.js +431 -0
  31. package/build/BMViewLayoutEditor/BMLayoutEditorSettingsDelegate.js +46 -0
  32. package/build/BMViewLayoutEditor/BMLayoutEditorVariablesController.js +460 -0
  33. package/build/BMViewLayoutEditor/BMLayoutEditorViewGroupSettings.js +293 -0
  34. package/build/BMViewLayoutEditor/BMLayoutEditorViewSettings.js +382 -0
  35. package/build/BMViewLayoutEditor/BMLayoutVariableProvider.js +202 -0
  36. package/build/BMWindow/BMConfirmationPopup.js +477 -0
  37. package/build/BMWindow/BMKeyboardShortcut.js +151 -0
  38. package/build/BMWindow/BMPopover/BMPopover.js +492 -0
  39. package/build/BMWindow/BMToolWindow.js +71 -0
  40. package/build/BMWindow/BMWindow.js +2000 -0
  41. package/build/Core/BMAnimationContext.js +1181 -0
  42. package/build/Core/BMColor.js +991 -0
  43. package/build/Core/BMCoreUI.js +470 -0
  44. package/build/Core/BMFunctionCollection.js +110 -0
  45. package/build/Core/BMIndexPath.js +165 -0
  46. package/build/Core/BMInset.js +138 -0
  47. package/build/Core/BMKeyPath.js +100 -0
  48. package/build/Core/BMPoint.js +291 -0
  49. package/build/Core/BMRect.js +556 -0
  50. package/build/Core/BMSize.js +137 -0
  51. package/build/iScroll/LICENSE +22 -0
  52. package/build/iScroll/iscroll-probe.js +2154 -0
  53. package/build/images/AlignBottom.png +0 -0
  54. package/build/images/AlignCenterX.png +0 -0
  55. package/build/images/AlignCenterY.png +0 -0
  56. package/build/images/AlignLeading.png +0 -0
  57. package/build/images/AlignTop.png +0 -0
  58. package/build/images/AlignTrailing.png +0 -0
  59. package/build/images/AllConstraints.png +0 -0
  60. package/build/images/BottomConstraint.png +0 -0
  61. package/build/images/CenterXConstraint.png +0 -0
  62. package/build/images/CenterYConstraint.png +0 -0
  63. package/build/images/CoreUI2.png +0 -0
  64. package/build/images/CoreUI2@2x.png +0 -0
  65. package/build/images/Desktop.png +0 -0
  66. package/build/images/DesktopMini.png +0 -0
  67. package/build/images/EqualHeight.png +0 -0
  68. package/build/images/EqualHorizontalSpacing.png +0 -0
  69. package/build/images/EqualHorizontalSpacingInSuperview.png +0 -0
  70. package/build/images/EqualVerticalSpacing.png +0 -0
  71. package/build/images/EqualVerticalSpacingInSuperview.png +0 -0
  72. package/build/images/EqualWidth.png +0 -0
  73. package/build/images/HeightConstraint.png +0 -0
  74. package/build/images/InactiveConstraints.png +0 -0
  75. package/build/images/Layout.png +0 -0
  76. package/build/images/LayoutVariables.png +0 -0
  77. package/build/images/LeftConstraint.png +0 -0
  78. package/build/images/OwnConstraints.png +0 -0
  79. package/build/images/Phone.png +0 -0
  80. package/build/images/PhoneLandscape.png +0 -0
  81. package/build/images/PhoneLandscapeMini.png +0 -0
  82. package/build/images/PhoneMini.png +0 -0
  83. package/build/images/PhonePortrait.png +0 -0
  84. package/build/images/PhonePortraitMini.png +0 -0
  85. package/build/images/Properties.png +0 -0
  86. package/build/images/RightConstraint.png +0 -0
  87. package/build/images/SubviewConstraints.png +0 -0
  88. package/build/images/Tablet.png +0 -0
  89. package/build/images/TabletLandscape.png +0 -0
  90. package/build/images/TabletLandscapeMini.png +0 -0
  91. package/build/images/TabletMini.png +0 -0
  92. package/build/images/TabletPortrait.png +0 -0
  93. package/build/images/TabletPortraitMini.png +0 -0
  94. package/build/images/TopConstraint.png +0 -0
  95. package/build/images/WidthConstraint.png +0 -0
  96. package/build/index.js +40 -0
  97. package/lib/@types/BMCoreUI.min.d.ts +14078 -0
  98. package/lib/BMCoreUI.min.js +1 -0
  99. package/package.json +58 -0
@@ -0,0 +1,4467 @@
1
+ // @ts-check
2
+
3
+ import {YES, NO, BMExtend} from '../Core/BMCoreUI'
4
+ import {BMInsetMake} from '../Core/BMInset'
5
+ import {BMSizeMake} from '../Core/BMSize'
6
+ import {BMRectMake} from '../Core/BMRect'
7
+ import {BMIndexPathMakeWithIndexes, BMIndexPathMakeWithRow} from '../Core/BMIndexPath'
8
+ import {BMCollectionViewLayoutAttributesMakeForCellAtIndexPath, BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier} from './BMCollectionViewLayoutAttributes'
9
+ import {BMLayoutConstraint, BMLayoutAttribute, BMLayoutConstraintRelation} from '../BMView/BMLayoutConstraint_v2.5'
10
+ import {BMCollectionViewLayout} from './BMCollectionViewLayout'
11
+
12
+ // When set to YES, this will cause flow layout to log messages to the console when the layout size changes as a
13
+ // result of performing cell measurements
14
+ const BM_AUTOMATIC_CELL_SIZE_MESSAGES = NO;
15
+
16
+ // @type BMCollectionViewTableLayoutSupplementaryView
17
+
18
+ /**
19
+ * Constants representing the types of supplementary views that table layouts support.
20
+ */
21
+ export var BMCollectionViewTableLayoutSupplementaryView = Object.freeze({
22
+ /**
23
+ * Indicates that this supplementary view is a footer.
24
+ */
25
+ Footer: "Footer", // <enum>
26
+
27
+ /**
28
+ * Indicates that this supplementary view is a header.
29
+ */
30
+ Header: "Header", // <enum>
31
+
32
+ /**
33
+ * Indicates that this supplementary is an empty content view.
34
+ */
35
+ Empty: "Empty" // <enum>
36
+ });
37
+
38
+ // @type
39
+
40
+ // @type BMCollectionViewTableLayout extends BMCollectionViewLayout
41
+
42
+ /**
43
+ * @deprecated Use `BMCollectionViewFlowLayout` with the `maximumCellsPerRow` property set to `1`.
44
+ * --------------------------------------
45
+ * A basic layout implementation, the BMCollectionViewTableLayout will lay out its elements as a list where each row can have
46
+ * either a fixed height or a variable height.
47
+ *
48
+ * If the row heights are variable, the collection must have a delegate that implements the
49
+ * `collectionViewRowHeightForCellAtIndexPath(_, _)` method.
50
+ *
51
+ * Optionally, the table layout may also generate supplementary views for section headers and section footers.
52
+ */
53
+ export function BMCollectionViewTableLayout() {}; // <constructor>
54
+
55
+ /**
56
+ * A value which may be assigned to the rowHeight property to indicate that the row heights vary by row.
57
+ */
58
+ export var BMCollectionViewTableLayoutRowHeightVariable = -1; // <Number>
59
+
60
+ BMCollectionViewTableLayout.prototype = BMExtend(Object.create(BMCollectionViewLayout.prototype), {
61
+ constructor: BMCollectionViewTableLayout,
62
+
63
+ // @override - BMCollectionViewLayout
64
+ get supportsCopying() {
65
+ return YES;
66
+ },
67
+
68
+ /**
69
+ * The row height to use for each row.
70
+ * This can either be a specific size in pixels or BMCollectionViewTableLayoutRowHeightVariable if each row can have a different height.
71
+ * If the row heights are variable, the collection must have a delegate that implements the Number collectionViewRowHeightForCellAtIndexPath(BMCollectionView, BMIndexPath) method.
72
+ */
73
+ _rowHeight: 44, // <Number>
74
+
75
+ get rowHeight() { return this._rowHeight; },
76
+ set rowHeight(rowHeight) {
77
+ this._rowHeight = rowHeight;
78
+ this.invalidateLayout();
79
+ },
80
+
81
+ /**
82
+ * The minimum width to use for each row.
83
+ * If this value is greater than the collection view's frame width, the rows will have this width applied to them.
84
+ */
85
+ _minimumWidth: 0, // <Number>
86
+
87
+ get minimumWidth() { return this._minimumWidth; },
88
+ set minimumWidth(width) {
89
+ this._minimumWidth = width;
90
+ this.invalidateLayout();
91
+ },
92
+
93
+ /**
94
+ * Determines whether or not the table layout should generate header supplementary views or not.
95
+ */
96
+ _showsHeaders: NO, // <Boolean>
97
+
98
+ get showsHeaders() { return this._showsHeaders; },
99
+ set showsHeaders(showsHeaders) {
100
+ this._showsHeaders = showsHeaders;
101
+ this.invalidateLayout();
102
+ },
103
+
104
+ /**
105
+ * The height to use for headers. Does not have any effect if showsHeaders is set to NO.
106
+ * This attribute is always 0 if showsHeader is set to NO.
107
+ */
108
+ _headerHeight: 22, // <Number>
109
+
110
+ get headerHeight() {
111
+ return this._showsHeaders ? this._headerHeight : 0;
112
+ },
113
+
114
+ set headerHeight(headerHeight) {
115
+ this._headerHeight = headerHeight;
116
+ if (this._showsHeader) this.invalidateLayout();
117
+ },
118
+
119
+ /**
120
+ * Determines whether or not the table layout should generate footer supplementary views or not.
121
+ */
122
+ _showsFooters: NO, // <Boolean>
123
+
124
+ get showsFooters() { return this._showsFooters; },
125
+ set showsFooters(showsFooters) {
126
+ this._showsFooters = showsFooters;
127
+ this.invalidateLayout();
128
+ },
129
+
130
+ /**
131
+ * The height to use for footers. Does not have any effect if showsFooters is set to NO.
132
+ * This attribute is always 0 if showsFooters is set to NO.
133
+ */
134
+ _footerHeight: 22, // <Number>
135
+
136
+ get footerHeight() {
137
+ return this._showsFooters? this._footerHeight : 0;
138
+ },
139
+
140
+ set footerHeight(footerHeight) {
141
+ this._footerHeight = footerHeight;
142
+ if (this._showsFooters) this.invalidateLayout();
143
+ },
144
+
145
+ /**
146
+ * The insets to apply to each section which control their distance from the collection view edges and from other sections.
147
+ */
148
+ _sectionInsets: BMInsetMake(), // <BMInset, nullResettable>
149
+
150
+ get sectionInsets() { return this._sectionInsets; },
151
+ set sectionInsets(insets) {
152
+ this._sectionInsets = insets || BMInsetMake();
153
+ this.invalidateLayout();
154
+ },
155
+
156
+ /**
157
+ * When set to YES, the table layout will stick the header supplementary views to the top edge of the collection view while scrolling.
158
+ * Otherwise, the header supplementary views will scroll with the rest of the content.
159
+ */
160
+ _pinsHeadersToContentEdge: NO, // <Boolean>
161
+
162
+ get pinsHeadersToContentEdge() { return this._pinsHeadersToContentEdge; },
163
+ set pinsHeadersToContentEdge(pins) {
164
+ this._pinsHeadersToContentEdge = pins;
165
+ this.invalidateLayout();
166
+ },
167
+
168
+ /**
169
+ * When set to YES, the table layout will stick the footer supplementary views to the bottom edge of the collection view while scrolling.
170
+ * Otherwise, the footer supplementary views will scroll with the rest of the content.
171
+ */
172
+ _pinsFootersToContentEdge: NO, // <Boolean>
173
+
174
+ get pinsFootersToContentEdge() { return this._pinsFootersToContentEdge; },
175
+ set pinsFootersToContentEdge(pins) {
176
+ this._pinsFootersToContentEdge = pins;
177
+ this.invalidateLayout();
178
+ },
179
+
180
+ // The cached content size.
181
+ _cachedContentSize: undefined, // <BMSize>
182
+
183
+ // @override - BMCollectionViewLayout
184
+ constraintsForMeasuringCell(cell, {atIndexPath}) {
185
+ // In table layout, cells are required to be as wide as the collection view's frame
186
+ return [BMLayoutConstraint.constraintWithView(cell, {attribute: BMLayoutAttribute.Width, constant: this.collectionView.frame.size.width})]
187
+ },
188
+
189
+ prepareLayout() {
190
+ return this.contentSize();
191
+ },
192
+
193
+ // @override
194
+ contentSize: function () {
195
+
196
+ // Each section adds height for its insets and headers and footers
197
+ var insetTop = this._sectionInsets.top;
198
+ var insetBottom = this._sectionInsets.bottom;
199
+
200
+ var headerHeight = this.headerHeight;
201
+ var footerHeight = this.footerHeight;
202
+
203
+ var height = 0;
204
+
205
+ //var sections = this.collectionView.sections;
206
+ var sectionCount = this.collectionView.numberOfSections();
207
+ height += sectionCount * (headerHeight + footerHeight + insetTop + insetBottom);
208
+
209
+ if (this._rowHeight >= 0) {
210
+
211
+ for (var i = 0; i < sectionCount; i++) {
212
+ height += this._rowHeight * this.collectionView.numberOfObjectsInSectionAtIndex(i);
213
+ }
214
+
215
+ }
216
+ else {
217
+
218
+ // If the rows have variable heights, the layout must query the delegate for each item's size
219
+ for (var i = 0; i < sectionCount; i++) {
220
+ var itemCount = this.collectionView.numberOfObjectsInSectionAtIndex(i);
221
+ for (var j = 0; j < itemCount; j++) {
222
+ height += this.collectionView.delegate.collectionViewRowHeightForCellAtIndexPath(
223
+ this.collectionView,
224
+ this.collectionView.indexPathForObjectAtRow(j, {inSectionAtIndex: i})
225
+ );
226
+ }
227
+ }
228
+
229
+ }
230
+
231
+ this._cachedContentSize = BMSizeMake(
232
+ Math.max(this.collectionView.frame.size.width, this.minimumWidth + this.collectionView.scrollBarSize),
233
+ Math.max(this.collectionView.frame.size.height, height)
234
+ );
235
+ return this._cachedContentSize;
236
+
237
+ },
238
+
239
+ // @override - BMCollectionViewLayout
240
+ shouldInvalidateLayoutForBoundsChange: function (bounds) {
241
+ return this._pinsHeadersToContentEdge || this._pinsFootersToContentEdge;
242
+ },
243
+
244
+ // @override - BMCollectionViewTableLayout
245
+ shouldInvalidateLayoutForFrameChange() {
246
+ // Because the measured size of the cells is dependent upon the size of collection view's frame, in addition
247
+ // to invalidating the layout, the measured size of cells should also be invalidated.
248
+ this.collectionView.invalidateMeasuredSizeOfCells();
249
+ return YES;
250
+ },
251
+
252
+ // @override
253
+ attributesForCellAtIndexPath: function (indexPath) {
254
+ var row = indexPath.row;
255
+ var section = indexPath.section;
256
+
257
+ var sectionPaddingTop = this._sectionInsets.top + this.headerHeight;
258
+ var sectionPaddingBottom = this._sectionInsets.bottom + this.footerHeight;
259
+
260
+ var top = (section + 1) * sectionPaddingTop + section * sectionPaddingBottom;
261
+
262
+ var useOffset = this._cachedContentSize.height > this.collectionView.frame.size.height;
263
+
264
+ if (this._rowHeight >= 0) {
265
+ // The count has to include the current section, so it includes objects that may be in the same section before the current object
266
+ for (var i = 0; i <= section; i++) {
267
+ // If the count has reached the current section, it will stop at the current object rather than the end of the section
268
+ var itemCount = i == section ? row : this.collectionView.numberOfObjectsInSectionAtIndex(i);
269
+
270
+ top += this._rowHeight * itemCount;
271
+ }
272
+ }
273
+ else {
274
+
275
+ // If the rows have variable heights, the layout must query the delegate for each item's size
276
+
277
+ // The count has to include the current section, so it includes objects that may be in the same section before the current object
278
+ for (var i = 0; i <= section; i++) {
279
+
280
+ // If the count has reached the current section, it will stop at the current object rather than the end of the section
281
+ var itemCount = i == section ? row : this.collectionView.numberOfObjectsInSectionAtIndex(i);
282
+
283
+ for (var j = 0; j < itemCount; j++) {
284
+ top += this.collectionView.delegate.collectionViewRowHeightForCellAtIndexPath(
285
+ this.collectionView,
286
+ this.collectionView.indexPathForObjectAtRow(j, {inSectionAtIndex: i})
287
+ );
288
+ }
289
+ }
290
+ }
291
+
292
+ var frame = BMRectMake(this._sectionInsets.left,
293
+ top,
294
+ Math.max(this.collectionView.frame.size.width, this.minimumWidth + this.collectionView.scrollBarSize) - this._sectionInsets.left - this._sectionInsets.right - (useOffset ? this.collectionView.scrollBarSize : 0),
295
+ this._rowHeight >= 0 ? this._rowHeight : this.collectionView.delegate.collectionViewRowHeightForCellAtIndexPath(
296
+ this.collectionView,
297
+ indexPath
298
+ )
299
+ );
300
+
301
+ var attributes = BMCollectionViewLayoutAttributesMakeForCellAtIndexPath(indexPath);
302
+ attributes.frame = frame;
303
+
304
+ return attributes;
305
+
306
+ },
307
+
308
+ /**
309
+ * Retrieves the base attributes for the given supplementary view, before adjusting for pinned headers or footers.
310
+ * @param identifier <String> The supplementary view's type identifier.
311
+ * {
312
+ * @param atIndexPath <BMIndexPath> The supplementary view's index path.
313
+ * }
314
+ * @return <BMCollectionViewLayoutAttributes> The index path with additional information.
315
+ */
316
+ attributesForSupplementaryViewWithIdentifier: function (identifier, options) {
317
+
318
+ var useOffset = this._cachedContentSize.height > this.collectionView.frame.size.height;
319
+
320
+ // The empty supplementary view will always ever have one instance with a preset index path
321
+ if (identifier === BMCollectionViewTableLayoutSupplementaryView.Empty) {
322
+ var attribute = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
323
+ BMCollectionViewTableLayoutSupplementaryView.Empty,
324
+ {atIndexPath: BMIndexPathMakeWithIndexes([0])}
325
+ );
326
+
327
+ attribute.frame = BMRectMake(0, 0, this.collectionView.frame.size.width, this.collectionView.frame.size.height);
328
+ return attribute;
329
+ }
330
+
331
+ var section = options.atIndexPath.section;
332
+
333
+ if (section >= this.collectionView.numberOfSections()) return undefined;
334
+
335
+ // The caret moves through the index paths until it reaches the target section
336
+ var caret = this.sectionInsets.top;
337
+
338
+ var hasVariableRowHeights = this._rowHeight < 0;
339
+ var sectionSpacing = this.sectionInsets.top + this.sectionInsets.bottom;
340
+
341
+ var cellPositionX = this.sectionInsets.left;
342
+ var cellWidth = Math.max(this.collectionView.frame.size.width, this.minimumWidth + this.collectionView.scrollBarSize) - this.sectionInsets.left - this.sectionInsets.right - (useOffset ? this.collectionView.scrollBarSize : 0);
343
+
344
+ // These variables are used to verify if the target section is the topmost or bottommost one,
345
+ // to pin their header and footers to collection view edges if needed
346
+ var isTopmostSection = NO;
347
+ var isBottommostSection = NO;
348
+
349
+ var scrollOffset = this.collectionView.scrollOffset;
350
+ var frame = this.collectionView.frame;
351
+
352
+ var topEdge;
353
+
354
+ // Skip all index paths until the target section
355
+ for (var i = 0; i < section; i++) {
356
+
357
+ topEdge = caret + sectionSpacing;
358
+
359
+ // Skip headers, footers and insets
360
+ caret += this.headerHeight + this.footerHeight + sectionSpacing;
361
+
362
+ var objectCount = this.collectionView.numberOfObjectsInSectionAtIndex(i);
363
+
364
+ // Skip the contents
365
+ if (hasVariableRowHeights) {
366
+ for (var j = 0; j < objectCount; j++) {
367
+ caret += this.collectionView.delegate.collectionViewRowHeightForCellAtIndexPath(this.collectionView, this.collectionView.indexPathForObjectAtRow(j, {inSectionAtIndex: i}));
368
+ }
369
+ }
370
+ else {
371
+ caret += objectCount * this._rowHeight;
372
+ }
373
+ }
374
+
375
+ // Verify if this section is the topmost or bottommost section
376
+ if (topEdge < scrollOffset.y && caret > scrollOffset.y) {
377
+ isTopmostSection = YES;
378
+ }
379
+ else {
380
+ isTopmostSection = NO;
381
+ }
382
+
383
+ if (topEdge > scrollOffset.y && caret > scrollOffset.y + frame.size.height) {
384
+ isBottommostSection = YES;
385
+ }
386
+ else {
387
+ isBottommostSection = NO;
388
+ }
389
+
390
+ if (identifier === BMCollectionViewTableLayoutSupplementaryView.Header) {
391
+ // For headers, the caret will stop directly at the specified header
392
+ var attributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(BMCollectionViewTableLayoutSupplementaryView.Header, {atIndexPath: options.atIndexPath});
393
+ attributes.frame = BMRectMake(cellPositionX, caret, cellWidth, this.headerHeight);
394
+
395
+ // If this header is in the topmost section and the layout pins headers to content edges, adjust the position of the frame
396
+ if (this._pinsHeadersToContentEdge && isTopmostSection) {
397
+ attributes.frame.origin.y = Math.max(attributes.frame.origin.y, scrollOffset.y);
398
+ // Float the header above the content
399
+ attributes.style.zIndex = 1;
400
+ }
401
+
402
+ return attributes;
403
+ }
404
+ else if (identifier === BMCollectionViewTableLayoutSupplementaryView.Footer) {
405
+ // For footers, the caret will stop at the end of the section
406
+
407
+ // Skip the contents
408
+ var objectCount = this.collectionView.numberOfObjectsInSectionAtIndex(i);
409
+ if (hasVariableRowHeights) {
410
+ for (var j = 0; j < objectCount; j++) {
411
+ caret += this.collectionView.delegate.collectionViewRowHeightForCellAtIndexPath(this.collectionView, this.collectionView.indexPathForObjectAtRow(j, {inSectionAtIndex: i}));
412
+ }
413
+ }
414
+ else {
415
+ caret += objectCount * this._rowHeight;
416
+ }
417
+
418
+ // And skip the header as well
419
+ caret += this.headerHeight;
420
+
421
+ var attributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(BMCollectionViewTableLayoutSupplementaryView.Footer, {atIndexPath: options.atIndexPath});
422
+ attributes.frame = BMRectMake(cellPositionX, caret, cellWidth, this.footerHeight);
423
+
424
+ // If this footer is in the bottommost section and the layout pins footers to content edges, adjust the position of the frame
425
+ if (this._pinsHeadersToContentEdge && isBottommostSection) {
426
+ attributes.frame.origin.y = Math.min(attributes.frame.origin.y, scrollOffset.y + this.collectionView.frame.size.height - attributes.frame.size.height);
427
+ // Float the header above the content
428
+ attributes.style.zIndex = 1;
429
+ }
430
+
431
+ return attributes;
432
+ }
433
+
434
+ // Other types of supplementary views are not supported.
435
+ return undefined;
436
+
437
+ },
438
+
439
+ /**
440
+ * @deprecated Currently unused.
441
+ * Retrieves the index path of the cell that intersects the given point.
442
+ * The x position of the point is ignored; this method assumes that all rows have infinite width.
443
+ * If no cell intersects the point, this method will either return the previous
444
+ * or the next closest index path, depending on its direction parameter.
445
+ * @param point <BMPoint> The point.
446
+ * {
447
+ * @param directionNext <Boolean, nullable> Defaults to NO. If set to YES, this method will return
448
+ * the next closest index path to the given point if no cell intersects it;
449
+ * otherwise the previous closest index path is returned.
450
+ * }
451
+ * @return <BMIndexPath, nullable> The intersecting index path or undefined if the collection view contains no data.
452
+ */
453
+ indexPathAtPoint: function (point, args) {
454
+ args = args || {};
455
+ var returnNext = args.directionNext;
456
+
457
+ var y = point.y;
458
+
459
+ // To determine the index path, the caret will continue to move through the index paths until it stops at the requested point.
460
+ var caret = 0;
461
+
462
+ var sectionPaddingTop = this._sectionInsets.top + this.headerHeight;
463
+ var sectionPaddingBottom = this._sectionInsets.bottom + this.footerHeight;
464
+
465
+ if (y <= sectionPaddingTop) return this.collectionView.indexPathForObjectAtRow(0, {inSectionAtIndex: 0});
466
+
467
+ caret = sectionPaddingTop;
468
+
469
+ var sectionCount = this.collectionView.numberOfSections();
470
+
471
+ if (this._rowHeight >= 0) {
472
+ for (var i = 0; i < sectionCount; i++) {
473
+ var objectCount = this.collectionView.numberOfObjectsInSectionAtIndex(i);
474
+
475
+
476
+ // Check if the requested point is less than the point obtained by adding the heights of the rows in the current section
477
+ // If it is, the requested point will be on one of these rows
478
+ if (y <= caret + objectCount * this._rowHeight) {
479
+ // The caret intersects one of the rows
480
+ var distance = y - caret;
481
+ var row = Math.floor(distance / this._rowHeight) | 0;
482
+
483
+ return this.collectionView.indexPathForObjectAtRow(row, {inSectionAtIndex: i});
484
+ }
485
+
486
+ // Otherwise advance the caret past these rows
487
+ caret += objectCount * this._rowHeight;
488
+
489
+ // If the requested point is less than the point obtained by adding the paddings between sections, the point falls between the current section
490
+ // and the next one
491
+ if (y <= caret + sectionPaddingBottom + sectionPaddingTop) {
492
+ // The caret doesn't intersect any row
493
+
494
+ if (returnNext) {
495
+ if (i < sectionCount - 1) {
496
+ // TODO: Handle empty sections
497
+ return this.collectionView.indexPathForObjectAtRow(0, {inSectionAtIndex: i + 1});
498
+ }
499
+ else {
500
+ // TODO: Handle empty sections
501
+ // If this is the last section, there is no next item to return
502
+ return this.collectionView.indexPathForObjectAtRow(objectCount - 1, {inSectionAtIndex: i});
503
+ }
504
+ }
505
+ else {
506
+ // TODO: Handle empty sections
507
+ // If the previous item should be returned, it is the last item in the current section
508
+ return this.collectionView.indexPathForObjectAtRow(objectCount - 1, {inSectionAtIndex: i});
509
+ }
510
+ }
511
+
512
+ // Otherwise advance the caret to the next section
513
+ caret += sectionPaddingBottom + sectionPaddingTop;
514
+ }
515
+
516
+ // TODO: Handle empty sections
517
+ // If none of the previous cases has returned any index path, the target point falls below all the content and the only closest item is the last one
518
+ return this.collectionView.indexPathForObjectAtRow(this.collectionView.numberOfObjectsInSectionAtIndex(sectionCount - 1), {inSectionAtIndex: sectionCount - 1});
519
+ }
520
+ else {
521
+ for (var i = 0; i < sectionCount; i++) {
522
+ var objectCount = this.collectionView.numberOfObjectsInSectionAtIndex(i);
523
+
524
+ // When dealing with variable height rows, each row must be treated separately
525
+ for (var j = 0; j < objectCount; j++) {
526
+ var rowHeight = this.collectionView.delegate.collectionViewRowHeightForCellAtIndexPath(
527
+ this.collectionView,
528
+ this.collectionView.indexPathForObjectAtRow(j, {inSectionAtIndex: i})
529
+ );
530
+
531
+ // The logic is similar to the one used for fixed-height rows, except that each row
532
+ // must be verified separately rather than in bulk.
533
+ if (y <= caret + rowHeight) {
534
+ // The point intesects this row
535
+ return this.collectionView.indexPathForObjectAtRow(j, {inSectionAtIndex: i});
536
+ }
537
+
538
+ // Otherwise advance to the next one
539
+ caret += rowHeight;
540
+ }
541
+
542
+ // If the requested point is less than the point obtained by adding the paddings between sections, the point falls between the current section
543
+ // and the next one
544
+ if (y <= caret + sectionPaddingBottom + sectionPaddingTop) {
545
+ // The caret doesn't intersect any row
546
+
547
+ if (returnNext) {
548
+ if (i < sectionCount - 1) {
549
+ return this.collectionView.indexPathForObjectAtRow(0, {inSectionAtIndex: i + 1});
550
+ }
551
+ else {
552
+ // If this is the last section, there is no next item to return
553
+ return this.collectionView.indexPathForObjectAtRow(objectCount - 1, {inSectionAtIndex: i});
554
+ }
555
+ }
556
+ else {
557
+ // If the previous item should be returned, it is the last item in the current section
558
+ return this.collectionView.indexPathForObjectAtRow(objectCount - 1, {inSectionAtIndex: i});
559
+ }
560
+ }
561
+
562
+ // Otherwise advance the caret to the next section
563
+ caret += sectionPaddingBottom + sectionPaddingTop;
564
+ }
565
+
566
+ // If none of the previous cases has returned any index path, the target point falls below all the content and the only closest item is the last one
567
+ return this.collectionView.indexPathForObjectAtRow(this.collectionView.numberOfObjectsInSectionAtIndex(sectionCount - 1), {inSectionAtIndex: sectionCount - 1});
568
+ }
569
+
570
+ },
571
+
572
+ // @override
573
+ attributesForElementsInRect: function (rect) {
574
+
575
+ var useOffset = this._cachedContentSize.height > this.collectionView.frame.size.height;
576
+
577
+ // When there are no sections in the collection view, this method will always return a single instance of attributes
578
+ // for the empty supplementary view
579
+ if (this.collectionView.numberOfSections() == 0) {
580
+ var attribute = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
581
+ BMCollectionViewTableLayoutSupplementaryView.Empty,
582
+ {atIndexPath: BMIndexPathMakeWithIndexes([0])}
583
+ );
584
+
585
+ attribute.frame = BMRectMake(0, 0, this.collectionView.frame.size.width, this.collectionView.frame.size.height);
586
+ return [attribute];
587
+ }
588
+
589
+ // The caret moves through the index paths until it reaches the end of the target rect
590
+ var caret = this._sectionInsets.top;
591
+
592
+ var hasVariableRowHeights = this._rowHeight < 0;
593
+
594
+ var sectionPaddingTop = this._sectionInsets.top + this.headerHeight;
595
+ var sectionPaddingBottom = this._sectionInsets.bottom + this.footerHeight;
596
+
597
+ var cellPositionX = this.sectionInsets.left;
598
+ var cellWidth = Math.max(this.collectionView.frame.size.width, this.minimumWidth + this.collectionView.scrollBarSize) - this.sectionInsets.left - this.sectionInsets.right - (useOffset ? this.collectionView.scrollBarSize : 0);
599
+
600
+ var topLimit = rect.origin.y;
601
+ var bottomLimit = rect.bottom;
602
+
603
+ var section = 0;
604
+ var row = -1;
605
+
606
+ // Skip the index paths that do not intersect this rect
607
+ while (true) {
608
+ // Check if the header intersects the rect
609
+ if (caret + this.headerHeight > topLimit) {
610
+ break;
611
+ }
612
+
613
+ // Otherwise move the caret below the header
614
+ caret += this.headerHeight;
615
+ row = 0;
616
+
617
+ if (hasVariableRowHeights) {
618
+ var sectionObjectCount = this.collectionView.numberOfObjectsInSectionAtIndex(section);
619
+
620
+ for (var i = 0; i < sectionObjectCount; i++) {
621
+ var indexPath = this.collectionView.indexPathForObjectAtRow(i, {inSectionAtIndex: section});
622
+ var rowHeight = this.collectionView.delegate.collectionViewRowHeightForCellAtIndexPath(this.collectionView, indexPath);
623
+ if (caret + rowHeight > topLimit) {
624
+ row = i;
625
+ break;
626
+ }
627
+
628
+ caret += rowHeight;
629
+ }
630
+
631
+ row = i;
632
+ }
633
+ else {
634
+ var sectionHeight = this.collectionView.numberOfObjectsInSectionAtIndex(section) * this._rowHeight;
635
+
636
+ // Check if the contents intersect this rect
637
+ if (caret + sectionHeight > topLimit) {
638
+ var targetIndex = Math.floor((topLimit - caret) / this._rowHeight);
639
+ row = targetIndex;
640
+
641
+ caret += targetIndex * this._rowHeight;
642
+ break;
643
+ }
644
+
645
+ // Otherwise move the caret to the footer
646
+ row = this.collectionView.numberOfObjectsInSectionAtIndex(section);
647
+ caret += sectionHeight;
648
+ }
649
+
650
+ // Check if the footer intersects the rect
651
+ if (caret + this.footerHeight > topLimit) {
652
+ break;
653
+ }
654
+
655
+ // Otherwise move on to the next section
656
+ caret += this.footerHeight + this.sectionInsets.top + this.sectionInsets.bottom;
657
+ section++;
658
+ row = -1;
659
+
660
+ // If the section index is out of bounds; there are no elements in the target rect
661
+ if (section >= this.collectionView.numberOfSections()) return [];
662
+
663
+ }
664
+
665
+ // When the while loop exits, the caret is positioned at the first visible index path and attributes may be added
666
+ var attributes = [];
667
+
668
+ var topmostSection = section;
669
+
670
+ // If this layout pins headers to the content edge and the first index path is not this section's header, create its attributes
671
+ // now and add it to the attributes array
672
+ if (this._pinsHeadersToContentEdge && row > -1) {
673
+ var attribute = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
674
+ BMCollectionViewTableLayoutSupplementaryView.Header,
675
+ {atIndexPath: BMIndexPathMakeWithIndexes([section])}
676
+ );
677
+
678
+ attribute.frame = BMRectMake(cellPositionX, this.collectionView.scrollOffset.y, cellWidth, this.headerHeight);
679
+
680
+ // Float the header above the content
681
+ attribute.style.zIndex = 1;
682
+
683
+ attributes.push(attribute);
684
+ }
685
+
686
+ var bottommostSection = section;
687
+ var lastFooterAttributes, lastFooterAttributesSection;
688
+
689
+ // Continue adding attributes until the bottom of the rect is reached
690
+ while (caret < bottomLimit) {
691
+
692
+ // If the section index is out of bounds; there are no more elements in the target rect
693
+ if (section >= this.collectionView.numberOfSections()) break;
694
+
695
+ // Otherwise this might be the bottommost section
696
+ bottommostSection = section;
697
+
698
+ // The caret is at a header
699
+ if (row == -1) {
700
+ if (!this.showsHeaders) {
701
+ row = 0;
702
+ continue;
703
+ }
704
+
705
+ var attribute = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
706
+ BMCollectionViewTableLayoutSupplementaryView.Header,
707
+ {atIndexPath: BMIndexPathMakeWithIndexes([section])}
708
+ );
709
+
710
+ attribute.frame = BMRectMake(cellPositionX, caret, cellWidth, this.headerHeight);
711
+
712
+ // If the layout pins headers to content edges and this is the topmost section, adjust the header's attributes
713
+ if (this._pinsHeadersToContentEdge && section === topmostSection) {
714
+
715
+ // If the header's Y origin is lower than the content edge, the topmost section is an incoming section
716
+ // and the previous section's header should be rendered as outgoing
717
+ if (attribute.frame.origin.y > this.collectionView.scrollOffset.y) {
718
+ var previousSection = section - 1;
719
+ if (previousSection > -1) {
720
+ var previousHeaderAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
721
+ BMCollectionViewTableLayoutSupplementaryView.Header,
722
+ {atIndexPath: BMIndexPathMakeWithIndexes([section - 1])}
723
+ );
724
+ previousHeaderAttributes.frame = BMRectMake(
725
+ cellPositionX,
726
+ Math.min(this.collectionView.scrollOffset.y, attribute.frame.origin.y - this.headerHeight),
727
+ cellWidth,
728
+ this.headerHeight
729
+ );
730
+ previousHeaderAttributes.style.zIndex = 1;
731
+ attributes.push(previousHeaderAttributes);
732
+ }
733
+ }
734
+
735
+
736
+ // Header attributes can only be pinned downwards - this is to resolve the case where the first section has additional padding to the top edge.
737
+ // In that case, the header should remain visible at its regular position, rather than being shifted upwards.
738
+ attribute.frame.origin.y = Math.max(this.collectionView.scrollOffset.y, attribute.frame.origin.y);
739
+
740
+ // Float the header above the content
741
+ attribute.style.zIndex = 1;
742
+ }
743
+
744
+ attributes.push(attribute);
745
+
746
+ caret += this.headerHeight;
747
+ row = 0;
748
+
749
+ continue;
750
+ }
751
+
752
+ // The caret is at a footer
753
+ if (row == this.collectionView.numberOfObjectsInSectionAtIndex(section)) {
754
+ if (!this.showsFooters) {
755
+ row = -1;
756
+ section++;
757
+ caret += this.footerHeight + this.sectionInsets.top + this.sectionInsets.bottom;
758
+ continue;
759
+ }
760
+
761
+ var attribute = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
762
+ BMCollectionViewTableLayoutSupplementaryView.Footer,
763
+ {atIndexPath: BMIndexPathMakeWithIndexes([section])}
764
+ );
765
+
766
+ attribute.frame = BMRectMake(cellPositionX, caret, cellWidth, this.footerHeight);
767
+ attributes.push(attribute);
768
+
769
+ // Retain these attributes in case they have to be adjust for edge pinning
770
+ lastFooterAttributes = attribute;
771
+ lastFooterAttributesSection = section;
772
+
773
+ caret += this.footerHeight + this.sectionInsets.top + this.sectionInsets.bottom;
774
+
775
+ row = -1;
776
+ section++;
777
+
778
+ continue;
779
+ }
780
+
781
+ // If the section index is out of bounds; there are no more elements left to add
782
+ if (section >= this.collectionView.numberOfSections()) return attributes;
783
+
784
+ // If the row doesn't match any of the other conditions, it is inside the section
785
+ var indexPath = this.collectionView.indexPathForObjectAtRow(row, {inSectionAtIndex: section});
786
+ var attribute = BMCollectionViewLayoutAttributesMakeForCellAtIndexPath(indexPath);
787
+ attribute.frame = BMRectMake(cellPositionX,
788
+ caret,
789
+ cellWidth,
790
+ hasVariableRowHeights ? this.collectionView.delegate.collectionViewRowHeightForCellAtIndexPath(this.collectionView, indexPath) : this._rowHeight
791
+ );
792
+
793
+ attributes.push(attribute);
794
+ caret += attribute.frame.size.height;
795
+
796
+ row++;
797
+ }
798
+
799
+ // If the footers are pinned to the content edge, create or retrieve the correct footer attributes for the bottommost section
800
+ // And adjust their position to the bottom edge of the collection view
801
+ if (this._pinsFootersToContentEdge) {
802
+ // Header attributes can only be pinned upwards - this is to resolve the case where the last section has additional padding to the bottom edge.
803
+ // In that case, the footer should remain visible at its regular position, rather than being shifted downwards.
804
+ if (bottommostSection === lastFooterAttributesSection) {
805
+
806
+ // If the footer's Y origin is higher than the content edge, the bottommost section is an outgoing section
807
+ // and the next section's footer should be rendered as incoming
808
+ if (lastFooterAttributes.frame.origin.y < this.collectionView.scrollOffset.y + this.collectionView.frame.size.height - this.footerHeight) {
809
+ var nextSection = lastFooterAttributesSection + 1;
810
+ if (nextSection < this.collectionView.numberOfSections()) {
811
+ var nextFooterAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
812
+ BMCollectionViewTableLayoutSupplementaryView.Footer,
813
+ {atIndexPath: BMIndexPathMakeWithIndexes([lastFooterAttributesSection + 1])}
814
+ );
815
+
816
+ nextFooterAttributes.frame = BMRectMake(
817
+ cellPositionX,
818
+ Math.max(this.collectionView.scrollOffset.y + this.collectionView.frame.size.height - this.footerHeight, lastFooterAttributes.frame.bottom),
819
+ cellWidth,
820
+ this.footerHeight
821
+ );
822
+
823
+ attributes.push(nextFooterAttributes);
824
+ }
825
+ }
826
+
827
+ // If the footer attributes were already created, they just have to be adjusted
828
+ lastFooterAttributes.frame.origin.y = Math.min(lastFooterAttributes.frame.origin.y, this.collectionView.scrollOffset.y + this.collectionView.frame.size.height - lastFooterAttributes.frame.size.height);
829
+
830
+ // Float the header above the content
831
+ lastFooterAttributes.style.zIndex = 1;
832
+ }
833
+ else {
834
+ // If the attributes were not already created, they should be created now
835
+ var attribute = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
836
+ BMCollectionViewTableLayoutSupplementaryView.Footer,
837
+ {atIndexPath: BMIndexPathMakeWithIndexes([bottommostSection])}
838
+ );
839
+
840
+ // If the attributes were not already created, then there is no chance they would appear above the bottom content edge, as they would normally
841
+ // appear below the currently visible area
842
+ attribute.frame = BMRectMake(cellPositionX, this.collectionView.scrollOffset.y + this.collectionView.frame.size.height - this.footerHeight, cellWidth, this.footerHeight);
843
+
844
+ // Float the header above the content
845
+ attribute.style.zIndex = 1;
846
+
847
+ attributes.push(attribute);
848
+ }
849
+ }
850
+
851
+ return attributes;
852
+
853
+ },
854
+
855
+ // @override - BMCollectionViewLayout
856
+ initialAttributesForPresentedCellAtIndexPath: function (indexPath) {
857
+ var attributes = this.attributesForCellAtIndexPath(indexPath).copy();
858
+
859
+ if (this.collectionView.delegate && this.collectionView.delegate.collectionViewInitialAttributesForPresentedCellAtIndexPath) {
860
+ return this.collectionView.delegate.collectionViewInitialAttributesForPresentedCellAtIndexPath(this.collectionView, indexPath, {withTargetAttributes: attributes});
861
+ }
862
+
863
+ attributes.frame.origin.x += attributes.frame.size.width / 2;
864
+
865
+ attributes.style = {
866
+ opacity: 0,
867
+ translateZ: 0
868
+ };
869
+
870
+ return attributes;
871
+ },
872
+
873
+ // @override - BMCollectionViewLayout
874
+ initialAttributesForPresentedSupplementaryViewWithIdentifier: function (identifier, options) {
875
+ var attributes = this.attributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath}).copy();
876
+
877
+ if (this.collectionView.delegate && this.collectionView.delegate.collectionViewInitialAttributesForPresentedSupplementaryViewWithIdentifier) {
878
+ return this.collectionView.delegate.collectionViewInitialAttributesForPresentedSupplementaryViewWithIdentifier(this.collectionView, identifier, {atIndexPath: options.atIndexPath, withTargetAttributes: attributes});
879
+ }
880
+
881
+ attributes.frame.origin.x += attributes.frame.size.width / 2;
882
+
883
+ attributes.style = {
884
+ opacity: 0,
885
+ translateZ: 0
886
+ };
887
+
888
+ return attributes;
889
+ },
890
+
891
+ // @override
892
+ initialAttributesForAppearingCellAtIndexPath: function (indexPath) {
893
+ var attributes = this.attributesForCellAtIndexPath(indexPath).copy();
894
+
895
+ if (this.collectionView.delegate && this.collectionView.delegate.collectionViewInitialAttributesForAppearingCellAtIndexPath) {
896
+ return this.collectionView.delegate.collectionViewInitialAttributesForAppearingCellAtIndexPath(this.collectionView, indexPath, {withTargetAttributes: attributes});
897
+ }
898
+
899
+ attributes.frame.origin.x += attributes.frame.size.width / 2;
900
+ attributes.style = {opacity: 0, translateZ: 0};
901
+
902
+ return attributes;
903
+
904
+ },
905
+
906
+ // @override
907
+ initialAttributesForAppearingSupplementaryViewWithIdentifier: function (identifier, options) {
908
+ var attributes = this.attributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath}).copy();
909
+
910
+ if (this.collectionView.delegate && this.collectionView.delegate.collectionViewInitialAttributesForAppearingSupplementaryViewWithIdentifier) {
911
+ return this.collectionView.delegate.collectionViewInitialAttributesForAppearingSupplementaryViewWithIdentifier(this.collectionView, identifier, {atIndexPath: options.atIndexPath, withTargetAttributes: attributes});
912
+ }
913
+
914
+ attributes.frame.origin.x += attributes.frame.size.width / 2;
915
+ attributes.style = {opacity: 0, translateZ: 0};
916
+
917
+ return attributes;
918
+
919
+ },
920
+
921
+ // @override
922
+ initialAttributesForMovingCellFromIndexPath: function (indexPath, options) {
923
+ var attributes;
924
+ var self = this;
925
+ this.collectionView.usingOldDataSet(function () {
926
+ attributes = self.attributesForCellAtIndexPath(indexPath);
927
+ });
928
+
929
+ return attributes;
930
+ },
931
+
932
+ // @override
933
+ initialAttributesForMovingSupplementaryViewWithIdentifier: function (identifier, options) {
934
+ var attributes;
935
+ var self = this;
936
+ this.collectionView.usingOldDataSet(function () {
937
+ attributes = self.attributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath});
938
+ });
939
+
940
+ return attributes;
941
+ },
942
+
943
+ // @override
944
+ supplementaryViewsToInsert: function () {
945
+ var count = this.collectionView.numberOfSections();
946
+ var oldCount;
947
+ var self = this;
948
+ this.collectionView.usingOldDataSet(function () {
949
+ oldCount = self.collectionView.numberOfSections();
950
+ });
951
+
952
+ var views = [];
953
+
954
+ for (var i = oldCount; i < count; i++) {
955
+ if (this.showsHeaders) {
956
+ views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
957
+ BMCollectionViewTableLayoutSupplementaryView.Header,
958
+ {atIndexPath: BMIndexPathMakeWithIndexes([i])}
959
+ ));
960
+ }
961
+
962
+ if (this.showsFooters) {
963
+ views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
964
+ BMCollectionViewTableLayoutSupplementaryView.Footer,
965
+ {atIndexPath: BMIndexPathMakeWithIndexes([i])}
966
+ ));
967
+ }
968
+ }
969
+
970
+ if (oldCount > 0 && count == 0) {
971
+ views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
972
+ BMCollectionViewTableLayoutSupplementaryView.Empty,
973
+ {atIndexPath: BMIndexPathMakeWithIndexes([0])}
974
+ ));
975
+ }
976
+
977
+ return views;
978
+ },
979
+
980
+ // @override
981
+ supplementaryViewsToDelete: function () {
982
+ var count = this.collectionView.numberOfSections();
983
+ var oldCount;
984
+ var self = this;
985
+ this.collectionView.usingOldDataSet(function () {
986
+ oldCount = self.collectionView.numberOfSections();
987
+ });
988
+
989
+ var views = [];
990
+
991
+ for (var i = count; i < oldCount; i++) {
992
+ if (this.showsHeaders) {
993
+ views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
994
+ BMCollectionViewTableLayoutSupplementaryView.Header,
995
+ {atIndexPath: BMIndexPathMakeWithIndexes([i])}
996
+ ));
997
+ }
998
+
999
+ if (this.showsFooters) {
1000
+ views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
1001
+ BMCollectionViewTableLayoutSupplementaryView.Footer,
1002
+ {atIndexPath: BMIndexPathMakeWithIndexes([i])}
1003
+ ));
1004
+ }
1005
+ }
1006
+
1007
+ if (oldCount == 0 && count > 0) {
1008
+ views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
1009
+ BMCollectionViewTableLayoutSupplementaryView.Empty,
1010
+ {atIndexPath: BMIndexPathMakeWithIndexes([0])}
1011
+ ));
1012
+ }
1013
+
1014
+ return views;
1015
+ },
1016
+
1017
+ // @override
1018
+ finalAttributesForDisappearingCellAtIndexPath: function (indexPath) {
1019
+ var attributes;
1020
+ var self = this;
1021
+
1022
+ this.collectionView.usingOldDataSet(function () {
1023
+ attributes = self.attributesForCellAtIndexPath(indexPath).copy();
1024
+
1025
+ if (self.collectionView.delegate && self.collectionView.delegate.collectionViewFinalAttributesForDisappearingCellAtIndexPath) {
1026
+ attributes = self.collectionView.delegate.collectionViewFinalAttributesForDisappearingCellAtIndexPath(self.collectionView, indexPath, {withTargetAttributes: attributes});
1027
+ }
1028
+ else {
1029
+ attributes.frame.origin.x -= attributes.frame.size.width / 2;
1030
+ attributes.style = {opacity: 0, translateZ: 0};
1031
+ }
1032
+
1033
+ });
1034
+
1035
+ return attributes;
1036
+ },
1037
+
1038
+ // @override
1039
+ finalAttributesForDisappearingSupplementaryViewWithIdentifier: function (identifier, options) {
1040
+ var attributes;
1041
+ var self = this;
1042
+
1043
+ this.collectionView.usingOldDataSet(function () {
1044
+ attributes = self.attributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath}).copy();
1045
+
1046
+ if (self.collectionView.delegate && self.collectionView.delegate.collectionViewFinalAttributesForDisappearingSupplementaryViewWithIdentifier) {
1047
+ attributes = self.collectionView.delegate.collectionViewFinalAttributesForDisappearingSupplementaryViewWithIdentifier(self.collectionView, identifier, {atIndexPath: options.atIndexPath, withTargetAttributes: attributes});
1048
+ }
1049
+ else {
1050
+ attributes.frame.origin.x -= attributes.frame.size.width / 2;
1051
+ attributes.style = {opacity: 0, translateZ: 0};
1052
+ }
1053
+
1054
+ });
1055
+
1056
+ return attributes;
1057
+ },
1058
+
1059
+ // @override - BMCollectionViewLayout
1060
+ copy: function () {
1061
+ var copy = new BMCollectionViewTableLayout();
1062
+
1063
+ copy.rowHeight = this.rowHeight;
1064
+
1065
+ copy.showsHeaders = this.showsHeaders;
1066
+ copy.headerHeight = this.headerHeight;
1067
+
1068
+ copy.showsFooters = this.showsFooters;
1069
+ copy.footerHeight = this.footerHeight;
1070
+
1071
+ copy.sectionInsets = this.sectionInsets;
1072
+
1073
+ copy.pinsHeadersToContentEdge = this.pinsHeadersToContentEdge;
1074
+ copy.pinsFootersToContentEdge = this.pinsFootersToContentEdge;
1075
+
1076
+ return copy;
1077
+ }
1078
+
1079
+ });
1080
+
1081
+ // @endtype
1082
+
1083
+ // @type BMCollectionViewTreeLayout extends BMCollectionViewLayout
1084
+
1085
+
1086
+ /**
1087
+ * The BMCollectionViewTreeLayout will lay out its elements as a list where each row can have
1088
+ * either a fixed height or a variable height. Additionally, the elements may be nested, creating a tree-like view.
1089
+ *
1090
+ * The tree layout supports collapsible sections and it also supports collapsing the tree elements themselves.
1091
+ * Collection views that use the tree layout are required to have a delegate that implements the following methods:
1092
+ * String collectionViewLayoutTreeIdentifierForItemAtIndexPath(BMCollectionView, BMCollectionViewTreeLayout, BMIndexPath) - returns a unique identifier for each item in the collection view
1093
+ * nullable String collectionViewLayoutTreeParentIdentifierForItemAtIndexPath(BMCollectionView, BMCollectionViewTreeLayout, BMIndexPath) - returns the identifier of each item's parent, if it has one
1094
+ *
1095
+ * If the row heights are variable, the collection must have a delegate that implements the
1096
+ * Number collectionViewRowHeightForCellAtIndexPath(BMCollectionView, BMIndexPath) method.
1097
+ *
1098
+ * Optionally, the tree layout may also generate supplementary views for section headers, section footers and empty views.
1099
+ */
1100
+ function BMCollectionViewTreeLayout() {} // <constructor>
1101
+
1102
+ BMExtend(BMCollectionViewTreeLayout.prototype, BMCollectionViewLayout.prototype, {
1103
+
1104
+ // @override - BMCollectionViewLayout
1105
+ get supportsCopying() {
1106
+ return YES;
1107
+ },
1108
+
1109
+ /**
1110
+ * The row height to use for each row.
1111
+ * This can either be a specific size in pixels or BMCollectionViewTreeLayoutRowHeightVariable if each row can have a different height.
1112
+ * If the row heights are variable, the collection must have a delegate that implements the Number collectionViewRowHeightForCellAtIndexPath(BMCollectionView, BMIndexPath) method.
1113
+ */
1114
+ _rowHeight: 44, // <Number>
1115
+
1116
+ get rowHeight() { return this._rowHeight; },
1117
+ set rowHeight(rowHeight) {
1118
+ this._rowHeight = rowHeight;
1119
+ this.invalidateLayout();
1120
+ },
1121
+
1122
+ /**
1123
+ * Determines whether or not the tree layout should generate header supplementary views or not.
1124
+ */
1125
+ _showsHeaders: NO, // <Boolean>
1126
+
1127
+ get showsHeaders() { return this._showsHeaders; },
1128
+ set showsHeaders(showsHeaders) {
1129
+ this._showsHeaders = showsHeaders;
1130
+ this.invalidateLayout();
1131
+ },
1132
+
1133
+ /**
1134
+ * The height to use for headers. Does not have any effect if showsHeaders is set to NO.
1135
+ * This attribute is always 0 if showsHeader is set to NO.
1136
+ */
1137
+ _headerHeight: 22, // <Number>
1138
+
1139
+ get headerHeight() {
1140
+ return this._showsHeaders ? this._headerHeight : 0;
1141
+ },
1142
+
1143
+ set headerHeight(headerHeight) {
1144
+ this._headerHeight = headerHeight;
1145
+ if (this._showsHeader) this.invalidateLayout();
1146
+ },
1147
+
1148
+ /**
1149
+ * Determines whether or not the tree layout should generate footer supplementary views or not.
1150
+ */
1151
+ _showsFooters: NO, // <Boolean>
1152
+
1153
+ get showsFooters() { return this._showsFooters; },
1154
+ set showsFooters(showsFooters) {
1155
+ this._showsFooters = showsFooters;
1156
+ this.invalidateLayout();
1157
+ },
1158
+
1159
+ /**
1160
+ * The height to use for footers. Does not have any effect if showsFooters is set to NO.
1161
+ * This attribute is always 0 if showsFooters is set to NO.
1162
+ */
1163
+ _footerHeight: 22, // <Number>
1164
+
1165
+ get footerHeight() {
1166
+ return this._showsFooters? this._footerHeight : 0;
1167
+ },
1168
+
1169
+ set footerHeight(footerHeight) {
1170
+ this._footerHeight = footerHeight;
1171
+ if (this._showsFooters) this.invalidateLayout();
1172
+ },
1173
+
1174
+ /**
1175
+ * The amount by which child views will be indented.
1176
+ */
1177
+ _indentWidth: 22, // <Number>
1178
+
1179
+ get indentWidth() {
1180
+ return this._indentWidth;
1181
+ },
1182
+
1183
+ set indentWidth(indentWidth) {
1184
+ this._indentWidth = indentWidth;
1185
+ this.invalidateLayout();
1186
+ },
1187
+
1188
+ /**
1189
+ * The insets to apply to each section which control their distance from the collection view edges and from other sections.
1190
+ */
1191
+ _sectionInsets: BMInsetMake(), // <BMInset, nullResettable>
1192
+
1193
+ get sectionInsets() { return this._sectionInsets; },
1194
+ set sectionInsets(insets) {
1195
+ this._sectionInsets = insets || BMInsetMake();
1196
+ this.invalidateLayout();
1197
+ },
1198
+
1199
+ /**
1200
+ * When set to YES, the tree layout will stick the header supplementary views to the top edge of the collection view while scrolling.
1201
+ * Otherwise, the header supplementary views will scroll with the rest of the content.
1202
+ */
1203
+ _pinsHeadersToContentEdge: NO, // <Boolean>
1204
+
1205
+ get pinsHeadersToContentEdge() { return this._pinsHeadersToContentEdge; },
1206
+ set pinsHeadersToContentEdge(pins) {
1207
+ this._pinsHeadersToContentEdge = pins;
1208
+ this.invalidateLayout();
1209
+ },
1210
+
1211
+ /**
1212
+ * When set to YES, the tree layout will stick the footer supplementary views to the bottom edge of the collection view while scrolling.
1213
+ * Otherwise, the footer supplementary views will scroll with the rest of the content.
1214
+ */
1215
+ _pinsFootersToContentEdge: NO, // <Boolean>
1216
+
1217
+ get pinsFootersToContentEdge() { return this._pinsFootersToContentEdge; },
1218
+ set pinsFootersToContentEdge(pins) {
1219
+ this._pinsFootersToContentEdge = pins;
1220
+ this.invalidateLayout();
1221
+ },
1222
+
1223
+
1224
+ /**
1225
+ * Contains various cached layout attributes.
1226
+ */
1227
+ _layoutCache: undefined, // <Object>
1228
+
1229
+
1230
+ // @override - BMCollectionViewLayout
1231
+ prepareLayout: function () {
1232
+ // Tree layout supports invalidating with context, which allows for more efficient layout updates
1233
+ // by only the updating the actually changed content, rather than the entire layout cache
1234
+ var context = this.collectionView.invalidationContext;
1235
+
1236
+ // If there is no invalidation context, the entire layout cache has to be reconstructed
1237
+ if (!context) return this.rebuildLayout();
1238
+ },
1239
+
1240
+ // @override - BMCollectionViewLayout
1241
+ contentSize: function () {
1242
+ return this._layoutCache.contentSize;
1243
+ },
1244
+
1245
+ // @override - BMCollectionViewLayout
1246
+ shouldInvalidateLayoutForBoundsChange: function (bounds) {
1247
+ return this._pinsHeadersToContentEdge || this._pinsFootersToContentEdge;
1248
+ },
1249
+
1250
+ /**
1251
+ * Creates and returns a copy of this tree layout.
1252
+ * @return <BMCollectionViewTreeLayout> A tree layout.
1253
+ */
1254
+ copy() {
1255
+ let layout = new BMCollectionViewTreeLayout();
1256
+
1257
+ layout._rowHeight = this._rowHeight;
1258
+
1259
+ layout._showsHeaders = this._showsHeaders;
1260
+ layout._headerHeight = this._headerHeight;
1261
+ layout._pinsHeadersToContentEdge = this._pinsHeadersToContentEdge;
1262
+
1263
+ layout._showsFooters = this._showsFooters;
1264
+ layout._footerHeight = this._footerHeight;
1265
+ layout._pinsFootersToContentEdge = this._pinsFootersToContentEdge;
1266
+
1267
+ layout._indentWidth = this._indentWidth;
1268
+
1269
+ layout._sectionInsets = this.sectionInsets.copy();
1270
+
1271
+ return layout;
1272
+ }
1273
+ });
1274
+
1275
+ // @endtype
1276
+
1277
+ // @type BMCollectionViewFlowLayoutGravity
1278
+
1279
+ /**
1280
+ * Controls how cells are distributed within their row.
1281
+ */
1282
+ export var BMCollectionViewFlowLayoutGravity = Object.freeze({
1283
+
1284
+ /**
1285
+ * The cells will flow to the center of the row with no spacing between them.
1286
+ */
1287
+ Center: {},
1288
+
1289
+ /**
1290
+ * The cells will be aligned to the start of the row.
1291
+ */
1292
+ Start: {},
1293
+
1294
+ /**
1295
+ * The cells will be aligned to the end of the row.
1296
+ */
1297
+ End: {},
1298
+
1299
+ /**
1300
+ * The cells will flow to the edges of the row with equal spacing between them.
1301
+ */
1302
+ Edge: {},
1303
+
1304
+ /**
1305
+ * The cells will flow such that they have equal spacing between them and the row edges.
1306
+ */
1307
+ Spaced: {},
1308
+
1309
+ /**
1310
+ * The cells will flow such that they will have no spacing between them.
1311
+ * If the cells in a row do not occupy the entire horizontal alrea of that row, they will be expanded proportionally until they do.
1312
+ */
1313
+ Expand: {}
1314
+
1315
+ });
1316
+
1317
+ // @endtype
1318
+
1319
+ // @type BMCollectionViewFlowLayoutAlignment
1320
+
1321
+ export var BMCollectionViewFlowLayoutAlignment = (function () {
1322
+ const start = {};
1323
+ const end = {};
1324
+
1325
+ /**
1326
+ * Controls how cells will be aligned within their row.
1327
+ */
1328
+ var BMCollectionViewFlowLayoutAlignment = Object.freeze({
1329
+
1330
+ /**
1331
+ * @deprecated Use `Start`
1332
+ * The cells will be aligned to the starting edge of the row.
1333
+ */
1334
+ Top: start,
1335
+
1336
+ /**
1337
+ * The cells will be aligned to the start of the row.
1338
+ */
1339
+ Start: start,
1340
+
1341
+ /**
1342
+ * The cells will be aligned to the center of the row.
1343
+ */
1344
+ Center: {},
1345
+
1346
+ /**
1347
+ * @deprecated Use `End`
1348
+ * The cells will be aligned to the end of the row.
1349
+ */
1350
+ Bottom: end,
1351
+
1352
+ /**
1353
+ * The cells will be aligned to the end of the row.
1354
+ */
1355
+ End: end,
1356
+
1357
+ /**
1358
+ * The cells will expand to fit the entire height of the row.
1359
+ */
1360
+ Expand: {}
1361
+
1362
+ });
1363
+
1364
+ return BMCollectionViewFlowLayoutAlignment;
1365
+ })();
1366
+
1367
+ // @endtype
1368
+
1369
+ // @type BMCollectionViewFlowLayoutOrientation
1370
+
1371
+ /**
1372
+ * An enum containing the possible orientations that the flow layout can use.
1373
+ */
1374
+ export var BMCollectionViewFlowLayoutOrientation = Object.freeze({
1375
+ /**
1376
+ * Indicates that the flow layout will arrange cells primarily along the horizontal axis.
1377
+ * When the orientation is set to this value the flow layout will scroll horizontally.
1378
+ */
1379
+ Horizontal: {},
1380
+
1381
+ /**
1382
+ * Indicates that the flow layout will arrange cells primarily along the vertical axis.
1383
+ * When the orientation is set to this value the tile flow will scroll vertically.
1384
+ */
1385
+ Vertical: {}
1386
+ });
1387
+
1388
+ // @endtype
1389
+
1390
+ // @type BMCollectionViewFlowLayout extends BMCollectionViewLayout
1391
+
1392
+ /**
1393
+ * The identifiers for flow layout supplementary views.
1394
+ * As the flow layout only provides supplementary views for section headers and footers and empty data sets just like the table layout,
1395
+ * the identifiers used by the table layout are reused in this case.
1396
+ */
1397
+ export var BMCollectionViewFlowLayoutSupplementaryView = BMCollectionViewTableLayoutSupplementaryView;
1398
+
1399
+ /**
1400
+ * The flow layout arranges cells in a horiztonally (TO DO) or vertically scrolling container.
1401
+ * The cells will each flow on a row until they no longer fit. After they extend past the horizontal margin, they will move on to the next row.
1402
+ */
1403
+ export function BMCollectionViewFlowLayout() {}; // <constructor>
1404
+
1405
+ BMCollectionViewFlowLayout.prototype = BMExtend(Object.create(BMCollectionViewLayout.prototype), {
1406
+
1407
+ // @override - BMCollectionViewLayout
1408
+ get supportsCopying() {
1409
+ return YES;
1410
+ },
1411
+
1412
+ /**
1413
+ * The size of the cells, or undefined if the cells should be sized dynamically.
1414
+ * If the cells are sized dynamically, the collection view must have a delegate that implements the
1415
+ * BMSize collectionViewSizeForCellAtIndexPath(BMCollectionView, BMIndexPath) method,
1416
+ * otherwise the collection view will not be able to render any cells.
1417
+ */
1418
+ _cellSize: undefined, // <BMSize, nullable>
1419
+
1420
+ get cellSize() { return this._cellSize; },
1421
+ set cellSize(size) {
1422
+ this._cellSize = size;
1423
+
1424
+ this.invalidateLayout();
1425
+ },
1426
+
1427
+ /**
1428
+ * When this property is set to a size, flow layout will use automatic cell sizing to measure and lay out
1429
+ * cells in its collection view.
1430
+ *
1431
+ * Because this will cause flow layout to continuously resize the collection view contents, the value of this property
1432
+ * should represent the size that most cells are expected to be in order to minimize the visual changes to the
1433
+ * scroll bar when cells are measured.
1434
+ */
1435
+ _expectedCellSize: undefined, // <BMSize, nullable>
1436
+
1437
+ get expectedCellSize() {
1438
+ return this._expectedCellSize;
1439
+ },
1440
+ set expectedCellSize(cellSize) {
1441
+ this._expectedCellSize = cellSize;
1442
+
1443
+ this.invalidateLayout();
1444
+ },
1445
+
1446
+ /**
1447
+ * The spacing between each row. This is also used between the header and the first row and between the last row and the footer.
1448
+ */
1449
+ _rowSpacing: 44, // <Number>
1450
+ get rowSpacing() { return this._rowSpacing; },
1451
+ set rowSpacing(spacing) {
1452
+ this._rowSpacing = spacing;
1453
+
1454
+ this.invalidateLayout();
1455
+ },
1456
+
1457
+ /**
1458
+ * Determines whether or not the table layout should generate header supplementary views or not.
1459
+ */
1460
+ _showsHeaders: NO, // <Boolean>
1461
+
1462
+ get showsHeaders() { return this._showsHeaders; },
1463
+ set showsHeaders(showsHeaders) {
1464
+ this._showsHeaders = showsHeaders;
1465
+ this.invalidateLayout();
1466
+ },
1467
+
1468
+ /**
1469
+ * The height to use for headers. Does not have any effect if showsHeaders is set to NO.
1470
+ * This attribute is always 0 if showsHeader is set to NO.
1471
+ */
1472
+ _headerHeight: 22, // <Number>
1473
+
1474
+ get headerHeight() {
1475
+ return this._showsHeaders ? this._headerHeight : 0;
1476
+ },
1477
+
1478
+ set headerHeight(headerHeight) {
1479
+ this._headerHeight = headerHeight;
1480
+ if (this._showsHeader) this.invalidateLayout();
1481
+ },
1482
+
1483
+ /**
1484
+ * Determines whether or not the table layout should generate footer supplementary views or not.
1485
+ */
1486
+ _showsFooters: NO, // <Boolean>
1487
+
1488
+ get showsFooters() { return this._showsFooters; },
1489
+ set showsFooters(showsFooters) {
1490
+ this._showsFooters = showsFooters;
1491
+ this.invalidateLayout();
1492
+ },
1493
+
1494
+ /**
1495
+ * The height to use for footers. Does not have any effect if showsFooters is set to NO.
1496
+ * This attribute is always 0 if showsFooters is set to NO.
1497
+ */
1498
+ _footerHeight: 22, // <Number>
1499
+
1500
+ get footerHeight() {
1501
+ return this._showsFooters? this._footerHeight : 0;
1502
+ },
1503
+
1504
+ set footerHeight(footerHeight) {
1505
+ this._footerHeight = footerHeight;
1506
+ if (this._showsFooters) this.invalidateLayout();
1507
+ },
1508
+
1509
+ /**
1510
+ * The insets to apply to each section which control their distance from the collection view edges and from other sections.
1511
+ */
1512
+ _sectionInsets: BMInsetMake(), // <BMInset, nullResettable>
1513
+
1514
+ get sectionInsets() { return this._sectionInsets; },
1515
+ set sectionInsets(insets) {
1516
+ this._sectionInsets = insets || BMInsetMake();
1517
+ this.invalidateLayout();
1518
+ },
1519
+
1520
+ /**
1521
+ * Controls how items are spaced in each row horizontally.
1522
+ */
1523
+ _gravity: BMCollectionViewFlowLayoutGravity.Spaced, // <BMCollectionViewFlowLayoutGravity>
1524
+ get gravity() { return this._gravity; },
1525
+ set gravity(gravity) {
1526
+ this._gravity = gravity;
1527
+
1528
+ this.invalidateLayout();
1529
+ },
1530
+
1531
+ /**
1532
+ * Controls the minimum horizontal spacing between cells. This spacing is guaranteed to be applied between the cells
1533
+ * irrespective of the selected gravity.
1534
+ */
1535
+ _minimumSpacing: 0, // <Number>
1536
+ get minimumSpacing() { return this._minimumSpacing; },
1537
+ set minimumSpacing(spacing) {
1538
+ this._minimumSpacing = spacing;
1539
+
1540
+ this.invalidateLayout();
1541
+ },
1542
+
1543
+ /**
1544
+ * When set to a positive number, this controls the maximum cells that are allowed to fit in each row.
1545
+ */
1546
+ _maximumCellsPerRow: 0, // <Number>
1547
+ get maximumCellsPerRow() {
1548
+ return this._maximumCellsPerRow;
1549
+ },
1550
+ set maximumCellsPerRow(max) {
1551
+ if (max < 0) max = 0;
1552
+ this._maximumCellsPerRow = max;
1553
+
1554
+ this.invalidateLayout();
1555
+ },
1556
+
1557
+ /**
1558
+ * Only used with fixed cell sizes. If set to YES, the final row in each section will be left-aligned.
1559
+ */
1560
+ _leftAlignFinalRow: NO, // <Boolean>
1561
+ get leftAlignFinalRow() { return this._leftAlignFinalRow; },
1562
+ set leftAlignFinalRow(align) {
1563
+ this._leftAlignFinalRow = align;
1564
+
1565
+ this.invalidateLayout();
1566
+ },
1567
+
1568
+ /**
1569
+ * Controls how items are aligned in each row vertically.
1570
+ */
1571
+ _alignment: BMCollectionViewFlowLayoutAlignment.Center, // <BMCollectionViewFlowLayoutAlignment>
1572
+ get alignment() { return this._alignment; },
1573
+ set alignment(alignment) {
1574
+ this._alignment = alignment;
1575
+
1576
+ this.invalidateLayout();
1577
+ },
1578
+
1579
+ /**
1580
+ * Controls the axis along which cells will be laid out primarily.
1581
+ */
1582
+ _orientation: BMCollectionViewFlowLayoutOrientation.Vertical, // <BMCollectionViewFlowLayoutOrientation>
1583
+ get orientation() { return this._orientation; },
1584
+ set orientation(orientation) {
1585
+ this._orientation = orientation;
1586
+
1587
+ this.invalidateLayout();
1588
+ },
1589
+
1590
+ /**
1591
+ * The padding from the top edge to the first item.
1592
+ */
1593
+ _topPadding: 0, // <Number>
1594
+ get topPadding() { return this._topPadding; },
1595
+ set topPadding(padding) {
1596
+ this._topPadding = padding;
1597
+
1598
+ this.invalidateLayout();
1599
+ },
1600
+
1601
+ /**
1602
+ * The padding from the bottom edge to the last item.
1603
+ */
1604
+ _bottomPadding: 0, // <Number>
1605
+ get bottomPadding() { return this._bottomPadding; },
1606
+ set bottomPadding(padding) {
1607
+ this._bottomPadding = padding;
1608
+
1609
+ this.invalidateLayout();
1610
+ },
1611
+
1612
+ /**
1613
+ * Controls how the content is aligned when it is smaller than the collection view's frame.
1614
+ */
1615
+ _contentGravity: BMCollectionViewFlowLayoutAlignment.Center, // <BMCollectionViewFlowLayoutAlignment>
1616
+ get contentGravity() { return this._contentGravity; },
1617
+ set contentGravity(gravity) {
1618
+ this._contentGravity = gravity;
1619
+
1620
+ this.invalidateLayout();
1621
+ },
1622
+
1623
+ /**
1624
+ * When set to YES, the flow layout will stick the header supplementary views to the top edge of the collection view while scrolling.
1625
+ * Otherwise, the header supplementary views will scroll with the rest of the content.
1626
+ */
1627
+ _pinsHeadersToContentEdge: NO, // <Boolean>
1628
+
1629
+ get pinsHeadersToContentEdge() { return this._pinsHeadersToContentEdge; },
1630
+ set pinsHeadersToContentEdge(pins) {
1631
+ this._pinsHeadersToContentEdge = pins;
1632
+ this.invalidateLayout();
1633
+ },
1634
+
1635
+ /**
1636
+ * When set to YES, the flow layout will stick the footer supplementary views to the bottom edge of the collection view while scrolling.
1637
+ * Otherwise, the footer supplementary views will scroll with the rest of the content.
1638
+ */
1639
+ _pinsFootersToContentEdge: NO, // <Boolean>
1640
+
1641
+ get pinsFootersToContentEdge() { return this._pinsFootersToContentEdge; },
1642
+ set pinsFootersToContentEdge(pins) {
1643
+ this._pinsFootersToContentEdge = pins;
1644
+ this.invalidateLayout();
1645
+ },
1646
+
1647
+
1648
+ cachedLayout: undefined, // <Object>
1649
+
1650
+ // @override
1651
+ collectionViewWillStartUpdates: function (updates) {
1652
+ this.prepareLayout();
1653
+
1654
+ // When using automatic cell sizes, this layout preparation will only build a rough outline of the layout,
1655
+ // but because of how animated updates work in collection view, it is required to also complete the layout process
1656
+ // at least up to the current collection view bounds
1657
+ if (this._expectedCellSize) {
1658
+ this.cachedLayout.iterator.next({rect: this.collectionView.bounds});
1659
+ }
1660
+ },
1661
+
1662
+ // @override
1663
+ collectionViewDidStartUpdates: function () {
1664
+ this.previousLayout = undefined;
1665
+ },
1666
+
1667
+ // @override
1668
+ prepareLayout: function () {
1669
+ return this._prepareLayoutWithScrollbarOffset(NO);
1670
+ },
1671
+
1672
+ /**
1673
+ * Prepares the layout, optionally taking the scrollbar size into account.
1674
+ * @param useOffset <Boolean> When set to `YES` the layout will take the scrollbar size into account.
1675
+ */
1676
+ _prepareLayoutWithScrollbarOffset: function (useOffset) {
1677
+ if (this.isChangingBounds) {
1678
+ // If the layout is invalidated because of scrolling, it is not required to recalculate the caches, so this request should be ignored.
1679
+ return;
1680
+ }
1681
+
1682
+ let iterator = this._prepareLayoutWithScrollbarOffsetGenerator(useOffset);
1683
+
1684
+ // When not using automatic cell sizes, the generator function will run to finish from the first
1685
+ // invocation of next
1686
+ // When using automatic cell sizes, the first iteration will only build a rough outline of the expected layout
1687
+ // and must be retained so different portions of the layout can subsequently be computed
1688
+ let result = iterator.next();
1689
+ if (!result.done) {
1690
+ this._layoutIterator = iterator;
1691
+ this.cachedLayout.iterator = iterator;
1692
+ }
1693
+ },
1694
+
1695
+ /**
1696
+ * A generator that iterates through creating the layout. With the exception of using automatic cell sizes,
1697
+ * this generator will execute as a single contiguous function.
1698
+ *
1699
+ * When using automatic cell sizes, the generator will yield execution and only calculate portions of the layout on demand.
1700
+ * With the exeption of the first invocation, it is required to pass an index path to the iterator's `next` method. The layout process
1701
+ * will continue until it fully lays out the cell for that index path, at which point it will yield again.
1702
+ * @param useOffset <Boolean> When set to `YES` the layout will take the scrollbar size into account.
1703
+ */
1704
+ *_prepareLayoutWithScrollbarOffsetGenerator(useOffset) {
1705
+
1706
+ // To standardize naming in order to support the orientation setting, the following terms are used:
1707
+ // Length - refers to the dimension across the axis on which collection view scrolls: height for vertical and width for horizontal
1708
+ // Breadth - refers to the dimension across the secondary axis: width for vertical and height for horizontal
1709
+ // Size - refers to the dimension of cells across the axis on which collection view scrolls: height for vertical and width for horizontal
1710
+ // Secondary Size - refers to the dimension of cells across the secondary axis: width for vertical and height for horizontal
1711
+
1712
+ if (this.isChangingBounds) {
1713
+ // If the layout is invalidated because of scrolling, it is not required to recalculate the caches, so this request should be ignored.
1714
+ return;
1715
+ }
1716
+
1717
+ // If useOffset is set to YES, this the second layout pass and the previous layout should no longer be changed
1718
+ this.previousLayout = useOffset ? this.previousLayout : this.cachedLayout;
1719
+
1720
+ var frameHeight = this.collectionView.frame.size.height;
1721
+ var frameWidth = this.collectionView.frame.size.width;
1722
+ let frameSize = this._orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? frameHeight : frameWidth;
1723
+
1724
+ if (!this._cellSize || this._expectedCellSize) {
1725
+ // When using custom cell sizes, the layout will pre-compute the entire layout attributes
1726
+ this.cachedLayout = { sections: [] };
1727
+
1728
+ /*
1729
+ declare interface BMCollectionViewFlowLayoutCache {
1730
+ availableWidth: number;
1731
+ availableHeight: number;
1732
+ resolvedWidth?: number;
1733
+ resolvedHeight?: number;
1734
+ resolvedIndexPath?: BMIndexPath;
1735
+ sections: {
1736
+ top: number;
1737
+ bottom: number;
1738
+ rows: {
1739
+ top: number;
1740
+ bottom: number;
1741
+ startIndex: number;
1742
+ endIndex: number;
1743
+ attributes: BMCollectionViewLayoutAttributes[];
1744
+ }[];
1745
+ }[];
1746
+ size: BMSize;
1747
+ }
1748
+ */
1749
+
1750
+ var cachedLayout = this.cachedLayout;
1751
+
1752
+ cachedLayout.pinsHeadersToContentEdge = this._pinsHeadersToContentEdge;
1753
+ cachedLayout.pinsFootersToContentEdge = this._pinsFootersToContentEdge;
1754
+
1755
+ let orientation = this._orientation;
1756
+ cachedLayout.orientation = orientation;
1757
+
1758
+ // Each section adds height for its insets and headers and footers
1759
+ var insetTop = this._sectionInsets.top;
1760
+ var insetBottom = this._sectionInsets.bottom;
1761
+ var insetLeft = this._sectionInsets.left;
1762
+ var insetRight = this._sectionInsets.right;
1763
+
1764
+ var topPadding = this._topPadding;
1765
+ var bottomPadding = this._bottomPadding;
1766
+
1767
+ var headerHeight = this.headerHeight;
1768
+ var footerHeight = this.footerHeight;
1769
+
1770
+ var left = this._sectionInsets.left;
1771
+ let right = this._sectionInsets.right;
1772
+
1773
+ // The available height is the collection view frame height minus the vertical insets and the scrollbar width
1774
+ // When headers, footers are used their size and the row spacing is included as well
1775
+ var height = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? topPadding :
1776
+ this.collectionView.frame.size.height - this._sectionInsets.top - this._sectionInsets.bottom - (useOffset ? this.collectionView.scrollBarSize : 0) - topPadding - bottomPadding;
1777
+ cachedLayout.availableHeight = height;
1778
+
1779
+ // The available width is the collection view frame width minus the horizontal insets and the scrollbar width
1780
+ var width = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ?
1781
+ this.collectionView.frame.size.width - this._sectionInsets.left - this._sectionInsets.right - (useOffset ? this.collectionView.scrollBarSize : 0) :
1782
+ left;
1783
+ cachedLayout.availableWidth = width;
1784
+
1785
+ var showsHeaders = this.showsHeaders;
1786
+ var showsFooters = this.showsFooters;
1787
+
1788
+ var headerHeight = this.headerHeight;
1789
+ var footerHeight = this.footerHeight;
1790
+
1791
+ var sectionCount = this.collectionView.numberOfSections();
1792
+
1793
+ var rowSpacing = this._rowSpacing;
1794
+
1795
+ var minimumSpacing = this._minimumSpacing;
1796
+
1797
+ // When using automatic cell sizes, first compute a preliminary size for the layout then yield,
1798
+ // awaiting for collection view to request attributes for cells at which point execution should
1799
+ // resume to compute the layout for those attributes
1800
+ /*
1801
+ declare interface BMCollectionViewAutomaticCellSizeTarget {
1802
+ targetIndexPath?: BMIndexPath;
1803
+ targetRect?: BMRect;
1804
+ }
1805
+ */
1806
+ let target;
1807
+
1808
+ // The sections that contain the preliminary layout
1809
+ const preliminarySections = [];
1810
+
1811
+ const self = this;
1812
+
1813
+ // This function measures a series of cells that exist in the preliminary layout and interect the given rect
1814
+ function measureCellsInRect(rect) {
1815
+ // Adjust the rows to account for the resolved layout
1816
+ if (cachedLayout.resolvedIndexPath) {
1817
+ let targetSection = cachedLayout.resolvedIndexPath.section;
1818
+ if (cachedLayout.resolvedIndexPath.row == -1) targetSection--;
1819
+ let adjustment;
1820
+ for (let i = 0; i <= targetSection; i++) {
1821
+ let preliminarySection = preliminarySections[i];
1822
+ let section = cachedLayout.sections[i];
1823
+
1824
+ if (i < targetSection || section.resolved) {
1825
+ // Completely resolved sections can just be replaced entirely
1826
+ preliminarySection.start = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? section.top : section.left;
1827
+ preliminarySection.preliminaryEnd = preliminarySection.end;
1828
+ preliminarySection.end = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? section.bottom : section.right;
1829
+
1830
+ preliminarySection.preliminaryRows = preliminarySection.rows;
1831
+ preliminarySection.rows = section.rows.map(r => orientation == BMCollectionViewFlowLayoutOrientation.Vertical ?
1832
+ {start: r.top, end: r.bottom, indexStart: r.startIndex, indexEnd: r.endIndex} :
1833
+ {start: r.left, end: r.right, indexStart: r.startIndex, indexEnd: r.endIndex});
1834
+ }
1835
+ else {
1836
+ preliminarySection.preliminaryRows = preliminarySection.rows;
1837
+ preliminarySection.rows = [];
1838
+ preliminarySection.start = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? section.top : section.left;
1839
+ // For in-progress sections, it is necessary to figure out the adjustment between the
1840
+ // preliminary locations and the resolved ones
1841
+ let end = preliminarySection.start;
1842
+ let indexEnd = 0;
1843
+ for (const r of section.rows) {
1844
+ let row = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ?
1845
+ {start: r.top, end: r.bottom, indexStart: r.startIndex, indexEnd: r.endIndex} :
1846
+ {start: r.left, end: r.right, indexStart: r.startIndex, indexEnd: r.endIndex};
1847
+
1848
+ preliminarySection.rows.push(row);
1849
+ end = row.end;
1850
+ indexEnd = row.indexEnd;
1851
+ }
1852
+
1853
+ end += rowSpacing;
1854
+
1855
+ // After inspecting the resolved rows, it is possible to calculate the adjustment
1856
+ for (const row of preliminarySection.preliminaryRows) {
1857
+ if (row.indexEnd > indexEnd) {
1858
+ adjustment = end - row.start;
1859
+ break;
1860
+ }
1861
+ }
1862
+
1863
+ // The adjustment can then be applied to all subsequent rows
1864
+ for (const row of preliminarySection.preliminaryRows) {
1865
+ if (row.indexEnd > indexEnd) {
1866
+ row.start += adjustment;
1867
+ row.end += adjustment;
1868
+ }
1869
+ }
1870
+ }
1871
+ }
1872
+
1873
+ // The remaining sections also need to be adjusted
1874
+ for (let i = targetSection + 1; i < sectionCount; i++) {
1875
+ // If the adjustment is still undefined, the target section was fully defined; in this case
1876
+ // the adjustment can be calculated directly from the section ends
1877
+ if (adjustment === undefined) {
1878
+ adjustment = (orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? cachedLayout.sections[targetSection].bottom : cachedLayout.sections[targetSection].right) - preliminarySections[targetSection].end;
1879
+ }
1880
+
1881
+ const preliminarySection = preliminarySections[i];
1882
+ preliminarySection.start += adjustment;
1883
+ preliminarySection.end += adjustment;
1884
+
1885
+ for (const row of preliminarySection.row) {
1886
+ row.start += adjustment;
1887
+ row.end += adjustment;
1888
+ }
1889
+ }
1890
+ }
1891
+
1892
+ let indexPaths = [];
1893
+
1894
+ var rectTop = rect.origin.y;
1895
+ var rectBottom = rect.bottom;
1896
+ var rectLeft = rect.origin.x;
1897
+ var rectRight = rect.right;
1898
+ var rectEnd = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? rectBottom : rectRight;
1899
+ const rectStart = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? rectTop : rectLeft;
1900
+
1901
+ var sectionIndex = 0;
1902
+ var sectionCount = preliminarySections.length;
1903
+ var section;
1904
+
1905
+ // Find the section that intersects this rect's top origin
1906
+ for (; sectionIndex < sectionCount; sectionIndex++) {
1907
+ section = preliminarySections[sectionIndex];
1908
+ if (section.end > rectStart) break;
1909
+ }
1910
+
1911
+ // If there were no sections return nothing
1912
+ if (sectionIndex == sectionCount) return;
1913
+
1914
+ // Find the first row index that intersects the rect's top.
1915
+ var rowIndex = 0;
1916
+
1917
+ for (const row of section.rows) {
1918
+ if (row.end > rectStart) break;
1919
+ rowIndex++;
1920
+ }
1921
+
1922
+ // Then continue adding rows until reaching the end of the rect or the end of the data set
1923
+ while (YES) {
1924
+ section = preliminarySections[sectionIndex];
1925
+ if (!section) return self.collectionView.measureSizesOfCellsAtIndexPaths(indexPaths);
1926
+ const rowCount = section.rows.length;
1927
+ for (let i = rowIndex; i < rowCount; i++) {
1928
+ const row = section.rows[i];
1929
+ // When encountering a row outside of the rect's bounds, perform the measurement and return
1930
+ if (row.start > rectEnd) return self.collectionView.measureSizesOfCellsAtIndexPaths(indexPaths);
1931
+ const indexEnd = Math.min(row.indexEnd + 1, self.collectionView.numberOfObjectsInSectionAtIndex(sectionIndex));
1932
+ for (let rowIndex = row.indexStart; rowIndex < indexEnd; rowIndex++) {
1933
+ indexPaths.push(self.collectionView.indexPathForObjectAtRow(rowIndex, {inSectionAtIndex: sectionIndex}));
1934
+ }
1935
+ }
1936
+
1937
+ sectionIndex++;
1938
+ rowIndex = 0;
1939
+ }
1940
+ };
1941
+
1942
+ if (this._expectedCellSize) {
1943
+ cachedLayout.resolvedHeight = 0;
1944
+ cachedLayout.resolvedWidth = 0;
1945
+ cachedLayout.resolvedIndexPath = BMIndexPathMakeWithRow(-1, {inSectionAtIndex: -1});
1946
+ cachedLayout.sectionCount = this.collectionView.numberOfSections();
1947
+
1948
+ let numberOfColumns = this._maximumCellsPerRow;
1949
+ let expectedWidth = this._expectedCellSize.width;
1950
+ let expectedHeight = this._expectedCellSize.height;
1951
+ let expectedSize = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? expectedWidth : expectedHeight;
1952
+ let expectedSecondarySize = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? expectedHeight : expectedWidth;
1953
+ let maximumLength = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? width : height;
1954
+ if (!numberOfColumns) {
1955
+ if (cachedLayout.minimumSpacing) {
1956
+ // Otherwise it has to be computed
1957
+ numberOfColumns = 0;
1958
+ let usedLength = 0;
1959
+
1960
+ // For spaced gravity, the used width starts with at least two minimum spacings
1961
+ if (this._gravity === BMCollectionViewFlowLayoutGravity.Spaced) {
1962
+ usedLength = 2 * minimumSpacing;
1963
+ }
1964
+
1965
+ do {
1966
+ // If this cell no longer fits within the row then the column limit has been reached
1967
+ if (usedLength && usedLength + expectedSize + minimumSpacing > maximumLength) {
1968
+ break;
1969
+ }
1970
+
1971
+ // For every cell other than the first, also include the required minimum spacing
1972
+ if (numberOfColumns) {
1973
+ usedLength += minimumSpacing;
1974
+ }
1975
+
1976
+ usedLength += expectedSize;
1977
+
1978
+ numberOfColumns++;
1979
+ } while (usedLength < maximumLength && expectedSize);
1980
+ }
1981
+ else {
1982
+ // If there is no minimum spacing required, then the number of columns is simply the available space divided
1983
+ // by the cell width
1984
+ numberOfColumns = ((maximumLength / expectedSize) | 0) || 1;
1985
+ }
1986
+ }
1987
+
1988
+ // Create the preliminary sections
1989
+ for (let i = 0; i < sectionCount; i++) {
1990
+ let numberOfObjects = this.collectionView.numberOfObjectsInSectionAtIndex(i);
1991
+
1992
+ let numberOfRows = Math.ceil(numberOfObjects / numberOfColumns) | 0;
1993
+
1994
+ let spacingBreadth = (numberOfRows - 1) * rowSpacing;
1995
+ if (headerHeight) spacingBreadth += rowSpacing;
1996
+ if (footerHeight) spacingBreadth += rowSpacing;
1997
+
1998
+ if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
1999
+ // Section objects retain the overall bounds as well as the preliminary rows
2000
+ let section = {
2001
+ top: height,
2002
+ start: height,
2003
+ bottom: height + footerHeight + headerHeight + (expectedSecondarySize * numberOfRows) + spacingBreadth,
2004
+ end: height + footerHeight + headerHeight + (expectedSecondarySize * numberOfRows) + spacingBreadth,
2005
+ numberOfObjects: numberOfObjects,
2006
+ numberOfRows: numberOfRows,
2007
+ rows: []
2008
+ };
2009
+
2010
+ let rowStart = headerHeight ? height + headerHeight + rowSpacing : height;
2011
+ let indexStart = 0;
2012
+ for (let i = 0; i < numberOfRows; i++) {
2013
+ // Preliminary rows contain the overall bounds and starting and ending index paths
2014
+ section.rows.push({start: rowStart, end: rowStart + expectedSecondarySize, indexStart: indexStart, indexEnd: indexStart + numberOfColumns - 1});
2015
+ rowStart += expectedSecondarySize + rowSpacing;
2016
+ indexStart += numberOfColumns;
2017
+ }
2018
+
2019
+ height = section.bottom + insetTop + insetBottom;
2020
+
2021
+ // If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
2022
+ if (height > frameHeight) {
2023
+ if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
2024
+ }
2025
+
2026
+ preliminarySections.push(section);
2027
+ }
2028
+ else {
2029
+ let section = {
2030
+ left: width,
2031
+ start: width,
2032
+ right: width + (expectedSecondarySize * numberOfRows) + spacingBreadth,
2033
+ end: width + (expectedSecondarySize * numberOfRows) + spacingBreadth,
2034
+ numberOfObjects: numberOfObjects,
2035
+ numberOfRows: numberOfRows,
2036
+ rows: []
2037
+ };
2038
+
2039
+ let rowStart = width;
2040
+ let indexStart = 0;
2041
+ for (let i = 0; i < numberOfRows; i++) {
2042
+ // Preliminary rows contain the overall bounds and starting and ending index paths
2043
+ section.rows.push({start: rowStart, end: rowStart + expectedSecondarySize, indexStart: indexStart, indexEnd: indexStart + numberOfColumns - 1});
2044
+ rowStart += expectedSecondarySize + rowSpacing;
2045
+ indexStart += numberOfColumns;
2046
+ }
2047
+
2048
+ width = section.right + insetLeft + insetRight;
2049
+
2050
+ // If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
2051
+ if (width > frameWidth) {
2052
+ if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
2053
+ }
2054
+
2055
+ preliminarySections.push(section);
2056
+ }
2057
+ }
2058
+
2059
+ if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
2060
+ height += this._bottomPadding;
2061
+
2062
+ // If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
2063
+ if (height > frameHeight) {
2064
+ if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
2065
+ }
2066
+
2067
+ cachedLayout.size = BMSizeMake(this.collectionView.frame.size.width, height);
2068
+
2069
+ target = yield;
2070
+
2071
+ // When requesting a rect initially, pre-measure the cells up to that rect
2072
+ if (target.rect) {
2073
+ measureCellsInRect(target.rect);
2074
+ }
2075
+
2076
+ height = topPadding;
2077
+ }
2078
+ else {
2079
+ if (width > frameWidth) {
2080
+ if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
2081
+ }
2082
+
2083
+ cachedLayout.size = BMSizeMake(width, this.collectionView.frame.size.height);
2084
+
2085
+ target = yield;
2086
+ // When requesting a rect initially, pre-measure the cells up to that rect
2087
+ if (target.rect) {
2088
+ measureCellsInRect(target.rect);
2089
+ }
2090
+
2091
+ width = 0;
2092
+ }
2093
+ }
2094
+
2095
+ // Go through the sections and create the layout attributes for all objects
2096
+ for (var i = 0; i < sectionCount; i++) {
2097
+
2098
+ // The section is plain old javascript object containing various cached attributes
2099
+ // And a rows array. Each item in a rows array is another object that contains
2100
+ // cached properties for that row and all the attributes of items in that row.
2101
+ var section = {
2102
+ rows: []
2103
+ };
2104
+
2105
+ // Save the cached section
2106
+ cachedLayout.sections.push(section);
2107
+
2108
+ if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
2109
+ height += insetTop;
2110
+ // Cache the top origin
2111
+ section.top = height;
2112
+ }
2113
+ else {
2114
+ width += insetLeft;
2115
+ // Cache the top origin
2116
+ section.left = width;
2117
+ }
2118
+
2119
+ if (showsHeaders) {
2120
+ if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
2121
+ // Cache the header attributes if they are enabled
2122
+ var headerAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
2123
+ BMCollectionViewFlowLayoutSupplementaryView.Header,
2124
+ {atIndexPath: BMIndexPathMakeWithIndexes([i])}
2125
+ );
2126
+
2127
+ headerAttributes.frame = BMRectMake(left, height, width, headerHeight);
2128
+ if (this.pinsHeadersToContentEdge) headerAttributes.style = {zIndex: 1};
2129
+
2130
+ section.headerAttributes = headerAttributes;
2131
+
2132
+ height += headerHeight + rowSpacing;
2133
+ }
2134
+ else {
2135
+ // Cache the header attributes if they are enabled
2136
+ var headerAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
2137
+ BMCollectionViewFlowLayoutSupplementaryView.Header,
2138
+ {atIndexPath: BMIndexPathMakeWithIndexes([i])}
2139
+ );
2140
+
2141
+ headerAttributes.frame = BMRectMake(width, topPadding + insetTop, headerHeight, height);
2142
+ if (this.pinsHeadersToContentEdge) headerAttributes.style = {zIndex: 1};
2143
+
2144
+ section.headerAttributes = headerAttributes;
2145
+
2146
+ width += headerHeight + rowSpacing;
2147
+ }
2148
+ }
2149
+
2150
+ var sectionItems = this.collectionView.numberOfObjectsInSectionAtIndex(i);
2151
+ var sectionIndex = 0;
2152
+
2153
+ let maximumRowLength = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? width : height;
2154
+ let lengthAttribute = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? 'width' : 'height';
2155
+ let breadthAttribute = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? 'height' : 'width';
2156
+ let rowStartAttribute = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? 'top' : 'left';
2157
+
2158
+ // Cache the cells in this sections
2159
+ while (sectionIndex < sectionItems) {
2160
+
2161
+ // Construct new rows until there are no more items in this sections
2162
+ var row = {
2163
+ top: height,
2164
+ left: width,
2165
+ attributes: [],
2166
+ startIndex: sectionIndex
2167
+ };
2168
+
2169
+ var rowLength = 0;
2170
+ var rowBreadth = 0;
2171
+
2172
+ // For spaced gravity, the used width starts with at least two minimum spacings
2173
+ if (this._gravity === BMCollectionViewFlowLayoutGravity.Spaced) {
2174
+ rowLength = 2 * minimumSpacing;
2175
+ }
2176
+
2177
+ while (rowLength < maximumRowLength || maximumRowLength <= 0) {
2178
+ // Add items to this row until no more items fit horizontally
2179
+ var indexPath = this.collectionView.indexPathForObjectAtRow(sectionIndex, {inSectionAtIndex: i});
2180
+
2181
+ // When using automatic cell sizes, use the measured cell size
2182
+ var cellSize = (this._expectedCellSize && this.collectionView.measuredSizeOfCellAtIndexPath(indexPath)) ||
2183
+ this.collectionView.delegate.collectionViewSizeForCellAtIndexPath(this.collectionView, indexPath);
2184
+
2185
+ // If the item no longer fits in the row, leave it for the next one
2186
+ // while making sure that there is at least one object in the row, even if doesn't fit,
2187
+ // otherwise this will lead to an infinite loop
2188
+ if (cellSize[lengthAttribute] + rowLength + minimumSpacing > maximumRowLength && row.attributes.length) break;
2189
+
2190
+ var cellAttributes = BMCollectionViewLayoutAttributesMakeForCellAtIndexPath(indexPath);
2191
+
2192
+ // When using automatic cell sizes, mark these attributes as requiring a measurement
2193
+ cellAttributes._requireMeasurement = YES;
2194
+
2195
+ // Construct the frame
2196
+ if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
2197
+ cellAttributes.frame = BMRectMake(rowLength, height, cellSize.width, cellSize.height);
2198
+ }
2199
+ else {
2200
+ cellAttributes.frame = BMRectMake(width, rowLength, cellSize.width, cellSize.height);
2201
+ }
2202
+
2203
+ if (rowBreadth < cellSize[breadthAttribute]) rowBreadth = cellSize[breadthAttribute];
2204
+
2205
+ // And add the attributes to the row
2206
+ row.attributes.push(cellAttributes);
2207
+
2208
+ rowLength += cellSize[lengthAttribute];
2209
+
2210
+ // For all cells other than the first also add the minimum spacing
2211
+ if (row.attributes.length > 1) {
2212
+ rowLength += minimumSpacing;
2213
+ }
2214
+
2215
+ // Advance to the next item
2216
+ sectionIndex++;
2217
+
2218
+ // If the row height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
2219
+ if (row[rowStartAttribute] + rowBreadth > frameSize) {
2220
+ if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
2221
+ }
2222
+
2223
+ // if there is one
2224
+ if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
2225
+ if (sectionIndex >= sectionItems || width == 0) break;
2226
+ }
2227
+ else {
2228
+ if (sectionIndex >= sectionItems || height == 0) break;
2229
+ }
2230
+
2231
+ // Also continue if the number of items added to this row matches the limit of cells
2232
+ if (this._maximumCellsPerRow && row.attributes.length == this._maximumCellsPerRow) break;
2233
+ }
2234
+
2235
+ // Skip empty rows
2236
+ if (row.attributes.length) {
2237
+ // Flow the items in this row based on the gravity and alignment
2238
+ // TODO: Consider lazily reflowing rows to shave the prepareLayout execution time
2239
+ if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
2240
+ this._reflowRow(row, {forLength: width, breadth: rowBreadth, withOccupiedLength: rowLength, spacing: minimumSpacing});
2241
+
2242
+ height += rowBreadth;
2243
+
2244
+ row.bottom = height;
2245
+ row.endIndex = sectionIndex - 1;
2246
+ section.rows.push(row);
2247
+
2248
+ height += rowSpacing;
2249
+ }
2250
+ else {
2251
+ this._reflowRow(row, {forLength: height, breadth: rowBreadth, withOccupiedLength: rowLength, spacing: minimumSpacing});
2252
+
2253
+ width += rowBreadth;
2254
+
2255
+ row.right = width;
2256
+ row.endIndex = sectionIndex - 1;
2257
+ section.rows.push(row);
2258
+
2259
+ width += rowSpacing;
2260
+ }
2261
+ }
2262
+
2263
+ // After having reached the requested target, yield execution when using automatic cell sizes
2264
+ if (target) {
2265
+ // When using automatic cell size and pinned footers, preliminary footer attributes must be created
2266
+ if (showsFooters && this.pinsFootersToContentEdge) {
2267
+ if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
2268
+ let footerAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
2269
+ BMCollectionViewFlowLayoutSupplementaryView.Footer,
2270
+ {atIndexPath: BMIndexPathMakeWithIndexes([i])}
2271
+ );
2272
+
2273
+ footerAttributes.frame = BMRectMake(left, height, width, footerHeight);
2274
+ footerAttributes.style = {zIndex: 1};
2275
+
2276
+ section.footerAttributes = footerAttributes;
2277
+ }
2278
+ else {
2279
+ let footerAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
2280
+ BMCollectionViewFlowLayoutSupplementaryView.Footer,
2281
+ {atIndexPath: BMIndexPathMakeWithIndexes([i])}
2282
+ );
2283
+
2284
+ footerAttributes.frame = BMRectMake(width, topPadding + insetTop, footerHeight, height);
2285
+ if (this.pinsFootersToContentEdge) footerAttributes.style = {zIndex: 1};
2286
+
2287
+ section.footerAttributes = footerAttributes;
2288
+ }
2289
+ }
2290
+
2291
+ if (target.indexPath) {
2292
+ let targetIndexPath = target.indexPath;
2293
+ if (targetIndexPath.section < i || (targetIndexPath.section == i && targetIndexPath.row < row.endIndex)) {
2294
+ // Retain how much of the layout was resolved
2295
+ cachedLayout.resolvedHeight = height;
2296
+ cachedLayout.resolvedWidth = width;
2297
+ cachedLayout.resolvedIndexPath = row.attributes[row.attributes.length - 1].indexPath;
2298
+ if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
2299
+ if (height > cachedLayout.size.height) {
2300
+ cachedLayout.size.height = height + rowSpacing + insetBottom + 1;
2301
+ if (BM_AUTOMATIC_CELL_SIZE_MESSAGES) console.log('Layout height has changed to ' + height);
2302
+ }
2303
+ }
2304
+ else {
2305
+ if (width > cachedLayout.size.width) {
2306
+ cachedLayout.size.width = width + rowSpacing + insetRight + 1;
2307
+ if (BM_AUTOMATIC_CELL_SIZE_MESSAGES) console.log('Layout width has changed to ' + width);
2308
+ }
2309
+ }
2310
+ section.bottom = height;
2311
+ section.right = width;
2312
+ target = yield;
2313
+
2314
+ // When requesting a rect initially, pre-measure the cells up to that rect
2315
+ if (target.rect) {
2316
+ measureCellsInRect(target.rect);
2317
+ }
2318
+ }
2319
+ }
2320
+ if (target.rect) {
2321
+ let targetRect = target.rect;
2322
+ if (height > targetRect.bottom) {
2323
+ // Retain how much of the layout was resolved
2324
+ cachedLayout.resolvedHeight = height;
2325
+ cachedLayout.resolvedWidth = width;
2326
+ cachedLayout.resolvedIndexPath = row.attributes[row.attributes.length - 1].indexPath;
2327
+ if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
2328
+ if (height > cachedLayout.size.height) {
2329
+ cachedLayout.size.height = height + rowSpacing + insetBottom + 1;
2330
+ if (BM_AUTOMATIC_CELL_SIZE_MESSAGES) console.log('Layout height has changed to ' + height);
2331
+ }
2332
+ }
2333
+ else {
2334
+ if (width > cachedLayout.size.width) {
2335
+ cachedLayout.size.width = width + rowSpacing + insetRight + 1;
2336
+ if (BM_AUTOMATIC_CELL_SIZE_MESSAGES) console.log('Layout width has changed to ' + width);
2337
+ }
2338
+ }
2339
+ section.bottom = height;
2340
+ section.right = width;
2341
+ target = yield;
2342
+
2343
+ // When requesting a rect initially, pre-measure the cells up to that rect
2344
+ if (target.rect) {
2345
+ measureCellsInRect(target.rect);
2346
+ }
2347
+ }
2348
+ }
2349
+ }
2350
+
2351
+ }
2352
+
2353
+ section.resolved == YES;
2354
+
2355
+ // Remove the row spacing from the last row
2356
+ if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
2357
+ height -= rowSpacing;
2358
+ }
2359
+ else {
2360
+ width -= rowSpacing;
2361
+ }
2362
+
2363
+
2364
+ if (showsFooters) {
2365
+ if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
2366
+ height += rowSpacing;
2367
+
2368
+ // Cache the footer attributes if they are enabled
2369
+ var footerAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
2370
+ BMCollectionViewFlowLayoutSupplementaryView.Footer,
2371
+ {atIndexPath: BMIndexPathMakeWithIndexes([i])}
2372
+ );
2373
+
2374
+ footerAttributes.frame = BMRectMake(left, height, width, footerHeight);
2375
+ if (this.pinsFootersToContentEdge) footerAttributes.style = {zIndex: 1};
2376
+
2377
+ section.footerAttributes = footerAttributes;
2378
+
2379
+ height += footerHeight;
2380
+
2381
+ // If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
2382
+ if (height > frameHeight) {
2383
+ if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
2384
+ }
2385
+ }
2386
+ else {
2387
+ width += rowSpacing;
2388
+
2389
+ // Cache the footer attributes if they are enabled
2390
+ var footerAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
2391
+ BMCollectionViewFlowLayoutSupplementaryView.Footer,
2392
+ {atIndexPath: BMIndexPathMakeWithIndexes([i])}
2393
+ );
2394
+
2395
+ footerAttributes.frame = BMRectMake(width, topPadding + insetTop, footerHeight, height);
2396
+ if (this.pinsFootersToContentEdge) footerAttributes.style = {zIndex: 1};
2397
+
2398
+ section.footerAttributes = footerAttributes;
2399
+
2400
+ width += footerHeight;
2401
+
2402
+ // If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
2403
+ if (width > frameWidth) {
2404
+ if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
2405
+ }
2406
+ }
2407
+ }
2408
+
2409
+
2410
+ if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
2411
+ // Cache the bottom origin
2412
+ section.bottom = height;
2413
+
2414
+ height += insetBottom;
2415
+
2416
+ // If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
2417
+ if (height > frameHeight) {
2418
+ if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
2419
+ }
2420
+ }
2421
+ else {
2422
+ // Cache the bottom origin
2423
+ section.right = width;
2424
+
2425
+ width += insetRight;
2426
+
2427
+ // If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
2428
+ if (width > frameWidth) {
2429
+ if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
2430
+ }
2431
+ }
2432
+
2433
+ }
2434
+
2435
+ if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
2436
+ height += bottomPadding;
2437
+
2438
+ // If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
2439
+ if (height > frameHeight) {
2440
+ if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
2441
+ }
2442
+
2443
+ // If the height is lower than the collection view's height, the content gravity must be applied to all cached attributes
2444
+ if (height < this.collectionView.frame.size.height) {
2445
+ let topAdjustment = 0;
2446
+ if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Center) {
2447
+ topAdjustment = ((this.collectionView.frame.size.height - height) / 2) | 0;
2448
+ }
2449
+ else if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Bottom) {
2450
+ topAdjustment = this.collectionView.frame.size.height - height;
2451
+ }
2452
+
2453
+ // This represents the height that is actually used by rows, excluding spaces and supplementary views
2454
+ let usedHeight = 0;
2455
+
2456
+ if (topAdjustment != 0 || this._contentGravity === BMCollectionViewFlowLayoutAlignment.Expand) {
2457
+ let sectionsLength = cachedLayout.sections.length;
2458
+
2459
+ // Displace the sections
2460
+ for (let i = 0; i < sectionsLength; i++) {
2461
+ let section = cachedLayout.sections[i];
2462
+
2463
+ section.top += topAdjustment;
2464
+ section.bottom += topAdjustment;
2465
+
2466
+ // Displace all the rows in this sections
2467
+ let rowsLength = section.rows.length;
2468
+ for (let j = 0; j < rowsLength; j++) {
2469
+ let row = section.rows[j];
2470
+
2471
+ row.top += topAdjustment;
2472
+ row.bottom += topAdjustment;
2473
+
2474
+ usedHeight += row.bottom - row.top;
2475
+
2476
+ // Displace all the cells in the row
2477
+ for (let k = 0; k < row.attributes.length; k++) {
2478
+ row.attributes[k].frame.origin.y += topAdjustment;
2479
+ }
2480
+ }
2481
+
2482
+ // Displace the header and footer in this section
2483
+ if (section.headerAttributes) section.headerAttributes.frame.origin.y += topAdjustment;
2484
+ if (section.footerAttributes) section.footerAttributes.frame.origin.y += topAdjustment;
2485
+
2486
+ }
2487
+ }
2488
+
2489
+ // When using the expand gravity, a factor is used to multiply the heights of all rows
2490
+ if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Expand) {
2491
+ const spacingSize = height - usedHeight;
2492
+ const usableHeight = this.collectionView.frame.size.height - spacingSize;
2493
+
2494
+ const scaleFactor = usableHeight / usedHeight;
2495
+
2496
+ let displacement = 0;
2497
+
2498
+ // Scale and displace the rows as needed
2499
+ for (const section of this.cachedLayout.sections) {
2500
+ // Displace the header and section origin
2501
+ section.top += displacement;
2502
+ if (section.headerAttributes) section.headerAttributes.frame.origin.y += displacement;
2503
+
2504
+ for (const row of section.rows) {
2505
+ // Displace the row origin
2506
+ const rowHeight = row.bottom - row.top;
2507
+ row.top += displacement;
2508
+
2509
+ // Scale and display each attribute's frame
2510
+ const scaledRowHeight = rowHeight * scaleFactor | 0;
2511
+ for (const attribute of row.attributes) {
2512
+ attribute.frame.origin.y += displacement;
2513
+ attribute.frame.size.height = attribute.frame.size.height * scaleFactor | 0;
2514
+ }
2515
+
2516
+ // Advance the displacement based on the row's new height then displace the row end
2517
+ displacement += scaledRowHeight - rowHeight;
2518
+ row.bottom += displacement;
2519
+ }
2520
+
2521
+ // Displace the section end and the footer
2522
+ section.bottom += displacement;
2523
+ if (section.footerAttributes) section.footerAttributes.frame.origin.y += displacement;
2524
+ }
2525
+ }
2526
+
2527
+ }
2528
+
2529
+ cachedLayout.size = BMSizeMake(this.collectionView.frame.size.width, Math.max(height, this.collectionView.frame.size.height));
2530
+ }
2531
+ else {
2532
+ // If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
2533
+ if (width > frameWidth) {
2534
+ if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
2535
+ }
2536
+
2537
+ // If the height is lower than the collection view's height, the content gravity must be applied to all cached attributes
2538
+ if (width < this.collectionView.frame.size.width) {
2539
+ let leftAdjustment = 0;
2540
+ if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Center) {
2541
+ leftAdjustment = ((this.collectionView.frame.size.width - width) / 2) | 0;
2542
+ }
2543
+ else if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Bottom) {
2544
+ leftAdjustment = this.collectionView.frame.size.width - width;
2545
+ }
2546
+
2547
+ // This represents the width that is actually used by rows, excluding spaces and supplementary views
2548
+ let usedWidth = 0;
2549
+
2550
+ if (leftAdjustment != 0 || this._contentGravity === BMCollectionViewFlowLayoutAlignment.Expand) {
2551
+ let sectionsLength = cachedLayout.sections.length;
2552
+
2553
+ // Displace the sections
2554
+ for (let i = 0; i < sectionsLength; i++) {
2555
+ let section = cachedLayout.sections[i];
2556
+
2557
+ section.left += leftAdjustment;
2558
+ section.right += leftAdjustment;
2559
+
2560
+ // Displace all the rows in this sections
2561
+ let rowsLength = section.rows.length;
2562
+ for (let j = 0; j < rowsLength; j++) {
2563
+ let row = section.rows[j];
2564
+
2565
+ row.left += leftAdjustment;
2566
+ row.right += leftAdjustment;
2567
+
2568
+ usedWidth += row.right - row.left;
2569
+
2570
+ // Displace all the cells in the row
2571
+ for (let k = 0; k < row.attributes.length; k++) {
2572
+ row.attributes[k].frame.origin.x += leftAdjustment;
2573
+ }
2574
+ }
2575
+
2576
+ // Displace the header and footer in this section
2577
+ if (section.headerAttributes) section.headerAttributes.frame.origin.x += leftAdjustment;
2578
+ if (section.footerAttributes) section.footerAttributes.frame.origin.x += leftAdjustment;
2579
+
2580
+ }
2581
+ }
2582
+
2583
+ // When using the expand gravity, a factor is used to multiply the heights of all rows
2584
+ if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Expand) {
2585
+ const spacingSize = width - usedWidth;
2586
+ const usableWidth = this.collectionView.frame.size.width - spacingSize;
2587
+
2588
+ const scaleFactor = usableWidth / usedWidth;
2589
+
2590
+ let displacement = 0;
2591
+
2592
+ // Scale and displace the rows as needed
2593
+ for (const section of this.cachedLayout.sections) {
2594
+ // Displace the header and section origin
2595
+ section.left += displacement;
2596
+ if (section.headerAttributes) section.headerAttributes.frame.origin.x += displacement;
2597
+
2598
+ for (const row of section.rows) {
2599
+ // Displace the row origin
2600
+ const rowWidth = row.right - row.left;
2601
+ row.left += displacement;
2602
+
2603
+ // Scale and display each attribute's frame
2604
+ const scaledRowHeight = rowWidth * scaleFactor | 0;
2605
+ for (const attribute of row.attributes) {
2606
+ attribute.frame.origin.x += displacement;
2607
+ attribute.frame.size.height = attribute.frame.size.height * scaleFactor | 0;
2608
+ }
2609
+
2610
+ // Advance the displacement based on the row's new width then displace the row end
2611
+ displacement += scaledRowHeight - rowWidth;
2612
+ row.right += displacement;
2613
+ }
2614
+
2615
+ // Displace the section end and the footer
2616
+ section.right += displacement;
2617
+ if (section.footerAttributes) section.footerAttributes.frame.origin.x += displacement;
2618
+ }
2619
+ }
2620
+ }
2621
+
2622
+ cachedLayout.size = BMSizeMake(Math.max(width, this.collectionView.frame.size.width), this.collectionView.frame.size.height);
2623
+ }
2624
+
2625
+ if (this._expectedCellSize && target) this.invalidateContentSize();
2626
+
2627
+
2628
+ }
2629
+ else {
2630
+ // When using fixed cell sizes, the layout will pre-compute the section start and end points
2631
+ // and generate prototype row frames for each column count
2632
+ // The attributes in these prototype rows will be displaced and reused for all data set rows
2633
+ this.cachedLayout = { sections: [], prototypeRows: [] };
2634
+ var cachedLayout = this.cachedLayout;
2635
+
2636
+ let orientation = this._orientation;
2637
+ cachedLayout.orientation = orientation;
2638
+
2639
+ // Cache all layout properties
2640
+ cachedLayout.sectionInsets = this.sectionInsets;
2641
+ cachedLayout.rowSpacing = this.rowSpacing;
2642
+ cachedLayout.showsHeaders = this.showsHeaders;
2643
+ cachedLayout.showsFooters = this.showsFooters;
2644
+ cachedLayout.headerHeight = this.headerHeight;
2645
+ cachedLayout.footerHeight = this.footerHeight;
2646
+ cachedLayout.minimumSpacing = this.minimumSpacing;
2647
+ cachedLayout.topPadding = this.topPadding;
2648
+ cachedLayout.bottomPadding = this.bottomPadding;
2649
+
2650
+ let topPadding = this.topPadding;
2651
+ let bottomPadding = this.bottomPadding;
2652
+
2653
+ cachedLayout.pinsHeadersToContentEdge = this._pinsHeadersToContentEdge;
2654
+ cachedLayout.pinsFootersToContentEdge = this._pinsFootersToContentEdge;
2655
+
2656
+ let cellWidth = this.cellSize.width;
2657
+ let rowHeight = this.cellSize.height;
2658
+ let cellSize = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? cellWidth : rowHeight;
2659
+ let breadth = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? rowHeight : cellWidth;
2660
+ let rowBreadth = breadth;
2661
+
2662
+ cachedLayout.cellWidth = cellWidth;
2663
+ cachedLayout.rowHeight = rowHeight;
2664
+ cachedLayout.rowBreadth = breadth;
2665
+
2666
+ var rowSpacing = this.rowSpacing;
2667
+ var minimumSpacing = this.minimumSpacing;
2668
+
2669
+ // The available height is the collection view frame height minus the vertical insets and the scrollbar width
2670
+ // When headers, footers are used their size and the row spacing is included as well
2671
+ var height = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? topPadding :
2672
+ this.collectionView.frame.size.height - this._sectionInsets.top - this._sectionInsets.bottom - (useOffset ? this.collectionView.scrollBarSize : 0) - topPadding - bottomPadding;
2673
+ cachedLayout.availableHeight = height;
2674
+
2675
+ // The available width is the collection view frame width minus the horizontal insets and the scrollbar width
2676
+ var width = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ?
2677
+ this.collectionView.frame.size.width - this._sectionInsets.left - this._sectionInsets.right - (useOffset ? this.collectionView.scrollBarSize : 0) :
2678
+ left;
2679
+ cachedLayout.availableWidth = width;
2680
+
2681
+ var length = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? width : height;
2682
+
2683
+ var numberOfColumns = this._maximumCellsPerRow;
2684
+ if (!numberOfColumns) {
2685
+ if (cachedLayout.minimumSpacing) {
2686
+ // Otherwise it has to be computed
2687
+ numberOfColumns = 0;
2688
+ var usedLength = 0;
2689
+
2690
+ // For spaced gravity, the used width starts with at least two minimum spacings
2691
+ if (this._gravity === BMCollectionViewFlowLayoutGravity.Spaced) {
2692
+ usedLength = 2 * minimumSpacing;
2693
+ }
2694
+
2695
+ do {
2696
+ // If this cell no longer fits within the row then the column limit has been reached
2697
+ if (usedLength && usedLength + cellSize + minimumSpacing > length) {
2698
+ break;
2699
+ }
2700
+
2701
+ // For every cell other than the first, also include the required minimum spacing
2702
+ if (numberOfColumns) {
2703
+ usedLength += minimumSpacing;
2704
+ }
2705
+
2706
+ usedLength += cellSize;
2707
+
2708
+ numberOfColumns++;
2709
+ } while (usedLength < length && cellSize);
2710
+ }
2711
+ else {
2712
+ // If there is no minimum spacing required, then the number of columns is simply the available space divided
2713
+ // by the cell width
2714
+ numberOfColumns = ((length / cellSize) | 0) || 1;
2715
+ }
2716
+ }
2717
+ cachedLayout.numberOfColumns = numberOfColumns;
2718
+
2719
+ // Generate prototype rows for each column count up to the maximum
2720
+ for (var i = 0; i < numberOfColumns; i++) {
2721
+ var prototypeRow = {attributes: []};
2722
+
2723
+ // When the final row is left-aligned all prototype rows have the maximum amount of columns
2724
+ var columns = this.leftAlignFinalRow ? numberOfColumns - 1 : i;
2725
+
2726
+ var occupiedLength = cellSize * (columns + 1) + minimumSpacing * columns;
2727
+
2728
+ // Push the prototype frames
2729
+ for (var j = 0; j <= columns; j++) {
2730
+ prototypeRow.attributes.push({frame: BMRectMake(0, 0, cellWidth, rowHeight)});
2731
+ }
2732
+
2733
+ // And reflow the row
2734
+ this._reflowRow(prototypeRow, {forLength: length, breadth: breadth, withOccupiedLength: occupiedLength, spacing: minimumSpacing});
2735
+
2736
+ cachedLayout.prototypeRows.push(prototypeRow);
2737
+
2738
+ }
2739
+
2740
+ // Save the section start and end points
2741
+ var numberOfSections = this.collectionView.numberOfSections();
2742
+
2743
+ // Each section adds height for its insets and headers and footers
2744
+ var insetTop = this._sectionInsets.top;
2745
+ var insetBottom = this._sectionInsets.bottom;
2746
+ let insetLeft = this._sectionInsets.left;
2747
+ let insetRight = this._sectionInsets.right;
2748
+
2749
+ var headerHeight = this.headerHeight;
2750
+ var footerHeight = this.footerHeight;
2751
+
2752
+ if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
2753
+ let height = insetTop + this._topPadding;
2754
+
2755
+ for (var i = 0; i < numberOfSections; i++) {
2756
+ var numberOfObjects = this.collectionView.numberOfObjectsInSectionAtIndex(i);
2757
+
2758
+ var numberOfRows = Math.ceil(numberOfObjects / numberOfColumns) | 0;
2759
+
2760
+ var spacingHeight = (numberOfRows - 1) * rowSpacing;
2761
+ if (headerHeight) spacingHeight += rowSpacing;
2762
+ if (footerHeight) spacingHeight += rowSpacing;
2763
+
2764
+ let section = {
2765
+ top: height,
2766
+ bottom: height + footerHeight + headerHeight + (rowBreadth * numberOfRows) + spacingHeight,
2767
+ numberOfObjects,
2768
+ numberOfRows
2769
+ };
2770
+ cachedLayout.sections.push(section);
2771
+
2772
+ height = section.bottom + insetTop + insetBottom;
2773
+
2774
+ // If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
2775
+ if (height > frameHeight) {
2776
+ if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
2777
+ }
2778
+ }
2779
+
2780
+ height += this._bottomPadding;
2781
+
2782
+ // If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
2783
+ if (height > frameHeight) {
2784
+ if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
2785
+ }
2786
+
2787
+ // If the height is lower than the collection view's height, the content gravity must be applied to all cached attributes
2788
+ if (height < this.collectionView.frame.size.height) {
2789
+ var topAdjustment = 0;
2790
+ if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Center) {
2791
+ topAdjustment = ((this.collectionView.frame.size.height - height) / 2) | 0;
2792
+ }
2793
+ else if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Bottom) {
2794
+ topAdjustment = this.collectionView.frame.size.height - height;
2795
+ }
2796
+
2797
+ if (topAdjustment != 0) {
2798
+ var sectionsLength = cachedLayout.sections.length;
2799
+
2800
+ // Displace the sections
2801
+ for (var i = 0; i < sectionsLength; i++) {
2802
+ var section = cachedLayout.sections[i];
2803
+
2804
+ section.top += topAdjustment;
2805
+ section.bottom += topAdjustment;
2806
+
2807
+ }
2808
+ }
2809
+
2810
+ if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Expand) {
2811
+ // With static sizes, all rows and attributes have the same height, so the adjustment can just be directly applied to each row
2812
+ let usedHeight = 0;
2813
+ for (const section of cachedLayout.sections) {
2814
+ usedHeight += section.numberOfRows * rowBreadth;
2815
+ }
2816
+
2817
+ const spacingSize = height - usedHeight;
2818
+ const scaleFactor = (this.collectionView.frame.size.height - spacingSize) / usedHeight;
2819
+
2820
+ // Multiply the height of the prototype rows by the scale factor
2821
+ for (const row of cachedLayout.prototypeRows) {
2822
+ for (const attribute of row.attributes) {
2823
+ attribute.frame.size.height = attribute.frame.size.height * scaleFactor | 0;
2824
+ }
2825
+ }
2826
+
2827
+ // Adjust the bounds of each section as needed
2828
+ let displacement = 0;
2829
+ for (const section of cachedLayout.sections) {
2830
+ section.top += displacement;
2831
+
2832
+ const sectionHeight = section.numberOfRows * rowBreadth;
2833
+ displacement += (sectionHeight * scaleFactor | 0) - sectionHeight;
2834
+
2835
+ section.bottom += displacement;
2836
+ }
2837
+
2838
+ cachedLayout.rowHeight = cachedLayout.rowHeight * scaleFactor | 0;
2839
+ cachedLayout.rowBreadth = cachedLayout.rowBreadth * scaleFactor | 0;
2840
+ }
2841
+ }
2842
+ }
2843
+ else {
2844
+ let width = insetLeft;
2845
+
2846
+ for (var i = 0; i < numberOfSections; i++) {
2847
+ var numberOfObjects = this.collectionView.numberOfObjectsInSectionAtIndex(i);
2848
+
2849
+ var numberOfRows = Math.ceil(numberOfObjects / numberOfColumns) | 0;
2850
+
2851
+ var spacingWidth = (numberOfRows - 1) * rowSpacing;
2852
+ if (headerHeight) spacingWidth += rowSpacing;
2853
+ if (footerHeight) spacingWidth += rowSpacing;
2854
+
2855
+ let section = {
2856
+ left: width,
2857
+ right: width + (breadth * numberOfRows) + spacingWidth,
2858
+ numberOfObjects,
2859
+ numberOfRows
2860
+ };
2861
+ cachedLayout.sections.push(section);
2862
+
2863
+ width = section.right + insetLeft + insetRight;
2864
+
2865
+ // If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
2866
+ if (width > frameWidth) {
2867
+ if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
2868
+ }
2869
+ }
2870
+
2871
+ // If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
2872
+ if (width > frameWidth) {
2873
+ if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
2874
+ }
2875
+
2876
+ // If the height is lower than the collection view's height, the content gravity must be applied to all cached attributes
2877
+ if (width < this.collectionView.frame.size.width) {
2878
+ var leftAdjustment = 0;
2879
+ if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Center) {
2880
+ leftAdjustment = ((this.collectionView.frame.size.width - width) / 2) | 0;
2881
+ }
2882
+ else if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Bottom) {
2883
+ leftAdjustment = this.collectionView.frame.size.width - width;
2884
+ }
2885
+
2886
+ if (leftAdjustment != 0) {
2887
+ var sectionsLength = cachedLayout.sections.length;
2888
+
2889
+ // Displace the sections
2890
+ for (var i = 0; i < sectionsLength; i++) {
2891
+ var section = cachedLayout.sections[i];
2892
+
2893
+ section.left += leftAdjustment;
2894
+ section.right += leftAdjustment;
2895
+
2896
+ }
2897
+ }
2898
+
2899
+ if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Expand) {
2900
+ // With static sizes, all rows and attributes have the same width, so the adjustment can just be directly applied to each row
2901
+ let usedWidth = 0;
2902
+ for (const section of cachedLayout.sections) {
2903
+ usedWidth += section.numberOfRows * rowBreadth;
2904
+ }
2905
+
2906
+ const spacingSize = width - usedWidth;
2907
+ const scaleFactor = (this.collectionView.frame.size.width - spacingSize) / usedWidth;
2908
+
2909
+ // Multiply the width of the prototype rows by the scale factor
2910
+ for (const row of cachedLayout.prototypeRows) {
2911
+ for (const attribute of row.attributes) {
2912
+ attribute.frame.size.width = attribute.frame.size.width * scaleFactor | 0;
2913
+ }
2914
+ }
2915
+
2916
+ // Adjust the bounds of each section as needed
2917
+ let displacement = 0;
2918
+ for (const section of cachedLayout.sections) {
2919
+ section.left += displacement;
2920
+
2921
+ const sectionWidth = section.numberOfRows * rowBreadth;
2922
+ displacement += (sectionWidth * scaleFactor | 0) - sectionWidth;
2923
+
2924
+ section.right += displacement;
2925
+ }
2926
+ cachedLayout.cellWidth = cachedLayout.cellWidth * scaleFactor | 0;
2927
+ cachedLayout.rowBreadth = cachedLayout.rowBreadth * scaleFactor | 0;
2928
+ }
2929
+ }
2930
+ }
2931
+
2932
+ }
2933
+
2934
+ },
2935
+
2936
+ // @override - BMCollectionViewLayout
2937
+ constraintsForMeasuringCell(cell, {atIndexPath}) {
2938
+ // In flow layout, a cells's width cannot exceed the collection view's frame
2939
+ return [BMLayoutConstraint.constraintWithView(cell, {
2940
+ attribute: BMLayoutAttribute.Width,
2941
+ relatedBy: BMLayoutConstraintRelation.LessThanOrEquals,
2942
+ constant: this.collectionView.frame.size.width - this.sectionInsets.left - this.sectionInsets.right
2943
+ })]
2944
+ },
2945
+
2946
+ /**
2947
+ * @deprecated Not used anymore
2948
+ * Reflows a cached row object based on the current alignment and gravity.
2949
+ * @param row <Object> The cached row object.
2950
+ * {
2951
+ * @param forWidth <Number> The row's width.
2952
+ * @param height <Number> The row's height.
2953
+ * @param withOccupiedWidth <Number> The width occupied by the cells.
2954
+ * @param spacing <Number> The minimum spacing to use between the cells.
2955
+ * }
2956
+ */
2957
+ reflowRow: function (row, options) {
2958
+ var width = options.forWidth;
2959
+ var rowWidth = options.withOccupiedWidth;
2960
+
2961
+ var rowHeight = options.height;
2962
+
2963
+ var spacing = options.spacing;
2964
+
2965
+ // Compute the width occupied by the spacing
2966
+ var spacingSize = spacing * (row.attributes.length - 1);
2967
+ //rowWidth += spacingSize;
2968
+
2969
+ // Compute the width occupied by the actual cells, without the spacing
2970
+ var cellOccupiedWidth = rowWidth - spacingSize;
2971
+
2972
+ var remainingSpace = width - rowWidth;
2973
+
2974
+ var gravity = this._gravity;
2975
+ var alignment = this._alignment;
2976
+
2977
+ var cellCount = row.attributes.length;
2978
+
2979
+ // When the items flow horizontally they start out at left margin
2980
+ // Their width is multiplied by the widthMultiplier
2981
+ // Then the next item is placed after spacing pixels from the previous one
2982
+ var widthMultiplier = gravity == BMCollectionViewFlowLayoutGravity.Expand ? (width - spacingSize) / cellOccupiedWidth : 1;
2983
+ // Because the width multiplier may lead to fractional values for the widths
2984
+ // which are then truncated to integer sizes, the additional pixels are accounted for
2985
+ // by adding 1 pixel to the width of cells until the entire space is used
2986
+ var extraPixels = 0;
2987
+ if (gravity == BMCollectionViewFlowLayoutGravity.Expand) {
2988
+ var expandedOccupiedWidth = 0;
2989
+ for (var i = 0; i < cellCount; i++) {
2990
+ expandedOccupiedWidth += (row.attributes[i].frame.size.width * widthMultiplier) | 0;
2991
+ }
2992
+ extraPixels = ((width - spacingSize) | 0) % (expandedOccupiedWidth | 0);
2993
+ }
2994
+
2995
+ // The spacing extra pixels is similar to the extraPixels variable, except that it is used for the other gravities
2996
+ // and is added to spacing other than the cell sizes
2997
+ var spacingExtraPixels = 0;
2998
+
2999
+ var leftMargin = this._sectionInsets.left;
3000
+
3001
+ // Implicitly, for left gravity, the leftMargin does not change
3002
+ if (gravity === BMCollectionViewFlowLayoutGravity.Center) {
3003
+ leftMargin = (leftMargin + (width - rowWidth) / 2) | 0;
3004
+ }
3005
+ else if (gravity === BMCollectionViewFlowLayoutGravity.End) {
3006
+ leftMargin = (leftMargin + width - rowWidth) | 0;
3007
+ }
3008
+ else if (gravity === BMCollectionViewFlowLayoutGravity.Edge) {
3009
+ spacing = ((width - cellOccupiedWidth) / (cellCount - 1)) | 0;
3010
+
3011
+ // Compute the extra spacing pixels as the remainder between the row width and the total occupied integer width
3012
+ spacingExtraPixels = width - (spacing * (cellCount - 1) + cellOccupiedWidth);
3013
+ }
3014
+ else if (gravity === BMCollectionViewFlowLayoutGravity.Spaced) {
3015
+ spacing = ((width - cellOccupiedWidth) / (cellCount + 1)) | 0;
3016
+
3017
+ // Compute the extra spacing pixels as the remainder between the row width and the total occupied integer width
3018
+ spacingExtraPixels = width - (spacing * (cellCount + 1) + cellOccupiedWidth);
3019
+
3020
+ leftMargin = leftMargin + spacing;
3021
+ }
3022
+
3023
+ // Flow the items horizontally and align them vertially
3024
+ for (var i = 0; i < cellCount; i++) {
3025
+ var cellAttributes = row.attributes[i];
3026
+
3027
+ // Flow the item horizontally
3028
+ cellAttributes.frame.origin.x = leftMargin;
3029
+ cellAttributes.frame.size.width *= widthMultiplier;
3030
+ // Constrain the width to an integer size
3031
+ cellAttributes.frame.size.width = cellAttributes.frame.size.width | 0;
3032
+
3033
+ // Add the additional extra pixel if needed, then subtract from the remaining pixels
3034
+ if (extraPixels) {
3035
+ cellAttributes.frame.size.width += 1;
3036
+ extraPixels--;
3037
+ }
3038
+
3039
+ leftMargin += spacing + cellAttributes.frame.size.width;
3040
+ // Add the additional spacing extra pixel if needed, then subtract from the remaining pixels
3041
+ if (spacingExtraPixels) {
3042
+ leftMargin += 1;
3043
+ spacingExtraPixels--;
3044
+ }
3045
+
3046
+ // Align it vertically
3047
+ if (alignment === BMCollectionViewFlowLayoutAlignment.Center) {
3048
+ cellAttributes.frame.origin.y += ((rowHeight - cellAttributes.frame.size.height) / 2) | 0;
3049
+ }
3050
+ else if (alignment === BMCollectionViewFlowLayoutAlignment.Bottom) {
3051
+ cellAttributes.frame.origin.y += rowHeight - cellAttributes.frame.size.height;
3052
+ }
3053
+ else if (alignment === BMCollectionViewFlowLayoutAlignment.Expand) {
3054
+ cellAttributes.frame.size.height = rowHeight;
3055
+ }
3056
+
3057
+ }
3058
+
3059
+ },
3060
+
3061
+
3062
+ /**
3063
+ * Reflows a cached row object based on the current alignment, axis and gravity.
3064
+ * @param row <Object> The cached row object.
3065
+ * {
3066
+ * @param forLength <Number> The row's size along the secondary axis.
3067
+ * @param breadth <Number> The row's size along the primary axis.
3068
+ * @param withOccupiedLength <Number> The size occupied by the cells along the secondary axis.
3069
+ * @param spacing <Number> The minimum spacing to use between the cells.
3070
+ * }
3071
+ */
3072
+ _reflowRow: function (row, {forLength: length, breadth, withOccupiedLength: occupiedLength, spacing}) {
3073
+ //var length = options.forWidth;
3074
+ //var occupiedLength = options.withOccupiedWidth;
3075
+
3076
+ //var breadth = options.height;
3077
+
3078
+ //var spacing = options.spacing;
3079
+
3080
+ // Compute the width occupied by the spacing
3081
+ var spacingSize = spacing * (row.attributes.length - 1);
3082
+ //rowWidth += spacingSize;
3083
+
3084
+ // Compute the width occupied by the actual cells, without the spacing
3085
+ var cellOccupiedLength = occupiedLength - spacingSize;
3086
+
3087
+ var remainingSpace = length - occupiedLength;
3088
+
3089
+ var gravity = this._gravity;
3090
+ var alignment = this._alignment;
3091
+ let orientation = this._orientation;
3092
+ let lengthAttribute = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? 'width' : 'height';
3093
+ let secondaryLengthAttribute = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? 'height' : 'width';
3094
+ let positionAttribute = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? 'x' : 'y';
3095
+ let secondaryPositionAttribute = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? 'y' : 'x';
3096
+
3097
+ var cellCount = row.attributes.length;
3098
+
3099
+ // When the items flow horizontally they start out at left margin
3100
+ // Their width is multiplied by the widthMultiplier
3101
+ // Then the next item is placed after spacing pixels from the previous one
3102
+ var lengthMultiplier = gravity == BMCollectionViewFlowLayoutGravity.Expand ? (length - spacingSize) / cellOccupiedLength : 1;
3103
+ // Because the width multiplier may lead to fractional values for the widths
3104
+ // which are then truncated to integer sizes, the additional pixels are accounted for
3105
+ // by adding 1 pixel to the width of cells until the entire space is used
3106
+ var extraPixels = 0;
3107
+ if (gravity == BMCollectionViewFlowLayoutGravity.Expand) {
3108
+ var expandedOccupiedLength = 0;
3109
+ for (var i = 0; i < cellCount; i++) {
3110
+ expandedOccupiedLength += (row.attributes[i].frame.size[lengthAttribute] * lengthMultiplier) | 0;
3111
+ }
3112
+ extraPixels = ((length - spacingSize) | 0) % (expandedOccupiedLength | 0);
3113
+ }
3114
+
3115
+ // The spacing extra pixels is similar to the extraPixels variable, except that it is used for the other gravities
3116
+ // and is added to spacing other than the cell sizes
3117
+ var spacingExtraPixels = 0;
3118
+
3119
+ var startMargin = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? this._sectionInsets.left : this._sectionInsets.top + this.topPadding;
3120
+
3121
+ // Implicitly, for left gravity, the leftMargin does not change
3122
+ if (gravity === BMCollectionViewFlowLayoutGravity.Center) {
3123
+ startMargin = (startMargin + (length - occupiedLength) / 2) | 0;
3124
+ }
3125
+ else if (gravity === BMCollectionViewFlowLayoutGravity.End) {
3126
+ startMargin = (startMargin + length - occupiedLength) | 0;
3127
+ }
3128
+ else if (gravity === BMCollectionViewFlowLayoutGravity.Edge) {
3129
+ spacing = ((length - cellOccupiedLength) / (cellCount - 1)) | 0;
3130
+
3131
+ // Compute the extra spacing pixels as the remainder between the row width and the total occupied integer width
3132
+ spacingExtraPixels = length - (spacing * (cellCount - 1) + cellOccupiedLength);
3133
+ }
3134
+ else if (gravity === BMCollectionViewFlowLayoutGravity.Spaced) {
3135
+ spacing = ((length - cellOccupiedLength) / (cellCount + 1)) | 0;
3136
+
3137
+ // Compute the extra spacing pixels as the remainder between the row width and the total occupied integer width
3138
+ spacingExtraPixels = length - (spacing * (cellCount + 1) + cellOccupiedLength);
3139
+
3140
+ startMargin = startMargin + spacing;
3141
+ }
3142
+
3143
+ // Flow the items horizontally and align them vertially
3144
+ for (var i = 0; i < cellCount; i++) {
3145
+ var cellAttributes = row.attributes[i];
3146
+
3147
+ // Flow the item horizontally
3148
+ cellAttributes.frame.origin[positionAttribute] = startMargin;
3149
+ cellAttributes.frame.size[lengthAttribute] *= lengthMultiplier;
3150
+ // Constrain the width to an integer size
3151
+ cellAttributes.frame.size[lengthAttribute] = cellAttributes.frame.size[lengthAttribute] | 0;
3152
+
3153
+ // Add the additional extra pixel if needed, then subtract from the remaining pixels
3154
+ if (extraPixels) {
3155
+ cellAttributes.frame.size[lengthAttribute] += 1;
3156
+ extraPixels--;
3157
+ }
3158
+
3159
+ startMargin += spacing + cellAttributes.frame.size[lengthAttribute];
3160
+ // Add the additional spacing extra pixel if needed, then subtract from the remaining pixels
3161
+ if (spacingExtraPixels) {
3162
+ startMargin += 1;
3163
+ spacingExtraPixels--;
3164
+ }
3165
+
3166
+ // Align it vertically
3167
+ if (alignment === BMCollectionViewFlowLayoutAlignment.Center) {
3168
+ cellAttributes.frame.origin[secondaryPositionAttribute] += ((breadth - cellAttributes.frame.size[secondaryLengthAttribute]) / 2) | 0;
3169
+ }
3170
+ else if (alignment === BMCollectionViewFlowLayoutAlignment.Bottom) {
3171
+ cellAttributes.frame.origin[secondaryPositionAttribute] += breadth - cellAttributes.frame.size[secondaryLengthAttribute];
3172
+ }
3173
+ else if (alignment === BMCollectionViewFlowLayoutAlignment.Expand) {
3174
+ cellAttributes.frame.size[secondaryLengthAttribute] = breadth;
3175
+ }
3176
+
3177
+ }
3178
+ },
3179
+
3180
+ // @override
3181
+ contentSize: function () {
3182
+
3183
+ if (!this._cellSize || this._expectedCellSize) return this.cachedLayout.size;
3184
+
3185
+ // The size should never be smaller than the collection view frame
3186
+ if (this._orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
3187
+ return this.cachedLayout.sections.length ?
3188
+ BMSizeMake(this.collectionView.frame.size.width,
3189
+ Math.max(this.cachedLayout.sections[this.cachedLayout.sections.length - 1].bottom + this._bottomPadding + this.sectionInsets.bottom,
3190
+ this.collectionView.frame.size.height)) :
3191
+ this.collectionView.frame.size.copy();
3192
+ }
3193
+ else {
3194
+ return this.cachedLayout.sections.length ?
3195
+ BMSizeMake(Math.max(this.cachedLayout.sections[this.cachedLayout.sections.length - 1].right + this.sectionInsets.right,
3196
+ this.collectionView.frame.size.width),
3197
+ this.collectionView.frame.size.height) :
3198
+ this.collectionView.frame.size.copy();
3199
+ }
3200
+
3201
+
3202
+ },
3203
+
3204
+ // @override - BMCollectionViewTableLayout
3205
+ shouldInvalidateLayoutForFrameChange(frame, {fromFrame}) {
3206
+ if (fromFrame && fromFrame.size.width < frame.size.width) {
3207
+ // If the size increases, invalidate all existing measured cells as their measured size can be less than the
3208
+ // collection view's frame size as a result of how text wraps
3209
+ this.collectionView.invalidateMeasuredSizeOfCells();
3210
+ }
3211
+ else {
3212
+ // Because the measured size of the cells can depend upon the size of collection view's frame, in addition
3213
+ // to invalidating the layout, flow layout also invalidates the size of cells that are as wide as collection view's previous frame.
3214
+ this.collectionView.invalidateMeasuredSizeOfCellsWithBlock(size => {
3215
+ return size.width >= this.cachedLayout.availableWidth;
3216
+ });
3217
+ }
3218
+
3219
+ return YES;
3220
+ },
3221
+
3222
+ // @override - BMCollectionViewLayout
3223
+ shouldInvalidateLayoutForBoundsChange: function (bounds) {
3224
+ var shouldInvalidate = this._pinsHeadersToContentEdge || this._pinsFootersToContentEdge;
3225
+
3226
+ // When using automatic cell sizes, calculate the rest of the layout up to the given bounds, if needed
3227
+ // and invalidate the layout appropriately
3228
+ if (this._orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
3229
+ if (this._expectedCellSize && bounds.bottom > this.cachedLayout.resolvedHeight) {
3230
+ this._layoutIterator.next({rect: bounds});
3231
+ shouldInvalidate = YES;
3232
+ }
3233
+ }
3234
+ else {
3235
+ if (this._expectedCellSize && bounds.right > this.cachedLayout.resolvedWidth) {
3236
+ this._layoutIterator.next({rect: bounds});
3237
+ shouldInvalidate = YES;
3238
+ }
3239
+ }
3240
+
3241
+ if (shouldInvalidate) {
3242
+ // While scrolling, it is not needed to recalculate the layout caches.
3243
+ this.isChangingBounds = YES;
3244
+ }
3245
+
3246
+ return shouldInvalidate;
3247
+ },
3248
+
3249
+ // @override - BMCollectionViewLayout
3250
+ didInvalidateLayoutForBoundsChange: function () {
3251
+ this.isChangingBounds = NO;
3252
+ },
3253
+
3254
+
3255
+ // @override
3256
+ attributesForCellAtIndexPath: function (indexPath) {
3257
+ // When using automatic cell sizes, it is important to ensure that the sizing information has been computed
3258
+ // for the requested index path
3259
+ if (this._expectedCellSize) {
3260
+ if (this.cachedLayout.resolvedIndexPath.section < indexPath.section ||
3261
+ (this.cachedLayout.resolvedIndexPath.section == indexPath.section && this.cachedLayout.resolvedIndexPath.row < indexPath.row)) {
3262
+ // If the layout has not been resolved up to this index path, continue computing it until it has
3263
+ this._layoutIterator.next({indexPath: indexPath});
3264
+ }
3265
+ }
3266
+
3267
+ // Return the cached attributes when using dynamic sizes
3268
+ if (!this._cellSize || this._expectedCellSize) return this.cachedAttributesForCellAtIndexPath(indexPath);
3269
+
3270
+ // Otherwise compute the attributes
3271
+ return this.computedAttributesForCellAtIndexPath(indexPath);
3272
+
3273
+ },
3274
+
3275
+ /**
3276
+ * Returns the cached cell attributes for the cell at the given index path.
3277
+ * This method should only be used when using variable cell sizes.
3278
+ * @param indexPath <BMIndexPath> The cell's index path.
3279
+ * {
3280
+ * @param usingCache <Object, nullable> Defaults to this layout's current cache. The layout cache.
3281
+ * }
3282
+ * @return <BMCollectionViewLayoutAttributes> The corresponding cell attributes.
3283
+ * @throw If there are no cached attributes for the specified index path.
3284
+ */
3285
+ cachedAttributesForCellAtIndexPath: function (indexPath, options) {
3286
+ var cache = (options && options.usingCache) || this.cachedLayout;
3287
+
3288
+ // Get the cached section corresponding to the index path
3289
+ var sectionRows = cache.sections[indexPath.section].rows;
3290
+ var index = indexPath.row;
3291
+
3292
+ // Find the row in which the index path is located
3293
+ var sectionRowsLength = sectionRows.length;
3294
+ for (var i = 0; i < sectionRowsLength; i++) {
3295
+ var row = sectionRows[i];
3296
+
3297
+ if (index >= row.startIndex && index <= row.endIndex) {
3298
+ return row.attributes[index - row.startIndex];
3299
+ }
3300
+
3301
+ }
3302
+
3303
+ throw 'Unable to find the cached attributes for the cell at the given index path';
3304
+ },
3305
+
3306
+ /**
3307
+ * Computes and returns the attributes for the cell at the given index path.
3308
+ * This method should only be used when using fixed cell sizes.
3309
+ * @param indexPath <BMIndexPath> The cell's index path.
3310
+ * {
3311
+ * @param usingCache <Object, nullable> Defaults to this layout's current cache. The layout cache.
3312
+ * }
3313
+ * @return <BMCollectionViewLayoutAttributes> The corresponding cell attributes.
3314
+ * @throws If the index path is out of bounds.
3315
+ */
3316
+ computedAttributesForCellAtIndexPath: function (indexPath, options) {
3317
+ var cache = (options && options.usingCache) || this.cachedLayout;
3318
+
3319
+ var sectionIndex = indexPath.section;
3320
+ var section = cache.sections[sectionIndex];
3321
+
3322
+ var numberOfColumns = cache.numberOfColumns;
3323
+ var numberOfObjects = section.numberOfObjects;
3324
+
3325
+ var objectIndex = indexPath.row;
3326
+
3327
+ // Find the row and column index within the section
3328
+ var cellRow = (objectIndex / numberOfColumns) | 0;
3329
+ var cellColumn = objectIndex % numberOfColumns;
3330
+
3331
+ // Find this cell's row prototype. In most cases this will be the prototype for the numberOfColumns columns,
3332
+ // unless this cell is in the last row - in those cases it is possible that there may be fewer columns
3333
+ if (cellRow == ((numberOfObjects / numberOfColumns) | 0)) {
3334
+ var numberOfColumnsInLastRow = (numberOfObjects % numberOfColumns) || numberOfColumns;
3335
+
3336
+ var prototypeRow = cache.prototypeRows[numberOfColumnsInLastRow - 1];
3337
+ }
3338
+ else {
3339
+ var prototypeRow = cache.prototypeRows[numberOfColumns - 1];
3340
+ }
3341
+
3342
+ var attributes;
3343
+
3344
+ // Find out where the row starts
3345
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
3346
+ let rowTop = section.top;
3347
+ if (cache.showsHeaders) {
3348
+ rowTop += cache.headerHeight + cache.rowSpacing;
3349
+ }
3350
+
3351
+ rowTop += cellRow * (cache.rowHeight + cache.rowSpacing);
3352
+
3353
+ // Construct the attributes
3354
+ attributes = BMCollectionViewLayoutAttributesMakeForCellAtIndexPath(indexPath);
3355
+ attributes.frame = prototypeRow.attributes[cellColumn].frame.copy();
3356
+ attributes.frame.origin.y = rowTop;
3357
+ }
3358
+ else {
3359
+ let rowLeft = section.left;
3360
+ if (cache.showsHeaders) {
3361
+ rowLeft += cache.headerHeight + cache.rowSpacing;
3362
+ }
3363
+
3364
+ rowLeft += cellRow * (cache.rowBreadth + cache.rowSpacing);
3365
+
3366
+ // Construct the attributes
3367
+ attributes = BMCollectionViewLayoutAttributesMakeForCellAtIndexPath(indexPath);
3368
+ attributes.frame = prototypeRow.attributes[cellColumn].frame.copy();
3369
+ attributes.frame.origin.x = rowLeft;
3370
+ }
3371
+
3372
+ return attributes;
3373
+
3374
+ },
3375
+
3376
+ // @override
3377
+ attributesForSupplementaryViewWithIdentifier: function (identifier, options) {
3378
+
3379
+ // The empty supplementary view will always ever have one instance with a preset index path regardless of whether the layout is cached or computed
3380
+ if (identifier === BMCollectionViewTableLayoutSupplementaryView.Empty) {
3381
+ var attribute = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
3382
+ BMCollectionViewTableLayoutSupplementaryView.Empty,
3383
+ {atIndexPath: BMIndexPathMakeWithIndexes([0])}
3384
+ );
3385
+
3386
+ attribute.frame = BMRectMake(0, 0, this.collectionView.frame.size.width, this.collectionView.frame.size.height);
3387
+ return attribute;
3388
+ }
3389
+
3390
+ if (this._expectedCellSize) {
3391
+ if (this.cachedLayout.resolvedIndexPath.section < options.atIndexPath.section ||
3392
+ (this.cachedLayout.resolvedIndexPath.section == options.atIndexPath.section && identifier == BMCollectionViewFlowLayoutSupplementaryView.Footer)) {
3393
+ if (options.atIndexPath.section == this.collectionView.numberOfSections() - 1) {
3394
+ // If there is no following section, compute until the last index path
3395
+ this._layoutIterator.next({indexPath: this.collectionView.indexPathForObjectAtRow(this.collectionView.numberOfObjectsInSectionAtIndex(options.atIndexPath.section) - 1, {inSectionAtIndex: options.atIndexPath.section})});
3396
+ }
3397
+ else {
3398
+ // If the layout has not been resolved up to this index path, continue computing up to the beginning of the next section
3399
+ this._layoutIterator.next({indexPath: this.collectionView.indexPathForObjectAtRow(0, {inSectionAtIndex: options.atIndexPath.section + 1})});
3400
+ }
3401
+ }
3402
+ }
3403
+
3404
+ // Return the cached attributes when using dynamic sizes
3405
+ if (!this._cellSize || this._expectedCellSize) return this.cachedAttributesForSupplementaryViewWithIdentifier(identifier, options);
3406
+
3407
+ // Otherwise compute the attributes
3408
+ return this.computedAttributesForSupplementaryViewWithIdentifier(identifier, options);
3409
+
3410
+ },
3411
+
3412
+
3413
+ /**
3414
+ * Returns the cached cell attributes for the supplementary view with the given identifier at the given index path.
3415
+ * This method should only be used when using variable cell sizes.
3416
+ * @param identifier <String> The supplementary view's type.
3417
+ * {
3418
+ * @param atIndexPath <BMIndexPath> The supplementary view's index path.
3419
+ * @param usingCache <Object, nullable> Defaults to this layout's current cache. The layout cache.
3420
+ * }
3421
+ * @return <BMCollectionViewLayoutAttributes, nullable> The corresponding cell attributes, or undefined if there are no cached attributes for the specified index path
3422
+ * or the requested supplementary view type is unsupported.
3423
+ */
3424
+ cachedAttributesForSupplementaryViewWithIdentifier: function (identifier, options) {
3425
+ var cache = (options && options.usingCache) || this.cachedLayout;
3426
+ var section = options.atIndexPath.section;
3427
+
3428
+ if (section >= cache.sections.length) return undefined;
3429
+
3430
+ if (identifier === BMCollectionViewFlowLayoutSupplementaryView.Header) {
3431
+ return cache.sections[section].headerAttributes;
3432
+ }
3433
+ else if (identifier === BMCollectionViewFlowLayoutSupplementaryView.Footer) {
3434
+ return cache.sections[section].footerAttributes;
3435
+ }
3436
+
3437
+ // Other types of supplementary views are not supported.
3438
+ return undefined;
3439
+ },
3440
+
3441
+
3442
+ /**
3443
+ * Computes and returns the cell attributes for the supplementary view with the given identifier at the given index path.
3444
+ * This method should only be used when using fixed cell sizes.
3445
+ * @param identifier <String> The supplementary view's type.
3446
+ * {
3447
+ * @param atIndexPath <BMIndexPath> The supplementary view's index path.
3448
+ * @param usingCache <Object, nullable> Defaults to this layout's current cache. The layout cache.
3449
+ * }
3450
+ * @return <BMCollectionViewLayoutAttributes, nullable> The corresponding cell attributes, or undefined if the section index is out of bounds
3451
+ * or the requested supplementary view type is unsupported.
3452
+ */
3453
+ computedAttributesForSupplementaryViewWithIdentifier: function (identifier, options) {
3454
+ var cache = (options && options.usingCache) || this.cachedLayout;
3455
+ var sectionIndex = options.atIndexPath.section;
3456
+
3457
+ if (sectionIndex >= cache.sections.length) return undefined;
3458
+
3459
+ var section = cache.sections[sectionIndex];
3460
+ var attributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath});
3461
+
3462
+ if (identifier === BMCollectionViewFlowLayoutSupplementaryView.Header) {
3463
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
3464
+ attributes.frame = BMRectMake( cache.sectionInsets.left,
3465
+ section.top,
3466
+ this.collectionView.frame.size.width - cache.sectionInsets.left - cache.sectionInsets.right,
3467
+ cache.headerHeight);
3468
+ }
3469
+ else {
3470
+ attributes.frame = BMRectMake( section.left,
3471
+ cache.sectionInsets.top + cache.topPadding,
3472
+ cache.headderHeight,
3473
+ this.collectionView.frame.size.height - cache.sectionInsets.top - cache.sectionInsets.bottom - cache.topPadding - cache.bottomPadding);
3474
+ }
3475
+
3476
+ return attributes;
3477
+ }
3478
+ else if (identifier === BMCollectionViewFlowLayoutSupplementaryView.Footer) {
3479
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
3480
+ attributes.frame = BMRectMake( cache.sectionInsets.left,
3481
+ section.bottom - cache.footerHeight,
3482
+ this.collectionView.frame.size.width - cache.sectionInsets.left - cache.sectionInsets.right,
3483
+ cache.footerHeight);
3484
+ }
3485
+ else {
3486
+ attributes.frame = BMRectMake( section.right - cache.footerHeight,
3487
+ cache.sectionInsets.top + cache.topPadding,
3488
+ cache.footerHeight,
3489
+ this.collectionView.frame.size.height - cache.sectionInsets.top - cache.sectionInsets.bottom - cache.topPadding - cache.bottomPadding);
3490
+ }
3491
+
3492
+ return attributes;
3493
+ }
3494
+
3495
+ // Other types of supplementary views are not supported.
3496
+ return undefined;
3497
+ },
3498
+
3499
+
3500
+ // @override
3501
+ attributesForElementsInRect: function (rect) {
3502
+ // When using automatic cell sizes, it is important to ensure that the sizing information has been computed
3503
+ // for the entire requested rexct
3504
+ if (this._expectedCellSize) {
3505
+ if (this._orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
3506
+ if (this.cachedLayout.resolvedHeight < rect.bottom) {
3507
+ // If the layout has not been resolved for this rect, continue computing it until it has
3508
+ this._layoutIterator.next({rect: rect});
3509
+
3510
+ // Invalidate the layout size
3511
+ this.invalidateContentSize();
3512
+ }
3513
+ }
3514
+ else {
3515
+ if (this.cachedLayout.resolvedWidth < rect.right) {
3516
+ // If the layout has not been resolved for this rect, continue computing it until it has
3517
+ this._layoutIterator.next({rect: rect});
3518
+
3519
+ // Invalidate the layout size
3520
+ this.invalidateContentSize();
3521
+ }
3522
+ }
3523
+ }
3524
+
3525
+ // When there are no sections in the collection view, this method will always return a single instance of attributes
3526
+ // for the empty supplementary view
3527
+ if (this.collectionView.numberOfSections() == 0) {
3528
+ var attribute = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
3529
+ BMCollectionViewTableLayoutSupplementaryView.Empty,
3530
+ {atIndexPath: BMIndexPathMakeWithIndexes([0])}
3531
+ );
3532
+
3533
+ attribute.frame = BMRectMake(0, 0, this.collectionView.frame.size.width, this.collectionView.frame.size.height);
3534
+ return [attribute];
3535
+ }
3536
+
3537
+ if (!this._cellSize || this._expectedCellSize) return this.cachedAttributesForElementsInRect(rect);
3538
+
3539
+ return this.computedAttributesForElementsInRect(rect);
3540
+
3541
+ },
3542
+
3543
+
3544
+ /**
3545
+ * Returns the cached cell attributes for all elements in the given rect. This method should only be used when using variable cell sizes.
3546
+ * @param rect <BMRect> The rect.
3547
+ * {
3548
+ * @param usingCache <Object, nullable> Defaults to this layout's current cache. The layout cache.
3549
+ * }
3550
+ * @return <[BMCollectionViewLayoutAttributes]> An array of cell attributes.
3551
+ */
3552
+ cachedAttributesForElementsInRect: function (rect, options) {
3553
+ var cache = (options && options.usingCache) || this.cachedLayout;
3554
+ var attributes = [];
3555
+
3556
+ var rectTop = rect.origin.y;
3557
+ var rectBottom = rect.bottom;
3558
+ var rectLeft = rect.origin.x;
3559
+ var rectRight = rect.right;
3560
+
3561
+ var sectionIndex = 0;
3562
+ var sectionCount = this._expectedCellSize ? cache.sectionCount : cache.sections.length;
3563
+ var section;
3564
+
3565
+ // Find the section that intersects this rect's top origin
3566
+ for (; sectionIndex < sectionCount; sectionIndex++) {
3567
+ section = cache.sections[sectionIndex];
3568
+
3569
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
3570
+ if (section.bottom > rectTop) break;
3571
+ }
3572
+ else {
3573
+ if (section.right > rectLeft) break;
3574
+ }
3575
+ }
3576
+
3577
+ // If there were no sections return nothing
3578
+ if (sectionIndex == sectionCount) return attributes;
3579
+
3580
+ // Find the first row index that intersects the rect's top.
3581
+ var rowIndex = -1;
3582
+
3583
+ // Check the header first
3584
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
3585
+ if (!section.headerAttributes || section.headerAttributes.frame.bottom < rectTop) {
3586
+
3587
+ // If the header doesn't intersect the point, check each row
3588
+ rowIndex = 0;
3589
+
3590
+ var rows = section.rows;
3591
+ var rowsLength = rows.length;
3592
+
3593
+ for (var i = 0; i < rowsLength; i++) {
3594
+ if (rows[i].bottom > rectTop) {
3595
+ rowIndex = i;
3596
+ break;
3597
+ }
3598
+ }
3599
+
3600
+ // And finally, if no row intersects the point, the footer must intersect it
3601
+ rowIndex = i;
3602
+ }
3603
+ }
3604
+ else {
3605
+ if (!section.headerAttributes || section.headerAttributes.frame.right < rectLeft) {
3606
+
3607
+ // If the header doesn't intersect the point, check each row
3608
+ rowIndex = 0;
3609
+
3610
+ var rows = section.rows;
3611
+ var rowsLength = rows.length;
3612
+
3613
+ for (var i = 0; i < rowsLength; i++) {
3614
+ if (rows[i].right > rectLeft) {
3615
+ rowIndex = i;
3616
+ break;
3617
+ }
3618
+ }
3619
+
3620
+ // And finally, if no row intersects the point, the footer must intersect it
3621
+ rowIndex = i;
3622
+ }
3623
+ }
3624
+
3625
+ var topmostSection = section;
3626
+ var bottommostSection = section;
3627
+ var lastFooterAttributes, lastFooterAttributesSection;
3628
+
3629
+ // If there are no header attributes but this layout pins headers to content edges, add them now
3630
+ if (cache.pinsHeadersToContentEdge && rowIndex != -1) {
3631
+ var headerAttributes = section.headerAttributes.copy();
3632
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
3633
+ headerAttributes.frame.origin.y = this.collectionView.scrollOffset.y;
3634
+ }
3635
+ else {
3636
+ headerAttributes.frame.origin.x = this.collectionView.scrollOffset.x;
3637
+ }
3638
+ attributes.push(headerAttributes);
3639
+ }
3640
+
3641
+ // Start adding attributes until the current supplementary view or row's bottom edge is below the rect
3642
+ try {
3643
+ for (; sectionIndex < sectionCount; sectionIndex++) {
3644
+ var section = cache.sections[sectionIndex];
3645
+ // This can happen if the last added element is at the end of the section and the first unresolved element
3646
+ // is in the next section
3647
+ if (!section && this._expectedCellSize) return attributes;
3648
+ var rows = section.rows;
3649
+ var rowsLength = rows.length;
3650
+
3651
+ for (; rowIndex <= rowsLength; rowIndex++) {
3652
+
3653
+ if (rowIndex == -1) {
3654
+ // Headers
3655
+ if (section.headerAttributes) {
3656
+ var headerAttributes = section.headerAttributes;
3657
+
3658
+ // Pin the headers to the content edge if needed
3659
+ if (cache.pinsHeadersToContentEdge && section === topmostSection) {
3660
+ headerAttributes = headerAttributes.copy();
3661
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
3662
+ headerAttributes.frame.origin.y = Math.max(this.collectionView.scrollOffset.y, headerAttributes.frame.origin.y);
3663
+ }
3664
+ else {
3665
+ headerAttributes.frame.origin.x = Math.max(this.collectionView.scrollOffset.x, headerAttributes.frame.origin.x);
3666
+ }
3667
+ }
3668
+
3669
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
3670
+ var origin = headerAttributes.frame.origin.y;
3671
+ if (origin > rectBottom) return attributes;
3672
+ }
3673
+ else {
3674
+ var origin = headerAttributes.frame.origin.x;
3675
+ if (origin > rectRight) return attributes;
3676
+ }
3677
+
3678
+ attributes.push(headerAttributes);
3679
+
3680
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
3681
+ var end = headerAttributes.frame.bottom;
3682
+ if (end > rectBottom) return attributes;
3683
+ }
3684
+ else {
3685
+ var end = headerAttributes.frame.right;
3686
+ if (end > rectRight) return attributes;
3687
+ }
3688
+ }
3689
+
3690
+ bottommostSection = section;
3691
+ }
3692
+ else if (rowIndex == rowsLength) {
3693
+ // Footers
3694
+ if (section.footerAttributes) {
3695
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
3696
+ var origin = section.footerAttributes.frame.origin.y;
3697
+ if (origin > rectBottom) return attributes;
3698
+ }
3699
+ else {
3700
+ var origin = section.footerAttributes.frame.origin.x;
3701
+ if (origin > rectRight) return attributes;
3702
+ }
3703
+
3704
+ var footerAttributes = section.footerAttributes;
3705
+ // When the footers are pinned, work on copies of the original attributes to not alter the cached layout
3706
+ // The actual pinning is performend in the final block
3707
+ if (this.collectionView._pinsFootersToContentEdge) {
3708
+ footerAttributes = footerAttributes.copy();
3709
+ lastFooterAttributes = footerAttributes;
3710
+ lastFooterAttributesSection = section;
3711
+ }
3712
+
3713
+ attributes.push(footerAttributes);
3714
+
3715
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
3716
+ var end = section.footerAttributes.frame.origin.y;
3717
+ if (end > rectBottom) return attributes;
3718
+ }
3719
+ else {
3720
+ var end = section.footerAttributes.frame.origin.x;
3721
+ if (end > rectRight) return attributes;
3722
+ }
3723
+ }
3724
+ }
3725
+ else {
3726
+ // Rows
3727
+ var row = rows[rowIndex];
3728
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
3729
+ var origin = row.top;
3730
+ if (origin > rectBottom) return attributes;
3731
+ }
3732
+ else {
3733
+ var origin = row.left;
3734
+ if (origin > rectRight) return attributes;
3735
+ }
3736
+
3737
+ for (var i = 0; i < row.attributes.length; i++) {
3738
+ attributes.push(row.attributes[i]);
3739
+ }
3740
+
3741
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
3742
+ var end = row.bottom;
3743
+ if (end > rectBottom) return attributes;
3744
+ }
3745
+ else {
3746
+ var end = row.right;
3747
+ if (end > rectRight) return attributes;
3748
+ }
3749
+ }
3750
+
3751
+ }
3752
+
3753
+ rowIndex = -1;
3754
+
3755
+ }
3756
+ }
3757
+ finally {
3758
+ // Defer adjusting the bottommost section's footer attributes until after all the other attributes have been processed
3759
+ if (cache.pinsFootersToContentEdge && this._showsFooters) {
3760
+ var footerAttributes;
3761
+
3762
+ if (lastFooterAttributesSection === bottommostSection) {
3763
+ footerAttributes = lastFooterAttributes;
3764
+ }
3765
+ else {
3766
+ footerAttributes = bottommostSection.footerAttributes.copy();
3767
+ attributes.push(footerAttributes);
3768
+ }
3769
+
3770
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
3771
+ footerAttributes.frame.origin.y = Math.min(footerAttributes.frame.origin.y, this.collectionView.scrollOffset.y + this.collectionView.frame.size.height - footerAttributes.frame.size.height);
3772
+ }
3773
+ else {
3774
+ footerAttributes.frame.origin.x = Math.min(footerAttributes.frame.origin.x, this.collectionView.scrollOffset.x + this.collectionView.frame.size.width - footerAttributes.frame.size.width);
3775
+ }
3776
+ }
3777
+ }
3778
+
3779
+ return attributes;
3780
+ },
3781
+
3782
+
3783
+ /**
3784
+ * Computes and returns the cell attributes for all elements in the given rect. This method should only be invoked when using fixed cell sizes.
3785
+ * @param rect <BMRect> The rect.
3786
+ * {
3787
+ * @param usingCache <Object, nullable> Defaults to this layout's current cache. The layout cache.
3788
+ * }
3789
+ * @return <[BMCollectionViewLayoutAttributes]> An array of cell attributes.
3790
+ */
3791
+ computedAttributesForElementsInRect: function (rect, options) {
3792
+ var cache = (options && options.usingCache) || this.cachedLayout;
3793
+ var attributes = [];
3794
+
3795
+ /*attributes.push = item => {
3796
+ let frame = document.createElement('div');
3797
+ frame.style.cssText = `left: ${item.frame.origin.x}px; top: ${item.frame.origin.y}px; width: ${item.frame.size.width}px; height: ${item.frame.size.height}px; outline: 1px solid red;`;
3798
+ this.collectionView._contentWrapper[0].appendChild(frame);
3799
+
3800
+ return Array.prototype.push.apply(attributes, arguments);
3801
+ }*/
3802
+
3803
+ var rectTop = rect.origin.y;
3804
+ var rectBottom = rect.bottom;
3805
+ var rectLeft = rect.origin.x;
3806
+ var rectRight = rect.right;
3807
+ var rectEnd = cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? rectBottom : rectRight;
3808
+
3809
+ var sectionIndex = 0;
3810
+ var sectionCount = cache.sections.length;
3811
+ var section;
3812
+
3813
+ // Find the section that intersects this rect's top origin
3814
+ for (; sectionIndex < sectionCount; sectionIndex++) {
3815
+ section = cache.sections[sectionIndex];
3816
+
3817
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
3818
+ if (section.bottom > rectTop) break;
3819
+ }
3820
+ else {
3821
+ if (section.right > rectLeft) break;
3822
+ }
3823
+ }
3824
+
3825
+ // If there were no sections return nothing
3826
+ if (sectionIndex == sectionCount) return attributes;
3827
+
3828
+ // Find the first row index that intersects the rect's top.
3829
+ var rowIndex = -1;
3830
+
3831
+ // The top is used as a caret that caches the previously rendered row or supplementary view's bottom position
3832
+ // and represents the top position at which the current row or supplementary view should be rendered
3833
+ var top = section.top;
3834
+ var left = section.left;
3835
+ var start = cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? top : left;
3836
+
3837
+ // These control variables keep track of the topmost and bottommost sections in case it is required to pin headers or footers to the content edges
3838
+ var topmostSection = section;
3839
+ var bottommostSection = section;
3840
+ var lastFooterAttributes, lastFooterAttributesSection;
3841
+
3842
+ let sectionStartAttribute = cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? 'top' : 'left';
3843
+ let originAttribute = cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? 'y' : 'x';
3844
+ let supplementaryViewSizeAttribute = cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? 'height' : 'width';
3845
+ let rectStart = cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? rectTop : rectLeft;
3846
+
3847
+ // Check the header first
3848
+ if (!cache.showsHeaders || section[sectionStartAttribute] + cache.headerHeight < rectStart) {
3849
+
3850
+ // If the header doesn't intersect the point, check each row
3851
+ rowIndex = 0;
3852
+
3853
+ var rowsLength = Math.ceil(section.numberOfObjects / cache.numberOfColumns) | 0;
3854
+
3855
+ start = cache.showsHeaders ? section[sectionStartAttribute] + cache.headerHeight + cache.rowSpacing : section[sectionStartAttribute];
3856
+
3857
+ // If no row advances past the rectTop, then rendering should begin at the first footer
3858
+ // In that case i will be equals to rowsLength, but the for condition will fail, which will not appropriately increase the rowIndex
3859
+ let caretIsAtRow = NO;
3860
+ for (var i = 0; i < rowsLength; i++) {
3861
+ rowIndex = i;
3862
+
3863
+ if (start + cache.rowBreadth > rectStart) {
3864
+ caretIsAtRow = YES;
3865
+ break;
3866
+ }
3867
+ else {
3868
+ start += cache.rowBreadth + cache.rowSpacing;
3869
+ }
3870
+
3871
+ }
3872
+
3873
+ if (!caretIsAtRow) {
3874
+ rowIndex = i;
3875
+ }
3876
+
3877
+ // And finally, if no row intersects the point, the footer must intersect it
3878
+ }
3879
+
3880
+ // If this layout pins headers to the content edge and the first index path is not this section's header, create its attributes
3881
+ // now and add it to the attributes array
3882
+ if (cache.pinsHeadersToContentEdge && rowIndex > -1 && cache.showsHeaders) {
3883
+ // Construct the attributes
3884
+ var headerAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
3885
+ BMCollectionViewFlowLayoutSupplementaryView.Header,
3886
+ {atIndexPath: BMIndexPathMakeWithIndexes([sectionIndex])}
3887
+ );
3888
+
3889
+ // The frame
3890
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
3891
+ headerAttributes.frame = BMRectMake(
3892
+ cache.sectionInsets.left, this.collectionView.scrollOffset.y,
3893
+ this.collectionView.frame.size.width - cache.sectionInsets.left - cache.sectionInsets.right,
3894
+ cache.headerHeight
3895
+ );
3896
+ }
3897
+ else {
3898
+ headerAttributes.frame = BMRectMake(
3899
+ this.collectionView.scrollOffset.x, cache.sectionInsets.top + cache.topPadding,
3900
+ cache.headerHeight,
3901
+ this.collectionView.frame.size.height - cache.sectionInsets.top - cache.sectionInsets.bottom - cache.topPadding - cache.bottomPadding
3902
+ );
3903
+ }
3904
+
3905
+ // Float the header above the content
3906
+ headerAttributes.style.zIndex = 1;
3907
+
3908
+ attributes.push(headerAttributes);
3909
+ }
3910
+
3911
+
3912
+ var numberOfColumns = cache.numberOfColumns;
3913
+
3914
+ // Start adding attributes until the current supplementary view or row's top origin is below the rect
3915
+ try {
3916
+ for (; sectionIndex < sectionCount; sectionIndex++) {
3917
+ var section = cache.sections[sectionIndex];
3918
+ var rowsLength = Math.ceil(section.numberOfObjects / numberOfColumns) | 0;
3919
+
3920
+ for (; rowIndex <= rowsLength; rowIndex++) {
3921
+
3922
+ if (rowIndex == -1) {
3923
+ // Headers
3924
+ if (cache.showsHeaders) {
3925
+ if (start > rectEnd) return attributes;
3926
+
3927
+ // Construct the attributes
3928
+ var headerAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
3929
+ BMCollectionViewFlowLayoutSupplementaryView.Header,
3930
+ {atIndexPath: BMIndexPathMakeWithIndexes([sectionIndex])}
3931
+ );
3932
+
3933
+ // The frame
3934
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
3935
+ headerAttributes.frame = BMRectMake(
3936
+ cache.sectionInsets.left, start,
3937
+ this.collectionView.frame.size.width - cache.sectionInsets.left - cache.sectionInsets.right,
3938
+ cache.headerHeight
3939
+ );
3940
+ }
3941
+ else {
3942
+ headerAttributes.frame = BMRectMake(
3943
+ start, cache.sectionInsets.top + cache.topPadding,
3944
+ cache.headerHeight,
3945
+ this.collectionView.frame.size.height - cache.sectionInsets.top - cache.sectionInsets.bottom - cache.topPadding - cache.bottomPadding
3946
+ );
3947
+ }
3948
+
3949
+ // Adjust the attributes for pinning if needed
3950
+ if (section === topmostSection && cache.pinsHeadersToContentEdge) {
3951
+
3952
+ // If the header's Y origin is lower than the content edge, the topmost section is an incoming section
3953
+ // and the previous section's header should be rendered as outgoing
3954
+ if (headerAttributes.frame.origin[originAttribute] > this.collectionView.scrollOffset[originAttribute]) {
3955
+ var previousSection = cache.sections[sectionIndex - 1];
3956
+ if (previousSection) {
3957
+ var previousHeaderAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
3958
+ BMCollectionViewFlowLayoutSupplementaryView.Header,
3959
+ {atIndexPath: BMIndexPathMakeWithIndexes([sectionIndex - 1])}
3960
+ );
3961
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
3962
+ previousHeaderAttributes.frame = BMRectMake(
3963
+ cache.sectionInsets.left,
3964
+ Math.min(this.collectionView.scrollOffset.y, headerAttributes.frame.origin.y - cache.headerHeight),
3965
+ this.collectionView.frame.size.width - cache.sectionInsets.left - cache.sectionInsets.right,
3966
+ cache.headerHeight
3967
+ );
3968
+ }
3969
+ else {
3970
+ previousHeaderAttributes.frame = BMRectMake(
3971
+ Math.min(this.collectionView.scrollOffset[originAttribute], headerAttributes.frame.origin[originAttribute] - cache.headerHeight),
3972
+ cache.sectionInsets.top + cache.topPadding,
3973
+ cache.headerHeight,
3974
+ this.collectionView.frame.size.height - cache.sectionInsets.top - cache.sectionInsets.bottom - cache.topPadding - cache.bottomPadding
3975
+ );
3976
+ }
3977
+ attributes.push(previousHeaderAttributes);
3978
+ }
3979
+ }
3980
+
3981
+ headerAttributes.frame.origin[originAttribute] = Math.max(headerAttributes.frame.origin[originAttribute], this.collectionView.scrollOffset[originAttribute]);
3982
+
3983
+ // Float the header above the content
3984
+ headerAttributes.style.zIndex = 1;
3985
+ }
3986
+
3987
+ // Advance the caret
3988
+ start += cache.headerHeight + cache.rowSpacing;
3989
+
3990
+ attributes.push(headerAttributes);
3991
+ }
3992
+
3993
+ bottommostSection = section;
3994
+ }
3995
+ else if (rowIndex == rowsLength) {
3996
+ // Footers
3997
+ if (cache.showsFooters) {
3998
+ if (start > rectEnd) return attributes;
3999
+
4000
+ var footerAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
4001
+ BMCollectionViewFlowLayoutSupplementaryView.Footer,
4002
+ {atIndexPath: BMIndexPathMakeWithIndexes([sectionIndex])}
4003
+ );
4004
+
4005
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
4006
+ footerAttributes.frame = BMRectMake(
4007
+ cache.sectionInsets.left, start,
4008
+ this.collectionView.frame.size.width - cache.sectionInsets.left - cache.sectionInsets.right,
4009
+ cache.footerHeight
4010
+ );
4011
+ }
4012
+ else {
4013
+ footerAttributes.frame = BMRectMake(
4014
+ start, cache.sectionInsets.top + cache.topPadding,
4015
+ cache.footerHeight,
4016
+ this.collectionView.frame.size.height - cache.sectionInsets.top - cache.sectionInsets.bottom - cache.topPadding - cache.bottomPadding
4017
+ );
4018
+ }
4019
+
4020
+ // Retain these attributes and their corresponding section in case they need to be adjusted for pinning afterwards
4021
+ lastFooterAttributes = footerAttributes;
4022
+ lastFooterAttributesSection = section;
4023
+
4024
+ // Because the section ends after the footer, the caret must move past the section insets to the next section
4025
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
4026
+ start += cache.footerHeight + cache.sectionInsets.top + cache.sectionInsets.bottom;
4027
+ }
4028
+ else {
4029
+ start += cache.footerHeight + cache.sectionInsets.left + cache.sectionInsets.right;
4030
+ }
4031
+
4032
+ attributes.push(footerAttributes);
4033
+ }
4034
+ else {
4035
+ // If there is no footer, the caret must move past the section insets to the next section
4036
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
4037
+ start += cache.sectionInsets.top + cache.sectionInsets.bottom;
4038
+ }
4039
+ else {
4040
+ start += cache.sectionInsets.left + cache.sectionInsets.right;
4041
+ }
4042
+
4043
+ if (start > rectEnd) return attributes;
4044
+ }
4045
+ }
4046
+ else {
4047
+ // Rows
4048
+ if (start > rectEnd) return attributes;
4049
+
4050
+ var startingObjectIndex = rowIndex * numberOfColumns;
4051
+ var prototypeRow;
4052
+ var numberOfColumnsInThisRow = numberOfColumns;
4053
+
4054
+ if (rowIndex == rowsLength - 1) {
4055
+ // For the last row determine the prototype row. When there is an exact match between the last row's column count and
4056
+ // other row's column count, the remainder will be 0. In that case the full number of columns should be assumed.
4057
+ numberOfColumnsInThisRow = (section.numberOfObjects % numberOfColumns) || numberOfColumns;
4058
+ prototypeRow = cache.prototypeRows[numberOfColumnsInThisRow - 1];
4059
+ }
4060
+ else {
4061
+ // All other rows use the prototype for the maximum column count
4062
+ prototypeRow = cache.prototypeRows[numberOfColumns - 1];
4063
+ }
4064
+
4065
+ // Construct the attributes for each cell in this row, using the prototype row as a starting point
4066
+ for (var i = 0; i < numberOfColumnsInThisRow; i++) {
4067
+ var objectIndex = startingObjectIndex + i;
4068
+
4069
+ var cellAttributes = BMCollectionViewLayoutAttributesMakeForCellAtIndexPath(this.collectionView.indexPathForObjectAtRow(objectIndex, {inSectionAtIndex: sectionIndex}));
4070
+
4071
+ cellAttributes.frame = prototypeRow.attributes[i].frame.copy();
4072
+ cellAttributes.frame.origin[originAttribute] = start;
4073
+
4074
+ attributes.push(cellAttributes);
4075
+
4076
+ }
4077
+
4078
+ // Advance the caret
4079
+ start += cache.rowBreadth;
4080
+
4081
+ // Add the spacing between the rows
4082
+ // If this is the last row and the layout doesn't use footers, don't add the spacing as this row is the bottom edge of the section
4083
+ if (cache.showsFooters || rowIndex < rowsLength - 1) {
4084
+ start += cache.rowSpacing;
4085
+ }
4086
+
4087
+ }
4088
+
4089
+ }
4090
+
4091
+ rowIndex = -1;
4092
+
4093
+ }
4094
+ }
4095
+ finally {
4096
+ // Defer adjusting the footer attributes until after all other attributes have been processed
4097
+ if (cache.pinsFootersToContentEdge && cache.showsFooters && cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
4098
+ var footerAttributes;
4099
+
4100
+ if (lastFooterAttributesSection === bottommostSection) {
4101
+ footerAttributes = lastFooterAttributes;
4102
+
4103
+ // If the footer's Y origin is higher than the content edge, the bottommost section is an outgoing section
4104
+ // and the next section's footer should be rendered as incoming
4105
+ if (footerAttributes.frame.origin[originAttribute] < this.collectionView.scrollOffset[originAttribute] + this.collectionView.frame.size[supplementaryViewSizeAttribute] - cache.footerHeight) {
4106
+ var nextSection = cache.sections[footerAttributes.indexPath.section + 1];
4107
+ if (nextSection) {
4108
+ var nextFooterAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
4109
+ BMCollectionViewFlowLayoutSupplementaryView.Footer,
4110
+ {atIndexPath: BMIndexPathMakeWithIndexes([footerAttributes.indexPath.section + 1])}
4111
+ );
4112
+
4113
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
4114
+ nextFooterAttributes.frame = BMRectMake(
4115
+ cache.sectionInsets.left,
4116
+ Math.max(this.collectionView.scrollOffset.y + this.collectionView.frame.size.height - cache.footerHeight, footerAttributes.frame.bottom),
4117
+ this.collectionView.frame.size.width - cache.sectionInsets.left - cache.sectionInsets.right,
4118
+ cache.footerHeight
4119
+ );
4120
+ }
4121
+ else {
4122
+ nextFooterAttributes.frame = BMRectMake(
4123
+ Math.max(this.collectionView.scrollOffset.x + this.collectionView.frame.size.width - cache.footerHeight, footerAttributes.frame.right),
4124
+ cache.sectionInsets.top + cache.topPadding,
4125
+ cache.footerHeight,
4126
+ this.collectionView.frame.size.height - cache.sectionInsets.top - cache.sectionInsets.bottom - cache.topPadding - cache.bottomPadding
4127
+ );
4128
+ }
4129
+
4130
+ attributes.push(nextFooterAttributes);
4131
+ }
4132
+ }
4133
+
4134
+ footerAttributes.frame.origin[originAttribute] = Math.min(footerAttributes.frame.origin[originAttribute], this.collectionView.scrollOffset[originAttribute] + this.collectionView.frame.size[supplementaryViewSizeAttribute] - footerAttributes.frame.size[supplementaryViewSizeAttribute]);
4135
+
4136
+ // Float this footer above all other content
4137
+ footerAttributes.style.zIndex = 1;
4138
+ }
4139
+ else {
4140
+ // Construct the footer attributes if they weren't already constructed
4141
+ footerAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
4142
+ BMCollectionViewFlowLayoutSupplementaryView.Footer,
4143
+ {atIndexPath: BMIndexPathMakeWithIndexes([sectionIndex])}
4144
+ );
4145
+
4146
+ if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
4147
+ footerAttributes.frame = BMRectMake(
4148
+ cache.sectionInsets.left, this.collectionView.scrollOffset.y + this.collectionView.frame.size.height - cache.footerHeight,
4149
+ this.collectionView.frame.size.width - cache.sectionInsets.left - cache.sectionInsets.right,
4150
+ cache.footerHeight
4151
+ );
4152
+ }
4153
+ else {
4154
+ footerAttributes.frame = BMRectMake(
4155
+ this.collectionView.scrollOffset.x + this.collectionView.frame.size.width - cache.footerHeight,
4156
+ cache.sectionInsets.top + cache.topPadding,
4157
+ cache.footerHeight,
4158
+ this.collectionView.frame.size.height - cache.sectionInsets.top - cache.sectionInsets.bottom - cache.topPadding - cache.bottomPadding
4159
+ );
4160
+ }
4161
+
4162
+ // Float this footer above all other content
4163
+ footerAttributes.style.zIndex = 1;
4164
+
4165
+ attributes.push(footerAttributes);
4166
+ }
4167
+
4168
+ }
4169
+ }
4170
+
4171
+ return attributes;
4172
+ },
4173
+
4174
+ // NOTE: All initialAttributes... methods will simply return a copy of the regular attributes with an additional style added
4175
+
4176
+ // @override - BMCollectionViewLayout
4177
+ initialAttributesForPresentedCellAtIndexPath: function (indexPath) {
4178
+ // Because cached layouts reuse the attributes, they must be copied before being modified
4179
+ var attributes = this.attributesForCellAtIndexPath(indexPath).copy();
4180
+
4181
+ if (this.collectionView.delegate && this.collectionView.delegate.collectionViewInitialAttributesForPresentedCellAtIndexPath) {
4182
+ return this.collectionView.delegate.collectionViewInitialAttributesForPresentedCellAtIndexPath(this.collectionView, indexPath, {withTargetAttributes: attributes});
4183
+ }
4184
+
4185
+ attributes.frame.origin.y += this.collectionView.frame.size.height / 2;
4186
+
4187
+ attributes.style = {
4188
+ rotateZ: '15deg',
4189
+ opacity: 0,
4190
+ translateZ: 0
4191
+ };
4192
+
4193
+ return attributes;
4194
+ },
4195
+
4196
+ // @override - BMCollectionViewLayout
4197
+ initialAttributesForPresentedSupplementaryViewWithIdentifier: function (identifier, options) {
4198
+ // Because cached layouts reuse the attributes, they must be copied before being modified
4199
+ var attributes = this.attributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath}).copy();
4200
+
4201
+ if (this.collectionView.delegate && this.collectionView.delegate.collectionViewInitialAttributesForPresentedSupplementaryViewWithIdentifier) {
4202
+ return this.collectionView.delegate.collectionViewInitialAttributesForPresentedSupplementaryViewWithIdentifier(this.collectionView, identifier, {atIndexPath: options.atIndexPath, withTargetAttributes: attributes});
4203
+ }
4204
+
4205
+ attributes.frame.origin.x += attributes.frame.size.width / 2;
4206
+
4207
+ attributes.style = {
4208
+ opacity: 0,
4209
+ translateZ: 0
4210
+ };
4211
+
4212
+ return attributes;
4213
+ },
4214
+
4215
+ // @override - BMCollectionViewLayout
4216
+ initialAttributesForAppearingCellAtIndexPath: function (indexPath) {
4217
+ // Because cached layouts reuse the attributes, they must be copied before being modified
4218
+ var attributes = this.attributesForCellAtIndexPath(indexPath).copy();
4219
+
4220
+ if (this.collectionView.delegate && this.collectionView.delegate.collectionViewInitialAttributesForAppearingCellAtIndexPath) {
4221
+ return this.collectionView.delegate.collectionViewInitialAttributesForAppearingCellAtIndexPath(this.collectionView, indexPath, {withTargetAttributes: attributes});
4222
+ }
4223
+
4224
+ attributes.style = {opacity: 0, translateZ: 0, scaleX: 0.5, scaleY: 0.5};
4225
+
4226
+ return attributes;
4227
+
4228
+ },
4229
+
4230
+ // @override - BMCollectionViewLayout
4231
+ initialAttributesForAppearingSupplementaryViewWithIdentifier: function (identifier, options) {
4232
+ // Because cached layouts reuse the attributes, they must be copied before being modified
4233
+ var attributes = this.attributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath}).copy();
4234
+
4235
+ if (this.collectionView.delegate && this.collectionView.delegate.collectionViewInitialAttributesForAppearingSupplementaryViewWithIdentifier) {
4236
+ return this.collectionView.delegate.collectionViewInitialAttributesForAppearingSupplementaryViewWithIdentifier(this.collectionView, identifier, {atIndexPath: options.atIndexPath, withTargetAttributes: attributes});
4237
+ }
4238
+
4239
+ attributes.style = {opacity: 0, translateZ: 0, scaleX: 0.5, scaleY: 0.5};
4240
+
4241
+ return attributes;
4242
+
4243
+ },
4244
+
4245
+ // @override - BMCollectionViewLayout
4246
+ initialAttributesForMovingCellFromIndexPath: function (indexPath, options) {
4247
+ var attributes;
4248
+ var self = this;
4249
+
4250
+ this.collectionView.usingOldDataSet(function () {
4251
+ // When using automatic cell sizes, it is important to ensure that the sizing information has been computed
4252
+ // for the requested index path
4253
+ if (self._expectedCellSize) {
4254
+ if (self.previousLayout.resolvedIndexPath.section < indexPath.section ||
4255
+ (self.previousLayout.resolvedIndexPath.section == indexPath.section && self.previousLayout.resolvedIndexPath.row < indexPath.row)) {
4256
+ // If the layout has not been resolved up to this index path, continue computing it until it has
4257
+ self.previousLayout.iterator.next({indexPath: indexPath});
4258
+ }
4259
+ }
4260
+
4261
+ if (self._cellSize && !self._expectedCellSize) {
4262
+ attributes = self.computedAttributesForCellAtIndexPath(indexPath, {usingCache: self.previousLayout});
4263
+ }
4264
+ else {
4265
+ attributes = self.cachedAttributesForCellAtIndexPath(indexPath, {usingCache: self.previousLayout});
4266
+ }
4267
+ });
4268
+
4269
+ return attributes;
4270
+ },
4271
+
4272
+ // @override - BMCollectionViewLayout
4273
+ initialAttributesForMovingSupplementaryViewWithIdentifier: function (identifier, options) {
4274
+ var attributes;
4275
+ var self = this;
4276
+
4277
+ this.collectionView.usingOldDataSet(function () {
4278
+ if (identifier === BMCollectionViewTableLayoutSupplementaryView.Empty) {
4279
+ attributes = self.attributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath});
4280
+ }
4281
+ else if (self._cellSize && !self._expectedCellSize) {
4282
+ attributes = self.computedAttributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath, usingCache: self.previousLayout});
4283
+ }
4284
+ else {
4285
+ attributes = self.cachedAttributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath, usingCache: self.previousLayout});
4286
+ }
4287
+ });
4288
+
4289
+ return attributes;
4290
+ },
4291
+
4292
+ // @override - BMCollectionViewLayout
4293
+ supplementaryViewsToInsert: function () {
4294
+ var count = this.collectionView.numberOfSections();
4295
+ var oldCount;
4296
+ var self = this;
4297
+
4298
+ this.collectionView.usingOldDataSet(function () {
4299
+ oldCount = self.collectionView.numberOfSections();
4300
+ });
4301
+
4302
+ var views = [];
4303
+
4304
+ for (var i = oldCount; i < count; i++) {
4305
+ if (this.showsHeaders) {
4306
+ views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
4307
+ BMCollectionViewFlowLayoutSupplementaryView.Header,
4308
+ {atIndexPath: BMIndexPathMakeWithIndexes([i])}
4309
+ ));
4310
+ }
4311
+
4312
+ if (this.showsFooters) {
4313
+ views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
4314
+ BMCollectionViewFlowLayoutSupplementaryView.Footer,
4315
+ {atIndexPath: BMIndexPathMakeWithIndexes([i])}
4316
+ ));
4317
+ }
4318
+ }
4319
+
4320
+ if (oldCount > 0 && count == 0) {
4321
+ views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
4322
+ BMCollectionViewTableLayoutSupplementaryView.Empty,
4323
+ {atIndexPath: BMIndexPathMakeWithIndexes([0])}
4324
+ ));
4325
+ }
4326
+
4327
+ return views;
4328
+ },
4329
+
4330
+ // @override - BMCollectionViewLayout
4331
+ supplementaryViewsToDelete: function () {
4332
+ var count = this.collectionView.numberOfSections();
4333
+ var oldCount;
4334
+ var self = this;
4335
+
4336
+ this.collectionView.usingOldDataSet(function () {
4337
+ oldCount = self.collectionView.numberOfSections();
4338
+ });
4339
+
4340
+ var views = [];
4341
+
4342
+ for (var i = count; i < oldCount; i++) {
4343
+ if (this.showsHeaders) {
4344
+ views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
4345
+ BMCollectionViewFlowLayoutSupplementaryView.Header,
4346
+ {atIndexPath: BMIndexPathMakeWithIndexes([i])}
4347
+ ));
4348
+ }
4349
+
4350
+ if (this.showsFooters) {
4351
+ views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
4352
+ BMCollectionViewFlowLayoutSupplementaryView.Footer,
4353
+ {atIndexPath: BMIndexPathMakeWithIndexes([i])}
4354
+ ));
4355
+ }
4356
+ }
4357
+
4358
+ if (oldCount == 0 && count > 0) {
4359
+ views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
4360
+ BMCollectionViewFlowLayoutSupplementaryView.Empty,
4361
+ {atIndexPath: BMIndexPathMakeWithIndexes([0])}
4362
+ ));
4363
+ }
4364
+
4365
+ return views;
4366
+ },
4367
+
4368
+ // NOTE: All finalAttributes... methods will simply return a copy of the regular attributes from the previous layout with an additional style added
4369
+
4370
+ // @override - BMCollectionViewLayout
4371
+ finalAttributesForDisappearingCellAtIndexPath: function (indexPath) {
4372
+ var attributes;
4373
+ var self = this;
4374
+
4375
+ this.collectionView.usingOldDataSet(function () {
4376
+ if (self._cellSize && !self._expectedCellSize) {
4377
+ attributes = self.computedAttributesForCellAtIndexPath(indexPath, {usingCache: self.previousLayout});
4378
+ }
4379
+ else {
4380
+ attributes = self.cachedAttributesForCellAtIndexPath(indexPath, {usingCache: self.previousLayout}).copy();
4381
+ }
4382
+
4383
+
4384
+ if (self.collectionView.delegate && self.collectionView.delegate.collectionViewFinalAttributesForDisappearingCellAtIndexPath) {
4385
+ attributes = self.collectionView.delegate.collectionViewFinalAttributesForDisappearingCellAtIndexPath(self.collectionView, indexPath, {withTargetAttributes: attributes});
4386
+ }
4387
+ else {
4388
+ attributes.style = {opacity: 0, translateZ: 0, scaleX: 0.5, scaleY: 0.5};
4389
+ }
4390
+
4391
+ });
4392
+
4393
+ return attributes;
4394
+ },
4395
+
4396
+ // @override - BMCollectionViewLayout
4397
+ finalAttributesForDisappearingSupplementaryViewWithIdentifier: function (identifier, options) {
4398
+ var attributes;
4399
+ var self = this;
4400
+
4401
+ this.collectionView.usingOldDataSet(function () {
4402
+ if (identifier === BMCollectionViewTableLayoutSupplementaryView.Empty) {
4403
+ attributes = self.attributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath});
4404
+ }
4405
+ else if (self._cellSize && !self._expectedCellSize) {
4406
+ attributes = self.computedAttributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath, usingCache: self.previousLayout});
4407
+ }
4408
+ else {
4409
+ attributes = self.cachedAttributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath, usingCache: self.previousLayout});
4410
+ }
4411
+
4412
+ if (self.collectionView.delegate && self.collectionView.delegate.collectionViewFinalAttributesForDisappearingSupplementaryViewWithIdentifier) {
4413
+ attributes = self.collectionView.delegate.collectionViewFinalAttributesForDisappearingSupplementaryViewWithIdentifier(self.collectionView, identifier, {atIndexPath: options.atIndexPath, withTargetAttributes: attributes});
4414
+ }
4415
+ else {
4416
+ attributes.style = {opacity: 0, translateZ: 0, scaleX: 0.5, scaleY: 0.5};
4417
+ }
4418
+
4419
+ });
4420
+
4421
+ return attributes;
4422
+ },
4423
+
4424
+ // @override - BMCollectionViewLayout
4425
+ copy: function () {
4426
+ var copy = new BMCollectionViewFlowLayout();
4427
+
4428
+ copy.cellSize = this.cellSize && this.cellSize.copy();
4429
+ copy.rowSpacing = this.rowSpacing;
4430
+
4431
+ copy.showsHeaders = this.showsHeaders;
4432
+ copy.headerHeight = this.headerHeight;
4433
+
4434
+ copy.showsFooters = this.showsFooters;
4435
+ copy.footerHeight = this.footerHeight;
4436
+
4437
+ copy.sectionInsets = this.sectionInsets;
4438
+
4439
+ copy.gravity = this.gravity;
4440
+ copy.leftAlignFinalRow = this.leftAlignFinalRow;
4441
+ copy.alignment = this.alignment;
4442
+ copy.orientation = this.orientation;
4443
+
4444
+ copy.topPadding = this.topPadding;
4445
+ copy.bottomPadding = this.bottomPadding;
4446
+ copy.contentGravity = this.contentGravity;
4447
+
4448
+ copy.pinsHeadersToContentEdge = this.pinsHeadersToContentEdge;
4449
+ copy.pinsFootersToContentEdge = this.pinsFootersToContentEdge;
4450
+
4451
+ return copy;
4452
+ }
4453
+
4454
+
4455
+
4456
+ });
4457
+
4458
+ /**
4459
+ * Constructs and returns a new flow layout.
4460
+ * @return <BMCollectionViewFlowLayout> A flow layout.
4461
+ */
4462
+ BMCollectionViewFlowLayout.flowLayout = function () {
4463
+ return new BMCollectionViewFlowLayout();
4464
+ }
4465
+
4466
+ // @endtype
4467
+