Repository navigation
Make a zoom step change only the rows and paint once - #43
Merged
Merged
Conversation
The shell smoke now zooms 100% to 300%, down to 50% and back, and checks that each column is as wide as one opened at that level and that the widths at the end equal the widths at the start. It also checks that a zoom step leaves the rows to repaint and does not invalidate the toolbar. The document smoke checks, with real rows on screen, that a zoom step asks the list for no cell before it returns and that the rows repaint after it. On main these fail: at 125% the columns are 139 and 900 px instead of 140 and 900, and a step paints before it returns. tools/Measure-LeanRowsZoom.ps1 times a zoom step and three kinds of burst from outside the process, with the working set sampled every 2 ms.
A zoom step rebuilt every font and brush, gave every control its font again, set each column width with the list's redraw on, resized the list twice, laid out the whole window and repainted all of it before it returned. Each LVM_SETCOLUMNWIDTH with redraw on repaints every visible row, so a six-column file paid seven full repaints per step: 176 of the 241 ms a step took. Now a step creates the two grid fonts, gives them to the list and its header, sets the widths and refits the header with the list's redraw off, and only invalidates the list. Windows paints it once when the message queue is empty, so queued steps paint once. Ctrl+wheel notches already queued behind the first one join it, so a fast spin lays the rows out once, at the level it ends on. Column widths now scale from a per-column basis instead of from the width shown before the step. Stepping 100% to 300% to 50% and back used to shrink the Row column from 112 to 102 px; now it returns to 112, and a column at a level is as wide as one opened there. Columns inserted while zoomed, including at start with a saved zoom, are scaled too.
Three Ctrl+wheel notches posted to the list are one change from 100% to 125%, and no notch is left in the queue after the first is handled.
Turning the list's redraw back on lays out the list and its header for the new font and marks both for repainting, so the two one-pixel resizes of the list and the explicit RedrawWindow did nothing more. Mutation runs showed both could be deleted with every check green; they are gone. The smoke now checks, at every level, that the column titles are as high as HDM_LAYOUT says their font needs, that they are higher at 300% than at 50%, and that a step leaves them to repaint. Switching tabs keeps setting column widths with redraw on, as on main.
A mutation run showed that deleting the header's WM_SETFONT left every check green, because the list hands its own font to the header. The smoke now checks that the header holds the zoomed title font at every step.
The measurement waited for the zoomed layout and then asked the window to paint whatever was pending. It now also waits until the list and its column titles have no update region and a no-op message to the list has been answered, so a step's time includes the paint that shows it.
This was referenced Nov 6, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was slow
A zoom step on 0.5.0 took about 265 ms on a six-column file (median of 72 steps, 1400 x 900 window). Most of it was the column widths.
set_grid_zoom(crates/leanrows-win32/src/native.rs:2087 on main) callsset_column_widths(native.rs:5807), which sendsLVM_SETCOLUMNWIDTHonce per column with redraw on. Each of those messages repaints every visible row before it returns. On top of that the step rebuilt the whole theme, re-applied it to every control, refitted the header and repainted the whole window withRDW_UPDATENOW(native.rs:2096,redraw_shell_childrenat native.rs:2182).A traced build (six-column 2 MB file, 72 steps, medians) split the 241 ms step like this:
set_column_widths: 7 xLVM_SETCOLUMNWIDTH, 28.6 ms each, 4,095 cell requests per stepredraw_shell_childrenwithRDW_UPDATENOWapply_native_presentationrefit_list_headerlayout_childrenThemeResources::new(all fonts, brushes)save_zoom_percentTurning redraw off around the widths alone took the step from 241 ms to 94 ms. Creating fonts is not the cost (0.15 ms). The column widths also drifted: each step rescaled the width it found, so 100 -> 300 -> 50 -> 100 % turned 112/720 px into 102/715 px, and stepping to 125 % gave 139 px where opening at 125 % gives 140 px.
What changes
GridFonts), gives them to the list and its header and sets the widths with the list's redraw off, then turns redraw back on. That marks the rows and titles for one paint and paints nothing before the step returns. The rest of the window is left alone.ColumnZoomin zoom.rs). A step scales the design width, so widths do not drift. A width the user dragged becomes the new basis.Choices considered
Measurements
Same machine, same harness (
tools/Measure-LeanRowsZoom.ps1), main d323dbf and this branch, 3 runs each, 1400 x 900 window, saved zoom set to 100 % for the run. A step is timed from the command until the list shows the new level and has painted it (no update region left on the list or its titles, and a no-op message to the list answered).A step's time now follows how many cells are on screen: on the 2 MB file it is 37.5 ms at 50 % and 16.9 ms at 300 % (medians per level). Timing inside the app (document smoke on the 2 MB file, 943 x 535 rows area, 9 steps) splits it into the step itself, 6.0 to 8.9 ms, and the list's own paint, 11.8 to 22.6 ms. On the wide file the step is 7.9 to 9.9 ms and the paint 103 to 170 ms, because the list asks for every column of every visible row (1,755 cells at 100 %), not only the columns on screen.
Memory and resources, same runs:
Everything else, measured the same way on both builds (
tools/Compare-LeanRowsBuilds.ps1with 7 starts, and a probe that opens the 10M-row file, pages down and wheels 300 times each, sorts column 1 and finds a value near the end; 3 alternating runs, medians):The start, wheel and sort differences are inside the run-to-run spread (an earlier start comparison gave 334 ms on main and 327 ms on the branch). Sort time is dominated by disk and varies 3x between runs on both builds; no sort code changed. The executable grows by 15,360 bytes: 7,680 for the first commit's smoke checks, 6,656 for the change (bb01d9e) and 1,024 for the later checks. The smoke checks ship in the executable because CI runs them against the release build.
Tests
Commit 6182106 adds the checks before the change. On main they fail with:
at 125% the columns are [139, 900] px, not [140, 900] as when opened therea zoom step painted the rows before it returneda background tab zoomed to 125% kept [139, 900] px and showed [139, 900], not [140, 900]a zoom step painted 50 cells before it returned(20 for the .log fixture)Later commits add a check that three queued Ctrl+wheel notches are one change to 150 %, that the titles' height follows their font at every level, and that the titles get the zoomed font.
Mutation table
Each row is one change to the branch, then
cargo test -p leanrows-win32 --lib zoomand the release build's--smoke-testand--document-smoke-test(both fixtures), as CI runs them. The first failing line is quoted. Each mutation was reverted afterwards and the suite is green again (264 workspace tests, clippy with-D warnings, smokes at saved zoom 50, 100, 125 and 300 %).WM_SETREDRAW(0)inwith_list_redraw_offa zoom step painted the rows before it returnedUpdateWindow(list)at the end of the stepa zoom step painted the rows before it returnedredraw_shell_children(window)at the end of the stepa zoom step painted the rows before it returneda_zoomed_column_is_as_wide_as_one_opened_at_that_level(zoom.rs:305)a_tab_shown_again_carries_on_from_its_widths(zoom.rs:338)a background tab zoomed to 125% kept [112, 720] px and showed [112, 720], not [140, 900]three queued notches from 100% gave 110% with 0 left queued, not 150%the rows' font is not 110% of the body fontthe column titles do not have the 110% title fontat 150% the column titles are 24 px high, not the 29 px their font needszoom command did not apply and save 110%M4 is caught by the unit test only. On the smoke's path (100 -> 300 -> 50 -> 100 %) compounding happens to round back to the same widths.
Two lines an earlier version had were deleted because removing them left every check green: a one-pixel resize of the list to refit the header, and a
RedrawWindowof the list after the step. Turning redraw back on already lays out the header for the new font and invalidates the list and header. Timing inside the app agrees: with the resize back the step takes 10.1 to 13.0 ms instead of 6.0 to 8.9 ms, and the paint after it is the same.Other checks run by hand on the branch build:
Ready | 10000000 rows | column 1 / Text / Ascending) with the widths kept.Back-compat and rollback
Not touched
header.rs:169paints the column titles withfonts.semibold(), so the titles' text does not zoom although the header gets taller. Separate change.restore_tabsets the widths with redraw on, so switching tabs still repaints once per column.Known gaps
WM_COMMANDand posted wheel messages, not real input.