diff --git a/Sources/OpenSwiftUICore/Accessibility/AccessibilityAnnouncementPriority.swift b/Sources/OpenSwiftUICore/Accessibility/AccessibilityAnnouncementPriority.swift new file mode 100644 index 000000000..fb5bb1749 --- /dev/null +++ b/Sources/OpenSwiftUICore/Accessibility/AccessibilityAnnouncementPriority.swift @@ -0,0 +1,94 @@ +// +// AccessibilityAnnouncementPriority.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Blocked by Text.Style + +// MARK: - AccessibilityAnnouncementPriority + +@_spi(_) +@available(OpenSwiftUI_v5_0, *) +public enum AccessibilityAnnouncementPriority: Sendable { + case low + + case `default` + + case high +} + +@_spi(_) +extension AccessibilityAnnouncementPriority { + package var platformRawValue: String { + if isUIKitBased() { + switch self { + case .low: "UIAccessibilityPriorityLow" + case .default: "UIAccessibilityPriorityDefault" + case .high: "UIAccessibilityPriorityHigh" + } + } else { + switch self { + case .low: "low" + case .default: "default" + case .high: "high" + } + } + } +} + +// MARK: - AccessibilitySpeechAttributes + +package struct AccessibilitySpeechAttributes: Equatable { + package var alwaysIncludesPunctuation: Bool? + + package var spellsOutCharacters: Bool? + + package var adjustedPitch: Double? + + package var announcementsPriority: AccessibilityAnnouncementPriority? + + package var phoneticRepresentation: String? + + package init( + alwaysIncludesPunctuation: Bool? = nil, + spellsOutCharacters: Bool? = nil, + adjustedPitch: Double? = nil, + announcementsPriority: AccessibilityAnnouncementPriority? = nil, + phoneticRepresentation: String? = nil + ) { + self.alwaysIncludesPunctuation = alwaysIncludesPunctuation + self.spellsOutCharacters = spellsOutCharacters + self.adjustedPitch = adjustedPitch + self.announcementsPriority = announcementsPriority + self.phoneticRepresentation = phoneticRepresentation + } + + package func applyTo(environment: inout EnvironmentValues) { + environment.speechAlwaysIncludesPunctuation = alwaysIncludesPunctuation + environment.speechSpellsOutCharacters = spellsOutCharacters + environment.speechAdjustedPitch = adjustedPitch + environment.speechAnnouncementsPriority = announcementsPriority + } + + package func combined(with other: AccessibilitySpeechAttributes) -> AccessibilitySpeechAttributes { + AccessibilitySpeechAttributes( + alwaysIncludesPunctuation: alwaysIncludesPunctuation ?? other.alwaysIncludesPunctuation, + spellsOutCharacters: spellsOutCharacters ?? other.spellsOutCharacters, + adjustedPitch: adjustedPitch ?? other.adjustedPitch, + announcementsPriority: announcementsPriority ?? other.announcementsPriority, + phoneticRepresentation: phoneticRepresentation ?? other.phoneticRepresentation + ) + } +} + +// MARK: - Text.Style + AccessibilitySpeechAttributes [WIP] + +//extension Text.Style { +// package func resolveAccessibilitySpeechAttributes( +// into attributes: inout [NSAttributedString.Key: Any], +// environment: EnvironmentValues, +// includeDefaultAttributes: Bool = true +// ) { +// _openSwiftUIUnimplementedFailure() +// } +//} diff --git a/Sources/OpenSwiftUICore/Accessibility/AccessibilityEnvironment.swift b/Sources/OpenSwiftUICore/Accessibility/AccessibilityEnvironment.swift index a3106c556..b0220c192 100644 --- a/Sources/OpenSwiftUICore/Accessibility/AccessibilityEnvironment.swift +++ b/Sources/OpenSwiftUICore/Accessibility/AccessibilityEnvironment.swift @@ -2,21 +2,249 @@ // AccessibilityEnvironment.swift // OpenSwiftUICore // -// Audited for iOS 18.0 -// Status: WIP +// Audited for 6.5.4 +// Status: Complete // ID: 1E80A5D8CD82563C298D64AC1337E839 (SwiftUICore) +// MARK: - Accessibility Environment Values [WIP] + +private struct AccessibilityEnabledKey: EnvironmentKey { + static var defaultValue: Bool { false } +} + +@available(OpenSwiftUI_v1_0, *) +extension EnvironmentValues { + /// A Boolean value that indicates whether the user has enabled an assistive + /// technology. + public var accessibilityEnabled: Bool { + get { self[AccessibilityEnabledKey.self] } + set { self[AccessibilityEnabledKey.self] = newValue } + } + + private struct AccessibilitySpeechAlwaysIncludesPunctuationKey: EnvironmentKey { + static var defaultValue: Bool? { nil } + } + + package var speechAlwaysIncludesPunctuation: Bool? { + get { self[AccessibilitySpeechAlwaysIncludesPunctuationKey.self] } + set { self[AccessibilitySpeechAlwaysIncludesPunctuationKey.self] = newValue } + } + + private struct AccessibilitySpeechSpellsOutCharactersKey: EnvironmentKey { + static var defaultValue: Bool? { nil } + } + + package var speechSpellsOutCharacters: Bool? { + get { self[AccessibilitySpeechSpellsOutCharactersKey.self] } + set { self[AccessibilitySpeechSpellsOutCharactersKey.self] = newValue } + } + + private struct AccessibilitySpeechAdjustedPitchKey: EnvironmentKey { + static var defaultValue: Double? { nil } + } + + package var speechAdjustedPitch: Double? { + get { self[AccessibilitySpeechAdjustedPitchKey.self] } + set { self[AccessibilitySpeechAdjustedPitchKey.self] = newValue } + } + + private struct AccessibilitySpeechAnnouncementsPriorityKey: EnvironmentKey { + static var defaultValue: AccessibilityAnnouncementPriority? { nil } + } + + package var speechAnnouncementsPriority: AccessibilityAnnouncementPriority? { + get { self[AccessibilitySpeechAnnouncementsPriorityKey.self] } + set { self[AccessibilitySpeechAnnouncementsPriorityKey.self] = newValue } + } +} + +// MARK: - EnvironmentValues + Accessibility v1 + +private struct AccessibilityDifferentiateWithoutColorKey: EnvironmentKey { + static var defaultValue: Bool { false } +} + private struct AccessibilityReduceTransparencyKey: EnvironmentKey { static var defaultValue: Bool { false } } +private struct AccessibilityReduceMotionKey: EnvironmentKey { + static var defaultValue: Bool { false } +} + +private struct AccessibilityPrefersCrossFadeTransitionsKey: EnvironmentKey { + static var defaultValue: Bool { false } +} + +private struct AccessibilityInvertColorsKey: EnvironmentKey { + static var defaultValue: Bool { false } +} + extension EnvironmentValues { + /// Whether the system preference for Differentiate without Color is enabled. + /// + /// If this is true, UI should not convey information using color alone + /// and instead should use shapes or glyphs to convey information. + public package(set) var accessibilityDifferentiateWithoutColor: Bool { + get { _accessibilityDifferentiateWithoutColor } + set { _accessibilityDifferentiateWithoutColor = newValue } + } + + public var _accessibilityDifferentiateWithoutColor: Bool { + get { self[AccessibilityDifferentiateWithoutColorKey.self] } + set { self[AccessibilityDifferentiateWithoutColorKey.self] = newValue } + } + /// Whether the system preference for Reduce Transparency is enabled. /// /// If this property's value is true, UI (mainly window) backgrounds should /// not be semi-transparent; they should be opaque. - public var accessibilityReduceTransparency: Bool { + public package(set) var accessibilityReduceTransparency: Bool { + get { _accessibilityReduceTransparency } + set { _accessibilityReduceTransparency = newValue } + } + + public var _accessibilityReduceTransparency: Bool { get { self[AccessibilityReduceTransparencyKey.self] } set { self[AccessibilityReduceTransparencyKey.self] = newValue } } + + /// Whether the system preference for Reduce Motion is enabled. + /// + /// If this property's value is true, UI should avoid large animations, + /// especially those that simulate the third dimension. + public package(set) var accessibilityReduceMotion: Bool { + get { _accessibilityReduceMotion } + set { _accessibilityReduceMotion = newValue } + } + + public var _accessibilityReduceMotion: Bool { + get { self[AccessibilityReduceMotionKey.self] } + set { self[AccessibilityReduceMotionKey.self] = newValue } + } + + package var accessibilityPrefersCrossFadeTransitions: Bool { + get { self[AccessibilityPrefersCrossFadeTransitionsKey.self] } + set { self[AccessibilityPrefersCrossFadeTransitionsKey.self] = newValue } + } + + /// Whether the system preference for Invert Colors is enabled. + /// + /// If this property's value is true then the display will be inverted. + /// In these cases it may be needed for UI drawing to be adjusted to in + /// order to display optimally when inverted. + public var accessibilityInvertColors: Bool { + get { _accessibilityInvertColors } + set { _accessibilityInvertColors = newValue } + } + + public var _accessibilityInvertColors: Bool { + get { self[AccessibilityInvertColorsKey.self] } + set { self[AccessibilityInvertColorsKey.self] = newValue } + } +} + +// MARK: - EnvironmentValues + Accessibility v2 + +private struct AccessibilityButtonShapesKey: EnvironmentKey { + static var defaultValue: Bool { false } +} + +@available(OpenSwiftUI_v2_0, *) +extension EnvironmentValues { + /// Whether the system preference for Show Button Shapes is enabled. + /// + /// If this property's value is true, interactive custom controls + /// such as buttons should be drawn in such a way that their edges + /// and borders are clearly visible. + public package(set) var accessibilityShowButtonShapes: Bool { + get { _accessibilityShowButtonShapes } + set { _accessibilityShowButtonShapes = newValue } + } + + public var _accessibilityShowButtonShapes: Bool { + get { self[AccessibilityButtonShapesKey.self] } + set { self[AccessibilityButtonShapesKey.self] = newValue } + } +} + +// MARK: - EnvironmentValues + Accessibility v5 + +private struct AccessibilityDimFlashingLightsKey: EnvironmentKey { + static var defaultValue: Bool { false } +} + +private struct AccessibilityPlayAnimatedImagesKey: EnvironmentKey { + static var defaultValue: Bool { false } +} + +@available(OpenSwiftUI_v5_0, *) +extension EnvironmentValues { + /// Whether the setting to reduce flashing or strobing lights in video + /// content is on. This setting can also be used to determine if UI in + /// playback controls should be shown to indicate upcoming content that + /// includes flashing or strobing lights. + public package(set) var accessibilityDimFlashingLights: Bool { + get { self[AccessibilityDimFlashingLightsKey.self] } + set { self[AccessibilityDimFlashingLightsKey.self] = newValue } + } + + /// Whether the setting for playing animations in an animated image is + /// on. When this value is false, any presented image that contains + /// animation should not play automatically. + public private(set) var accessibilityPlayAnimatedImages: Bool { + get { self[AccessibilityPlayAnimatedImagesKey.self] } + set { self[AccessibilityPlayAnimatedImagesKey.self] = newValue } + } +} + +private struct AccessibilityOnOffLabelsKey: EnvironmentKey { + static var defaultValue: Bool { false } +} + +extension EnvironmentValues { + package var accessibilityPrefersOnOffLabels: Bool { + get { self[AccessibilityOnOffLabelsKey.self] } + set { self[AccessibilityOnOffLabelsKey.self] = newValue } + } +} + +private struct AccessibilityHeadAnchorAlternativeKey: EnvironmentKey { + static var defaultValue: Bool { false } +} + +private struct AccessibilityIncreaseFocusStateKey: EnvironmentKey { + static var defaultValue: Bool { false } +} + +private struct AccessibilityReduceHoverRevealKey: EnvironmentKey { + static var defaultValue: Bool { false } +} + +@available(OpenSwiftUI_v5_0, *) +@available(iOS, unavailable) +@available(macOS, unavailable) +@available(watchOS, unavailable) +@available(tvOS, unavailable) +extension EnvironmentValues { + /// Whether the system setting to prefer alternatives to head-anchored + /// content is on. + /// + /// If this property's value is true, alternate anchors should be used for + /// most head-anchored UI, such as world anchors. + public package(set) var accessibilityPrefersHeadAnchorAlternative: Bool { + get { self[AccessibilityHeadAnchorAlternativeKey.self] } + set { self[AccessibilityHeadAnchorAlternativeKey.self] = newValue } + } + + @_spi(Private) + public var accessibilityIncreaseFocusStateEnabled: Bool { + get { self[AccessibilityIncreaseFocusStateKey.self] } + set { self[AccessibilityIncreaseFocusStateKey.self] = newValue } + } + + package var accessibilityReduceHoverReveal: Bool { + get { self[AccessibilityReduceHoverRevealKey.self] } + set { self[AccessibilityReduceHoverRevealKey.self] = newValue } + } } diff --git a/Sources/OpenSwiftUICore/Util/UIUtils.swift b/Sources/OpenSwiftUICore/Util/UIUtils.swift index 16caf197b..936c39b91 100644 --- a/Sources/OpenSwiftUICore/Util/UIUtils.swift +++ b/Sources/OpenSwiftUICore/Util/UIUtils.swift @@ -2,13 +2,11 @@ // UIUtils.swift // OpenSwiftUICore // -// Audited for iOS 18.0 +// Audited for 6.5.4 // Status: WIP import OpenSwiftUI_SPI -#if canImport(Darwin) - // NOTE: use runtime check instead of #if targetEnvironment(macCatalyst) // Because Mac Catalyst will use macOS-varient build of OpenSwiftUICore.framework and Mac Catalyst/UIKitForMac varient of OpenSwiftUI.framework @inline(__always) @@ -24,8 +22,10 @@ package func isCatalyst() -> Bool { package func isUIKitBased() -> Bool { #if os(macOS) || targetEnvironment(macCatalyst) _CFMZEnabled() - #else + #elseif os(iOS) || os(visionOS) true + #else + false #endif } @@ -42,5 +42,3 @@ extension CoreSystem { @inline(__always) static var defaults: CoreSystem = isAppKitBased() ? .appKit : .uiKit } - -#endif