diff --git a/MathPad/MPDocument.m b/MathPad/MPDocument.m index 9ed8e20..aa4ee62 100644 --- a/MathPad/MPDocument.m +++ b/MathPad/MPDocument.m @@ -61,7 +61,6 @@ #pragma mark Actions - (IBAction)changeExpression:(id)sender { [self.resultExpressionView.expressionStorage insertElement:@"abc" atLocation:6]; - self.resultExpressionView.needsDisplay = YES; } @end diff --git a/MathPad/MPExpressionLayout.h b/MathPad/MPExpressionLayout.h index 95d5cee..6cc7162 100644 --- a/MathPad/MPExpressionLayout.h +++ b/MathPad/MPExpressionLayout.h @@ -14,6 +14,6 @@ - (instancetype)initRootLayoutWithExpressionStorage:(MPExpressionStorage *)expressionStorage; -@property (readonly, nonatomic, weak) MPExpression *expression; // Convenience +@property (readonly, nonatomic, weak) MPExpression *expression; @end diff --git a/MathPad/MPExpressionLayout.m b/MathPad/MPExpressionLayout.m index ed1aae4..cbccff5 100644 --- a/MathPad/MPExpressionLayout.m +++ b/MathPad/MPExpressionLayout.m @@ -9,45 +9,38 @@ #import "MPExpressionLayout.h" #import "MPFunctionLayout.h" -@interface MPExpressionLayout (MPPathGeneration) - -- (NSBezierPath *)bezierPathForChildAtIndex:(NSUInteger)index; -- (NSBezierPath *)generateBezierPathForString:(NSString *)aString; +@interface MPExpressionLayout (MPLineGeneration) +- (CTLineRef)lineForElementAtIndex:(NSUInteger)index; +- (CTLineRef)createLineForString:(NSString *)aString; @end -@implementation MPExpressionLayout (MPPathGeneration) +@implementation MPExpressionLayout (MPLineGeneration) -- (NSBezierPath *)bezierPathForChildAtIndex:(NSUInteger)index +- (CTLineRef)lineForElementAtIndex:(NSUInteger)index { - id symbol = [self.expression elementAtIndex:index]; - if ([symbol isString]) { - return [self cachableObjectForIndex:index - generator:^id{ - return [self generateBezierPathForString:symbol]; - }]; - } else { - MPLayout *layout = [self childLayoutAtIndex:index]; - return layout.bezierPath; + id element = [self.expression elementAtIndex:index]; + if (![element isString]) { + return NULL; } + NSString *string = element; + id lineObject = [self cachableObjectForIndex:index generator:^id{ + CTLineRef line = [self createLineForString:string]; + return CFBridgingRelease(line); + }]; + return (__bridge CTLineRef)lineObject; } -- (NSBezierPath *)generateBezierPathForString:(NSString *)aString +- (CTLineRef)createLineForString:(NSString *)aString { + NSAttributedString *text = [[NSAttributedString alloc] initWithString:aString attributes:@{NSFontAttributeName: [NSFont fontWithName:@"Lucida Grande" size:18.0]}]; - self.textStorage.attributedString = text; - NSRange glyphRange = [self.layoutManager glyphRangeForTextContainer:self.textContainer]; - NSGlyph glyphs[glyphRange.length+1]; - NSUInteger actualGlyphCount = [self.layoutManager getGlyphs:glyphs - range:glyphRange]; - NSBezierPath *path = [NSBezierPath bezierPath]; - [path moveToPoint:NSZeroPoint]; - [path appendBezierPathWithGlyphs:glyphs - count:actualGlyphCount - inFont:[NSFont fontWithName:@"Lucida Grande" size:18.0]]; - return path; + CFAttributedStringRef attributedString = CFBridgingRetain(text); + CTLineRef line = CTLineCreateWithAttributedString(attributedString); + CFRelease(attributedString); // TODO: Is this release appropriate? + return line; } @end @@ -60,6 +53,18 @@ self = [super init]; if (self) { _expressionStorage = expressionStorage; + _expression = expressionStorage; + } + return self; +} + +- (instancetype)initWithPath:(NSIndexPath *)path + parent:(MPLayout *)parent +{ + self = [super initWithPath:path + parent:parent]; + if (self) { + _expression = [parent.expressionStorage elementAtIndexPath:path]; } return self; } @@ -74,52 +79,92 @@ return self.parent.expressionStorage; } -- (MPExpression *)expression -{ - return [self.expressionStorage elementAtIndexPath:self.path]; -} +//- (MPExpression *)expression +//{ +// return [self.expressionStorage elementAtIndexPath:self.path]; +//} #pragma mark Cache Methods - (MPLayout *)childLayoutAtIndex:(NSUInteger)index { id cachedObject = [self cachableObjectForIndex:index generator:^id{ - NSIndexPath *indexPath = [self.path indexPathByAddingIndex:index]; + NSIndexPath *indexPath = [self.expression.indexPath indexPathByAddingIndex:index]; MPFunctionLayout *layout = [MPFunctionLayout functionLayoutForFunctionAtIndexPath:indexPath parent:self]; return layout; }]; - if ([cachedObject isKindOfClass:[NSBezierPath class]]) { - return nil; + if ([cachedObject isKindOfClass:[MPLayout class]]) { + return cachedObject; } - return cachedObject; + return nil; } -- (NSSize)sizeForChildAtIndex:(NSUInteger)index +- (NSSize)sizeForElementAtIndex:(NSUInteger)index { id symbol = [self.expression elementAtIndex:index]; if ([symbol isString]) { - return [self bezierPathForChildAtIndex:index].bounds.size; + CTLineRef line = [self lineForElementAtIndex:index]; + CFRetain(line); + CGRect bounds = CTLineGetBoundsWithOptions(line, /*kCTLineBoundsUseOpticalBounds*/ 0); + CFRelease(line); + return bounds.size; } else { return [self childLayoutAtIndex:index].size; } } #pragma mark Drawing Methods -- (NSBezierPath *)generateBezierPath +- (NSSize)generateSize { - NSBezierPath *fullPath = [NSBezierPath bezierPath]; - [fullPath moveToPoint:NSZeroPoint]; - NSUInteger x = 0; - for (NSInteger index = 0; index < self.expression.numberOfElements; ++index) { - NSAffineTransform *transform = [NSAffineTransform transform]; - // TODO: Translate by the right amount - [transform translateXBy:x yBy:0]; - NSBezierPath *path = [self bezierPathForChildAtIndex:index].copy; - [path transformUsingAffineTransform:transform]; - [fullPath appendBezierPath:path]; - x += path.bounds.size.width; + CGFloat width = 0, height = 0; + for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) { + NSSize elementSize = [self sizeForElementAtIndex:index]; + width += elementSize.width; + height = MAX(height, elementSize.height); } - return fullPath; + return NSMakeSize(width, height); +} + +- (void)drawAtPoint:(NSPoint)point +{ + // Get the current context + CGContextRef context = + (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + CGContextSaveGState(context); + + // Set the text matrix + CGContextSetTextMatrix(context, CGAffineTransformIdentity); + + // Track the x position + CGFloat x = point.x; + + for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) { + // The current element + id element = [self.expression elementAtIndex:index]; + NSSize elementSize = [self sizeForElementAtIndex:index]; + CGFloat dy = (self.size.height - elementSize.height) / 2; + CGFloat y = point.y + dy; + + if ([element isString]) { + // Get the line to draw + CTLineRef line = [self lineForElementAtIndex:index]; + CFRetain(line); + + // Move to the appropriate position + CGContextSetTextPosition(context, x, y); + + // Perform the drawing + CTLineDraw(line, context); + + CFRelease(line); + } else { + // Let the child layout draw itself + MPLayout *layout = [self childLayoutAtIndex:index]; + [layout drawAtPoint:NSMakePoint(x, y)]; + } + x += elementSize.width; + } + CGContextRestoreGState(context); } @end diff --git a/MathPad/MPExpressionStorage.h b/MathPad/MPExpressionStorage.h index e54d725..c186c0e 100644 --- a/MathPad/MPExpressionStorage.h +++ b/MathPad/MPExpressionStorage.h @@ -8,10 +8,14 @@ #import "MPExpression.h" -@class MPExpressionStorage, MPExpressionLayout; +@class MPExpressionStorage, MPExpressionView, MPExpressionLayout; @interface MPExpressionStorage : MPExpression +- (instancetype)initWithExpressionView:(MPExpressionView *)expressionView + elements:(NSArray *)elements; + +@property (readonly, nonatomic, weak) MPExpressionView *expressionView; @property (nonatomic, strong) MPExpressionLayout *rootLayout; - (NSLayoutManager *)layoutManager; diff --git a/MathPad/MPExpressionStorage.m b/MathPad/MPExpressionStorage.m index 5a4cf91..62ef2fa 100644 --- a/MathPad/MPExpressionStorage.m +++ b/MathPad/MPExpressionStorage.m @@ -7,6 +7,7 @@ // #import "MPExpressionStorage.h" +#import "MPExpressionView.h" #import "MPExpressionLayout.h" #import "MPFunctionLayout.h" #import "MPRangePath.h" @@ -19,6 +20,15 @@ @implementation MPExpressionStorage +- (instancetype)initWithExpressionView:(MPExpressionView *)expressionView elements:(NSArray *)elements +{ + self = [self initWithElements:elements]; + if (self) { + _expressionView = expressionView; + } + return self; +} + - (instancetype)initWithElements:(NSArray *)elements { self = [super initWithElements:elements]; @@ -69,7 +79,9 @@ for (NSUInteger index = 1; index < rangePath.location.length-1; index++) { current = [current childLayoutAtIndex:index]; } - [current clearCacheInRange:rangePath.rangeAtLastIndex replacementLength:replacementLength]; + [current clearCacheInRange:rangePath.rangeAtLastIndex + replacementLength:replacementLength]; + self.expressionView.needsDisplay = YES; } @end diff --git a/MathPad/MPExpressionView.h b/MathPad/MPExpressionView.h index 23060d5..e1a3b51 100644 --- a/MathPad/MPExpressionView.h +++ b/MathPad/MPExpressionView.h @@ -12,7 +12,7 @@ @class MPExpressionView, MPExpressionStorage, MPExpressionLayout, MPRangePath; -@interface MPExpressionView : NSView +@interface MPExpressionView : NSView #pragma mark Creation Methods @@ -23,6 +23,7 @@ @property (readonly, nonatomic, strong) MPExpressionStorage *expressionStorage; @property (nonatomic, getter = isEditable) BOOL editable; -@property (nonatomic, strong) MPRangePath *selection; +//@property (nonatomic, strong) MPRangePath *selection; +@property (nonatomic, strong) NSIndexPath *caretLocation; @end diff --git a/MathPad/MPExpressionView.m b/MathPad/MPExpressionView.m index 4b270c9..60cc613 100644 --- a/MathPad/MPExpressionView.m +++ b/MathPad/MPExpressionView.m @@ -13,7 +13,7 @@ #import "MPSumFunction.h" @interface MPExpressionView (MPCursor) -@property (nonatomic, strong) NSTimer *cursorTimer; + @end @implementation MPExpressionView @@ -49,8 +49,9 @@ - (void)initializeObjects { - MPExpressionStorage *expressionStorage = [[MPExpressionStorage alloc] initWithElements:@[@"12345", [[MPSumFunction alloc] init], [[MPSumFunction alloc] init]]]; + MPExpressionStorage *expressionStorage = [[MPExpressionStorage alloc] initWithExpressionView:self elements:@[@"12345", [[MPSumFunction alloc] init], [[MPSumFunction alloc] init]]]; _expressionStorage = expressionStorage; + _caretLocation = [[NSIndexPath alloc] initWithIndex:0]; } #pragma mark Properties @@ -60,9 +61,25 @@ return NO; } -- (void)setExpressionStorage:(MPExpressionStorage *)expressionStorage +- (BOOL)acceptsFirstResponder { - _expressionStorage = expressionStorage; + return YES; +} + +- (BOOL)canBecomeKeyView +{ + return YES; +} + +#pragma mark Event Handling +- (void)keyDown:(NSEvent *)theEvent +{ + [self interpretKeyEvents:@[theEvent]]; +} + +- (void)moveRight:(id)sender +{ + [self.expressionStorage deleteElementsInRange:NSMakeRange(0, 1)]; } #pragma mark Drawing Methods diff --git a/MathPad/MPFunctionLayout.h b/MathPad/MPFunctionLayout.h index 7fb8a61..2cd6818 100644 --- a/MathPad/MPFunctionLayout.h +++ b/MathPad/MPFunctionLayout.h @@ -14,18 +14,24 @@ + (MPFunctionLayout *)functionLayoutForFunctionAtIndexPath:(NSIndexPath *)path parent:(MPExpressionLayout *)parent; -@property (readonly, nonatomic, weak) MPFunction *function; // Convenience +@property (readonly, nonatomic, weak) MPFunction *function; @end @interface MPFunctionLayout (MPSubclassOverride) +#pragma mark Cache Methods +- (CTLineRef)lineForPrivateCacheIndex:(NSUInteger)index + generator:(CTLineRef (^)())generator; + // Should also implement accessor method for special function type: // - (MPCustomFunction *)customFunction // { // return (MPCustomFunction *)self.function; // } -- (NSBezierPath *)generateBezierPath; +#pragma mark Size and Drawing Methods +- (NSSize)generateSize; // To be implemented +- (void)drawAtPoint:(NSPoint)point; // To be implemented @end \ No newline at end of file diff --git a/MathPad/MPFunctionLayout.m b/MathPad/MPFunctionLayout.m index 8fdfa82..dd9c0ac 100644 --- a/MathPad/MPFunctionLayout.m +++ b/MathPad/MPFunctionLayout.m @@ -27,32 +27,63 @@ return [[self alloc] initWithPath:path parent:parent]; } -#pragma mark Properties -- (MPFunction *)function +- (instancetype)initWithPath:(NSIndexPath *)path + parent:(MPLayout *)parent { - return [self.expressionStorage elementAtIndexPath:self.path]; + self = [super initWithPath:path + parent:parent]; + if (self) { + _function = [parent.expressionStorage elementAtIndexPath:path]; + } + return self; } +#pragma mark Properties +//- (MPFunction *)function +//{ +// return [self.expressionStorage elementAtIndexPath:self.path]; +//} + #pragma mark Cache Methods +- (CTLineRef)lineForPrivateCacheIndex:(NSUInteger)index + generator:(CTLineRef (^)())generator +{ + NSUInteger actualIndex = self.function.numberOfChildren + index; + id (^actualGenerator)() = ^{ + CTLineRef line = generator(); + return CFBridgingRelease(line); + }; + id lineObject = [self cachableObjectForIndex:actualIndex + generator:actualGenerator]; + return (__bridge CTLineRef)(lineObject); +} + - (MPLayout *)childLayoutAtIndex:(NSUInteger)index { return [self cachableObjectForIndex:index generator:^id{ - NSIndexPath *childPath = [self.path indexPathByAddingIndex:index]; + NSIndexPath *childPath = [self.function.indexPath indexPathByAddingIndex:index]; MPExpressionLayout *layout = [[MPExpressionLayout alloc] initWithPath:childPath parent:self]; return layout; }]; } +#pragma mark Size and Drawing Methods + - (NSSize)sizeForChildAtIndex:(NSUInteger)index { MPLayout *childLayout = [self childLayoutAtIndex:index]; return [childLayout size]; } -- (NSBezierPath *)generateBezierPath +- (NSSize)generateSize { - return [NSBezierPath bezierPath]; + return NSZeroSize; +} + +- (void)drawAtPoint:(NSPoint)point +{ + } @end diff --git a/MathPad/MPLayout.h b/MathPad/MPLayout.h index 812598a..ed9f063 100644 --- a/MathPad/MPLayout.h +++ b/MathPad/MPLayout.h @@ -20,13 +20,9 @@ #pragma mark Text System Objects @property (readonly, nonatomic, weak) MPExpressionStorage *expressionStorage; -@property (readonly, nonatomic, weak) NSLayoutManager *layoutManager; -@property (readonly, nonatomic, weak) NSTextContainer *textContainer; -@property (readonly, nonatomic, weak) NSTextStorage *textStorage; #pragma mark Cache Tree @property (readonly, nonatomic, weak) MPLayout *parent; -@property (readonly, nonatomic, strong) NSIndexPath *path; #pragma mark Cache Methods // Querying Caches @@ -39,18 +35,14 @@ - (void)invalidate; #pragma mark Calculation and Drawing Methods +// TODO: Implement Small Size // @property (nonatomic) BOOL usesSmallSize; - (NSSize)size; - -- (NSBezierPath *)bezierPath; -- (NSBezierPath *)bezierPathAtOrigin:(NSPoint)point; - - (void)drawAtPoint:(NSPoint)point; @end @interface MPLayout (MPSubclassImplement) - (MPLayout *)childLayoutAtIndex:(NSUInteger)index; // To be implemented -- (NSSize)sizeForChildAtIndex:(NSUInteger)index; // To be implemented -- (NSBezierPath *)generateBezierPath; // To be implemented +- (NSSize)generateSize; // To be implemented @end \ No newline at end of file diff --git a/MathPad/MPLayout.m b/MathPad/MPLayout.m index 515c45f..0c49c79 100644 --- a/MathPad/MPLayout.m +++ b/MathPad/MPLayout.m @@ -10,19 +10,15 @@ @interface MPLayout () -// Querying Caches +// Querying and Storing Caches - (BOOL)hasCacheForElementAtIndex:(NSUInteger)index; - -// Storing Caches -- (void)cacheObject:(id)anObject - forElementAtIndex:(NSUInteger)index; - (void)ensureCacheSizeForIndex:(NSUInteger)index; @end @implementation MPLayout { NSMutableArray *_cache; - NSBezierPath *_cachedPath; + NSSize _cachedSize; } #pragma mark Creation Methods @@ -31,8 +27,7 @@ self = [super init]; if (self) { _cache = [[NSMutableArray alloc] init]; - _cachedPath = nil; - _path = [[NSIndexPath alloc] init]; + _cachedSize = NSZeroSize; } return self; } @@ -42,7 +37,6 @@ { self = [self init]; if (self) { - _path = path; _parent = parent; } return self; @@ -54,23 +48,8 @@ return self.parent.expressionStorage; } -- (NSLayoutManager *)layoutManager -{ - return self.expressionStorage.layoutManager; -} - -- (NSTextContainer *)textContainer -{ - return self.expressionStorage.textContainer; -} - -- (NSTextStorage *)textStorage -{ - return self.expressionStorage.textStorage; -} - #pragma mark Cache Tree -// Querying Caches +// Querying and Storing Caches - (BOOL)hasCacheForElementAtIndex:(NSUInteger)index { if (index >= _cache.count) { @@ -86,17 +65,9 @@ return _cache[index]; } id object = generator(); - [self cacheObject:object - forElementAtIndex:index]; - return object; -} - -// Storing Caches -- (void)cacheObject:(id)anObject - forElementAtIndex:(NSUInteger)index -{ [self ensureCacheSizeForIndex:index]; - _cache[index] = anObject; + _cache[index] = object; + return object; } - (void)ensureCacheSizeForIndex:(NSUInteger)index @@ -121,39 +92,41 @@ - (void)invalidate { - _cachedPath = nil; + _cachedSize = NSZeroSize; [self.parent invalidate]; } -#pragma mark Calculation Methods +#pragma mark Calculation and Drawing Methods - (NSSize)size { - return self.bezierPath.bounds.size; -} - -- (NSBezierPath *)bezierPath -{ - if (!_cachedPath) { - _cachedPath = [self generateBezierPath]; + if (NSEqualSizes(_cachedSize, NSZeroSize)) { + _cachedSize = [self generateSize]; } - return _cachedPath; -} - -- (NSBezierPath *)bezierPathAtOrigin:(NSPoint)point -{ - NSAffineTransform *transform = [NSAffineTransform transform]; - [transform translateXBy:point.x - yBy:point.y]; - NSBezierPath *path = [NSBezierPath bezierPath]; - [path appendBezierPath:self.bezierPath]; - [path transformUsingAffineTransform:transform]; - return path; + return _cachedSize; } - (void)drawAtPoint:(NSPoint)point { - NSBezierPath *path = [self bezierPathAtOrigin:point]; - [path fill]; + +} + +@end + +@implementation MPLayout (MPSubclassImplement) + +- (MPLayout *)childLayoutAtIndex:(NSUInteger)index +{ + return nil; +} + +- (NSSize)sizeForChildAtIndex:(NSUInteger)index +{ + return NSZeroSize; +} + +- (NSSize)generateSize +{ + return NSZeroSize; } @end diff --git a/MathPad/MPSumFunctionLayout.m b/MathPad/MPSumFunctionLayout.m index 7a0c2d2..14e2aa9 100644 --- a/MathPad/MPSumFunctionLayout.m +++ b/MathPad/MPSumFunctionLayout.m @@ -16,21 +16,46 @@ return (MPSumFunction *)self.function; } -- (NSBezierPath *)generateBezierPath +- (CTLineRef)line { - NSAttributedString *text = [[NSAttributedString alloc] initWithString:@"∑" - attributes:@{NSFontAttributeName: [NSFont fontWithName:@"Lucida Grande" size:18.0]}]; - self.textStorage.attributedString = text; - NSRange glyphRange = [self.layoutManager glyphRangeForTextContainer:self.textContainer]; - NSGlyph glyphs[glyphRange.length+1]; - NSUInteger actualGylphCount = [self.layoutManager getGlyphs:glyphs - range:glyphRange]; - NSBezierPath *path = [NSBezierPath bezierPath]; - [path moveToPoint:NSZeroPoint]; - [path appendBezierPathWithGlyphs:glyphs - count:actualGylphCount - inFont:[NSFont fontWithName:@"Lucida Grande" size:18.0]]; - return path; + CTLineRef line = [self lineForPrivateCacheIndex:0 generator:^CTLineRef{ + NSAttributedString *text = + [[NSAttributedString alloc] initWithString:@"∑" + attributes:@{NSFontAttributeName: [NSFont fontWithName:@"Lucida Grande" size:18.0]}]; + CFAttributedStringRef attributedString = CFBridgingRetain(text); + CTLineRef line = CTLineCreateWithAttributedString(attributedString); + CFRelease(attributedString); // TODO: Is this release appropriate + return line; + }]; + return line; +} + +- (NSSize)generateSize +{ + CTLineRef line = [self line]; + CFRetain(line); + CGSize size = CTLineGetBoundsWithOptions(line, /*kCTLineBoundsUseOpticalBounds*/0).size; + CFRelease(line); + return size; +} + +- (void)drawAtPoint:(NSPoint)point +{ + // Get the current context + CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + + // Set the text matrix + CGContextSetTextMatrix(context, CGAffineTransformIdentity); + + // Get the line of text + CTLineRef line = [self line]; + CFRetain(line); + + // Draw the line + CGContextSetTextPosition(context, point.x, point.y); + CTLineDraw(line, context); + + CFRelease(line); } @end