Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Finished TUIViewNSViewContainer

  • Loading branch information...
commit 66c39d516feb192e0392659d2bf57a31d65b302c 1 parent 91b4a21
@jspahrsummers jspahrsummers authored
View
2  TwUI.xcodeproj/project.pbxproj
@@ -481,6 +481,7 @@
D0C7655B15B6297300E7AC2C /* NSScrollView+TUIExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSScrollView+TUIExtensions.h"; sourceTree = "<group>"; };
D0C7655C15B6297300E7AC2C /* NSScrollView+TUIExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSScrollView+TUIExtensions.m"; sourceTree = "<group>"; };
D0C7656915B62EFA00E7AC2C /* TUINSView+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TUINSView+Private.h"; sourceTree = "<group>"; };
+ D0C7656D15B6322A00E7AC2C /* TUIViewNSViewContainer+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TUIViewNSViewContainer+Private.h"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -761,6 +762,7 @@
CBB74C8E13BE6E1900C85CB5 /* TUIViewController.m */,
CBB74C8F13BE6E1900C85CB5 /* TUIViewNSViewContainer.h */,
CBB74C9013BE6E1900C85CB5 /* TUIViewNSViewContainer.m */,
+ D0C7656D15B6322A00E7AC2C /* TUIViewNSViewContainer+Private.h */,
);
name = UIKit;
path = lib/UIKit;
View
1  lib/UIKit/TUIKit.h
@@ -53,6 +53,7 @@
#import "TUIView.h"
#import "TUIView+TUIBridgedView.h"
#import "TUIViewController.h"
+#import "TUIViewNSViewContainer.h"
extern CGContextRef TUIGraphicsGetCurrentContext(void);
extern void TUIGraphicsPushContext(CGContextRef context);
View
58 lib/UIKit/TUIViewNSViewContainer+Private.h
@@ -0,0 +1,58 @@
+//
+// TUIViewNSView+Private.h
+//
+// Created by James Lawton on 23.11.11.
+// Copyright (c) 2011 Bitswift. All rights reserved.
+//
+
+#import "TUIViewNSViewContainer.h"
+
+/**
+ * Private functionality of TUIViewNSViewContainer that needs to be exposed to other parts
+ * of the framework.
+ */
+@interface TUIViewNSViewContainer ()
+
+/**
+ * Whether the receiver is rendering its NSView.
+ */
+@property (nonatomic, getter = isRenderingContainedView, readonly) BOOL renderingContainedView;
+
+/**
+ * Renders the receiver's rootView once and caches into the receiver's layer,
+ * supporting an animation on the NSView as part of the TwUI hierarchy.
+ *
+ * This method can be called multiple times, but each call must eventually be
+ * balanced with a call to -stopRenderingContainedView.
+ */
+- (void)startRenderingContainedView;
+
+/**
+ * Balances a previous call to -startRenderingContainedView.
+ *
+ * If this balances the initial call to -startRenderingContainedView, the
+ * receiver stop rendering the rootView in its layer, and restore the normal
+ * AppKit rendering path.
+ *
+ * This method can be called multiple times to match multiple calls to
+ * -startRenderingContainedView.
+ */
+- (void)stopRenderingContainedView;
+
+/**
+ * The frame that the receiver's NSView should have at the time of call.
+ *
+ * This is used by -synchronizeNSViewGeometry to keep the NSView attached to
+ * its TwUI view.
+ *
+ * If the receiver has no host view, the value is undefined.
+ */
+@property (readonly) CGRect NSViewFrame;
+
+/**
+ * This will synchronize the geometry of the receiver's NSView with that of
+ * the receiver, ensuring that the NSView is laid out correctly on screen.
+ */
+- (void)synchronizeNSViewGeometry;
+
+@end
View
48 lib/UIKit/TUIViewNSViewContainer.h
@@ -18,21 +18,47 @@
// which is copyright (c) 2012 Bitswift, Inc.
// See LICENSE.txt for more information.
+#import "NSView+TUIExtensions.h"
+#import "TUIHostView.h"
#import "TUIView.h"
-/*
- Stub class
- It would be nice to be able to embed NSView-based views inside of a
- TUIView-based view. The only ways I can think to do it are incredibly hacky.
- Plus, it still wouldn't work right if you apply CAAnimations. Maybe someone
- smarter than me can figure out a good way to do it.
+/**
+ * A view that is responsible for displaying and handling an NSView within the
+ * normal TwUI view hierarchy.
+ *
+ * TUIViewNSViewContainer is powerful, but many of its interactions with AppKit rely upon
+ * assumptions, magic, or unspecified behavior. To that end, there are several
+ * restrictions on what you can do with TUIViewNSViewContainer:
+ *
+ * - A TUIViewNSViewContainer must always appear on top of TwUI views. A TUIViewNSViewContainer
+ * should always appear at the end of a subview list. You should not attempt to
+ * add TwUI subviews directly to a TUIViewNSViewContainer. Instead, if you need TwUI
+ * views to appear on top, nest an TUINSView within the NSView and start
+ * a new TwUI hierarchy.
+ *
+ * - You must not modify the geometry of the hosted NSView. If you need to
+ * rearrange or resize the NSView, modify the TUIViewNSViewContainer and it will perform
+ * the necessary updates.
+ *
+ * - You must not touch the layer of the hosted NSView. If you wish to
+ * perform animations or apply other Core Animation effects, use the layer of
+ * the TUIViewNSViewContainer. Note that not all Core Animation features may be available.
+ *
+ * - You should not subclass TUIViewNSViewContainer. If you need additional features,
+ * create a new view class which contains a TUIViewNSViewContainer instead.
*/
+@interface TUIViewNSViewContainer : TUIView <TUIHostView>
-@interface TUIViewNSViewContainer : TUIView
-{
- NSView *nsView;
-}
+/**
+ * Initializes the receiver, setting its rootView to the given view.
+ *
+ * The frame of the receiver will automatically be set to that of the given view.
+ */
+- (id)initWithNSView:(NSView *)view;
-- (id)initWithNSView:(NSView *)v;
+/**
+ * The view displayed by the receiver.
+ */
+@property (nonatomic, strong) NSView *rootView;
@end
View
270 lib/UIKit/TUIViewNSViewContainer.m
@@ -19,26 +19,274 @@
// See LICENSE.txt for more information.
#import "TUIViewNSViewContainer.h"
+#import "CATransaction+TUIExtensions.h"
+#import "NSView+TUIExtensions.h"
+#import "TUINSView.h"
+#import "TUINSView+Private.h"
+#import "TUIViewNSViewContainer+Private.h"
+#import <QuartzCore/QuartzCore.h>
+
+@interface TUIViewNSViewContainer () {
+ /**
+ * A count indicating how many nested calls to <startRenderingContainedView>
+ * are in effect.
+ */
+ NSUInteger _renderingContainedViewCount;
+
+ #ifdef DEBUG
+ /**
+ * An observer for `TUIHostViewDebugModeChangedNotification`.
+ */
+ id _hostViewDebugModeObserver;
+ #endif
+}
+
+- (void)synchronizeNSViewGeometry;
+- (void)startRenderingContainedView;
+- (void)stopRenderingContainedView;
+@end
@implementation TUIViewNSViewContainer
-- (id)initWithNSView:(NSView *)v
-{
- if((self = [super initWithFrame:[v frame]])) {
- nsView = v;
- [v setWantsLayer:YES];
- CALayer *l = [v layer];
- l.delegate = self;
- [self.layer addSublayer:l];
+#pragma mark Properties
+
+@dynamic hostView;
+
+- (void)setGuestView:(NSView *)view {
+ NSAssert1([NSThread isMainThread], @"%s should only be called from the main thread", __func__);
+
+ // remove any existing guest view
+ [_rootView removeFromSuperview];
+ _rootView.hostView = nil;
+
+ _rootView = view;
+
+ TUINSView *nsView = self.ancestorTUINSView;
+
+ // and set up our new view
+ if (_rootView) {
+ // set up layer-backing on the view
+ [_rootView setWantsLayer:YES];
+ [_rootView setNeedsDisplay:YES];
+
+ [nsView.appKitHostView addSubview:_rootView];
+ _rootView.hostView = self;
+
+ [nsView recalculateNSViewOrdering];
+
+ _rootView.nextResponder = self;
+ [self synchronizeNSViewGeometry];
+ } else {
+ // remove the old view from the TUINSView's clipping path
+ [nsView recalculateNSViewClipping];
}
+}
+
+- (CGRect)NSViewFrame; {
+ // we use 'self' and 'bounds' here instead of the superview and frame
+ // because the superview may be a TUIScrollView, and accessing it directly
+ // will skip over the CAScrollLayer that's in the hierarchy
+ return [self convertRect:self.bounds toView:self.ancestorTUINSView.rootView];
+}
+
+- (void)setCenter:(CGPoint)center {
+ [super setCenter:center];
+ [self synchronizeNSViewGeometry];
+}
+
+- (void)didAddSubview:(TUIView *)subview {
+ NSAssert(NO, @"%@ must be a leaf in the TwUI hierarchy, should not have added subview: %@", self, subview);
+ [super didAddSubview:subview];
+}
+
+- (BOOL)isRenderingContainedView {
+ return _renderingContainedViewCount > 0;
+}
+
+#pragma mark Lifecycle
+
+- (id)init {
+ self = [super init];
+ if (!self)
+ return nil;
+
+ self.layer.masksToBounds = NO;
+
+ // prevents the layer from displaying until we need to render our contained
+ // view
+ self.contentMode = TUIViewContentModeScaleToFill;
+ return self;
+}
+
+- (id)initWithNSView:(NSView *)view; {
+ NSAssert1([NSThread isMainThread], @"%s should only be called from the main thread", __func__);
+
+ self = [self init];
+ if (!self)
+ return nil;
+
+ self.rootView = view;
+ self.frame = view.frame;
return self;
}
+- (void)dealloc {
+ #ifdef DEBUG
+ if (_hostViewDebugModeObserver) {
+ [[NSNotificationCenter defaultCenter] removeObserver:_hostViewDebugModeObserver];
+ _hostViewDebugModeObserver = nil;
+ }
+ #endif
+
+ self.rootView.hostView = nil;
+}
+
+#pragma mark Geometry
+
+- (void)synchronizeNSViewGeometry; {
+ NSAssert1([NSThread isMainThread], @"%s should only be called from the main thread", __func__);
+
+ if (!self.nsWindow) {
+ // can't do this without being in a window
+ return;
+ }
+
+ NSAssert(self.ancestorTUINSView, @"%@ should be in an TUINSView if it has a window", self);
+
+ CGRect frame = self.NSViewFrame;
+ self.rootView.frame = frame;
+
+ [self.ancestorTUINSView recalculateNSViewClipping];
+}
+
+#pragma mark Drawing
+
+- (void)drawRect:(CGRect)rect {
+ if (!self.renderingContainedView) {
+ return;
+ }
-- (void)layoutSubviews
-{
+ CGContextRef context = [NSGraphicsContext currentContext].graphicsPort;
+ [self.rootView.layer renderInContext:context];
+}
+
+- (void)startRenderingContainedView; {
+ if (_renderingContainedViewCount++ == 0) {
+ [CATransaction tui_performWithDisabledActions:^{
+ [self synchronizeNSViewGeometry];
+ [self.rootView displayIfNeeded];
+ [self.layer display];
+ }];
+ }
+}
+
+- (void)stopRenderingContainedView; {
+ NSAssert(_renderingContainedViewCount > 0, @"Mismatched call to %s", __func__);
+
+ if (--_renderingContainedViewCount == 0) {
+ self.layer.contents = nil;
+ }
+}
+
+#pragma mark View hierarchy
+
+- (void)ancestorDidLayout; {
+ [self synchronizeNSViewGeometry];
+ [super ancestorDidLayout];
+}
+
+- (void)willMoveToTUINSView:(TUINSView *)view; {
+ [super willMoveToTUINSView:view];
+ [self.rootView willMoveToTUINSView:view];
+
+ [CATransaction tui_performWithDisabledActions:^{
+ [self.rootView removeFromSuperview];
+ }];
+}
+
+- (void)didMoveFromTUINSView:(TUINSView *)view; {
+ [super didMoveFromTUINSView:view];
+
+ TUINSView *newView = self.ancestorTUINSView;
+ if (newView) {
+ [CATransaction tui_performWithDisabledActions:^{
+ [newView.appKitHostView addSubview:self.rootView];
+ }];
+
+ self.rootView.nextResponder = self;
+ }
+
+ [self.rootView didMoveFromTUINSView:view];
+}
+
+- (void)viewHierarchyDidChange {
+ [super viewHierarchyDidChange];
+
+ // verify that TUIViewNSViewContainers are on top of other subviews
+ #if DEBUG
+ NSArray *siblings = self.superview.subviews;
+ __block BOOL foundTUIView = NO;
+
+ [siblings enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(TUIView *view, NSUInteger index, BOOL *stop){
+ if ([view isKindOfClass:[TUIViewNSViewContainer class]]) {
+ NSAssert2(!foundTUIView, @"%@ must be on top of its sibling TUIViews: %@", view, siblings);
+ } else {
+ foundTUIView = YES;
+ }
+ }];
+ #endif
+
+ [self.ancestorTUINSView recalculateNSViewOrdering];
+ [self synchronizeNSViewGeometry];
+ [self.rootView viewHierarchyDidChange];
+}
+
+- (id<TUIBridgedView>)descendantViewAtPoint:(CGPoint)point {
+ if (![self pointInside:point])
+ return nil;
+
+ CGPoint NSViewPoint = [self.rootView convertFromWindowPoint:[self convertToWindowPoint:point]];
+
+ // never return 'self', since we don't want to catch clicks that didn't
+ // directly hit the NSView
+ return [self.rootView descendantViewAtPoint:NSViewPoint];
+}
+
+#pragma mark Layout
+
+- (void)layoutSubviews {
[super layoutSubviews];
- NSLog(@"layoutSubviews %@ %@ %@", self, nsView, NSStringFromRect(self.frame));
+ [self synchronizeNSViewGeometry];
+}
+
+- (CGSize)sizeThatFits:(CGSize)constraint {
+ NSAssert1([NSThread isMainThread], @"%s should only be called from the main thread", __func__);
+
+ id view = self.rootView;
+ NSSize cellSize = NSMakeSize(10000, 10000);
+
+ NSCell *cell = nil;
+
+ if ([view respondsToSelector:@selector(cell)]) {
+ cell = [view cell];
+ }
+
+ if ([cell respondsToSelector:@selector(cellSize)]) {
+ cellSize = [cell cellSize];
+ }
+
+ // if we don't have a cell, or it didn't give us a true size
+ if (CGSizeEqualToSize(cellSize, CGSizeMake(10000, 10000))) {
+ return [super sizeThatFits:constraint];
+ }
+
+ return cellSize;
+}
+
+#pragma mark NSObject overrides
+
+- (NSString *)description {
+ return [NSString stringWithFormat:@"<%@ %p> frame = %@, NSView = %@ %@", [self class], self, NSStringFromRect(self.frame), self.rootView, NSStringFromRect(self.rootView.frame)];
}
@end
Please sign in to comment.
Something went wrong with that request. Please try again.