Make zooming animation smooth for ZoomIt on mouse move (#42339)

This change coalesces the `WM_MOUSEMOVE` events with the `WM_TIMER`
events in the cases 1, 2 (telescope animation for zoom in, respectively,
out). I factored into a new lambda the `WM_TIMER` code and I made it
save the "now" amount of ticks into the new `g_TelescopingZoomLastTick`.

During `WM_MOUSEMOVE` processing, if the current tick count (rather
imprecise, but good enough) is larger than the animation step,
`ZOOM_LEVEL_STEP_TIME`, then I call that lambda. It's convenient that
the code in there already handles the "end of telescope zooming
animation" case.

## Validation Steps Performed

The choppy zooming animation was evident while moving the mouse during a
telescope zoom. It was also [reported on the Sysinternals
blog](https://techcommunity.microsoft.com/blog/sysinternals-blog/zoomit-v9-10-procdump-3-5-for-linux-and-jcd-1-0-1/4461244/replies/4461281).
This commit is contained in:
Alex Mihaiuc
2025-11-11 16:42:59 +01:00
committed by GitHub
parent e1edcc13b7
commit a0f33c8af1
2 changed files with 106 additions and 83 deletions

View File

@@ -122,7 +122,7 @@ BEGIN
DEFPUSHBUTTON "OK",IDOK,166,306,50,14 DEFPUSHBUTTON "OK",IDOK,166,306,50,14
PUSHBUTTON "Cancel",IDCANCEL,223,306,50,14 PUSHBUTTON "Cancel",IDCANCEL,223,306,50,14
LTEXT "ZoomIt v9.20",IDC_VERSION,42,7,73,10 LTEXT "ZoomIt v9.20",IDC_VERSION,42,7,73,10
LTEXT "Copyright <EFBFBD> 2006-2025 Mark Russinovich",IDC_COPYRIGHT,42,17,231,8 LTEXT "Copyright © 2006-2025 Mark Russinovich",IDC_COPYRIGHT,42,17,231,8
CONTROL "<a HREF=""https://www.sysinternals.com"">Sysinternals - www.sysinternals.com</a>",IDC_LINK, CONTROL "<a HREF=""https://www.sysinternals.com"">Sysinternals - www.sysinternals.com</a>",IDC_LINK,
"SysLink",WS_TABSTOP,42,26,150,9 "SysLink",WS_TABSTOP,42,26,150,9
ICON "APPICON",IDC_STATIC,12,9,20,20 ICON "APPICON",IDC_STATIC,12,9,20,20

View File

@@ -3906,6 +3906,7 @@ LRESULT APIENTRY MainWndProc(
OPENFILENAME openFileName; OPENFILENAME openFileName;
static TCHAR filePath[MAX_PATH] = {L"zoomit"}; static TCHAR filePath[MAX_PATH] = {L"zoomit"};
NOTIFYICONDATA tNotifyIconData; NOTIFYICONDATA tNotifyIconData;
static DWORD64 g_TelescopingZoomLastTick = 0ull;
const auto drawAllRightJustifiedLines = [&rc]( long lineHeight, bool doPop = false ) { const auto drawAllRightJustifiedLines = [&rc]( long lineHeight, bool doPop = false ) {
rc.top = textPt.y - static_cast<LONG>(g_TextBufferPreviousLines.size()) * lineHeight; rc.top = textPt.y - static_cast<LONG>(g_TextBufferPreviousLines.size()) * lineHeight;
@@ -3932,6 +3933,97 @@ LRESULT APIENTRY MainWndProc(
} }
}; };
const auto doTelescopingZoomTimer = [hWnd, wParam, lParam, &x, &y]( bool invalidate = true ) {
if( zoomTelescopeStep != 0.0f )
{
zoomLevel *= zoomTelescopeStep;
g_TelescopingZoomLastTick = GetTickCount64();
if( (zoomTelescopeStep > 1 && zoomLevel >= zoomTelescopeTarget) ||
(zoomTelescopeStep < 1 && zoomLevel <= zoomTelescopeTarget) )
{
zoomLevel = zoomTelescopeTarget;
g_TelescopingZoomLastTick = 0ull;
KillTimer( hWnd, wParam );
OutputDebug( L"SETCURSOR mon_left: %x mon_top: %x x: %d y: %d\n",
monInfo.rcMonitor.left,
monInfo.rcMonitor.top,
cursorPos.x,
cursorPos.y );
SetCursorPos( monInfo.rcMonitor.left + cursorPos.x,
monInfo.rcMonitor.top + cursorPos.y );
}
}
else
{
// Case where we didn't zoom at all
g_TelescopingZoomLastTick = 0ull;
KillTimer( hWnd, wParam );
}
if( wParam == 2 && zoomLevel == 1 )
{
g_Zoomed = FALSE;
if( g_ZoomOnLiveZoom )
{
GetCursorPos( &cursorPos );
cursorPos = ScalePointInRects( cursorPos, monInfo.rcMonitor, g_LiveZoomSourceRect );
SetCursorPos( cursorPos.x, cursorPos.y );
SendMessage( hWnd, WM_HOTKEY, LIVE_HOTKEY, 0 );
}
else if( lParam != SHALLOW_ZOOM )
{
// Figure out where final unzoomed cursor should be
if( g_Drawing )
{
cursorPos = prevPt;
}
OutputDebug( L"FINAL MOUSE: x: %d y: %d\n", cursorPos.x, cursorPos.y );
GetZoomedTopLeftCoordinates( zoomLevel, &cursorPos, &x, width, &y, height );
cursorPos.x = monInfo.rcMonitor.left + x + static_cast<int>((cursorPos.x - x) * zoomLevel);
cursorPos.y = monInfo.rcMonitor.top + y + static_cast<int>((cursorPos.y - y) * zoomLevel);
SetCursorPos( cursorPos.x, cursorPos.y );
}
if( hTargetWindow )
{
SetWindowPos( hTargetWindow, HWND_BOTTOM, rcTargetWindow.left, rcTargetWindow.top, rcTargetWindow.right - rcTargetWindow.left, rcTargetWindow.bottom - rcTargetWindow.top, 0 );
hTargetWindow = NULL;
}
DeleteDrawUndoList( &drawUndoList );
// Restore live zoom if we came from that mode
if( g_ZoomOnLiveZoom )
{
SendMessage( g_hWndLiveZoom, WM_USER_SET_ZOOM, static_cast<WPARAM>(g_LiveZoomLevel), reinterpret_cast<LPARAM>(&g_LiveZoomSourceRect) );
g_ZoomOnLiveZoom = FALSE;
forcePenResize = TRUE;
}
SetForegroundWindow( g_ActiveWindow );
ClipCursor( NULL );
g_HaveDrawn = FALSE;
g_TypeMode = TypeModeOff;
g_HaveTyped = FALSE;
g_Drawing = FALSE;
EnableDisableStickyKeys( TRUE );
DeleteObject( hTypingFont );
DeleteDC( hdcScreen );
DeleteDC( hdcScreenCompat );
DeleteDC( hdcScreenCursorCompat );
DeleteDC( hdcScreenSaveCompat );
DeleteObject( hbmpCompat );
DeleteObject (hbmpCursorCompat );
DeleteObject( hbmpDrawingCompat );
DeleteObject( hDrawingPen );
SetFocus( g_ActiveWindow );
ShowWindow( hWnd, SW_HIDE );
}
if( invalidate )
{
InvalidateRect( hWnd, NULL, FALSE );
}
};
switch (message) { switch (message) {
case WM_CREATE: case WM_CREATE:
@@ -4776,7 +4868,10 @@ LRESULT APIENTRY MainWndProc(
zoomTelescopeStep = ZOOM_LEVEL_STEP_IN; zoomTelescopeStep = ZOOM_LEVEL_STEP_IN;
zoomTelescopeTarget = g_ZoomLevels[g_SliderZoomLevel]; zoomTelescopeTarget = g_ZoomLevels[g_SliderZoomLevel];
if( g_AnimateZoom ) if( g_AnimateZoom )
{
zoomLevel = static_cast<float>(1.0) * zoomTelescopeStep; zoomLevel = static_cast<float>(1.0) * zoomTelescopeStep;
g_TelescopingZoomLastTick = GetTickCount64();
}
else else
zoomLevel = zoomTelescopeTarget; zoomLevel = zoomTelescopeTarget;
SetTimer( hWnd, 1, ZOOM_LEVEL_STEP_TIME, NULL ); SetTimer( hWnd, 1, ZOOM_LEVEL_STEP_TIME, NULL );
@@ -4794,6 +4889,7 @@ LRESULT APIENTRY MainWndProc(
// Start telescoping zoom. // Start telescoping zoom.
zoomTelescopeStep = ZOOM_LEVEL_STEP_OUT; zoomTelescopeStep = ZOOM_LEVEL_STEP_OUT;
zoomTelescopeTarget = 1.0; zoomTelescopeTarget = 1.0;
g_TelescopingZoomLastTick = GetTickCount64();
SetTimer( hWnd, 2, ZOOM_LEVEL_STEP_TIME, NULL ); SetTimer( hWnd, 2, ZOOM_LEVEL_STEP_TIME, NULL );
} else { } else {
@@ -5470,6 +5566,14 @@ LRESULT APIENTRY MainWndProc(
g_Zoomed, g_Drawing, g_Tracing); g_Zoomed, g_Drawing, g_Tracing);
OutputDebug(L"Window visible: %d Topmost: %d\n", IsWindowVisible(hWnd), GetWindowLong(hWnd, GWL_EXSTYLE)& WS_EX_TOPMOST); OutputDebug(L"Window visible: %d Topmost: %d\n", IsWindowVisible(hWnd), GetWindowLong(hWnd, GWL_EXSTYLE)& WS_EX_TOPMOST);
if( g_Zoomed && g_TelescopingZoomLastTick != 0ull && !g_Drawing && !g_Tracing )
{
ULONG64 now = GetTickCount64();
if( now - g_TelescopingZoomLastTick >= ZOOM_LEVEL_STEP_TIME )
{
doTelescopingZoomTimer( false );
}
}
if( g_Zoomed && (g_TypeMode == TypeModeOff) && !g_bSaveInProgress ) { if( g_Zoomed && (g_TypeMode == TypeModeOff) && !g_bSaveInProgress ) {
@@ -6735,88 +6839,7 @@ LRESULT APIENTRY MainWndProc(
case 2: case 2:
case 1: case 1:
// doTelescopingZoomTimer();
// Telescoping zoom timer
//
if( zoomTelescopeStep ) {
zoomLevel *= zoomTelescopeStep;
if( (zoomTelescopeStep > 1 && zoomLevel >= zoomTelescopeTarget ) ||
(zoomTelescopeStep < 1 && zoomLevel <= zoomTelescopeTarget )) {
zoomLevel = zoomTelescopeTarget;
KillTimer( hWnd, wParam );
OutputDebug( L"SETCURSOR mon_left: %x mon_top: %x x: %d y: %d\n",
monInfo.rcMonitor.left, monInfo.rcMonitor.top, cursorPos.x, cursorPos.y );
SetCursorPos( monInfo.rcMonitor.left + cursorPos.x,
monInfo.rcMonitor.top + cursorPos.y );
}
} else {
// Case where we didn't zoom at all
KillTimer( hWnd, wParam );
}
if( wParam == 2 && zoomLevel == 1 ) {
g_Zoomed = FALSE;
if( g_ZoomOnLiveZoom )
{
GetCursorPos( &cursorPos );
cursorPos = ScalePointInRects( cursorPos, monInfo.rcMonitor, g_LiveZoomSourceRect );
SetCursorPos( cursorPos.x, cursorPos.y );
SendMessage(hWnd, WM_HOTKEY, LIVE_HOTKEY, 0);
}
else if( lParam != SHALLOW_ZOOM )
{
// Figure out where final unzoomed cursor should be
if (g_Drawing) {
cursorPos = prevPt;
}
OutputDebug(L"FINAL MOUSE: x: %d y: %d\n", cursorPos.x, cursorPos.y );
GetZoomedTopLeftCoordinates(zoomLevel, &cursorPos, &x, width, &y, height);
cursorPos.x = monInfo.rcMonitor.left + x + static_cast<int>((cursorPos.x - x) * zoomLevel);
cursorPos.y = monInfo.rcMonitor.top + y + static_cast<int>((cursorPos.y - y) * zoomLevel);
SetCursorPos(cursorPos.x, cursorPos.y);
}
if( hTargetWindow ) {
SetWindowPos( hTargetWindow, HWND_BOTTOM, rcTargetWindow.left, rcTargetWindow.top,
rcTargetWindow.right - rcTargetWindow.left,
rcTargetWindow.bottom - rcTargetWindow.top, 0 );
hTargetWindow = NULL;
}
DeleteDrawUndoList( &drawUndoList );
// Restore live zoom if we came from that mode
if( g_ZoomOnLiveZoom ) {
SendMessage( g_hWndLiveZoom, WM_USER_SET_ZOOM, static_cast<WPARAM>(g_LiveZoomLevel), reinterpret_cast<LPARAM>(&g_LiveZoomSourceRect) );
g_ZoomOnLiveZoom = FALSE;
forcePenResize = TRUE;
}
SetForegroundWindow( g_ActiveWindow );
ClipCursor( NULL );
g_HaveDrawn = FALSE;
g_TypeMode = TypeModeOff;
g_HaveTyped = FALSE;
g_Drawing = FALSE;
EnableDisableStickyKeys( TRUE );
DeleteObject( hTypingFont );
DeleteDC( hdcScreen );
DeleteDC( hdcScreenCompat );
DeleteDC( hdcScreenCursorCompat );
DeleteDC( hdcScreenSaveCompat );
DeleteObject( hbmpCompat );
DeleteObject( hbmpCursorCompat );
DeleteObject( hbmpDrawingCompat );
DeleteObject( hDrawingPen );
SetFocus( g_ActiveWindow );
ShowWindow( hWnd, SW_HIDE );
}
InvalidateRect( hWnd, NULL, FALSE );
break; break;
case 3: case 3: