84 lines
3.6 KiB
C++
84 lines
3.6 KiB
C++
#pragma once
|
|
#include "platform.h"
|
|
#include <algorithm>
|
|
#include <commctrl.h>
|
|
#include <windowsx.h>
|
|
|
|
namespace ks {
|
|
inline constexpr UINT ScrollActivity=WM_APP+42;
|
|
inline constexpr UINT_PTR ScrollFrameTimer=0x4b5301, ScrollIdleTimer=0x4b5302;
|
|
struct WheelBatch {
|
|
long long vertical{}, horizontal{};
|
|
WORD keys{};
|
|
LPARAM point{};
|
|
bool frame{}, active{}, listBox{};
|
|
int remainder{};
|
|
};
|
|
inline LRESULT CALLBACK wheelBatchProc(HWND w,UINT msg,WPARAM wp,LPARAM lp,UINT_PTR id,DWORD_PTR data)
|
|
{
|
|
auto *batch=reinterpret_cast<WheelBatch *>(data);
|
|
if(msg==WM_MOUSEWHEEL || msg==WM_MOUSEHWHEEL) {
|
|
(msg==WM_MOUSEWHEEL?batch->vertical:batch->horizontal)+=GET_WHEEL_DELTA_WPARAM(wp);
|
|
batch->keys=GET_KEYSTATE_WPARAM(wp); batch->point=lp;
|
|
if(!batch->active) {
|
|
batch->active=true;
|
|
SendMessageW(GetParent(w),ScrollActivity,(WPARAM)w,TRUE);
|
|
}
|
|
// Do not restart the frame timer: an uninterrupted wheel stream must
|
|
// keep moving, rather than wait for a debounce interval to expire.
|
|
if(!batch->frame) batch->frame=SetTimer(w,ScrollFrameTimer,16,nullptr)!=0;
|
|
SetTimer(w,ScrollIdleTimer,100,nullptr);
|
|
return 0;
|
|
}
|
|
if(msg==WM_TIMER && wp==ScrollFrameTimer) {
|
|
KillTimer(w,ScrollFrameTimer); batch->frame=false;
|
|
if(batch->listBox && batch->vertical) {
|
|
UINT lines=3; SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0,&lines,0);
|
|
if(lines==WHEEL_PAGESCROLL) {
|
|
RECT client{}; GetClientRect(w,&client);
|
|
auto height=SendMessageW(w,LB_GETITEMHEIGHT,0,0);
|
|
lines=std::max<LRESULT>(1,client.bottom/std::max<LRESULT>(1,height));
|
|
}
|
|
auto delta=batch->vertical+batch->remainder;
|
|
batch->remainder=(int)(delta%WHEEL_DELTA);
|
|
auto top=SendMessageW(w,LB_GETTOPINDEX,0,0);
|
|
auto count=SendMessageW(w,LB_GETCOUNT,0,0);
|
|
auto next=std::clamp<long long>(top-(delta/WHEEL_DELTA)*lines,0,std::max<LRESULT>(0,count-1));
|
|
if(next!=top) SendMessageW(w,LB_SETTOPINDEX,(WPARAM)next,0);
|
|
batch->vertical=0;
|
|
} else if(batch->vertical) {
|
|
// Native edit/list-view/window handlers retain their own scrolling
|
|
// semantics, including system settings and partial wheel deltas.
|
|
auto delta=(short)std::clamp<long long>(batch->vertical,-32760,32760);
|
|
batch->vertical-=delta;
|
|
DefSubclassProc(w,WM_MOUSEWHEEL,MAKEWPARAM(batch->keys,(WORD)delta),batch->point);
|
|
}
|
|
if(batch->horizontal) {
|
|
auto delta=(short)std::clamp<long long>(batch->horizontal,-32760,32760);
|
|
batch->horizontal-=delta;
|
|
DefSubclassProc(w,WM_MOUSEHWHEEL,MAKEWPARAM(batch->keys,(WORD)delta),batch->point);
|
|
}
|
|
if(batch->vertical || batch->horizontal)
|
|
batch->frame=SetTimer(w,ScrollFrameTimer,16,nullptr)!=0;
|
|
return 0;
|
|
}
|
|
if(msg==WM_TIMER && wp==ScrollIdleTimer) {
|
|
if(batch->frame) return 0;
|
|
KillTimer(w,ScrollIdleTimer); batch->active=false;
|
|
SendMessageW(GetParent(w),ScrollActivity,(WPARAM)w,FALSE);
|
|
return 0;
|
|
}
|
|
if(msg==WM_NCDESTROY) {
|
|
KillTimer(w,ScrollFrameTimer); KillTimer(w,ScrollIdleTimer);
|
|
RemoveWindowSubclass(w,wheelBatchProc,id); delete batch;
|
|
}
|
|
return DefSubclassProc(w,msg,wp,lp);
|
|
}
|
|
inline void enableWheelBatching(HWND window,bool listBox=false)
|
|
{
|
|
if(!window) return;
|
|
auto *batch=new WheelBatch; batch->listBox=listBox;
|
|
if(!SetWindowSubclass(window,wheelBatchProc,ScrollFrameTimer,(DWORD_PTR)batch)) delete batch;
|
|
}
|
|
}
|