mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 04:57:03 +00:00
FIXED: CPTrackingArea incorrect handling of view hierarchy updates while processing events
This commit is contained in:
+20
-20
@@ -805,13 +805,13 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
[_window _noteUnregisteredDraggedTypes:_registeredDraggedTypes];
|
||||
[aWindow _noteRegisteredDraggedTypes:_registeredDraggedTypes];
|
||||
}
|
||||
|
||||
|
||||
// View must be removed from the current window viewsWithTrackingAreas
|
||||
if (_window && (_trackingAreas.length > 0))
|
||||
[_window _removeTrackingAreaView:self];
|
||||
|
||||
_window = aWindow;
|
||||
|
||||
|
||||
if (_window)
|
||||
{
|
||||
var owners;
|
||||
@@ -3456,16 +3456,16 @@ setBoundsOrigin:
|
||||
// Consistency check
|
||||
if (!trackingArea || [_trackingAreas containsObjectIdenticalTo:trackingArea])
|
||||
return;
|
||||
|
||||
|
||||
if ([trackingArea view])
|
||||
[CPException raise:CPInternalInconsistencyException reason:"Tracking area has already been added to another view."];
|
||||
|
||||
[_trackingAreas addObject:trackingArea];
|
||||
[trackingArea setView:self];
|
||||
|
||||
|
||||
if (_window)
|
||||
[_window _addTrackingArea:trackingArea];
|
||||
|
||||
|
||||
[trackingArea _updateWindowRect];
|
||||
}
|
||||
|
||||
@@ -3474,14 +3474,14 @@ setBoundsOrigin:
|
||||
// Consistency check
|
||||
if (!trackingArea)
|
||||
return;
|
||||
|
||||
|
||||
if (![_trackingAreas containsObjectIdenticalTo:trackingArea])
|
||||
[CPException raise:CPInternalInconsistencyException reason:"Trying to remove unreferenced trackingArea"];
|
||||
|
||||
[self _removeTrackingArea:trackingArea];
|
||||
}
|
||||
|
||||
/*!
|
||||
/*!
|
||||
Invoked automatically when the view’s geometry changes such that its tracking areas need to be recalculated.
|
||||
|
||||
You should override this method to remove out of date tracking areas and add recomputed tracking areas;
|
||||
@@ -3527,7 +3527,7 @@ setBoundsOrigin:
|
||||
{
|
||||
if (_window)
|
||||
[_window _removeTrackingArea:trackingArea];
|
||||
|
||||
|
||||
[trackingArea setView:nil];
|
||||
[_trackingAreas removeObjectIdenticalTo:trackingArea];
|
||||
}
|
||||
@@ -3535,16 +3535,16 @@ setBoundsOrigin:
|
||||
- (void)_updateTrackingAreas
|
||||
{
|
||||
_inhibitUpdateTrackingAreas = YES;
|
||||
|
||||
|
||||
[self _recursivelyUpdateTrackingAreas];
|
||||
|
||||
|
||||
_inhibitUpdateTrackingAreas = NO;
|
||||
}
|
||||
|
||||
- (void)_recursivelyUpdateTrackingAreas
|
||||
{
|
||||
[self _updateTrackingAreasForOwners:[self _calcTrackingAreaOwners]];
|
||||
|
||||
|
||||
for (var i = 0; i < _subviews.length; i++)
|
||||
[_subviews[i] _recursivelyUpdateTrackingAreas];
|
||||
}
|
||||
@@ -3554,25 +3554,25 @@ setBoundsOrigin:
|
||||
// First search all owners that must be notified
|
||||
// Remark: 99.99% of time, the only owner will be the view itself
|
||||
// In the same time, update the rects of InVisibleRect tracking areas
|
||||
|
||||
|
||||
var owners = [];
|
||||
|
||||
|
||||
for (var i = 0; i < _trackingAreas.length; i++)
|
||||
{
|
||||
var trackingArea = _trackingAreas[i];
|
||||
|
||||
|
||||
if ([trackingArea options] & CPTrackingInVisibleRect)
|
||||
[trackingArea _updateWindowRect];
|
||||
|
||||
|
||||
else
|
||||
{
|
||||
var owner = [trackingArea owner];
|
||||
|
||||
|
||||
if (![owners containsObjectIdenticalTo:owner])
|
||||
[owners addObject:owner];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return owners;
|
||||
}
|
||||
|
||||
@@ -3603,7 +3603,7 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
|
||||
CPViewScaleKey = @"CPViewScaleKey",
|
||||
CPViewSizeScaleKey = @"CPViewSizeScaleKey",
|
||||
CPViewIsScaledKey = @"CPViewIsScaledKey",
|
||||
CPViewAppearanceKey = @"CPViewAppearanceKey";
|
||||
CPViewAppearanceKey = @"CPViewAppearanceKey",
|
||||
CPViewTrackingAreasKey = @"CPViewTrackingAreasKey";
|
||||
|
||||
@implementation CPView (CPCoding)
|
||||
@@ -3633,10 +3633,10 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
|
||||
if (self)
|
||||
{
|
||||
_trackingAreas = [aCoder decodeObjectForKey:CPViewTrackingAreasKey];
|
||||
|
||||
|
||||
if (!_trackingAreas)
|
||||
_trackingAreas = [];
|
||||
|
||||
|
||||
// We have to manually check because it may be 0, so we can't use ||
|
||||
_tag = [aCoder containsValueForKey:CPViewTagKey] ? [aCoder decodeIntForKey:CPViewTagKey] : -1;
|
||||
_identifier = [aCoder decodeObjectForKey:CPReuseIdentifierKey];
|
||||
|
||||
+234
-150
@@ -198,8 +198,13 @@ var CPWindowActionMessageKeys = [
|
||||
CPView _contentView;
|
||||
CPView _toolbarView;
|
||||
|
||||
BOOL _handlingTrackingAreaEvent;
|
||||
BOOL _restartHandlingTrackingAreaEvent;
|
||||
CPArray _previousMouseEnteredStack;
|
||||
CPArray _previousCursorUpdateStack;
|
||||
CPArray _mouseEnteredStack;
|
||||
CPArray _cursorUpdateStack;
|
||||
CPArray _queuedEvents;
|
||||
CPArray _trackingAreaViews;
|
||||
id _activeCursorTrackingArea;
|
||||
CPArray _queuedTrackingEvents;
|
||||
@@ -340,9 +345,14 @@ CPTexturedBackgroundWindowMask
|
||||
|
||||
[self setLevel:CPNormalWindowLevel];
|
||||
|
||||
_handlingTrackingAreaEvent = NO;
|
||||
_restartHandlingTrackingAreaEvent = NO;
|
||||
_trackingAreaViews = [];
|
||||
_previousMouseEnteredStack = [];
|
||||
_previousCursorUpdateStack = [];
|
||||
_mouseEnteredStack = [];
|
||||
_cursorUpdateStack = [];
|
||||
_queuedEvents = [];
|
||||
_queuedTrackingEvents = [];
|
||||
_activeCursorTrackingArea = nil;
|
||||
|
||||
@@ -1980,7 +1990,7 @@ CPTexturedBackgroundWindowMask
|
||||
// First, we search for any tracking area requesting CPTrackingEnabledDuringMouseDrag.
|
||||
// At the same time, we update the entered stack.
|
||||
[self _handleTrackingAreaEvent:anEvent];
|
||||
|
||||
|
||||
// Normal mouseDragged workflow
|
||||
if (!_leftMouseDownView)
|
||||
return [[_windowView hitTest:point] mouseDragged:anEvent];
|
||||
@@ -2004,7 +2014,7 @@ CPTexturedBackgroundWindowMask
|
||||
// Ignore mouse moves for parents of sheets
|
||||
if (!_acceptsMouseMovedEvents || sheet)
|
||||
return;
|
||||
|
||||
|
||||
[self _handleTrackingAreaEvent:anEvent];
|
||||
}
|
||||
}
|
||||
@@ -3824,7 +3834,7 @@ var interpolate = function(fromValue, toValue, progress)
|
||||
- (void)_addTrackingAreaView:(CPView)aView
|
||||
{
|
||||
var trackingAreas = [aView trackingAreas];
|
||||
|
||||
|
||||
for (var i = 0; i < trackingAreas.length; i++)
|
||||
[self _addTrackingArea:trackingAreas[i]];
|
||||
}
|
||||
@@ -3832,7 +3842,7 @@ var interpolate = function(fromValue, toValue, progress)
|
||||
- (void)_removeTrackingAreaView:(CPView)aView
|
||||
{
|
||||
var trackingAreas = [aView trackingAreas];
|
||||
|
||||
|
||||
for (var i = 0; i < trackingAreas.length; i++)
|
||||
[self _removeTrackingArea:trackingAreas[i]];
|
||||
}
|
||||
@@ -3840,63 +3850,141 @@ var interpolate = function(fromValue, toValue, progress)
|
||||
- (void)_addTrackingArea:(CPTrackingArea)trackingArea
|
||||
{
|
||||
var trackingAreaView = [trackingArea view];
|
||||
|
||||
|
||||
if (![_trackingAreaViews containsObjectIdenticalTo:trackingAreaView])
|
||||
[_trackingAreaViews addObject:trackingAreaView];
|
||||
|
||||
// If CPTrackingAssumeInside option is set, put the tracking area in the _mouseEnteredStack
|
||||
|
||||
if ([trackingArea options] & CPTrackingAssumeInside)
|
||||
[_mouseEnteredStack addObject:trackingArea];
|
||||
|
||||
// If CPTrackingAssumeInside option is set, insert the tracking area in the events management system
|
||||
// in order to have the first event sent only when mouse leaves the tracking area
|
||||
|
||||
[self _insertTrackingArea:trackingArea assumeInside:([trackingArea options] & CPTrackingAssumeInside)];
|
||||
}
|
||||
|
||||
- (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
|
||||
|
||||
[_mouseEnteredStack removeObjectIdenticalTo:trackingArea];
|
||||
|
||||
|
||||
[self _purgeTrackingArea:trackingArea];
|
||||
|
||||
var trackingAreaView = [trackingArea view];
|
||||
|
||||
|
||||
[_trackingAreaViews removeObjectIdenticalTo:trackingAreaView];
|
||||
}
|
||||
|
||||
- (void)_insertTrackingArea:(CPTrackingArea)trackingArea assumeInside:(BOOL)assumeInside
|
||||
{
|
||||
if (_handlingTrackingAreaEvent)
|
||||
_restartHandlingTrackingAreaEvent = YES;
|
||||
|
||||
if (assumeInside)
|
||||
if (_handlingTrackingAreaEvent)
|
||||
[_mouseEnteredStack addObject:trackingArea];
|
||||
else
|
||||
[_previousMouseEnteredStack addObject:trackingArea];
|
||||
}
|
||||
|
||||
- (void)_purgeTrackingArea:(CPTrackingArea)trackingArea
|
||||
{
|
||||
if (_handlingTrackingAreaEvent)
|
||||
{
|
||||
[_mouseEnteredStack removeObjectIdenticalTo:trackingArea];
|
||||
|
||||
var i = _queuedEvents.length;
|
||||
|
||||
while (i--)
|
||||
if ([_queuedEvents[i] trackingArea] === trackingArea)
|
||||
[_queuedEvents removeObjectAtIndex:i];
|
||||
|
||||
_cursorUpdateStack = [];
|
||||
_activeCursorTrackingArea = nil;
|
||||
}
|
||||
else
|
||||
{
|
||||
[_previousMouseEnteredStack removeObjectIdenticalTo:trackingArea];
|
||||
[_previousCursorUpdateStack removeObjectIdenticalTo:trackingArea];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_handleTrackingAreaEvent:(CPEvent)anEvent
|
||||
{
|
||||
var mouseEnteredStack = [],
|
||||
cursorUpdateStack = [],
|
||||
point = [anEvent locationInWindow],
|
||||
dragging = ([anEvent type] !== CPMouseMoved);
|
||||
_handlingTrackingAreaEvent = YES;
|
||||
|
||||
// Handle mouse entering tracking areas (and calc mouseEnteredStack and cursorUpdateStack)
|
||||
|
||||
[self _handleMouseMovedAndEnteredEventsForEvent:anEvent atPoint:point dragging:dragging mouseEnteredStack:mouseEnteredStack cursorUpdateStack:cursorUpdateStack];
|
||||
var point = [anEvent locationInWindow],
|
||||
dragging = ([anEvent type] !== CPMouseMoved);
|
||||
|
||||
// Handle mouse exiting tracking areas
|
||||
|
||||
[self _handleMouseExitedEventsForEvent:anEvent atPoint:point dragging:dragging mouseEnteredStack:mouseEnteredStack];
|
||||
|
||||
// Cursor update
|
||||
|
||||
if (cursorUpdateStack.length > 0)
|
||||
do
|
||||
{
|
||||
[self _handleCursorUpdateEventsForEvent:anEvent atPoint:point dragging:dragging cursorUpdateStack:cursorUpdateStack];
|
||||
}
|
||||
else if (!dragging)
|
||||
{
|
||||
// Here, we are outsite the window content view tracking area, so let _windowView set the cursor (resize cursor, ...)
|
||||
// Initialize this run
|
||||
_restartHandlingTrackingAreaEvent = NO;
|
||||
|
||||
[_windowView setCursorForLocation:point resizing:NO];
|
||||
_activeCursorTrackingArea = nil;
|
||||
}
|
||||
_mouseEnteredStack = [];
|
||||
_cursorUpdateStack = [];
|
||||
|
||||
// Prepare for next call
|
||||
|
||||
_mouseEnteredStack = mouseEnteredStack;
|
||||
_cursorUpdateStack = cursorUpdateStack;
|
||||
// Important remark: we must queue events to avoid running conditions when a view uses a mouse event to modify view hierarchy
|
||||
|
||||
_queuedEvents = [];
|
||||
|
||||
// Handle mouse entering tracking areas (and calc _mouseEnteredStack and _cursorUpdateStack)
|
||||
|
||||
[self _handleMouseMovedAndEnteredEventsForEvent:anEvent atPoint:point dragging:dragging];
|
||||
|
||||
// Handle mouse exiting tracking areas
|
||||
|
||||
[self _handleMouseExitedEventsForEvent:anEvent atPoint:point dragging:dragging];
|
||||
|
||||
// Cursor update
|
||||
|
||||
if (_cursorUpdateStack.length > 0)
|
||||
{
|
||||
[self _handleCursorUpdateEventsForEvent:anEvent atPoint:point dragging:dragging];
|
||||
}
|
||||
else if (!dragging)
|
||||
{
|
||||
// Here, we are outside the window content view tracking area, so let _windowView set the cursor (resize cursor, ...)
|
||||
|
||||
[_windowView setCursorForLocation:point resizing:NO];
|
||||
_activeCursorTrackingArea = nil;
|
||||
}
|
||||
|
||||
// Send all queued events
|
||||
// Important remark : as an event can modify the view hierarchy, the events queue could be modified while processing it
|
||||
|
||||
while (_queuedEvents.length > 0)
|
||||
{
|
||||
var queuedEvent = _queuedEvents[0],
|
||||
trackingArea = [queuedEvent trackingArea],
|
||||
trackingOwner = [trackingArea owner];
|
||||
|
||||
switch ([queuedEvent type])
|
||||
{
|
||||
case CPMouseEntered:
|
||||
[trackingOwner mouseEntered:queuedEvent];
|
||||
break;
|
||||
|
||||
case CPMouseExited:
|
||||
[trackingOwner mouseExited:queuedEvent];
|
||||
break;
|
||||
|
||||
case CPCursorUpdate:
|
||||
[trackingOwner cursorUpdate:queuedEvent];
|
||||
break;
|
||||
}
|
||||
|
||||
if (queuedEvent === _queuedEvents[0])
|
||||
[_queuedEvents removeObjectAtIndex:0];
|
||||
}
|
||||
|
||||
// Prepare for next call
|
||||
|
||||
_previousMouseEnteredStack = _mouseEnteredStack;
|
||||
_previousCursorUpdateStack = _cursorUpdateStack;
|
||||
}
|
||||
while (_restartHandlingTrackingAreaEvent)
|
||||
|
||||
_handlingTrackingAreaEvent = NO;
|
||||
}
|
||||
|
||||
- (void)_handleMouseMovedAndEnteredEventsForEvent:(CPEvent)anEvent atPoint:(CGPoint)point dragging:(BOOL)dragging mouseEnteredStack:(CPArray)mouseEnteredStack cursorUpdateStack:(CPArray)cursorUpdateStack
|
||||
- (void)_handleMouseMovedAndEnteredEventsForEvent:(CPEvent)anEvent atPoint:(CGPoint)point dragging:(BOOL)dragging
|
||||
{
|
||||
var isKeyWindow = [self isKeyWindow];
|
||||
|
||||
@@ -3923,9 +4011,9 @@ var interpolate = function(fromValue, toValue, progress)
|
||||
continue;
|
||||
}
|
||||
|
||||
[mouseEnteredStack addObject:aTrackingArea];
|
||||
[_mouseEnteredStack addObject:aTrackingArea];
|
||||
|
||||
if ([_mouseEnteredStack containsObjectIdenticalTo:aTrackingArea])
|
||||
if ([_previousMouseEnteredStack containsObjectIdenticalTo:aTrackingArea])
|
||||
{
|
||||
// Mouse was already in this rect so it's a mouseMoved
|
||||
|
||||
@@ -3946,32 +4034,33 @@ var interpolate = function(fromValue, toValue, progress)
|
||||
if (dragging && !(trackingOptions & CPTrackingEnabledDuringMouseDrag))
|
||||
[self _queueTrackingEvent:mouseEnteredEvent];
|
||||
else
|
||||
[[aTrackingArea owner] mouseEntered:mouseEnteredEvent];
|
||||
[_queuedEvents addObject:mouseEnteredEvent];
|
||||
}
|
||||
|
||||
if ((trackingOptions & CPTrackingCursorUpdate) && (trackingImplementedMethods & CPTrackingOwnerImplementsCursorUpdate))
|
||||
[cursorUpdateStack addObject:aTrackingArea];
|
||||
[_cursorUpdateStack addObject:aTrackingArea];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_handleMouseExitedEventsForEvent:(CPEvent)anEvent atPoint:(CGPoint)point dragging:(BOOL)dragging mouseEnteredStack:(CPArray)mouseEnteredStack
|
||||
- (void)_handleMouseExitedEventsForEvent:(CPEvent)anEvent atPoint:(CGPoint)point dragging:(BOOL)dragging
|
||||
{
|
||||
// Search for exited views (were in _mouseEnteredStack but no more in mouseEnteredStack)
|
||||
// Search for exited views (were in _previousMouseEnteredStack but no more in _mouseEnteredStack)
|
||||
|
||||
for (var i = 0; i < _mouseEnteredStack.length; i++)
|
||||
for (var i = 0; i < _previousMouseEnteredStack.length; i++)
|
||||
{
|
||||
var aTrackingArea = _mouseEnteredStack[i],
|
||||
var aTrackingArea = _previousMouseEnteredStack[i],
|
||||
trackingOptions = [aTrackingArea options];
|
||||
|
||||
if ([mouseEnteredStack containsObjectIdenticalTo:aTrackingArea])
|
||||
if ([_mouseEnteredStack containsObjectIdenticalTo:aTrackingArea])
|
||||
continue;
|
||||
|
||||
// Mouse is no more in this area so it's a mouseExited
|
||||
|
||||
if ((trackingOptions & CPTrackingMouseEnteredAndExited) && ([aTrackingArea implementedOwnerMethods] & CPTrackingOwnerImplementsMouseExited))
|
||||
{
|
||||
var mouseExitedEvent = [CPEvent enterExitEventWithType:CPMouseExited
|
||||
var theView = [aTrackingArea owner],
|
||||
mouseExitedEvent = [CPEvent enterExitEventWithType:CPMouseExited
|
||||
location:point
|
||||
modifierFlags:[anEvent modifierFlags]
|
||||
timestamp:[anEvent timestamp]
|
||||
@@ -3983,132 +4072,127 @@ var interpolate = function(fromValue, toValue, progress)
|
||||
if (dragging && !(trackingOptions & CPTrackingEnabledDuringMouseDrag))
|
||||
[self _queueTrackingEvent:mouseExitedEvent];
|
||||
else
|
||||
[[aTrackingArea owner] mouseExited:mouseExitedEvent];
|
||||
[_queuedEvents addObject:mouseExitedEvent];
|
||||
}
|
||||
|
||||
// If this is the active cursor area, we reset _cursorUpdateStack so a new active area will be computed
|
||||
// If this is the active cursor area, we reset _previousCursorUpdateStack so a new active area will be computed
|
||||
|
||||
if (aTrackingArea === _activeCursorTrackingArea)
|
||||
{
|
||||
_cursorUpdateStack = [];
|
||||
_previousCursorUpdateStack = [];
|
||||
_activeCursorTrackingArea = nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_handleCursorUpdateEventsForEvent:(CPEvent)anEvent atPoint:(CGPoint)point dragging:(BOOL)dragging cursorUpdateStack:(CPArray)cursorUpdateStack
|
||||
- (void)_handleCursorUpdateEventsForEvent:(CPEvent)anEvent atPoint:(CGPoint)point dragging:(BOOL)dragging
|
||||
{
|
||||
var overlappingTrackingAreas = [];
|
||||
|
||||
for (var i = 0; i < cursorUpdateStack.length; i++)
|
||||
for (var i = 0; i < _cursorUpdateStack.length; i++)
|
||||
{
|
||||
var aTrackingArea = cursorUpdateStack[i];
|
||||
var aTrackingArea = _cursorUpdateStack[i];
|
||||
|
||||
if ((![_cursorUpdateStack containsObjectIdenticalTo:aTrackingArea]) || (aTrackingArea === _activeCursorTrackingArea))
|
||||
if ((![_previousCursorUpdateStack containsObjectIdenticalTo:aTrackingArea]) || (aTrackingArea === _activeCursorTrackingArea))
|
||||
[overlappingTrackingAreas addObject:aTrackingArea];
|
||||
}
|
||||
|
||||
var nbOverlappingTrackingAreas = overlappingTrackingAreas.length;
|
||||
var frontmostTrackingArea = overlappingTrackingAreas[0],
|
||||
frontmostView = [frontmostTrackingArea view];
|
||||
|
||||
if (nbOverlappingTrackingAreas > 0)
|
||||
for (var i = 1; i < overlappingTrackingAreas.length; i++)
|
||||
{
|
||||
var frontmostTrackingArea = overlappingTrackingAreas[0],
|
||||
frontmostView = [frontmostTrackingArea view];
|
||||
var aTrackingArea = overlappingTrackingAreas[i],
|
||||
aView = [aTrackingArea view];
|
||||
|
||||
for (var i = 1; i < nbOverlappingTrackingAreas; i++)
|
||||
// First, if aView is _windowView, skip to next overlapping tracking area
|
||||
// as _windowView can't be the frontmost view if there's multiple overlapping tracking areas.
|
||||
|
||||
if (aView === _windowView)
|
||||
continue;
|
||||
|
||||
// Then, if frontmostView is _windowView, aView must become frontmostView
|
||||
|
||||
if (frontmostView === _windowView)
|
||||
{
|
||||
var aTrackingArea = overlappingTrackingAreas[i],
|
||||
aView = [aTrackingArea view];
|
||||
frontmostTrackingArea = aTrackingArea;
|
||||
frontmostView = aView;
|
||||
|
||||
// First, if aView is _windowView, skip to next overlapping tracking area
|
||||
// as _windowView can't be the frontmost view if there's multiple overlapping tracking areas.
|
||||
|
||||
if (aView === _windowView)
|
||||
continue;
|
||||
|
||||
// Then, if frontmostView is _windowView, aView must become frontmostView
|
||||
|
||||
if (frontmostView === _windowView)
|
||||
{
|
||||
frontmostTrackingArea = aTrackingArea;
|
||||
frontmostView = aView;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Next verify if aView is a subview of frontmostView
|
||||
// If so, it's our new frontmost view
|
||||
|
||||
var searchingView = aView;
|
||||
|
||||
while ((searchingView !== _contentView) && ([searchingView superview] !== frontmostView))
|
||||
searchingView = [searchingView superview];
|
||||
|
||||
if (searchingView !== _contentView)
|
||||
{
|
||||
frontmostTrackingArea = aTrackingArea;
|
||||
frontmostView = aView;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// aView is not a subview of frontmostView
|
||||
// Search in view hierarchy which one will be over the other
|
||||
// (this is done by comparing their draw order)
|
||||
|
||||
var firstView = frontmostView,
|
||||
firstSuperview = [firstView superview];
|
||||
|
||||
while (firstView !== _contentView)
|
||||
{
|
||||
var secondView = aView,
|
||||
secondSuperview = [secondView superview];
|
||||
|
||||
while ((secondSuperview !== _contentView) && (firstSuperview !== secondSuperview))
|
||||
{
|
||||
secondView = secondSuperview;
|
||||
secondSuperview = [secondView superview];
|
||||
}
|
||||
|
||||
if (firstSuperview === secondSuperview)
|
||||
break;
|
||||
|
||||
firstView = firstSuperview;
|
||||
firstSuperview = [firstView superview];
|
||||
}
|
||||
|
||||
if (firstSuperview !== secondSuperview)
|
||||
[CPException raise:CPInternalInconsistencyException reason:"Problem with view hierarchy"];
|
||||
|
||||
var firstSuperviewSubviews = [firstSuperview subviews],
|
||||
firstViewIndex = [firstSuperviewSubviews indexOfObject:firstView],
|
||||
secondViewIndex = [firstSuperviewSubviews indexOfObject:secondView];
|
||||
|
||||
if (secondViewIndex > firstViewIndex)
|
||||
{
|
||||
frontmostTrackingArea = aTrackingArea;
|
||||
frontmostView = aView;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (frontmostTrackingArea !== _activeCursorTrackingArea)
|
||||
// Next verify if aView is a subview of frontmostView
|
||||
// If so, it's our new frontmost view
|
||||
|
||||
var searchingView = aView;
|
||||
|
||||
while ((searchingView !== _contentView) && ([searchingView superview] !== frontmostView))
|
||||
searchingView = [searchingView superview];
|
||||
|
||||
if (searchingView !== _contentView)
|
||||
{
|
||||
var cursorUpdateEvent = [CPEvent enterExitEventWithType:CPCursorUpdate
|
||||
location:point
|
||||
modifierFlags:[anEvent modifierFlags]
|
||||
timestamp:[anEvent timestamp]
|
||||
windowNumber:_windowNumber
|
||||
context:nil
|
||||
eventNumber:-1
|
||||
trackingArea:frontmostTrackingArea];
|
||||
frontmostTrackingArea = aTrackingArea;
|
||||
frontmostView = aView;
|
||||
|
||||
if (dragging)
|
||||
[self _queueTrackingEvent:cursorUpdateEvent];
|
||||
else
|
||||
[[frontmostTrackingArea owner] cursorUpdate:cursorUpdateEvent];
|
||||
|
||||
_activeCursorTrackingArea = frontmostTrackingArea;
|
||||
continue;
|
||||
}
|
||||
|
||||
// aView is not a subview of frontmostView
|
||||
// Search in view hierarchy which one will be over the other
|
||||
// (this is done by comparing their draw order)
|
||||
|
||||
var firstView = frontmostView,
|
||||
firstSuperview = [firstView superview];
|
||||
|
||||
while (firstView !== _contentView)
|
||||
{
|
||||
var secondView = aView,
|
||||
secondSuperview = [secondView superview];
|
||||
|
||||
while ((secondSuperview !== _contentView) && (firstSuperview !== secondSuperview))
|
||||
{
|
||||
secondView = secondSuperview;
|
||||
secondSuperview = [secondView superview];
|
||||
}
|
||||
|
||||
if (firstSuperview === secondSuperview)
|
||||
break;
|
||||
|
||||
firstView = firstSuperview;
|
||||
firstSuperview = [firstView superview];
|
||||
}
|
||||
|
||||
if (firstSuperview !== secondSuperview)
|
||||
[CPException raise:CPInternalInconsistencyException reason:"Problem with view hierarchy"];
|
||||
|
||||
var firstSuperviewSubviews = [firstSuperview subviews],
|
||||
firstViewIndex = [firstSuperviewSubviews indexOfObject:firstView],
|
||||
secondViewIndex = [firstSuperviewSubviews indexOfObject:secondView];
|
||||
|
||||
if (secondViewIndex > firstViewIndex)
|
||||
{
|
||||
frontmostTrackingArea = aTrackingArea;
|
||||
frontmostView = aView;
|
||||
}
|
||||
}
|
||||
|
||||
if (frontmostTrackingArea !== _activeCursorTrackingArea)
|
||||
{
|
||||
var cursorUpdateEvent = [CPEvent enterExitEventWithType:CPCursorUpdate
|
||||
location:point
|
||||
modifierFlags:[anEvent modifierFlags]
|
||||
timestamp:[anEvent timestamp]
|
||||
windowNumber:_windowNumber
|
||||
context:nil
|
||||
eventNumber:-1
|
||||
trackingArea:frontmostTrackingArea];
|
||||
|
||||
if (dragging)
|
||||
[self _queueTrackingEvent:cursorUpdateEvent];
|
||||
else
|
||||
[_queuedEvents addObject:cursorUpdateEvent];
|
||||
|
||||
_activeCursorTrackingArea = frontmostTrackingArea;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4181,7 +4265,7 @@ var interpolate = function(fromValue, toValue, progress)
|
||||
case CPMouseExited:
|
||||
[trackingOwner mouseExited:queuedEvent];
|
||||
break;
|
||||
|
||||
|
||||
case CPCursorUpdate:
|
||||
[trackingOwner updateTrackingAreas];
|
||||
|
||||
|
||||
@@ -1360,6 +1360,144 @@ var updateTrackingAreasCalls,
|
||||
|
||||
}
|
||||
|
||||
- (void)testTrackingAreasLiveViewHierarchyModification
|
||||
{
|
||||
// 1. viewB inside viewA with mouseEntered removing itself
|
||||
|
||||
var viewA = [[CPTrackingAreaViewWithCursorUpdate alloc] initWithFrame:CGRectMake(20, 20, 40, 40)];
|
||||
var viewB = [[CPTrackingAreaViewLiveRemoval alloc] initWithFrame:CGRectMake(10, 10, 20, 20)];
|
||||
|
||||
[[window contentView] setSubviews:[CPArray arrayWithObject:viewA]];
|
||||
[viewA addSubview:viewB];
|
||||
|
||||
var options = CPTrackingMouseEnteredAndExited | CPTrackingCursorUpdate | CPTrackingActiveInActiveApp | CPTrackingInVisibleRect;
|
||||
|
||||
var viewATrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options owner:viewA userInfo:nil];
|
||||
var viewBTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options owner:viewB userInfo:nil];
|
||||
|
||||
[viewA addTrackingArea:viewATrackingArea];
|
||||
[viewB addTrackingArea:viewBTrackingArea];
|
||||
|
||||
// Step 1.1 : enter viewA
|
||||
|
||||
[self moveMouseAtPoint:CGPointMake(25, 25) dragging:NO];
|
||||
|
||||
[self assert:1 equals:mouseEnteredCalls message:@"1.1 There should be one and only one mouseEntered call"];
|
||||
[self assert:0 equals:mouseExitedCalls message:@"1.1 There should be no mouseExited call"];
|
||||
[self assert:0 equals:mouseMovedCalls message:@"1.1 There should be no mouseMoved call"];
|
||||
[self assert:1 equals:cursorUpdateCalls message:@"1.1 There should be one and only one cursorUpdate call"];
|
||||
|
||||
[self assert:viewA equals:involvedViewForMouseEntered message:@"1.1 viewA should receive mouseEntered"];
|
||||
[self assert:viewA equals:involvedViewForCursorUpdate message:@"1.1 viewA should receive cursorUpdate"];
|
||||
|
||||
// Step 1.2 : enter viewB
|
||||
|
||||
[self moveMouseAtPoint:CGPointMake(40, 40) dragging:NO];
|
||||
|
||||
[self assert:1 equals:mouseEnteredCalls message:@"1.2 There should be one and only one mouseEntered call"];
|
||||
[self assert:0 equals:mouseExitedCalls message:@"1.2 There should be no mouseExited call"];
|
||||
[self assert:0 equals:mouseMovedCalls message:@"1.2 There should be no mouseMoved call"];
|
||||
[self assert:0 equals:cursorUpdateCalls message:@"1.2 There should be no cursorUpdate call"];
|
||||
|
||||
[self assert:viewB equals:involvedViewForMouseEntered message:@"1.2 viewB should receive mouseEntered"];
|
||||
|
||||
// Step 1.3 : move back to viewA (there should be no more viewB)
|
||||
|
||||
[self moveMouseAtPoint:CGPointMake(25, 25) dragging:NO];
|
||||
|
||||
[self assert:0 equals:mouseEnteredCalls message:@"1.3 There should be no mouseEntered call"];
|
||||
[self assert:0 equals:mouseExitedCalls message:@"1.3 There should be no mouseExited call"];
|
||||
[self assert:0 equals:mouseMovedCalls message:@"1.3 There should be no mouseMoved call"];
|
||||
[self assert:1 equals:cursorUpdateCalls message:@"1.3 There should be one and only one cursorUpdate call"];
|
||||
|
||||
[self assert:viewA equals:involvedViewForCursorUpdate message:@"1.3 viewA should receive cursorUpdate"];
|
||||
|
||||
// Step 1.4 : exit viewA
|
||||
|
||||
[self moveMouseAtPoint:CGPointMake(5, 5) dragging:NO];
|
||||
|
||||
[self assert:0 equals:mouseEnteredCalls message:@"1.4 There should be one and only one mouseEntered call"];
|
||||
[self assert:1 equals:mouseExitedCalls message:@"1.4 There should be one and only on mouseExited call"];
|
||||
[self assert:0 equals:mouseMovedCalls message:@"1.4 There should be no mouseMoved call"];
|
||||
[self assert:0 equals:cursorUpdateCalls message:@"1.4 There should be no cursorUpdate call"];
|
||||
|
||||
[self assert:viewA equals:involvedViewForMouseExited message:@"1.4 viewA should receive mouseExited"];
|
||||
|
||||
// 2. viewA with mouseEntered adding viewB inside it. Testing if viewB receive mouseEntered & cursorUpdate
|
||||
|
||||
var viewA = [[CPTrackingAreaViewLiveAddition alloc] initWithFrame:CGRectMake(20, 20, 40, 40)];
|
||||
|
||||
[[window contentView] setSubviews:[CPArray arrayWithObject:viewA]];
|
||||
|
||||
var options = CPTrackingMouseEnteredAndExited | CPTrackingCursorUpdate | CPTrackingActiveInActiveApp | CPTrackingInVisibleRect;
|
||||
|
||||
var viewATrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options owner:viewA userInfo:nil];
|
||||
|
||||
[viewA addTrackingArea:viewATrackingArea];
|
||||
|
||||
// Step 2.1 : enter viewA (then add viewB thus enter also viewB)
|
||||
|
||||
[self moveMouseAtPoint:CGPointMake(25, 25) dragging:NO];
|
||||
|
||||
[self assert:2 equals:mouseEnteredCalls message:@"2.1 There should be two mouseEntered calls"];
|
||||
[self assert:0 equals:mouseExitedCalls message:@"2.1 There should be no mouseExited call"];
|
||||
[self assert:0 equals:mouseMovedCalls message:@"2.1 There should be no mouseMoved call"];
|
||||
[self assert:2 equals:cursorUpdateCalls message:@"2.1 There should be two cursorUpdate calls"];
|
||||
|
||||
[self assert:[[viewA subviews] firstObject] equals:involvedViewForMouseEntered message:@"2.1 viewB should receive mouseEntered"];
|
||||
[self assert:[[viewA subviews] firstObject] equals:involvedViewForCursorUpdate message:@"2.1 viewB should receive cursorUpdate"];
|
||||
[self assert:[CPCursor crosshairCursor] equals:[CPCursor currentCursor] message:@"2.1 Final cursor should be crosshair cursor, determined by viewB"];
|
||||
|
||||
// Step 2.2 : exit viewA (thus also viewB)
|
||||
|
||||
[self moveMouseAtPoint:CGPointMake(5, 5) dragging:NO];
|
||||
|
||||
[self assert:0 equals:mouseEnteredCalls message:@"2.2 There should be no mouseEntered calls"];
|
||||
[self assert:2 equals:mouseExitedCalls message:@"2.2 There should be two mouseExited call"];
|
||||
[self assert:0 equals:mouseMovedCalls message:@"2.2 There should be no mouseMoved call"];
|
||||
[self assert:0 equals:cursorUpdateCalls message:@"2.2 There should be no cursorUpdate calls"];
|
||||
|
||||
[self assert:[[viewA subviews] firstObject] equals:involvedViewForMouseExited message:@"2.2 viewB should receive mouseExited"];
|
||||
[self assert:[CPCursor arrowCursor] equals:[CPCursor currentCursor] message:@"2.2 Cursor should be an arrow"];
|
||||
|
||||
// 3. viewA with mouseEntered adding viewB inside it BUT with CPTrackingAssumeInside. Testing if viewB receive only cursorUpdate
|
||||
|
||||
var viewA = [[CPTrackingAreaViewLiveAddition2 alloc] initWithFrame:CGRectMake(20, 20, 40, 40)];
|
||||
|
||||
[[window contentView] setSubviews:[CPArray arrayWithObject:viewA]];
|
||||
|
||||
var options = CPTrackingMouseEnteredAndExited | CPTrackingCursorUpdate | CPTrackingActiveInActiveApp | CPTrackingInVisibleRect;
|
||||
|
||||
var viewATrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options owner:viewA userInfo:nil];
|
||||
|
||||
[viewA addTrackingArea:viewATrackingArea];
|
||||
|
||||
// Step 3.1 : enter viewA (then add viewB thus enter also viewB)
|
||||
|
||||
[self moveMouseAtPoint:CGPointMake(25, 25) dragging:NO];
|
||||
|
||||
[self assert:1 equals:mouseEnteredCalls message:@"3.1 There should be two mouseEntered calls"];
|
||||
[self assert:0 equals:mouseExitedCalls message:@"3.1 There should be no mouseExited call"];
|
||||
[self assert:0 equals:mouseMovedCalls message:@"3.1 There should be no mouseMoved call"];
|
||||
[self assert:2 equals:cursorUpdateCalls message:@"3.1 There should be two cursorUpdate calls"];
|
||||
|
||||
[self assert:viewA equals:involvedViewForMouseEntered message:@"3.1 viewB should receive mouseEntered"];
|
||||
[self assert:[[viewA subviews] firstObject] equals:involvedViewForCursorUpdate message:@"3.1 viewB should receive cursorUpdate"];
|
||||
[self assert:[CPCursor crosshairCursor] equals:[CPCursor currentCursor] message:@"3.1 Final cursor should be crosshair cursor, determined by viewB"];
|
||||
|
||||
// Step 3.2 : exit viewA (thus also viewB)
|
||||
|
||||
[self moveMouseAtPoint:CGPointMake(5, 5) dragging:NO];
|
||||
|
||||
[self assert:0 equals:mouseEnteredCalls message:@"3.2 There should be no mouseEntered calls"];
|
||||
[self assert:2 equals:mouseExitedCalls message:@"3.2 There should be two mouseExited call"];
|
||||
[self assert:0 equals:mouseMovedCalls message:@"3.2 There should be no mouseMoved call"];
|
||||
[self assert:0 equals:cursorUpdateCalls message:@"3.2 There should be no cursorUpdate calls"];
|
||||
|
||||
[self assert:[[viewA subviews] firstObject] equals:involvedViewForMouseExited message:@"3.2 viewB should receive mouseExited"];
|
||||
[self assert:[CPCursor arrowCursor] equals:[CPCursor currentCursor] message:@"3.2 Cursor should be an arrow"];
|
||||
}
|
||||
|
||||
- (void)updateTrackingAreas
|
||||
{
|
||||
updateTrackingAreasCalls++;
|
||||
@@ -1472,6 +1610,77 @@ var updateTrackingAreasCalls,
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPTrackingAreaViewLiveRemoval : CPTrackingAreaView
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
- (void)cursorUpdate:(CPEvent)anEvent
|
||||
{
|
||||
[[CPCursor pointingHandCursor] set];
|
||||
|
||||
[super cursorUpdate:anEvent];
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(CPEvent)anEvent
|
||||
{
|
||||
[self removeFromSuperview];
|
||||
|
||||
[super mouseEntered:anEvent];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPTrackingAreaViewLiveAddition : CPTrackingAreaView
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
- (void)cursorUpdate:(CPEvent)anEvent
|
||||
{
|
||||
[[CPCursor pointingHandCursor] set];
|
||||
|
||||
[super cursorUpdate:anEvent];
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(CPEvent)anEvent
|
||||
{
|
||||
var viewB = [[CPTrackingAreaViewWithCursorUpdate alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
|
||||
|
||||
[self addSubview:viewB];
|
||||
|
||||
[viewB addTrackingArea:[[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:CPTrackingMouseEnteredAndExited | CPTrackingCursorUpdate | CPTrackingActiveInActiveApp | CPTrackingInVisibleRect owner:viewB userInfo:nil]];
|
||||
|
||||
[super mouseEntered:anEvent];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPTrackingAreaViewLiveAddition2 : CPTrackingAreaView
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
- (void)cursorUpdate:(CPEvent)anEvent
|
||||
{
|
||||
[[CPCursor pointingHandCursor] set];
|
||||
|
||||
[super cursorUpdate:anEvent];
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(CPEvent)anEvent
|
||||
{
|
||||
var viewB = [[CPTrackingAreaViewWithCursorUpdate alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
|
||||
|
||||
[self addSubview:viewB];
|
||||
|
||||
[viewB addTrackingArea:[[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:CPTrackingMouseEnteredAndExited | CPTrackingCursorUpdate | CPTrackingActiveInActiveApp | CPTrackingInVisibleRect | CPTrackingAssumeInside owner:viewB userInfo:nil]];
|
||||
|
||||
[super mouseEntered:anEvent];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPLayoutView : CPView
|
||||
{
|
||||
|
||||
|
||||
Reference in New Issue
Block a user