Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c630740
feat(button): update button style API and configuration
Kyle-Ye Oct 6, 2026
b4948dc
feat(button): update button role API
Kyle-Ye Oct 6, 2026
bd5384b
feat(button): update primitive button style API
Kyle-Ye Oct 6, 2026
0cbc7eb
feat(button): add wrapped style and body resolution
Kyle-Ye Oct 6, 2026
cea7f50
feat(button): implement button border shapes
Kyle-Ye Oct 6, 2026
ec9a94f
feat(app-intent): implement action execution
Kyle-Ye Oct 7, 2026
5579d21
feat(button): implement plain button style
Kyle-Ye Oct 7, 2026
6264e02
feat(button): implement default rendering mode
Kyle-Ye Oct 7, 2026
5d4710c
feat(button): implement borderless button style
Kyle-Ye Oct 7, 2026
aa7e266
feat(button): add bordered button specs
Kyle-Ye Oct 7, 2026
f85fae1
feat(button): implement AppKit button style
Kyle-Ye Oct 7, 2026
f86b4c0
feat(toggle): implement button toggle style
Kyle-Ye Oct 7, 2026
537707d
feat(button): implement widget bordered styles
Kyle-Ye Oct 7, 2026
f3c3ded
feat(button): implement bordered prominent style
Kyle-Ye Oct 7, 2026
006c18f
feat(button): implement bordered button style
Kyle-Ye Oct 7, 2026
fcc5889
feat(button): implement default button style
Kyle-Ye Oct 7, 2026
211fed8
feat(button): update platform bordered button style
Kyle-Ye Oct 7, 2026
c9a88d5
feat(button): implement button repeat behavior
Kyle-Ye Oct 7, 2026
71951a6
feat(button): implement button APIs
Kyle-Ye Oct 7, 2026
2a6df4c
feat(gesture): add StaticIf gesture support
Kyle-Ye Oct 9, 2026
59f9421
build(deps): use Compute 0.6.0-bugfix.1
Kyle-Ye Oct 11, 2026
b815669
fix(accessibility): build large content view preferences
Kyle-Ye Oct 11, 2026
ba75c7f
feat(button): add platform item selection support
Kyle-Ye Oct 11, 2026
89f74e6
feat(button): implement wrapped style interactions
Kyle-Ye Oct 11, 2026
0207857
feat(gesture): implement button gestures
Kyle-Ye Oct 11, 2026
ef3a414
feat(gesture): add pressable gesture callbacks
Kyle-Ye Oct 11, 2026
b6fe750
build(deps): update DarwinPrivateFrameworks and OpenRenderBox
Kyle-Ye Oct 11, 2026
f1fa904
build(deps): update DarwinPrivateFrameworks and OpenRenderBox
Kyle-Ye Oct 11, 2026
5b5e46d
feat(text): implement keyed text rendering
Kyle-Ye Oct 11, 2026
28915da
test(button): add shared example and interaction tests
Kyle-Ye Oct 11, 2026
33e93ed
fix(button): guard native styles with canImport(Darwin)
Kyle-Ye Oct 11, 2026
686fee0
fix(button): read content style through the AppKit host
Kyle-Ye Oct 11, 2026
648dbf6
fix(button): install action environments and preserve fonts
Kyle-Ye Oct 12, 2026
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
3 changes: 3 additions & 0 deletions .github/workflows/stdout_renderer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ jobs:
OPENSWIFTUI_OPENATTRIBUTESHIMS_ATTRIBUTEGRAPH: ${{ matrix.backend == 'AttributeGraph' && '1' || '0' }}
OPENSWIFTUI_OPENATTRIBUTESHIMS_COMPUTE: ${{ matrix.backend == 'Compute' && '1' || '0' }}
OPENSWIFTUI_OPENATTRIBUTESHIMS_COMPUTE_BINARY: 1
OPENSWIFTUI_OPENATTRIBUTESHIMS_COMPUTE_BINARY_REPO: "OpenSwiftUIProject/Compute"
OPENSWIFTUI_OPENATTRIBUTESHIMS_COMPUTE_BINARY_VERSION: "0.6.0-bugfix.1"
OPENSWIFTUI_OPENATTRIBUTESHIMS_COMPUTE_BINARY_CHECKSUM: "faa4f1598a8b7850a3f68b1b2588ecb15a474e9325195f9fd8a274e3e6132f35"
GH_TOKEN: ${{ github.token }}
steps:
- name: Mark PR status running
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
OPENSWIFTUI_OPENATTRIBUTESHIMS_ATTRIBUTEGRAPH: 0
OPENSWIFTUI_OPENATTRIBUTESHIMS_COMPUTE: ${{ matrix.backend == 'IAG' && '1' || '0' }}
OPENSWIFTUI_OPENATTRIBUTESHIMS_COMPUTE_BINARY: 0
OPENSWIFTUI_OPENATTRIBUTESHIMS_COMPUTE_SOURCE_VERSION: "0.6.0"
OPENSWIFTUI_OPENATTRIBUTESHIMS_COMPUTE_SOURCE_VERSION: "0.6.0-bugfix.1"
OPENSWIFTUI_COMPATIBILITY_TEST: 0
OPENSWIFTUI_SWIFT_LOG: 1
OPENSWIFTUI_SWIFT_CRYPTO: 1
Expand Down
29 changes: 29 additions & 0 deletions Example/Shared/View/ButtonExample.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// ButtonExample.swift
// Shared

