Skip to content

[Screen Ruler] Add DIP as an extra measurement unit - #49588

Merged
Niels Laute (niels9001) merged 2 commits into
mainfrom
screen-ruler-measurement-units
Aug 31, 2026
Merged

Niels Laute (niels9001) merged 2 commits into
mainfrom
screen-ruler-measurement-units

Conversation

@niels9001

@niels9001 Niels Laute (niels9001) commented Jul 30, 2026 •

Copy link
Copy Markdown
Collaborator

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.

image

Before vs after:

image

PR Checklist

Detailed Description of the Pull Request / Additional comments

  • Preserves the existing persisted values for pixels, inches, centimeters, and millimeters; DIP is appended as value 4.
  • Preserves the existing overlay behavior: pixels are always displayed, with one optional extra unit on a second line.
  • Preserves the existing clipboard behavior: pixel-only output has no suffix, while a selected extra unit is copied by itself.
  • Converts physical pixels to DIP using each overlay window's monitor DPI.
  • Corrects the 96-DPI millimeter fallback so 96 px equals 25.4 mm.
  • Adds focused native conversion tests, settings compatibility tests, and DIP clipboard UI coverage.
  • Keeps guides, calibration, browser-relative units, and other measurement modes out of scope.

Validation Steps Performed

  • Built MeasureToolCore for ARM64 Debug.
  • Built MeasureToolUI for ARM64 Debug.
  • Built PowerToys Settings for ARM64 Debug.
  • Built and ran MeasureToolCore.UnitTests: 5 passed.
  • Built and ran the Screen Ruler settings tests: 6 passed.
  • Built ScreenRuler.UITests; local execution was not run because WinAppDriver is not installed.
  • Ran XAML Styler on the changed Settings page.

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 px suffix).
  • 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
@niels9001 Niels Laute (niels9001) changed the title [Screen Ruler] Add selectable measurement units and DIP support [Screen Ruler] Add DIP as an extra measurement unit Jul 30, 2026
@niels9001
Niels Laute (niels9001) marked this pull request as ready for review July 31, 2026 10:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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). Using 0x80000 works 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.measurements plus the current selection, and SetClipboardToMeasurements prints 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");

@niels9001
Niels Laute (niels9001) merged commit 05ba63b into main Aug 31, 2026
10 checks passed
@niels9001
Niels Laute (niels9001) deleted the screen-ruler-measurement-units branch August 31, 2026 11:32
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Product-Screen Ruler Refers to the Screen Ruler PowerToy

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Screen Ruler] Millimetre conversion is 100x wrong in Measurement.cpp Add "Scale" setting to Screen Ruler to match Window's scaling features

3 participants