Skip to content

Add initial rendering support for NSHostingView #291

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 18, 2025
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
31 changes: 31 additions & 0 deletions Sources/COpenSwiftUI/AppKit/OpenSwiftUI+NSView.h
Original file line number Diff line number Diff line change
@@ -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 <AppKit/AppKit.h>

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 */
12 changes: 12 additions & 0 deletions Sources/COpenSwiftUI/AppKit/OpenSwiftUI+NSView.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// OpenSwiftUI+NSView.m
// COpenSwiftUI
//
// Audited for macOS 15.0
// Status: WIP

#import "OpenSwiftUI+NSView.h"

#if OPENSWIFTUI_TARGET_OS_OSX

#endif
23 changes: 23 additions & 0 deletions Sources/COpenSwiftUI/QuartzCore/CAChameleonLayer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// CAChameleonLayer.h
// COpenSwiftUI
//
// Audited for macOS 15.0
// Status: WIP

#include "OpenSwiftUIBase.h"

#if OPENSWIFTUI_TARGET_OS_OSX

#import <QuartzCore/QuartzCore.h>

OPENSWIFTUI_ASSUME_NONNULL_BEGIN

@interface CAChameleonLayer : CALayer

@end

OPENSWIFTUI_ASSUME_NONNULL_END

#endif /* OPENSWIFTUI_TARGET_OS_OSX */

9 changes: 9 additions & 0 deletions Sources/COpenSwiftUI/QuartzCore/CAChameleonLayer.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//
// CAChameleonLayer.m
// COpenSwiftUI
//
// Audited for macOS 15.0
// Status: WIP

#import "CAChameleonLayer.h"

Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ open class NSHostingView<Content>: NSView, XcodeViewDebugDataProvider where Cont
// TODO
wantsLayer = true
// TODO
renderer.host = self
HostingViewRegistry.shared.add(self)
// TODO
Update.end()
Expand Down Expand Up @@ -182,7 +183,7 @@ open class NSHostingView<Content>: NSView, XcodeViewDebugDataProvider where Cont
context.allowsImplicitAnimation = false
isUpdating = true
// TODO
render()
render(targetTimestamp: Time())
// TODO
isUpdating = false
// TODO
Expand Down Expand Up @@ -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 {
Copy link
Preview

Copilot AI May 17, 2025

Choose a reason for hiding this comment

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

[nitpick] The nested render() function increases the complexity of renderDisplayList. Consider extracting it into a separate method to improve readability and maintainability.

Suggested change
func render() -> Time {
private func renderDisplayListSynchronously(
list: DisplayList,
time: Time,
nextTime: Time,
version: DisplayList.Version,
maxVersion: DisplayList.Version
) -> Time {

Copilot uses AI. Check for mistakes.

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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions Sources/OpenSwiftUICore/Render/RendererLeafView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,14 @@ private struct LeafDisplayList<V>: 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)
Comment on lines +145 to 147
Copy link
Preview

Copilot AI May 17, 2025

Choose a reason for hiding this comment

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

The temporary hard-coded frame values may hinder adaptability across different screen sizes. Consider deferring to the layout system or using configurable parameters once implemented.

Suggested change
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)
let defaultSize = CGSize(width: 500, height: 300)
item.frame = CGRect(origin: .zero, size: proposedSize.or(defaultSize))
#elseif os(iOS)
let defaultSize = CGSize(width: 402, height: 739)
item.frame = CGRect(origin: CGPoint(x: 0, y: 100.333), size: proposedSize.or(defaultSize))

Copilot uses AI. Check for mistakes.

#endif

#endif
value = DisplayList(item)
}
Expand Down