diff --git a/AppKit/AppKit.j b/AppKit/AppKit.j index f9b0e4d72..84fc150c9 100644 --- a/AppKit/AppKit.j +++ b/AppKit/AppKit.j @@ -103,6 +103,7 @@ @import "CPTokenField.j" @import "CPToolbar.j" @import "CPToolbarItem.j" +@import "CPTrackingArea.j" @import "CPTreeNode.j" @import "CPUserDefaultsController.j" @import "CPView.j" diff --git a/AppKit/CPControl.j b/AppKit/CPControl.j index 8c7ea8958..18d40bf7d 100644 --- a/AppKit/CPControl.j +++ b/AppKit/CPControl.j @@ -27,6 +27,7 @@ @import "CPShadow.j" @import "CPView.j" @import "CPKeyValueBinding.j" +@import "CPTrackingArea.j" @global CPApp @@ -195,11 +196,23 @@ var CPControlBlackColor = [CPColor blackColor]; { _sendActionOn = CPLeftMouseUpMask; _trackingMouseDownFlags = 0; + + [self addTrackingArea:[[CPTrackingArea alloc] initWithRect:CGRectMakeZero() + options:CPTrackingMouseEnteredAndExited | CPTrackingActiveInKeyWindow | CPTrackingInVisibleRect + owner:self + userInfo:nil]]; } return self; } +- (void)awakeFromCib +{ + [self addTrackingArea:[[CPTrackingArea alloc] initWithRect:CGRectMakeZero() + options:CPTrackingMouseEnteredAndExited | CPTrackingActiveInKeyWindow | CPTrackingInVisibleRect + owner:self + userInfo:nil]]; +} #pragma mark - #pragma mark Control Size diff --git a/AppKit/CPEvent.j b/AppKit/CPEvent.j index 1189ad962..5d633ad9a 100644 --- a/AppKit/CPEvent.j +++ b/AppKit/CPEvent.j @@ -29,6 +29,7 @@ @import "CPCompatibility.j" @import "CGGeometry.j" @import "CPText.j" +@import "CPTrackingArea.j" @class CPTextField @class CPWindow @@ -82,6 +83,8 @@ var _CPEventPeriodicEventPeriod = 0, BOOL _suppressCappuccinoCut; BOOL _suppressCappuccinoPaste; #endif + + CPTrackingArea _trackingArea; } /*! @@ -141,6 +144,27 @@ var _CPEventPeriodicEventPeriod = 0, timestamp:aTimestamp windowNumber:aWindowNumber context:aGraphicsContext eventNumber:anEventNumber clickCount:aClickCount pressure:aPressure]; } +/*! + Creates a new mouse tracking event. + + @param anEventType the event type + @param aPoint the location of the cursor in the window specified by \c aWindowNumber + @param modifierFlags a bitwise combination of the modifiers specified in the CPEvent globals + @param aTimestamp the time the event occurred + @param aWindowNumber the number of the CPWindow where the event occurred + @param aGraphicsContext the graphics context where the event occurred + @param anEventNumber a number for this event + @param aTrackingArea the tracking area that triggered the event + @throws CPInternalInconsistencyException if an invalid event type is provided + @return the new mouse event + */ ++ (id)enterExitEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned)modifierFlags + timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext + eventNumber:(int)anEventNumber trackingArea:(CPTrackingArea)aTrackingArea +{ + return [[self alloc] _initEnterExitEventWithType:anEventType location:aPoint modifierFlags:modifierFlags timestamp:aTimestamp windowNumber:aWindowNumber context:aGraphicsContext eventNumber:anEventNumber trackingArea:aTrackingArea]; +} + /*! Creates a new custom event. @@ -201,6 +225,28 @@ var _CPEventPeriodicEventPeriod = 0, return self; } +/* @ignore */ +- (id)_initEnterExitEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned)modifierFlags + timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext + eventNumber:(int)anEventNumber trackingArea:(CPTrackingArea)aTrackingArea +{ + if ((anEventType != CPMouseEntered) && (anEventType != CPMouseExited) && (anEventType != CPCursorUpdate)) + [CPException raise:CPInternalInconsistencyException reason:"Invalid event type"]; + + if (self = [self _initWithType:anEventType]) + { + _location = CGPointCreateCopy(aPoint); + _modifierFlags = modifierFlags; + _timestamp = aTimestamp; + _context = aGraphicsContext; + _eventNumber = anEventNumber; + _trackingArea = aTrackingArea; + _window = [CPApp windowWithWindowNumber:aWindowNumber]; + } + + return self; +} + /* @ignore */ - (id)_initKeyEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned int)modifierFlags timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext @@ -583,6 +629,14 @@ var _CPEventPeriodicEventPeriod = 0, } } +- (CPTrackingArea)trackingArea +{ + if ((_type != CPMouseEntered) && (_type != CPMouseExited) && (_type != CPCursorUpdate)) + [CPException raise:CPInternalInconsistencyException reason:"You can't call trackingArea for this event type"]; + + return _trackingArea; +} + @end function _CPEventFirePeriodEvent() diff --git a/AppKit/CPSplitView.j b/AppKit/CPSplitView.j index e10c67e8d..9825f0c21 100644 --- a/AppKit/CPSplitView.j +++ b/AppKit/CPSplitView.j @@ -26,6 +26,7 @@ @import "CPImage.j" @import "CPView.j" @import "CPCursor.j" +@import "CPTrackingArea.j" @class CPUserDefaults @global CPApp @@ -145,6 +146,14 @@ var ShouldSuppressResizeNotifications = 1, }; } +- (void)awakeFromCib +{ + [self addTrackingArea:[[CPTrackingArea alloc] initWithRect:CGRectMakeZero() + options:CPTrackingMouseEnteredAndExited | CPTrackingMouseMoved | CPTrackingActiveInKeyWindow | CPTrackingInVisibleRect + owner:self + userInfo:nil]]; +} + - (id)initWithFrame:(CGRect)aFrame { if (self = [super initWithFrame:aFrame]) @@ -159,6 +168,11 @@ var ShouldSuppressResizeNotifications = 1, _shouldAutosave = YES; [self _setVertical:YES]; + + [self addTrackingArea:[[CPTrackingArea alloc] initWithRect:CGRectMakeZero() + options:CPTrackingMouseEnteredAndExited | CPTrackingMouseMoved | CPTrackingActiveInKeyWindow | CPTrackingInVisibleRect + owner:self + userInfo:nil]]; } return self; diff --git a/AppKit/CPTableHeaderView.j b/AppKit/CPTableHeaderView.j index 36d6d1ef1..86f128471 100644 --- a/AppKit/CPTableHeaderView.j +++ b/AppKit/CPTableHeaderView.j @@ -24,6 +24,7 @@ @import "CPView.j" @import "CPCursor.j" @import "_CPImageAndTextView.j" +@import "CPTrackingArea.j" @class CPTableView @@ -281,6 +282,14 @@ var CPTableHeaderViewResizeZone = 3.0, }; } +- (void)awakeFromCib +{ + [self addTrackingArea:[[CPTrackingArea alloc] initWithRect:CGRectMakeZero() + options:CPTrackingMouseEnteredAndExited | CPTrackingMouseMoved | CPTrackingMouseMoved | CPTrackingActiveInKeyWindow | CPTrackingInVisibleRect + owner:self + userInfo:nil]]; +} + - (void)_init { _mouseDownLocation = CGPointMakeZero(); @@ -305,7 +314,13 @@ var CPTableHeaderViewResizeZone = 3.0, self = [super initWithFrame:aFrame]; if (self) + { [self _init]; + [self addTrackingArea:[[CPTrackingArea alloc] initWithRect:CGRectMakeZero() + options:CPTrackingMouseEnteredAndExited | CPTrackingMouseMoved | CPTrackingMouseMoved | CPTrackingActiveInKeyWindow | CPTrackingInVisibleRect + owner:self + userInfo:nil]]; + } return self; } diff --git a/AppKit/CPTrackingArea.j b/AppKit/CPTrackingArea.j new file mode 100644 index 000000000..93ba9cfb8 --- /dev/null +++ b/AppKit/CPTrackingArea.j @@ -0,0 +1,138 @@ +/* + * CPTrackingArea.j + * AppKit + * + * Created by Didier Korthoudt. + * Copyright 2015, Cappuccino Project. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import +@import "CPView.j" + +@typedef CPTrackingAreaOptions +CPTrackingMouseEnteredAndExited = 1 << 1, +CPTrackingMouseMoved = 1 << 2, +CPTrackingCursorUpdate = 1 << 3, +CPTrackingActiveWhenFirstResponder = 1 << 4, +CPTrackingActiveInKeyWindow = 1 << 5, +CPTrackingActiveInActiveApp = 1 << 6, // both mean the same in cappuccino context +CPTrackingActiveAlways = 1 << 6, +CPTrackingAssumeInside = 1 << 7, +CPTrackingInVisibleRect = 1 << 8, +CPTrackingEnabledDuringMouseDrag = 1 << 9; + +/*! + @ingroup appkit + + A CPTrackingArea defines a region of view that generates mouse-tracking and + cursor-update events when the mouse is over that region. + */ +@implementation CPTrackingArea : CPObject +{ + CGRect _rect @accessors(property=rect, readonly); + CPTrackingAreaOptions _options @accessors(property=options, readonly); + id _owner @accessors(property=owner, readonly); + CPDictionary _userInfo @accessors(property=userInfo, readonly); + + CPView _referencingView; +} + + +#pragma mark - +#pragma mark Initialization + +/*! Initializes and returns an object defining a region of a view to receive mouse-tracking events, mouse-moved events, cursor-update events, or possibly all these events. + */ +- (CPTrackingArea)initWithRect:(CGRect)aRect options:(CPTrackingAreaOptions)options owner:(id)owner userInfo:(CPDictionary)userInfo +{ + if (self = [super init]) + { + if (!(options > 0)) + [CPException raise:CPInternalInconsistencyException reason:"Invalid CPTrackingArea options"]; + + // Temporary, waiting for cursor rect management + if (options & CPTrackingCursorUpdate) + [CPException raise:CPInternalInconsistencyException reason:"CPTrackingCursorUpdate not yet implemented"]; + + // Check options: + // - at least one in CPTrackingMouseEnteredAndExited, CPTrackingMouseMoved, CPTrackingCursorUpdate + // - exactly one in CPTrackingActiveWhenFirstResponder, CPTrackingActiveInKeyWindow, CPTrackingActiveAlways + // - no check on CPTrackingAssumeInside, CPTrackingInVisibleRect, CPTrackingEnableDuringMouseDrag + + if (!((options & CPTrackingMouseEnteredAndExited) || (options & CPTrackingMouseMoved) || (options & CPTrackingCursorUpdate))) + [CPException raise:CPInternalInconsistencyException reason:"You must at least select a type of event message"]; + + if ((((options & CPTrackingActiveWhenFirstResponder) > 0) + ((options & CPTrackingActiveInKeyWindow) > 0) + ((options & CPTrackingActiveAlways) > 0)) != 1) + [CPException raise:CPInternalInconsistencyException reason:"You must select one and only one scope of tracking"]; + + _rect = aRect; + _options = options; + _owner = owner; + _userInfo = userInfo; + + _referencingView = nil; + } + + return self; +} + + +#pragma mark - +#pragma mark Implementation + +- (void)_setReferencingView:(CPView)aView +{ + _referencingView = aView; +} + +- (CPView)_referencingView +{ + return _referencingView; +} + +- (BOOL)_isReferenced +{ + return (!!_referencingView); +} + +#pragma mark - +#pragma mark CPCoding + +- (id)initWithCoder:(CPCoder)aCoder +{ + if (self = [super init]) + { + _rect = [aCoder decodeObjectForKey:@"_rect"]; + _options = [aCoder decodeObjectForKey:@"_options"]; + _owner = [aCoder decodeObjectForKey:@"_owner"]; + _userInfo = [aCoder decodeObjectForKey:@"_userInfo"]; + _referencingView = [aCoder decodeObjectForKey:@"_referencingView"]; + } + + return self; +} + +- (void)encodeWithCoder:(CPCoder)aCoder +{ + [aCoder encodeObject:_rect forKey:@"_rect"]; + [aCoder encodeObject:_options forKey:@"_options"]; + [aCoder encodeObject:_owner forKey:@"_owner"]; + [aCoder encodeObject:_userInfo forKey:@"_userInfo"]; + [aCoder encodeObject:_referencingView forKey:@"_referencingView"]; +} + +@end diff --git a/AppKit/CPView.j b/AppKit/CPView.j index 0057c0b89..2b9c2a341 100644 --- a/AppKit/CPView.j +++ b/AppKit/CPView.j @@ -32,6 +32,7 @@ @import "CPGraphicsContext.j" @import "CPResponder.j" @import "CPTheme.j" +@import "CPTrackingArea.j" @import "CPWindow_Constants.j" @import "_CPDisplayServer.j" @@ -239,6 +240,8 @@ var CPViewHighDPIDrawingEnabled = YES; BOOL _allowsVibrancy @accessors(property=allowsVibrancy); CPAppearance _appearance @accessors(getter=appearance); CPAppearance _effectiveAppearance; + + CPMutableArray _trackingAreas @accessors(property=trackingAreas, readonly); } /* @@ -352,6 +355,8 @@ var CPViewHighDPIDrawingEnabled = YES; _registeredDraggedTypes = [CPSet set]; _registeredDraggedTypesArray = []; + _trackingAreas = []; + _tag = -1; _frame = CGRectMakeCopy(aFrame); @@ -799,8 +804,16 @@ var CPViewHighDPIDrawingEnabled = YES; [_window _noteUnregisteredDraggedTypes:_registeredDraggedTypes]; [aWindow _noteRegisteredDraggedTypes:_registeredDraggedTypes]; } + + // View must be removed from the current window viewsWithTrackingAreas + if (_window && ([_trackingAreas count] > 0)) + [_window _removeFromViewsWithTrackingAreas:self]; _window = aWindow; + + // View must be added to the new window viewsWithTrackingAreas + if (_window && ([_trackingAreas count] > 0)) + [_window _addToViewsWithTrackingAreas:self]; var count = [_subviews count]; @@ -3390,6 +3403,60 @@ setBoundsOrigin: } +@end + +@implementation CPView (TrackingArea) + +- (void)addTrackingArea:(CPTrackingArea)trackingArea +{ + CPLog.trace("addTrackingArea for view:"+self); + // Consistency check + if (!trackingArea || [_trackingAreas containsObject:trackingArea]) + return; + + if ([trackingArea _isReferenced]) + [CPException raise:CPInternalInconsistencyException reason:"TrackingArea is already associated with another view"]; + + CPLog.trace("addTrackingArea here we go. FYI, _window="+_window); + [_trackingAreas addObject:trackingArea]; + [trackingArea _setReferencingView:self]; + + CPLog.trace("addTrackingArea count="+[_trackingAreas count]); + + if (_window) + if ([_trackingAreas count] == 1) + [_window _addToViewsWithTrackingAreas:self]; + else + [_window _addTrackingArea:trackingArea]; +} + +- (void)removeTrackingArea:(CPTrackingArea)trackingArea +{ + // Consistency check + if (!trackingArea) + return; + + if (![_trackingAreas containsObject:trackingArea]) + [CPException raise:CPInternalInconsistencyException reason:"Trying to remove unreferenced trackingArea"]; + + if (_window) + if ([_trackingAreas count] == 1) + [_window _removeFromViewsWithTrackingAreas:self]; + else + [_window _removeTrackingArea:trackingArea]; + + [trackingArea _setReferencingView:nil]; + [_trackingAreas removeObject:trackingArea]; +} + +// Invoked automatically when the view’s geometry changes such that its tracking areas need to be recalculated. + +- (void)updateTrackingAreas +{ + // You should override this method to remove out of date tracking areas and add recomputed tracking areas; + // your implementation should call super. +} + @end var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask", @@ -3412,6 +3479,7 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask", CPViewSizeScaleKey = @"CPViewSizeScaleKey", CPViewIsScaledKey = @"CPViewIsScaledKey", CPViewAppearanceKey = @"CPViewAppearanceKey"; + CPViewTrackingAreasKey = @"CPViewTrackingAreasKey"; @implementation CPView (CPCoding) @@ -3511,6 +3579,10 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask", [self _decodeThemeObjectsWithCoder:aCoder]; [self setAppearance:[aCoder decodeObjectForKey:CPViewAppearanceKey]]; + + _trackingAreas = [aCoder decodeObjectForKey:CPViewTrackingAreasKey]; + if (!_trackingAreas) + _trackingAreas = []; [self setNeedsDisplay:YES]; [self setNeedsLayout]; @@ -3596,6 +3668,7 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask", [aCoder encodeSize:[self _hierarchyScaleSize] forKey:CPViewSizeScaleKey]; [aCoder encodeBool:_isScaled forKey:CPViewIsScaledKey]; [aCoder encodeObject:_appearance forKey:CPViewAppearanceKey]; + [aCoder encodeObject:_trackingAreas forKey:CPViewTrackingAreasKey]; } @end diff --git a/AppKit/CPWindow/CPWindow.j b/AppKit/CPWindow/CPWindow.j index 5606fcac4..7266c6c0e 100644 --- a/AppKit/CPWindow/CPWindow.j +++ b/AppKit/CPWindow/CPWindow.j @@ -36,6 +36,7 @@ @import "CPResponder.j" @import "CPScreen.j" @import "CPText.j" +@import "CPTrackingArea.j" @import "CPView.j" @import "CPWindow_Constants.j" @import "_CPBorderlessBridgeWindowView.j" @@ -198,6 +199,8 @@ var CPWindowActionMessageKeys = [ CPView _toolbarView; CPArray _mouseEnteredStack; + CPArray mouseEnteredStack; + CPArray _viewsWithTrackingAreas; CPView _leftMouseDownView; CPView _rightMouseDownView; @@ -386,6 +389,9 @@ CPTexturedBackgroundWindowMask selector:@selector(_didReceiveResizeStyleChange:) name:CPWindowResizeStyleGlobalChangeNotification object:nil]; + + _viewsWithTrackingAreas = []; + _mouseEnteredStack = []; } return self; @@ -1958,6 +1964,74 @@ CPTexturedBackgroundWindowMask case CPLeftMouseDragged: case CPRightMouseDragged: + // First, we search for any tracking area requesting CPTrackingEnabledDuringMouseDrag. + // At the same time, we update the entered stack. + mouseEnteredStack = []; + + for (var i = 0, nb = [_viewsWithTrackingAreas count], aView, trackingAreas, convertedPoint, event, trackingOptions; i < nb; i++) + { + aView = [_viewsWithTrackingAreas objectAtIndex:i]; + trackingAreas = [aView trackingAreas]; + convertedPoint = [aView convertPoint:point fromView:_windowView]; + + for (var j = 0, nbTA = [trackingAreas count], aTrackingArea; j < nbTA; j++) + { + aTrackingArea = [trackingAreas objectAtIndex:j]; + trackingOptions = [aTrackingArea options]; + + if (trackingOptions & CPTrackingEnabledDuringMouseDrag) + { + if ((trackingOptions & CPTrackingActiveAlways) || + ((trackingOptions & CPTrackingActiveInKeyWindow) && ([self isKeyWindow])) || + ((trackingOptions & CPTrackingActiveWhenFirstResponder) && ([self isKeyWindow]) && (_firstResponder == aView))) + { + if (CGRectContainsPoint(((trackingOptions & CPTrackingInVisibleRect) ? [aView visibleRect] : [aTrackingArea rect]), convertedPoint)) + { + [mouseEnteredStack addObject:aTrackingArea]; + + if (![_mouseEnteredStack containsObject:aTrackingArea]) + { + // Mouse was not in this rect so it's a mouseEntered + + if (trackingOptions & CPTrackingMouseEnteredAndExited) + { + event = [CPEvent enterExitEventWithType:CPMouseEntered location:point modifierFlags:[anEvent modifierFlags] timestamp:[anEvent timestamp] windowNumber:_windowNumber context:nil eventNumber:-1 trackingArea:aTrackingArea]; + + [[aTrackingArea owner] mouseEntered:event]; + } + } + } + } + } + } + } + + // Search now for exited views (were in _mouseEnteredStack but no more in mouseEnteredStack) + + for (var i = 0, nb = [_mouseEnteredStack count], aTrackingArea, trackingOptions; i < nb; i++) + { + aTrackingArea = [_mouseEnteredStack objectAtIndex:i]; + trackingOptions = [aTrackingArea options]; + + if (trackingOptions & CPTrackingEnabledDuringMouseDrag) + { + if (![mouseEnteredStack containsObject:aTrackingArea]) + { + // Mouse is no more in this area so it's a mouseExited + + if (trackingOptions & CPTrackingMouseEnteredAndExited) + { + event = [CPEvent enterExitEventWithType:CPMouseExited location:point modifierFlags:[anEvent modifierFlags] timestamp:[anEvent timestamp] windowNumber:_windowNumber context:nil eventNumber:-1 trackingArea:aTrackingArea]; + + [[aTrackingArea owner] mouseExited:event]; + } + } + } + } + + _mouseEnteredStack = mouseEnteredStack; + + // Normal mouseDragged workflow if (!_leftMouseDownView) return [[_windowView hitTest:point] mouseDragged:anEvent]; @@ -1981,56 +2055,73 @@ CPTexturedBackgroundWindowMask // Ignore mouse moves for parents of sheets if (!_acceptsMouseMovedEvents || sheet) return; - - if (!_mouseEnteredStack) - _mouseEnteredStack = []; - - var hitTestView = [_windowView hitTest:point]; - - if ([_mouseEnteredStack count] && [_mouseEnteredStack lastObject] === hitTestView) - return [hitTestView mouseMoved:anEvent]; - - var view = hitTestView, - mouseEnteredStack = []; - - while (view) + + mouseEnteredStack = []; + + for (var i = 0, nb = [_viewsWithTrackingAreas count], aView, trackingAreas, convertedPoint, event, trackingOptions; i < nb; i++) { - mouseEnteredStack.unshift(view); - - view = [view superview]; + aView = [_viewsWithTrackingAreas objectAtIndex:i]; + trackingAreas = [aView trackingAreas]; + convertedPoint = [aView convertPoint:point fromView:_windowView]; + + for (var j = 0, nbTA = [trackingAreas count], aTrackingArea; j < nbTA; j++) + { + aTrackingArea = [trackingAreas objectAtIndex:j]; + trackingOptions = [aTrackingArea options]; + + if ((trackingOptions & CPTrackingActiveAlways) || + ((trackingOptions & CPTrackingActiveInKeyWindow) && ([self isKeyWindow])) || + ((trackingOptions & CPTrackingActiveWhenFirstResponder) && ([self isKeyWindow]) && (_firstResponder == aView))) + { + if (CGRectContainsPoint(((trackingOptions & CPTrackingInVisibleRect) ? [aView visibleRect] : [aTrackingArea rect]), convertedPoint)) + { + [mouseEnteredStack addObject:aTrackingArea]; + + if ([_mouseEnteredStack containsObject:aTrackingArea]) + { + // Mouse was already in this rect so it's a mouseMoved + + if (trackingOptions & CPTrackingMouseMoved) + [[aTrackingArea owner] mouseMoved:anEvent]; + } + else + { + // Mouse was not in this rect so it's a mouseEntered + + if (trackingOptions & CPTrackingMouseEnteredAndExited) + { + event = [CPEvent enterExitEventWithType:CPMouseEntered location:point modifierFlags:[anEvent modifierFlags] timestamp:[anEvent timestamp] windowNumber:_windowNumber context:nil eventNumber:-1 trackingArea:aTrackingArea]; + + [[aTrackingArea owner] mouseEntered:event]; + } + } + } + } + } } - - var deviation = MIN(_mouseEnteredStack.length, mouseEnteredStack.length); - - while (deviation--) - if (_mouseEnteredStack[deviation] === mouseEnteredStack[deviation]) - break; - - var index = deviation + 1, - count = _mouseEnteredStack.length; - - if (index < count) + + // Search now for exited views (were in _mouseEnteredStack but no more in mouseEnteredStack) + + for (var i = 0, nb = [_mouseEnteredStack count], aTrackingArea; i < nb; i++) { - var event = [CPEvent mouseEventWithType:CPMouseExited location:point modifierFlags:[anEvent modifierFlags] timestamp:[anEvent timestamp] windowNumber:_windowNumber context:nil eventNumber:-1 clickCount:1 pressure:0]; - - for (; index < count; ++index) - [_mouseEnteredStack[index] mouseExited:event]; + aTrackingArea = [_mouseEnteredStack objectAtIndex:i]; + + if (![mouseEnteredStack containsObject:aTrackingArea]) + { + // Mouse is no more in this area so it's a mouseExited + + if ([aTrackingArea options] & CPTrackingMouseEnteredAndExited) + { + event = [CPEvent enterExitEventWithType:CPMouseExited location:point modifierFlags:[anEvent modifierFlags] timestamp:[anEvent timestamp] windowNumber:_windowNumber context:nil eventNumber:-1 trackingArea:aTrackingArea]; + + [[aTrackingArea owner] mouseExited:event]; + } + } } - - index = deviation + 1; - count = mouseEnteredStack.length; - - if (index < count) - { - var event = [CPEvent mouseEventWithType:CPMouseEntered location:point modifierFlags:[anEvent modifierFlags] timestamp:[anEvent timestamp] windowNumber:_windowNumber context:nil eventNumber:-1 clickCount:1 pressure:0]; - - for (; index < count; ++index) - [mouseEnteredStack[index] mouseEntered:event]; - } - + + // End of treatment + _mouseEnteredStack = mouseEnteredStack; - - [hitTestView mouseMoved:anEvent]; } } @@ -3844,6 +3935,42 @@ var interpolate = function(fromValue, toValue, progress) @end +@implementation CPWindow (TrackingArea) + +- (void)_addToViewsWithTrackingAreas:(CPView)aView +{ + [_viewsWithTrackingAreas addObject:aView]; + + for (var i = 0, tas = [aView trackingAreas], nb = [tas count]; i < nb; i++) + [self _addTrackingArea:[tas objectAtIndex:i]]; +} + +- (void)_removeFromViewsWithTrackingAreas:(CPView)aView +{ + for (var i = 0, tas = [aView trackingAreas], nb = [tas count]; i < nb; i++) + [self _removeTrackingArea:[tas objectAtIndex:i]]; + + [_viewsWithTrackingAreas removeObject:aView]; +} + +- (void)_addTrackingArea:(CPTrackingArea)trackingArea +{ + // If CPTrackingAssumeInside option is set, put the tracking area in the mouseEnteredStack + + if ([trackingArea options] & CPTrackingAssumeInside) + [_mouseEnteredStack addObject:trackingArea]; +} + +- (void)_removeTrackingArea:(CPTrackingArea)trackingArea +{ + // If mouse is in the tracking area, we remove it from the stack to avoid to fire a future mouseExited event + + if ([_mouseEnteredStack containsObject:trackingArea]) + [_mouseEnteredStack removeObject:trackingArea]; +} + +@end + function _CPWindowFullPlatformWindowSessionMake(aWindowView, aContentRect, hasShadow, aLevel) { return { windowView:aWindowView, contentRect:aContentRect, hasShadow:hasShadow, level:aLevel };