From ac6fbe5dfef79e026d451431258f05e36909dc7c Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 1 Jan 2024 23:51:28 +0800 Subject: [PATCH] Update Transaction and EnvironmentValues (#14) --- .../EnvironmentValues/EnvironmentValues.swift | 168 ++++++++++++++++++ .../TODO/EnvironmentValues.swift | 64 ------- .../Internal/Property/TODO/PropertyList.swift | 6 +- .../Transaction/Transaction+Animation.swift | 43 +++++ .../Transaction+isContinuous.swift | 26 +++ .../TODO => Transaction}/Transaction.swift | 58 +++--- .../Views/View/Extension/View_Font.swift | 41 ++++- 7 files changed, 305 insertions(+), 101 deletions(-) create mode 100644 Sources/OpenSwiftUI/DataAndStorage/EnvironmentValues/EnvironmentValues.swift delete mode 100644 Sources/OpenSwiftUI/DataAndStorage/EnvironmentValues/TODO/EnvironmentValues.swift create mode 100644 Sources/OpenSwiftUI/Views/Transaction/Transaction+Animation.swift create mode 100644 Sources/OpenSwiftUI/Views/Transaction/Transaction+isContinuous.swift rename Sources/OpenSwiftUI/Views/{Animations/TODO => Transaction}/Transaction.swift (50%) diff --git a/Sources/OpenSwiftUI/DataAndStorage/EnvironmentValues/EnvironmentValues.swift b/Sources/OpenSwiftUI/DataAndStorage/EnvironmentValues/EnvironmentValues.swift new file mode 100644 index 0000000..a43888a --- /dev/null +++ b/Sources/OpenSwiftUI/DataAndStorage/EnvironmentValues/EnvironmentValues.swift @@ -0,0 +1,168 @@ +// +// EnvironmentValues.swift +// OpenSwiftUI +// +// Created by Kyle on 2023/9/22. +// Lastest Version: iOS 15.5 +// Status: Complete +// ID: 83E729E7BD00420AB79EFD8DF557072A + +/// A collection of environment values propagated through a view hierarchy. +/// +/// OpenSwiftUI exposes a collection of values to your app's views in an +/// `EnvironmentValues` structure. To read a value from the structure, +/// declare a property using the ``Environment`` property wrapper and +/// specify the value's key path. For example, you can read the current locale: +/// +/// @Environment(\.locale) var locale: Locale +/// +/// Use the property you declare to dynamically control a view's layout. +/// OpenSwiftUI automatically sets or updates many environment values, like +/// ``EnvironmentValues/pixelLength``, ``EnvironmentValues/scenePhase``, or +/// ``EnvironmentValues/locale``, based on device characteristics, system state, +/// or user settings. For others, like ``EnvironmentValues/lineLimit``, OpenSwiftUI +/// provides a reasonable default value. +/// +/// You can set or override some values using the ``View/environment(_:_:)`` +/// view modifier: +/// +/// MyView() +/// .environment(\.lineLimit, 2) +/// +/// The value that you set affects the environment for the view that you modify +/// --- including its descendants in the view hierarchy --- but only up to the +/// point where you apply a different environment modifier. +/// +/// OpenSwiftUI provides dedicated view modifiers for setting some values, which +/// typically makes your code easier to read. For example, rather than setting +/// the ``EnvironmentValues/lineLimit`` value directly, as in the previous +/// example, you should instead use the ``View/lineLimit(_:)-513mb`` modifier: +/// +/// MyView() +/// .lineLimit(2) +/// +/// In some cases, using a dedicated view modifier provides additional +/// functionality. For example, you must use the +/// ``View/preferredColorScheme(_:)`` modifier rather than setting +/// ``EnvironmentValues/colorScheme`` directly to ensure that the new +/// value propagates up to the presenting container when presenting a view +/// like a popover: +/// +/// MyView() +/// .popover(isPresented: $isPopped) { +/// PopoverContent() +/// .preferredColorScheme(.dark) +/// } +/// +/// Create custom environment values by defining a type that +/// conforms to the ``EnvironmentKey`` protocol, and then extending the +/// environment values structure with a new property. Use your key to get and +/// set the value, and provide a dedicated modifier for clients to use when +/// setting the value: +/// +/// private struct MyEnvironmentKey: EnvironmentKey { +/// static let defaultValue: String = "Default value" +/// } +/// +/// extension EnvironmentValues { +/// var myCustomValue: String { +/// get { self[MyEnvironmentKey.self] } +/// set { self[MyEnvironmentKey.self] = newValue } +/// } +/// } +/// +/// extension View { +/// func myCustomValue(_ myCustomValue: String) -> some View { +/// environment(\.myCustomValue, myCustomValue) +/// } +/// } +/// +/// Clients of your value then access the value in the usual way, reading it +/// with the ``Environment`` property wrapper, and setting it with the +/// `myCustomValue` view modifier. +public struct EnvironmentValues: CustomStringConvertible { + /// Creates an environment values instance. + /// + /// You don't typically create an instance of ``EnvironmentValues`` + /// directly. Doing so would provide access only to default values that + /// don't update based on system settings or device characteristics. + /// Instead, you rely on an environment values' instance + /// that OpenSwiftUI manages for you when you use the ``Environment`` + /// property wrapper and the ``View/environment(_:_:)`` view modifier. + public init() { + _plist = PropertyList() + tracker = nil + } + + /// Accesses the environment value associated with a custom key. + /// + /// Create custom environment values by defining a key + /// that conforms to the ``EnvironmentKey`` protocol, and then using that + /// key with the subscript operator of the ``EnvironmentValues`` structure + /// to get and set a value for that key: + /// + /// private struct MyEnvironmentKey: EnvironmentKey { + /// static let defaultValue: String = "Default value" + /// } + /// + /// extension EnvironmentValues { + /// var myCustomValue: String { + /// get { self[MyEnvironmentKey.self] } + /// set { self[MyEnvironmentKey.self] = newValue } + /// } + /// } + /// + /// You use custom environment values the same way you use system-provided + /// values, setting a value with the ``View/environment(_:_:)`` view + /// modifier, and reading values with the ``Environment`` property wrapper. + /// You can also provide a dedicated view modifier as a convenience for + /// setting the value: + /// + /// extension View { + /// func myCustomValue(_ myCustomValue: String) -> some View { + /// environment(\.myCustomValue, myCustomValue) + /// } + /// } + /// + public subscript(key: K.Type) -> K.Value { + get { + if let tracker { + tracker.value(_plist, for: EnvironmentPropertyKey.self) + } else { + _plist[EnvironmentPropertyKey.self] + } + } + set { + _plist[EnvironmentPropertyKey.self] = newValue + if let tracker { + tracker.invalidateValue( + for: EnvironmentPropertyKey.self, + from: _plist, + to: _plist + ) + } + } + } + + /// A string that represents the contents of the environment values + /// instance. + public var description: String { _plist.description } + + private var _plist: PropertyList + private let tracker: PropertyList.Tracker? + + init(plist: PropertyList) { + _plist = plist + tracker = nil + } +} + +private struct EnvironmentPropertyKey: PropertyKey { + static var defaultValue: EnvKey.Value { EnvKey.defaultValue } +} + +private struct DerivedEnvironmentPropertyKey: DerivedPropertyKey { + static func value(in plist: PropertyList) -> some Equatable { + EnvKey.value(in: EnvironmentValues(plist: plist)) + } +} diff --git a/Sources/OpenSwiftUI/DataAndStorage/EnvironmentValues/TODO/EnvironmentValues.swift b/Sources/OpenSwiftUI/DataAndStorage/EnvironmentValues/TODO/EnvironmentValues.swift deleted file mode 100644 index 39af302..0000000 --- a/Sources/OpenSwiftUI/DataAndStorage/EnvironmentValues/TODO/EnvironmentValues.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// EnvironmentValues.swift -// OpenSwiftUI -// -// Created by Kyle on 2023/9/22. -// Lastest Version: iOS 15.5 -// Status: Empty -// ID: CEAC6A812C645DD28AF9055EFDEFFB46 - -// MARK: - EnvironmentValues -public struct EnvironmentValues: CustomStringConvertible { - public init() {} - - // FIXME: - public subscript(_: K.Type) -> K.Value where K: EnvironmentKey { - get { - K.defaultValue - } - set {} - } - - public var description: String { "" } - - // var _plist: PropertyList - // let tracker: PropertyList.Tracker? -} - -// MARK: - Font -private struct FontKey: EnvironmentKey { - static var defaultValue: Font? { nil } -} - -private struct DefaultFontKey: EnvironmentKey { - typealias Value = Font? - static var defaultValue: Font? { nil } -} - -private struct EffectiveFontKey: DerivedEnvironmentKey { - typealias Value = Font - - static func value(in environment: EnvironmentValues) -> Font { - environment.font ?? environment.defaultFont ?? .body - } -} - -// TODO -extension EnvironmentValues { - @inline(__always) - public var font: Font? { - get { self[FontKey.self] } - set { self[FontKey.self] = newValue } - } - - @inline(__always) - var defaultFont: Font? { - get { self[DefaultFontKey.self] } - set { self[DefaultFontKey.self] = newValue } - } - - @inline(__always) - var effectiveFont: Font { - EffectiveFontKey.value(in: self) - } -} diff --git a/Sources/OpenSwiftUI/DataAndStorage/Internal/Property/TODO/PropertyList.swift b/Sources/OpenSwiftUI/DataAndStorage/Internal/Property/TODO/PropertyList.swift index 0a3fa32..8c5d4ca 100644 --- a/Sources/OpenSwiftUI/DataAndStorage/Internal/Property/TODO/PropertyList.swift +++ b/Sources/OpenSwiftUI/DataAndStorage/Internal/Property/TODO/PropertyList.swift @@ -35,7 +35,11 @@ struct PropertyList: CustomStringConvertible { return } elements.forEach { element, stop in - fatalError("TODO") + let element = element.takeUnretainedValue() + guard element.keyType == Key.self else { + return + } + body((element as! TypedElement).value, &stop) } } diff --git a/Sources/OpenSwiftUI/Views/Transaction/Transaction+Animation.swift b/Sources/OpenSwiftUI/Views/Transaction/Transaction+Animation.swift new file mode 100644 index 0000000..01254ad --- /dev/null +++ b/Sources/OpenSwiftUI/Views/Transaction/Transaction+Animation.swift @@ -0,0 +1,43 @@ +// +// Transaction+Animation.swift +// OpenSwiftUI +// +// Created by Kyle on 2024/1/1. +// Lastest Version: iOS 15.5 +// Status: Complete +// ID: 39EC6D46662E6D7A6963F5C611934B0A + +private struct AnimationKey: TransactionKey { + static let defaultValue: Animation? = nil +} + +private struct DisablesAnimationsKey: TransactionKey { + static var defaultValue: Bool { false } +} + +extension Transaction { + /// Creates a transaction and assigns its animation property. + /// + /// - Parameter animation: The animation to perform when the current state + /// changes. + public init(animation: Animation?) { + self.plist = PropertyList() + self.animation = animation + } + + /// The animation, if any, associated with the current state change. + public var animation: Animation? { + get { plist[Key.self] } + set { plist[Key.self] = newValue } + } + + /// A Boolean value that indicates whether views should disable animations. + /// + /// This value is `true` during the initial phase of a two-part transition + /// update, to prevent ``View/animation(_:)`` from inserting new animations + /// into the transaction. + public var disablesAnimations: Bool { + get { plist[Key.self] } + set { plist[Key.self] = newValue } + } +} diff --git a/Sources/OpenSwiftUI/Views/Transaction/Transaction+isContinuous.swift b/Sources/OpenSwiftUI/Views/Transaction/Transaction+isContinuous.swift new file mode 100644 index 0000000..00bedba --- /dev/null +++ b/Sources/OpenSwiftUI/Views/Transaction/Transaction+isContinuous.swift @@ -0,0 +1,26 @@ +// +// Transaction+isContinuous.swift +// OpenSwiftUI +// +// Created by Kyle on 2024/1/1. +// Lastest Version: iOS 15.5 +// Status: Complete +// ID: 73F9B1285B58E062EB66BE983530A970 + +private struct ContinuousKey: TransactionKey { + static var defaultValue: Bool { false } +} + +extension Transaction { + /// A Boolean value that indicates whether the transaction originated from + /// an action that produces a sequence of values. + /// + /// This value is `true` if a continuous action created the transaction, and + /// is `false` otherwise. Continuous actions include things like dragging a + /// slider or pressing and holding a stepper, as opposed to tapping a + /// button. + public var isContinuous: Bool { + get { plist[Key.self] } + set { plist[Key.self] = newValue } + } +} diff --git a/Sources/OpenSwiftUI/Views/Animations/TODO/Transaction.swift b/Sources/OpenSwiftUI/Views/Transaction/Transaction.swift similarity index 50% rename from Sources/OpenSwiftUI/Views/Animations/TODO/Transaction.swift rename to Sources/OpenSwiftUI/Views/Transaction/Transaction.swift index 76b2e6f..0128ae8 100644 --- a/Sources/OpenSwiftUI/Views/Animations/TODO/Transaction.swift +++ b/Sources/OpenSwiftUI/Views/Transaction/Transaction.swift @@ -4,61 +4,49 @@ // // Created by Kyle on 2023/11/2. // Lastest Version: iOS 15.5 -// Status: Empty -// ID: 39EC6D46662E6D7A6963F5C611934B0A +// Status: Complete internal import OpenSwiftUIShims +/// The context of the current state-processing update. +/// +/// Use a transaction to pass an animation between views in a view hierarchy. +/// +/// The root transaction for a state change comes from the binding that changed, +/// plus any global values set by calling ``withTransaction(_:_:)`` or +/// ``withAnimation(_:_:)`` @frozen public struct Transaction { - @usableFromInline - var plist: PropertyList - + /// Creates a transaction. @inlinable public init() { plist = PropertyList() } + + @usableFromInline + var plist: PropertyList } extension Transaction { - // Blocked by PropertyList implementation -// public init(animation: Animation?) { -// -// } - // TODO - public var animation: Animation? { - get { nil } - set { } + struct Key: PropertyKey { + static var defaultValue: K.Value { K.defaultValue } } -// public var disablesAnimations: Bool { -// get -// set -// } } -// extension Transaction { -// public var isContinuous: Bool { -// get -// set -// } -// } -// -// extension Transaction { -// public var _scrollViewAnimates: _ScrollViewAnimationMode { -// get -// set -// } -// } - protocol TransactionKey { associatedtype Value static var defaultValue: Value { get } } -private struct AnimationKey: TransactionKey { - static let defaultValue: Animation? = nil -} - +/// Executes a closure with the specified transaction and returns the result. +/// +/// - Parameters: +/// - transaction : An instance of a transaction, set as the thread's current +/// transaction. +/// - body: A closure to execute. +/// +/// - Returns: The result of executing the closure with the specified +/// transaction. public func withTransaction( _ transaction: Transaction, _ body: () throws -> Result diff --git a/Sources/OpenSwiftUI/Views/View/Extension/View_Font.swift b/Sources/OpenSwiftUI/Views/View/Extension/View_Font.swift index 02747fe..ab10071 100644 --- a/Sources/OpenSwiftUI/Views/View/Extension/View_Font.swift +++ b/Sources/OpenSwiftUI/Views/View/Extension/View_Font.swift @@ -4,7 +4,46 @@ // // Created by Kyle on 2023/9/25. // Lastest Version: iOS 15.5 -// Status: Complete +// Status: TODO + +// MARK: - Font +private struct FontKey: EnvironmentKey { + static var defaultValue: Font? { nil } +} + +private struct DefaultFontKey: EnvironmentKey { + typealias Value = Font? + static var defaultValue: Font? { nil } +} + +private struct EffectiveFontKey: DerivedEnvironmentKey { + typealias Value = Font + + static func value(in environment: EnvironmentValues) -> Font { + environment.font ?? environment.defaultFont ?? .body + } +} + +// TODO +extension EnvironmentValues { + @inline(__always) + public var font: Font? { + get { self[FontKey.self] } + set { self[FontKey.self] = newValue } + } + + @inline(__always) + var defaultFont: Font? { + get { self[DefaultFontKey.self] } + set { self[DefaultFontKey.self] = newValue } + } + + @inline(__always) + var effectiveFont: Font { + EffectiveFontKey.value(in: self) + } +} + extension View { @inlinable