#if OPENSWIFTUI
import OpenSwiftUI
#else
import SwiftUI
#endif

struct ButtonExample: View {
var onPress: (Int) -> Void = { _ in }

@State private var count = 0

var body: some View {
Button {
count += 1
onPress(count)
} label: {
Text("Count: \(count)")
.frame(minWidth: 120, minHeight: 44)
}
}
}

#Preview {
ButtonExample()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// ButtonInteractionTests.swift
// OpenSwiftUIInteractionTests

#if OPENSWIFTUI
import OpenSwiftUI
#else
import SwiftUI
#endif
import SnapshotTesting
import Testing
@testable import TestingHost

@MainActor
@Suite(.serialized, .tags(.interaction), .snapshots(record: .never, diffTool: diffTool))
struct ButtonInteractionTests {
@Test
func actionUpdatesCount() async throws {
var counts: [Int] = []
let content = ButtonExample { counts.append($0) }

try await withInteractionTestHost(of: content) { host in
#expect(counts.isEmpty)
try await host.assertSnapshot(named: "initial")

for count in 1...2 {
try await host.tap()
try await host.waitUntil(counts.count >= count)
// Capture the idle appearance after the button's release animation.
try await Task.sleep(for: .milliseconds(600))
try await host.assertSnapshot(named: "tap-\(count)")
#expect(counts == Array(1...count))
}
}
}

@Test
func disabledButtonDoesNotRunAction() async throws {
var counts: [Int] = []
let content = ButtonExample { counts.append($0) }
.disabled(true)

try await withInteractionTestHost(of: content) { host in
try await host.assertSnapshot(named: "initial")

for count in 1...2 {
try await host.tap()
try await Task.sleep(for: .milliseconds(600))
try await host.assertSnapshot(named: "tap-\(count)")
#expect(counts.isEmpty)
}
}
}
}
4 changes: 2 additions & 2 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Renderer/Stdout/configure-env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Darwin)
*)
export OPENSWIFTUI_OPENATTRIBUTESHIMS_ATTRIBUTEGRAPH="${OPENSWIFTUI_OPENATTRIBUTESHIMS_ATTRIBUTEGRAPH:-0}"
export OPENSWIFTUI_OPENATTRIBUTESHIMS_COMPUTE="${OPENSWIFTUI_OPENATTRIBUTESHIMS_COMPUTE:-1}"
export OPENSWIFTUI_OPENATTRIBUTESHIMS_COMPUTE_SOURCE_VERSION="${OPENSWIFTUI_OPENATTRIBUTESHIMS_COMPUTE_SOURCE_VERSION:-0.6.0}"
export OPENSWIFTUI_OPENATTRIBUTESHIMS_COMPUTE_SOURCE_VERSION="${OPENSWIFTUI_OPENATTRIBUTESHIMS_COMPUTE_SOURCE_VERSION:-0.6.0-bugfix.1}"
if [[ -z "${OPENSWIFTUI_LIB_SWIFT_PATH:-}" ]]; then
swift_binary="$(readlink -f "$(command -v swift)")"
if [[ "$(basename "$swift_binary")" == "swiftly" ]]; then
Expand Down
2 changes: 1 addition & 1 deletion Scripts/CI/compute_setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ clone_checkout_compute() {
revision=$(Scripts/CI/get_revision.sh compute 2>/dev/null || true)
fi
if [ -z "$revision" ]; then
revision="0.6.0"
revision="0.6.0-bugfix.1"
fi
cd ..
if [ ! -d Compute ]; then
Expand Down
1 change: 1 addition & 0 deletions Sources/COpenSwiftUI/Shims/AppKit/AppKit_Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#if __has_include(<AppKit/AppKit.h>)

#import <AppKit/AppKit.h>
#import "NSButton_Private.h"

OPENSWIFTUI_ASSUME_NONNULL_BEGIN

Expand Down
36 changes: 36 additions & 0 deletions Sources/COpenSwiftUI/Shims/AppKit/NSButton_Private.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// NSButton_Private.h
// OpenSwiftUI_SPI
//
// Audited for macOS 15.5

#ifndef NSButton_Private_h
#define NSButton_Private_h

#include "OpenSwiftUIBase.h"

#if __has_include(<AppKit/AppKit.h>)
#include <AppKit/AppKit.h>

OPENSWIFTUI_ASSUME_NONNULL_BEGIN

@interface NSControl (OpenSwiftUI_SPI)
- (void)addTarget:(id)target action:(SEL)action forControlEvents:(unsigned long long)events;
- (void)removeTarget:(id)target action:(SEL)action forControlEvents:(unsigned long long)events;
@end

@interface NSButton (OpenSwiftUI_SPI)
@property (nullable, strong) NSView *contentView;
@property NSInteger toolbarAppearance;
- (int)effectiveVibrancyBlendModeForAppearance:(NSAppearance *)appearance;
- (void)_setUsesCautionaryAppearanceWhenActionIsDestructive:(BOOL)destructive;
- (BOOL)_getIntrinsicArtworkSize:(CGSize *)size
alignmentRectInsets:(NSEdgeInsets *)alignmentInsets
idealContentInsets:(NSEdgeInsets *)contentInsets
maxContentInsets:(nullable NSEdgeInsets *)maxContentInsets;
@end

OPENSWIFTUI_ASSUME_NONNULL_END
#endif

#endif /* NSButton_Private_h */
15 changes: 15 additions & 0 deletions Sources/COpenSwiftUI/Shims/AppKit/NSView_Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,26 @@
#if __has_include(<AppKit/AppKit.h>)

#include <AppKit/AppKit.h>
#include "_NSConstraintBasedLayoutHostingView.h"

@protocol NSContentStyle <NSObject, NSCopying>
@property (nullable, readonly) NSColor *equivalentForegroundColorForTemplateImage;
@property (readonly) int outputBlendModeForTemplateContent;
@end

@interface NSView (OpenSwiftUI_SPI)
@property (getter=isOpaque) BOOL opaque;
@property (nonatomic, assign, readonly) NSEdgeInsets computedSafeAreaInsets;
@property (nonatomic) BOOL ignoreHitTest_openswiftui_safe_wrapper OPENSWIFTUI_SWIFT_NAME(ignoreHitTest);
@property (nullable, copy) id<NSContentStyle> contentStyle;
@property NSUserInterfaceLayoutDirection userInterfaceLayoutDirection;

- (BOOL)_userInterfaceLayoutDirectionPropagatesToDescendants;
- (void)_setUserInterfaceLayoutDirectionPropagatesToDescendants:(BOOL)propagates;
- (void)_setVibrantBlendingStyleForSubtree:(NSUInteger)style;

- (BaselineOffset)_baselineOffsetsAtSize:(CGSize)size;
- (nullable NSView *)designatedFocusRingView OPENSWIFTUI_SWIFT_NAME(designatedFocusRing());

- (nullable NSResponder *)_nextResponderForEvent:(nullable NSEvent *)event;
- (nonnull id)_observerForChangesInGeometryInWindow:(void (^ _Nonnull)(NSView * _Nonnull view))block;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,14 @@ enum AccessibilityLargeContentViewTree: Equatable {
case empty

var hasValue: Bool {
// TODO: Check whether the tree contains an enabled large content item.
_openSwiftUIUnimplementedFailure()
switch self {
case let .leaf(item):
item.behavior == .enabled
case let .branch(children):
children.contains { $0.hasValue }
case .empty:
false
}
}

func hitTest(at point: CGPoint) -> AccessibilityLargeContentViewItem? {
Expand Down Expand Up @@ -203,7 +209,7 @@ struct AccessibilityLargeContentViewItem: Equatable {
var behavior: AccessibilityLargeContentViewBehavior
}

// MARK: - AccessibilityLargeContentViewModifier [WIP]
// MARK: - AccessibilityLargeContentViewModifier

private struct AccessibilityLargeContentViewModifier<Content: View>: MultiViewModifier, PrimitiveViewModifier {
var behavior: AccessibilityLargeContentViewBehavior
Expand All @@ -214,7 +220,26 @@ private struct AccessibilityLargeContentViewModifier<Content: View>: MultiViewMo
inputs: _ViewInputs,
body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs
) -> _ViewOutputs {
_openSwiftUIUnimplementedFailure()
let generator = PlatformItemListGenerator(
flags: LabelPlatformItemListFlags.self,
content: modifier.value[offset: { .of(&$0.largeContentView) }],
inputs: inputs,
inputsIncludeGeometry: true
)
var outputs = body(_Graph(), inputs)
let platformItemList = Attribute(generator)
outputs.preferences.makePreferenceTransformer(
inputs: inputs.preferences,
key: AccessibilityLargeContentViewTree.Key.self,
transform: Attribute(AccessibilityLargeContentViewTransform(
behavior: modifier.value[offset: { .of(&$0.behavior) }],
platformItemList: platformItemList,
size: inputs.size,
position: inputs.position,
transform: inputs.transform
))
)
return outputs
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ extension AccessibilityCore {
}
}

private extension NSView {
extension NSView {
var enclosingViewRendererHost: (any ViewRendererHost)? {
if let host = self as? any ViewRendererHost {
return host
Expand Down
24 changes: 21 additions & 3 deletions Sources/OpenSwiftUI/Data/Environment/AppIntentAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// OpenSwiftUI
//
// Audited for 6.5.4
// Status: WIP
// Status: Complete

import COpenSwiftUI
import Foundation
Expand All @@ -14,13 +14,31 @@ import Logging
import os.log
#endif

// MARK: - AppIntentAction [TODO]
// MARK: - AppIntentAction

struct AppIntentAction {
struct AppIntentAction: DynamicProperty {
@Environment(\.appIntentExecutor) private var appIntentExecutor
var lnAction: NSObject
var defaultExecutor: @MainActor () -> Void

init(lnAction: NSObject, defaultExecutor: @escaping @MainActor () -> Void) {
self.lnAction = lnAction
self.defaultExecutor = defaultExecutor
}

func perform() {
MainActor.assumeIsolatedIfLinkedOnOrAfter(
.v7,
context: "App Intent expects to run on the main actor."
) {
if let appIntentExecutor {
Comment thread
Kyle-Ye marked this conversation as resolved.
Comment thread
Kyle-Ye marked this conversation as resolved.
appIntentExecutor.perform(lnAction)
} else {
defaultExecutor()
}
}
}

static let logger = Logger(subsystem: "org.openswiftuiproject.openswiftui", category: "appintent")

enum Error: Swift.Error {
Expand Down
Loading
Loading