diff --git a/Sources/OpenSwiftUICore/View/Text/Text/Text+Justification.swift b/Sources/OpenSwiftUICore/View/Text/Text/Text+Justification.swift new file mode 100644 index 000000000..a838f83c3 --- /dev/null +++ b/Sources/OpenSwiftUICore/View/Text/Text/Text+Justification.swift @@ -0,0 +1,61 @@ +// +// Text+Justification.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete +// ID: F89CCC57FFF9CABCAC4F565338DE677C (SwiftUICore) + +@_spi(Private) +@available(OpenSwiftUI_v5_0, *) +public struct TextJustification: Sendable, Equatable { + struct Full: Equatable { + var allLines: Bool + var flexible: Bool + } + + enum Storage: Equatable { + case full(Full) + case none + } + + var storage: Storage + + public static let none: TextJustification = .init(storage: .none) + + public static let full: TextJustification = .full() + + public static let stretched: TextJustification = .stretched(true) + + public static func stretched(_ flexible: Bool = true) -> TextJustification { + .full(allLines: true, flexible: flexible) + } + + public static func full( + allLines: Bool = false, + flexible: Bool = false + ) -> TextJustification { + .init(storage: .full(.init(allLines: allLines, flexible: flexible))) + } +} + +private struct TextJustificationKey: EnvironmentKey { + static let defaultValue: TextJustification = .none +} + +extension EnvironmentValues { + var textJustification: TextJustification { + get { self[TextJustificationKey.self] } + set { self[TextJustificationKey.self] = newValue } + } +} + +@_spi(Private) +@available(OpenSwiftUI_v5_0, *) +extension View { + + @available(OpenSwiftUI_v5_0, *) + nonisolated public func justification(_ justfication: TextJustification) -> some View { + environment(\.textJustification, justfication) + } +} diff --git a/Sources/OpenSwiftUICore/View/Text/Text/Text+LayoutShape.swift b/Sources/OpenSwiftUICore/View/Text/Text/Text+LayoutShape.swift index 954810e45..e22d7f0cc 100644 --- a/Sources/OpenSwiftUICore/View/Text/Text/Text+LayoutShape.swift +++ b/Sources/OpenSwiftUICore/View/Text/Text/Text+LayoutShape.swift @@ -104,3 +104,7 @@ package struct TextShape: Equatable { ) } } + +extension EnvironmentValues { + @Entry var textShape: TextShape = .bounds +} diff --git a/Sources/OpenSwiftUICore/View/Text/Text/Text+Sizing.swift b/Sources/OpenSwiftUICore/View/Text/Text/Text+Sizing.swift index 91e894a11..2eb4baafc 100644 --- a/Sources/OpenSwiftUICore/View/Text/Text/Text+Sizing.swift +++ b/Sources/OpenSwiftUICore/View/Text/Text/Text+Sizing.swift @@ -6,6 +6,46 @@ // Status: Complete // ID: 22747AAF70EE5063D02F299CE90A18BE (SwiftUICore) +// MARK: TextSizingModifier + +protocol TextSizingModifier: Equatable { + func updateLayoutMargins(_ margins: inout EdgeInsets) +} + +class AnyTextSizingModifier: Equatable, TextSizingModifier, @unchecked Sendable { + static func == (lhs: AnyTextSizingModifier, rhs: AnyTextSizingModifier) -> Bool { + lhs.isEqual(to: rhs) + } + + func updateLayoutMargins(_ margins: inout EdgeInsets) { + _openSwiftUIBaseClassAbstractMethod() + } + + func isEqual(to other: AnyTextSizingModifier) -> Bool { + _openSwiftUIBaseClassAbstractMethod() + } +} + +private class ConcreteTextSizingModifier: AnyTextSizingModifier, @unchecked Sendable where M: TextSizingModifier { + let modifier: M + + init(modifier: M) { + self.modifier = modifier + } + + override func updateLayoutMargins(_ margins: inout EdgeInsets) { + modifier.updateLayoutMargins(&margins) + } + + override func isEqual(to other: AnyTextSizingModifier) -> Bool { + guard let other = other as? ConcreteTextSizingModifier else { + return false + } + return modifier == other.modifier + } +} + + // MARK: - Text + Sizing @_spi(Private) @@ -22,6 +62,8 @@ extension Text { package var storage: Text.Sizing.Storage + private var modifiers: [AnyTextSizingModifier] = [] + package init(_ storage: Text.Sizing.Storage) { self.storage = storage } @@ -34,6 +76,16 @@ extension Text { } } +//extension Text.Sizing { +// func layoutMargins( +// for: NSAttributedString, +// metrics: inout NSAttributedString.EncodedFontMetrics? +// layoutProperties: LayoutProperties +// ) { +// _openSwiftUIUnimplementedFailure() +// } +//} + private struct TextSizingKey: EnvironmentKey { static let defaultValue: Text.Sizing = .standard } diff --git a/Sources/OpenSwiftUICore/View/Text/Text/Text+View.swift b/Sources/OpenSwiftUICore/View/Text/Text/Text+View.swift index c7a02ba2f..6db001cbd 100644 --- a/Sources/OpenSwiftUICore/View/Text/Text/Text+View.swift +++ b/Sources/OpenSwiftUICore/View/Text/Text/Text+View.swift @@ -45,13 +45,13 @@ public struct TextLayoutProperties: Equatable { package var lowerLineLimit: Int? -// public var truncationMode: Text.TruncationMode = + public var truncationMode: Text.TruncationMode = .tail - public var multilineTextAlignment: TextAlignment + public var multilineTextAlignment: TextAlignment = .leading - public var layoutDirection: LayoutDirection + public var layoutDirection: LayoutDirection = .leftToRight - package var transitionStyle: ContentTransition.Style + package var transitionStyle: ContentTransition.Style = .default public var minScaleFactor: CGFloat = 1.0 @@ -59,9 +59,9 @@ public struct TextLayoutProperties: Equatable { public var lineHeightMultiple: CGFloat = .zero -// public var maximumLineHeight: CGFloat = + public var maximumLineHeight: CGFloat = MaximumLineHeightKey.defaultValue -// public var minimumLineHeight: CGFloat + public var minimumLineHeight: CGFloat = MinimumLineHeightKey.defaultValue public var hyphenationFactor: CGFloat = .zero @@ -75,45 +75,119 @@ public struct TextLayoutProperties: Equatable { package var textSizing: Text.Sizing = .standard - // package var textShape: TextShape + private var textShape: TextShape = .bounds - // package var flags: Flags + private struct Flags: OptionSet { + var rawValue: UInt8 + + static let widthIsFlexible = Flags(rawValue: 1 << 0) + + static let sizeFitting = Flags(rawValue: 1 << 1) + } + + private var flags: Flags = [] package var widthIsFlexible: Bool { - get { _openSwiftUIUnimplementedFailure() } - set { _openSwiftUIUnimplementedFailure() } + get { flags.contains(.widthIsFlexible) } + set { flags.setValue(newValue, for: .widthIsFlexible) } } package var sizeFitting: Bool { - get { _openSwiftUIUnimplementedFailure() } - set { _openSwiftUIUnimplementedFailure() } + get { flags.contains(.sizeFitting) } + set { flags.setValue(newValue, for: .sizeFitting) } } package init() { - _openSwiftUIUnimplementedFailure() + _openSwiftUIEmptyStub() + } + public init(_ env: EnvironmentValues) { + self = env[Key.self] } private struct Key: DerivedEnvironmentKey { static func value(in environment: EnvironmentValues) -> TextLayoutProperties { - // TODO - .init() + TextLayoutProperties(from: environment) } } - public init(_ env: EnvironmentValues) { - self = env[Key.self] + init(from env: EnvironmentValues) { + lineLimit = env.lineLimit + lowerLineLimit = env.lowerLineLimit + truncationMode = env.truncationMode + multilineTextAlignment = env.multilineTextAlignment + layoutDirection = env.layoutDirection + transitionStyle = env.contentTransitionStyle + minScaleFactor = env.minimumScaleFactor + lineSpacing = env.lineSpacing + lineHeightMultiple = env.lineHeightMultiple + maximumLineHeight = env.maximumLineHeight + minimumLineHeight = env.minimumLineHeight + hyphenationFactor = env.hyphenationFactor + hyphenationDisabled = env.hyphenationDisabled + writingMode = env.writingMode + bodyHeadOutdent = env.bodyHeadOutdent + pixelLength = env.pixelLength + textSizing = env.textSizing + textShape = env.textShape + widthIsFlexible = switch env.textJustification.storage { + case .full(let full): full.flexible + case .none: false + } } package func update( _ env: inout EnvironmentValues, from old: TextLayoutProperties ) { - _openSwiftUIUnimplementedFailure() - } - - public static func == (a: TextLayoutProperties, b: TextLayoutProperties) -> Bool { - _openSwiftUIUnimplementedFailure() + if lineLimit != old.lineLimit { + env.lineLimit = lineLimit + } + if lowerLineLimit != old.lowerLineLimit { + env.lowerLineLimit = lowerLineLimit + } + if truncationMode != old.truncationMode { + env.truncationMode = truncationMode + } + if multilineTextAlignment != old.multilineTextAlignment { + env.multilineTextAlignment = multilineTextAlignment + } + if layoutDirection != old.layoutDirection { + env.layoutDirection = layoutDirection + } + if minScaleFactor != old.minScaleFactor { + env.minimumScaleFactor = minScaleFactor + } + if lineSpacing != old.lineSpacing { + env.lineSpacing = lineSpacing + } + if lineHeightMultiple != old.lineHeightMultiple { + env.lineHeightMultiple = lineHeightMultiple + } + if maximumLineHeight != old.maximumLineHeight { + env.maximumLineHeight = maximumLineHeight + } + if minimumLineHeight != old.minimumLineHeight { + env.minimumLineHeight = minimumLineHeight + } + if hyphenationFactor != old.hyphenationFactor { + env.hyphenationFactor = hyphenationFactor + } + if hyphenationDisabled != old.hyphenationDisabled { + env.hyphenationDisabled = hyphenationDisabled + } + if transitionStyle != old.transitionStyle { + env.contentTransitionStyle = transitionStyle + } + if textSizing != old.textSizing { + env.textSizing = textSizing + } + if writingMode != old.writingMode { + env.writingMode = writingMode + } + if textShape != old.textShape { + env.textShape = textShape + } } } @@ -132,7 +206,6 @@ extension TextLayoutProperties: ProtobufMessage { } } - // MARK: - ResolvedTextFilter [WIP] struct ResolvedTextFilter: StatefulRule, AsyncAttribute { @@ -195,3 +268,32 @@ extension ResolvedStyledText { private struct TextFilter { } + +// FIXME + +extension EnvironmentValues { + private struct LineLimitKey: EnvironmentKey { + static var defaultValue: Int? { nil } + } + + public var lineLimit: Int? { + get { self[LineLimitKey.self] } + set { self[LineLimitKey.self] = newValue } + } + + private struct LowerLineLimitKey: EnvironmentKey { + static var defaultValue: Int? { nil } + } + + package var lowerLineLimit: Int? { + get { self[LowerLineLimitKey.self] } + set { self[LowerLineLimitKey.self] = newValue } + } +} + +extension EnvironmentValues { + var contentTransitionStyle: ContentTransition.Style { + get { _openSwiftUIUnimplementedFailure() } + set { _openSwiftUIUnimplementedFailure() } + } +} diff --git a/Sources/OpenSwiftUICore/View/Text/Util/TruncationMode.swift b/Sources/OpenSwiftUICore/View/Text/Util/TruncationMode.swift index a81205f43..bd1c8c5f1 100644 --- a/Sources/OpenSwiftUICore/View/Text/Util/TruncationMode.swift +++ b/Sources/OpenSwiftUICore/View/Text/Util/TruncationMode.swift @@ -248,6 +248,11 @@ extension EnvironmentValues { set { self[MinimumLineHeightKey.self] = newValue } } + var hyphenationDisabled: Bool { + get { self[HyphenationDisabledKey.self] } + set { self[HyphenationDisabledKey.self] = newValue } + } + @_spi(Private) @available(OpenSwiftUI_v2_0, *) public var hyphenationFactor: CGFloat { @@ -463,6 +468,10 @@ extension View { environment(\.minimumLineHeight, lineHeight) } + func hyphenationDisabled(_ flag: Bool) -> some View { + environment(\.hyphenationDisabled, flag) + } + @_spi(Private) @available(OpenSwiftUI_v2_0, *) nonisolated public func hyphenationFactor(_ factor: CGFloat) -> some View {