diff --git a/src/Opus/rulerdrw.c b/src/Opus/rulerdrw.c index 8cfefaa..c6638fb 100644 --- a/src/Opus/rulerdrw.c +++ b/src/Opus/rulerdrw.c @@ -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. */ diff --git a/src/Opus/wordtech/disp3.c b/src/Opus/wordtech/disp3.c index b646fb6..d757e21 100644 --- a/src/Opus/wordtech/disp3.c +++ b/src/Opus/wordtech/disp3.c @@ -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); diff --git a/src/Opus/wordtech/scroll.c b/src/Opus/wordtech/scroll.c index 400abe0..980e90f 100644 --- a/src/Opus/wordtech/scroll.c +++ b/src/Opus/wordtech/scroll.c @@ -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 } diff --git a/src/Opus/wproc.c b/src/Opus/wproc.c index 1d6ba0e..e483160 100644 --- a/src/Opus/wproc.c +++ b/src/Opus/wproc.c @@ -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 diff --git a/src/port/original/opus_win95_chrome.cpp b/src/port/original/opus_win95_chrome.cpp index e1708ee..52b70f2 100644 --- a/src/port/original/opus_win95_chrome.cpp +++ b/src/port/original/opus_win95_chrome.cpp @@ -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 g_page_snapshots; + int dpi_for_window(HWND window) { using GetDpiForWindowProc = UINT(WINAPI*)(HWND); static const auto get_dpi = reinterpret_cast( @@ -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(static_cast(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(-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( + SelectObject(memory, created.bitmap)); + RECT background{0, 0, width, height}; + FillRect(memory, &background, + static_cast(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( + 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( + 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,