Drawing in Cocoa, wrong xy value
I'm trying to draw a bunch of rectangles in an NSView which is inside an NSScrollView. The code is working pretty well, but it seems like the computer is drawing at the wrong points.
When I use NSLog()
to log the current x
and y
the values are right, but when I look at my view, there is extra space added before the drawing coordinate.
This is the code:
//
// CoverFlowView.m
// iWatch
//
// Created by ief2 on 03/11/10.
//
//
#import "CoverFlowView.h"
#pragma mark Defaults
#define COVER_HEIGHT2WIDTH(a) ((float)a / 1.4)
#define COVER_HEIGHT (200.0)
#define COVER_WIDTH (COVER_HEIGHT2WIDTH(COVER_HEIGHT))
#define COVER_SPACE_MIN (20.0)
#pragma mark Private Methods
@interface CoverFlowView (PrivateMethods)
- (CGFloat)coverSpaceForMoviesPerLine:(NSUInteger *)n;
- (void)frameDidChange;
- (void)updateFrame;
@end
NSColor *RandomColor() {
float red = (float)(rand() % 255) / 254.0;
float green = (float)(rand() % 255) / 254.0;
float blue = (float)(rand() % 255) / 254.0;
NSColor *theColor = [[NSColor colorWithDeviceRed:red
green:green
blue:blue
alpha:1.0] retain];
return [theColor autorelease];
}
#pragma mark Implementation
@implementation CoverFlowView
#pragma mark Init and Dealloc
- (void)awakeFromNib {
srand(time(NULL));
NSMutableArray *colors = [[NSMutableArray alloc] initWithCapacity:100];
register int i;
for(i = 0; i < 100; i++) {
[colors addObject:RandomColor()];
}
self.movies = colors;
[colors autorelease];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(frameDidChange)
name:NSViewFrameDidChangeNotification
object:self];
}
- (id)initWithFrame:(NSRect)frame movies:(NSArray *)movs {
self = [super initWithFrame:frame];
if(self != nil) {
self.movies = movs;
// FIXME: Debug color array to movie
NSMutableArray *colors = [[NSMutableArray alloc] initWithCapacity:100];
register int i;
srand((unsigned int)time(NULL));
for(i = 0; i < 100; i++) {
[colors addObject:RandomColor()];
}
}
return self;
}
- (void)dealloc {
[_movies release];
[super dealloc];
}
#pragma mark Drawing
- (void)drawRect:(NSRect)dirtyRect {
[[NSColor blackColor] setFill];
NSRectFill(dirtyRect);
NSUInteger moviesPerLine = 0;
CGFloat coverSpace = 0;
// Get the values
coverSpace = [self coverSpaceForMoviesPerLine:&moviesPerLine];
// if it's less than 1, break the drawing
if(moviesPerLine < 1) {
return;
}
// get the rows not to draw
// p = top left point's y
开发者_JAVA技巧 // h = movie cover height ->\
// s = space between -->The movies height and it's space above it
//
// p
// f(x) = -------
// h + s
double rows = ((dirtyRect.origin.y) / (COVER_HEIGHT + coverSpace));
// get the first movie that has to be drawn
// could be half-draw
//
// (NSUInteger)rows * moviesPerLine
NSUInteger index = (NSUInteger)rows * moviesPerLine;
if(!(index < [self.movies count]))
return;
// variable declaration
CGFloat curY;
CGFloat curX;
// start drawing until the line after max
// start the loop
do {
NSUInteger rowIndex;
// get the y for the movie
// x = movie index (start from 1)
// m = movies per row
// h = movie height
// s = movie space
//
// [x - (x % m)]
// f(x) = (|-----------|) * (h + s) + s
// [ m ]
// BUT: if rows == 2, rows -= 1
rowIndex = ((index + 1) - ((index + 1) % moviesPerLine)) / moviesPerLine;
((index + 1) % moviesPerLine == 0) ? (rowIndex -= 1) : (rowIndex = rowIndex);
curY = rowIndex * (COVER_HEIGHT + coverSpace) + coverSpace;
// get the x fot the movie
// x = movie index (start from 1)
// m = movies per line
// w = movie width
// s = cover space
//
//
// f(x) = ((x - (x - (x % m))) - 1) * (w + s) + s
// BUT: if row == 0, row = m
//
rowIndex = (index+1) - (((index+1) - ((index + 1) % moviesPerLine)));
(rowIndex == 0) ? (rowIndex = moviesPerLine) : (rowIndex = rowIndex);
curX = (rowIndex - 1) * (COVER_WIDTH + coverSpace) + coverSpace;
NSLog(@"%s index: %3u || x: %5.0f || y: %5.0f", __FUNCTION__, index, curX, curY);
// Start the drawing
NSRect bezierPathRect = NSMakeRect(curX + coverSpace, curY, COVER_WIDTH, COVER_HEIGHT);
NSBezierPath *path = [NSBezierPath bezierPathWithRect:bezierPathRect];
[path setLineWidth:2.0];
[[NSColor whiteColor] setStroke];
[(NSColor *)[self.movies objectAtIndex:index] setFill];
[path fill];
[path stroke];
// add up values
index++;
if(!(index < [self.movies count]))
return;
} while(curY - (COVER_HEIGHT + coverSpace) < dirtyRect.origin.y + dirtyRect.size.height);
}
- (BOOL)isFlipped {
return YES;
}
#pragma mark Private Methods
- (void)frameDidChange {
}
- (CGFloat)coverSpaceForMoviesPerLine:(NSUInteger *)n {
// x = number of covers
// w = view width
// m = cover width
// y = the space
//
// w - (mx)
// f(x) = --------
// x + 1
CGFloat m = COVER_WIDTH;
CGFloat w = [self bounds].size.width;
register NSUInteger x = 1;
CGFloat space;
while(1) {
space = ((w-(m*x)) / ( x + 1.0));
if(space < COVER_SPACE_MIN) {
x--;
space = ((w - (m*x)) / (x + 1.0));
break;
}
x++;
}
if(n) *n = x;
return space;
}
#pragma mark Properties
@synthesize movies=_movies;
- (void)setMovies:(NSArray *)ar {
if(ar != _movies) {
[_movies release];
_movies = [ar retain];
// Update frame size
NSUInteger m;
CGFloat space = [self coverSpaceForMoviesPerLine:&m];
NSUInteger lines = [ar count] / m;
CGFloat height = (COVER_HEIGHT + space) * ((CGFloat)lines) + space;
CGFloat width = [self frame].size.width;
NSRect frame = [self frame];
frame.size = NSMakeSize(width, height);
[self setFrame:frame];
[self setNeedsDisplay:YES];
}
}
@end
and here you can find a screen shot of the running application:
If someone can tell me what I'm doing wrong, I'd really appreciate it!
Thank you, ief2
EDIT: Don't mind the wrong titles, I made mistake giving the files a name :-)
Seems to me you’re calculating the X coordinate the wrong way. You have twice the spacing at the beginning of the line and no space at the end of the line. So if you subtract one spacing from curX
you get the right result:
curX = (rowIndex - 1) * (COVER_WIDTH + coverSpace);
Also your code is more complicated than it needs to be. For example, you don’t really need a loop in coverSpaceForMoviesPerLine:
and the loop in drawRect:
can be simplified (and made more efficient at the same time) too.
精彩评论