From 31169253158df0c4889f7d6507d8d7a7ac6a43a6 Mon Sep 17 00:00:00 2001 From: Kim Wittenburg Date: Wed, 23 Apr 2014 03:13:55 +0200 Subject: [PATCH] Added MPExpressionLayout, MPFunctionLayout and MPExpressionStorage as Rendering System for Expressions --- MathPad/MPExpressionLayout.h | 63 ++++++++++ MathPad/MPExpressionLayout.m | 209 ++++++++++++++++++++++++++++++++++ MathPad/MPExpressionStorage.h | 21 ++++ MathPad/MPExpressionStorage.m | 60 ++++++++++ MathPad/MPExpressionView.h | 17 ++- MathPad/MPExpressionView.m | 56 ++++++++- MathPad/MPFunctionLayout.h | 56 +++++++++ MathPad/MPFunctionLayout.m | 121 ++++++++++++++++++++ 8 files changed, 597 insertions(+), 6 deletions(-) create mode 100644 MathPad/MPExpressionLayout.h create mode 100644 MathPad/MPExpressionLayout.m create mode 100644 MathPad/MPExpressionStorage.h create mode 100644 MathPad/MPExpressionStorage.m create mode 100644 MathPad/MPFunctionLayout.h create mode 100644 MathPad/MPFunctionLayout.m diff --git a/MathPad/MPExpressionLayout.h b/MathPad/MPExpressionLayout.h new file mode 100644 index 0000000..4fec051 --- /dev/null +++ b/MathPad/MPExpressionLayout.h @@ -0,0 +1,63 @@ +// +// MPExpressionLayout.h +// MathPad +// +// Created by Kim Wittenburg on 22.04.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import + +@class MPExpressionLayout, MPFunctionLayout, MPExpressionStorage, MPExpression; + +@interface MPExpressionLayout : NSObject { + BOOL _valid; + NSSize _cachedSize; + NSMutableArray *_symbolCache; +} + +#pragma mark Creation Methods + +// -init not supported +- (id)initRootLayoutWithExpressionStorage:(MPExpressionStorage *)expressionStorage; +- (id)initWithExpressionPath:(NSIndexPath *)expressionPath + parent:(MPFunctionLayout *)parent; + +#pragma mark Properties + +@property (readonly, nonatomic, weak) MPFunctionLayout *parent; + +@property (readonly, nonatomic, weak) MPExpressionStorage *expressionStorage; +@property (readonly, nonatomic, strong) NSIndexPath *expressionPath; + +- (MPExpression *)expression; // Convenience +- (NSLayoutManager *)layoutManager; +- (NSTextContainer *)textContainer; +- (NSTextStorage *)textStorage; + +#pragma mark Cache Methods + +- (void)invalidate; +- (void)expressionEditedInRange:(NSRange)range + replacementLength:(NSUInteger)length; + +- (BOOL)hasCacheForSymbolAtIndex:(NSUInteger)index; +- (MPFunctionLayout *)functionLayoutForFunctionAtIndex:(NSUInteger)index; +- (NSSize)cachedSizeForSymbolAtIndex:(NSUInteger)index; +- (void)cacheSize:(NSSize)size forSymbolAtIndex:(NSUInteger)index; + +#pragma mark Sizes Calculation Methods + +- (NSSize)sizeForAllSymbols; +- (NSSize)sizeForSymbolAtIndex:(NSUInteger)index; +- (NSSize)sizeForSymbolsInRange:(NSRange)range; + +#pragma mark Drawing Methods + +- (void)drawSymbolAtIndex:(NSUInteger)index + atPoint:(NSPoint)point; +- (void)drawSymbolsInRange:(NSRange)range + atPoint:(NSPoint)point; +- (void)drawAllSymbolsAtPoint:(NSPoint)point; + +@end diff --git a/MathPad/MPExpressionLayout.m b/MathPad/MPExpressionLayout.m new file mode 100644 index 0000000..387fa35 --- /dev/null +++ b/MathPad/MPExpressionLayout.m @@ -0,0 +1,209 @@ +// +// MPExpressionLayout.m +// MathPad +// +// Created by Kim Wittenburg on 22.04.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPExpressionLayout.h" +#import "MPExpressionStorage.h" +#import "MPFunctionLayout.h" +#import "MPModel.h" + +@implementation MPExpressionLayout + +#pragma mark Creation Methods + +- (instancetype)initRootLayoutWithExpressionStorage:(MPExpressionStorage *)expressionStorage +{ + self = [super init]; + if (self) { + _symbolCache = [[NSMutableArray alloc] init]; + _expressionStorage = expressionStorage; + _expressionPath = [[NSIndexPath alloc] init]; + } + return self; +} + +- (instancetype)initWithExpressionPath:(NSIndexPath *)expressionPath + parent:(MPFunctionLayout *)parent +{ + self = [super init]; + if (self) { + _symbolCache = [[NSMutableArray alloc] init]; + _expressionPath = expressionPath; + _parent = parent; + } + return self; +} + +#pragma mark Properties + +@synthesize expressionStorage = _expressionStorage; +- (MPExpressionStorage *)expressionStorage +{ + if (_expressionStorage) { + return _expressionStorage; + } + return self.parent.expressionStorage; +} + +- (MPExpression *)expression +{ + return [self.expressionStorage symbolAtIndexPath:self.expressionPath]; +} + +- (NSLayoutManager *)layoutManager +{ + return self.expressionStorage.layoutManager; +} + +- (NSTextContainer *)textContainer +{ + return self.expressionStorage.textContainer; +} + +- (NSTextStorage *)textStorage +{ + return self.expressionStorage.textStorage; +} + +#pragma mark Cache Methods + +- (void)invalidate +{ + _valid = NO; +} + +- (void)expressionEditedInRange:(NSRange)range replacementLength:(NSUInteger)length +{ + while (_symbolCache.count < self.expression.numberOfSymbols) { + [_symbolCache addObject:[NSNull null]]; + } + NSMutableArray *newPlaceholders = [[NSMutableArray alloc] initWithCapacity:length]; + while (newPlaceholders.count < length) { + [newPlaceholders addObject:[NSNull null]]; + } + [_symbolCache replaceObjectsInRange:range withObjectsFromArray:newPlaceholders]; + _valid = NO; +} + +- (BOOL)hasCacheForSymbolAtIndex:(NSUInteger)index +{ + if (index >= _symbolCache.count) { + return NO; + } + return _symbolCache[index] != [NSNull null]; +} + +- (MPFunctionLayout *)functionLayoutForFunctionAtIndex:(NSUInteger)index; +{ + if ([self hasCacheForSymbolAtIndex:index]) { + return _symbolCache[index]; + } + MPFunctionLayout *layout = [MPFunctionLayout functionLayoutForFunctionAtIndexPath:[self.expressionPath indexPathByAddingIndex:index] parent:self]; + while (index >= _symbolCache.count) { + [_symbolCache addObject:[NSNull null]]; + } + _symbolCache[index] = layout; + return layout; +} + +- (NSSize)cachedSizeForSymbolAtIndex:(NSUInteger)index +{ + id cachedSymbol = _symbolCache[index]; + if ([cachedSymbol isKindOfClass:[NSValue class]]) { + return [cachedSymbol sizeValue]; + } + return [(MPFunctionLayout *)cachedSymbol sizeOfFunction]; +} + +- (void)cacheSize:(NSSize)size forSymbolAtIndex:(NSUInteger)index +{ + while (index >= _symbolCache.count) { + [_symbolCache addObject:[NSNull null]]; + } + _symbolCache[index] = [NSValue valueWithSize:size]; +} + +#pragma mark Size Calculation Methods + +- (NSSize)sizeForAllSymbols +{ + if (!_valid) { + _cachedSize = [self sizeForSymbolsInRange:NSMakeRange(0, self.expression.numberOfSymbols)]; + _valid = YES; + } + return _cachedSize; +} + +- (NSSize)sizeForSymbolsInRange:(NSRange)range +{ + NSSize size = NSMakeSize(0, 0); + for (NSUInteger index = range.location; index < NSMaxRange(range); index++) { + NSSize symbolSize = [self sizeForSymbolAtIndex:index]; + size.width += symbolSize.width; + size.height = MAX(size.height, symbolSize.height); + } + return size; +} + +- (NSSize)sizeForSymbolAtIndex:(NSUInteger)index +{ + if ([self hasCacheForSymbolAtIndex:index]) { + return [self cachedSizeForSymbolAtIndex:index]; + } + id symbol = [self.expression symbolAtIndex:index]; + if ([symbol isString]) { + [self.textStorage setString:symbol]; + NSRange glyphRange = [self.layoutManager glyphRangeForTextContainer:self.textContainer]; + NSSize symbolSize = [self.layoutManager boundingRectForGlyphRange:glyphRange + inTextContainer:self.textContainer].size; + [self cacheSize:symbolSize + forSymbolAtIndex:index]; + + return symbolSize; + } else { + MPFunctionLayout *layout = [self functionLayoutForFunctionAtIndex:index]; + return [layout sizeOfFunction]; + } +} + +#pragma mark Drawing Methods + +- (void)drawSymbolAtIndex:(NSUInteger)index + atPoint:(NSPoint)point +{ + id symbol = [self.expression symbolAtIndex:index]; + if ([symbol isString]) { + [self.textStorage setString:symbol]; + NSRange glyphRange = [self.layoutManager glyphRangeForTextContainer:self.textContainer]; + [self.layoutManager drawGlyphsForGlyphRange:glyphRange + atPoint:point]; + } else { + MPFunctionLayout *layout = [self functionLayoutForFunctionAtIndex:index]; + [layout drawFunctionAtPoint:point]; + } +} + +- (void)drawSymbolsInRange:(NSRange)range + atPoint:(NSPoint)point +{ + NSSize overallSize = [self sizeForSymbolsInRange:range]; + CGFloat x = point.x; + for (NSUInteger index = range.location; index < NSMaxRange(range); index++) { + NSSize symbolSize = [self sizeForSymbolAtIndex:index]; + CGFloat dy = (overallSize.height - symbolSize.height) / 2; + [self drawSymbolAtIndex:index + atPoint:NSMakePoint(x, point.y + dy)]; + x += symbolSize.width; + } +} + +- (void)drawAllSymbolsAtPoint:(NSPoint)point +{ + [self drawSymbolsInRange:NSMakeRange(0, [self.expression numberOfSymbols]) atPoint:point]; +} + +@end diff --git a/MathPad/MPExpressionStorage.h b/MathPad/MPExpressionStorage.h new file mode 100644 index 0000000..9042766 --- /dev/null +++ b/MathPad/MPExpressionStorage.h @@ -0,0 +1,21 @@ +// +// MPExpressionStorage.h +// MathPad +// +// Created by Kim Wittenburg on 22.04.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPExpression.h" + +@class MPExpressionStorage, MPExpressionLayout; + +@interface MPExpressionStorage : MPMutableExpression + +@property (nonatomic, strong) MPExpressionLayout *expressionLayout; + +- (NSLayoutManager *)layoutManager; +- (NSTextContainer *)textContainer; +- (NSTextStorage *)textStorage; + +@end diff --git a/MathPad/MPExpressionStorage.m b/MathPad/MPExpressionStorage.m new file mode 100644 index 0000000..7254755 --- /dev/null +++ b/MathPad/MPExpressionStorage.m @@ -0,0 +1,60 @@ +// +// MPExpressionStorage.m +// MathPad +// +// Created by Kim Wittenburg on 22.04.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPExpressionStorage.h" +#import "MPExpressionLayout.h" + +@interface MPExpressionStorage () +@property (nonatomic, strong) NSLayoutManager *layoutManager; +@property (nonatomic, strong) NSTextContainer *textContainer; +@property (nonatomic, strong) NSTextStorage *textStorage; +@end + +@implementation MPExpressionStorage + +- (instancetype)initWithSymbols:(NSArray *)symbols +{ + self = [super initWithSymbols:symbols]; + if (self) { + _expressionLayout = [[MPExpressionLayout alloc] initRootLayoutWithExpressionStorage:self]; + } + return self; +} + +- (NSLayoutManager *)layoutManager +{ + [self ensureTextSystemObjects]; + return _layoutManager; +} + +- (NSTextContainer *)textContainer +{ + [self ensureTextSystemObjects]; + return _textContainer; +} + +- (NSTextStorage *)textStorage +{ + [self ensureTextSystemObjects]; + return _textStorage; +} + +- (void)ensureTextSystemObjects +{ + if (_layoutManager == nil || _textContainer == nil || _textStorage == nil) { + _textStorage = [[NSTextStorage alloc] init]; + NSTextContainer *textContainer = [[NSTextContainer alloc] initWithContainerSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX)]; + NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; + [layoutManager addTextContainer:textContainer]; + [_textStorage addLayoutManager:layoutManager]; + _textContainer = textContainer; + _layoutManager = layoutManager; + } +} + +@end diff --git a/MathPad/MPExpressionView.h b/MathPad/MPExpressionView.h index 364de6d..b1c1c78 100644 --- a/MathPad/MPExpressionView.h +++ b/MathPad/MPExpressionView.h @@ -6,13 +6,24 @@ // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // +// TODO: Undo/Redo + Delegate + #import -@class MPExpressionView, MPRange; +@class MPExpressionView, MPExpressionStorage, MPExpressionLayout, MPRangePath; -@interface MPExpressionView : NSView +@interface MPExpressionView : NSView + +#pragma mark Creation Methods + +- (id)initWithFrame:(NSRect)frameRect; + +#pragma mark Properties + +@property (readonly, nonatomic, copy) MPExpressionStorage *expressionStorage; +- (MPExpressionLayout *)expressionLayout; // Convenience Method @property (nonatomic, getter = isEditable) BOOL editable; -@property (nonatomic, strong) MPRange *selection; +@property (nonatomic, strong) MPRangePath *selection; @end diff --git a/MathPad/MPExpressionView.m b/MathPad/MPExpressionView.m index 4e709e7..0897b64 100644 --- a/MathPad/MPExpressionView.m +++ b/MathPad/MPExpressionView.m @@ -7,23 +7,73 @@ // #import "MPExpressionView.h" +#import "MPExpressionLayout.h" +#import "MPExpressionStorage.h" + +#import "NSObject+MPStringTest.h" + +#import "MPSumFunction.h" + +@interface MPExpressionView () +@end @implementation MPExpressionView +#pragma mark Creation Methods + +- (instancetype)init +{ + self = [super init]; + if (self) { + [self initializeObjects]; + } + return self; +} + - (id)initWithFrame:(NSRect)frame { self = [super initWithFrame:frame]; if (self) { - // Initialization code here. + [self initializeObjects]; } return self; } +- (id)initWithCoder:(NSCoder *)aDecoder +{ + self = [super initWithCoder:aDecoder]; + if (self) { + [self initializeObjects]; + } + return self; +} + +- (void)initializeObjects +{ + MPExpressionStorage *expressionStorage = [[MPExpressionStorage alloc] initWithSymbols:@[@"12345", [[MPSumFunction alloc] init]]]; + _expressionStorage = expressionStorage; +} + +#pragma mark Properties + +- (MPExpressionLayout *)expressionLayout +{ + return self.expressionStorage.expressionLayout; +} + +#pragma mark Drawing Methods + - (void)drawRect:(NSRect)dirtyRect { [super drawRect:dirtyRect]; - - // Drawing code here. + [[NSColor whiteColor] set]; + NSRectFill(self.bounds); + [[NSColor blackColor] set]; + NSSize expressionSize = [self.expressionLayout sizeForAllSymbols]; + CGFloat y = (self.bounds.size.height - expressionSize.height) / 2; + NSLog(@"%f", self.bounds.origin.y); + NSPoint point = NSMakePoint(self.bounds.origin.x, self.bounds.origin.y + y); + [self.expressionLayout drawAllSymbolsAtPoint:point]; } @end diff --git a/MathPad/MPFunctionLayout.h b/MathPad/MPFunctionLayout.h new file mode 100644 index 0000000..0de16a3 --- /dev/null +++ b/MathPad/MPFunctionLayout.h @@ -0,0 +1,56 @@ +// +// MPFunctionLayout.h +// MathPad +// +// Created by Kim Wittenburg on 22.04.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import + +@class MPFunctionLayout, MPExpressionLayout, MPExpressionStorage, MPFunction; + +@interface MPFunctionLayout : NSObject { + @protected + BOOL _valid; + NSSize _cachedSize; + NSMutableArray *_childCache; +} + +#pragma mark Creation Methods + ++ (instancetype)functionLayoutForFunctionAtIndexPath:(NSIndexPath *)functionPath + parent:(MPExpressionLayout *)parent; + +- (id)initWithFunctionPath:(NSIndexPath *)functionPath + parent:(MPExpressionLayout *)parent; + +#pragma mark Properties + +@property (readonly, nonatomic, weak) MPExpressionLayout *parent; + +@property (readonly, nonatomic, strong) NSIndexPath *functionPath; + +- (MPExpressionStorage *)expressionStorage; +- (MPFunction *)function; // Convenience +- (NSLayoutManager *)layoutManager; +- (NSTextContainer *)textContainer; +- (NSTextStorage *)textStorage; + +#pragma mark Cache Methods + +- (void)invalidate; + +- (BOOL)hasCacheForChildAtIndex:(NSUInteger)index; +- (MPExpressionLayout *)expressionLayoutForChildAtIndex:(NSUInteger)index; + +#pragma mark Size Calculation Methods + +- (NSSize)sizeOfFunction; +- (NSSize)calculateSize; + +#pragma mark Drawing Methods + +- (void)drawFunctionAtPoint:(NSPoint)point; + +@end diff --git a/MathPad/MPFunctionLayout.m b/MathPad/MPFunctionLayout.m new file mode 100644 index 0000000..6b98f49 --- /dev/null +++ b/MathPad/MPFunctionLayout.m @@ -0,0 +1,121 @@ +// +// MPFunctionLayout.m +// MathPad +// +// Created by Kim Wittenburg on 22.04.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPFunctionLayout.h" +#import "MPExpressionLayout.h" +#import "MPExpressionStorage.h" +#import "MPFunction.h" + +#import "MPSumFunction.h" +#import "MPSumFunctionLayout.h" + +@implementation MPFunctionLayout + +#pragma mark Creation Methods + ++ (instancetype)functionLayoutForFunctionAtIndexPath:(NSIndexPath *)functionPath + parent:(MPExpressionLayout *)parent +{ + MPFunction *function = [parent.expressionStorage symbolAtIndexPath:functionPath]; + Class class = [function class]; + if (class == [MPSumFunction class]) { + return [[MPSumFunctionLayout alloc] initWithFunctionPath:functionPath parent:parent]; + } + return nil; +} + +- (id)initWithFunctionPath:(NSIndexPath *)functionPath + parent:(MPExpressionLayout *)parent +{ + self = [super init]; + if (self) { + _functionPath = functionPath; + _parent = parent; + _childCache = [[NSMutableArray alloc] init]; + } + return self; +} + +#pragma mark Properties + +- (MPExpressionStorage *)expressionStorage +{ + return self.parent.expressionStorage; +} + +- (MPFunction *)function +{ + return [self.expressionStorage symbolAtIndexPath:self.functionPath]; +} + +- (NSLayoutManager *)layoutManager +{ + return self.expressionStorage.layoutManager; +} + +- (NSTextContainer *)textContainer +{ + return self.expressionStorage.textContainer; +} + +- (NSTextStorage *)textStorage +{ + return self.expressionStorage.textStorage; +} + +#pragma mark Cache Methods + +- (void)invalidate +{ + _valid = NO; +} + +- (BOOL)hasCacheForChildAtIndex:(NSUInteger)index +{ + if (index >= _childCache.count) { + return NO; + } + return _childCache[index] != [NSNull null]; +} + +- (MPExpressionLayout *)expressionLayoutForChildAtIndex:(NSUInteger)index +{ + if ([self hasCacheForChildAtIndex:index]) { + return _childCache[index]; + } + while (index >= _childCache.count) { + [_childCache addObject:[NSNull null]]; + } + MPExpressionLayout *expressionLayout = [[MPExpressionLayout alloc] initWithExpressionPath:[self.functionPath indexPathByAddingIndex:index] parent:self]; + _childCache[index] = expressionLayout; + return expressionLayout; +} + +#pragma mark Size Calculation Methods + +- (NSSize)sizeOfFunction +{ + if (!_valid) { + _cachedSize = [self calculateSize]; + _valid = YES; + } + return _cachedSize; +} + +- (NSSize)calculateSize +{ + return NSMakeSize(0, 0); +} + +#pragma mark Drawing Methods + +- (void)drawFunctionAtPoint:(NSPoint)point +{ +} + +@end