@@ -13,6 +13,11 @@ internal sealed class MandelbrotImageView : ImageView
1313 private const double DEFAULT_CENTER_Y = 0 ;
1414 private const double DEFAULT_SPAN = 3 ;
1515 private const int DEFAULT_ITERATIONS = 80 ;
16+ private const double PAN_FACTOR = 0.1 ;
17+ private const double ZOOM_IN_FACTOR = 0.8 ;
18+ private const double ZOOM_OUT_FACTOR = 1.25 ;
19+
20+ private Point ? _lastDragPosition ;
1621
1722 private double _centerX = DEFAULT_CENTER_X ;
1823 public double CenterX => _centerX ;
@@ -73,6 +78,7 @@ public MandelbrotImageView ()
7378 Arrangement = ViewArrangement . Resizable ;
7479 UseRasterGraphics = true ;
7580
81+ ReplacePanAndZoomCommands ( ) ;
7682 ViewportChanged += ( _ , _ ) => RenderMandelbrot ( ) ;
7783 }
7884
@@ -117,6 +123,16 @@ protected override bool CenterOnViewportPoint (Point position)
117123 return true ;
118124 }
119125
126+ protected override bool OnMouseEvent ( Mouse mouse )
127+ {
128+ if ( HandleFractalDrag ( mouse ) )
129+ {
130+ return true ;
131+ }
132+
133+ return base . OnMouseEvent ( mouse ) ;
134+ }
135+
120136 private static int CountIterations ( double cx , double cy , int maxIterations )
121137 {
122138 double zx = 0 ;
@@ -191,6 +207,80 @@ private Size GetPreferredRasterResolution ()
191207 return _sixelSupportResult . Resolution ;
192208 }
193209
210+ private bool HandleFractalDrag ( Mouse mouse )
211+ {
212+ if ( mouse . Position is not { } position )
213+ {
214+ return false ;
215+ }
216+
217+ if ( mouse . Flags . FastHasFlags ( MouseFlags . LeftButtonPressed ) && ! mouse . Flags . FastHasFlags ( MouseFlags . PositionReport ) )
218+ {
219+ _lastDragPosition = position ;
220+ App ? . Mouse . GrabMouse ( this ) ;
221+
222+ return true ;
223+ }
224+
225+ if ( mouse . Flags == ( MouseFlags . LeftButtonPressed | MouseFlags . PositionReport ) && _lastDragPosition is { } lastDragPosition )
226+ {
227+ bool panned = PanMandelbrot ( lastDragPosition . X - position . X , lastDragPosition . Y - position . Y ) ;
228+ _lastDragPosition = position ;
229+
230+ return panned ;
231+ }
232+
233+ if ( ! mouse . Flags . FastHasFlags ( MouseFlags . LeftButtonReleased ) )
234+ {
235+ return false ;
236+ }
237+
238+ _lastDragPosition = null ;
239+
240+ if ( App is { } && App . Mouse . IsGrabbed ( this ) )
241+ {
242+ App . Mouse . UngrabMouse ( ) ;
243+ }
244+
245+ return true ;
246+ }
247+
248+ private bool PanMandelbrot ( int deltaX , int deltaY )
249+ {
250+ Rectangle viewport = Viewport ;
251+
252+ if ( viewport . Width <= 0 || viewport . Height <= 0 )
253+ {
254+ return false ;
255+ }
256+
257+ double spanY = _span * viewport . Height / viewport . Width ;
258+ double centerX = _centerX + deltaX * _span * PAN_FACTOR ;
259+ double centerY = _centerY + deltaY * spanY * PAN_FACTOR ;
260+ SetMandelbrot ( centerX , centerY , _span , _maxIterations , true ) ;
261+
262+ return true ;
263+ }
264+
265+ private void ReplacePanAndZoomCommands ( )
266+ {
267+ AddCommand ( Command . ScrollLeft , ( ) => PanMandelbrot ( - 1 , 0 ) ) ;
268+ AddCommand ( Command . ScrollRight , ( ) => PanMandelbrot ( 1 , 0 ) ) ;
269+ AddCommand ( Command . ScrollUp , ( ) => PanMandelbrot ( 0 , - 1 ) ) ;
270+ AddCommand ( Command . ScrollDown , ( ) => PanMandelbrot ( 0 , 1 ) ) ;
271+ AddCommand ( Command . Home ,
272+ ( ) =>
273+ {
274+ ResetMandelbrot ( ) ;
275+
276+ return true ;
277+ } ) ;
278+ AddCommand ( Command . ZoomIn , context => ZoomMandelbrot ( context , ZOOM_IN_FACTOR ) ) ;
279+ AddCommand ( Command . ZoomOut , context => ZoomMandelbrot ( context , ZOOM_OUT_FACTOR ) ) ;
280+ AddCommand ( Command . PageUp , context => ZoomMandelbrot ( context , ZOOM_IN_FACTOR ) ) ;
281+ AddCommand ( Command . PageDown , context => ZoomMandelbrot ( context , ZOOM_OUT_FACTOR ) ) ;
282+ }
283+
194284 private void RenderMandelbrot ( )
195285 {
196286 Rectangle viewport = Viewport ;
@@ -243,4 +333,51 @@ private void SetMandelbrot (double centerX, double centerY, double span, int max
243333 private bool ShouldUseKittyGraphics ( ) =>
244334 _kittyGraphicsSupportResult is { IsSupported : true }
245335 && App ? . Driver ? . GetOutput ( ) . UseKittyGraphics == true ;
336+
337+ private bool ZoomMandelbrot ( ICommandContext ? context , double spanMultiplier )
338+ {
339+ Point ? anchor = context ? . Binding is MouseBinding { MouseEvent . Position : { } mousePosition } ? mousePosition : null ;
340+ double nextSpan = Math . Max ( MinimumSpan , _span * spanMultiplier ) ;
341+
342+ if ( Math . Abs ( nextSpan - _span ) < double . Epsilon )
343+ {
344+ return false ;
345+ }
346+
347+ if ( anchor is { } anchorPosition && TryGetMandelbrotPoint ( anchorPosition , _span , out double anchorX , out double anchorY ) )
348+ {
349+ double centerX = anchorX - ( anchorX - _centerX ) * spanMultiplier ;
350+ double centerY = anchorY - ( anchorY - _centerY ) * spanMultiplier ;
351+ SetMandelbrot ( centerX , centerY , nextSpan , _maxIterations , true ) ;
352+
353+ return true ;
354+ }
355+
356+ SetMandelbrot ( _centerX , _centerY , nextSpan , _maxIterations , true ) ;
357+
358+ return true ;
359+ }
360+
361+ private bool TryGetMandelbrotPoint ( Point position , double span , out double x , out double y )
362+ {
363+ x = _centerX ;
364+ y = _centerY ;
365+ Rectangle viewport = Viewport ;
366+
367+ if ( viewport . Width <= 0 || viewport . Height <= 0 )
368+ {
369+ return false ;
370+ }
371+
372+ if ( position . X < 0 || position . Y < 0 || position . X >= viewport . Width || position . Y >= viewport . Height )
373+ {
374+ return false ;
375+ }
376+
377+ double spanY = span * viewport . Height / viewport . Width ;
378+ x = _centerX + ( ( position . X + 0.5d ) / viewport . Width - 0.5d ) * span ;
379+ y = _centerY + ( ( position . Y + 0.5d ) / viewport . Height - 0.5d ) * spanY ;
380+
381+ return true ;
382+ }
246383}
0 commit comments