mirror of https://github.com/bsnes-emu/bsnes.git
362 lines
13 KiB
Objective-C
362 lines
13 KiB
Objective-C
//
|
|
// HFRepresenterLayoutView.m
|
|
// HexFiend_2
|
|
//
|
|
// Copyright 2007 ridiculous_fish. All rights reserved.
|
|
//
|
|
|
|
#import <HexFiend/HFLayoutRepresenter.h>
|
|
|
|
@interface HFRepresenterLayoutViewInfo : NSObject {
|
|
@public
|
|
HFRepresenter *rep;
|
|
NSView *view;
|
|
NSPoint layoutPosition;
|
|
NSRect frame;
|
|
NSUInteger autoresizingMask;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation HFRepresenterLayoutViewInfo
|
|
|
|
- (NSString *)description {
|
|
return [NSString stringWithFormat:@"<%@ : %@>", view, NSStringFromRect(frame)];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation HFLayoutRepresenter
|
|
|
|
static NSInteger sortByLayoutPosition(id a, id b, void *self) {
|
|
USE(self);
|
|
NSPoint pointA = [a layoutPosition];
|
|
NSPoint pointB = [b layoutPosition];
|
|
if (pointA.y < pointB.y) return -1;
|
|
else if (pointA.y > pointB.y) return 1;
|
|
else if (pointA.x < pointB.x) return -1;
|
|
else if (pointA.x > pointB.x) return 1;
|
|
else return 0;
|
|
}
|
|
|
|
- (NSArray *)arraysOfLayoutInfos {
|
|
if (! representers) return nil;
|
|
|
|
NSMutableArray *result = [NSMutableArray array];
|
|
NSArray *reps = [representers sortedArrayUsingFunction:sortByLayoutPosition context:self];
|
|
NSMutableArray *currentReps = [NSMutableArray array];
|
|
CGFloat currentRepY = - CGFLOAT_MAX;
|
|
FOREACH(HFRepresenter*, rep, reps) {
|
|
HFRepresenterLayoutViewInfo *info = [[HFRepresenterLayoutViewInfo alloc] init];
|
|
info->rep = rep;
|
|
info->view = [rep view];
|
|
info->frame = [info->view frame];
|
|
info->layoutPosition = [rep layoutPosition];
|
|
info->autoresizingMask = [info->view autoresizingMask];
|
|
if (info->layoutPosition.y != currentRepY && [currentReps count] > 0) {
|
|
[result addObject:[[currentReps copy] autorelease]];
|
|
[currentReps removeAllObjects];
|
|
}
|
|
currentRepY = info->layoutPosition.y;
|
|
[currentReps addObject:info];
|
|
[info release];
|
|
}
|
|
if ([currentReps count]) [result addObject:[[currentReps copy] autorelease]];
|
|
return result;
|
|
}
|
|
|
|
- (NSRect)boundsRectForLayout {
|
|
NSRect result = [[self view] bounds];
|
|
/* Sometimes when we are not yet in a window, we get wonky bounds, so be paranoid. */
|
|
if (result.size.width < 0 || result.size.height < 0) result = NSZeroRect;
|
|
return result;
|
|
}
|
|
|
|
- (CGFloat)_computeMinHeightForLayoutInfos:(NSArray *)infos {
|
|
CGFloat result = 0;
|
|
HFASSERT(infos != NULL);
|
|
HFASSERT([infos count] > 0);
|
|
FOREACH(HFRepresenterLayoutViewInfo *, info, infos) {
|
|
if (! (info->autoresizingMask & NSViewHeightSizable)) result = MAX(result, NSHeight([info->view frame]));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
- (void)_applyYLocation:(CGFloat)yLocation andMinHeight:(CGFloat)height toInfos:(NSArray *)layoutInfos {
|
|
FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfos) {
|
|
info->frame.origin.y = yLocation;
|
|
if (info->autoresizingMask & NSViewHeightSizable) info->frame.size.height = height;
|
|
}
|
|
}
|
|
|
|
- (void)_layoutInfosHorizontally:(NSArray *)infos inRect:(NSRect)layoutRect withBytesPerLine:(NSUInteger)bytesPerLine {
|
|
CGFloat nextX = NSMinX(layoutRect);
|
|
NSUInteger numHorizontallyResizable = 0;
|
|
FOREACH(HFRepresenterLayoutViewInfo *, info, infos) {
|
|
CGFloat minWidth = [info->rep minimumViewWidthForBytesPerLine:bytesPerLine];
|
|
info->frame.origin.x = nextX;
|
|
info->frame.size.width = minWidth;
|
|
nextX += minWidth;
|
|
numHorizontallyResizable += !! (info->autoresizingMask & NSViewWidthSizable);
|
|
}
|
|
|
|
CGFloat remainingWidth = NSMaxX(layoutRect) - nextX;
|
|
if (numHorizontallyResizable > 0 && remainingWidth > 0) {
|
|
NSView *view = [self view];
|
|
CGFloat remainingPixels = [view convertSize:NSMakeSize(remainingWidth, 0) toView:nil].width;
|
|
HFASSERT(remainingPixels > 0);
|
|
CGFloat pixelsPerView = HFFloor(HFFloor(remainingPixels) / (CGFloat)numHorizontallyResizable);
|
|
if (pixelsPerView > 0) {
|
|
CGFloat pointsPerView = [view convertSize:NSMakeSize(pixelsPerView, 0) fromView:nil].width;
|
|
CGFloat pointsAdded = 0;
|
|
FOREACH(HFRepresenterLayoutViewInfo *, info, infos) {
|
|
info->frame.origin.x += pointsAdded;
|
|
if (info->autoresizingMask & NSViewWidthSizable) {
|
|
info->frame.size.width += pointsPerView;
|
|
pointsAdded += pointsPerView;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine {
|
|
CGFloat result = 0;
|
|
NSArray *arraysOfLayoutInfos = [self arraysOfLayoutInfos];
|
|
|
|
FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) {
|
|
CGFloat minWidthForRow = 0;
|
|
FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfos) {
|
|
minWidthForRow += [info->rep minimumViewWidthForBytesPerLine:bytesPerLine];
|
|
}
|
|
result = MAX(result, minWidthForRow);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
- (NSUInteger)_computeBytesPerLineForArraysOfLayoutInfos:(NSArray *)arraysOfLayoutInfos forLayoutInRect:(NSRect)layoutRect {
|
|
/* The granularity is our own granularity (probably 1), LCMed with the granularities of all other representers */
|
|
NSUInteger granularity = [self byteGranularity];
|
|
FOREACH(HFRepresenter *, representer, representers) {
|
|
granularity = HFLeastCommonMultiple(granularity, [representer byteGranularity]);
|
|
}
|
|
HFASSERT(granularity >= 1);
|
|
|
|
NSUInteger newNumGranules = (NSUIntegerMax - 1) / granularity;
|
|
FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) {
|
|
NSUInteger maxKnownGood = 0, minKnownBad = newNumGranules + 1;
|
|
while (maxKnownGood + 1 < minKnownBad) {
|
|
CGFloat requiredSpace = 0;
|
|
NSUInteger proposedNumGranules = maxKnownGood + (minKnownBad - maxKnownGood)/2;
|
|
NSUInteger proposedBytesPerLine = proposedNumGranules * granularity;
|
|
FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfos) {
|
|
requiredSpace += [info->rep minimumViewWidthForBytesPerLine:proposedBytesPerLine];
|
|
if (requiredSpace > NSWidth(layoutRect)) break;
|
|
}
|
|
if (requiredSpace > NSWidth(layoutRect)) minKnownBad = proposedNumGranules;
|
|
else maxKnownGood = proposedNumGranules;
|
|
}
|
|
newNumGranules = maxKnownGood;
|
|
}
|
|
return MAX(1u, newNumGranules) * granularity;
|
|
}
|
|
|
|
- (BOOL)_anyLayoutInfoIsVerticallyResizable:(NSArray *)vals {
|
|
HFASSERT(vals != NULL);
|
|
FOREACH(HFRepresenterLayoutViewInfo *, info, vals) {
|
|
if (info->autoresizingMask & NSViewHeightSizable) return YES;
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (BOOL)_addVerticalHeight:(CGFloat)heightPoints andOffset:(CGFloat)offsetPoints toLayoutInfos:(NSArray *)layoutInfos {
|
|
BOOL isVerticallyResizable = [self _anyLayoutInfoIsVerticallyResizable:layoutInfos];
|
|
CGFloat totalHeight = [self _computeMinHeightForLayoutInfos:layoutInfos] + heightPoints;
|
|
FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfos) {
|
|
info->frame.origin.y += offsetPoints;
|
|
if (isVerticallyResizable) {
|
|
if (info->autoresizingMask & NSViewHeightSizable) {
|
|
info->frame.size.height = totalHeight;
|
|
}
|
|
else {
|
|
CGFloat diff = totalHeight - info->frame.size.height;
|
|
HFASSERT(diff >= 0);
|
|
info->frame.origin.y += HFFloor(diff);
|
|
}
|
|
}
|
|
}
|
|
return isVerticallyResizable;
|
|
}
|
|
|
|
- (void)_distributeVerticalSpace:(CGFloat)space toArraysOfLayoutInfos:(NSArray *)arraysOfLayoutInfos {
|
|
HFASSERT(space >= 0);
|
|
HFASSERT(arraysOfLayoutInfos != NULL);
|
|
|
|
NSUInteger consumers = 0;
|
|
FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) {
|
|
if ([self _anyLayoutInfoIsVerticallyResizable:layoutInfos]) consumers++;
|
|
}
|
|
if (consumers > 0) {
|
|
NSView *view = [self view];
|
|
CGFloat availablePixels = [view convertSize:NSMakeSize(0, space) toView:nil].height;
|
|
HFASSERT(availablePixels > 0);
|
|
CGFloat pixelsPerView = HFFloor(HFFloor(availablePixels) / (CGFloat)consumers);
|
|
CGFloat pointsPerView = [view convertSize:NSMakeSize(0, pixelsPerView) fromView:nil].height;
|
|
CGFloat yOffset = 0;
|
|
if (pointsPerView > 0) {
|
|
FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) {
|
|
if ([self _addVerticalHeight:pointsPerView andOffset:yOffset toLayoutInfos:layoutInfos]) {
|
|
yOffset += pointsPerView;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)performLayout {
|
|
HFController *controller = [self controller];
|
|
if (! controller) return;
|
|
if (! representers) return;
|
|
|
|
NSArray *arraysOfLayoutInfos = [self arraysOfLayoutInfos];
|
|
if (! [arraysOfLayoutInfos count]) return;
|
|
|
|
NSUInteger transaction = [controller beginPropertyChangeTransaction];
|
|
|
|
NSRect layoutRect = [self boundsRectForLayout];
|
|
|
|
NSUInteger bytesPerLine;
|
|
if (maximizesBytesPerLine) bytesPerLine = [self _computeBytesPerLineForArraysOfLayoutInfos:arraysOfLayoutInfos forLayoutInRect:layoutRect];
|
|
else bytesPerLine = [controller bytesPerLine];
|
|
|
|
CGFloat yPosition = NSMinY(layoutRect);
|
|
FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) {
|
|
HFASSERT([layoutInfos count] > 0);
|
|
CGFloat minHeight = [self _computeMinHeightForLayoutInfos:layoutInfos];
|
|
[self _applyYLocation:yPosition andMinHeight:minHeight toInfos:layoutInfos];
|
|
yPosition += minHeight;
|
|
[self _layoutInfosHorizontally:layoutInfos inRect:layoutRect withBytesPerLine:bytesPerLine];
|
|
}
|
|
|
|
CGFloat remainingVerticalSpace = NSMaxY(layoutRect) - yPosition;
|
|
if (remainingVerticalSpace > 0) {
|
|
[self _distributeVerticalSpace:remainingVerticalSpace toArraysOfLayoutInfos:arraysOfLayoutInfos];
|
|
}
|
|
|
|
FOREACH(NSArray *, layoutInfoArray, arraysOfLayoutInfos) {
|
|
FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfoArray) {
|
|
[info->view setFrame:info->frame];
|
|
}
|
|
}
|
|
|
|
[controller endPropertyChangeTransaction:transaction];
|
|
}
|
|
|
|
- (NSArray *)representers {
|
|
return representers ? [[representers copy] autorelease] : @[];
|
|
}
|
|
|
|
- (instancetype)init {
|
|
self = [super init];
|
|
maximizesBytesPerLine = YES;
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSViewFrameDidChangeNotification object:[self view]];
|
|
[representers release];
|
|
[super dealloc];
|
|
}
|
|
|
|
- (void)encodeWithCoder:(NSCoder *)coder {
|
|
HFASSERT([coder allowsKeyedCoding]);
|
|
[super encodeWithCoder:coder];
|
|
[coder encodeObject:representers forKey:@"HFRepresenters"];
|
|
[coder encodeBool:maximizesBytesPerLine forKey:@"HFMaximizesBytesPerLine"];
|
|
}
|
|
|
|
- (instancetype)initWithCoder:(NSCoder *)coder {
|
|
HFASSERT([coder allowsKeyedCoding]);
|
|
self = [super initWithCoder:coder];
|
|
representers = [[coder decodeObjectForKey:@"HFRepresenters"] retain];
|
|
maximizesBytesPerLine = [coder decodeBoolForKey:@"HFMaximizesBytesPerLine"];
|
|
NSView *view = [self view];
|
|
[view setPostsFrameChangedNotifications:YES];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(frameChanged:) name:NSViewFrameDidChangeNotification object:view];
|
|
return self;
|
|
}
|
|
|
|
- (void)addRepresenter:(HFRepresenter *)representer {
|
|
REQUIRE_NOT_NULL(representer);
|
|
if (! representers) representers = [[NSMutableArray alloc] init];
|
|
HFASSERT([representers indexOfObjectIdenticalTo:representer] == NSNotFound);
|
|
[representers addObject:representer];
|
|
HFASSERT([[representer view] superview] != [self view]);
|
|
[[self view] addSubview:[representer view]];
|
|
[self performLayout];
|
|
}
|
|
|
|
- (void)removeRepresenter:(HFRepresenter *)representer {
|
|
REQUIRE_NOT_NULL(representer);
|
|
HFASSERT([representers indexOfObjectIdenticalTo:representer] != NSNotFound);
|
|
NSView *view = [representer view];
|
|
HFASSERT([view superview] == [self view]);
|
|
[view removeFromSuperview];
|
|
[representers removeObjectIdenticalTo:representer];
|
|
[self performLayout];
|
|
}
|
|
|
|
- (void)frameChanged:(NSNotification *)note {
|
|
USE(note);
|
|
[self performLayout];
|
|
}
|
|
|
|
- (void)initializeView {
|
|
NSView *view = [self view];
|
|
[view setPostsFrameChangedNotifications:YES];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(frameChanged:) name:NSViewFrameDidChangeNotification object:view];
|
|
}
|
|
|
|
- (NSView *)createView {
|
|
return [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)];
|
|
}
|
|
|
|
- (void)setMaximizesBytesPerLine:(BOOL)val {
|
|
maximizesBytesPerLine = val;
|
|
}
|
|
|
|
- (BOOL)maximizesBytesPerLine {
|
|
return maximizesBytesPerLine;
|
|
}
|
|
|
|
- (NSUInteger)maximumBytesPerLineForLayoutInProposedWidth:(CGFloat)proposedWidth {
|
|
NSArray *arraysOfLayoutInfos = [self arraysOfLayoutInfos];
|
|
if (! [arraysOfLayoutInfos count]) return 0;
|
|
|
|
NSRect layoutRect = [self boundsRectForLayout];
|
|
layoutRect.size.width = proposedWidth;
|
|
|
|
NSUInteger bytesPerLine = [self _computeBytesPerLineForArraysOfLayoutInfos:arraysOfLayoutInfos forLayoutInRect:layoutRect];
|
|
return bytesPerLine;
|
|
}
|
|
|
|
- (CGFloat)minimumViewWidthForLayoutInProposedWidth:(CGFloat)proposedWidth {
|
|
NSUInteger bytesPerLine;
|
|
if ([self maximizesBytesPerLine]) {
|
|
bytesPerLine = [self maximumBytesPerLineForLayoutInProposedWidth:proposedWidth];
|
|
} else {
|
|
bytesPerLine = [[self controller] bytesPerLine];
|
|
}
|
|
CGFloat newWidth = [self minimumViewWidthForBytesPerLine:bytesPerLine];
|
|
return newWidth;
|
|
}
|
|
|
|
- (void)controllerDidChange:(HFControllerPropertyBits)bits {
|
|
[super controllerDidChange:bits];
|
|
if (bits & (HFControllerViewSizeRatios | HFControllerBytesPerColumn | HFControllerByteGranularity)) {
|
|
[self performLayout];
|
|
}
|
|
}
|
|
|
|
@end
|