@@ -33,20 +33,48 @@ final class ZoomTimeline: @unchecked Sendable {
3333 return CGRect ( x: 0 , y: 0 , width: 1 , height: 1 )
3434 }
3535
36- let zoom = interpolatedZoom ( at: time, keyframes: kfs)
36+ if time <= kfs. first!. t {
37+ return ztpRect ( for: kfs. first!)
38+ }
39+ if time >= kfs. last!. t {
40+ return ztpRect ( for: kfs. last!)
41+ }
3742
38- if zoom. zoomLevel <= 1.0 {
39- return CGRect ( x: 0 , y: 0 , width: 1 , height: 1 )
43+ var lo = 0
44+ var hi = kfs. count - 1
45+ while lo < hi - 1 {
46+ let mid = ( lo + hi) / 2
47+ if kfs [ mid] . t <= time {
48+ lo = mid
49+ } else {
50+ hi = mid
51+ }
4052 }
4153
42- let visibleW = 1.0 / zoom. zoomLevel
43- let visibleH = 1.0 / zoom. zoomLevel
54+ let k0 = kfs [ lo]
55+ let k1 = kfs [ hi]
56+ let span = k1. t - k0. t
57+ guard span > 0 else {
58+ return ztpRect ( for: k1)
59+ }
60+
61+ let linearT = ( time - k0. t) / span
62+ let t = easeInOut ( linearT)
63+
64+ let inv0 = 1.0 / k0. zoomLevel
65+ let inv1 = 1.0 / k1. zoomLevel
66+ let zoom = 1.0 / ( inv0 + ( inv1 - inv0) * t)
4467
45- var originX = zoom. centerX - visibleW / 2
46- var originY = zoom. centerY - visibleH / 2
68+ if zoom <= 1.0 {
69+ return CGRect ( x: 0 , y: 0 , width: 1 , height: 1 )
70+ }
4771
48- originX = max ( 0 , min ( 1 - visibleW, originX) )
49- originY = max ( 0 , min ( 1 - visibleH, originY) )
72+ let cx = k0. centerX + ( k1. centerX - k0. centerX) * t
73+ let cy = k0. centerY + ( k1. centerY - k0. centerY) * t
74+ let visibleW = 1.0 / zoom
75+ let visibleH = 1.0 / zoom
76+ let originX = cx * ( 1 - visibleW)
77+ let originY = cy * ( 1 - visibleH)
5078
5179 return CGRect ( x: originX, y: originY, width: visibleW, height: visibleH)
5280 }
@@ -87,54 +115,23 @@ final class ZoomTimeline: @unchecked Sendable {
87115 return empty
88116 }
89117
90- private func interpolatedZoom( at time: Double , keyframes kfs: [ ZoomKeyframe ] ) -> ( zoomLevel: Double , centerX: Double , centerY: Double ) {
91- guard !kfs. isEmpty else {
92- return ( 1.0 , 0.5 , 0.5 )
93- }
94-
95- if time <= kfs. first!. t {
96- let k = kfs. first!
97- return ( k. zoomLevel, k. centerX, k. centerY)
98- }
99- if time >= kfs. last!. t {
100- let k = kfs. last!
101- return ( k. zoomLevel, k. centerX, k. centerY)
102- }
103-
104- var lo = 0
105- var hi = kfs. count - 1
106- while lo < hi - 1 {
107- let mid = ( lo + hi) / 2
108- if kfs [ mid] . t <= time {
109- lo = mid
110- } else {
111- hi = mid
112- }
113- }
114-
115- let k0 = kfs [ lo]
116- let k1 = kfs [ hi]
117- let span = k1. t - k0. t
118- guard span > 0 else {
119- return ( k1. zoomLevel, k1. centerX, k1. centerY)
118+ private func ztpRect( for k: ZoomKeyframe ) -> CGRect {
119+ if k. zoomLevel <= 1.0 {
120+ return CGRect ( x: 0 , y: 0 , width: 1 , height: 1 )
120121 }
121-
122- let linearT = ( time - k0. t) / span
123- let t = easeInOut ( linearT)
124-
125- let inv0 = 1.0 / k0. zoomLevel
126- let inv1 = 1.0 / k1. zoomLevel
127- let zoom = 1.0 / ( inv0 + ( inv1 - inv0) * t)
128- let cx = k0. centerX + ( k1. centerX - k0. centerX) * t
129- let cy = k0. centerY + ( k1. centerY - k0. centerY) * t
130-
131- return ( zoom, cx, cy)
122+ let visibleW = 1.0 / k. zoomLevel
123+ let visibleH = 1.0 / k. zoomLevel
124+ let originX = k. centerX * ( 1 - visibleW)
125+ let originY = k. centerY * ( 1 - visibleH)
126+ return CGRect ( x: originX, y: originY, width: visibleW, height: visibleH)
132127 }
133128
134129 static func followCursor( _ rect: CGRect , cursorPosition: CGPoint ) -> CGRect {
135130 guard rect. width < 1.0 || rect. height < 1.0 else { return rect }
136- let originX = max ( 0 , min ( 1 - rect. width, cursorPosition. x - rect. width / 2 ) )
137- let originY = max ( 0 , min ( 1 - rect. height, cursorPosition. y - rect. height / 2 ) )
131+ let cx = max ( 0 , min ( 1 , cursorPosition. x) )
132+ let cy = max ( 0 , min ( 1 , cursorPosition. y) )
133+ let originX = cx * ( 1 - rect. width)
134+ let originY = cy * ( 1 - rect. height)
138135 return CGRect ( x: originX, y: originY, width: rect. width, height: rect. height)
139136 }
140137
0 commit comments