Archived
1

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:
Kim Wittenburg
2014-08-31 15:41:17 +02:00
parent 9aa4bca234
commit 4a3ea0cede
23 changed files with 885 additions and 262 deletions

View File

@@ -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);
}