@@ -354,126 +354,111 @@ private void HandleMouseMove(int screenX, int screenY)
354354 // --- Main Window Logic ---
355355 if ( frameOuterRect != null && frameInnerRect != null && hoverCursorRing != null && baseFrameGeometry != null )
356356 {
357- // Manual coordinate calculation for Main Window as well to be safe with mixed DPIs
358- // We positioned the window using _dpiScaleX/Y relative to the screen WorkingArea.
359357 var screen = availableMonitors . Length > 0 ? availableMonitors [ currentMonitorIndex ] : Screen . PrimaryScreen ;
360358 if ( screen != null )
361359 {
362- double relX = ( screenX - screen . WorkingArea . X ) / _dpiScaleX ;
363- double relY = ( screenY - screen . WorkingArea . Y ) / _dpiScaleY ;
364- var windowPt = new System . Windows . Point ( relX , relY ) ;
360+ ApplyHolePunchEffect (
361+ screenX , screenY ,
362+ screen ,
363+ _dpiScaleX , _dpiScaleY ,
364+ frameOuterRect . Value , frameInnerRect . Value ,
365+ hoverCursorRing ,
366+ EdgeLightBorder ,
367+ baseFrameGeometry ,
368+ pathOffsetX , pathOffsetY ,
369+ ( ring , x , y ) => { Canvas . SetLeft ( ring , x ) ; Canvas . SetTop ( ring , y ) ; }
370+ ) ;
371+ }
372+ }
365373
366- // Existing frame band detection (outer minus inner)
367- bool inFrameBand = frameOuterRect . Value . Contains ( windowPt ) && ! frameInnerRect . Value . Contains ( windowPt ) ;
374+ // --- Additional Windows Logic ---
375+ foreach ( var ctx in additionalMonitorWindows )
376+ {
377+ try
378+ {
379+ ApplyHolePunchEffect (
380+ screenX , screenY ,
381+ ctx . Screen ,
382+ ctx . DpiScaleX , ctx . DpiScaleY ,
383+ ctx . FrameOuterRect , ctx . FrameInnerRect ,
384+ ctx . HoverRing ,
385+ ctx . BorderPath ,
386+ ctx . BaseGeometry ,
387+ ctx . PathOffsetX , ctx . PathOffsetY ,
388+ ( ring , x , y ) => { ring . Margin = new Thickness ( x , y , 0 , 0 ) ; }
389+ ) ;
390+ }
391+ catch ( InvalidOperationException )
392+ {
393+ // Can happen if window is closing or not ready
394+ }
395+ }
396+ }
368397
369- // Early detection zone just inside the inner edge: a band with thickness = hole radius (cursor ring radius)
370- double ringDiameter = hoverCursorRing . Width ;
371- double holeRadius = ringDiameter / 2 ; // match ring size
372- var innerProximityRect = new Rect (
373- frameInnerRect . Value . X + holeRadius ,
374- frameInnerRect . Value . Y + holeRadius ,
375- frameInnerRect . Value . Width - ( holeRadius * 2 ) ,
376- frameInnerRect . Value . Height - ( holeRadius * 2 ) ) ;
398+ private void ApplyHolePunchEffect (
399+ int screenX , int screenY ,
400+ Screen screen ,
401+ double dpiScaleX , double dpiScaleY ,
402+ Rect frameOuterRect , Rect frameInnerRect ,
403+ Ellipse hoverRing ,
404+ System . Windows . Shapes . Path borderPath ,
405+ Geometry baseGeometry ,
406+ double pathOffsetX , double pathOffsetY ,
407+ Action < Ellipse , double , double > positionRing )
408+ {
409+ // Manual coordinate calculation to avoid PointFromScreen issues across monitors/DPIs
410+ // We positioned the window using dpiScaleX/Y relative to the screen WorkingArea.
411+ double relX = ( screenX - screen . WorkingArea . X ) / dpiScaleX ;
412+ double relY = ( screenY - screen . WorkingArea . Y ) / dpiScaleY ;
413+ var windowPt = new System . Windows . Point ( relX , relY ) ;
377414
378- // Near from inside means inside innerRect but within holeRadius of its edge (i.e., not deep inside innerProximityRect )
379- bool nearFromInside = frameInnerRect . Value . Contains ( windowPt ) && ! innerProximityRect . Contains ( windowPt ) ;
415+ // Existing frame band detection (outer minus inner )
416+ bool inFrameBand = frameOuterRect . Contains ( windowPt ) && ! frameInnerRect . Contains ( windowPt ) ;
380417
381- bool overFrame = inFrameBand || nearFromInside ;
418+ // Early detection zone just inside the inner edge: a band with thickness = hole radius (cursor ring radius)
419+ double ringDiameter = hoverRing . Width ;
420+ double holeRadius = ringDiameter / 2 ; // match ring size
421+ var innerProximityRect = new Rect (
422+ frameInnerRect . X + holeRadius ,
423+ frameInnerRect . Y + holeRadius ,
424+ frameInnerRect . Width - ( holeRadius * 2 ) ,
425+ frameInnerRect . Height - ( holeRadius * 2 ) ) ;
382426
383- if ( overFrame )
384- {
385- Canvas . SetLeft ( hoverCursorRing , windowPt . X - ringDiameter / 2 ) ;
386- Canvas . SetTop ( hoverCursorRing , windowPt . Y - ringDiameter / 2 ) ;
387- if ( hoverCursorRing . Visibility != Visibility . Visible )
388- {
389- hoverCursorRing . Visibility = Visibility . Visible ;
390- }
427+ // Near from inside means inside innerRect but within holeRadius of its edge (i.e., not deep inside innerProximityRect)
428+ bool nearFromInside = frameInnerRect . Contains ( windowPt ) && ! innerProximityRect . Contains ( windowPt ) ;
391429
392- // Punch a transparent hole under the ring by excluding a circle geometry from the frame
393- // Convert window coordinates to geometry local coordinates by subtracting stored offsets
394- var localCenter = new System . Windows . Point ( windowPt . X - pathOffsetX , windowPt . Y - pathOffsetY ) ;
395- var hole = new EllipseGeometry ( localCenter , holeRadius , holeRadius ) ;
396- EdgeLightBorder . Data = new CombinedGeometry ( GeometryCombineMode . Exclude , baseFrameGeometry , hole ) ;
397- }
398- else
399- {
400- if ( hoverCursorRing . Visibility != Visibility . Collapsed )
401- {
402- hoverCursorRing . Visibility = Visibility . Collapsed ;
403- }
430+ bool overFrame = inFrameBand || nearFromInside ;
404431
405- if ( EdgeLightBorder . Visibility != Visibility . Visible )
406- {
407- EdgeLightBorder . Visibility = Visibility . Visible ;
408- }
409- // Restore original geometry (remove hole)
410- if ( baseFrameGeometry != null && EdgeLightBorder . Data != baseFrameGeometry )
411- {
412- EdgeLightBorder . Data = baseFrameGeometry ;
413- }
414- }
432+ if ( overFrame )
433+ {
434+ positionRing ( hoverRing , windowPt . X - ringDiameter / 2 , windowPt . Y - ringDiameter / 2 ) ;
435+
436+ if ( hoverRing . Visibility != Visibility . Visible )
437+ {
438+ hoverRing . Visibility = Visibility . Visible ;
415439 }
416- }
417440
418- // --- Additional Windows Logic ---
419- int monitorIdx = 0 ;
420- foreach ( var ctx in additionalMonitorWindows )
441+ // Punch a transparent hole under the ring by excluding a circle geometry from the frame
442+ // Convert window coordinates to geometry local coordinates by subtracting stored offsets
443+ var localCenter = new System . Windows . Point ( windowPt . X - pathOffsetX , windowPt . Y - pathOffsetY ) ;
444+ var hole = new EllipseGeometry ( localCenter , holeRadius , holeRadius ) ;
445+ borderPath . Data = new CombinedGeometry ( GeometryCombineMode . Exclude , baseGeometry , hole ) ;
446+ }
447+ else
421448 {
422- monitorIdx ++ ;
423- try
449+ if ( hoverRing . Visibility != Visibility . Collapsed )
424450 {
425- // Manual coordinate calculation to avoid PointFromScreen issues across monitors/DPIs
426- // We positioned the window using ctx.DpiScaleX/Y relative to the screen WorkingArea.
427- // So we reverse that logic here.
428- double relX = ( screenX - ctx . Screen . WorkingArea . X ) / ctx . DpiScaleX ;
429- double relY = ( screenY - ctx . Screen . WorkingArea . Y ) / ctx . DpiScaleY ;
430- var windowPt = new System . Windows . Point ( relX , relY ) ;
431-
432- bool inFrameBand = ctx . FrameOuterRect . Contains ( windowPt ) && ! ctx . FrameInnerRect . Contains ( windowPt ) ;
433-
434- double ringDiameter = ctx . HoverRing . Width ;
435- double holeRadius = ringDiameter / 2 ;
436- var innerProximityRect = new Rect (
437- ctx . FrameInnerRect . X + holeRadius ,
438- ctx . FrameInnerRect . Y + holeRadius ,
439- ctx . FrameInnerRect . Width - ( holeRadius * 2 ) ,
440- ctx . FrameInnerRect . Height - ( holeRadius * 2 ) ) ;
441-
442- bool nearFromInside = ctx . FrameInnerRect . Contains ( windowPt ) && ! innerProximityRect . Contains ( windowPt ) ;
443- bool overFrame = inFrameBand || nearFromInside ;
451+ hoverRing . Visibility = Visibility . Collapsed ;
452+ }
444453
445- if ( overFrame )
446- {
447- // For Grid/Canvas positioning, we need to check how the ring is added.
448- // In CreateMonitorWindow, we added it to a Grid. Grid doesn't support Canvas.SetLeft/Top directly unless it's in a Canvas.
449- // Wait, CreateMonitorWindow adds it to a Grid.
450- // I should have used a Canvas or set Margins.
451- // Let's assume I can set Margins for now since it's in a Grid.
452-
453- ctx . HoverRing . Margin = new Thickness ( windowPt . X - ringDiameter / 2 , windowPt . Y - ringDiameter / 2 , 0 , 0 ) ;
454-
455- if ( ctx . HoverRing . Visibility != Visibility . Visible )
456- ctx . HoverRing . Visibility = Visibility . Visible ;
457-
458- var localCenter = new System . Windows . Point ( windowPt . X - ctx . PathOffsetX , windowPt . Y - ctx . PathOffsetY ) ;
459- var hole = new EllipseGeometry ( localCenter , holeRadius , holeRadius ) ;
460- ctx . BorderPath . Data = new CombinedGeometry ( GeometryCombineMode . Exclude , ctx . BaseGeometry , hole ) ;
461- }
462- else
463- {
464- if ( ctx . HoverRing . Visibility != Visibility . Collapsed )
465- ctx . HoverRing . Visibility = Visibility . Collapsed ;
466-
467- if ( ctx . BorderPath . Visibility != Visibility . Visible )
468- ctx . BorderPath . Visibility = Visibility . Visible ;
469-
470- if ( ctx . BorderPath . Data != ctx . BaseGeometry )
471- ctx . BorderPath . Data = ctx . BaseGeometry ;
472- }
454+ if ( borderPath . Visibility != Visibility . Visible )
455+ {
456+ borderPath . Visibility = Visibility . Visible ;
473457 }
474- catch ( InvalidOperationException )
458+ // Restore original geometry (remove hole)
459+ if ( baseGeometry != null && borderPath . Data != baseGeometry )
475460 {
476- // Can happen if window is closing or not ready
461+ borderPath . Data = baseGeometry ;
477462 }
478463 }
479464 }
0 commit comments