#include "ui_scroll.h" #include namespace { int forwarded{}; long long totalDelta{}; LRESULT CALLBACK observe(HWND w,UINT msg,WPARAM wp,LPARAM lp,UINT_PTR,DWORD_PTR) { if(msg==WM_MOUSEWHEEL) { ++forwarded; totalDelta+=GET_WHEEL_DELTA_WPARAM(wp); } return DefSubclassProc(w,msg,wp,lp); } void require(bool ok,const char *message) { if(!ok) throw std::runtime_error(message); } void wheel(HWND w,int delta) { SendMessageW(w,WM_MOUSEWHEEL,MAKEWPARAM(0,(WORD)(short)delta),0); } void flush(HWND w) { SendMessageW(w,WM_TIMER,ks::ScrollFrameTimer,0); } } int main() { INITCOMMONCONTROLSEX init{sizeof(init),ICC_STANDARD_CLASSES|ICC_LISTVIEW_CLASSES}; InitCommonControlsEx(&init); auto instance=GetModuleHandleW(nullptr); auto parent=CreateWindowW(L"STATIC",L"Scroll regression",WS_OVERLAPPEDWINDOW,0,0,500,400,nullptr,nullptr,instance,nullptr); try { auto list=CreateWindowW(L"LISTBOX",L"",WS_CHILD|WS_VSCROLL,0,0,300,200,parent,nullptr,instance,nullptr); for(int i=0;i<2000;++i) SendMessageW(list,LB_ADDSTRING,0,(LPARAM)L"row"); SendMessageW(list,LB_SETCURSEL,5,0); SendMessageW(list,LB_SETTOPINDEX,0,0); ks::enableWheelBatching(list,true); for(int i=0;i<1000;++i) wheel(list,-120); require(SendMessageW(list,LB_GETTOPINDEX,0,0)==0,"burst is deferred to one frame"); flush(list); UINT lines{}; SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0,&lines,0); require(!lines || SendMessageW(list,LB_GETTOPINDEX,0,0)>0,"burst moves list"); require(SendMessageW(list,LB_GETCURSEL,0,0)==5,"scroll does not change selection"); for(int i=0;i<1000;++i) wheel(list,120); flush(list); require(SendMessageW(list,LB_GETTOPINDEX,0,0)==0,"reverse burst clamps at top"); wheel(list,-60); flush(list); require(SendMessageW(list,LB_GETTOPINDEX,0,0)==0,"partial wheel step is retained"); wheel(list,-60); flush(list); require(!lines || SendMessageW(list,LB_GETTOPINDEX,0,0)>0,"partial steps combine across frames"); DestroyWindow(list); for(auto cls:{L"EDIT",WC_LISTVIEWW,L"COMBOBOX",L"STATIC"}) { auto w=CreateWindowW(cls,L"",WS_CHILD|WS_VSCROLL,0,0,300,200,parent,nullptr,instance,nullptr); require(w!=nullptr,"native scroll control created"); SetWindowSubclass(w,observe,1,0); ks::enableWheelBatching(w); forwarded=0; totalDelta=0; for(int i=0;i<100;++i) wheel(w,-120); require(forwarded==0,"native wheel burst is batched"); flush(w); require(forwarded==1 && totalDelta==-12000,"one native dispatch preserves burst distance"); wheel(w,60); flush(w); require(forwarded==2 && totalDelta==-11940,"native partial steps are preserved"); forwarded=0; totalDelta=0; for(int i=0;i<400;++i) wheel(w,-120); flush(w); flush(w); require(forwarded==2 && totalDelta==-48000,"large native burst is not truncated to 16 bits"); SendMessageW(w,WM_TIMER,ks::ScrollIdleTimer,0); DestroyWindow(w); } DestroyWindow(parent); std::cout<<"Passed shared wheel batching regressions (list, edit, table, combo, window)\n"; return 0; } catch(const std::exception &e) { DestroyWindow(parent); std::cerr<