Files
twui/lib/UIKit/TUINSView.m
2012-10-31 11:44:34 -07:00

862 lines
23 KiB
Objective-C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
Copyright 2011 Twitter, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this work except in compliance with the License.
You may obtain a copy of the License in the LICENSE file, or at:
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Portions of this code were taken from Velvet,
// which is copyright (c) 2012 Bitswift, Inc.
// See LICENSE.txt for more information.
#import "TUINSView.h"
#import "CALayer+TUIExtensions.h"
#import "TUIBridgedScrollView.h"
#import "TUINSView+Hyperfocus.h"
#import "TUINSView+Private.h"
#import "TUIViewNSViewContainer.h"
#import "TUITooltipWindow.h"
// If enabled, NSViews contained within TUIViewNSViewContainers will be clipped
// by any TwUI ancestors that enable clipping to bounds.
//
// This should really only be disabled for debugging.
#define ENABLE_NSVIEW_CLIPPING 1
static NSComparisonResult compareNSViewOrdering (NSView *viewA, NSView *viewB, void *context) {
TUIViewNSViewContainer *hostA = viewA.hostView;
TUIViewNSViewContainer *hostB = viewB.hostView;
// hosted NSViews should be on top of everything else
if (!hostA) {
if (!hostB) {
return NSOrderedSame;
} else {
return NSOrderedAscending;
}
} else if (!hostB) {
return NSOrderedDescending;
}
TUIView *ancestor = [hostA ancestorSharedWithView:(TUIView *)hostB];
NSCAssert2(ancestor, @"TwUI-hosted NSViews in the same TUINSView should share a TwUI ancestor: %@, %@", viewA, viewB);
__block NSInteger orderA = -1;
__block NSInteger orderB = -1;
[ancestor.subviews enumerateObjectsUsingBlock:^(TUIView *subview, NSUInteger index, BOOL *stop){
if ([hostA isDescendantOfView:subview]) {
orderA = (NSInteger)index;
} else if ([hostB isDescendantOfView:subview]) {
orderB = (NSInteger)index;
}
if (orderA >= 0 && orderB >= 0) {
*stop = YES;
}
}];
if (orderA < orderB) {
return NSOrderedAscending;
} else if (orderA > orderB) {
return NSOrderedDescending;
} else {
return NSOrderedSame;
}
}
@interface TUINSView ()
- (void)recalculateNSViewClipping;
- (void)recalculateNSViewOrdering;
/*
* A layer used to mask the rendering of NSView-owned layers added to the
* receiver.
*
* This masking will keep the rendering of a given NSView consistent with the
* clipping its TUIViewNSViewContainer would have in the TwUI hierarchy.
*/
@property (nonatomic, strong) CAShapeLayer *maskLayer;
/*
* Returns any existing AppKit-created focus ring layer for the given view, or
* nil if one could not be found.
*/
- (CALayer *)focusRingLayerForView:(NSView *)view;
/*
* Configures all the necessary properties on the receiver. This is outside of
* an initializer because NSView has no true designated initializer.
*/
- (void)setUp;
- (void)windowDidResignKey:(NSNotification *)notification;
- (void)windowDidBecomeKey:(NSNotification *)notification;
- (void)screenProfileOrBackingPropertiesDidChange:(NSNotification *)notification;
@end
@implementation TUINSView
// implemented by TUIView
@dynamic layer;
// these cannot be implicitly synthesized because they're from protocols/categories
@synthesize hostView = _hostView;
@synthesize appKitHostView = _appKitHostView;
@synthesize trackingView = _trackingView;
@synthesize rootView = _rootView;
@synthesize maskLayer = _maskLayer;
- (id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self == nil)
return nil;
[self setUp];
return self;
}
- (id)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
if (self == nil)
return nil;
[self setUp];
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
_rootView.hostView = nil;
_rootView.nsView = nil;
[_rootView removeFromSuperview];
_rootView = nil;
_hoverView = nil;
_trackingView = nil;
_trackingArea = nil;
}
- (void)resetCursorRects
{
NSRect f = [self frame];
f.origin = NSZeroPoint;
[self addCursorRect:f cursor:[NSCursor arrowCursor]];
}
- (void)tui_setOpaque:(BOOL)o
{
opaque = o;
}
- (BOOL)isOpaque
{
return opaque;
}
- (BOOL)mouseDownCanMoveWindow
{
return NO;
}
- (void)updateTrackingAreas
{
[super updateTrackingAreas];
if(_trackingArea) {
[self removeTrackingArea:_trackingArea];
}
NSRect r = [self frame];
r.origin = NSZeroPoint;
_trackingArea = [[NSTrackingArea alloc] initWithRect:r options:NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways owner:self userInfo:nil];
[self addTrackingArea:_trackingArea];
}
- (void)viewWillStartLiveResize
{
[super viewWillStartLiveResize];
inLiveResize = YES;
[_rootView viewWillStartLiveResize];
}
- (BOOL)inLiveResize
{
return inLiveResize;
}
- (void)viewDidEndLiveResize
{
[super viewDidEndLiveResize];
inLiveResize = NO;
[_rootView viewDidEndLiveResize]; // will send to all subviews
if([[self window] respondsToSelector:@selector(ensureWindowRectIsOnScreen)])
[[self window] performSelector:@selector(ensureWindowRectIsOnScreen)];
}
- (void)setRootView:(TUIView *)v
{
v.autoresizingMask = TUIViewAutoresizingFlexibleSize;
TUINSView *originalNSView = v.ancestorTUINSView;
TUIView *originalRootView = _rootView;
[v willMoveToTUINSView:self];
[originalRootView willMoveToTUINSView:nil];
_rootView.nsView = nil;
_rootView.hostView = nil;
_rootView = v;
_rootView.nsView = self;
_rootView.hostView = self;
[_rootView setNextResponder:self];
[self setWantsLayer:YES];
CALayer *layer = [self layer];
[layer setDelegate:self];
CGSize s = [self frame].size;
v.frame = CGRectMake(0, 0, s.width, s.height);
[self.layer insertSublayer:_rootView.layer atIndex:0];
[self _updateLayerScaleFactor];
[originalRootView didMoveFromTUINSView:self];
[v didMoveFromTUINSView:originalNSView];
}
- (void)setNextResponder:(NSResponder *)r
{
NSResponder *nextResponder = [self nextResponder];
if([nextResponder isKindOfClass:[NSViewController class]]) {
// keep view controller in chain
[nextResponder setNextResponder:r];
} else {
[super setNextResponder:r];
}
}
- (void)viewWillMoveToWindow:(NSWindow *)newWindow {
if(self.window != nil) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidBecomeKeyNotification object:self.window];
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidResignKeyNotification object:self.window];
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidChangeScreenProfileNotification object:self.window];
}
CALayer *hostLayer = self.layer;
if(newWindow != nil && _rootView.layer.superlayer != hostLayer) {
_rootView.layer.frame = hostLayer.bounds;
[hostLayer insertSublayer:_rootView.layer atIndex:0];
}
[self.rootView willMoveToWindow:(TUINSWindow *) newWindow];
if(newWindow == nil) {
[_rootView removeFromSuperview];
// since the layer retains the layoutManger, we need to set it to nil to
// make sure TUINSView will be deallocated
self.appKitHostView.layer.layoutManager = nil;
} else {
self.appKitHostView.layer.layoutManager = self;
}
}
- (void)viewDidMoveToWindow
{
[self _updateLayerScaleFactor];
[self.rootView didMoveToWindow];
if(self.window != nil) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidResignKey:) name:NSWindowDidResignKeyNotification object:self.window];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidBecomeKey:) name:NSWindowDidBecomeKeyNotification object:self.window];
// make sure the window will post NSWindowDidChangeScreenProfileNotification
[self.window setDisplaysWhenScreenProfileChanges:YES];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(screenProfileOrBackingPropertiesDidChange:) name:NSWindowDidChangeScreenProfileNotification object:self.window];
}
}
- (void)_updateLayerScaleFactor {
if([self window] != nil) {
CGFloat scale = 1.0f;
if([[self window] respondsToSelector:@selector(backingScaleFactor)]) {
scale = [[self window] backingScaleFactor];
}
if([self.layer respondsToSelector:@selector(setContentsScale:)]) {
if(fabs(self.layer.contentsScale - scale) > 0.1f) {
self.layer.contentsScale = scale;
}
}
[self.rootView _updateLayerScaleFactor];
}
}
- (void)screenProfileOrBackingPropertiesDidChange:(NSNotification *)notification
{
[self performSelector:@selector(_updateLayerScaleFactor) withObject:nil afterDelay:0.0]; // the window's backingScaleFactor doesn't update until after this notification fires (10.8) - so delay it a bit.
}
- (TUIView *)viewForLocalPoint:(NSPoint)p
{
return [_rootView hitTest:p withEvent:nil];
}
- (NSPoint)localPointForLocationInWindow:(NSPoint)locationInWindow
{
return [self convertPoint:locationInWindow fromView:nil];
}
- (TUIView *)viewForLocationInWindow:(NSPoint)locationInWindow
{
return [self viewForLocalPoint:[self localPointForLocationInWindow:locationInWindow]];
}
- (TUIView *)viewForEvent:(NSEvent *)event
{
return [self viewForLocationInWindow:[event locationInWindow]];
}
- (void)windowDidResignKey:(NSNotification *)notification
{
[TUITooltipWindow endTooltip];
if(![self isWindowKey]) {
[self.rootView windowDidResignKey];
}
}
- (void)windowDidBecomeKey:(NSNotification *)notification
{
[self.rootView windowDidBecomeKey];
}
- (BOOL)isWindowKey
{
if([self.window isKeyWindow]) return YES;
NSWindow *keyWindow = [NSApp keyWindow];
if(keyWindow == nil) return NO;
return keyWindow == [self.window attachedSheet];
}
- (void)viewWillMoveToSuperview:(NSView *)newSuperview
{
[super viewWillMoveToSuperview:newSuperview];
if(newSuperview == nil) {
[TUITooltipWindow endTooltip];
}
}
- (void)_updateHoverView:(TUIView *)_newHoverView withEvent:(NSEvent *)event
{
if(_hyperFocusView) {
if(![_newHoverView isDescendantOfView:_hyperFocusView]) {
_newHoverView = nil; // don't allow hover
}
}
if(_newHoverView != _hoverView) {
[_hoverView mouseExited:event];
[_newHoverView mouseEntered:event];
_hoverView = _newHoverView;
if([[self window] isKeyWindow]) {
[TUITooltipWindow updateTooltip:_hoverView.toolTip delay:_hoverView.toolTipDelay];
} else {
[TUITooltipWindow updateTooltip:nil delay:_hoverView.toolTipDelay];
}
} else {
[_hoverView mouseMoved:event];
}
}
- (void)_updateHoverViewWithEvent:(NSEvent *)event
{
TUIView *_newHoverView = [self viewForEvent:event];
if(![[self window] isKeyWindow]) {
if(![_newHoverView acceptsFirstMouse:event]) {
// in background, don't do hover for things that don't accept first mouse
_newHoverView = nil;
}
}
[self _updateHoverView:_newHoverView withEvent:event];
}
- (void)invalidateHover
{
[self _updateHoverView:nil withEvent:nil];
}
- (void)invalidateHoverForView:(TUIView *)v
{
if([_hoverView isDescendantOfView:v]) {
[self invalidateHover];
}
}
- (void)mouseDown:(NSEvent *)event
{
if(_hyperFocusView) {
TUIView *v = [self viewForEvent:event];
if([v isDescendantOfView:_hyperFocusView]) {
// activate it normally
[self endHyperFocus:NO]; // not cancelled
goto normal;
} else {
// dismiss hover, don't click anything
[self endHyperFocus:YES];
}
} else {
// normal case
normal:
;
self.trackingView = [self viewForEvent:event];
[self.trackingView mouseDown:event];
}
[TUITooltipWindow endTooltip];
}
- (void)mouseUp:(NSEvent *)event
{
TUIView *lastTrackingView = self.trackingView;
self.trackingView = nil;
[lastTrackingView mouseUp:event]; // after trackingView is set to nil, will call mouseUp:fromSubview:
[self _updateHoverViewWithEvent:event];
}
- (void)mouseDragged:(NSEvent *)event
{
[self.trackingView mouseDragged:event];
}
- (void)mouseMoved:(NSEvent *)event
{
[self _updateHoverViewWithEvent:event];
}
-(void)mouseEntered:(NSEvent *)event {
[self _updateHoverViewWithEvent:event];
}
-(void)mouseExited:(NSEvent *)event {
[self _updateHoverViewWithEvent:event];
}
- (void)rightMouseDown:(NSEvent *)event
{
self.trackingView = [self viewForEvent:event];
[self.trackingView rightMouseDown:event];
[TUITooltipWindow endTooltip];
[super rightMouseDown:event]; // we need to send this up the responder chain so that -menuForEvent: will get called for two-finger taps
}
- (void)rightMouseUp:(NSEvent *)event
{
TUIView *lastTrackingView = self.trackingView;
self.trackingView = nil;
[lastTrackingView rightMouseUp:event]; // after trackingView is set to nil, will call mouseUp:fromSubview:
}
- (void)scrollWheel:(NSEvent *)event
{
[[self viewForEvent:event] scrollWheel:event];
[self _updateHoverView:nil withEvent:event]; // don't pop in while scrolling
}
- (void)beginGestureWithEvent:(NSEvent *)event
{
[[self viewForEvent:event] beginGestureWithEvent:event];
}
- (void)endGestureWithEvent:(NSEvent *)event
{
[[self viewForEvent:event] endGestureWithEvent:event];
}
- (void)magnifyWithEvent:(NSEvent *)event
{
if(!deliveringEvent) {
deliveringEvent = YES;
[[self viewForEvent:event] magnifyWithEvent:event];
deliveringEvent = NO;
}
}
- (void)rotateWithEvent:(NSEvent *)event
{
if(!deliveringEvent) {
deliveringEvent = YES;
[[self viewForEvent:event] rotateWithEvent:event];
deliveringEvent = NO;
}
}
- (void)swipeWithEvent:(NSEvent *)event
{
if(!deliveringEvent) {
deliveringEvent = YES;
[[self viewForEvent:event] swipeWithEvent:event];
deliveringEvent = NO;
}
}
- (void)keyDown:(NSEvent *)event
{
BOOL consumed = NO;
// TUIView uses -performKeyAction: in -keyDown: to do its key equivalents. If none of our TUIViews consumed the key down as a key action, we want to give our view controller a chance to handle the key down as a key equivalent.
if([[self nextResponder] isKindOfClass:[NSViewController class]]) {
consumed = [[self nextResponder] performKeyEquivalent:event];
}
if(!consumed) {
[super keyDown:event];
}
}
- (BOOL)performKeyEquivalent:(NSEvent *)event
{
return [_rootView performKeyEquivalent:event];
}
- (void)setEverythingNeedsDisplay
{
[_rootView setEverythingNeedsDisplay];
}
- (BOOL)isTrackingSubviewOfView:(TUIView *)v
{
return [self.trackingView isDescendantOfView:v];
}
- (BOOL)isHoveringSubviewOfView:(TUIView *)v
{
return [_hoverView isDescendantOfView:v];
}
- (BOOL)isHoveringView:(TUIView *)v
{
return _hoverView == v;
}
- (BOOL)acceptsFirstMouse:(NSEvent *)event
{
return [[self viewForEvent:event] acceptsFirstMouse:event];
}
/* http://developer.apple.com/Mac/library/documentation/Cocoa/Conceptual/MenuList/Articles/EnablingMenuItems.html
If the menu items target is not set and the NSMenu object is a contextual menu, NSMenu goes through the same steps as before but the search order for the responder chain is different:
- The responder chain for the window in which the view that triggered the context menu resides, starting with the view.
- The window itself.
- The windows delegate.
- The NSApplication object.
- The NSApplication objects delegate.
*/
- (NSResponder *)firstResponderForSelector:(SEL)action
{
if(!action)
return nil;
NSResponder *f = [[self window] firstResponder];
// NSLog(@"starting search at %@", f);
do {
if([f respondsToSelector:action])
return f;
} while((f = [f nextResponder]));
return nil;
}
- (void)_patchMenu:(NSMenu *)menu
{
for(NSMenuItem *item in [menu itemArray]) {
if(![item target]) {
// would normally travel the responder chain starting too high up, patch it to target what it would target if it hit the true responder chain
[item setTarget:[self firstResponderForSelector:[item action]]];
}
if([item submenu])
[self _patchMenu:[item submenu]]; // recurse
}
}
// the problem is for context menus the responder chain search starts with the NSView... we want it to start deeper, so we can patch up targets of a copy of the menu here
- (NSMenu *)menuWithPatchedItems:(NSMenu *)menu
{
NSData *d = [NSKeyedArchiver archivedDataWithRootObject:menu]; // this is bad - doesn't persist 'target'?
menu = [NSKeyedUnarchiver unarchiveObjectWithData:d];
[self _patchMenu:menu];
return menu;
}
- (NSMenu *)menuForEvent:(NSEvent *)event
{
TUIView *v = [self viewForEvent:event];
do {
NSMenu *m = [v menuForEvent:event];
if(m)
return m; // not patched
v = v.superview;
} while(v);
return nil;
}
- (void)setUp {
opaque = YES;
_maskLayer = [CAShapeLayer layer];
_maskLayer.frame = self.bounds;
_maskLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
// enable layer-backing for this view
self.wantsLayer = YES;
self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawNever;
_appKitHostView = [[NSView alloc] initWithFrame:self.bounds];
_appKitHostView.autoresizesSubviews = NO;
_appKitHostView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
_appKitHostView.wantsLayer = YES;
_appKitHostView.layerContentsRedrawPolicy = NSViewLayerContentsRedrawNever;
// keep this on top of TUIViews
_appKitHostView.layer.zPosition = 1;
[self addSubview:_appKitHostView];
// set up masking on the AppKit host view, and make ourselves the layout
// manager, so that we'll know when new sublayers are added
self.appKitHostView.layer.layoutManager = self;
#if ENABLE_NSVIEW_CLIPPING
self.appKitHostView.layer.mask = self.maskLayer;
[self recalculateNSViewClipping];
#endif
}
- (void)didAddSubview:(NSView *)view {
NSAssert(view == self || view == self.appKitHostView, @"Subviews should not be added to TUINSView %@: %@", self, view);
[super didAddSubview:view];
}
#pragma mark AppKit bridging
- (NSView *)hitTest:(NSPoint)point {
// convert point into our coordinate system, so it's ready to go for all
// subviews (which expect it in their superview's coordinate system)
point = [self convertPoint:point fromView:self.superview];
if (!CGRectContainsPoint(self.bounds, point))
return nil;
__block NSView *result = self;
// we need to avoid hitting any NSViews that are clipped by their
// corresponding TwUI views
[self.appKitHostView.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSView *view, NSUInteger index, BOOL *stop){
id<TUIBridgedView> hostView = view.hostView;
if (hostView) {
CGRect bounds = hostView.layer.bounds;
CGRect clippedBounds = [hostView.layer tui_convertAndClipRect:bounds toLayer:self.layer];
if (!CGRectContainsPoint(clippedBounds, point)) {
// skip this view
return;
}
}
NSView *hitTestedView = [view hitTest:point];
if (hitTestedView) {
result = hitTestedView;
*stop = YES;
}
}];
return result;
}
- (void)recalculateNSViewOrdering; {
NSAssert([NSThread isMainThread], @"");
[self.appKitHostView sortSubviewsUsingFunction:&compareNSViewOrdering context:NULL];
}
- (void)recalculateNSViewClipping; {
NSAssert([NSThread isMainThread], @"");
#if !ENABLE_NSVIEW_CLIPPING
return;
#endif
CGMutablePathRef clippingPath = CGPathCreateMutable();
for (NSView *view in self.appKitHostView.subviews) {
id<TUIBridgedView> hostView = view.hostView;
if (!hostView)
continue;
CALayer *focusRingLayer = [self focusRingLayerForView:view];
if (focusRingLayer) {
id<TUIBridgedScrollView> clippingView = hostView.ancestorScrollView;
CGRect clippedFocusRingBounds = CGRectNull;
if (clippingView && self.ancestorScrollView != clippingView) {
CGRect rect = [clippingView.layer tui_convertAndClipRect:clippingView.layer.visibleRect toLayer:focusRingLayer];
if (!CGRectIsNull(rect) && !CGRectIsInfinite(rect) && !CGRectContainsRect(rect, clippedFocusRingBounds)) {
clippedFocusRingBounds = CGRectIntersection(rect, focusRingLayer.bounds);
}
}
// the frame of the focus ring, represented in the TUINSView's
// coordinate system
CGRect focusRingFrame;
if (CGRectIsNull(clippedFocusRingBounds)) {
focusRingLayer.mask = nil;
focusRingFrame = [focusRingLayer tui_convertAndClipRect:focusRingLayer.bounds toLayer:self.layer];
} else {
// set up a mask on the focus ring that clips to any ancestor scroll views
CAShapeLayer *maskLayer = (id)focusRingLayer.mask;
if (![maskLayer isKindOfClass:[CAShapeLayer class]]) {
maskLayer = [CAShapeLayer layer];
focusRingLayer.mask = maskLayer;
}
CGPathRef focusRingPath = CGPathCreateWithRect(clippedFocusRingBounds, NULL);
maskLayer.path = focusRingPath;
CGPathRelease(focusRingPath);
focusRingFrame = [focusRingLayer tui_convertAndClipRect:clippedFocusRingBounds toLayer:self.layer];
}
CGPathAddRect(clippingPath, NULL, focusRingFrame);
}
// clip the frame of each NSView using the TwUI hierarchy
CGRect rect = [hostView.layer tui_convertAndClipRect:hostView.layer.visibleRect toLayer:self.layer];
if (CGRectIsNull(rect) || CGRectIsInfinite(rect))
continue;
CGPathAddRect(clippingPath, NULL, rect);
}
// mask them all at once (so fast!)
self.maskLayer.path = clippingPath;
CGPathRelease(clippingPath);
}
#pragma mark CALayer delegate
- (void)layoutSublayersOfLayer:(CALayer *)layer {
NSAssert([NSThread isMainThread], @"");
if (layer == self.layer) {
// TUINSView.layer is being laid out
return;
}
// appKitHostView.layer is being laid out
//
// this often happens in response to AppKit adding a focus ring layer, so
// recalculate our clipping paths to take it into account
[self recalculateNSViewClipping];
}
- (CALayer *)focusRingLayerForView:(NSView *)view; {
CALayer *resultSoFar = nil;
for (CALayer *layer in self.appKitHostView.layer.sublayers) {
// don't return the layer of the view itself
if (layer == view.layer) {
continue;
}
// if the layer doesn't wrap around this view, it's not the focus ring
if (!CGRectContainsRect(layer.frame, view.frame)) {
continue;
}
// if resultSoFar matched more tightly than this layer, consider the
// former to be the focus ring
if (resultSoFar && !CGRectContainsRect(resultSoFar.frame, layer.frame)) {
continue;
}
resultSoFar = layer;
}
return resultSoFar;
}
#pragma mark TUIHostView
- (void)ancestorDidLayout; {
[super ancestorDidLayout];
[self.rootView ancestorDidLayout];
}
- (id<TUIBridgedView>)descendantViewAtPoint:(CGPoint)point {
if (!CGRectContainsPoint(self.bounds, point))
return nil;
return [self.rootView descendantViewAtPoint:point] ?: self;
}
- (id<TUIHostView>)hostView {
if (_hostView)
return _hostView;
else
return self.superview.hostView;
}
- (void)viewHierarchyDidChange {
[super viewHierarchyDidChange];
[self.rootView viewHierarchyDidChange];
}
- (void)willMoveToTUINSView:(TUINSView *)view; {
// despite the TUIBridgedView contract that says we should forward this
// message onto all subviews and our rootView, doing so could result in
// crazy behavior, since the TUINSView of those views is and will remain
// 'self' by definition
}
- (void)didMoveFromTUINSView:(TUINSView *)view; {
// despite the TUIBridgedView contract that says we should forward this
// message onto all subviews and our rootView, doing so could result in
// crazy behavior, since the TUINSView of those views is and will remain
// 'self' by definition
}
@end