1//
2//  PSTGridLayoutRow.m
3//  PSPDFKit
4//
5//  Copyright (c) 2012-2013 Peter Steinberger. All rights reserved.
6//
7
8#import "PSTCollectionView.h"
9#import "PSTGridLayoutRow.h"
10#import "PSTGridLayoutSection.h"
11#import "PSTGridLayoutItem.h"
12#import "PSTGridLayoutInfo.h"
13
14@interface PSTGridLayoutRow () {
15    NSMutableArray *_items;
16    BOOL _isValid;
17    int _verticalAlignement;
18    int _horizontalAlignement;
19}
20@property (nonatomic, strong) NSArray *items;
21@end
22
23@implementation PSTGridLayoutRow
24
25///////////////////////////////////////////////////////////////////////////////////////////
26#pragma mark - NSObject
27
28- (id)init {
29    if ((self = [super init])) {
30        _items = [NSMutableArray new];
31    }
32    return self;
33}
34
35- (NSString *)description {
36    return [NSString stringWithFormat:@"<%@: %p frame:%@ index:%ld items:%@>", NSStringFromClass(self.class), self, NSStringFromCGRect(self.rowFrame), (long)self.index, self.items];
37}
38
39///////////////////////////////////////////////////////////////////////////////////////////
40#pragma mark - Public
41
42- (void)invalidate {
43    _isValid = NO;
44    _rowSize = CGSizeZero;
45    _rowFrame = CGRectZero;
46}
47
48- (NSArray *)itemRects {
49    return [self layoutRowAndGenerateRectArray:YES];
50}
51
52- (void)layoutRow {
53    [self layoutRowAndGenerateRectArray:NO];
54}
55
56- (NSArray *)layoutRowAndGenerateRectArray:(BOOL)generateRectArray {
57    NSMutableArray *rects = generateRectArray ? [NSMutableArray array] : nil;
58    if (!_isValid || generateRectArray) {
59        // properties for aligning
60        BOOL isHorizontal = self.section.layoutInfo.horizontal;
61        BOOL isLastRow = self.section.indexOfImcompleteRow == self.index;
62        PSTFlowLayoutHorizontalAlignment horizontalAlignment = [self.section.rowAlignmentOptions[isLastRow ? PSTFlowLayoutLastRowHorizontalAlignmentKey : PSTFlowLayoutCommonRowHorizontalAlignmentKey] integerValue];
63
64        // calculate space that's left over if we would align it from left to right.
65        CGFloat leftOverSpace = self.section.layoutInfo.dimension;
66        if (isHorizontal) {
67            leftOverSpace -= self.section.sectionMargins.top + self.section.sectionMargins.bottom;
68        }else {
69            leftOverSpace -= self.section.sectionMargins.left + self.section.sectionMargins.right;
70        }
71
72        // calculate the space that we have left after counting all items.
73        // UICollectionView is smart and lays out items like they would have been placed on a full row
74        // So we need to calculate the "usedItemCount" with using the last item as a reference size.
75        // This allows us to correctly justify-place the items in the grid.
76        NSUInteger usedItemCount = 0;
77        NSInteger itemIndex = 0;
78        CGFloat spacing = isHorizontal ? self.section.verticalInterstice : self.section.horizontalInterstice;
79        // the last row should justify as if it is filled with more (invisible) items so that the whole
80        // UICollectionView feels more like a grid than a random line of blocks
81        while (itemIndex < self.itemCount || isLastRow) {
82            CGFloat nextItemSize;
83            // first we need to find the size (width/height) of the next item to fit
84            if (!self.fixedItemSize) {
85                PSTGridLayoutItem *item = self.items[MIN(itemIndex, self.itemCount - 1)];
86                nextItemSize = isHorizontal ? item.itemFrame.size.height : item.itemFrame.size.width;
87            }else {
88                nextItemSize = isHorizontal ? self.section.itemSize.height : self.section.itemSize.width;
89            }
90
91            // the first item does not add a separator spacing,
92            // every one afterwards in the same row will need this spacing constant
93            if (itemIndex > 0) {
94                nextItemSize += spacing;
95            }
96
97            // check to see if we can at least fit an item (+separator if necessary)
98            if (leftOverSpace < nextItemSize) {
99                break;
100            }
101
102            // we need to maintain the leftover space after the maximum amount of items have
103            // occupied, so we know how to adjust equal spacing among all the items in a row
104            leftOverSpace -= nextItemSize;
105
106            itemIndex++;
107            usedItemCount = itemIndex;
108        }
109
110        // push everything to the right if right-aligning and divide in half for centered
111        // currently there is no public API supporting this behavior
112        CGPoint itemOffset = CGPointZero;
113        if (horizontalAlignment == PSTFlowLayoutHorizontalAlignmentRight) {
114            itemOffset.x += leftOverSpace;
115        }else if (horizontalAlignment == PSTFlowLayoutHorizontalAlignmentCentered ||
116                (horizontalAlignment == PSTFlowLayoutHorizontalAlignmentJustify && usedItemCount == 1)) {
117            // Special case one item row to split leftover space in half
118            itemOffset.x += leftOverSpace / 2;
119        }
120
121        // calculate the justified spacing among all items in a row if we are using
122        // the default PSTFlowLayoutHorizontalAlignmentJustify layout
123        CGFloat interSpacing = usedItemCount <= 1 ? 0 : leftOverSpace / (CGFloat)(usedItemCount - 1);
124
125        // calculate row frame as union of all items
126        CGRect frame = CGRectZero;
127        CGRect itemFrame = (CGRect){.size=self.section.itemSize};
128        for (itemIndex = 0; itemIndex < self.itemCount; itemIndex++) {
129            PSTGridLayoutItem *item = nil;
130            if (!self.fixedItemSize) {
131                item = self.items[itemIndex];
132                itemFrame = [item itemFrame];
133            }
134            // depending on horizontal/vertical for an item size (height/width),
135            // we add the minimum separator then an equally distributed spacing
136            // (since our default mode is justify) calculated from the total leftover
137            // space divided by the number of intervals
138            if (isHorizontal) {
139                itemFrame.origin.y = itemOffset.y;
140                itemOffset.y += itemFrame.size.height + self.section.verticalInterstice;
141                if (horizontalAlignment == PSTFlowLayoutHorizontalAlignmentJustify) {
142                    itemOffset.y += interSpacing;
143                }
144            }else {
145                itemFrame.origin.x = itemOffset.x;
146                itemOffset.x += itemFrame.size.width + self.section.horizontalInterstice;
147                if (horizontalAlignment == PSTFlowLayoutHorizontalAlignmentJustify) {
148                    itemOffset.x += interSpacing;
149                }
150            }
151            item.itemFrame = itemFrame; // might call nil; don't care
152            [rects addObject:[NSValue valueWithCGRect:itemFrame]];
153            frame = CGRectUnion(frame, itemFrame);
154        }
155        _rowSize = frame.size;
156        //        _rowFrame = frame; // set externally
157        _isValid = YES;
158    }
159    return rects;
160}
161
162- (void)addItem:(PSTGridLayoutItem *)item {
163    [_items addObject:item];
164    item.rowObject = self;
165    [self invalidate];
166}
167
168- (PSTGridLayoutRow *)snapshot {
169    PSTGridLayoutRow *snapshotRow = [self.class new];
170    snapshotRow.section = self.section;
171    snapshotRow.items = self.items;
172    snapshotRow.rowSize = self.rowSize;
173    snapshotRow.rowFrame = self.rowFrame;
174    snapshotRow.index = self.index;
175    snapshotRow.complete = self.complete;
176    snapshotRow.fixedItemSize = self.fixedItemSize;
177    snapshotRow.itemCount = self.itemCount;
178    return snapshotRow;
179}
180
181- (PSTGridLayoutRow *)copyFromSection:(PSTGridLayoutSection *)section {
182    return nil; // ???
183}
184
185- (NSInteger)itemCount {
186    if (self.fixedItemSize) {
187        return _itemCount;
188    }else {
189        return self.items.count;
190    }
191}
192
193@end
194