From 8a5cccc4f1946310ad5bb4e9bd03b0315e22d529 Mon Sep 17 00:00:00 2001 From: Mx-Iris Date: Sun, 18 May 2025 00:31:36 +0800 Subject: [PATCH 1/2] Add initial rendering support for NSHostingView --- .../COpenSwiftUI/AppKit/OpenSwiftUI+NSView.h | 31 +++++++++ .../COpenSwiftUI/AppKit/OpenSwiftUI+NSView.m | 12 ++++ .../QuartzCore/CAChameleonLayer.h | 23 +++++++ .../QuartzCore/CAChameleonLayer.m | 9 +++ .../Hosting/AppKit/View/NSGraphicsView.swift | 27 ++++++++ .../Hosting/AppKit/View/NSHostingView.swift | 66 ++++++++++++++++++- .../Hosting/AppKit/View/NSInheritedView.swift | 25 +++++++ .../AppKit/View/NSProjectionView.swift | 26 ++++++++ .../AppKit/NSViewPlatformViewDefinition.swift | 42 +++++++++++- .../DisplayList/DisplayListViewRenderer.swift | 13 +++- .../Render/RendererLeafView.swift | 6 ++ 11 files changed, 274 insertions(+), 6 deletions(-) create mode 100644 Sources/COpenSwiftUI/AppKit/OpenSwiftUI+NSView.h create mode 100644 Sources/COpenSwiftUI/AppKit/OpenSwiftUI+NSView.m create mode 100644 Sources/COpenSwiftUI/QuartzCore/CAChameleonLayer.h create mode 100644 Sources/COpenSwiftUI/QuartzCore/CAChameleonLayer.m create mode 100644 Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSGraphicsView.swift create mode 100644 Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSInheritedView.swift create mode 100644 Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSProjectionView.swift diff --git a/Sources/COpenSwiftUI/AppKit/OpenSwiftUI+NSView.h b/Sources/COpenSwiftUI/AppKit/OpenSwiftUI+NSView.h new file mode 100644 index 00000000..d980469a --- /dev/null +++ b/Sources/COpenSwiftUI/AppKit/OpenSwiftUI+NSView.h @@ -0,0 +1,31 @@ +// +// OpenSwiftUI+NSView.h +// COpenSwiftUI +// +// Audited for macOS 15.0 +// Status: WIP + +#ifndef OpenSwiftUI_NSView_h +#define OpenSwiftUI_NSView_h + +#include "OpenSwiftUIBase.h" + +#if OPENSWIFTUI_TARGET_OS_OSX + +#import + +OPENSWIFTUI_ASSUME_NONNULL_BEGIN + +@interface NSView (OpenSwiftUI) + +@property (strong, nullable) NSView *maskView; + +- (void)setFlipped:(BOOL)flipped; + +@end + +OPENSWIFTUI_ASSUME_NONNULL_END + +#endif /* OPENSWIFTUI_TARGET_OS_OSX */ + +#endif /* OpenSwiftUI_NSView_h */ diff --git a/Sources/COpenSwiftUI/AppKit/OpenSwiftUI+NSView.m b/Sources/COpenSwiftUI/AppKit/OpenSwiftUI+NSView.m new file mode 100644 index 00000000..3aa516ab --- /dev/null +++ b/Sources/COpenSwiftUI/AppKit/OpenSwiftUI+NSView.m @@ -0,0 +1,12 @@ +// +// __FILEBASENAME__.m +// COpenSwiftUI +// +// Audited for macOS 15.0 +// Status: WIP + +#import "OpenSwiftUI+NSView.h" + +#if OPENSWIFTUI_TARGET_OS_OSX + +#endif diff --git a/Sources/COpenSwiftUI/QuartzCore/CAChameleonLayer.h b/Sources/COpenSwiftUI/QuartzCore/CAChameleonLayer.h new file mode 100644 index 00000000..97536bb5 --- /dev/null +++ b/Sources/COpenSwiftUI/QuartzCore/CAChameleonLayer.h @@ -0,0 +1,23 @@ +// +// CAChameleonLayer.h +// COpenSwiftUI +// +// Audited for macOS 15.0 +// Status: WIP + +#include "OpenSwiftUIBase.h" + +#if OPENSWIFTUI_TARGET_OS_OSX + +#import + +OPENSWIFTUI_ASSUME_NONNULL_BEGIN + +@interface CAChameleonLayer : CALayer + +@end + +OPENSWIFTUI_ASSUME_NONNULL_END + +#endif /* OPENSWIFTUI_TARGET_OS_OSX */ + diff --git a/Sources/COpenSwiftUI/QuartzCore/CAChameleonLayer.m b/Sources/COpenSwiftUI/QuartzCore/CAChameleonLayer.m new file mode 100644 index 00000000..ff83704d --- /dev/null +++ b/Sources/COpenSwiftUI/QuartzCore/CAChameleonLayer.m @@ -0,0 +1,9 @@ +// +// CAChameleonLayer.m +// OpenSwiftUI +// +// Created by JH on 2025/5/14. +// + +#import "CAChameleonLayer.h" + diff --git a/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSGraphicsView.swift b/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSGraphicsView.swift new file mode 100644 index 00000000..bb3ec8e3 --- /dev/null +++ b/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSGraphicsView.swift @@ -0,0 +1,27 @@ +// +// NSGraphicsView.swift +// OpenSwiftUI +// +// Audited for macOS 15.0 +// Status: WIP + +#if os(macOS) + +import OpenSwiftUI_SPI +import AppKit + +final class _NSGraphicsView: NSView { + var recursiveIgnoreHitTest: Bool = false + + var customAcceptsFirstMouse: Bool? + + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } +} + +#endif diff --git a/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSHostingView.swift b/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSHostingView.swift index 35caa88b..9cec921b 100644 --- a/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSHostingView.swift +++ b/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSHostingView.swift @@ -127,6 +127,7 @@ open class NSHostingView: NSView, XcodeViewDebugDataProvider where Cont // TODO wantsLayer = true // TODO + renderer.host = self HostingViewRegistry.shared.add(self) // TODO Update.end() @@ -182,7 +183,7 @@ open class NSHostingView: NSView, XcodeViewDebugDataProvider where Cont context.allowsImplicitAnimation = false isUpdating = true // TODO - render() + render(targetTimestamp: Time()) // TODO isUpdating = false // TODO @@ -318,8 +319,67 @@ extension NSHostingView: ViewRendererHost { package func updateScrollableContainerSize() {} - package func renderDisplayList(_ list: DisplayList, asynchronously: Bool, time: Time, nextTime: Time, targetTimestamp: Time?, version: DisplayList.Version, maxVersion: DisplayList.Version) -> Time { - .infinity + package func renderDisplayList( + _ list: DisplayList, + asynchronously: Bool, + time: Time, + nextTime: Time, + targetTimestamp: Time?, + version: DisplayList.Version, + maxVersion: DisplayList.Version + ) -> Time { + func render() -> Time { + let scale = window?.screen?.backingScaleFactor ?? 1 + let environment = DisplayList.ViewRenderer.Environment(contentsScale: scale, opaqueBackground: isOpaque) + #if canImport(SwiftUI, _underlyingVersion: 6.0.87) && _OPENSWIFTUI_SWIFTUI_RENDER + + return renderer.swiftUI_render( + rootView: self, + from: list, + time: time, + nextTime: nextTime, + version: version, + maxVersion: maxVersion, + environment: environment + ) + + #else + return renderer.render( + rootView: self, + from: list, + time: time, + nextTime: nextTime, + version: version, + maxVersion: maxVersion, + environment: environment + ) + #endif + } + + if asynchronously { + if let renderedTime = renderer.renderAsync( + to: list, + time: time, + nextTime: nextTime, + targetTimestamp: targetTimestamp, + version: version, + maxVersion: maxVersion + ) { + return renderedTime + } else { + var renderedTime = nextTime + Update.syncMain { + renderedTime = render() + } + return renderedTime + } + } else { + var renderedTime = nextTime + Update.syncMain { + renderedTime = render() + } + return renderedTime + } } package func updateRootView() { diff --git a/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSInheritedView.swift b/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSInheritedView.swift new file mode 100644 index 00000000..c80b7346 --- /dev/null +++ b/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSInheritedView.swift @@ -0,0 +1,25 @@ +// +// NSInheritedView.swift +// OpenSwiftUI +// +// Audited for macOS 15.0 +// Status: WIP + +#if os(macOS) + +import OpenSwiftUI_SPI +import AppKit + +final class _NSInheritedView: NSView { + var hitTestsAsOpaque: Bool = false + + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } +} + +#endif diff --git a/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSProjectionView.swift b/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSProjectionView.swift new file mode 100644 index 00000000..f5497c38 --- /dev/null +++ b/Sources/OpenSwiftUI/Integration/Hosting/AppKit/View/NSProjectionView.swift @@ -0,0 +1,26 @@ +// +// NSProjectionView.swift +// OpenSwiftUI +// +// Audited for macOS 15.0 +// Status: WIP + +#if os(macOS) + +import OpenSwiftUI_SPI +import AppKit + +final class _NSProjectionView: NSView { + + override var wantsUpdateLayer: Bool { true } + + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } +} + +#endif diff --git a/Sources/OpenSwiftUI/Integration/Render/AppKit/NSViewPlatformViewDefinition.swift b/Sources/OpenSwiftUI/Integration/Render/AppKit/NSViewPlatformViewDefinition.swift index 7afc54ea..aee03f7a 100644 --- a/Sources/OpenSwiftUI/Integration/Render/AppKit/NSViewPlatformViewDefinition.swift +++ b/Sources/OpenSwiftUI/Integration/Render/AppKit/NSViewPlatformViewDefinition.swift @@ -8,16 +8,54 @@ #if os(macOS) @_spi(DisplayList_ViewSystem) import OpenSwiftUICore import AppKit +import OpenSwiftUISymbolDualTestsSupport +import COpenSwiftUI // TODO final class NSViewPlatformViewDefinition: PlatformViewDefinition, @unchecked Sendable { override final class var system: PlatformViewDefinition.System { .nsView } override static func makeView(kind: PlatformViewDefinition.ViewKind) -> AnyObject { - // TODO - return NSView() + let view: NSView + switch kind { + case .chameleonColor: + return makeLayerView(type: CAChameleonLayer.self, kind: kind) + case .projection: + view = _NSProjectionView() + case .mask: + view = _NSGraphicsView() + view.mask = _NSInheritedView() + initView(view.mask!, kind: kind) + 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 + } + } + override static func makeLayerView(type: CALayer.Type, kind: PlatformViewDefinition.ViewKind) -> AnyObject { preconditionFailure("TODO") } diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift index c51395db..63ade70d 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift @@ -22,12 +22,23 @@ extension DisplayList { final public class ViewRenderer { package struct Environment: Equatable { package var contentsScale: CGFloat - + + #if os(macOS) + package var opaqueBackground: Bool = false + #endif + package static let invalid = Environment(contentsScale: .zero) package init(contentsScale: CGFloat) { self.contentsScale = contentsScale } + + #if os(macOS) + package init(contentsScale: CGFloat, opaqueBackground: Bool) { + self.contentsScale = contentsScale + self.opaqueBackground = opaqueBackground + } + #endif } let platform: DisplayList.ViewUpdater.Platform diff --git a/Sources/OpenSwiftUICore/Render/RendererLeafView.swift b/Sources/OpenSwiftUICore/Render/RendererLeafView.swift index 13d9e85a..efe1ebf0 100644 --- a/Sources/OpenSwiftUICore/Render/RendererLeafView.swift +++ b/Sources/OpenSwiftUICore/Render/RendererLeafView.swift @@ -139,8 +139,14 @@ private struct LeafDisplayList: StatefulRule, CustomStringConvertible where V ) item.canonicalize(options: options) #if _OPENSWIFTUI_SWIFTUI_RENDER + // FIXME: Remove me after Layout system is implemented + #if os(macOS) + item.frame = CGRect(x: 0, y: 0, width: 500, height: 300) + #elseif os(iOS) item.frame = CGRect(x: 0, y: 100.333, width: 402, height: 739) + #endif + #endif value = DisplayList(item) } From 6967387946794cb66bd7f6a758b52944d081ccf9 Mon Sep 17 00:00:00 2001 From: Mx-Iris Date: Sun, 18 May 2025 01:04:57 +0800 Subject: [PATCH 2/2] Fix file headers --- Sources/COpenSwiftUI/AppKit/OpenSwiftUI+NSView.m | 2 +- Sources/COpenSwiftUI/QuartzCore/CAChameleonLayer.m | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/COpenSwiftUI/AppKit/OpenSwiftUI+NSView.m b/Sources/COpenSwiftUI/AppKit/OpenSwiftUI+NSView.m index 3aa516ab..a36d4ec0 100644 --- a/Sources/COpenSwiftUI/AppKit/OpenSwiftUI+NSView.m +++ b/Sources/COpenSwiftUI/AppKit/OpenSwiftUI+NSView.m @@ -1,5 +1,5 @@ // -// __FILEBASENAME__.m +// OpenSwiftUI+NSView.m // COpenSwiftUI // // Audited for macOS 15.0 diff --git a/Sources/COpenSwiftUI/QuartzCore/CAChameleonLayer.m b/Sources/COpenSwiftUI/QuartzCore/CAChameleonLayer.m index ff83704d..785f294a 100644 --- a/Sources/COpenSwiftUI/QuartzCore/CAChameleonLayer.m +++ b/Sources/COpenSwiftUI/QuartzCore/CAChameleonLayer.m @@ -1,9 +1,9 @@ // // CAChameleonLayer.m -// OpenSwiftUI -// -// Created by JH on 2025/5/14. +// COpenSwiftUI // +// Audited for macOS 15.0 +// Status: WIP #import "CAChameleonLayer.h"