mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 21:17:03 +00:00
Rewritten split view resizing which plays well with a mix of fixed size and non-fixed size panes.
This commit is contained in:
+54
-26
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user