Skip to content

Fix bottom rows being clipped when the panel is resized at high DPI - #254

Open
leoshone wants to merge 3 commits into
NPP-JSONViewer:masterfrom
leoshone:fix/panel-dpi-clipping
Open

Fix bottom rows being clipped when the panel is resized at high DPI#254
leoshone wants to merge 3 commits into
NPP-JSONViewer:masterfrom
leoshone:fix/panel-dpi-clipping

Conversation

@leoshone

@leoshone leoshone commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fix bottom rows being clipped when the panel is resized at high DPI

The bug

On a monitor whose DPI scale is not 100%, expanding a JSON tree fully and
scrolling to the bottom leaves the last few rows physically unreachable: the
vertical scrollbar reports that it is at the end, but the bottom rows are
simply outside the visible panel. The node path box at the bottom of the panel
disappears completely. The larger the panel (and the higher the DPI scale),
the more rows are lost.

Measured on a 150% DPI display (JSON Viewer docked, panel client 394x968):

what before the fix
tree control bottom, in panel client coordinates 1181 px (213 px below the panel)
unreachable rows at 100% zoom (item height 22 px) ~10 rows
node path edit box 115 px below the panel, fully invisible

Root cause

JsonViewDlg::AdjustDocPanelSize() multiplied the size delta between the
current panel size and the initially recorded one by CUtility::GetDesktopScale():

auto newDeltaHeight = nHeight - m_lfInitialClientHeight;
auto addHeight      = static_cast<int>((newDeltaHeight - m_lfDeltaHeight) * fDeskScale);

nHeight comes from WM_SIZE and m_lfInitialClientHeight from
GetClientRect - both are already in physical pixels. Multiplying the
delta by the DPI scale again makes the child controls grow s times faster
than the panel itself on any scaled display (s > 1). The tree control
computes its scroll range from its own (oversized) client height, so its
scrollbar is convinced it has reached the end while the overflow rows are
clipped by the parent dialog. At 100% scaling (s == 1) the error is exactly
zero, which is why the bug only shows up on scaled monitors.

The fix

Position the resizable controls from the current client size instead of
accumulating scaled deltas:

  • the template layout of the four controls (search box, search button, tree,
    node path box) is captured once when the dialog is created;
  • on every WM_SIZE each control is placed at template rect + unscaled
    delta
    , so its designed margins to the panel edge are preserved at any DPI;
  • the tree height is derived as "everything between the tool bar row and the
    node path box", with the node path box pinned to the bottom of the client
    area - the layout is now self-correcting instead of accumulated.

This also fixes two adjacent effects of the same arithmetic: the needless
horizontal scrollbar caused by the oversized tree width, and the drift
between repeated resizes caused by truncating scaled deltas.

Verification

Measured with live Win32 geometry probes against the real plugin inside
Notepad++, at 150% DPI, with the tree fully expanded (TVM_GETVISIBLECOUNT,
TVM_GETNEXTITEM(TVGN_LASTVISIBLE), control rects mapped into the panel's
client space):

  • before: tree bottom 213 px below the panel client area, last visible row
    reported as the final one while it was clipped;
  • after: tree bottom 36 px above the panel bottom (the space of the node path
    box), all 91 rows of the test document reachable after scrolling to the
    bottom, node path box visible again;
  • re-measured across five window resizes (+150 px, +300 px, -200 px, back):
    no drift, overflow stays at zero;
  • existing E2E suites (zoom persistence, per-tab snapshot, expansion on
    refresh, draw-on-open, jsonc recognition) all pass unchanged.

@SinghRajenM

Copy link
Copy Markdown
Collaborator

Please share the before after comparison.

@leoshone

leoshone commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Here is the before/after comparison, taken in the same portable Notepad++ - same document (91 nodes, fully expanded), same window size, zoom 100%. Only the plugin DLL differs between the two runs.

Before - the tree is scrolled to the very bottom (scrollbar reports the end), yet the bottom rows are physically outside the panel and the node path box is gone:

before

After - all rows reachable, the horizontal scrollbar is back inside the tree client area, and the node path box reappears:

after

Measured geometry at 150% DPI:

what before after
tree bottom vs panel client area 213 px below the panel 36 px inside (the node path box area)
unreachable rows at 100% zoom (item height 22 px) ~10 0
node path edit box 115 px below the panel, fully invisible visible, pinned to the panel bottom
vertical scrollbar lower part clipped fully visible
across 5 window resizes (+150 px, +300 px, -200 px, back) - no drift, overflow stays at 0

The screenshots were produced with a live geometry probe (control rects mapped into the panel's client space, TVM_GETNEXTITEM(TVGN_LASTVISIBLE) for the reachable row), so the numbers above are measured, not estimated.

AdjustDocPanelSize multiplied the pixel delta between the new panel size
and the initial one by the desktop DPI scale. Both values are already in
physical pixels, so on any monitor whose scale is not 100% the tree (and
the node path box) grew s times faster than the panel itself and slid
below the panel's client area. The tree control computed its scroll
range from its own oversized height, so the scrollbar reported the end
while the last (s-1)*growth/itemHeight rows were physically outside the
visible panel: fully expanded long documents showed rows that could
never be scrolled into view.

Measurements at 150% scaling before the fix: tree bottom 213 px below
the panel client area (5+ unreachable rows at 100% zoom), node path box
115 px below it. Positioning is now absolute - template rect plus the
unscaled delta, with the tree ending above a node path box that is
pinned to the bottom of the client area - which also makes repeated
resizes idempotent instead of accumulated.
MSVC's windows.h defines max as a macro unless NOMINMAX is set, so
std::max(...) failed to compile as std::(...). Parenthesize the call -
the standard portable workaround - instead of touching the project's
include settings.
The tree height is now derived from the node path box position instead
of the raw height delta, so the variable is gone.
@leoshone
leoshone force-pushed the fix/panel-dpi-clipping branch from be21c0c to 2bf3b4e Compare September 7, 2026 10:20
leoshone added a commit to leoshone/JSON-Viewer that referenced this pull request Sep 7, 2026
…NPP-JSONViewer#255 opened

- NPP-JSONViewer#251 was approved and merged - the small-pure-PR strategy works.
- Upstream moved to fa8d2b9 (settings saved on exit via writeIfChanged,
  include reordering) and renamed our TREE_ZOOM key to TREE_ZOOM_LEVEL;
  documented the consequences for the next integration sync.
- NPP-JSONViewer#253/NPP-JSONViewer#254 rebased onto fa8d2b9, all three PRs mergeable again.
- NPP-JSONViewer#255 opened for the narrow jsonc recognition, with the forward pointer
  added to the NPP-JSONViewer#251 thread as planned.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants