diff options
author | pinkerton@chromium.org <pinkerton@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-22 18:05:31 +0000 |
---|---|---|
committer | pinkerton@chromium.org <pinkerton@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-22 18:05:31 +0000 |
commit | 8538894e63ccd5239049484737fb4b819fe49bad (patch) | |
tree | 96a646bffcdfa4b3d6ce2c43a30655bf81f798f2 /chrome/browser/cocoa/tab_view.mm | |
parent | 2e63183c36ee22e7c3c470cfcf49c9266016dc21 (diff) | |
download | chromium_src-8538894e63ccd5239049484737fb4b819fe49bad.zip chromium_src-8538894e63ccd5239049484737fb4b819fe49bad.tar.gz chromium_src-8538894e63ccd5239049484737fb4b819fe49bad.tar.bz2 |
Fix a lot of jankiness during tab dragging. Fix dragging last tab in a different way (overlay view) so we can continue to use the "sprouting up" new tab animation. Improved animation with placeholder so it's smoother. Fixed jank closing and tearing off windows by suppressing window updates. Patch from alcor@google.com
BUG=none
TEST=tab dragging w/in a window, between windows. Creating new tabs. Closing window with lots of tabs.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@16753 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/cocoa/tab_view.mm')
-rw-r--r-- | chrome/browser/cocoa/tab_view.mm | 421 |
1 files changed, 220 insertions, 201 deletions
diff --git a/chrome/browser/cocoa/tab_view.mm b/chrome/browser/cocoa/tab_view.mm index 1033546..02b9867 100644 --- a/chrome/browser/cocoa/tab_view.mm +++ b/chrome/browser/cocoa/tab_view.mm @@ -51,258 +51,277 @@ // TODO(pinkerton/alcor): This routine needs *a lot* of work to marry Cole's // ideas of dragging cocoa views between windows and how the Browser and // TabStrip models want to manage tabs. -- (void)mouseDown:(NSEvent *)theEvent { - static const CGFloat kTearDistance = 36.0; - // Make sure the controller doesn't go away while we're doing this. - // TODO(pinkerton): cole had this, not sure why it's necessary. - [[controller_ retain] autorelease]; +static const CGFloat kTearDistance = 36.0; +static const NSTimeInterval kTearDuration = 0.333; +static const double kDragStartDistance = 3.0; +- (void)mouseDown:(NSEvent *)theEvent { // Fire the action to select the tab. if ([[controller_ target] respondsToSelector:[controller_ action]]) [[controller_ target] performSelector:[controller_ action] withObject:self]; // Resolve overlay back to original window. - NSWindow* sourceWindow = [self window]; - if ([sourceWindow isKindOfClass:[NSPanel class]]) { - sourceWindow = [sourceWindow parentWindow]; + sourceWindow_ = [self window]; + if ([sourceWindow_ isKindOfClass:[NSPanel class]]) { + sourceWindow_ = [sourceWindow_ parentWindow]; } - TabWindowController* sourceController = [sourceWindow windowController]; + sourceWindowFrame_ = [sourceWindow_ frame]; + sourceTabFrame_ = [self frame]; + sourceController_ = [sourceWindow_ windowController]; + draggedController_ = nil; + dragWindow_ = nil; + dragOverlay_ = nil; + targetController_ = nil; + tabWasDragged_ = NO; + tearTime_ = 0.0; + draggingWithinTabStrip_ = YES; // We don't want to "tear off" a tab if there's only one in the window. Treat // it like we're dragging around a tab we've already detached. Note that // unit tests might have |-numberOfTabs| reporting zero since the model // won't be fully hooked up. We need to be prepared for that and not send // them into the "magnetic" codepath. - BOOL isLastRemainingTab = [sourceController numberOfTabs] <= 1; + isTheOnlyTab_ = [sourceController_ numberOfTabs] <= 1; - BOOL dragging = YES; - BOOL moveBetweenWindows = NO; + dragOrigin_ = [NSEvent mouseLocation]; - NSPoint lastPoint = - [[theEvent window] convertBaseToScreen:[theEvent locationInWindow]]; + // Because we move views between windows, we need to handle the event loop + // ourselves. Ideally we should use the standard event loop. + while (1) { + theEvent = + [NSApp nextEventMatchingMask:NSLeftMouseUpMask | NSLeftMouseDraggedMask + untilDate:[NSDate distantFuture] + inMode:NSDefaultRunLoopMode dequeue:YES]; + NSPoint thisPoint = [NSEvent mouseLocation]; - // First, go through the magnetic drag cycle. We break out of this if - // "stretchiness" ever exceeds the a set amount. - NSRect frame = [self frame]; - if (!isLastRemainingTab) { - while (dragging) { - theEvent = - [NSApp nextEventMatchingMask:NSLeftMouseUpMask | NSLeftMouseDraggedMask - untilDate:[NSDate distantFuture] - inMode:NSDefaultRunLoopMode dequeue:YES]; - NSPoint thisPoint = [NSEvent mouseLocation]; - CGFloat stretchiness = thisPoint.y - lastPoint.y; - stretchiness = copysign(sqrtf(fabs(stretchiness))/sqrtf(kTearDistance), - stretchiness); - [sourceController insertPlaceholderForTab:self - frame:NSOffsetRect(frame, - thisPoint.x - lastPoint.x, 0) - yStretchiness:stretchiness]; - - CGFloat tearForce = fabs(thisPoint.y - lastPoint.y); - if (tearForce > kTearDistance) break; - if ([theEvent type] == NSLeftMouseUp) { - // Mouse up, break out of the drag event tracking loop - dragging = NO; - break; - } + NSEventType type = [theEvent type]; + if (type == NSLeftMouseDragged) { + [self mouseDragged:theEvent]; + } else { // Mouse Up + [self mouseUp:theEvent]; + break; } } +} - if (dragging) - [sourceController removePlaceholder]; +- (void)mouseDragged:(NSEvent *)theEvent { + // First, go through the magnetic drag cycle. We break out of this if + // "stretchiness" ever exceeds the a set amount. + tabWasDragged_ = YES; - TabWindowController* draggedController = nil; - TabWindowController* targetController = nil; + if (isTheOnlyTab_) draggingWithinTabStrip_ = NO; + if (draggingWithinTabStrip_) { + NSRect frame = [self frame]; + NSPoint thisPoint = [NSEvent mouseLocation]; + CGFloat stretchiness = thisPoint.y - dragOrigin_.y; + stretchiness = copysign(sqrtf(fabs(stretchiness))/sqrtf(kTearDistance), + stretchiness) / 2.0; + CGFloat offset = thisPoint.x - dragOrigin_.x; + if (fabsf(offset) > 100) stretchiness = 0; + [sourceController_ insertPlaceholderForTab:self + frame:NSOffsetRect(sourceTabFrame_, + offset, 0) + yStretchiness:stretchiness]; + + CGFloat tearForce = fabs(thisPoint.y - dragOrigin_.y); + if (tearForce > kTearDistance) { + draggingWithinTabStrip_ = NO; + // When you finally leave the strip, we treat that as the origin. + dragOrigin_.x = thisPoint.x; + } else { + return; + } + } - NSWindow* dragWindow = nil; - NSWindow* dragOverlay = nil; + NSPoint lastPoint = + [[theEvent window] convertBaseToScreen:[theEvent locationInWindow]]; // Do not start dragging until the user has "torn" the tab off by // moving more than 3 pixels. - BOOL torn = NO; - static const double kDragStartDistance = 3.0; - NSDate* targetDwellDate = nil; // The date this target was first chosen NSMutableArray* targets = [NSMutableArray array]; - while (dragging) { - theEvent = - [NSApp nextEventMatchingMask:NSLeftMouseUpMask | NSLeftMouseDraggedMask - untilDate:[NSDate distantFuture] - inMode:NSDefaultRunLoopMode dequeue:YES]; - NSPoint thisPoint = [NSEvent mouseLocation]; - - if (!torn) { - double dx = thisPoint.x - lastPoint.x; - double dy = thisPoint.y - lastPoint.y; - - if (dx * dx + dy * dy < kDragStartDistance * kDragStartDistance - && [theEvent type] == NSLeftMouseDragged) { - continue; + NSPoint thisPoint = [NSEvent mouseLocation]; + + // Find all the windows that could be a target. It has to be of the + // appropriate class, and visible (obviously). + if (![targets count]) { + for (NSWindow* window in [NSApp windows]) { + if (window == sourceWindow_ && isTheOnlyTab_) continue; + if (window == dragWindow_) continue; + if (![window isVisible]) continue; + NSWindowController *controller = [window windowController]; + if ([controller isKindOfClass:[TabWindowController class]]) { + [targets addObject:controller]; } - torn = YES; } + } - // Find all the windows that could be a target. It has to be of the - // appropriate class, and visible (obviously). - if (![targets count]) { - for (NSWindow* window in [NSApp windows]) { - if (window == sourceWindow && isLastRemainingTab) continue; - if (window == dragWindow) continue; - if (![window isVisible]) continue; - NSWindowController *controller = [window windowController]; - if ([controller isKindOfClass:[TabWindowController class]]) { - [targets addObject:controller]; - } + // Iterate over possible targets checking for the one the mouse is in. + // The mouse can be in either the tab or window frame. + TabWindowController* newTarget = nil; + for (TabWindowController* target in targets) { + NSRect windowFrame = [[target window] frame]; + if (NSPointInRect(thisPoint, windowFrame)) { + NSRect tabStripFrame = [[target tabStripView] frame]; + tabStripFrame = [[target tabStripView] convertRectToBase:tabStripFrame]; + tabStripFrame.origin = [[target window] + convertBaseToScreen:tabStripFrame.origin]; + if (NSPointInRect(thisPoint, tabStripFrame)) { + newTarget = target; } + break; } + } - // Iterate over possible targets checking for the one the mouse is in. - // The mouse can be in either the tab or window frame. - TabWindowController* newTarget = nil; - for (TabWindowController* target in targets) { - NSRect windowFrame = [[target window] frame]; - if (NSPointInRect(thisPoint, windowFrame)) { - NSRect tabStripFrame = [[target tabStripView] frame]; - tabStripFrame = [[target tabStripView] convertRectToBase:tabStripFrame]; - tabStripFrame.origin = [[target window] - convertBaseToScreen:tabStripFrame.origin]; - if (NSPointInRect(thisPoint, tabStripFrame)) { - newTarget = target; - } - break; - } + // If we're now targeting a new window, re-layout the tabs in the old + // target and reset how long we've been hovering over this new one. + if (targetController_ != newTarget) { + targetDwellDate = [NSDate date]; + [targetController_ removePlaceholder]; + targetController_ = newTarget; + if (!newTarget) { + tearTime_ = [NSDate timeIntervalSinceReferenceDate]; + tearOrigin_ = [dragWindow_ frame].origin; } + } - // If we're now targeting a new window, re-layout the tabs in the old - // target and reset how long we've been hovering over this new one. - if (targetController != newTarget) { - targetDwellDate = [NSDate date]; - [targetController removePlaceholder]; - targetController = newTarget; + // Create or identify the dragged controller. + if (!draggedController_) { + if (isTheOnlyTab_) { + draggedController_ = sourceController_; + dragWindow_ = [draggedController_ window]; + } else { + // Detach from the current window and put it in a new window. + draggedController_ = [sourceController_ detachTabToNewWindow:self]; + dragWindow_ = [draggedController_ window]; + [dragWindow_ setAlphaValue:0.0]; } - NSEventType type = [theEvent type]; - if (type == NSLeftMouseDragged) { - moveBetweenWindows = YES; - if (!draggedController) { - if (isLastRemainingTab) { - draggedController = sourceController; - dragWindow = [draggedController window]; - } else { - // Detach from the current window and put it in a new window. - draggedController = [sourceController detachTabToNewWindow:self]; - dragWindow = [draggedController window]; - [dragWindow setAlphaValue:0.0]; - } - - // Bring the target window to the front and make sure it has a border. - [dragWindow setLevel:NSFloatingWindowLevel]; - [dragWindow orderFront:nil]; - [dragWindow makeMainWindow]; - [draggedController showOverlay]; - dragOverlay = [draggedController overlayWindow]; - - NSPoint origin = [sourceWindow frame].origin; - - origin.y += thisPoint.y - lastPoint.y; - [dragWindow setFrameOrigin:NSMakePoint(origin.x, origin.y)]; - //if (![targets count]) - // [dragOverlay setHasShadow:NO]; - } else { - NSPoint origin = [dragWindow frame].origin; - origin.x += thisPoint.x - lastPoint.x; - origin.y += thisPoint.y - lastPoint.y; - [dragWindow setFrameOrigin:NSMakePoint(origin.x, origin.y)]; - } + // Bring the target window to the front and make sure it has a border. + [dragWindow_ setLevel:NSFloatingWindowLevel]; + [dragWindow_ orderFront:nil]; + [dragWindow_ makeMainWindow]; + [draggedController_ showOverlay]; + dragOverlay_ = [draggedController_ overlayWindow]; + //if (![targets count]) + // [dragOverlay_ setHasShadow:NO]; + if (!isTheOnlyTab_) { + tearTime_ = [NSDate timeIntervalSinceReferenceDate]; + tearOrigin_ = sourceWindowFrame_.origin; + } + } - // If we're not hovering over any window, make the window is fully - // opaque. Otherwise, find where the tab might be dropped and insert - // a placeholder so it appears like it's part of that window. - if (targetController) { - if (![[targetController window] isKeyWindow]) { - // && ([targetDwellDate timeIntervalSinceNow] < -REQUIRED_DWELL)) { - [[targetController window] makeKeyAndOrderFront:nil]; - [targets removeAllObjects]; - targetDwellDate = nil; - } - - // Compute where placeholder should go and insert it into the - // destination tab strip. - NSRect dropTabFrame = [[targetController tabStripView] frame]; - NSView *draggedTabView = [draggedController selectedTabView]; - NSRect tabFrame = [draggedTabView frame]; - tabFrame = [draggedTabView convertRectToBase:[self bounds]]; - tabFrame.origin = [dragWindow - convertBaseToScreen:tabFrame.origin]; - tabFrame.origin = [[targetController window] - convertScreenToBase:tabFrame.origin]; - tabFrame = [[targetController tabStripView] - convertRectFromBase:tabFrame]; - NSPoint point = - [sourceWindow convertBaseToScreen: - [self convertPointToBase:NSZeroPoint]]; - [targetController insertPlaceholderForTab:self - frame:tabFrame - yStretchiness:0]; - [targetController layoutTabs]; - } else { - [dragWindow makeKeyAndOrderFront:nil]; - } + float tearProgress = [NSDate timeIntervalSinceReferenceDate] - tearTime_; + tearProgress /= kTearDuration; + tearProgress = sqrtf(MAX(MIN(tearProgress, 1.0), 0.0)); + + // Move the dragged window to the right place on the screen. + NSPoint origin = sourceWindowFrame_.origin; + origin.x += (thisPoint.x - dragOrigin_.x); + origin.y += (thisPoint.y - dragOrigin_.y); + + if (tearProgress < 1) { + // If the tear animation is not complete, call back to ourself with the + // same event to animate even if the mouse isn't moving. + [NSObject cancelPreviousPerformRequestsWithTarget:self]; + [self performSelector:@selector(mouseDragged:) + withObject:theEvent + afterDelay:1.0f/30.0f]; + + origin.x = (1 - tearProgress) * tearOrigin_.x + tearProgress * origin.x; + origin.y = (1 - tearProgress) * tearOrigin_.y + tearProgress * origin.y; + } - [dragWindow setHasShadow:NO]; - [dragWindow setAlphaValue:targetController ? 0.1 : 0.5]; - [[draggedController overlayWindow] - setAlphaValue:targetController ? 0.85 : 1.0]; - } else if (type == NSLeftMouseUp) { - // Mouse up, break out of the drag event tracking loop - dragging = NO; + if (targetController_) + origin.y = [[targetController_ window] frame].origin.y; + [dragWindow_ setFrameOrigin:NSMakePoint(origin.x, origin.y)]; + + // If we're not hovering over any window, make the window is fully + // opaque. Otherwise, find where the tab might be dropped and insert + // a placeholder so it appears like it's part of that window. + if (targetController_) { + if (![[targetController_ window] isKeyWindow]) { + // && ([targetDwellDate timeIntervalSinceNow] < -REQUIRED_DWELL)) { + [[targetController_ window] orderFront:nil]; + [targets removeAllObjects]; + targetDwellDate = nil; } - lastPoint = thisPoint; - } // while tracking mouse - // The drag/click is done. If the user dragged the mouse, finalize the drag - // and clean up. - if (moveBetweenWindows) { - // Move between windows. If |targetController| is nil, we're not dropping - // into any existing window. - TabWindowController* dropController = targetController; - if (dropController) { - NSView* draggedTabView = [draggedController selectedTabView]; - [draggedController removeOverlay]; - [dropController moveTabView:draggedTabView - fromController:draggedController]; - [dropController showWindow:nil]; + // Compute where placeholder should go and insert it into the + // destination tab strip. + NSRect dropTabFrame = [[targetController_ tabStripView] frame]; + TabView *draggedTabView = (TabView *)[draggedController_ selectedTabView]; + NSRect tabFrame = [draggedTabView frame]; + tabFrame.origin = [dragWindow_ convertBaseToScreen:tabFrame.origin]; + tabFrame.origin = [[targetController_ window] + convertScreenToBase:tabFrame.origin]; + tabFrame = [[targetController_ tabStripView] + convertRectFromBase:tabFrame]; + NSPoint point = + [sourceWindow_ convertBaseToScreen: + [draggedTabView convertPointToBase:NSZeroPoint]]; + [targetController_ insertPlaceholderForTab:self + frame:tabFrame + yStretchiness:0]; + [targetController_ layoutTabs]; + } else { + [dragWindow_ makeKeyAndOrderFront:nil]; + } + BOOL chromeShouldBeVisible = targetController_ == nil; + + if (chromeIsVisible_ != chromeShouldBeVisible) { + [dragWindow_ setHasShadow:YES]; + if (targetController_) { + [NSAnimationContext beginGrouping]; + [[NSAnimationContext currentContext] setDuration:0.00001]; + [[dragWindow_ animator] setAlphaValue:0.0]; + [NSAnimationContext endGrouping]; + [[draggedController_ overlayWindow] setHasShadow:YES]; + [[[draggedController_ overlayWindow] animator] setAlphaValue:1.0]; } else { - [targetController removePlaceholder]; - [[dragWindow animator] setAlphaValue:1.0]; - [dragOverlay setHasShadow:NO]; - [dragWindow setHasShadow:YES]; - [draggedController removeOverlay]; - [dragWindow makeKeyAndOrderFront:nil]; - - [[draggedController window] setLevel:NSNormalWindowLevel]; - [draggedController layoutTabs]; + [[draggedController_ overlayWindow] setAlphaValue:1.0]; + [[draggedController_ overlayWindow] setHasShadow:NO]; + [[dragWindow_ animator] setAlphaValue:0.5]; } - [sourceController layoutTabs]; - } else { - // Move or click within a window. We need to differentiate between a - // click on the tab and a drag by checking against the initial x position. - NSPoint currentPoint = [NSEvent mouseLocation]; - BOOL wasDrag = fabs(currentPoint.x - lastPoint.x) > kDragStartDistance; - if (wasDrag) { + chromeIsVisible_ = chromeShouldBeVisible; + } +} + +- (void)mouseUp:(NSEvent *)theEvent { + // The drag/click is done. If the user dragged the mouse, finalize the drag + // and clean up. + + if (draggingWithinTabStrip_) { + if (tabWasDragged_) { // Move tab to new location. - TabWindowController* dropController = sourceController; + TabWindowController* dropController = sourceController_; [dropController moveTabView:[dropController selectedTabView] fromController:nil]; } + } else if (targetController_) { + // Move between windows. If |targetController_| is nil, we're not dropping + // into any existing window. + NSView* draggedTabView = [draggedController_ selectedTabView]; + [draggedController_ removeOverlay]; + [targetController_ moveTabView:draggedTabView + fromController:draggedController_]; + [targetController_ showWindow:nil]; + } else { + [dragWindow_ setAlphaValue:1.0]; + [dragOverlay_ setHasShadow:NO]; + [dragWindow_ setHasShadow:YES]; + [draggedController_ removeOverlay]; + [dragWindow_ makeKeyAndOrderFront:nil]; + + [[draggedController_ window] setLevel:NSNormalWindowLevel]; + [draggedController_ removePlaceholder]; + [draggedController_ layoutTabs]; } - - [sourceController removePlaceholder]; + [sourceController_ removePlaceholder]; } - (void)otherMouseUp:(NSEvent*) theEvent { |