// // MPLayout.m // MathPad // // Created by Kim Wittenburg on 11.08.14. // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // #import "MPLayout.h" #import "MPRangePath.h" #import "NSIndexPath+MPAdditions.h" @interface MPLayout () // Querying and Storing Caches - (BOOL)hasCacheForElementAtIndex:(NSUInteger)index; - (void)ensureCacheSizeForIndex:(NSUInteger)index; @end @implementation MPLayout { NSMutableArray *_cache; NSRect _cachedBounds; } #pragma mark Creation Methods - (id)init { self = [super init]; if (self) { _cache = [[NSMutableArray alloc] init]; _cachedBounds = NSZeroRect; } return self; } - (instancetype)initWithParent:(MPLayout *)parent { self = [self init]; if (self) { _parent = parent; } return self; } #pragma mark Properties - (NSFont *)font { return self.usesSmallSize ? self.smallFont : self.normalFont; } - (CGFloat)fontSize { return self.usesSmallSize ? self.smallFontSize : self.normalFontSize; } - (NSFont *)normalFont { return [NSFont fontWithName:@"CMU Serif" size:self.fontSize]; } - (CGFloat)normalFontSize { return 18.0; } - (NSFont *)smallFont { return [NSFont fontWithName:@"CMU Serif" size:self.smallFontSize]; } - (CGFloat)smallFontSize { return 12.0; } #pragma mark Cache Tree // Querying and Storing Caches - (BOOL)hasCacheForElementAtIndex:(NSUInteger)index { if (index >= _cache.count) { return NO; } return _cache[index] != MPNull; } - (id)cachableObjectForIndex:(NSUInteger)index generator:(id (^)())generator { if ([self hasCacheForElementAtIndex:index]) { return _cache[index]; } id object = generator(); [self ensureCacheSizeForIndex:index]; _cache[index] = object; return object; } - (void)ensureCacheSizeForIndex:(NSUInteger)index { while (index >= _cache.count) { [_cache addObject:MPNull]; } } // Clearing Caches - (void)clearCacheInRange:(NSRange)range replacementLength:(NSUInteger)replacementLength { NSMutableArray *placeholders = [[NSMutableArray alloc] initWithCapacity:replacementLength]; while (placeholders.count < replacementLength) { [placeholders addObject:MPNull]; } [_cache replaceObjectsInRange:range withObjectsFromArray:placeholders]; [self invalidate]; } - (void)invalidate { _cachedBounds = NSZeroRect; [self.parent invalidate]; } - (MPLayout *)childLayoutAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.length == 0) { return self; } MPLayout *child = [self childLayoutAtIndex:indexPath.firstIndex]; return [child childLayoutAtIndexPath:[indexPath indexPathByRemovingFirstIndex]]; } #pragma mark Calculation and Drawing Methods - (CTLineRef)createLineForString:(NSString *)aString { return [self createLineForString:aString usingFont:self.font]; } - (CTLineRef)createLineForString:(NSString *)aString usingFont:(NSFont *)font { NSAttributedString *text = [[NSAttributedString alloc] initWithString:aString attributes:@{NSFontAttributeName: font}]; CFAttributedStringRef attributedString = CFBridgingRetain(text); CTLineRef line = CTLineCreateWithAttributedString(attributedString); CFRelease(attributedString); // TODO: Is this release appropriate? return line; } - (NSRect)bounds { if (NSEqualRects(_cachedBounds, NSZeroRect)) { _cachedBounds = [self generateBounds]; } return _cachedBounds; } - (NSRect)boundingRectForRangePath:(MPRangePath *)rangePath { if (rangePath.location.length == 1) { return [self boundingRectForRange:rangePath.rangeAtLastIndex]; } NSUInteger nextIndex = [rangePath.location indexAtPosition:0]; NSIndexPath *newLocation = [rangePath.location indexPathByRemovingFirstIndex]; MPRangePath *newRangePath = [[MPRangePath alloc] initWithLocation:newLocation length:rangePath.length]; NSRect bounds = [[self childLayoutAtIndex:nextIndex] boundingRectForRangePath:newRangePath]; NSPoint offset = [self offsetOfChildLayoutAtIndex:nextIndex]; bounds.origin = NSMakePoint(bounds.origin.x + offset.x, bounds.origin.y + offset.y); return bounds; } - (BOOL)drawsChildrenManually { return NO; } - (void)drawAtPoint:(NSPoint)point { NSAffineTransform *transform = [NSAffineTransform transform]; [transform translateXBy:point.x yBy:point.y]; [transform concat]; [self draw]; if (!self.drawsChildrenManually) { for (NSUInteger index = 0; index < [self numberOfChildren]; index++) { MPLayout *childLayout = [self childLayoutAtIndex:index]; NSPoint offset = [self offsetOfChildLayoutAtIndex:index]; [childLayout drawAtPoint:offset]; } } [transform invert]; [transform concat]; } @end