Skip to content

Commit 74f1693

Browse files
authored
Add Text.LineStyle (#621)
1 parent 9938504 commit 74f1693

File tree

4 files changed

+273
-0
lines changed

4 files changed

+273
-0
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//
2+
// TextLineStyleConversions.swift
3+
// OpenSwiftUI
4+
//
5+
// Audited for 6.5.4
6+
// Status: Complete
7+
8+
public import OpenSwiftUI_SPI
9+
10+
@available(OpenSwiftUI_v3_0, *)
11+
extension Text.LineStyle {
12+
13+
/// Creates a ``Text.LineStyle`` from ``NSUnderlineStyle``.
14+
///
15+
/// > Note: Use this initializer only if you need to convert an existing
16+
/// ``NSUnderlineStyle`` to a OpenSwiftUI ``Text.LineStyle``.
17+
/// Otherwise, create a ``Text.LineStyle`` using an
18+
/// initializer like ``init(pattern:color:)``.
19+
///
20+
/// - Parameter nsUnderlineStyle: A value of ``NSUnderlineStyle``
21+
/// to wrap with ``Text.LineStyle``.
22+
///
23+
/// - Returns: A new ``Text.LineStyle`` or `nil` when
24+
/// `nsUnderlineStyle` contains styles not supported by ``Text.LineStyle``.
25+
public init?(nsUnderlineStyle: NSUnderlineStyle) {
26+
self.init(_nsUnderlineStyle: nsUnderlineStyle)
27+
}
28+
}
29+
30+
@available(OpenSwiftUI_v3_0, *)
31+
extension NSUnderlineStyle {
32+
33+
/// Creates a ``NSUnderlineStyle`` from ``Text.LineStyle``.
34+
///
35+
/// - Parameter lineStyle: A value of ``Text.LineStyle``
36+
/// to wrap with ``NSUnderlineStyle``.
37+
///
38+
/// - Returns: A new ``NSUnderlineStyle``.
39+
public init(_ lineStyle: Text.LineStyle) {
40+
self = lineStyle.nsUnderlineStyle
41+
}
42+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
//
2+
// TextLineStyle.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for 6.5.4
6+
// Status: Complete
7+
8+
package import OpenSwiftUI_SPI
9+
10+
@available(OpenSwiftUI_v3_0, *)
11+
extension Text {
12+
13+
/// Description of the style used to draw the line for `StrikethroughStyleAttribute`
14+
/// and `UnderlineStyleAttribute`.
15+
///
16+
/// Use this type to specify `underlineStyle` and `strikethroughStyle`
17+
/// OpenSwiftUI attributes of an `AttributedString`.
18+
public struct LineStyle: Hashable, Sendable {
19+
package var nsUnderlineStyleValue: Int
20+
21+
package var color: Color?
22+
23+
package var nsUnderlineStyle: NSUnderlineStyle {
24+
get { NSUnderlineStyle(rawValue: nsUnderlineStyleValue) }
25+
set { nsUnderlineStyleValue = newValue.rawValue }
26+
}
27+
28+
/// Creates a line style.
29+
///
30+
/// - Parameters:
31+
/// - pattern: The pattern of the line.
32+
/// - color: The color of the line. If not provided, the foreground
33+
/// color of text is used.
34+
public init(pattern: Text.LineStyle.Pattern = .solid, color: Color? = nil) {
35+
self.nsUnderlineStyleValue = NSUnderlineStyle([pattern.nsUnderlineStyle, .single]).rawValue
36+
self.color = color
37+
}
38+
39+
/// The pattern, that the line has.
40+
public struct Pattern: Sendable {
41+
let nsUnderlineStyle: NSUnderlineStyle
42+
43+
/// Draw a solid line.
44+
public static let solid: Text.LineStyle.Pattern = .init(nsUnderlineStyle: [])
45+
46+
/// Draw a line of dots.
47+
public static let dot: Text.LineStyle.Pattern = .init(nsUnderlineStyle: .patternDot)
48+
49+
/// Draw a line of dashes.
50+
public static let dash: Text.LineStyle.Pattern = .init(nsUnderlineStyle: .patternDash)
51+
52+
/// Draw a line of alternating dashes and dots.
53+
public static let dashDot: Text.LineStyle.Pattern = .init(nsUnderlineStyle: .patternDashDot)
54+
55+
/// Draw a line of alternating dashes and two dots.
56+
public static let dashDotDot: Text.LineStyle.Pattern = .init(nsUnderlineStyle: .patternDashDotDot)
57+
}
58+
59+
/// Draw a single solid line.
60+
public static let single: Text.LineStyle = .init()
61+
62+
package init?(_nsUnderlineStyle: NSUnderlineStyle) {
63+
guard !_nsUnderlineStyle.contains(.thick),
64+
!_nsUnderlineStyle.contains(.double),
65+
!_nsUnderlineStyle.contains(.byWord),
66+
!_nsUnderlineStyle.isEmpty else {
67+
return nil
68+
}
69+
self.nsUnderlineStyleValue = _nsUnderlineStyle.rawValue
70+
self.color = nil
71+
}
72+
73+
package struct Resolved {
74+
package var nsUnderlineStyle: NSUnderlineStyle
75+
76+
package var color: Color.Resolved?
77+
}
78+
}
79+
}
80+
81+
82+
struct UnderlineStyleKey: EnvironmentKey {
83+
static var defaultValue: Text.LineStyle? { nil }
84+
}
85+
86+
struct StrikethroughStyleKey: EnvironmentKey {
87+
static var defaultValue: Text.LineStyle? { nil }
88+
}
89+
90+
extension EnvironmentValues {
91+
var underlineStyle: Text.LineStyle? {
92+
get { self[UnderlineStyleKey.self] }
93+
set { self[UnderlineStyleKey.self] = newValue }
94+
}
95+
96+
var strikethroughStyle: Text.LineStyle? {
97+
get { self[StrikethroughStyleKey.self] }
98+
set { self[StrikethroughStyleKey.self] = newValue }
99+
}
100+
}
101+
102+
@available(OpenSwiftUI_v4_0, *)
103+
extension View {
104+
105+
/// Applies an underline to the text in this view.
106+
///
107+
/// - Parameters:
108+
/// - isActive: A Boolean value that indicates whether underline
109+
/// is added. The default value is `true`.
110+
/// - pattern: The pattern of the line. The default value is `solid`.
111+
/// - color: The color of the underline. If `color` is `nil`, the
112+
/// underline uses the default foreground color.
113+
///
114+
/// - Returns: A view where text has a line running along its baseline.
115+
@available(OpenSwiftUI_v4_0, *)
116+
nonisolated public func underline(
117+
_ isActive: Bool = true,
118+
pattern: Text.LineStyle.Pattern = .solid,
119+
color: Color? = nil
120+
) -> some View {
121+
environment(
122+
\.underlineStyle,
123+
isActive ? Text.LineStyle(pattern: pattern, color: color) : nil
124+
)
125+
}
126+
127+
/// Applies a strikethrough to the text in this view.
128+
///
129+
/// - Parameters:
130+
/// - isActive: A Boolean value that indicates whether
131+
/// strikethrough is added. The default value is `true`.
132+
/// - pattern: The pattern of the line. The default value is `solid`.
133+
/// - color: The color of the strikethrough. If `color` is `nil`, the
134+
/// strikethrough uses the default foreground color.
135+
///
136+
/// - Returns: A view where text has a line through its center.
137+
@available(OpenSwiftUI_v4_0, *)
138+
nonisolated public func strikethrough(
139+
_ isActive: Bool = true,
140+
pattern: Text.LineStyle.Pattern = .solid,
141+
color: Color? = nil
142+
) -> some View {
143+
environment(
144+
\.strikethroughStyle,
145+
isActive ? Text.LineStyle(pattern: pattern, color: color) : nil
146+
)
147+
}
148+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// NSAttributedString.h
3+
// OpenSwiftUI_SPI
4+
5+
#ifndef OpenSwiftUI_SPI_NSAttributedString_h
6+
#define OpenSwiftUI_SPI_NSAttributedString_h
7+
8+
#include "OpenSwiftUIBase.h"
9+
10+
#if OPENSWIFTUI_TARGET_OS_DARWIN
11+
// FIXME: OpenSwiftUI_SPI currently will expose UIKit & AppKit which will conflict the following declaration
12+
#else
13+
14+
typedef long NSInteger;
15+
16+
// This defines currently supported values for NSUnderlineStyleAttributeName and NSStrikethroughStyleAttributeName. These values are or'ed together to produce an underline style.
17+
// Underlines will be drawn with a solid pattern by default, so NSUnderlineStylePatternSolid does not need to be specified.
18+
typedef OPENSWIFTUI_OPTIONS(NSInteger, NSUnderlineStyle) {
19+
NSUnderlineStyleNone = 0x00,
20+
NSUnderlineStyleSingle = 0x01,
21+
NSUnderlineStyleThick API_AVAILABLE(macos(10.0), ios(7.0), tvos(9.0), watchos(2.0), visionos(1.0)) = 0x02,
22+
NSUnderlineStyleDouble API_AVAILABLE(macos(10.0), ios(7.0), tvos(9.0), watchos(2.0), visionos(1.0)) = 0x09,
23+
24+
NSUnderlineStylePatternSolid API_AVAILABLE(macos(10.0), ios(7.0), tvos(9.0), watchos(2.0), visionos(1.0)) = 0x0000,
25+
NSUnderlineStylePatternDot API_AVAILABLE(macos(10.0), ios(7.0), tvos(9.0), watchos(2.0), visionos(1.0)) = 0x0100,
26+
NSUnderlineStylePatternDash API_AVAILABLE(macos(10.0), ios(7.0), tvos(9.0), watchos(2.0), visionos(1.0)) = 0x0200,
27+
NSUnderlineStylePatternDashDot API_AVAILABLE(macos(10.0), ios(7.0), tvos(9.0), watchos(2.0), visionos(1.0)) = 0x0300,
28+
NSUnderlineStylePatternDashDotDot API_AVAILABLE(macos(10.0), ios(7.0), tvos(9.0), watchos(2.0), visionos(1.0)) = 0x0400,
29+
30+
NSUnderlineStyleByWord API_AVAILABLE(macos(10.0), ios(7.0), tvos(9.0), watchos(2.0), visionos(1.0)) = 0x8000
31+
} API_AVAILABLE(macos(10.0), ios(6.0), tvos(9.0), watchos(2.0), visionos(1.0));
32+
33+
#endif /* OPENSWIFTUI_TARGET_OS_DARWIN */
34+
35+
#endif /* OpenSwiftUI_SPI_NSAttributedString_h */
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//
2+
// TextLineStyleConversionsCompatibilityTests.swift
3+
// OpenSwiftUICompatibilityTests
4+
5+
import Testing
6+
import OpenSwiftUITestsSupport
7+
import OpenSwiftUI_SPI
8+
9+
@MainActor
10+
struct TextLineStyleConversionsCompatibilityTests {
11+
@Test(arguments: [
12+
(.single, Text.LineStyle(pattern: .solid)),
13+
(NSUnderlineStyle([.single, .patternDot]), Text.LineStyle(pattern: .dot)),
14+
(NSUnderlineStyle([.single, .patternDash]), Text.LineStyle(pattern: .dash)),
15+
(NSUnderlineStyle([.single, .patternDashDot]), Text.LineStyle(pattern: .dashDot)),
16+
(NSUnderlineStyle([.single, .patternDashDotDot]), Text.LineStyle(pattern: .dashDotDot)),
17+
(.thick, nil),
18+
(.double, nil),
19+
(.byWord, nil),
20+
(NSUnderlineStyle([]), nil),
21+
(NSUnderlineStyle([.single, .thick]), nil),
22+
(NSUnderlineStyle([.single, .byWord]), nil),
23+
(NSUnderlineStyle([.single, .patternDot, .thick]), nil),
24+
] as [(NSUnderlineStyle, Text.LineStyle?)])
25+
func nsUnderlineStyleToLineStyle(
26+
_ nsUnderlineStyle: NSUnderlineStyle,
27+
_ expectedLineStyle: Text.LineStyle?
28+
) throws {
29+
let lineStyle = Text.LineStyle(nsUnderlineStyle: nsUnderlineStyle)
30+
#expect(lineStyle == expectedLineStyle)
31+
}
32+
33+
@Test(arguments: [
34+
(Text.LineStyle(pattern: .solid, color: .red), .single), // Add color to avoid issue with single
35+
(Text.LineStyle(pattern: .dot), NSUnderlineStyle([.single, .patternDot])),
36+
(Text.LineStyle(pattern: .dash), NSUnderlineStyle([.single, .patternDash])),
37+
(Text.LineStyle(pattern: .dashDot), NSUnderlineStyle([.single, .patternDashDot])),
38+
(Text.LineStyle(pattern: .dashDotDot), NSUnderlineStyle([.single, .patternDashDotDot])),
39+
(Text.LineStyle.single, .single),
40+
] as [(Text.LineStyle, NSUnderlineStyle)])
41+
func lineStyleToNSUnderlineStyle(
42+
_ lineStyle: Text.LineStyle,
43+
_ expectedNSUnderlineStyle: NSUnderlineStyle
44+
) throws {
45+
let nsUnderlineStyle = NSUnderlineStyle(lineStyle)
46+
#expect(nsUnderlineStyle == expectedNSUnderlineStyle)
47+
}
48+
}

0 commit comments

Comments
 (0)