Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move smooth scroll timers to a separate thread, instead of using SetTimer #4722

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions src/Canvas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,10 @@ bool gNoFlickerRender = true;

Kind kNotifAnnotation = "notifAnnotation";

// Timer for mouse wheel smooth scrolling
constexpr UINT_PTR kSmoothScrollTimerID = 6;

// Smooth scrolling factor. This is a value between 0 and 1.
// Each step, we scroll the needed delta times this factor.
// Therefore, a higher factor makes smooth scrolling faster.
static const double gSmoothScrollingFactor = 0.2;
static const double gSmoothScrollingFactor = 0.3;

// these can be global, as the mouse wheel can't affect more than one window at once
static int gDeltaPerLine = 0;
Expand Down Expand Up @@ -151,7 +148,7 @@ static void OnVScroll(MainWindow* win, WPARAM wp) {
if (si.nPos != currPos || msg == SB_THUMBTRACK) {
if (gGlobalPrefs->smoothScroll) {
win->scrollTargetY = si.nPos;
SetTimer(win->hwndCanvas, kSmoothScrollTimerID, USER_TIMER_MINIMUM, nullptr);
SetEvent(win->scrollTimer);
} else {
win->AsFixed()->ScrollYTo(si.nPos);
}
Expand Down Expand Up @@ -392,8 +389,8 @@ static void OnMouseMove(MainWindow* win, int x, int y, WPARAM) {

case MouseAction::Scrolling: {
win->annotationUnderCursor = nullptr;
win->yScrollSpeed = (y - win->dragStart.y) / SMOOTHSCROLL_SLOW_DOWN_FACTOR;
win->xScrollSpeed = (x - win->dragStart.x) / SMOOTHSCROLL_SLOW_DOWN_FACTOR;
win->yScrollSpeed = (y - win->dragStart.y) * SMOOTHSCROLL_FACTOR;
win->xScrollSpeed = (x - win->dragStart.x) * SMOOTHSCROLL_FACTOR;
break;
}
case MouseAction::SelectingText:
Expand Down Expand Up @@ -1211,7 +1208,7 @@ static void ZoomByMouseWheel(MainWindow* win, WPARAM wp) {
win->dragStartPending = false;
// Kill the smooth scroll timer when zooming
// We don't want to move to the new updated y offset after zooming
KillTimer(win->hwndCanvas, kSmoothScrollTimerID);
ResetEvent(win->scrollTimer);

short delta = GET_WHEEL_DELTA_WPARAM(wp);
Point pt = HwndGetCursorPos(win->hwndCanvas);
Expand Down Expand Up @@ -1639,7 +1636,7 @@ static LRESULT WndProcCanvasFixedPageUI(MainWindow* win, HWND hwnd, UINT msg, WP
return 0;

case WM_MBUTTONDOWN:
SetTimer(hwnd, SMOOTHSCROLL_TIMER_ID, SMOOTHSCROLL_DELAY_IN_MS, nullptr);
SetEvent(win->smoothscrollTimer);
// TODO: Create window that shows location of initial click for reference
OnMouseMiddleButtonDown(win, x, y, wp);
return 0;
Expand Down Expand Up @@ -1819,14 +1816,15 @@ static void OnTimer(MainWindow* win, HWND hwnd, WPARAM timerId) {

case SMOOTHSCROLL_TIMER_ID:
if (MouseAction::Scrolling == win->mouseAction) {
win->MoveDocBy(win->xScrollSpeed, win->yScrollSpeed);
double factor = win->scrollTimerDeltaTime / 1000.0;
win->MoveDocBy((int)(win->xScrollSpeed * factor), (int)(win->yScrollSpeed * factor));
} else if (MouseAction::Selecting == win->mouseAction || MouseAction::SelectingText == win->mouseAction) {
pt = HwndGetCursorPos(win->hwndCanvas);
if (NeedsSelectionEdgeAutoscroll(win, pt.x, pt.y)) {
OnMouseMove(win, pt.x, pt.y, MK_CONTROL);
}
} else {
KillTimer(hwnd, SMOOTHSCROLL_TIMER_ID);
ResetEvent(win->smoothscrollTimer);
win->yScrollSpeed = 0;
win->xScrollSpeed = 0;
}
Expand Down Expand Up @@ -1875,11 +1873,12 @@ static void OnTimer(MainWindow* win, HWND hwnd, WPARAM timerId) {
int delta = target - current;

if (delta == 0) {
KillTimer(hwnd, kSmoothScrollTimerID);
ResetEvent(win->scrollTimer);
} else {
// logf("Smooth scrolling from %d to %d (delta %d)\n", current, target, delta);

double step = delta * gSmoothScrollingFactor;
double a = pow(1.0 - gSmoothScrollingFactor, 60 * win->scrollTimerDeltaTime / 1000.0);
double step = delta * (1.0 - a);

// Round away from zero
int dy = step < 0 ? (int)floor(step) : (int)ceil(step);
Expand Down
3 changes: 3 additions & 0 deletions src/Canvas.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
/* Copyright 2022 the SumatraPDF project authors (see AUTHORS file).
License: GPLv3 */

// Timer for mouse wheel smooth scrolling
constexpr UINT_PTR kSmoothScrollTimerID = 6;

void UpdateDeltaPerLine();

LRESULT CALLBACK WndProcCanvas(HWND, UINT, WPARAM, LPARAM);
Expand Down
9 changes: 9 additions & 0 deletions src/MainWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,15 @@ struct MainWindow {

DocControllerCallback* cbHandler = nullptr;

// Seprate scroll thread for higher precision scrolling updates
HANDLE scrollTimerThread = nullptr;
bool scrollTimerCancelled = false;
// Scroll thread delta time (e.g. frame time) in ms
int scrollTimerDeltaTime = 0;

HANDLE scrollTimer = nullptr;
HANDLE smoothscrollTimer = nullptr;

// The target y offset for smooth scrolling.
// We use a timer to gradually scroll there.
int scrollTargetY = 0;
Expand Down
4 changes: 2 additions & 2 deletions src/Selection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -396,15 +396,15 @@ void OnSelectionStart(MainWindow* win, int x, int y, WPARAM) {
}

SetCapture(win->hwndCanvas);
SetTimer(win->hwndCanvas, SMOOTHSCROLL_TIMER_ID, SMOOTHSCROLL_DELAY_IN_MS, nullptr);
SetEvent(win->smoothscrollTimer);
ScheduleRepaint(win, 0);
}

void OnSelectionStop(MainWindow* win, int x, int y, bool aborted) {
if (GetCapture() == win->hwndCanvas) {
ReleaseCapture();
}
KillTimer(win->hwndCanvas, SMOOTHSCROLL_TIMER_ID);
ResetEvent(win->smoothscrollTimer);

// update the text selection before changing the selectionRect
if (MouseAction::SelectingText == win->mouseAction) {
Expand Down
3 changes: 1 addition & 2 deletions src/Selection.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
License: GPLv3 */

#define SMOOTHSCROLL_TIMER_ID 2
#define SMOOTHSCROLL_DELAY_IN_MS 20
#define SMOOTHSCROLL_SLOW_DOWN_FACTOR 10
#define SMOOTHSCROLL_FACTOR 10

/* Represents selected area on given page */
struct SelectionOnPage {
Expand Down
55 changes: 55 additions & 0 deletions src/SumatraPDF.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1464,6 +1464,50 @@ static void UpdateToolbarSidebarText(MainWindow* win) {
win->favLabelWithClose->SetLabel(_TRA("Favorites"));
}

static void ScrollTimerThread(MainWindow* win) {
std::array<HANDLE, 2> timers = {
win->scrollTimer,
win->smoothscrollTimer,
};

std::array<unsigned int, timers.size()> ids = {
kSmoothScrollTimerID,
SMOOTHSCROLL_TIMER_ID,
};

while (WaitForMultipleObjects(timers.size(), timers.data(), false, INFINITE) <= WAIT_OBJECT_0 + timers.size()) {
if (win->scrollTimerCancelled) {
break;
}

for (int i = 0; i < timers.size(); i++) {
if (WaitForSingleObject(timers[i], 0) == WAIT_OBJECT_0)
SendMessage(win->hwndCanvas, WM_TIMER, ids[i], 0);
}

// Re-fetch the monitor refresh rate in case window is moved to
// new window, or monitor refresh rate changed. Default to 60fps if
// anything goes wrong.

win->scrollTimerDeltaTime = 1000 / 60;

HMONITOR monitor = MonitorFromWindow(win->hwndCanvas, MONITOR_DEFAULTTOPRIMARY);
MONITORINFOEX info;
info.cbSize = sizeof(MONITORINFOEX);
if (GetMonitorInfo(monitor, &info)) {
DEVMODE mode;
mode.dmSize = sizeof(DEVMODE);
mode.dmDriverExtra = 0;

if (EnumDisplaySettings(info.szDevice, ENUM_CURRENT_SETTINGS, &mode) && mode.dmDisplayFrequency > 0) {
win->scrollTimerDeltaTime = 1000 / mode.dmDisplayFrequency;
}
}

Sleep(win->scrollTimerDeltaTime);
}
}

static MainWindow* CreateMainWindow() {
Rect windowPos = gGlobalPrefs->windowPos;
if (!windowPos.IsEmpty()) {
Expand Down Expand Up @@ -1513,6 +1557,11 @@ static MainWindow* CreateMainWindow() {
// hide scrollbars to avoid showing/hiding on empty window
ShowScrollBar(win->hwndCanvas, SB_BOTH, FALSE);

win->scrollTimer = CreateEvent(nullptr, true, false, nullptr);
win->smoothscrollTimer = CreateEvent(nullptr, true, false, nullptr);
Func0 fn = MkFunc0(ScrollTimerThread, win);
win->scrollTimerThread = StartThread(fn, "Scroll Thread");

ReportIf(win->menu);
win->menu = BuildMenu(win);
win->isMenuHidden = !gGlobalPrefs->showMenubar;
Expand Down Expand Up @@ -2728,6 +2777,12 @@ void CloseWindow(MainWindow* win, bool quitIfLast, bool forceClose) {

AbortFinding(win, true);
AbortPrinting(win);
if (win->scrollTimerThread) {
win->scrollTimerCancelled = true;
SetEvent(win->scrollTimer);
WaitForSingleObject(win->scrollTimerThread, INFINITE);
}
win->scrollTimerCancelled = false;

for (auto& tab : win->Tabs()) {
if (tab->AsFixed()) {
Expand Down