Repository navigation
[Screen Ruler] Add DIP as an extra measurement unit - #49588
Merged
Merged
Conversation
Show and copy only the selected unit, add per-monitor DIP conversion, and correct the physical-unit fallback. Preserve existing persisted unit values and add focused native, settings, and UI coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1a6361cf-d701-4a9b-b8e8-d5700c5ba140
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a single measurement-unit setting to the Screen Ruler (MeasureTool) experience, so overlay and clipboard output are rendered in exactly one user-selected unit (Pixels, DIP, Inches, Centimeters, Millimeters), with DIP calculated using the DPI of the monitor containing the measurement.
Changes:
- Updated Settings UI strings/XAML to present a single âMeasurement unitâ ComboBox option (including DIP).
- Updated MeasureToolCore measurement pipeline to carry per-monitor DPI into conversions/formatting, fix mm fallback conversion, and print one unit (with explicit
pxsuffix). - Added/updated unit tests (native conversion/formatting + settings serialization compatibility) and UITest coverage for DIP clipboard output.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/settings-ui/Settings.UI/Strings/en-us/Resources.resw | Updates Settings strings for the new single measurement-unit setting and adds DIP option text. |
| src/settings-ui/Settings.UI/SettingsXAML/Views/MeasureToolPage.xaml | Adds an AutomationId and appends the DIP option to the measurement unit ComboBox. |
| src/settings-ui/Settings.UI.UnitTests/ModelsTests/MeasureToolSettingsTests.cs | Adds serialization/compatibility coverage for unit index persistence (including DIP). |
| src/modules/MeasureTool/Tests/ScreenRuler.UITests/TestHelper.cs | Adds UI helper to set measurement unit and tightens clipboard validation to include explicit unit suffixes. |
| src/modules/MeasureTool/Tests/ScreenRuler.UITests/TestBounds.cs | Adds a UITest validating DIP clipboard output for the bounds tool. |
| src/modules/MeasureTool/Tests/MeasureToolCore.UnitTests/MeasureToolCore.UnitTests.vcxproj | Introduces a native unit test project for MeasureToolCore conversion/formatting logic. |
| src/modules/MeasureTool/Tests/MeasureToolCore.UnitTests/MeasurementLogicTests.cpp | Adds focused native tests for DPI/DIP conversion, physical-unit fallback, and formatting behavior. |
| src/modules/MeasureTool/MeasureToolUI/Strings/en-us/Resources.resw | Adds the DIP abbreviation resource used by the overlay/clipboard formatter. |
| src/modules/MeasureTool/MeasureToolCore/ToolState.h | Adds a helper to retrieve per-window monitor DPI to support DIP conversion. |
| src/modules/MeasureTool/MeasureToolCore/ScreenCapturing.cpp | Plumbs monitor DPI into captured measurements so conversions can be DPI-aware. |
| src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj.filters | Adds MeasurementLogic.h to the project filters. |
| src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj | Adds MeasurementLogic.h to the project includes. |
| src/modules/MeasureTool/MeasureToolCore/MeasureToolOverlayUI.cpp | Switches overlay formatting to show only the selected unit (no forced pixel output). |
| src/modules/MeasureTool/MeasureToolCore/MeasurementLogic.h | Adds shared conversion + formatting helper functions (including DIP + corrected mm fallback). |
| src/modules/MeasureTool/MeasureToolCore/Measurement.h | Refactors Measurement to use MeasurementLogic and carry monitor DPI for conversions. |
| src/modules/MeasureTool/MeasureToolCore/Measurement.cpp | Updates conversion/printing to use the shared formatter and include explicit unit suffix. |
| src/modules/MeasureTool/MeasureToolCore/BoundsToolOverlayUI.cpp | Plumbs monitor DPI into bounds measurements and prints only the selected unit. |
| PowerToys.slnx | Adds the new MeasureToolCore native unit test project to the solution. |
| doc/devdocs/modules/screenruler.md | Updates module documentation to reflect the expanded unit support (including DIP). |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1a6361cf-d701-4a9b-b8e8-d5700c5ba140
Niels Laute (niels9001)
marked this pull request as ready for review
July 31, 2026 10:39
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/modules/MeasureTool/MeasureToolCore/BoundsToolOverlayUI.cpp:88
GetKeyState(VK_SHIFT)checks the high-order bit (0x8000). Using0x80000works only due to sign-extension quirks and is inconsistent with the same check in MeasureToolOverlayUI.cpp, making the intent unclear and potentially fragile if the return value is cast/handled differently.
if (const bool shiftPress = GetKeyState(VK_SHIFT) & 0x80000; shiftPress && perScreen.currentBounds)
src/modules/MeasureTool/Tests/ScreenRuler.UITests/TestHelper.cs:456
- The clipboard can contain multiple measurements separated by newlines (BoundsToolOverlayUI appends
perScreen.measurementsplus the current selection, andSetClipboardToMeasurementsprints each measurement on its own line). Anchoring the regex to the full clipboard string (^...$) makes this assertion brittle if any prior measurements are present. Consider validating each non-empty line instead, and ensure none of the lines include the pixel suffix.
Assert.IsTrue(
Regex.IsMatch(
clipboardText,
$@"^\d+(?:\.\d+)?\s*[xÃ]\s*\d+(?:\.\d+)?\s+{Regex.Escape(expectedUnit)}$"),
$"Clipboard should contain one {expectedUnit} measurement, but contained: '{clipboardText}'");
Assert.IsFalse(clipboardText.Contains(" px", StringComparison.Ordinal), "Clipboard should not include an extra pixel measurement");
Boliang Zhang (LegendaryBlair)
approved these changes
Aug 11, 2026
2 of 11 tasks
Yu Leng (moooyo)
added a commit
that referenced
this pull request
Sep 22, 2026
## Summary of the Pull Request This fixes an issue which prevented the "Device-independent pixels" option from being selected as Screen Ruler's secondary measurement option. <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #50684 <!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) --> - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments Screen Ruler used to store the secondary measurement unit as the enum value from `MeasureToolCore.MeasurementLogic.Unit`: ```cpp enum Unit { Pixel = 1, Inch = 2, Centimetre = 4, Millimetre = 8, Dip = 16, }; ``` This was changed to store the combo box index, but at the time the combo box only had 4 items with indexes 0 to 3. This crashed the application if a user had Centimetre or Millimetre saved (DIP was not available at the time). In PR #49898 a migration method was added to translate legacy settings values: ``` internal static int NormalizeUnitsOfMeasureIndex(int value) { return value switch { // Before the units selector was added, this setting stored Measurement::Unit // enum values. Preserve the user's centimetre/millimetre choice during migration. 4 => 2, 8 => 3, >= DefaultUnitsOfMeasureIndex and <= MaximumUnitsOfMeasureIndex => value, _ => DefaultUnitsOfMeasureIndex, }; } ``` (This worked for Centimetre, Millimetre and Inch, but actually broke for Pixel, setting it to Inch accidentally.) Later, PR #49588 added DIP as the fifth combo box item at index 4 and updated the basic mapping, but did not update the `NormalizeUnitsOfMeasureIndex` routine or increase the `MaximumUnitsOfMeasureIndex` const from 3 to 4. The effect of this was that the DIP value was saved as the combo box index of 4, but the normalisation method translated it straight back to 2 - Inch. ### Solution As a value of 4 is ambiguous we must remove the normalise step for it; otherwise DIPs cannot be selected. Legacy users who upgrade from a very old version of PowerToys and who have Centimetres saved will now get DIPs, but this should affect a very small number of individuals. Note: this will hit us again if the number of combo box entries ever gets to 9, as the 8th entry will get mapped to millimetre. We should remove the `8 => 3` line at that point. ## Validation Steps Performed Manual tests: - Confirmed that all values can be selected and that the ruler correctly updates to show the correct secondary units. - Updated the settings file manually to include an invalid value for the unit and confirmed that it was updated to 0 (pixels only). Unit tests: - Updated existing unit test to confirm that the remaining millimetre normalisation from 8 to 3 still works. - Wrote a new unit test to ensure that the `MaximumUnitsOfMeasureIndex` can no longer deviate from the number of items in the combo box. All unit tests pass: <img width="263" height="182" alt="image" src="https://github.com/user-attachments/assets/825ac3df-40c0-46d2-840e-4dca49ec2ae2" /> <img width="288" height="72" alt="image" src="https://github.com/user-attachments/assets/c8b028af-188d-47d5-8307-da89cc104f5f" /> --------- Co-authored-by: Yu Leng <42196638+moooyo@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This was referenced Sep 30, 2026
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.
Summary of the Pull Request
Adds display-independent pixels (DIP) to Screen Ruler's existing Extra units of measurement setting. Physical pixels remain the primary overlay value; selecting DIP adds a second, parenthesized measurement that adjusts for Windows display scaling.
DIP values use the DPI of the monitor containing the measurement. This also corrects the millimeter fallback conversion when physical monitor dimensions are unavailable.
Before vs after:
PR Checklist
Detailed Description of the Pull Request / Additional comments
4.Validation Steps Performed
MeasureToolCorefor ARM64 Debug.MeasureToolUIfor ARM64 Debug.MeasureToolCore.UnitTests: 5 passed.ScreenRuler.UITests; local execution was not run because WinAppDriver is not installed.