From 0e480bee307430f641d554c3a716f20748518974 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 18 May 2026 16:13:41 -0700
Subject: [PATCH] visionOS: re-enable dimming mode
---
.../uikit/SDL_CurvedContentHosting.swift | 48 ++++++++++---------
src/video/uikit/SDL_CurvedContentView.swift | 13 ++++-
src/video/uikit/SDL_UIKitBridge-objc.h | 5 ++
src/video/uikit/SDL_UIKitBridge.m | 22 +++++++++
src/video/uikit/SDL_uikitwindow.m | 4 ++
5 files changed, 69 insertions(+), 23 deletions(-)
diff --git a/src/video/uikit/SDL_CurvedContentHosting.swift b/src/video/uikit/SDL_CurvedContentHosting.swift
index 3648c391e8a2f..88d95499edd6c 100644
--- a/src/video/uikit/SDL_CurvedContentHosting.swift
+++ b/src/video/uikit/SDL_CurvedContentHosting.swift
@@ -151,6 +151,16 @@ internal class SDL_CurvedContentHosting: NSObject {
//NSLog("SDL_CurvedContentHosting: Bootstrapping RealityView as hidden child")
}
+ @objc public func dismiss() {
+ guard let hc = self.hostingController else { return }
+
+ settings.dimmingReady = false
+
+ hc.dismiss(animated: false)
+
+ hostingController = nil
+ }
+
private func updateOrnaments() {
guard let hostingController else { return }
let settings = self.settings
@@ -263,8 +273,8 @@ internal class SDL_CurvedContentSettings {
var inputType: InputType = .eyes
var showHover: Bool = true
- var enableDimming: Bool = false // Doesn't seem to be reliable at the moment
var isDimmed: Bool = false
+ var dimmingReady: Bool = false
var curvatureRadius: Float = 0.0
var sceneState: SceneState = .interactive
var isSnapped: Bool = false
@@ -320,10 +330,8 @@ struct SDL_SettingsPanelView: View {
HStack(spacing: 12) {
Image(systemName: settings.showHover ? "eye" : "eye.slash")
- if settings.enableDimming {
- Image(systemName: settings.isDimmed ? "moon.fill" : "sun.max")
- .foregroundStyle(settings.isDimmed ? .primary : .secondary)
- }
+ Image(systemName: settings.isDimmed ? "moon.fill" : "sun.max")
+ .foregroundStyle(settings.isDimmed ? .primary : .secondary)
Divider().frame(height: 8)
@@ -344,10 +352,8 @@ struct SDL_SettingsPanelView: View {
VStack(spacing: 12) {
Image(systemName: settings.showHover ? "eye" : "eye.slash")
- if settings.enableDimming {
- Image(systemName: settings.isDimmed ? "moon.fill" : "sun.max")
- .foregroundStyle(settings.isDimmed ? .primary : .secondary)
- }
+ Image(systemName: settings.isDimmed ? "moon.fill" : "sun.max")
+ .foregroundStyle(settings.isDimmed ? .primary : .secondary)
Divider().frame(height: 8)
@@ -394,21 +400,19 @@ struct SDL_SettingsPanelView: View {
Image(systemName: "eye")
Spacer()
- if settings.enableDimming {
- Spacer()
- Image(systemName: "sun.max")
-
- Toggle(isOn: $settings.isDimmed) {
- }
- .onChange(of: settings.isDimmed) {
- settings.save()
- }
- .labelsHidden()
- .tint(.secondary)
+ Spacer()
+ Image(systemName: "sun.max")
- Image(systemName: "moon.fill")
- Spacer()
+ Toggle(isOn: $settings.isDimmed) {
+ }
+ .onChange(of: settings.isDimmed) {
+ settings.save()
}
+ .labelsHidden()
+ .tint(.secondary)
+
+ Image(systemName: "moon.fill")
+ Spacer()
}
// Curvature slider
diff --git a/src/video/uikit/SDL_CurvedContentView.swift b/src/video/uikit/SDL_CurvedContentView.swift
index 275a85f562ccc..d7c0abdac666b 100644
--- a/src/video/uikit/SDL_CurvedContentView.swift
+++ b/src/video/uikit/SDL_CurvedContentView.swift
@@ -71,6 +71,10 @@ internal struct SDL_CurvedContentView: View {
return curvedUIEntity != nil && helper.collisionShape != nil && !mouseInputEnabled
}
+ private var shouldEnableDimming: Bool {
+ return settings.isDimmed && settings.dimmingReady
+ }
+
/// Value use to animate the screen radius
@State private var animatedScreenRadius: Float = 1010
@@ -265,6 +269,13 @@ internal struct SDL_CurvedContentView: View {
guard let curvedUIEntity else { return }
if let shape = helper.collisionShape, shouldPopulateCollisionShape {
curvedUIEntity.components.set(CollisionComponent(shapes: [shape]))
+
+ // Dimming is possible now that we have a collision component
+ Task {
+ try await Task.sleep(nanoseconds: 1_000_000_000 / 4)
+
+ settings.dimmingReady = true
+ }
} else {
curvedUIEntity.components.set(CollisionComponent(shapes: []))
}
@@ -273,7 +284,7 @@ internal struct SDL_CurvedContentView: View {
settings.isSnapped = snappedStatus.isSnapped
helper.updateSnappedStatus(snapped: snappedStatus.isSnapped)
}
- .preferredSurroundingsEffect(settings.enableDimming && settings.isDimmed ? .dark : nil)
+ .preferredSurroundingsEffect(shouldEnableDimming ? .dark : nil)
.frame(depth: 0)
.ignoresSafeArea()
.persistentSystemOverlays(settings.sceneState == .cinematic ? .hidden : .automatic)
diff --git a/src/video/uikit/SDL_UIKitBridge-objc.h b/src/video/uikit/SDL_UIKitBridge-objc.h
index db80a8cb5dbbc..676e19a6bc11a 100644
--- a/src/video/uikit/SDL_UIKitBridge-objc.h
+++ b/src/video/uikit/SDL_UIKitBridge-objc.h
@@ -42,6 +42,11 @@ bool SDL_UIKit_HasCurvedWindow();
*/
bool SDL_UIKit_IsCurvedWindow(SDL_Window *window);
+/**
+ * Dismiss the curved content view
+ */
+void SDL_UIKit_HideCurvedWindow(SDL_Window *window);
+
/**
* Get the curved content display texture.
*/
diff --git a/src/video/uikit/SDL_UIKitBridge.m b/src/video/uikit/SDL_UIKitBridge.m
index e869d4b22c856..a7f30aa30cf55 100644
--- a/src/video/uikit/SDL_UIKitBridge.m
+++ b/src/video/uikit/SDL_UIKitBridge.m
@@ -152,6 +152,28 @@ bool SDL_UIKit_IsCurvedWindow(SDL_Window *window)
return data && data.curvedContentHosting;
}
+void SDL_UIKit_HideCurvedWindow(SDL_Window *window)
+{
+ SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
+ if (!data || !data.curvedContentHosting) {
+ return nil;
+ }
+
+ id hosting = data.curvedContentHosting;
+ SEL dismissSelector = NSSelectorFromString(@"dismiss");
+ if (![hosting respondsToSelector:dismissSelector]) {
+ return nil;
+ }
+
+ NSMethodSignature *signature = [hosting methodSignatureForSelector:dismissSelector];
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
+ [invocation setSelector:dismissSelector];
+ [invocation setTarget:hosting];
+ [invocation invoke];
+
+ data.curvedContentHosting = nil;
+}
+
id<MTLTexture> SDL_UIKit_GetCurvedDisplayTexture(SDL_Window *window, id<MTLCommandBuffer> commandBuffer, int width, int height, MTLPixelFormat pixelFormat)
{
SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
diff --git a/src/video/uikit/SDL_uikitwindow.m b/src/video/uikit/SDL_uikitwindow.m
index 5523d6c55f07e..43076e8a40753 100644
--- a/src/video/uikit/SDL_uikitwindow.m
+++ b/src/video/uikit/SDL_uikitwindow.m
@@ -33,6 +33,7 @@
#include "SDL_uikitappdelegate.h"
#include "SDL_uikitview.h"
#include "SDL_uikitopenglview.h"
+#include "SDL_UIKitBridge-objc.h"
#include <Foundation/Foundation.h>
@@ -386,6 +387,9 @@ void UIKit_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
[data.viewcontroller stopAnimation];
+#ifdef SDL_PLATFORM_VISIONOS
+ SDL_UIKit_HideCurvedWindow(window);
+#endif
/* Detach all views from this window. We use a copy of the array
* because setSDLWindow will remove the object from the original
* array, which would be undesirable if we were iterating over it. */