Release v0.6.20: edit history images and share smooth wheel handling

This commit is contained in:
2026-10-31 09:12:59 +02:00
parent 6603d28f68
commit 70ea9b10bc
22 changed files with 334 additions and 33 deletions
+83
View File
@@ -0,0 +1,83 @@
#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;
}
}