Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Sources/COpenSwiftUI/Shims/AppKit/AppKit_Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ typedef OPENSWIFTUI_ENUM(NSInteger, NSViewVibrantBlendingStyle) {

@interface NSView ()
@property (getter=isOpaque) BOOL opaque;
@property (nonatomic) BOOL ignoreHitTest;
- (nullable NSResponder *)_nextResponderForEvent:(nullable NSEvent *)event;
- (void)_updateLayerGeometryFromView;
- (void)_updateLayerShadowFromView;
- (void)_updateLayerShadowColorFromView;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// AllowsWindowActivationEventsResponder.swift
// OpenSwiftUI
//
// Audited for 6.5.4
// Status: WIP
// ID: 302179F1EB9AE99B83C6A183C0B4143E (?)

#if os(macOS)
// TODO
class AllowsWindowActivationEventsResponder {}

// MARK: - AllowsWindowActivationEventsKey

private struct AllowsWindowActivationEventsKey: EnvironmentKey {
static var defaultValue: Bool?
}

extension EnvironmentValues {
var allowsWindowActivationEvents: Bool? {
get { self[AllowsWindowActivationEventsKey.self] }
set { self[AllowsWindowActivationEventsKey.self] = newValue }
}
}
#endif
203 changes: 150 additions & 53 deletions Sources/OpenSwiftUI/Render/DisplayList/AppKitDisplayList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,25 @@
// AppKitDisplayList.swift
// OpenSwiftUI
//
// Audited for 6.0.87
// Status: WIP
// Audited for 6.5.4
// Status: Complete
// ID: 33EEAA67E0460DA84AE814EA027152BA (SwiftUI)

#if os(macOS)
@_spi(DisplayList_ViewSystem) import OpenSwiftUICore
@_spi(ForOpenSwiftUIOnly)
@_spi(DisplayList_ViewSystem)
import OpenSwiftUICore
import AppKit
import OpenSwiftUISymbolDualTestsSupport
import COpenSwiftUI
import QuartzCore_Private
import OpenRenderBoxShims
import OpenSwiftUI_SPI

// MARK: - NSViewPlatformViewDefinition [TODO]
// MARK: - NSViewPlatformViewDefinition

final class NSViewPlatformViewDefinition: PlatformViewDefinition, @unchecked Sendable {
override final class var system: PlatformViewDefinition.System { .nsView }
override static var system: PlatformViewDefinition.System { .nsView }

override static func makeView(kind: PlatformViewDefinition.ViewKind) -> AnyObject {
let view: NSView
Expand All @@ -27,47 +30,25 @@ final class NSViewPlatformViewDefinition: PlatformViewDefinition, @unchecked Sen
case .projection:
view = _NSProjectionView()
case .mask:
view = _NSGraphicsView()
view.mask = _NSInheritedView()
initView(view.mask!, kind: kind)
view = _NSInheritedView()
let maskView = _NSInheritedView()
view.mask = maskView
initView(maskView, kind: .inherited)
default:
view = kind.isContainer ? _NSInheritedView() : _NSGraphicsView()
}
initView(view, kind: kind)
return view
}

private static func initView(_ view: NSView, kind: PlatformViewDefinition.ViewKind) {
view.wantsLayer = true

if kind != .platformView && kind != .platformGroup {
view.setFlipped(true)
view.autoresizesSubviews = false
// TODO - UnifiedHitTestingFeature.isEnabled
// setIgnoreHitTest: true
}

switch kind {
case .color, .image, .shape:
view.layer?.edgeAntialiasingMask = [.layerTopEdge, .layerBottomEdge, .layerLeftEdge, .layerRightEdge]
view.layer?.allowsEdgeAntialiasing = true
break
case .geometry, .projection, .mask:
view.layer?.allowsGroupOpacity = false
view.layer?.allowsGroupBlending = false
default:
break
}
}

// Audited for 6.5.4
override static func makeLayerView(type: CALayer.Type, kind: PlatformViewDefinition.ViewKind) -> AnyObject {
let cls: NSView.Type
if kind == .shape {
switch kind {
case .shape:
cls = _NSShapeHitTestingView.self
} else if kind == .platformLayer {
case .platformLayer:
cls = _NSPlatformLayerView.self
} else {
default:
cls = kind.isContainer ? _NSInheritedView.self : _NSGraphicsView.self
}
let view = cls.init()
Expand All @@ -78,37 +59,97 @@ final class NSViewPlatformViewDefinition: PlatformViewDefinition, @unchecked Sen
return view
}

override class func makePlatformView(view: AnyObject, kind: PlatformViewDefinition.ViewKind) {
override static func makePlatformView(view: AnyObject, kind: PlatformViewDefinition.ViewKind) {
Self.initView(view as! NSView, kind: kind)
}

override static func makeDrawingView(options: PlatformDrawableOptions) -> any PlatformDrawable {
let view: NSView & PlatformDrawable
if options.isAccelerated && ORBDevice.isSupported() {
view = RBDrawingView(options: options)
} else {
view = CGDrawingView(options: options)
}
view.layerContentsRedrawPolicy = .onSetNeedsDisplay
view.layerContentsPlacement = .topLeft
initView(view, kind: .drawing)
return view
}

override static func setPath(_ path: Path, shapeView: AnyObject) {
let view = unsafeBitCast(shapeView, to: _NSShapeHitTestingView.self)
guard let view = shapeView as? _NSShapeHitTestingView else { return }
view.path = path
}

override class func setProjectionTransform(_ transform: ProjectionTransform, projectionView: AnyObject) {
override static func setProjectionTransform(_ transform: ProjectionTransform, projectionView: AnyObject) {
guard let view = projectionView as? _NSProjectionView else {
return
}
view.projectionTransform = transform
view.layer?.transform = .init(transform)
view.layer?.transform = CATransform3D(transform)
}

override static func getRBLayer(drawingView: AnyObject) -> AnyObject? {
guard let rbView = drawingView as? RBDrawingView else { return nil }
return rbView.layer
}

override static func setIgnoresEvents(_ state: Bool, of view: AnyObject) {
guard !ResponderBasedHitTesting.enabled else { return }
if UnifiedHitTestingFeature.isEnabled {
if let customizing = view as? RecursiveIgnoreHitTestCustomizing {
customizing.recursiveIgnoreHitTest = state
}
} else {
let view = unsafeBitCast(view, to: NSView.self)
view.ignoreHitTest = state
}
}

override static func setAllowsWindowActivationEvents(_ value: Bool?, for view: AnyObject) {
guard !ResponderBasedHitTesting.enabled else { return }
if let customizing = view as? AcceptsFirstMouseCustomizing {
customizing.customAcceptsFirstMouse = value
}
}

override class func setAllowsWindowActivationEvents(_ value: Bool?, for view: AnyObject) {
_openSwiftUIUnimplementedWarning()
override static func setHitTestsAsOpaque(_ value: Bool, for view: AnyObject) {
guard !ResponderBasedHitTesting.enabled else { return }
if let customizing = view as? HitTestsAsOpaqueCustomizing {
customizing.hitTestsAsOpaque = value
}
}

override class func setHitTestsAsOpaque(_ value: Bool, for view: AnyObject) {
_openSwiftUIUnimplementedWarning()
private static func initView(_ view: NSView, kind: PlatformViewDefinition.ViewKind) {
view.wantsLayer = true
if kind != .platformView && kind != .platformGroup {
view.setFlipped(true)
view.autoresizesSubviews = false
view.clipsToBounds = false
if !UnifiedHitTestingFeature.isEnabled {
view.ignoreHitTest = true
}
}
switch kind {
case .color, .image, .shape:
let layer = view.layer!
layer.edgeAntialiasingMask = [.layerTopEdge, .layerBottomEdge, .layerLeftEdge, .layerRightEdge]
layer.allowsEdgeAntialiasing = true
case .inherited, .geometry, .projection, .mask:
let layer = view.layer!
layer.allowsGroupOpacity = false
layer.allowsGroupBlending = false
default:
break
}
}
}

// MARK: - _NSGraphicsView

typealias PlatformGraphicsView = _NSGraphicsView

class _NSGraphicsView: NSView {
class _NSGraphicsView: NSView, RecursiveIgnoreHitTestCustomizing, AcceptsFirstMouseCustomizing {
var recursiveIgnoreHitTest: Bool = false

var customAcceptsFirstMouse: Bool?
Expand All @@ -120,13 +161,43 @@ class _NSGraphicsView: NSView {
required init?(coder: NSCoder) {
super.init(coder: coder)
}

override func hitTest(_ point: NSPoint) -> NSView? {
guard !ResponderBasedHitTesting.enabled else {
return super.hitTest(point)
}
if UnifiedHitTestingFeature.isEnabled {
guard !recursiveIgnoreHitTest,
alphaValue >= ViewResponder.minOpacityForHitTest else {
return nil
}
return super.hitTest(point)
} else {
guard !ignoreHitTest, frame.contains(point) else {
return nil
}
return self
}
}

override func _nextResponder(for event: NSEvent?) -> NSResponder? {
nextResponder
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NSView.hitTest(_:) receives point in the receiver’s local coordinates, so checking frame.contains(point) (superview coordinates) can mis-evaluate hit-testing when the view’s frame origin isn’t (0,0).

Severity: high

Other Locations
  • Sources/OpenSwiftUI/Render/DisplayList/AppKitDisplayList.swift:232

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.


override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
guard !ResponderBasedHitTesting.enabled,
let value = effectiveAcceptsFirstMouse else {
return super.acceptsFirstMouse(for: event)
}
return value
}
}

// MARK: - _NSInheritedView

typealias PlatformInheritedView = _NSInheritedView

class _NSInheritedView: _NSGraphicsView {
class _NSInheritedView: _NSGraphicsView, HitTestsAsOpaqueCustomizing {
var hitTestsAsOpaque: Bool = false

override init(frame frameRect: NSRect) {
Expand All @@ -136,11 +207,30 @@ class _NSInheritedView: _NSGraphicsView {
required init?(coder: NSCoder) {
super.init(coder: coder)
}

override func hitTest(_ point: NSPoint) -> NSView? {
guard !ResponderBasedHitTesting.enabled,
UnifiedHitTestingFeature.isEnabled else {
return super.hitTest(point)
}
guard !isHiddenOrHasHiddenAncestor, !recursiveIgnoreHitTest else {
return nil
}
for subview in subviews.reversed() {
let convertedPoint = convert(point, from: superview)
if let result = subview.hitTest(convertedPoint) {
return result
}
}
guard hitTestsAsOpaque, frame.contains(point) else {
return nil
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In _NSInheritedView.hitTest, converting point “from: superview” and then passing that into each subview.hitTest looks inconsistent with AppKit’s coordinate expectations and can cause subviews to never be hittable.

Severity: high

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

return self
}
}

// MARK: - _NSProjectionView [6.5.4]
// MARK: - _NSProjectionView

@objc
private class _NSProjectionView: _NSInheritedView {
var projectionTransform: ProjectionTransform

Expand All @@ -158,11 +248,11 @@ private class _NSProjectionView: _NSInheritedView {

override func _updateLayerGeometryFromView() {
super._updateLayerGeometryFromView()
layer?.transform = .init(projectionTransform)
layer?.transform = CATransform3D(projectionTransform)
}
}

// MARK: - _NSShapeHitTestingView [WIP]
// MARK: - _NSShapeHitTestingView

@objc
private class _NSShapeHitTestingView: _NSGraphicsView {
Expand All @@ -179,15 +269,22 @@ private class _NSShapeHitTestingView: _NSGraphicsView {
}

override func hitTest(_ point: NSPoint) -> NSView? {
// path.contains(, eoFill: false)
_openSwiftUIUnimplementedWarning()
return nil
guard !ResponderBasedHitTesting.enabled else {
return super.hitTest(point)
}
if UnifiedHitTestingFeature.isEnabled, super.hitTest(point) == nil {
return nil
}
let point = convert(point, from: superview)
guard path.contains(point, eoFill: false) else {
return nil
}
return self
}
}

// MARK: - _NSPlatformLayerView

@objc
private class _NSPlatformLayerView: _NSGraphicsView {
override init(frame frameRect: NSRect) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_NSShapeHitTestingView.hitTest converts point from superview, but hitTest(_:)’s point is already in the view’s local coordinates; this can shift the point and make path.contains fail unexpectedly.

Severity: high

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

super.init(frame: frameRect)
Expand Down
Loading
Loading