Rewritten split view resizing which plays well with a mix of fixed size and non-fixed size panes.

This commit is contained in:
Alexander Ljungberg
2012-01-27 20:44:38 +00:00
parent 9d7b822409
commit b27a3cdbe5
2 changed files with 107 additions and 36 deletions
+54 -26
View File
@@ -773,69 +773,97 @@ var CPSplitViewHorizontalImage = nil,
return;
}
var count = [_subviews count];
if (!count)
return;
SPLIT_VIEW_MAYBE_POST_WILL_RESIZE();
[self _postNotificationWillResize];
var index = 0,
count = [_subviews count],
bounds = [self bounds],
boundsSize = bounds.size[_sizeComponent],
dividerThickness = [self dividerThickness],
totalDividers = count - 1,
totalSizableSpace = 0,
nonSizableSpace = 0,
lastSizableIndex = -1,
oldFlexibleSpace = 0,
totalSizablePanes = 0,
isVertical = [self isVertical],
isSizableMap = {},
viewSizes = [],
delegateRespondsToShouldAdjust = [_delegate respondsToSelector:@selector(splitView:shouldAdjustSizeOfSubview:)];
// What we want to do is to preserve non resizable sizes first, and then to preserve the ratio of size to available
// non fixed space for every other subview. E.g. assume fixed space was 20 pixels initially, view 1 was 20 and
// view 2 was 30 pixels, for a total of 70 pixels. Then the new total size becomes 140 pixels. Now we want the fixed
// space to still be 20 pixels, view 1 to be 48 pixels and view 2 to be 72 pixels. This way the relative size of
// view 1 to view 2 remains the same - view 1 was 66% of view 2 initially and after the resize view 1 is still
// 66% of view 2's size.
//
// For this calculation, we can consider the dividers themselves to also be fixed size areas - they should remain
// the same size before and after.
// How much flexible size do we have in pre-resize pixels?
for (index = 0; index < count; ++index)
{
var view = _subviews[index],
isSizable = !delegateRespondsToShouldAdjust || [_delegate splitView:self shouldAdjustSizeOfSubview:view];
isSizable = !delegateRespondsToShouldAdjust || [_delegate splitView:self shouldAdjustSizeOfSubview:view],
size = [view frame].size[_sizeComponent];
isSizableMap[index] = isSizable;
viewSizes.push(size);
if (isSizable)
{
totalSizableSpace += [view frame].size[_sizeComponent];
lastSizableIndex = index;
oldFlexibleSpace += size;
totalSizablePanes++;
}
}
if (totalSizablePanes === count)
totalSizableSpace = 0;
// nonSizableSpace is the number of fixed pixels in pre-resize terms and the desired number post-resize.
var nonSizableSpace = oldSize[_sizeComponent] - oldFlexibleSpace,
newFlexibleSpace = boundsSize - nonSizableSpace,
remainingFixedPixelsToRemove = 0;
var nonSizableSpace = totalSizableSpace ? bounds.size[_sizeComponent] - totalSizableSpace : 0,
remainingFlexibleSpace = bounds.size[_sizeComponent] - oldSize[_sizeComponent],
oldDimension = (oldSize[_sizeComponent] - totalDividers * dividerThickness - nonSizableSpace),
ratio = oldDimension <= 0 ? 0 : (bounds.size[_sizeComponent] - totalDividers * dividerThickness - nonSizableSpace) / oldDimension;
if (newFlexibleSpace < 0)
{
remainingFixedPixelsToRemove = -newFlexibleSpace;
newFlexibleSpace = 0;
}
var remainingFixedPanes = count - totalSizablePanes;
for (index = 0; index < count; ++index)
{
var view = _subviews[index],
viewFrame = CGRectMakeCopy(bounds),
isSizable = isSizableMap[index];
isSizable = isSizableMap[index],
targetSize = 0;
// The last area must take up exactly the remaining space, fixed or not.
if (index + 1 === count)
viewFrame.size[_sizeComponent] = bounds.size[_sizeComponent] - viewFrame.origin[_originComponent];
else if (totalSizableSpace && isSizable && lastSizableIndex === index)
viewFrame.size[_sizeComponent] = MAX(0, ROUND([view frame].size[_sizeComponent] + remainingFlexibleSpace))
else if (isSizable || !totalSizableSpace)
targetSize = boundsSize - viewFrame.origin[_originComponent];
// Try to keep fixed size areas the same size.
else if (!isSizable)
{
viewFrame.size[_sizeComponent] = MAX(0, ROUND(ratio * [view frame].size[_sizeComponent]));
remainingFlexibleSpace -= (viewFrame.size[_sizeComponent] - [view frame].size[_sizeComponent]);
var removedFixedPixels = MIN(remainingFixedPixelsToRemove / remainingFixedPanes, viewSizes[index]);
targetSize = viewSizes[index] - removedFixedPixels;
remainingFixedPixelsToRemove -= removedFixedPixels;
remainingFixedPanes--;
}
// (new size / flexible size available) == (old size / old flexible size available)
else if (oldFlexibleSpace > 0)
targetSize = newFlexibleSpace * viewSizes[index] / oldFlexibleSpace;
// oldFlexibleSpace <= 0 so all flexible areas were crushed. When we get space, allocate it evenly.
// totalSizablePanes cannot be 0 since isSizable.
else
targetSize = newFlexibleSpace / totalSizablePanes;
else if (totalSizableSpace && !isSizable)
viewFrame.size[_sizeComponent] = [view frame].size[_sizeComponent];
targetSize = MAX(0, ROUND(targetSize));
bounds.origin[_originComponent] += viewFrame.size[_sizeComponent] + dividerThickness;
viewFrame.size[_sizeComponent] = targetSize;
[view setFrame:viewFrame];
bounds.origin[_originComponent] += targetSize + dividerThickness;
}
SPLIT_VIEW_MAYBE_POST_DID_RESIZE();
+53 -10
View File
@@ -26,20 +26,44 @@
- (void)testSplitViewResize
{
var dividerThickness = [splitView dividerThickness];
[self assert:50 equals:[viewA frameSize].height];
[self assert:(50 - dividerThickness) equals:[viewB frameSize].height];
[self assert:49 equals:[viewB frameSize].height];
[splitView setPosition:40 ofDividerAtIndex:0];
[self assert:40 equals:[viewA frameSize].height];
[self assert:(60 - dividerThickness) equals:[viewB frameSize].height];
[self assert:59 equals:[viewB frameSize].height];
[splitView setFrame:CGRectMake(0, 0, 200, 200)];
// The extra size should be distributed proportionally to the original sizes of the subviews.
[self assert:80 equals:[viewA frameSize].height];
[self assert:(120 - dividerThickness) equals:[viewB frameSize].height];
[self assert:119 equals:[viewB frameSize].height];
// It should work for shrinking as well.
[splitView setFrame:CGRectMake(0, 0, 200, 100)];
[self assert:40 equals:[viewA frameSize].height];
[self assert:59 equals:[viewB frameSize].height];
// And for multiple areas.
var viewC = [[CPView alloc] initWithFrame:CGRectMake(0, 0, 200, 50)];
[splitView addSubview:viewC];
// Force an immediate adjustment of subview sizes.
[splitView viewWillDraw];
[self assert:26 equals:[viewA frameSize].height message:"adding viewC shrinks viewA"];
[self assert:39 equals:[viewB frameSize].height message:"adding viewC shrinks viewB"];
[self assert:33 equals:[viewC frameSize].height message:"new viewC is fit into remaining space"];
// Grow with 3 areas.
[splitView setFrame:CGRectMake(0, 0, 200, 200)];
[self assert:53 equals:[viewA frameSize].height];
[self assert:79 equals:[viewB frameSize].height];
[self assert:66 equals:[viewC frameSize].height];
// Shrink with 3 areas.
[splitView setFrame:CGRectMake(0, 0, 200, 100)];
[self assert:26 equals:[viewA frameSize].height];
[self assert:39 equals:[viewB frameSize].height];
[self assert:33 equals:[viewC frameSize].height];
}
- (void)testSplitView_shouldAdjustSizeOfSubview_
@@ -54,14 +78,33 @@
[self assert:50 equals:[viewA frameSize].height];
[self assert:(150 - dividerThickness) equals:[viewB frameSize].height];
// Should work just as well with three views.
var viewC = [[CPView alloc] initWithFrame:CGRectMake(0, 0, 200, 50)];
[splitView addSubview:viewC];
[splitView setPosition:66 ofDividerAtIndex:0];
[splitView setPosition:(132 + dividerThickness) ofDividerAtIndex:1];
[splitView setPosition:50 ofDividerAtIndex:0];
[splitView setPosition:100 ofDividerAtIndex:1];
[self assert:66 equals:[viewA frameSize].height];
[self assert:66 equals:[viewB frameSize].height];
[self assert:66 equals:[viewC frameSize].height];
[self assert:50 equals:[viewA frameSize].height];
[self assert:49 equals:[viewB frameSize].height];
[self assert:99 equals:[viewC frameSize].height];
// Shrink
[splitView setFrame:CGRectMake(0, 0, 200, 100)];
[self assert:50 equals:[viewA frameSize].height];
[self assert:16 equals:[viewB frameSize].height];
[self assert:32 equals:[viewC frameSize].height];
// Crush fixed
[splitView setFrame:CGRectMake(0, 0, 200, 40)];
[self assert:38 equals:[viewA frameSize].height message:"fixed size area should be forced to fit"];
[self assert:0 equals:[viewB frameSize].height];
[self assert:0 equals:[viewC frameSize].height];
// Regrow
[splitView setFrame:CGRectMake(0, 0, 200, 100)];
[self assert:38 equals:[viewA frameSize].height];
[self assert:30 equals:[viewB frameSize].height];
[self assert:30 equals:[viewC frameSize].height];
}
- (void)testAutosave