Skip to content

Commit 3be8076

Browse files
authored
Refactor the DirectionalNavigationAdapter (#592)
1 parent c8fdd9f commit 3be8076

File tree

4 files changed

+238
-66
lines changed

4 files changed

+238
-66
lines changed

Package.resolved

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
{
2+
"originHash" : "6c57d05a93b1a6b24af759f2ad90ce6166f484f12c6d5f744324fed58e7e684d",
3+
"pins" : [
4+
{
5+
"identity" : "cryptoswift",
6+
"kind" : "remoteSourceControl",
7+
"location" : "https://github.com/krzyzanowskim/CryptoSwift.git",
8+
"state" : {
9+
"revision" : "729e01bc9b9dab466ac85f21fb9ee2bc1c61b258",
10+
"version" : "1.8.4"
11+
}
12+
},
13+
{
14+
"identity" : "differencekit",
15+
"kind" : "remoteSourceControl",
16+
"location" : "https://github.com/ra1028/DifferenceKit.git",
17+
"state" : {
18+
"revision" : "073b9671ce2b9b5b96398611427a1f929927e428",
19+
"version" : "1.3.0"
20+
}
21+
},
22+
{
23+
"identity" : "fuzi",
24+
"kind" : "remoteSourceControl",
25+
"location" : "https://github.com/readium/Fuzi.git",
26+
"state" : {
27+
"revision" : "347aab158ff8894966ff80469b384bb5337928cd",
28+
"version" : "4.0.0"
29+
}
30+
},
31+
{
32+
"identity" : "gcdwebserver",
33+
"kind" : "remoteSourceControl",
34+
"location" : "https://github.com/readium/GCDWebServer.git",
35+
"state" : {
36+
"revision" : "584db89a4c3c3be27206cce6afde037b2b6e38d8",
37+
"version" : "4.0.1"
38+
}
39+
},
40+
{
41+
"identity" : "grdb.swift",
42+
"kind" : "remoteSourceControl",
43+
"location" : "https://github.com/groue/GRDB.swift.git",
44+
"state" : {
45+
"revision" : "2cf6c756e1e5ef6901ebae16576a7e4e4b834622",
46+
"version" : "6.29.3"
47+
}
48+
},
49+
{
50+
"identity" : "kingfisher",
51+
"kind" : "remoteSourceControl",
52+
"location" : "https://github.com/onevcat/Kingfisher.git",
53+
"state" : {
54+
"revision" : "1a0c2df04b31ed7aa318354f3583faea24f006fc",
55+
"version" : "5.15.8"
56+
}
57+
},
58+
{
59+
"identity" : "mbprogresshud",
60+
"kind" : "remoteSourceControl",
61+
"location" : "https://github.com/jdg/MBProgressHUD.git",
62+
"state" : {
63+
"revision" : "bca42b801100b2b3a4eda0ba8dd33d858c780b0d",
64+
"version" : "1.2.0"
65+
}
66+
},
67+
{
68+
"identity" : "sqlite.swift",
69+
"kind" : "remoteSourceControl",
70+
"location" : "https://github.com/stephencelis/SQLite.swift.git",
71+
"state" : {
72+
"revision" : "a95fc6df17d108bd99210db5e8a9bac90fe984b8",
73+
"version" : "0.15.3"
74+
}
75+
},
76+
{
77+
"identity" : "swiftsoup",
78+
"kind" : "remoteSourceControl",
79+
"location" : "https://github.com/scinfu/SwiftSoup.git",
80+
"state" : {
81+
"revision" : "bba848db50462894e7fc0891d018dfecad4ef11e",
82+
"version" : "2.8.7"
83+
}
84+
},
85+
{
86+
"identity" : "zip",
87+
"kind" : "remoteSourceControl",
88+
"location" : "https://github.com/marmelroy/Zip.git",
89+
"state" : {
90+
"revision" : "67fa55813b9e7b3b9acee9c0ae501def28746d76",
91+
"version" : "2.1.2"
92+
}
93+
},
94+
{
95+
"identity" : "zipfoundation",
96+
"kind" : "remoteSourceControl",
97+
"location" : "https://github.com/readium/ZIPFoundation.git",
98+
"state" : {
99+
"revision" : "175c389832d90cb0e992b2cb9d5d7878eccfe725",
100+
"version" : "3.0.0"
101+
}
102+
}
103+
],
104+
"version" : 3
105+
}

Sources/Navigator/DirectionalNavigationAdapter.swift

+127-62
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,18 @@ import Foundation
1313
/// This takes into account the reading progression of the navigator to turn
1414
/// pages in the right direction.
1515
public final class DirectionalNavigationAdapter {
16-
/// Indicates which viewport edges trigger page turns on tap.
17-
public struct TapEdges: OptionSet {
16+
@available(*, deprecated, renamed: "Edges")
17+
public typealias TapEdges = Edges
18+
19+
/// Indicates which viewport edges trigger page turns on pointer activation.
20+
public struct Edges: OptionSet {
1821
/// The user can turn pages when tapping on the edges of both the
1922
/// horizontal and vertical axes.
20-
public static let all: TapEdges = [.horizontal, .vertical]
23+
public static let all: Edges = [.horizontal, .vertical]
2124
/// The user can turn pages when tapping on the left and right edges.
22-
public static let horizontal = TapEdges(rawValue: 1 << 0)
25+
public static let horizontal = Edges(rawValue: 1 << 0)
2326
/// The user can turn pages when tapping on the top and bottom edges.
24-
public static let vertical = TapEdges(rawValue: 1 << 1)
27+
public static let vertical = Edges(rawValue: 1 << 1)
2528

2629
public var rawValue: Int
2730

@@ -30,62 +33,119 @@ public final class DirectionalNavigationAdapter {
3033
}
3134
}
3235

33-
private let tapEdges: TapEdges
34-
private let handleTapsWhileScrolling: Bool
35-
private let minimumHorizontalEdgeSize: Double
36-
private let horizontalEdgeThresholdPercent: Double?
37-
private let minimumVerticalEdgeSize: Double
38-
private let verticalEdgeThresholdPercent: Double?
36+
public struct PointerPolicy {
37+
/// The types of pointer that will trigger page turns.
38+
public var types: [PointerType]
39+
40+
/// Indicates which viewport edges recognize pointer activation.
41+
public var edges: Edges
42+
43+
/// Indicates whether to ignore pointer events when the publication is
44+
/// scrollable.
45+
public var ignoreWhileScrolling: Bool
46+
47+
/// The minimum horizontal edge dimension that triggers page turns, in
48+
/// pixels.
49+
public var minimumHorizontalEdgeSize: Double
50+
51+
/// The percentage of the viewport dimension used to calculate the
52+
/// horizontal edge size. If it is nil, a fixed edge of
53+
/// `minimumHorizontalEdgeSize` will be used instead.
54+
public var horizontalEdgeThresholdPercent: Double?
55+
56+
/// The minimum vertical edge dimension that triggers page turns, in
57+
/// pixels.
58+
public var minimumVerticalEdgeSize: Double
59+
60+
/// The percentage of the viewport dimension used to calculate the
61+
/// vertical edge size. If it is nil, a fixed edge of
62+
/// `minimumVerticalEdgeSize` will be used instead.
63+
public var verticalEdgeThresholdPercent: Double?
64+
65+
public init(
66+
types: [PointerType] = [.touch, .mouse],
67+
edges: Edges = .horizontal,
68+
ignoreWhileScrolling: Bool = true,
69+
minimumHorizontalEdgeSize: Double = 80.0,
70+
horizontalEdgeThresholdPercent: Double? = 0.3,
71+
minimumVerticalEdgeSize: Double = 80.0,
72+
verticalEdgeThresholdPercent: Double? = 0.3
73+
) {
74+
self.types = types
75+
self.edges = edges
76+
self.ignoreWhileScrolling = ignoreWhileScrolling
77+
self.minimumHorizontalEdgeSize = minimumHorizontalEdgeSize
78+
self.horizontalEdgeThresholdPercent = horizontalEdgeThresholdPercent
79+
self.minimumVerticalEdgeSize = minimumVerticalEdgeSize
80+
self.verticalEdgeThresholdPercent = verticalEdgeThresholdPercent
81+
}
82+
}
83+
84+
public struct KeyboardPolicy {
85+
/// Indicates whether arrow keys should turn pages.
86+
public var handleArrowKeys: Bool
87+
88+
/// Indicates whether the space key should turn the page forward.
89+
public var handleSpaceKey: Bool
90+
91+
public init(
92+
handleArrowKeys: Bool = true,
93+
handleSpaceKey: Bool = true
94+
) {
95+
self.handleArrowKeys = handleArrowKeys
96+
self.handleSpaceKey = handleSpaceKey
97+
}
98+
}
99+
100+
private let pointerPolicy: PointerPolicy
101+
private let keyboardPolicy: KeyboardPolicy
39102
private let animatedTransition: Bool
40103

41104
private weak var navigator: VisualNavigator?
42105

43106
/// Initializes a new `DirectionalNavigationAdapter`.
44107
///
45108
/// - Parameters:
46-
/// - tapEdges: Indicates which viewport edges handle taps.
47-
/// - handleTapsWhileScrolling: Indicates whether the page turns should be
48-
/// handled when the publication is scrollable.
49-
/// - minimumHorizontalEdgeSize: The minimum horizontal edge dimension
50-
/// triggering page turns, in pixels.
51-
/// - horizontalEdgeThresholdPercent: The percentage of the viewport
52-
/// dimension used to compute the horizontal edge size. When null,
53-
/// `minimumHorizontalEdgeSize` will be used instead.
54-
/// - minimumVerticalEdgeSize: The minimum vertical edge dimension
55-
/// triggering page turns, in pixels.
56-
/// - verticalEdgeThresholdPercent: The percentage of the viewport
57-
/// dimension used to compute the vertical edge size. When null,
58-
/// `minimumVerticalEdgeSize` will be used instead.
109+
/// - pointerPolicy: Policy on page turns using pointers (touches, mouse).
110+
/// - keyboardPolicy: Policy on page turns using the keyboard.
59111
/// - animatedTransition: Indicates whether the page turns should be
60112
/// animated.
61113
public init(
62-
tapEdges: TapEdges = .horizontal,
63-
handleTapsWhileScrolling: Bool = false,
64-
minimumHorizontalEdgeSize: Double = 80.0,
65-
horizontalEdgeThresholdPercent: Double? = 0.3,
66-
minimumVerticalEdgeSize: Double = 80.0,
67-
verticalEdgeThresholdPercent: Double? = 0.3,
114+
pointerPolicy: PointerPolicy = PointerPolicy(),
115+
keyboardPolicy: KeyboardPolicy = KeyboardPolicy(),
68116
animatedTransition: Bool = false
69117
) {
70-
self.tapEdges = tapEdges
71-
self.handleTapsWhileScrolling = handleTapsWhileScrolling
72-
self.minimumHorizontalEdgeSize = minimumHorizontalEdgeSize
73-
self.horizontalEdgeThresholdPercent = horizontalEdgeThresholdPercent
74-
self.minimumVerticalEdgeSize = minimumVerticalEdgeSize
75-
self.verticalEdgeThresholdPercent = verticalEdgeThresholdPercent
118+
self.pointerPolicy = pointerPolicy
119+
self.keyboardPolicy = keyboardPolicy
76120
self.animatedTransition = animatedTransition
77121
}
78122

79123
/// Binds the adapter to the given visual navigator.
80124
///
81125
/// It will automatically observe pointer and key events to turn pages.
82126
@MainActor public func bind(to navigator: VisualNavigator) {
83-
navigator.addObserver(.tap { [self, weak navigator] event in
84-
guard let navigator = navigator else {
85-
return false
127+
for pointerType in PointerType.allCases {
128+
guard pointerPolicy.types.contains(pointerType) else {
129+
continue
86130
}
87-
return await onTap(at: event.location, in: navigator)
88-
})
131+
132+
switch pointerType {
133+
case .touch:
134+
navigator.addObserver(.tap { [self, weak navigator] event in
135+
guard let navigator = navigator else {
136+
return false
137+
}
138+
return await onTap(at: event.location, in: navigator)
139+
})
140+
case .mouse:
141+
navigator.addObserver(.click { [self, weak navigator] event in
142+
guard let navigator = navigator else {
143+
return false
144+
}
145+
return await onTap(at: event.location, in: navigator)
146+
})
147+
}
148+
}
89149

90150
navigator.addObserver(.key { [self, weak navigator] event in
91151
guard let navigator = navigator else {
@@ -97,17 +157,17 @@ public final class DirectionalNavigationAdapter {
97157

98158
@MainActor
99159
private func onTap(at point: CGPoint, in navigator: VisualNavigator) async -> Bool {
100-
guard handleTapsWhileScrolling || !navigator.presentation.scroll else {
160+
guard !pointerPolicy.ignoreWhileScrolling || !navigator.presentation.scroll else {
101161
return false
102162
}
103163

104164
let bounds = navigator.view.bounds
105165
let options = NavigatorGoOptions(animated: animatedTransition)
106166

107-
if tapEdges.contains(.horizontal) {
108-
let horizontalEdgeSize = horizontalEdgeThresholdPercent
109-
.map { max(minimumHorizontalEdgeSize, $0 * bounds.width) }
110-
?? minimumHorizontalEdgeSize
167+
if pointerPolicy.edges.contains(.horizontal) {
168+
let horizontalEdgeSize = pointerPolicy.horizontalEdgeThresholdPercent
169+
.map { max(pointerPolicy.minimumHorizontalEdgeSize, $0 * bounds.width) }
170+
?? pointerPolicy.minimumHorizontalEdgeSize
111171
let leftRange = 0.0 ... horizontalEdgeSize
112172
let rightRange = (bounds.width - horizontalEdgeSize) ... bounds.width
113173

@@ -118,10 +178,10 @@ public final class DirectionalNavigationAdapter {
118178
}
119179
}
120180

121-
if tapEdges.contains(.vertical) {
122-
let verticalEdgeSize = verticalEdgeThresholdPercent
123-
.map { max(minimumVerticalEdgeSize, $0 * bounds.height) }
124-
?? minimumVerticalEdgeSize
181+
if pointerPolicy.edges.contains(.vertical) {
182+
let verticalEdgeSize = pointerPolicy.verticalEdgeThresholdPercent
183+
.map { max(pointerPolicy.minimumVerticalEdgeSize, $0 * bounds.height) }
184+
?? pointerPolicy.minimumVerticalEdgeSize
125185
let topRange = 0.0 ... verticalEdgeSize
126186
let bottomRange = (bounds.height - verticalEdgeSize) ... bounds.height
127187

@@ -143,23 +203,25 @@ public final class DirectionalNavigationAdapter {
143203
let options = NavigatorGoOptions(animated: animatedTransition)
144204

145205
switch event.key {
146-
case .arrowUp:
206+
case .arrowUp where keyboardPolicy.handleArrowKeys:
147207
return await navigator.goBackward(options: options)
148-
case .arrowDown, .space:
208+
case .arrowDown where keyboardPolicy.handleArrowKeys:
149209
return await navigator.goForward(options: options)
150-
case .arrowLeft:
210+
case .arrowLeft where keyboardPolicy.handleArrowKeys:
151211
return await navigator.goLeft(options: options)
152-
case .arrowRight:
212+
case .arrowRight where keyboardPolicy.handleArrowKeys:
153213
return await navigator.goRight(options: options)
214+
case .space where keyboardPolicy.handleSpaceKey:
215+
return await navigator.goForward(options: options)
154216
default:
155217
return false
156218
}
157219
}
158220

159-
@available(*, deprecated, message: "Use the initializer without the navigator parameter and call `bind(to:)`. See the migration guide.")
221+
@available(*, deprecated, message: "Use the new initializer without the navigator parameter and call `bind(to:)`. See the migration guide.")
160222
public init(
161223
navigator: VisualNavigator,
162-
tapEdges: TapEdges = .horizontal,
224+
tapEdges: Edges = .horizontal,
163225
handleTapsWhileScrolling: Bool = false,
164226
minimumHorizontalEdgeSize: Double = 80.0,
165227
horizontalEdgeThresholdPercent: Double? = 0.3,
@@ -168,12 +230,15 @@ public final class DirectionalNavigationAdapter {
168230
animatedTransition: Bool = false
169231
) {
170232
self.navigator = navigator
171-
self.tapEdges = tapEdges
172-
self.handleTapsWhileScrolling = handleTapsWhileScrolling
173-
self.minimumHorizontalEdgeSize = minimumHorizontalEdgeSize
174-
self.horizontalEdgeThresholdPercent = horizontalEdgeThresholdPercent
175-
self.minimumVerticalEdgeSize = minimumVerticalEdgeSize
176-
self.verticalEdgeThresholdPercent = verticalEdgeThresholdPercent
233+
pointerPolicy = PointerPolicy(
234+
types: [.touch, .mouse],
235+
ignoreWhileScrolling: !handleTapsWhileScrolling,
236+
minimumHorizontalEdgeSize: minimumHorizontalEdgeSize,
237+
horizontalEdgeThresholdPercent: horizontalEdgeThresholdPercent,
238+
minimumVerticalEdgeSize: minimumVerticalEdgeSize,
239+
verticalEdgeThresholdPercent: verticalEdgeThresholdPercent
240+
)
241+
keyboardPolicy = KeyboardPolicy()
177242
self.animatedTransition = animatedTransition
178243
}
179244

Sources/Navigator/Input/Pointer/PointerEvent.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public enum Pointer: Equatable, CustomStringConvertible {
7979
}
8080

8181
/// Type of a pointer.
82-
public enum PointerType: Equatable {
82+
public enum PointerType: Equatable, CaseIterable {
8383
case touch
8484
case mouse
8585
}

0 commit comments

Comments
 (0)