Reorganized File Structure
Added Documentation
This commit is contained in:
297
MathKit/MPExpressionLayout.m
Normal file
297
MathKit/MPExpressionLayout.m
Normal file
@@ -0,0 +1,297 @@
|
||||
//
|
||||
// MPExpressionLayout.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 07.08.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPExpressionLayout.h"
|
||||
|
||||
#import "MPExpression.h"
|
||||
#import "MPExpressionElement.h"
|
||||
#import "MPPowerFunction.h"
|
||||
|
||||
#import "MPFunctionLayout.h"
|
||||
#import "MPPowerFunctionLayout.h"
|
||||
|
||||
#import "MPToken.h"
|
||||
#import "NSIndexPath+MPAdditions.h"
|
||||
|
||||
@interface MPExpressionLayout (MPLineGeneration)
|
||||
|
||||
- (CTLineRef)lineForElementAtIndex:(NSUInteger)index;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPExpressionLayout (MPLineGeneration)
|
||||
|
||||
- (CTLineRef)lineForElementAtIndex:(NSUInteger)index
|
||||
{
|
||||
id element = [self.expression elementAtIndex:index];
|
||||
if (![element isString]) {
|
||||
return NULL;
|
||||
}
|
||||
NSRange tokensRange = [self.expression convertRange:NSMakeRange(index, 1)
|
||||
fromReferenceFrame:MPElementReferenceFrame
|
||||
toReferenceFrame:MPTokenReferenceFrame];
|
||||
NSArray *tokens = [self.expression itemsInRange:tokensRange
|
||||
referenceFrame:MPTokenReferenceFrame];
|
||||
id lineObject = [self cachableObjectForIndex:index generator:^id{
|
||||
NSMutableAttributedString *text = [[NSMutableAttributedString alloc] init];
|
||||
for (id<MPToken> token in tokens) {
|
||||
NSFont *font;
|
||||
if (token.tokenType == MPElementaryFunctionToken) {
|
||||
font = [self specialFontWithSize:self.contextInferredFontSize];
|
||||
} else {
|
||||
font = [self normalFontWithSize:self.contextInferredFontSize];
|
||||
}
|
||||
NSAttributedString *tokenText = [[NSAttributedString alloc] initWithString:token.stringValue
|
||||
attributes:@{NSFontAttributeName: font}];
|
||||
[text appendAttributedString:tokenText];
|
||||
}
|
||||
CFAttributedStringRef attributedString = CFBridgingRetain(text);
|
||||
CTLineRef line = CTLineCreateWithAttributedString(attributedString);
|
||||
CFRelease(attributedString);
|
||||
return CFBridgingRelease(line);
|
||||
}];
|
||||
return (__bridge CTLineRef)lineObject;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPExpressionLayout
|
||||
|
||||
# pragma mark Creation Methods
|
||||
- (instancetype)initWithExpression:(MPExpression *)expression
|
||||
parent:(MPFunctionLayout *)parent
|
||||
{
|
||||
self = [super initWithParent:parent];
|
||||
if (self) {
|
||||
_expression = expression;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark Cache Methods
|
||||
- (NSUInteger)numberOfChildren
|
||||
{
|
||||
return self.expression.countElements;
|
||||
}
|
||||
|
||||
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index
|
||||
{
|
||||
id cachedObject = [self cachableObjectForIndex:index generator:^id{
|
||||
MPFunction *function = (MPFunction *)[self.expression elementAtIndex:index];
|
||||
MPFunctionLayout *layout = [MPFunctionLayout functionLayoutForFunction:function
|
||||
parent:self];
|
||||
layout.flipped = self.flipped;
|
||||
layout.usesSmallSize = self.usesSmallSize;
|
||||
return layout;
|
||||
}];
|
||||
if ([cachedObject isKindOfClass:[MPLayout class]]) {
|
||||
return cachedObject;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSRect)boundsOfElementAtIndex:(NSUInteger)index
|
||||
{
|
||||
id symbol = [self.expression elementAtIndex:index];
|
||||
if ([symbol isString]) {
|
||||
CTLineRef line = [self lineForElementAtIndex:index];
|
||||
CFRetain(line);
|
||||
CGRect bounds = CTLineGetBoundsWithOptions(line, 0);
|
||||
CFRelease(line);
|
||||
return bounds;
|
||||
} else {
|
||||
return [self childLayoutAtIndex:index].bounds;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Drawing Methods
|
||||
- (NSRect)generateBounds
|
||||
{
|
||||
if (self.expression.countElements == 0) {
|
||||
return NSMakeRect(0, kMPEmptyBoxYOrigin, kMPEmptyBoxWidth, kMPEmptyBoxHeight);
|
||||
}
|
||||
CGFloat x = 0, y = 0, width = 0, height = 0;
|
||||
for (NSUInteger index = 0; index < self.expression.countElements; 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);
|
||||
}
|
||||
|
||||
- (NSRect)boundingRectForRange:(NSRange)range
|
||||
{
|
||||
NSUInteger startOffset;
|
||||
NSUInteger startElementIndex = [self.expression convertIndex:range.location
|
||||
fromReferenceFrame:MPSymbolReferenceFrame
|
||||
toReferenceFrame:MPElementReferenceFrame
|
||||
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.countElements) { // 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 convertIndex:NSMaxRange(range)
|
||||
fromReferenceFrame:MPSymbolReferenceFrame
|
||||
toReferenceFrame:MPElementReferenceFrame
|
||||
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.countElements; index++) {
|
||||
NSRect elementBounds = [self boundsOfElementAtIndex:index];
|
||||
NSPoint elementOffset = [self offsetOfChildLayoutAtIndex:index];
|
||||
elementBounds.origin.x += elementOffset.x;
|
||||
elementBounds.origin.y += elementOffset.y;
|
||||
|
||||
// Only the horizontal location is to consider for hit testing
|
||||
elementBounds.size.height = CGFLOAT_MAX;
|
||||
|
||||
id<MPExpressionElement> element = [self.expression elementAtIndex:index];
|
||||
if (NSMouseInRect(point, elementBounds, self.flipped)) {
|
||||
NSPoint pointInElement = NSMakePoint(point.x - elementOffset.x, point.y + elementOffset.y);
|
||||
if ([element isString]) {
|
||||
CTLineRef line = [self lineForElementAtIndex:index];
|
||||
CFRetain(line);
|
||||
CFIndex localIndex = CTLineGetStringIndexForPosition(line, pointInElement);
|
||||
CFRelease(line);
|
||||
return [NSIndexPath indexPathWithIndex:currentPosition+localIndex];
|
||||
} else {
|
||||
NSIndexPath *subPath = [[self childLayoutAtIndex:index] indexPathForMousePoint:pointInElement];
|
||||
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.countSymbols];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)drawsChildrenManually
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)draw
|
||||
{
|
||||
// Get the current context
|
||||
CGContextRef context =
|
||||
(CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
|
||||
CGContextSaveGState(context);
|
||||
|
||||
[[NSColor textColor] set];
|
||||
if (self.expression.countElements == 0) {
|
||||
CGContextRestoreGState(context);
|
||||
NSBezierPath *path = [NSBezierPath bezierPathWithRect:NSMakeRect(0, kMPEmptyBoxDrawingYOrigin, kMPEmptyBoxDrawingWidth, kMPEmptyBoxDrawingHeight)];
|
||||
path.lineWidth = 0.5;
|
||||
[path stroke];
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the text matrix
|
||||
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
|
||||
|
||||
// Track the x position and the bounds of the last element
|
||||
CGFloat x = 0;
|
||||
NSRect lastElementBounds = NSMakeRect(0, kMPEmptyBoxYOrigin, kMPEmptyBoxWidth, kMPEmptyBoxHeight);
|
||||
|
||||
for (NSUInteger index = 0; index < self.expression.countElements; index++) {
|
||||
// The current element
|
||||
id element = [self.expression elementAtIndex:index];
|
||||
NSRect elementBounds = [self boundsOfElementAtIndex:index];
|
||||
|
||||
if ([element isString]) {
|
||||
// Get the line to draw
|
||||
CTLineRef line = [self lineForElementAtIndex:index];
|
||||
CFRetain(line);
|
||||
|
||||
// Move to the appropriate position
|
||||
CGContextSetTextPosition(context, x, 0);
|
||||
|
||||
// Perform the drawing
|
||||
CTLineDraw(line, context);
|
||||
|
||||
CFRelease(line);
|
||||
} else {
|
||||
// Let the child layout draw itself
|
||||
MPLayout *layout = [self childLayoutAtIndex:index];
|
||||
if ([layout isKindOfClass:[MPPowerFunctionLayout class]]) {
|
||||
((MPPowerFunctionLayout *)layout).baseBounds = lastElementBounds;
|
||||
}
|
||||
[layout drawAtPoint:NSMakePoint(x, 0)];
|
||||
}
|
||||
x += elementBounds.size.width;
|
||||
lastElementBounds = elementBounds;
|
||||
}
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user