@@ -16,15 +16,19 @@ internal class ZoomSlider : Grid
1616 private Border m_zoomRatioLevelBorder ;
1717 private Label m_zoomRatioLevelLabel ;
1818
19+ private readonly float m_minRatio ;
1920 private readonly float m_maxRatio ;
2021
2122 private readonly Action < float > m_onChangedZoomRatio ;
2223 private readonly Action < PanUpdatedEventArgs > m_onPanned ;
2324
2425 private double m_startingX ;
26+ private float m_startingZoomRatio ;
2527 private double m_minimumZoomRatiosLayoutTranslationX ;
2628 private double m_maxZoomRatiosLayoutTranslationX ;
2729 private double m_zoomRatioLevel ;
30+ private float ? m_pendingZoomRatio ;
31+ private bool m_hasMeasuredZoomRatiosLayout ;
2832
2933 private int m_currentSnappedZoomRatio ;
3034
@@ -34,6 +38,7 @@ public ZoomSlider(float minRatio, float maxRatio, Action<float> onChangedZoomRat
3438 {
3539 InputTransparent = true ;
3640 CascadeInputTransparent = false ;
41+ m_minRatio = minRatio ;
3742 m_maxRatio = maxRatio ;
3843 m_onChangedZoomRatio = onChangedZoomRatio ;
3944 m_onPanned = onPanned ;
@@ -159,12 +164,23 @@ private static List<float> GenerateZoomRatios(float minRatio, float maxRatio)
159164
160165 private void ZoomRatiosLayoutOnSizeChanged ( object ? sender , EventArgs e )
161166 {
162- if ( m_zoomRatiosLayout . TranslationX ! = 0 )
167+ if ( m_zoomRatiosLayout . Width < = 0 )
163168 return ;
164-
165- m_zoomRatiosLayout . TranslationX += m_zoomRatiosLayout . Width / 2 ;
166- m_maxZoomRatiosLayoutTranslationX = m_zoomRatiosLayout . TranslationX ;
169+
170+ if ( m_hasMeasuredZoomRatiosLayout && m_pendingZoomRatio is null )
171+ return ;
172+
173+ m_maxZoomRatiosLayoutTranslationX = m_zoomRatiosLayout . Width / 2 ;
167174 m_minimumZoomRatiosLayoutTranslationX = m_maxZoomRatiosLayoutTranslationX - m_zoomRatiosLayout . Width ;
175+ m_hasMeasuredZoomRatiosLayout = true ;
176+
177+ if ( m_pendingZoomRatio is { } pendingZoomRatio )
178+ {
179+ SetZoomRatio ( pendingZoomRatio ) ;
180+ return ;
181+ }
182+
183+ m_zoomRatiosLayout . TranslationX = m_maxZoomRatiosLayoutTranslationX ;
168184 }
169185
170186 private double CalculateZoomRatio ( double desiredTranslationX )
@@ -173,48 +189,52 @@ private double CalculateZoomRatio(double desiredTranslationX)
173189 currentTranslation = m_zoomRatiosLayout . Width - currentTranslation ;
174190 var translationPercentage = ( currentTranslation / m_zoomRatiosLayout . Width ) ;
175191
176- var zoomRatioIndex = ( ( m_maxRatio - 1 ) * 10 ) * translationPercentage ;
192+ var zoomRatioIndex = ( ( m_maxRatio - m_minRatio ) * 10 ) * translationPercentage ;
177193
178- return zoomRatioIndex / 10 + 1 ;
194+ return zoomRatioIndex / 10 + m_minRatio ;
179195 }
180196
181197 private double CalculateTranslationXFromZoomRatio ( float zoomRatio )
182198 {
183- return m_maxZoomRatiosLayoutTranslationX - ( m_zoomRatiosLayout . Width * ( ( zoomRatio - 1 ) / ( m_maxRatio - 1 ) ) ) ;
199+ if ( Math . Abs ( m_maxRatio - m_minRatio ) < 0.01f )
200+ {
201+ return m_maxZoomRatiosLayoutTranslationX ;
202+ }
203+
204+ return m_maxZoomRatiosLayoutTranslationX - ( m_zoomRatiosLayout . Width * ( ( zoomRatio - m_minRatio ) / ( m_maxRatio - m_minRatio ) ) ) ;
184205 }
185206
186207 private void PanGestureRecognizerOnPanUpdated ( PanUpdatedEventArgs e )
187208 {
188209 m_onPanned . Invoke ( e ) ;
189210 }
211+
212+ private float ClampZoomRatio ( float zoomRatio ) => Math . Clamp ( zoomRatio , m_minRatio , m_maxRatio ) ;
190213
191214 public void TranslateZoomSlider ( PanUpdatedEventArgs e )
192215 {
193216 switch ( e . StatusType )
194217 {
195218 case GestureStatus . Started :
196219 m_startingX = e . TotalX ;
220+ m_startingZoomRatio = ClampZoomRatio ( ( float ) m_zoomRatioLevel ) ;
197221
198222 m_cancellationTokenSource . Cancel ( ) ;
199223 m_cancellationTokenSource = new CancellationTokenSource ( ) ;
200224 break ;
201225 case GestureStatus . Running :
202226 {
203- var desiredTranslationX = m_zoomRatiosLayout . TranslationX + e . TotalX - m_startingX ;
204-
205- var actualZoomRatio = CalculateZoomRatio ( m_zoomRatiosLayout . TranslationX ) ;
206-
207- // Vibrate when near integers
208- var roundedZoomRatio = ( int ) MathF . Round ( ( float ) actualZoomRatio ) ;
209- if ( Math . Abs ( roundedZoomRatio - actualZoomRatio ) < 0.025f )
227+ if ( m_minRatio < 1 )
210228 {
211- if ( roundedZoomRatio != m_currentSnappedZoomRatio )
212- {
213- VibrationService . SelectionChanged ( ) ;
214- }
215- m_currentSnappedZoomRatio = roundedZoomRatio ;
229+ var dragRange = Math . Max ( Width , 1 ) ;
230+ var zoomRatioDelta = ( float ) ( ( m_startingX - e . TotalX ) / dragRange * ( m_maxRatio - m_minRatio ) ) ;
231+ var desiredZoomRatio = ClampZoomRatio ( m_startingZoomRatio + zoomRatioDelta ) ;
232+ m_zoomRatiosLayout . TranslationX = CalculateTranslationXFromZoomRatio ( desiredZoomRatio ) ;
233+ UpdateZoomRatio ( desiredZoomRatio ) ;
234+ break ;
216235 }
217- else m_currentSnappedZoomRatio = - 1 ;
236+
237+ var desiredTranslationX = m_zoomRatiosLayout . TranslationX + e . TotalX - m_startingX ;
218238
219239 // Stop the user from panning too far or too little
220240 if ( desiredTranslationX > m_maxZoomRatiosLayoutTranslationX )
@@ -229,13 +249,30 @@ public void TranslateZoomSlider(PanUpdatedEventArgs e)
229249 m_zoomRatiosLayout . TranslationX = desiredTranslationX ;
230250 m_startingX = e . TotalX ;
231251
232- ZoomRatioLevel = actualZoomRatio ;
252+ UpdateZoomRatio ( CalculateZoomRatio ( desiredTranslationX ) ) ;
233253
234254 break ;
235255 }
236256 }
237257 }
238258
259+ private void UpdateZoomRatio ( double zoomRatio )
260+ {
261+ // Vibrate when near integers
262+ var roundedZoomRatio = ( int ) MathF . Round ( ( float ) zoomRatio ) ;
263+ if ( Math . Abs ( roundedZoomRatio - zoomRatio ) < 0.025f )
264+ {
265+ if ( roundedZoomRatio != m_currentSnappedZoomRatio )
266+ {
267+ VibrationService . SelectionChanged ( ) ;
268+ }
269+ m_currentSnappedZoomRatio = roundedZoomRatio ;
270+ }
271+ else m_currentSnappedZoomRatio = - 1 ;
272+
273+ ZoomRatioLevel = zoomRatio ;
274+ }
275+
239276 public double ZoomRatioLevel
240277 {
241278 get => m_zoomRatioLevel ;
@@ -249,13 +286,23 @@ private set
249286
250287 public void SetZoomRatio ( float zoomRatio )
251288 {
252- m_zoomRatiosLayout . TranslationX = CalculateTranslationXFromZoomRatio ( ( int ) zoomRatio ) ;
289+ zoomRatio = ClampZoomRatio ( zoomRatio ) ;
290+ m_zoomRatioLevel = zoomRatio ;
291+ m_zoomRatioLevelLabel . Text = zoomRatio . ToString ( "F1" ) ;
292+
293+ if ( ! m_hasMeasuredZoomRatiosLayout )
294+ {
295+ m_pendingZoomRatio = zoomRatio ;
296+ return ;
297+ }
298+
299+ m_zoomRatiosLayout . TranslationX = CalculateTranslationXFromZoomRatio ( zoomRatio ) ;
300+ m_pendingZoomRatio = null ;
253301 }
254302
255303 public async Task < bool > OnPinchToZoom ( float zoomRatio )
256304 {
257- m_zoomRatiosLayout . TranslationX = CalculateTranslationXFromZoomRatio ( zoomRatio ) ;
258- m_zoomRatioLevelLabel . Text = zoomRatio . ToString ( "F1" ) ;
305+ SetZoomRatio ( zoomRatio ) ;
259306
260307 m_cancellationTokenSource . Cancel ( ) ;
261308 m_cancellationTokenSource = new CancellationTokenSource ( ) ;
0 commit comments