Fixed mouse scrolling and margin issues.

This commit is contained in:
Justin Marshall
2026-08-06 21:37:13 -07:00
parent cc5a66a272
commit e5bab83116
5 changed files with 680 additions and 3 deletions
+172
View File
@@ -99,6 +99,10 @@ extern int vdbmgDevice;
extern struct REB * vpreb;
#ifdef OPUS_X64
static int vWin95ZoomPercent = 100;
static int vWin95BaseDxsInch = 0;
static int vWin95BaseDysInch = 0;
/* Read-only layout metrics used by the Win95 vertical ruler overlay. */
int OpusGetWin95VerticalRulerMetrics(hwnd, pypPageTop, pypPageBottom,
pdypTopMargin, pdypBottomMargin, pdypInch)
@@ -124,6 +128,174 @@ int *pdypTopMargin, *pdypBottomMargin, *pdypInch;
return fTrue;
}
/* Drive Page View by pixels so wheel/trackpad input can move continuously
through the inter-page gap instead of being reduced to scrollbar clicks. */
int OpusScrollWin95ContinuousPages(hwnd, dypScroll)
HWND hwnd;
int dypScroll;
{
int ww = WwFromHwnd(hwnd);
struct WWD *pwwd;
if (ww < wwDocMin || ww >= wwMac || dypScroll == 0)
return fFalse;
pwwd = PwwdWw(ww);
if (!pwwd->fPageView || pwwd->ipgd == ipgdNil)
return fFalse;
FScrollPageDyeHome(ww, dypScroll, fFalse, 0, fFalse);
return fTrue;
}
int OpusGetWin95ZoomPercent(hwnd)
HWND hwnd;
{
int ww = WwFromHwnd(hwnd);
return ww < wwDocMin || ww >= wwMac ? 100 : vWin95ZoomPercent;
}
int OpusGetWin95CurrentPageIndex(hwnd)
HWND hwnd;
{
int ww = WwFromHwnd(hwnd);
if (ww < wwDocMin || ww >= wwMac || !PwwdWw(ww)->fPageView)
return ipgdNil;
return PwwdWw(ww)->ipgd;
}
/* Change the screen mapping only. Page layout continues to use the original
twip/printer measurements, while fonts, sheets, rulers, and hit testing are
regenerated at the requested display scale. */
int OpusAdjustWin95Zoom(hwnd, dPercent)
HWND hwnd;
int dPercent;
{
int ww = WwFromHwnd(hwnd);
int wwT, flmRestore, zoomNew;
struct WWD *pwwd;
struct DOD *pdod;
struct FTI *pftiScreen;
if (ww < wwDocMin || ww >= wwMac)
return vWin95ZoomPercent;
zoomNew = max(25, min(300, vWin95ZoomPercent + dPercent));
if (zoomNew == vWin95ZoomPercent)
return zoomNew;
if (vWin95BaseDxsInch <= 0)
vWin95BaseDxsInch = max(1,
NMultDiv(vfli.dxsInch, 100, vWin95ZoomPercent));
if (vWin95BaseDysInch <= 0)
vWin95BaseDysInch = max(1,
NMultDiv(vfli.dysInch, 100, vWin95ZoomPercent));
pwwd = PwwdWw(ww);
flmRestore = FlmDisplayFromFlags(pwwd->fDisplayAsPrint,
pwwd->fPageView, vpref.fDraftView);
pftiScreen = vsci.pfti;
if (pftiScreen != NULL)
FreeFontsPfti(pftiScreen);
SetFlm(flmIdle);
vWin95ZoomPercent = zoomNew;
vfli.dxsInch = max(1,
NMultDiv(vWin95BaseDxsInch, zoomNew, 100));
vfli.dysInch = max(1,
NMultDiv(vWin95BaseDysInch, zoomNew, 100));
SetFlm(flmRestore);
InvalFli();
for (wwT = wwDocMin; wwT < wwMac; ++wwT)
if (mpwwhwwd[wwT] != hNil)
{
int dypOldPage, dypOldStep, dypOldOffset;
int dypNewPage, dypNewStep, dypNewOffset, yeNew;
pwwd = PwwdWw(wwT);
if (!pwwd->fPageView || pwwd->ipgd == ipgdNil)
continue;
dypOldPage = DyOfRc(&pwwd->rcePage);
dypOldStep = max(1, dypOldPage +
max(8, dypGrayOutsideSci / 3));
dypOldOffset = YeTopPage(wwT) - pwwd->rcePage.yeTop;
pdod = PdodDoc(PmwdWw(wwT)->doc);
dypNewPage = DysFromDya(pdod->dop.yaPage);
dypNewStep = max(1, dypNewPage +
max(8, dypGrayOutsideSci / 3));
dypNewOffset = NMultDiv(dypOldOffset, dypNewStep,
dypOldStep);
yeNew = YeTopPage(wwT) - dypNewOffset;
PrcSet(&pwwd->rcePage, 0, 0,
DxsFromDxa(pwwd, pdod->dop.xaPage), dypNewPage);
MoveRc(&pwwd->rcePage, XeLeftPage(wwT), yeNew);
pwwd->xhScroll = pwwd->xeScroll;
pwwd->fDirty = pwwd->fDrDirty = fTrue;
pwwd->fBackgroundDirty = fTrue;
TrashWw(wwT);
}
if (ww == wwCur)
selCur.fUpdateRuler = fTrue;
RefreshPage(ww);
return zoomNew;
}
/* Pane resizing changes rcwDisp but the original Page View leaves the old
horizontal sheet coordinate untouched. Recompute only that presentation
coordinate so document measurements and the vertical scroll position stay
intact. */
int OpusRecenterWin95PageView(hwnd)
HWND hwnd;
{
int ww = WwFromHwnd(hwnd);
int xeLeft;
struct WWD *pwwd;
if (ww < wwDocMin || ww >= wwMac)
return fFalse;
pwwd = PwwdWw(ww);
if (!pwwd->fPageView || pwwd->ipgd == ipgdNil)
return fFalse;
xeLeft = XeLeftPage(ww);
if (pwwd->rcePage.xeLeft != xeLeft)
{
MoveRc(&pwwd->rcePage, xeLeft, pwwd->rcePage.yeTop);
pwwd->xhScroll = pwwd->xeScroll;
TrashWw(ww);
SyncSbHor(ww);
}
return fTrue;
}
/* Expose the physical sheet rectangle and adjacent-page availability to the
Win95 chrome. The legacy display engine keeps one editable page live at a
time; the chrome uses these read-only values to paint the neighboring sheet
during a continuous-page scroll. */
int OpusGetWin95ContinuousPageMetrics(hwnd, pxpPageLeft, pypPageTop,
pxpPageRight, pypPageBottom, pdypPageGap, pfHasPrevious,
pfHasNext)
HWND hwnd;
int *pxpPageLeft, *pypPageTop, *pxpPageRight, *pypPageBottom;
int *pdypPageGap, *pfHasPrevious, *pfHasNext;
{
int ww = WwFromHwnd(hwnd);
int ipgdMac;
struct WWD *pwwd;
struct DOD *pdod;
if (ww < wwDocMin || ww >= wwMac)
return fFalse;
pwwd = PwwdWw(ww);
if (!pwwd->fPageView || pwwd->ipgd == ipgdNil)
return fFalse;
pdod = PdodDoc(PmwdWw(ww)->doc);
ipgdMac = pdod->hplcpgd == hNil ? 0 : IMacPlc(pdod->hplcpgd);
*pxpPageLeft = pwwd->rcePage.xeLeft;
*pypPageTop = pwwd->rcePage.yeTop;
*pxpPageRight = pwwd->rcePage.xeRight;
*pypPageBottom = pwwd->rcePage.yeBottom;
*pdypPageGap = max(8, dypGrayOutsideSci / 3);
*pfHasPrevious = pwwd->ipgd > 0;
*pfHasNext = pwwd->ipgd + 1 < ipgdMac;
return fTrue;
}
/* Commit a vertical-ruler drag through the document's existing DOP state.
The UI passes a screen-pixel margin; preserve header/footer overlap signs
and invalidate page layout exactly as the Page Setup path does. */
+38 -2
View File
@@ -2054,6 +2054,7 @@ IpgdNextWw(ww, fRepag)
int ipgd = PwwdWw(ww)->ipgd + 1;
int doc = PmwdWw(ww)->doc;
struct PLC **hplcpgd;
struct PGD pgdLast;
struct RPL rpl;
struct LBS lbsText, lbsFtn;
int fFormatVisiSave;
@@ -2062,8 +2063,25 @@ IpgdNextWw(ww, fRepag)
int flmSave = vflm;
#endif
Assert(PwwdWw(ww)->fPageView);
hplcpgd = PdodDoc(doc)->hplcpgd;
if (ipgd >= IMacPlc(PdodDoc(doc)->hplcpgd) && fRepag)
#ifdef OPUS_X64
/* A valid final PGD whose sentinel already reaches cpMac is a known end
of document. Re-running the 1989 repaginator every time a wheel event
presses against that edge can invalidate the live page descriptors and
briefly erase (or crash) the last page. */
if (fRepag && ipgd >= IMacPlc(hplcpgd) &&
IMacPlc(hplcpgd) > 0)
{
GetPlc(hplcpgd, IMacPlc(hplcpgd) - 1, &pgdLast);
if (!pgdLast.fUnk &&
CpPlc(hplcpgd, IMacPlc(hplcpgd)) >=
CpMacDocEdit(doc))
return ipgdNil;
}
#endif
if (ipgd >= IMacPlc(hplcpgd) && fRepag)
{
#ifdef MAC
InitFliForLayout(lmPagevw, &fFormatVisiSave);
@@ -2081,7 +2099,7 @@ IpgdNextWw(ww, fRepag)
EndFliLayout(lmSave, WinMac(flmSave, fFormatVisiSave));
}
return ipgd >= IMacPlc(PdodDoc(doc)->hplcpgd) ? ipgdNil : ipgd;
return ipgd >= IMacPlc(hplcpgd) ? ipgdNil : ipgd;
}
@@ -2441,6 +2459,9 @@ EXPORT SetElevWw(ww)
if (pwwd->fPageView)
{
int dqNextPg, dqCurPg;
#ifdef OPUS_X64
int yeHome, yeLastPage;
#endif
cpCurPg = CpFromIpgd(doc, ww, pwwd->ipgd); /* HM */
if ((ipgd = IpgdNextWw(ww, fFalse)) == ipgdNil)
cpNextPg = CpMacDoc(doc);
@@ -2451,9 +2472,24 @@ EXPORT SetElevWw(ww)
pwwd = PwwdWw(ww);
dqCurPg = DqRatio(cpCurPg, dqMax - dqPgvwElevSpace, cpMac);
dqNextPg = DqRatio(cpNextPg, dqMax - dqPgvwElevSpace, cpMac);
#ifdef OPUS_X64
yeHome = YeTopPage(ww);
if (ipgd == ipgdNil)
{
yeLastPage = min(yeHome, pwwd->rcwDisp.ywBottom -
DyOfRc(&pwwd->rcePage) - dypGrayOutsideSci);
dyeScrollMac = max(1, yeHome - yeLastPage);
}
else
dyeScrollMac = max(1, DyOfRc(&pwwd->rcePage) +
max(8, dypGrayOutsideSci / 3));
dyeScroll = min(dyeScrollMac,
max(0, yeHome - pwwd->rcePage.yeTop));
#else
dyeScrollMac = max(1,
DyOfRc(&pwwd->rcePage) - DyOfRc(&pwwd->rcwDisp));
dyeScroll = min(dyeScrollMac, max(0, -pwwd->rcePage.yeTop));
#endif
if (dyeScrollMac > 0)
dq = min( dqCurPg + NMultDiv(dqNextPg - dqCurPg, dyeScroll, dyeScrollMac),
dqMax - dqPgvwElevSpace);
+161
View File
@@ -51,6 +51,15 @@ extern int wwCur;
extern struct DR *PdrWw();
#ifdef OPUS_X64
/* Remember a completed end-of-document pagination probe. The old engine was
written for page-at-a-time buttons and does not tolerate the same failed
next-page repagination being requested for every wheel tick. */
static int vrgdocWin95NoNext[wwMax];
static int vrgipgdWin95NoNext[wwMax];
static CP vrgcpWin95NoNext[wwMax];
#endif
#ifdef WIN
extern int vlm;
extern int vflm;
@@ -1052,12 +1061,126 @@ FScrollPageDyeHome(ww, dye, fHome, ncp, fNoNewPage)
{
struct WWD *pwwd = PwwdWw(ww);
int dyeHome = pwwd->yeScroll - YeTopPage(ww);
#ifndef OPUS_X64
int w;
BOOL fSmallPage = FSmallPage(ww, &w, &w);
#else
int dypPage = DyOfRc(&pwwd->rcePage);
int dypPageGap = max(8, dypGrayOutsideSci / 3);
int yeHome = YeTopPage(ww);
int yeLastPage = min(yeHome,
pwwd->rcwDisp.ywBottom - dypPage - dypGrayOutsideSci);
int yeLimit;
int dyeRequested;
int dyeRemainder;
int docScroll;
int ipgdMacScroll;
CP cpMacScroll;
#endif
#ifdef WIN
int ipgdNew;
#endif
#ifdef OPUS_X64
/* Word 1.x normally swaps its single Page View display as soon as the
current sheet reaches the viewport edge. Let the sheet instead travel
one full page plus a gray inter-page gap. The Win95 chrome paints the
adjacent sheet in that space; when it reaches the home position we hand
the live layout to that page. */
if (dye >= 0)
{
dyeRequested = dye;
ipgdNew = IpgdNextWw(ww, fFalse);
/* Repaginate only when the following sheet is about to become visible.
This avoids doing page layout on every ordinary line-scroll. */
if (ipgdNew == ipgdNil &&
pwwd->yeScroll - dye <= yeLastPage)
{
docScroll = PmwdWw(ww)->doc;
cpMacScroll = CpMacDocEdit(docScroll);
ipgdMacScroll = IMacPlc(PdodDoc(docScroll)->hplcpgd);
if (vrgdocWin95NoNext[ww] != docScroll ||
vrgipgdWin95NoNext[ww] != pwwd->ipgd + 1 ||
vrgcpWin95NoNext[ww] != cpMacScroll)
{
/* Once the page table already contains multiple pages, its
active last page is authoritative. Typing/background layout
extends that table when content grows; a wheel tick must not
re-enter pagination on the live last-page descriptors. */
if (ipgdMacScroll > 1 &&
pwwd->ipgd + 1 >= ipgdMacScroll)
ipgdNew = ipgdNil;
else
ipgdNew = IpgdNextWw(ww, fTrue);
if (ipgdNew == ipgdNil)
{
vrgdocWin95NoNext[ww] = docScroll;
vrgipgdWin95NoNext[ww] = pwwd->ipgd + 1;
vrgcpWin95NoNext[ww] = cpMacScroll;
}
}
}
yeLimit = (!fNoNewPage && ipgdNew != ipgdNil) ?
yeHome - dypPage - dypPageGap : yeLastPage;
if ((ncp & ncpForceYPos) == 0)
dye = min(dye, max(0, pwwd->yeScroll - yeLimit));
if (dyeHome > 0 && fHome)
dye = min(dye, dyeHome);
dyeRemainder = max(0, dyeRequested - dye);
if (dye <= 0)
{
if (fNoNewPage)
return fTrue;
if (ipgdNew != ipgdNil)
SetPageDisp(ww, ipgdNew, yeHome, fFalse, fFalse);
else
Beep();
return fTrue;
}
ScrollPageDye(ww, dye);
if (!fNoNewPage && ipgdNew != ipgdNil &&
PwwdWw(ww)->yeScroll <= yeLimit)
{
SetPageDisp(ww, ipgdNew, yeHome, fFalse, fFalse);
if (dyeRemainder > 0)
FScrollPageDyeHome(ww, dyeRemainder, fFalse, ncp,
fFalse);
return fTrue;
}
}
else
{
dyeRequested = -dye;
ipgdNew = IpgdPrevWw(ww);
yeLimit = (!fNoNewPage && ipgdNew != ipgdNil) ?
yeHome + dypPage + dypPageGap : yeHome;
dye = min(-dye, max(0, yeLimit - pwwd->yeScroll));
if (dyeHome < 0 && fHome)
dye = min(dye, -dyeHome);
dyeRemainder = max(0, dyeRequested - dye);
if (dye <= 0)
{
if (fNoNewPage)
return fTrue;
if (ipgdNew != ipgdNil)
SetPageDisp(ww, ipgdNew, yeHome, fFalse, fFalse);
else
Beep();
return fTrue;
}
ScrollPageDye(ww, -dye);
if (!fNoNewPage && ipgdNew != ipgdNil &&
PwwdWw(ww)->yeScroll >= yeLimit)
{
SetPageDisp(ww, ipgdNew, yeHome, fFalse, fFalse);
if (dyeRemainder > 0)
FScrollPageDyeHome(ww, -dyeRemainder, fFalse, ncp,
fFalse);
return fTrue;
}
}
return fFalse;
#else /* !OPUS_X64 */
if (dye >= 0)
{
if ((ncp & ncpForceYPos) == 0)
@@ -1110,6 +1233,7 @@ FScrollPageDyeHome(ww, dye, fHome, ncp, fNoNewPage)
}
}
return fFalse;
#endif /* OPUS_X64 */
}
@@ -1121,16 +1245,20 @@ FScrollPageDyeHome(ww, dye, fHome, ncp, fNoNewPage)
/* %%Function:ScrollPageDye %%Owner:bryanl */
ScrollPageDye(ww, dye)
{
#ifndef OPUS_X64
int w;
#endif
Assert(cHpFreeze == 0);
vfLastScroll = fTrue;
Assert(PwwdWw(ww)->fPageView);
#ifndef OPUS_X64
if (FSmallPage(ww, &w, &w))
{
Beep();
return;
}
#endif
ClearInsertLine(&selCur);
ClearInsertLine(&selDotted);
@@ -1275,24 +1403,50 @@ int ww, dqThumb, dqThumbOld;
"want" to be. That is compared with the current
scroll and, presto, we scroll */
pwwd = PwwdWw(ww);
#ifdef OPUS_X64
if (ipgd == ipgdNil)
{
int yeHome = YeTopPage(ww);
int yeLastPage = min(yeHome, pwwd->rcwDisp.ywBottom -
DyOfRc(&pwwd->rcePage) - dypGrayOutsideSci);
dyeScrollMac = max(0, yeHome - yeLastPage);
}
else
dyeScrollMac = DyOfRc(&pwwd->rcePage) +
max(8, dypGrayOutsideSci / 3);
dyeScrollOld = min(dyeScrollMac,
max(0, YeTopPage(ww) - pwwd->rcePage.yeTop));
#else
dyeScrollMac = DyOfRc(&pwwd->rcePage) - DyOfRc(&pwwd->rcwDisp);
dyeScrollOld = max(0, -pwwd->rcePage.yeTop);
#endif
dqCurPage = DqRatio(cpCurPage, dqMax - dqPgvwElevSpace, cpMac);
dqNextPage = DqRatio(cpNextPage, dqMax - dqPgvwElevSpace, cpMac);
dyeScrollNew = NMultDiv(min(dqThumb - dqCurPage, dqNextPage - dqCurPage),
dyeScrollMac, max(1, dqNextPage - dqCurPage));
#ifdef OPUS_X64
dyeScrollNew = min(dyeScrollMac, max(0, dyeScrollNew));
#else
dyeScrollNew = max(-YeTopPage(ww), dyeScrollNew);
#endif
if (dqThumb > dqThumbOld)
{
#ifdef OPUS_X64
dye = dyeScrollNew - dyeScrollOld;
#else
dyeT = pwwd->rcePage.yeBottom + pwwd->rcwDisp.ywTop
- pwwd->rcwDisp.ywBottom;
dye = min(dyeT, dyeScrollNew - dyeScrollOld);
#endif
if (dye > 0)
ScrollPageDye(ww, dye);
}
else
{
/* move up on the page (move text down, looking at higher portions of page) */
#ifdef OPUS_X64
dye = dyeScrollOld - dyeScrollNew;
#else
if (cpCurPage == cp0 && pwwd->rcePage.yeTop
>= YeTopPage(ww))
dye = -pwwd->rcePage.yeTop + dypGrayOutsideSci;
@@ -1301,6 +1455,7 @@ int ww, dqThumb, dqThumbOld;
dyeT = pwwd->ywMin - pwwd->rcePage.yeTop;
dye = min(dyeT, dyeScrollOld - dyeScrollNew);
}
#endif
if (dye > 0)
ScrollPageDye(ww, -dye);
}
@@ -1680,7 +1835,13 @@ ScrollWwDyw(ww, dyw, ywTop)
/* InvalWwRc will not work because rcePage has not been offset yet,
it is done in caller */
#ifdef OPUS_X64
/* DrawBlankPage and the Win95 neighbor overlay repaint the uncovered strip;
suppress a separate background erase that otherwise flashes gray first. */
InvalidateRect(PwwdWw(ww)->hwnd, (LPRECT)&rcwUpdate, fFalse);
#else
InvalidateRect(PwwdWw(ww)->hwnd, (LPRECT)&rcwUpdate, fTrue);
#endif
}
+19
View File
@@ -1734,6 +1734,25 @@ LONG lParam;
return (LRESULT)MAKELONG((WORD)vibpProtect,
(WORD)vibp);
}
case 72:
return (LRESULT)PwwdWw(WwFromHwnd(hwnd))->ipgd;
case 73:
{
int wwQuery = WwFromHwnd(hwnd);
struct DOD *pdod = PdodDoc(PmwdWw(wwQuery)->doc);
return (LRESULT)(pdod->hplcpgd == hNil ? 0 :
IMacPlc(pdod->hplcpgd));
}
case 74:
return (LRESULT)PwwdWw(WwFromHwnd(hwnd))->rcePage.xeLeft;
case 75:
return (LRESULT)PwwdWw(WwFromHwnd(hwnd))->rcePage.yeTop;
case 76:
return (LRESULT)PwwdWw(WwFromHwnd(hwnd))->rcePage.xeRight;
case 77:
return (LRESULT)PwwdWw(WwFromHwnd(hwnd))->rcePage.yeBottom;
case 78:
return (LRESULT)OpusGetWin95ZoomPercent(hwnd);
}
return (LRESULT) -1;
#endif
+290 -1
View File
@@ -14,6 +14,14 @@
extern "C" int OpusGetWin95VerticalRulerMetrics(
HWND pane, int* page_top, int* page_bottom, int* top_margin,
int* bottom_margin, int* pixels_per_inch);
extern "C" int OpusGetWin95ContinuousPageMetrics(
HWND pane, int* page_left, int* page_top, int* page_right,
int* page_bottom, int* page_gap, int* has_previous, int* has_next);
extern "C" int OpusScrollWin95ContinuousPages(HWND pane, int pixels);
extern "C" int OpusGetWin95ZoomPercent(HWND pane);
extern "C" int OpusGetWin95CurrentPageIndex(HWND pane);
extern "C" int OpusAdjustWin95Zoom(HWND pane, int percent_delta);
extern "C" int OpusRecenterWin95PageView(HWND pane);
extern "C" int OpusSetWin95VerticalMargin(
HWND pane, int top_margin, int margin_pixels);
extern "C" int OpusGetWin95HorizontalRulerMetrics(
@@ -176,6 +184,26 @@ struct HorizontalRulerDragState {
HorizontalRulerDragState g_horizontal_ruler_drag{};
struct DocumentWheelState {
HWND pane = nullptr;
int scroll_remainder = 0;
int zoom_remainder = 0;
};
DocumentWheelState g_document_wheel{};
struct PageSnapshot {
HWND pane = nullptr;
int page_index = -1;
int zoom_percent = 100;
int width = 0;
int height = 0;
HBITMAP bitmap = nullptr;
ULONGLONG last_used = 0;
};
std::vector<PageSnapshot> g_page_snapshots;
int dpi_for_window(HWND window) {
using GetDpiForWindowProc = UINT(WINAPI*)(HWND);
static const auto get_dpi = reinterpret_cast<GetDpiForWindowProc>(
@@ -1010,7 +1038,7 @@ HWND create_zoom_combo(HWND toolbar) {
HWND combo = CreateWindowExW(
0, L"COMBOBOX", L"",
WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
CBS_DROPDOWNLIST,
CBS_DROPDOWN | CBS_AUTOHSCROLL,
0, 0, 10, 200, toolbar,
reinterpret_cast<HMENU>(static_cast<UINT_PTR>(kComboZoom)),
GetModuleHandleW(nullptr), nullptr);
@@ -1027,6 +1055,19 @@ HWND create_zoom_combo(HWND toolbar) {
return combo;
}
void update_zoom_combo(HWND pane, int percent) {
const HWND app = GetAncestor(pane, GA_ROOT);
const HWND toolbar = FindWindowExW(app, nullptr, kToolbarClass, nullptr);
ToolbarState* state = toolbar_state(toolbar);
if (state == nullptr || state->zoom_combo == nullptr) {
return;
}
const std::wstring text = std::to_wstring(percent) + L"%";
SendMessageW(state->zoom_combo, CB_SETCURSEL,
static_cast<WPARAM>(-1), 0);
SetWindowTextW(state->zoom_combo, text.c_str());
}
void update_toolbar_menu_checks(HWND app, const ToolbarState& state) {
const UINT standard = MF_BYCOMMAND |
(state.standard_visible ? MF_CHECKED : MF_UNCHECKED);
@@ -1196,6 +1237,195 @@ void cancel_vertical_margin_drag(HWND pane) {
redraw_vertical_ruler(pane);
}
PageSnapshot* find_page_snapshot(HWND pane, int page_index, int width,
int height, int zoom_percent) {
for (auto& snapshot : g_page_snapshots) {
if (snapshot.pane == pane && snapshot.page_index == page_index &&
snapshot.width == width && snapshot.height == height &&
snapshot.zoom_percent == zoom_percent) {
snapshot.last_used = GetTickCount64();
return &snapshot;
}
}
return nullptr;
}
void prune_page_snapshots(HWND pane) {
std::size_t pane_count = 0;
for (const auto& snapshot : g_page_snapshots) {
if (snapshot.pane == pane) {
++pane_count;
}
}
while (pane_count > 4) {
auto oldest = g_page_snapshots.end();
for (auto iterator = g_page_snapshots.begin();
iterator != g_page_snapshots.end(); ++iterator) {
if (iterator->pane == pane &&
(oldest == g_page_snapshots.end() ||
iterator->last_used < oldest->last_used)) {
oldest = iterator;
}
}
if (oldest == g_page_snapshots.end()) {
break;
}
if (oldest->bitmap != nullptr) {
DeleteObject(oldest->bitmap);
}
g_page_snapshots.erase(oldest);
--pane_count;
}
}
void update_current_page_snapshot(HWND pane, HDC dc) {
int page_left = 0;
int page_top = 0;
int page_right = 0;
int page_bottom = 0;
int page_gap = 0;
int has_previous = 0;
int has_next = 0;
if (!OpusGetWin95ContinuousPageMetrics(
pane, &page_left, &page_top, &page_right, &page_bottom,
&page_gap, &has_previous, &has_next)) {
return;
}
const int page_index = OpusGetWin95CurrentPageIndex(pane);
const int zoom_percent = OpusGetWin95ZoomPercent(pane);
const int width = page_right - page_left;
const int height = page_bottom - page_top;
if (page_index < 0 || width <= 0 || height <= 0) {
return;
}
PageSnapshot* snapshot = find_page_snapshot(
pane, page_index, width, height, zoom_percent);
if (snapshot == nullptr) {
PageSnapshot created{};
created.pane = pane;
created.page_index = page_index;
created.zoom_percent = zoom_percent;
created.width = width;
created.height = height;
created.bitmap = CreateCompatibleBitmap(dc, width, height);
created.last_used = GetTickCount64();
if (created.bitmap == nullptr) {
return;
}
HDC memory = CreateCompatibleDC(dc);
HBITMAP previous = static_cast<HBITMAP>(
SelectObject(memory, created.bitmap));
RECT background{0, 0, width, height};
FillRect(memory, &background,
static_cast<HBRUSH>(GetStockObject(WHITE_BRUSH)));
SelectObject(memory, previous);
DeleteDC(memory);
g_page_snapshots.push_back(created);
snapshot = &g_page_snapshots.back();
prune_page_snapshots(pane);
snapshot = find_page_snapshot(
pane, page_index, width, height, zoom_percent);
}
if (snapshot == nullptr || snapshot->bitmap == nullptr) {
return;
}
RECT client{};
RECT page{page_left, page_top, page_right, page_bottom};
RECT visible{};
GetClientRect(pane, &client);
if (!IntersectRect(&visible, &page, &client)) {
return;
}
HDC memory = CreateCompatibleDC(dc);
HBITMAP previous = static_cast<HBITMAP>(
SelectObject(memory, snapshot->bitmap));
BitBlt(memory, visible.left - page_left, visible.top - page_top,
visible.right - visible.left, visible.bottom - visible.top,
dc, visible.left, visible.top, SRCCOPY);
SelectObject(memory, previous);
DeleteDC(memory);
}
void draw_neighbor_sheet(HDC dc, const RECT& sheet, const RECT& client,
const PageSnapshot* snapshot) {
RECT visible{};
if (!IntersectRect(&visible, &sheet, &client)) {
return;
}
RECT shadow = sheet;
OffsetRect(&shadow, 3, 3);
RECT visible_shadow{};
if (IntersectRect(&visible_shadow, &shadow, &client)) {
HBRUSH shadow_brush = CreateSolidBrush(RGB(96, 96, 96));
FillRect(dc, &visible_shadow, shadow_brush);
DeleteObject(shadow_brush);
}
HBRUSH page_brush = CreateSolidBrush(RGB(255, 255, 255));
FillRect(dc, &visible, page_brush);
DeleteObject(page_brush);
if (snapshot != nullptr && snapshot->bitmap != nullptr) {
HDC memory = CreateCompatibleDC(dc);
HBITMAP previous = static_cast<HBITMAP>(
SelectObject(memory, snapshot->bitmap));
BitBlt(dc, visible.left, visible.top,
visible.right - visible.left, visible.bottom - visible.top,
memory, visible.left - sheet.left, visible.top - sheet.top,
SRCCOPY);
SelectObject(memory, previous);
DeleteDC(memory);
}
HBRUSH border_brush = CreateSolidBrush(RGB(96, 96, 96));
FrameRect(dc, &sheet, border_brush);
DeleteObject(border_brush);
}
void draw_continuous_page_neighbors(HWND pane, HDC dc) {
int page_left = 0;
int page_top = 0;
int page_right = 0;
int page_bottom = 0;
int page_gap = 0;
int has_previous = 0;
int has_next = 0;
if (!OpusGetWin95ContinuousPageMetrics(
pane, &page_left, &page_top, &page_right, &page_bottom,
&page_gap, &has_previous, &has_next)) {
return;
}
RECT client{};
GetClientRect(pane, &client);
const int page_height = page_bottom - page_top;
if (page_right <= page_left || page_height <= 0) {
return;
}
const int page_step = page_height + page_gap;
const int page_index = OpusGetWin95CurrentPageIndex(pane);
const int zoom_percent = OpusGetWin95ZoomPercent(pane);
if (has_previous) {
RECT previous{page_left, page_top - page_step,
page_right, page_bottom - page_step};
draw_neighbor_sheet(
dc, previous, client,
find_page_snapshot(pane, page_index - 1,
page_right - page_left, page_height,
zoom_percent));
}
if (has_next) {
RECT next{page_left, page_top + page_step,
page_right, page_bottom + page_step};
draw_neighbor_sheet(
dc, next, client,
find_page_snapshot(pane, page_index + 1,
page_right - page_left, page_height,
zoom_percent));
}
}
void draw_vertical_ruler(HWND pane, HDC dc) {
RECT client{};
GetClientRect(pane, &client);
@@ -1756,6 +1986,41 @@ void show_document_context_menu(HWND pane, POINT screen_point) {
LRESULT CALLBACK document_pane_proc(HWND pane, UINT message,
WPARAM w_param, LPARAM l_param) {
WNDPROC original = original_pane_proc(pane);
if (message == WM_MOUSEWHEEL) {
if (g_document_wheel.pane != pane) {
g_document_wheel = {};
g_document_wheel.pane = pane;
}
const int wheel_delta = GET_WHEEL_DELTA_WPARAM(w_param);
if ((GET_KEYSTATE_WPARAM(w_param) & MK_CONTROL) != 0) {
g_document_wheel.zoom_remainder += wheel_delta;
int percent_delta = 0;
while (g_document_wheel.zoom_remainder >= WHEEL_DELTA) {
percent_delta += 10;
g_document_wheel.zoom_remainder -= WHEEL_DELTA;
}
while (g_document_wheel.zoom_remainder <= -WHEEL_DELTA) {
percent_delta -= 10;
g_document_wheel.zoom_remainder += WHEEL_DELTA;
}
if (percent_delta != 0) {
const int percent = OpusAdjustWin95Zoom(
pane, percent_delta);
update_zoom_combo(pane, percent);
}
return 0;
}
const int scaled_delta = g_document_wheel.scroll_remainder -
wheel_delta * scale(pane, 48);
const int pixels = scaled_delta / WHEEL_DELTA;
g_document_wheel.scroll_remainder =
scaled_delta % WHEEL_DELTA;
if (pixels != 0 &&
OpusScrollWin95ContinuousPages(pane, pixels)) {
return 0;
}
}
if (message == WM_LBUTTONDOWN) {
POINT point{GET_X_LPARAM(l_param), GET_Y_LPARAM(l_param)};
bool top_marker = false;
@@ -1865,11 +2130,21 @@ LRESULT CALLBACK document_pane_proc(HWND pane, UINT message,
DefWindowProcW(pane, message, w_param, l_param);
HDC dc = GetDC(pane);
if (dc != nullptr) {
update_current_page_snapshot(pane, dc);
draw_continuous_page_neighbors(pane, dc);
draw_vertical_ruler(pane, dc);
ReleaseDC(pane, dc);
}
return result;
}
if (message == WM_SIZE) {
const LRESULT result = original != nullptr ?
CallWindowProcW(original, pane, message, w_param, l_param) :
DefWindowProcW(pane, message, w_param, l_param);
OpusRecenterWin95PageView(pane);
InvalidateRect(pane, nullptr, TRUE);
return result;
}
if (message == WM_RBUTTONDOWN) {
SetFocus(pane);
return 0;
@@ -1889,6 +2164,20 @@ LRESULT CALLBACK document_pane_proc(HWND pane, UINT message,
return 0;
}
if (message == WM_NCDESTROY) {
if (g_document_wheel.pane == pane) {
g_document_wheel = {};
}
for (auto iterator = g_page_snapshots.begin();
iterator != g_page_snapshots.end();) {
if (iterator->pane == pane) {
if (iterator->bitmap != nullptr) {
DeleteObject(iterator->bitmap);
}
iterator = g_page_snapshots.erase(iterator);
} else {
++iterator;
}
}
RemovePropW(pane, kOriginalPaneProcProperty);
if (original != nullptr) {
SetWindowLongPtrW(pane, GWLP_WNDPROC,