Improved Model
Added Keyboard Selection Support Added Mouse Selection Support Added Keyboard Editing Support Corrected Some Bugs Abstracted the Layout System further Added Functions Button (test)
This commit is contained in:
@@ -9,6 +9,10 @@
|
||||
#import "MPExpressionLayout.h"
|
||||
#import "MPFunctionLayout.h"
|
||||
|
||||
#import "NSIndexPath+MPAdditions.h"
|
||||
|
||||
#define kMPEmptyBoxWidth (self.usesSmallSize ? 2.0 : 3.0)
|
||||
|
||||
@interface MPExpressionLayout (MPLineGeneration)
|
||||
|
||||
- (CTLineRef)lineForElementAtIndex:(NSUInteger)index;
|
||||
@@ -34,9 +38,8 @@
|
||||
|
||||
- (CTLineRef)createLineForString:(NSString *)aString
|
||||
{
|
||||
|
||||
NSAttributedString *text = [[NSAttributedString alloc] initWithString:aString
|
||||
attributes:@{NSFontAttributeName: [NSFont fontWithName:@"Lucida Grande" size:18.0]}];
|
||||
attributes:@{NSFontAttributeName: self.font}];
|
||||
CFAttributedStringRef attributedString = CFBridgingRetain(text);
|
||||
CTLineRef line = CTLineCreateWithAttributedString(attributedString);
|
||||
CFRelease(attributedString); // TODO: Is this release appropriate?
|
||||
@@ -48,49 +51,38 @@
|
||||
@implementation MPExpressionLayout
|
||||
|
||||
# pragma mark Creation Methods
|
||||
- (instancetype)initRootLayoutWithExpressionStorage:(MPExpressionStorage *)expressionStorage
|
||||
- (instancetype)initRootLayoutWithExpression:(MPExpression *)expression
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_expressionStorage = expressionStorage;
|
||||
_expression = expressionStorage;
|
||||
_expression = expression;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithPath:(NSIndexPath *)path
|
||||
parent:(MPLayout *)parent
|
||||
- (instancetype)initWithElementAtPath:(NSIndexPath *)path
|
||||
inRootExpression:(MPExpression *)rootExpression
|
||||
parent:(MPLayout *)parent
|
||||
{
|
||||
self = [super initWithPath:path
|
||||
parent:parent];
|
||||
self = [super initWithElementAtPath:path
|
||||
inRootExpression:rootExpression
|
||||
parent:parent];
|
||||
if (self) {
|
||||
_expression = [parent.expressionStorage elementAtIndexPath:path];
|
||||
_expression = [rootExpression elementAtIndexPath:path];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark Properties
|
||||
@synthesize expressionStorage = _expressionStorage;
|
||||
- (MPExpressionStorage *)expressionStorage
|
||||
{
|
||||
if (_expressionStorage) {
|
||||
return _expressionStorage;
|
||||
}
|
||||
return self.parent.expressionStorage;
|
||||
}
|
||||
|
||||
//- (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.expression.indexPath indexPathByAddingIndex:index];
|
||||
MPFunctionLayout *layout = [MPFunctionLayout functionLayoutForFunctionAtIndexPath:indexPath
|
||||
inRootExpression:self.expression.rootExpression
|
||||
parent:self];
|
||||
layout.flipped = self.flipped;
|
||||
layout.usesSmallSize = self.usesSmallSize;
|
||||
return layout;
|
||||
}];
|
||||
if ([cachedObject isKindOfClass:[MPLayout class]]) {
|
||||
@@ -99,51 +91,165 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSSize)sizeForElementAtIndex:(NSUInteger)index
|
||||
- (NSRect)boundsOfElementAtIndex:(NSUInteger)index
|
||||
{
|
||||
id symbol = [self.expression elementAtIndex:index];
|
||||
if ([symbol isString]) {
|
||||
CTLineRef line = [self lineForElementAtIndex:index];
|
||||
CFRetain(line);
|
||||
CGRect bounds = CTLineGetBoundsWithOptions(line, /*kCTLineBoundsUseOpticalBounds*/ 0);
|
||||
CGRect bounds = CTLineGetBoundsWithOptions(line, 0);
|
||||
CFRelease(line);
|
||||
return bounds.size;
|
||||
return bounds;
|
||||
} else {
|
||||
return [self childLayoutAtIndex:index].size;
|
||||
return [self childLayoutAtIndex:index].bounds;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Drawing Methods
|
||||
- (NSSize)generateSize
|
||||
- (NSRect)generateBounds
|
||||
{
|
||||
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);
|
||||
if (self.expression.numberOfElements == 0) {
|
||||
return NSMakeRect(0, [self.font descender], kMPEmptyBoxWidth, self.fontSize);
|
||||
}
|
||||
return NSMakeSize(width, height);
|
||||
CGFloat x = 0, y = 0, width = 0, height = 0;
|
||||
for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) {
|
||||
NSRect elementBounds = [self boundsOfElementAtIndex:index];
|
||||
width += elementBounds.size.width;
|
||||
height = MAX(height, elementBounds.size.height);
|
||||
y = MIN(y, elementBounds.origin.y);
|
||||
}
|
||||
return NSMakeRect(x, y, width, height);
|
||||
}
|
||||
|
||||
- (void)drawAtPoint:(NSPoint)point
|
||||
- (NSRect)boundingRectForRange:(NSRange)range
|
||||
{
|
||||
NSUInteger startOffset;
|
||||
NSUInteger startElementIndex = [self.expression indexOfElementAtSymbolLocation:range.location
|
||||
offset:&startOffset];
|
||||
// Calculate x position
|
||||
CGFloat x = 0, width = 0;
|
||||
for (NSUInteger index = 0; index < startElementIndex; index++) {
|
||||
x += [self boundsOfElementAtIndex:index].size.width;
|
||||
}
|
||||
|
||||
if (startOffset > 0) {
|
||||
CTLineRef line = [self lineForElementAtIndex:startElementIndex];
|
||||
CFRetain(line);
|
||||
CGFloat xOffset = CTLineGetOffsetForStringIndex(line, startOffset, NULL);
|
||||
x += xOffset;
|
||||
width += CTLineGetBoundsWithOptions(line, 0).size.width - xOffset;
|
||||
CFRelease(line);
|
||||
} else if (startElementIndex < self.expression.numberOfElements) { // Otherwise the selection is after the last symbol
|
||||
width += [self boundsOfElementAtIndex:startElementIndex].size.width;
|
||||
}
|
||||
|
||||
// If we search the caret position we are done
|
||||
if (range.length == 0) {
|
||||
return NSMakeRect(x, self.bounds.origin.y, 0, self.bounds.size.height);
|
||||
}
|
||||
|
||||
NSUInteger endOffset;
|
||||
NSUInteger endElementIndex = [self.expression indexOfElementAtSymbolLocation:NSMaxRange(range)
|
||||
offset:&endOffset];
|
||||
|
||||
// Selection is inside of one string element
|
||||
if (startElementIndex == endElementIndex) {
|
||||
CTLineRef line = [self lineForElementAtIndex:endElementIndex];
|
||||
CFRetain(line);
|
||||
CGFloat xStart = CTLineGetOffsetForStringIndex(line, startOffset, NULL);
|
||||
CGFloat xEnd = CTLineGetOffsetForStringIndex(line, endOffset, NULL);
|
||||
width = xEnd - xStart;
|
||||
CFRelease(line);
|
||||
return NSMakeRect(x, self.bounds.origin.y, width, self.bounds.size.height);
|
||||
}
|
||||
|
||||
// Calculate width
|
||||
for (NSUInteger index = startElementIndex + 1; index < endElementIndex; index++) {
|
||||
width += [self boundsOfElementAtIndex:index].size.width;
|
||||
}
|
||||
if (endOffset > 0) {
|
||||
CTLineRef line = [self lineForElementAtIndex:endElementIndex];
|
||||
CFRetain(line);
|
||||
width += CTLineGetOffsetForStringIndex(line, endOffset, NULL);
|
||||
CFRelease(line);
|
||||
}
|
||||
return NSMakeRect(x, self.bounds.origin.y, width, self.bounds.size.height);
|
||||
}
|
||||
|
||||
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index
|
||||
{
|
||||
CGFloat x = 0;
|
||||
for (NSUInteger i = 0; i < index; i++) {
|
||||
x += [self boundsOfElementAtIndex:i].size.width;
|
||||
}
|
||||
return NSMakePoint(x, 0);
|
||||
}
|
||||
|
||||
- (NSIndexPath *)indexPathForMousePoint:(NSPoint)point
|
||||
{
|
||||
NSUInteger currentPosition = 0;
|
||||
for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) {
|
||||
NSRect elementBounds = [self boundsOfElementAtIndex:index];
|
||||
NSPoint elementOffset = [self offsetOfChildLayoutAtIndex:index];
|
||||
elementBounds.origin.x += elementOffset.x;
|
||||
elementBounds.origin.y += elementOffset.y;
|
||||
|
||||
id<MPExpressionElement> element = [self.expression elementAtIndex:index];
|
||||
if (NSMouseInRect(point, elementBounds, self.flipped)) {
|
||||
if ([element isString]) {
|
||||
CTLineRef line = [self lineForElementAtIndex:index];
|
||||
CFRetain(line);
|
||||
CFIndex localIndex = CTLineGetStringIndexForPosition(line, point);
|
||||
CFRelease(line);
|
||||
return [NSIndexPath indexPathWithIndex:currentPosition+localIndex];
|
||||
} else {
|
||||
NSPoint pointInFunction = NSMakePoint(point.x - elementOffset.x, point.y + elementOffset.y);
|
||||
NSIndexPath *subPath = [[self childLayoutAtIndex:index] indexPathForMousePoint:pointInFunction];
|
||||
if (subPath.length == 1) {
|
||||
// A single index is used to communicate back wether the
|
||||
// selection should be before or after the function.
|
||||
// A 0 means before, a 1 means after.
|
||||
return [NSIndexPath indexPathWithIndex:currentPosition + [subPath indexAtPosition:0]];
|
||||
} else {
|
||||
return [subPath indexPathByPreceedingIndex:index];
|
||||
}
|
||||
}
|
||||
}
|
||||
currentPosition += element.length;
|
||||
}
|
||||
if (point.x < self.bounds.size.width / 2) {
|
||||
return [NSIndexPath indexPathWithIndex:0];
|
||||
} else {
|
||||
return [NSIndexPath indexPathWithIndex:self.expression.length];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)draw
|
||||
{
|
||||
// Get the current context
|
||||
CGContextRef context =
|
||||
(CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
|
||||
CGContextSaveGState(context);
|
||||
|
||||
if (self.expression.numberOfElements == 0) {
|
||||
|
||||
CGContextRestoreGState(context);
|
||||
NSBezierPath *path = [NSBezierPath bezierPathWithRect:NSMakeRect(0, 0 + self.font.descender, kMPEmptyBoxWidth, self.fontSize)];
|
||||
path.lineWidth = 0.5;
|
||||
[path stroke];
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the text matrix
|
||||
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
|
||||
|
||||
// Track the x position
|
||||
CGFloat x = point.x;
|
||||
|
||||
CGFloat x = 0;
|
||||
|
||||
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;
|
||||
NSRect elementBounds = [self boundsOfElementAtIndex:index];
|
||||
|
||||
if ([element isString]) {
|
||||
// Get the line to draw
|
||||
@@ -151,7 +257,7 @@
|
||||
CFRetain(line);
|
||||
|
||||
// Move to the appropriate position
|
||||
CGContextSetTextPosition(context, x, y);
|
||||
CGContextSetTextPosition(context, x, 0);
|
||||
|
||||
// Perform the drawing
|
||||
CTLineDraw(line, context);
|
||||
@@ -160,9 +266,9 @@
|
||||
} else {
|
||||
// Let the child layout draw itself
|
||||
MPLayout *layout = [self childLayoutAtIndex:index];
|
||||
[layout drawAtPoint:NSMakePoint(x, y)];
|
||||
[layout drawAtPoint:NSMakePoint(x, 0)];
|
||||
}
|
||||
x += elementSize.width;
|
||||
x += elementBounds.size.width;
|
||||
}
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user