mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-08-29 10:09:43 +02:00
Ready for Review - [Mouse Jump] - port upstream WinUI3 code to Mouse Jump (microsoft#48290) (#48393)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Changes for #48290 to convert Mouse Jump to a WinUI app and remove all
remaining dependencies on WinForms, based on work already done in the
original "FancyMouse" project
(https://github.com/mikeclayton/FancyMouse).
## Notes for reviewers
* the new WinUI build of the app is output into the "/WinUI3Apps"
subfolder
* there's 2 new assemblies that need to be added to the installation as
well - MouseJump.HotKeys.dll and MouseJump.Models.dll. I'm not sure how
to add those to the installer for signing / shipping...
---
### Summary of changes
* New thumbnail layout and rendering code
* WinUI rewrite (winforms version still committed)
* MouseJump.Kicker (dev launch tool)
* CsWin32 for interop
* New assemblies - code reorganised
---
### 1. New layout code
Incorporates latest FancyMouse core layout and rendering logic into
Mouse Jump:
* includes **support** for multiple devices in layout algorithms
* preview still only shows local machine though
* prerequisite for long-term goal #34126
<img width="650" height="709" alt="image"
src="https://github.com/user-attachments/assets/9d1d996d-ed05-4471-b8a5-bd93442f70dc"
/>
### 2. WinUI rewrite
Port latest stable FancyMouse WinUI implementation into Mouse Jump.
Existing WinForms UI left in-situ side-by-side for now - easy to delete
if not needed.
### 3. MouseJump.Kicker
A small dev utility to start Mouse Jump without needing to build the
runner project:
<img width="283" height="274" alt="image"
src="https://github.com/user-attachments/assets/c81bb3f5-5008-48ce-8bc0-eef18413dee6"
/>
### 4. Cswin32 for interop
All win32 interop is now accessed via CsWin32 bindings.
The original win32 bindings were heavily influenced by CsWin32 generated
code (e.g. ```BOOL```, ```HWND```, etc structs), so there's not actually
much change other than deleting a lot of boilerplate code.
### 5. New assemblies
Some code has been reorganised into new assemblies to make it easier to
keep Mouse Jump in sync with upstream FancyMouse
* MouseJump.HotKeys
* MouseJump.Models
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes #48290
- [x] **Communication:** I've discussed this with core contributors
already. If work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end user facing strings can be localized
- all ui strings come from Resources.resw
- [x] **Dev docs:** Added/updated
- no changes required
- [x] **New binaries:** Added on the required places
- no changes required
- [x] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [x] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [x] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [x] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [x] **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
- no changes required
<!-- 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
<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
- **Workflow tests**
- [x] Automated tests passing locally
- [x] Minimal actions workflow (spelling check) passing for PR
- [ ] Full actions workflow (msbuild) passing for PR
- **UI tests**
- [x] Happy path
- [x] preview image appears when activated
- [x] clicking the preview image moves the mouse cursor to the correct
location
- [x] right-click dismisses the preview image without moving the mouse
- [x] pressing escape dismisses the preview image without moving the
mouse
- [x] left or right clicking another application / desktop dismisses the
preview image without moving the mouse
- [x] Works on multiple monitors with different dpi scaling settings
(e.g. 100% vs 150%)
*
https://github.com/microsoft/PowerToys/pull/23566#issuecomment-1411869418
*
https://github.com/microsoft/PowerToys/pull/23566#issuecomment-1412834413
- [x] Handling negative coordinates on non-primary monitors if higher or
"lefter" than primary monitor
*
https://github.com/microsoft/PowerToys/pull/23566#issuecomment-1404931694
- [x] Mouse crosshair moves when Mouse Jump moves the cursor (mouse
clicks *and* keyboard shortcuts)
* #24523
* #24527
- [x] Activating when the preview window is already visible moves the
form to the new mouse position
- [x] Number and key shortcuts (1-9, Home / End, Left / Right Arrow)
jump to the appropriate monitor
- [x] Number-pad shortcuts (1-9 jump to the appropriate monitor
- **Settings tests**
- [x] Changing thumbnail size settings updates the size of the thumbnail
- [x] Changing preview type between Compact, Bezelled and Custom shows
the correct preview type
- [x] Changing custom preview settings shows the correct settings
- [ ] Launching with settings version 1.0 upgrades settings to version
1.1, with "Bezelled" as the default style and the "Custom" settings
preconfigured to match "Bezelled"
- **Lifecycle tests**
- [x] Starting PowerToys Runner launches MouseJump exe when enabled, and
not when disabled
- [x] Enabling / disabling Mouse Jump in settings starts / stops
MouseJump exe
- [x] Exiting PowerToys Runner stops MouseJump exe
- [x] Killing runner exe via Task Manager stops MouseJump exe
- [x] Stopping Visual Studio local debug run stops MouseJump exe
- note - runner needs to be in *non*-admin mode otherwise Visual Studio
debugger disconnects at launch
- [x] Hotkey and size settings are automatically reloaded when config
file is modified from Settings UI
- [ ] ~~Hotkey and size settings are automatically reloaded when config
file is modified manually (e.g. in notepad) while runner and
MouseJumpUI.exe are running~~
- **[Internal Test
Suite](5bc7201ae2/doc/releases/tests-checklist-template.md (mouse-utils))**
- [x] Enable Mouse Jump. Then:
- [x] Press the activation shortcut and verify the screens preview
appears.
- [x] Change activation shortcut and verify that new shortcut triggers
Mouse Jump.
- [x] Click around the screen preview and ensure that mouse cursor
jumped to clicked location.
- [x] Reorder screens in Display settings and confirm that Mouse Jump
reflects the change and still works correctly.
- [x] Change scaling of screens and confirm that Mouse Jump still works
correctly.
- [ ] Unplug additional monitors and confirm that Mouse Jump still works
correctly.
- [x] Disable Mouse Jump and verify that the module is not activated
when you press the activation shortcut.
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Boliang Zhang (from Dev Box) <bozhang@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: moooyo <42196638+moooyo@users.noreply.github.com>
Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Copilot-Session: 8e04a72e-3b0f-4ac4-8156-d04ea9b8bb85
This commit is contained in:
3
.github/actions/spell-check/excludes.txt
vendored
3
.github/actions/spell-check/excludes.txt
vendored
@@ -114,7 +114,7 @@
|
|||||||
^src/modules/cmdpal/Tests/Microsoft\.CmdPal\.Common\.UnitTests/Text/.*\.cs$
|
^src/modules/cmdpal/Tests/Microsoft\.CmdPal\.Common\.UnitTests/Text/.*\.cs$
|
||||||
^src/modules/colorPicker/ColorPickerUI/Shaders/GridShader\.cso$
|
^src/modules/colorPicker/ColorPickerUI/Shaders/GridShader\.cso$
|
||||||
^src/modules/launcher/Plugins/Microsoft\.PowerToys\.Run\.Plugin\.TimeDate/Properties/
|
^src/modules/launcher/Plugins/Microsoft\.PowerToys\.Run\.Plugin\.TimeDate/Properties/
|
||||||
^src/modules/MouseUtils/MouseJumpUI/MainForm\.resx$
|
^src/modules/MouseUtils/MouseJumpUI/UI/MainForm\.resx$
|
||||||
^src/modules/MouseWithoutBorders/App/.*/NativeMethods\.cs$
|
^src/modules/MouseWithoutBorders/App/.*/NativeMethods\.cs$
|
||||||
^src/modules/MouseWithoutBorders/App/Form/.*\.Designer\.cs$
|
^src/modules/MouseWithoutBorders/App/Form/.*\.Designer\.cs$
|
||||||
^src/modules/MouseWithoutBorders/App/Form/.*\.resx$
|
^src/modules/MouseWithoutBorders/App/Form/.*\.resx$
|
||||||
@@ -139,6 +139,7 @@
|
|||||||
^src/modules/ZoomIt/ZoomIt/selfie_segmentation\.onnx$
|
^src/modules/ZoomIt/ZoomIt/selfie_segmentation\.onnx$
|
||||||
^src/modules/ZoomIt/ZoomIt/ZoomIt\.idc$
|
^src/modules/ZoomIt/ZoomIt/ZoomIt\.idc$
|
||||||
^src/Monaco/
|
^src/Monaco/
|
||||||
|
^tools/MouseJump.Kicker/KickerForm\.resx$
|
||||||
^tools/project_template/ModuleTemplate/resource\.h$
|
^tools/project_template/ModuleTemplate/resource\.h$
|
||||||
^tools/Verification scripts/Check preview handler registration\.ps1$
|
^tools/Verification scripts/Check preview handler registration\.ps1$
|
||||||
ignore$
|
ignore$
|
||||||
|
|||||||
45
.github/actions/spell-check/expect.txt
vendored
45
.github/actions/spell-check/expect.txt
vendored
@@ -47,7 +47,6 @@ ALLVIEW
|
|||||||
ALPHATYPE
|
ALPHATYPE
|
||||||
altkey
|
altkey
|
||||||
amr
|
amr
|
||||||
ANDSCANS
|
|
||||||
animatedvisuals
|
animatedvisuals
|
||||||
Animnate
|
Animnate
|
||||||
AOC
|
AOC
|
||||||
@@ -194,6 +193,7 @@ CAPTURECHANGED
|
|||||||
CARETBLINKING
|
CARETBLINKING
|
||||||
carlos
|
carlos
|
||||||
Carlseibert
|
Carlseibert
|
||||||
|
cascadia
|
||||||
caseinsensitive
|
caseinsensitive
|
||||||
caub
|
caub
|
||||||
CBN
|
CBN
|
||||||
@@ -313,7 +313,6 @@ CREATESCHEDULEDTASK
|
|||||||
CREATESTRUCT
|
CREATESTRUCT
|
||||||
CREATEWINDOWFAILED
|
CREATEWINDOWFAILED
|
||||||
creativecommons
|
creativecommons
|
||||||
CRECT
|
|
||||||
CRH
|
CRH
|
||||||
critsec
|
critsec
|
||||||
cropandlock
|
cropandlock
|
||||||
@@ -389,7 +388,6 @@ DELA
|
|||||||
DELD
|
DELD
|
||||||
deld
|
deld
|
||||||
DELETEDKEYIMAGE
|
DELETEDKEYIMAGE
|
||||||
DELETESCANS
|
|
||||||
DEMOTYPE
|
DEMOTYPE
|
||||||
DENORMAL
|
DENORMAL
|
||||||
depersist
|
depersist
|
||||||
@@ -457,10 +455,8 @@ dreamsofameaningfullife
|
|||||||
drivedetectionwarning
|
drivedetectionwarning
|
||||||
DROPFILES
|
DROPFILES
|
||||||
DSPDLOG
|
DSPDLOG
|
||||||
DSTINVERT
|
|
||||||
DString
|
DString
|
||||||
dto
|
dto
|
||||||
DUMMYUNIONNAME
|
|
||||||
dumpbin
|
dumpbin
|
||||||
dutil
|
dutil
|
||||||
DVASPECT
|
DVASPECT
|
||||||
@@ -505,6 +501,8 @@ encodedlaunch
|
|||||||
encryptor
|
encryptor
|
||||||
ENDSESSION
|
ENDSESSION
|
||||||
ENSUREVISIBLE
|
ENSUREVISIBLE
|
||||||
|
ENTERIDLE
|
||||||
|
ENTERMENULOOP
|
||||||
ENTERSIZEMOVE
|
ENTERSIZEMOVE
|
||||||
ENTRYW
|
ENTRYW
|
||||||
ENU
|
ENU
|
||||||
@@ -538,6 +536,8 @@ EXECUTEDEFAULT
|
|||||||
executionpolicy
|
executionpolicy
|
||||||
exename
|
exename
|
||||||
exf
|
exf
|
||||||
|
EXITCOMMAND
|
||||||
|
EXITMENULOOP
|
||||||
EXITSIZEMOVE
|
EXITSIZEMOVE
|
||||||
exlist
|
exlist
|
||||||
EXPCMDFLAGS
|
EXPCMDFLAGS
|
||||||
@@ -669,6 +669,7 @@ gwl
|
|||||||
GWLP
|
GWLP
|
||||||
GWLSTYLE
|
GWLSTYLE
|
||||||
hangeul
|
hangeul
|
||||||
|
Hanguel
|
||||||
Hantai
|
Hantai
|
||||||
Hanzi
|
Hanzi
|
||||||
Hardlines
|
Hardlines
|
||||||
@@ -812,6 +813,7 @@ imageresizerinput
|
|||||||
imageresizersettings
|
imageresizersettings
|
||||||
imagingdevices
|
imagingdevices
|
||||||
ime
|
ime
|
||||||
|
IMEAceept
|
||||||
imgflip
|
imgflip
|
||||||
inapp
|
inapp
|
||||||
inbox
|
inbox
|
||||||
@@ -826,6 +828,8 @@ INITCOMMONCONTROLSEX
|
|||||||
INITDIALOG
|
INITDIALOG
|
||||||
INITGUID
|
INITGUID
|
||||||
initialfile
|
initialfile
|
||||||
|
INITMENU
|
||||||
|
INITMENUPOPUP
|
||||||
INITTOLOGFONTSTRUCT
|
INITTOLOGFONTSTRUCT
|
||||||
inkscape
|
inkscape
|
||||||
INLINEPREFIX
|
INLINEPREFIX
|
||||||
@@ -930,6 +934,7 @@ Lclean
|
|||||||
Ldone
|
Ldone
|
||||||
Ldr
|
Ldr
|
||||||
LEFTALIGN
|
LEFTALIGN
|
||||||
|
LEFTBUTTON
|
||||||
leftclick
|
leftclick
|
||||||
LEFTSCROLLBAR
|
LEFTSCROLLBAR
|
||||||
LEFTTEXT
|
LEFTTEXT
|
||||||
@@ -981,11 +986,9 @@ LPCLSID
|
|||||||
lpcmi
|
lpcmi
|
||||||
LPCMINVOKECOMMANDINFO
|
LPCMINVOKECOMMANDINFO
|
||||||
LPCREATESTRUCT
|
LPCREATESTRUCT
|
||||||
LPCRECT
|
|
||||||
LPCTSTR
|
LPCTSTR
|
||||||
lpdw
|
lpdw
|
||||||
lpfn
|
lpfn
|
||||||
LPINPUT
|
|
||||||
LPITEMIDLIST
|
LPITEMIDLIST
|
||||||
lpmi
|
lpmi
|
||||||
LPMINMAXINFO
|
LPMINMAXINFO
|
||||||
@@ -994,6 +997,7 @@ LPOSVERSIONINFOEXW
|
|||||||
LPQUERY
|
LPQUERY
|
||||||
lprc
|
lprc
|
||||||
LPSAFEARRAY
|
LPSAFEARRAY
|
||||||
|
LPSECURITY
|
||||||
lpstr
|
lpstr
|
||||||
lpsz
|
lpsz
|
||||||
LPTHREAD
|
LPTHREAD
|
||||||
@@ -1054,8 +1058,7 @@ meme
|
|||||||
memicmp
|
memicmp
|
||||||
MENUITEMINFO
|
MENUITEMINFO
|
||||||
MENUITEMINFOW
|
MENUITEMINFOW
|
||||||
MERGECOPY
|
MENUSELECT
|
||||||
MERGEPAINT
|
|
||||||
Metacharacter
|
Metacharacter
|
||||||
metadatamatters
|
metadatamatters
|
||||||
Metadatas
|
Metadatas
|
||||||
@@ -1202,7 +1205,6 @@ NOASYNC
|
|||||||
NOCHANGEDIR
|
NOCHANGEDIR
|
||||||
NOCLIP
|
NOCLIP
|
||||||
NOCLOSEPROCESS
|
NOCLOSEPROCESS
|
||||||
NOCOALESCE
|
|
||||||
NOCOMM
|
NOCOMM
|
||||||
NOCONFIRMMKDIR
|
NOCONFIRMMKDIR
|
||||||
NOCOPYBITS
|
NOCOPYBITS
|
||||||
@@ -1219,10 +1221,10 @@ NOLINKINFO
|
|||||||
nologo
|
nologo
|
||||||
NOMCX
|
NOMCX
|
||||||
NOMINMAX
|
NOMINMAX
|
||||||
NOMIRRORBITMAP
|
|
||||||
nomove
|
nomove
|
||||||
NONANTIALIASED
|
NONANTIALIASED
|
||||||
nonclient
|
nonclient
|
||||||
|
Nonconvert
|
||||||
NONCLIENTMETRICSW
|
NONCLIENTMETRICSW
|
||||||
NONELEVATED
|
NONELEVATED
|
||||||
nonspace
|
nonspace
|
||||||
@@ -1251,8 +1253,6 @@ NOTIFYICONDATAW
|
|||||||
NOTIMPL
|
NOTIMPL
|
||||||
NOTOPMOST
|
NOTOPMOST
|
||||||
NOTRACK
|
NOTRACK
|
||||||
NOTSRCCOPY
|
|
||||||
NOTSRCERASE
|
|
||||||
Notupdated
|
Notupdated
|
||||||
notwindows
|
notwindows
|
||||||
NOTXORPEN
|
NOTXORPEN
|
||||||
@@ -1277,7 +1277,10 @@ nwc
|
|||||||
ocr
|
ocr
|
||||||
Ocrsettings
|
Ocrsettings
|
||||||
odbccp
|
odbccp
|
||||||
|
Oemcomma
|
||||||
OEMCONVERT
|
OEMCONVERT
|
||||||
|
Oemplus
|
||||||
|
Oemtilde
|
||||||
officehubintl
|
officehubintl
|
||||||
OFN
|
OFN
|
||||||
ofs
|
ofs
|
||||||
@@ -1302,7 +1305,6 @@ openxmlformats
|
|||||||
OPTIMIZEFORINVOKE
|
OPTIMIZEFORINVOKE
|
||||||
Optronics
|
Optronics
|
||||||
ORPHANEDDIALOGTITLE
|
ORPHANEDDIALOGTITLE
|
||||||
ORSCANS
|
|
||||||
oss
|
oss
|
||||||
OSVERSIONINFO
|
OSVERSIONINFO
|
||||||
OSVERSIONINFOEX
|
OSVERSIONINFOEX
|
||||||
@@ -1334,10 +1336,7 @@ PARENTRELATIVEPARSING
|
|||||||
parray
|
parray
|
||||||
parseable
|
parseable
|
||||||
PARTIALCONFIRMATIONDIALOGTITLE
|
PARTIALCONFIRMATIONDIALOGTITLE
|
||||||
PATCOPY
|
|
||||||
PATHMUSTEXIST
|
PATHMUSTEXIST
|
||||||
PATINVERT
|
|
||||||
PATPAINT
|
|
||||||
pbc
|
pbc
|
||||||
pbi
|
pbi
|
||||||
PBP
|
PBP
|
||||||
@@ -1567,6 +1566,7 @@ REMAPSUCCESSFUL
|
|||||||
REMAPUNSUCCESSFUL
|
REMAPUNSUCCESSFUL
|
||||||
Remotable
|
Remotable
|
||||||
remotedesktop
|
remotedesktop
|
||||||
|
remotehost
|
||||||
remoteip
|
remoteip
|
||||||
Removelnk
|
Removelnk
|
||||||
renamable
|
renamable
|
||||||
@@ -1603,7 +1603,6 @@ RIGHTBUTTON
|
|||||||
RIGHTSCROLLBAR
|
RIGHTSCROLLBAR
|
||||||
riid
|
riid
|
||||||
rollups
|
rollups
|
||||||
rop
|
|
||||||
ROUNDSMALL
|
ROUNDSMALL
|
||||||
rpcrt
|
rpcrt
|
||||||
RRF
|
RRF
|
||||||
@@ -1717,6 +1716,7 @@ shortdate
|
|||||||
SHORTPATH
|
SHORTPATH
|
||||||
shortsplit
|
shortsplit
|
||||||
showcolorname
|
showcolorname
|
||||||
|
SHOWCONTEXTMENU
|
||||||
SHOWDEFAULT
|
SHOWDEFAULT
|
||||||
SHOWELEVATIONPROMPT
|
SHOWELEVATIONPROMPT
|
||||||
SHOWMAGNIFIEDCURSOR
|
SHOWMAGNIFIEDCURSOR
|
||||||
@@ -1779,12 +1779,8 @@ spsi
|
|||||||
spsia
|
spsia
|
||||||
spsrm
|
spsrm
|
||||||
spsv
|
spsv
|
||||||
SRCAND
|
|
||||||
SRCCOPY
|
SRCCOPY
|
||||||
SRCERASE
|
|
||||||
Srch
|
Srch
|
||||||
SRCINVERT
|
|
||||||
SRCPAINT
|
|
||||||
SResize
|
SResize
|
||||||
srme
|
srme
|
||||||
srre
|
srre
|
||||||
@@ -1948,6 +1944,7 @@ tracerpt
|
|||||||
trackbar
|
trackbar
|
||||||
trafficmanager
|
trafficmanager
|
||||||
transicc
|
transicc
|
||||||
|
TRAYICON
|
||||||
TRANSIENTWINDOW
|
TRANSIENTWINDOW
|
||||||
transitioning
|
transitioning
|
||||||
TRAYMOUSEMESSAGE
|
TRAYMOUSEMESSAGE
|
||||||
@@ -1966,6 +1963,8 @@ TYPEMOUSE
|
|||||||
TYPESHORTCUT
|
TYPESHORTCUT
|
||||||
UAC
|
UAC
|
||||||
UACUI
|
UACUI
|
||||||
|
UAHINITMENU
|
||||||
|
UAHMEASUREMENUITEM
|
||||||
UAL
|
UAL
|
||||||
uap
|
uap
|
||||||
UBR
|
UBR
|
||||||
@@ -1990,6 +1989,7 @@ unescaped
|
|||||||
unforgeable
|
unforgeable
|
||||||
ungroup
|
ungroup
|
||||||
UNICODETEXT
|
UNICODETEXT
|
||||||
|
UNINITMENUPOPUP
|
||||||
unins
|
unins
|
||||||
uninsdeletekey
|
uninsdeletekey
|
||||||
Uninstaller
|
Uninstaller
|
||||||
@@ -2060,7 +2060,6 @@ vgetq
|
|||||||
viewmodels
|
viewmodels
|
||||||
virama
|
virama
|
||||||
VIRTKEY
|
VIRTKEY
|
||||||
VIRTUALDESK
|
|
||||||
VISEGRADRELAY
|
VISEGRADRELAY
|
||||||
visiblecolorformats
|
visiblecolorformats
|
||||||
visio
|
visio
|
||||||
|
|||||||
@@ -545,11 +545,19 @@
|
|||||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||||
<Platform Solution="*|x64" Project="x64" />
|
<Platform Solution="*|x64" Project="x64" />
|
||||||
</Project>
|
</Project>
|
||||||
<Project Path="src/modules/MouseUtils/MouseJump/MouseJump.vcxproj" Id="8a08d663-4995-40e3-b42c-3f910625f284" />
|
<Project Path="src/modules/MouseUtils/MouseJump.HotKeys/MouseJump.HotKeys.csproj">
|
||||||
<Project Path="src/modules/MouseUtils/MouseJumpUI/MouseJumpUI.csproj">
|
|
||||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||||
<Platform Solution="*|x64" Project="x64" />
|
<Platform Solution="*|x64" Project="x64" />
|
||||||
</Project>
|
</Project>
|
||||||
|
<Project Path="src/modules/MouseUtils/MouseJump.Models/MouseJump.Models.csproj">
|
||||||
|
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||||
|
<Platform Solution="*|x64" Project="x64" />
|
||||||
|
</Project>
|
||||||
|
<Project Path="src/modules/MouseUtils/MouseJump.WinUI3/MouseJump.WinUI3.csproj">
|
||||||
|
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||||
|
<Platform Solution="*|x64" Project="x64" />
|
||||||
|
</Project>
|
||||||
|
<Project Path="src/modules/MouseUtils/MouseJump/MouseJump.vcxproj" Id="8a08d663-4995-40e3-b42c-3f910625f284" />
|
||||||
<Project Path="src/modules/MouseUtils/MousePointerCrosshairs/MousePointerCrosshairs.vcxproj" Id="eae14c0e-7a6b-45da-9080-a7d8c077ba6e" />
|
<Project Path="src/modules/MouseUtils/MousePointerCrosshairs/MousePointerCrosshairs.vcxproj" Id="eae14c0e-7a6b-45da-9080-a7d8c077ba6e" />
|
||||||
</Folder>
|
</Folder>
|
||||||
<Folder Name="/modules/MouseUtils/Tests/">
|
<Folder Name="/modules/MouseUtils/Tests/">
|
||||||
@@ -557,6 +565,14 @@
|
|||||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||||
<Platform Solution="*|x64" Project="x64" />
|
<Platform Solution="*|x64" Project="x64" />
|
||||||
</Project>
|
</Project>
|
||||||
|
<Project Path="src/modules/MouseUtils/MouseJump.HotKeys.UnitTests/MouseJump.HotKeys.UnitTests.csproj">
|
||||||
|
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||||
|
<Platform Solution="*|x64" Project="x64" />
|
||||||
|
</Project>
|
||||||
|
<Project Path="src/modules/MouseUtils/MouseJump.Models.UnitTests/MouseJump.Models.UnitTests.csproj">
|
||||||
|
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||||
|
<Platform Solution="*|x64" Project="x64" />
|
||||||
|
</Project>
|
||||||
<Project Path="src/modules/MouseUtils/MouseUtils.UITests/MouseUtils.UITests.csproj">
|
<Project Path="src/modules/MouseUtils/MouseUtils.UITests/MouseUtils.UITests.csproj">
|
||||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||||
<Platform Solution="*|x64" Project="x64" />
|
<Platform Solution="*|x64" Project="x64" />
|
||||||
|
|||||||
@@ -2,29 +2,32 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System.Globalization;
|
using System.Drawing;
|
||||||
|
using System.Drawing.Imaging;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
using MouseJump.Common.Helpers;
|
using MouseJump.Common.Helpers;
|
||||||
using MouseJump.Common.Imaging;
|
using MouseJump.Common.Imaging;
|
||||||
using MouseJump.Common.Models.Drawing;
|
using MouseJump.Models.Display;
|
||||||
using MouseJump.Common.Models.Styles;
|
using MouseJump.Models.Drawing;
|
||||||
|
using MouseJump.Models.Styles;
|
||||||
|
|
||||||
namespace MouseJump.Common.UnitTests.Helpers;
|
namespace MouseJump.Common.UnitTests.Helpers;
|
||||||
|
|
||||||
[TestClass]
|
|
||||||
public static class DrawingHelperTests
|
public static class DrawingHelperTests
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class RenderPreviewTests
|
public sealed class GetPreviewLayoutTests
|
||||||
{
|
{
|
||||||
public sealed class TestCase
|
public sealed class TestCase
|
||||||
{
|
{
|
||||||
public TestCase(PreviewStyle previewStyle, List<RectangleInfo> screens, PointInfo activatedLocation, string desktopImageFilename, string expectedImageFilename)
|
public TestCase(PreviewStyle previewStyle, DisplayInfo displayInfo, ScreenInfo activatedScreen, PointInfo activatedLocation, string desktopImageFilename, string expectedImageFilename)
|
||||||
{
|
{
|
||||||
this.PreviewStyle = previewStyle;
|
this.PreviewStyle = previewStyle;
|
||||||
this.Screens = screens;
|
this.DisplayInfo = displayInfo;
|
||||||
|
this.ActivatedScreen = activatedScreen;
|
||||||
this.ActivatedLocation = activatedLocation;
|
this.ActivatedLocation = activatedLocation;
|
||||||
this.DesktopImageFilename = desktopImageFilename;
|
this.DesktopImageFilename = desktopImageFilename;
|
||||||
this.ExpectedImageFilename = expectedImageFilename;
|
this.ExpectedImageFilename = expectedImageFilename;
|
||||||
@@ -32,7 +35,9 @@ public static class DrawingHelperTests
|
|||||||
|
|
||||||
public PreviewStyle PreviewStyle { get; }
|
public PreviewStyle PreviewStyle { get; }
|
||||||
|
|
||||||
public List<RectangleInfo> Screens { get; }
|
public DisplayInfo DisplayInfo { get; }
|
||||||
|
|
||||||
|
public ScreenInfo ActivatedScreen { get; }
|
||||||
|
|
||||||
public PointInfo ActivatedLocation { get; }
|
public PointInfo ActivatedLocation { get; }
|
||||||
|
|
||||||
@@ -43,32 +48,94 @@ public static class DrawingHelperTests
|
|||||||
|
|
||||||
public static IEnumerable<object[]> GetTestCases()
|
public static IEnumerable<object[]> GetTestCases()
|
||||||
{
|
{
|
||||||
/* 4-grid */
|
// colors like "SystemColors.Highlight" can change between versions of windows,
|
||||||
|
// so we'll use hard-coded colors instead to reduce false test failures
|
||||||
|
var systemHighlight = Color.FromArgb(0, 120, 215);
|
||||||
|
var previewStyle = StyleHelper.BezelledPreviewStyle;
|
||||||
|
previewStyle = new(
|
||||||
|
canvasSize: previewStyle.CanvasSize,
|
||||||
|
canvasStyle: new(
|
||||||
|
marginStyle: previewStyle.CanvasStyle.MarginStyle,
|
||||||
|
borderStyle: previewStyle.CanvasStyle.BorderStyle.WithColor(systemHighlight),
|
||||||
|
paddingStyle: previewStyle.CanvasStyle.PaddingStyle,
|
||||||
|
backgroundStyle: previewStyle.CanvasStyle.BackgroundStyle
|
||||||
|
),
|
||||||
|
screenStyle: previewStyle.ScreenStyle,
|
||||||
|
extraColors: previewStyle.ExtraColors
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4-grid
|
||||||
|
var displayInfo = new DisplayInfo(
|
||||||
|
devices: new DeviceInfo[]
|
||||||
|
{
|
||||||
|
new(
|
||||||
|
hostname: "localhost",
|
||||||
|
localhost: true,
|
||||||
|
screens: new List<ScreenInfo>
|
||||||
|
{
|
||||||
|
new(
|
||||||
|
handle: 0,
|
||||||
|
primary: true,
|
||||||
|
displayArea: new(0, 0, 500, 500),
|
||||||
|
workingArea: new(0, 0, 500, 500)),
|
||||||
|
new(
|
||||||
|
handle: 0,
|
||||||
|
primary: false,
|
||||||
|
displayArea: new(500, 0, 500, 500),
|
||||||
|
workingArea: new(500, 0, 500, 500)),
|
||||||
|
new(
|
||||||
|
handle: 0,
|
||||||
|
primary: false,
|
||||||
|
displayArea: new(500, 500, 500, 500),
|
||||||
|
workingArea: new(500, 500, 500, 500)),
|
||||||
|
new(
|
||||||
|
handle: 0,
|
||||||
|
primary: false,
|
||||||
|
displayArea: new(0, 500, 500, 500),
|
||||||
|
workingArea: new(0, 500, 500, 500)),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
});
|
||||||
yield return new object[]
|
yield return new object[]
|
||||||
{
|
{
|
||||||
new TestCase(
|
new TestCase(
|
||||||
previewStyle: StyleHelper.BezelledPreviewStyle,
|
previewStyle: previewStyle,
|
||||||
screens: new List<RectangleInfo>()
|
displayInfo: displayInfo,
|
||||||
{
|
activatedScreen: displayInfo.Devices[0].Screens[0],
|
||||||
new(0, 0, 500, 500),
|
|
||||||
new(500, 0, 500, 500),
|
|
||||||
new(500, 500, 500, 500),
|
|
||||||
new(0, 500, 500, 500),
|
|
||||||
},
|
|
||||||
activatedLocation: new(x: 50, y: 50),
|
activatedLocation: new(x: 50, y: 50),
|
||||||
desktopImageFilename: "_test-4grid-desktop.png",
|
desktopImageFilename: "_test-4grid-desktop.png",
|
||||||
expectedImageFilename: "_test-4grid-expected.png"),
|
expectedImageFilename: "_test-4grid-expected.png"),
|
||||||
};
|
};
|
||||||
/* win 11 */
|
|
||||||
|
// win 11
|
||||||
|
displayInfo = new(
|
||||||
|
devices: new DeviceInfo[]
|
||||||
|
{
|
||||||
|
new(
|
||||||
|
hostname: "localhost",
|
||||||
|
localhost: true,
|
||||||
|
screens: new List<ScreenInfo>
|
||||||
|
{
|
||||||
|
new(
|
||||||
|
handle: 0,
|
||||||
|
primary: true,
|
||||||
|
displayArea: new(5120, 349, 1920, 1080),
|
||||||
|
workingArea: new(5120, 349, 1920, 1080)),
|
||||||
|
new(
|
||||||
|
handle: 0,
|
||||||
|
primary: false,
|
||||||
|
displayArea: new(0, 0, 5120, 1440),
|
||||||
|
workingArea: new(0, 0, 5120, 1440)),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
yield return new object[]
|
yield return new object[]
|
||||||
{
|
{
|
||||||
new TestCase(
|
new TestCase(
|
||||||
previewStyle: StyleHelper.BezelledPreviewStyle,
|
previewStyle: previewStyle,
|
||||||
screens: new List<RectangleInfo>()
|
displayInfo: displayInfo,
|
||||||
{
|
activatedScreen: displayInfo.Devices[0].Screens[0],
|
||||||
new(5120, 349, 1920, 1080),
|
|
||||||
new(0, 0, 5120, 1440),
|
|
||||||
},
|
|
||||||
activatedLocation: new(x: 50, y: 50),
|
activatedLocation: new(x: 50, y: 50),
|
||||||
desktopImageFilename: "_test-win11-desktop.png",
|
desktopImageFilename: "_test-win11-desktop.png",
|
||||||
expectedImageFilename: "_test-win11-expected.png"),
|
expectedImageFilename: "_test-win11-expected.png"),
|
||||||
@@ -76,22 +143,36 @@ public static class DrawingHelperTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DynamicData(nameof(GetTestCases), DynamicDataSourceType.Method)]
|
[DynamicData(nameof(GetTestCases))]
|
||||||
public void RunTestCases(TestCase data)
|
public async Task RunTestCases(TestCase data)
|
||||||
{
|
{
|
||||||
// load the fake desktop image
|
// load the fake desktop image
|
||||||
using var desktopImage = RenderPreviewTests.LoadImageResource(data.DesktopImageFilename);
|
using var desktopImage = GetPreviewLayoutTests.LoadImageResource(data.DesktopImageFilename);
|
||||||
|
|
||||||
|
var formLayout = LayoutHelper.GetFormLayout(
|
||||||
|
previewStyle: data.PreviewStyle,
|
||||||
|
displayInfo: data.DisplayInfo,
|
||||||
|
activatedScreen: data.ActivatedScreen,
|
||||||
|
activatedLocation: data.ActivatedLocation);
|
||||||
|
|
||||||
|
var imageCopyServices = formLayout.CanvasLayout.DeviceLayouts
|
||||||
|
.Select(
|
||||||
|
deviceLayout => (IImageRegionCopyService)new StaticImageRegionCopyService(desktopImage))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
// draw the preview image
|
// draw the preview image
|
||||||
var previewLayout = LayoutHelper.GetPreviewLayout(
|
using var actual = await DrawingHelper.RenderPreviewAsync(formLayout.CanvasLayout, data.ActivatedScreen, imageCopyServices);
|
||||||
previewStyle: data.PreviewStyle,
|
|
||||||
screens: data.Screens,
|
// save the actual image so we can pick it up as a build artifact
|
||||||
activatedLocation: data.ActivatedLocation);
|
var actualFilename = Path.GetFileNameWithoutExtension(data.ExpectedImageFilename) + "_actual" + Path.GetExtension(data.ExpectedImageFilename);
|
||||||
var imageCopyService = new StaticImageRegionCopyService(desktopImage);
|
actual.Save(actualFilename, ImageFormat.Png);
|
||||||
using var actual = DrawingHelper.RenderPreview(previewLayout, imageCopyService);
|
|
||||||
|
|
||||||
// load the expected image
|
// load the expected image
|
||||||
var expected = RenderPreviewTests.LoadImageResource(data.ExpectedImageFilename);
|
var expected = GetPreviewLayoutTests.LoadImageResource(data.ExpectedImageFilename);
|
||||||
|
|
||||||
|
// save the actual image so we can pick it up as a build artifact
|
||||||
|
var expectedFilename = Path.GetFileNameWithoutExtension(data.ExpectedImageFilename) + "_expected" + Path.GetExtension(data.ExpectedImageFilename);
|
||||||
|
expected.Save(expectedFilename, ImageFormat.Png);
|
||||||
|
|
||||||
// compare the images
|
// compare the images
|
||||||
AssertImagesEqual(expected, actual);
|
AssertImagesEqual(expected, actual);
|
||||||
@@ -99,24 +180,26 @@ public static class DrawingHelperTests
|
|||||||
|
|
||||||
private static Bitmap LoadImageResource(string filename)
|
private static Bitmap LoadImageResource(string filename)
|
||||||
{
|
{
|
||||||
var assembly = Assembly.GetExecutingAssembly();
|
var referenceType = typeof(DrawingHelperTests);
|
||||||
var assemblyName = new AssemblyName(assembly.FullName ?? throw new InvalidOperationException());
|
var resourceAssembly = referenceType.Assembly;
|
||||||
var resourceName = $"{typeof(DrawingHelperTests).Namespace}.{filename.Replace("/", ".")}";
|
|
||||||
var resourceNames = assembly.GetManifestResourceNames();
|
var resourcePrefix = referenceType.Namespace;
|
||||||
|
var resourceName = $"{resourcePrefix}.{filename.Replace("/", ".")}";
|
||||||
|
var resourceNames = resourceAssembly.GetManifestResourceNames();
|
||||||
if (!resourceNames.Contains(resourceName))
|
if (!resourceNames.Contains(resourceName))
|
||||||
{
|
{
|
||||||
var message = new StringBuilder();
|
var message = string.Join(
|
||||||
message.AppendLine(CultureInfo.InvariantCulture, $"Embedded resource '{resourceName}' does not exist.");
|
Environment.NewLine,
|
||||||
message.AppendLine($"Known resources:");
|
new List<string>([
|
||||||
foreach (var name in resourceNames)
|
$"Embedded resource '{resourceName}' does not exist.",
|
||||||
{
|
string.Empty,
|
||||||
message.AppendLine(name);
|
"Valid resource names are:",
|
||||||
}
|
string.Empty])
|
||||||
|
.Concat(resourceNames));
|
||||||
throw new InvalidOperationException(message.ToString());
|
throw new InvalidOperationException(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
var stream = assembly.GetManifestResourceStream(resourceName)
|
var stream = resourceAssembly.GetManifestResourceStream(resourceName)
|
||||||
?? throw new InvalidOperationException();
|
?? throw new InvalidOperationException();
|
||||||
var image = (Bitmap)Image.FromStream(stream);
|
var image = (Bitmap)Image.FromStream(stream);
|
||||||
return image;
|
return image;
|
||||||
@@ -143,12 +226,11 @@ public static class DrawingHelperTests
|
|||||||
var actualPixel = actual.GetPixel(x, y);
|
var actualPixel = actual.GetPixel(x, y);
|
||||||
|
|
||||||
// allow a small tolerance for rounding differences in gdi
|
// allow a small tolerance for rounding differences in gdi
|
||||||
// using a tolerance of 3 for support of minor differences in Windows Server 2025 CI
|
|
||||||
Assert.IsTrue(
|
Assert.IsTrue(
|
||||||
(Math.Abs(expectedPixel.A - actualPixel.A) <= 3) &&
|
(Math.Abs(expectedPixel.A - actualPixel.A) <= 1) &&
|
||||||
(Math.Abs(expectedPixel.R - actualPixel.R) <= 3) &&
|
(Math.Abs(expectedPixel.R - actualPixel.R) <= 1) &&
|
||||||
(Math.Abs(expectedPixel.G - actualPixel.G) <= 3) &&
|
(Math.Abs(expectedPixel.G - actualPixel.G) <= 1) &&
|
||||||
(Math.Abs(expectedPixel.B - actualPixel.B) <= 3),
|
(Math.Abs(expectedPixel.B - actualPixel.B) <= 1),
|
||||||
$"images differ at pixel ({x}, {y}) - expected: {expectedPixel}, actual: {actualPixel}");
|
$"images differ at pixel ({x}, {y}) - expected: {expectedPixel}, actual: {actualPixel}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,20 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Drawing;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
using MouseJump.Common.Helpers;
|
using MouseJump.Common.Helpers;
|
||||||
using MouseJump.Common.Models.Drawing;
|
using MouseJump.Models.Display;
|
||||||
using MouseJump.Common.Models.Layout;
|
using MouseJump.Models.Drawing;
|
||||||
using MouseJump.Common.Models.Styles;
|
using MouseJump.Models.Styles;
|
||||||
|
using MouseJump.Models.ViewModel;
|
||||||
|
|
||||||
namespace MouseJump.Common.UnitTests.Helpers;
|
namespace MouseJump.Common.UnitTests.Helpers;
|
||||||
|
|
||||||
[TestClass]
|
|
||||||
public static class LayoutHelperTests
|
public static class LayoutHelperTests
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@@ -109,26 +112,32 @@ public static class LayoutHelperTests
|
|||||||
{
|
{
|
||||||
public sealed class TestCase
|
public sealed class TestCase
|
||||||
{
|
{
|
||||||
public TestCase(PreviewStyle previewStyle, List<RectangleInfo> screens, PointInfo activatedLocation, PreviewLayout expectedResult)
|
public TestCase(string testName, PreviewStyle previewStyle, DisplayInfo displayInfo, ScreenInfo activatedScreen, PointInfo activatedLocation, FormViewModel expectedResult)
|
||||||
{
|
{
|
||||||
|
this.TestName = testName;
|
||||||
this.PreviewStyle = previewStyle;
|
this.PreviewStyle = previewStyle;
|
||||||
this.Screens = screens;
|
this.DisplayInfo = displayInfo;
|
||||||
this.ActivatedLocation = activatedLocation;
|
this.ActivatedLocation = activatedLocation;
|
||||||
|
this.ActivatedScreen = activatedScreen;
|
||||||
this.ExpectedResult = expectedResult;
|
this.ExpectedResult = expectedResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string TestName { get; }
|
||||||
|
|
||||||
public PreviewStyle PreviewStyle { get; }
|
public PreviewStyle PreviewStyle { get; }
|
||||||
|
|
||||||
public List<RectangleInfo> Screens { get; }
|
public DisplayInfo DisplayInfo { get; }
|
||||||
|
|
||||||
|
public ScreenInfo ActivatedScreen { get; }
|
||||||
|
|
||||||
public PointInfo ActivatedLocation { get; }
|
public PointInfo ActivatedLocation { get; }
|
||||||
|
|
||||||
public PreviewLayout ExpectedResult { get; }
|
public FormViewModel ExpectedResult { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<object[]> GetTestCases()
|
public static IEnumerable<object[]> GetTestCases()
|
||||||
{
|
{
|
||||||
// happy path - single screen with 50% scaling,
|
// happy path - single device with screen and 50% scaling,
|
||||||
// *has* a preview border but *no* screenshot borders
|
// *has* a preview border but *no* screenshot borders
|
||||||
//
|
//
|
||||||
// +----------------+
|
// +----------------+
|
||||||
@@ -136,10 +145,11 @@ public static class LayoutHelperTests
|
|||||||
// | 0 |
|
// | 0 |
|
||||||
// | |
|
// | |
|
||||||
// +----------------+
|
// +----------------+
|
||||||
|
var testName = "Test 1 - Happy Path";
|
||||||
var previewStyle = new PreviewStyle(
|
var previewStyle = new PreviewStyle(
|
||||||
canvasSize: new(
|
canvasSize: new(
|
||||||
width: 524,
|
width: (1024 / 2) + (5 * 2) + (1 * 2), // half the screen size, plus additional room for canvas border and padding
|
||||||
height: 396
|
height: (768 / 2) + (5 * 2) + (1 * 2) // half the screen size, plus additional room for canvas border and padding
|
||||||
),
|
),
|
||||||
canvasStyle: new(
|
canvasStyle: new(
|
||||||
marginStyle: MarginStyle.Empty,
|
marginStyle: MarginStyle.Empty,
|
||||||
@@ -154,38 +164,56 @@ public static class LayoutHelperTests
|
|||||||
color2: Color.FromArgb(3, 68, 192) // darker blue
|
color2: Color.FromArgb(3, 68, 192) // darker blue
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
screenStyle: BoxStyle.Empty);
|
screenStyle: BoxStyle.Empty,
|
||||||
var screens = new List<RectangleInfo>
|
extraColors: Array.Empty<Color>());
|
||||||
{
|
var displayInfo = new DisplayInfo(
|
||||||
new(0, 0, 1024, 768),
|
devices: new List<DeviceInfo>
|
||||||
};
|
|
||||||
var activatedLocation = new PointInfo(512, 384);
|
|
||||||
var expectedResult = new PreviewLayout(
|
|
||||||
virtualScreen: new(0, 0, 1024, 768),
|
|
||||||
screens: screens,
|
|
||||||
activatedScreenIndex: 0,
|
|
||||||
formBounds: new(250, 186, 524, 396),
|
|
||||||
previewStyle: previewStyle,
|
|
||||||
previewBounds: new(
|
|
||||||
outerBounds: new(0, 0, 524, 396),
|
|
||||||
marginBounds: new(0, 0, 524, 396),
|
|
||||||
borderBounds: new(0, 0, 524, 396),
|
|
||||||
paddingBounds: new(5, 5, 514, 386),
|
|
||||||
contentBounds: new(6, 6, 512, 384)
|
|
||||||
),
|
|
||||||
screenshotBounds: new()
|
|
||||||
{
|
{
|
||||||
new(
|
new(
|
||||||
outerBounds: new(6, 6, 512, 384),
|
hostname: "localhost",
|
||||||
marginBounds: new(6, 6, 512, 384),
|
localhost: true,
|
||||||
borderBounds: new(6, 6, 512, 384),
|
screens: new List<ScreenInfo>
|
||||||
paddingBounds: new(6, 6, 512, 384),
|
{
|
||||||
contentBounds: new(6, 6, 512, 384)
|
new(
|
||||||
|
handle: 0,
|
||||||
|
primary: true,
|
||||||
|
displayArea: new(0, 0, 1024, 768),
|
||||||
|
workingArea: new(0, 0, 1024, 768)),
|
||||||
|
}
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, expectedResult) };
|
var activatedScreen = displayInfo.Devices[0].Screens[0];
|
||||||
|
var activatedLocation = activatedScreen.DisplayArea.Midpoint;
|
||||||
|
var expectedResult = new FormViewModel(
|
||||||
|
formBounds: new(250, 186, 524, 396),
|
||||||
|
canvasLayout: new(
|
||||||
|
canvasBounds: BoxBounds.CreateFromOuterBounds(
|
||||||
|
outerBounds: new(0, 0, 524, 396),
|
||||||
|
boxStyle: previewStyle.CanvasStyle),
|
||||||
|
canvasStyle: previewStyle.CanvasStyle,
|
||||||
|
deviceLayouts: new List<DeviceViewModel>()
|
||||||
|
{
|
||||||
|
new(
|
||||||
|
deviceInfo: displayInfo.Devices[0],
|
||||||
|
deviceBounds: BoxBounds.CreateFromOuterBounds(
|
||||||
|
outerBounds: new(6, 6, 512, 384),
|
||||||
|
boxStyle: BoxStyle.Empty),
|
||||||
|
deviceStyle: BoxStyle.Empty,
|
||||||
|
screenLayouts: new List<ScreenViewModel>
|
||||||
|
{
|
||||||
|
new(
|
||||||
|
screenInfo: displayInfo.Devices[0].Screens[0],
|
||||||
|
screenBounds: BoxBounds.CreateFromOuterBounds(
|
||||||
|
outerBounds: new(6, 6, 512, 384),
|
||||||
|
boxStyle: previewStyle.ScreenStyle),
|
||||||
|
screenStyle: previewStyle.ScreenStyle),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
));
|
||||||
|
yield return new object[] { new TestCase(testName, previewStyle, displayInfo, activatedScreen, activatedLocation, expectedResult) };
|
||||||
|
|
||||||
// happy path - single screen with 50% scaling,
|
// happy path - single device with screen and 50% scaling,
|
||||||
// *no* preview borders but *has* screenshot borders
|
// *no* preview borders but *has* screenshot borders
|
||||||
//
|
//
|
||||||
// +----------------+
|
// +----------------+
|
||||||
@@ -193,10 +221,11 @@ public static class LayoutHelperTests
|
|||||||
// | 0 |
|
// | 0 |
|
||||||
// | |
|
// | |
|
||||||
// +----------------+
|
// +----------------+
|
||||||
|
testName = "Test 2 - Happy Path";
|
||||||
previewStyle = new PreviewStyle(
|
previewStyle = new PreviewStyle(
|
||||||
canvasSize: new(
|
canvasSize: new(
|
||||||
width: 512,
|
width: 1024 / 2, // half the screen size
|
||||||
height: 384
|
height: 768 / 2 // half the screen size
|
||||||
),
|
),
|
||||||
canvasStyle: BoxStyle.Empty,
|
canvasStyle: BoxStyle.Empty,
|
||||||
screenStyle: new(
|
screenStyle: new(
|
||||||
@@ -210,37 +239,54 @@ public static class LayoutHelperTests
|
|||||||
backgroundStyle: new(
|
backgroundStyle: new(
|
||||||
color1: Color.FromArgb(13, 87, 210), // light blue
|
color1: Color.FromArgb(13, 87, 210), // light blue
|
||||||
color2: Color.FromArgb(3, 68, 192) // darker blue
|
color2: Color.FromArgb(3, 68, 192) // darker blue
|
||||||
)
|
)),
|
||||||
));
|
extraColors: Array.Empty<Color>());
|
||||||
screens = new List<RectangleInfo>
|
displayInfo = new DisplayInfo(
|
||||||
{
|
devices: new List<DeviceInfo>
|
||||||
new(0, 0, 1024, 768),
|
|
||||||
};
|
|
||||||
activatedLocation = new PointInfo(512, 384);
|
|
||||||
expectedResult = new PreviewLayout(
|
|
||||||
virtualScreen: new(0, 0, 1024, 768),
|
|
||||||
screens: screens,
|
|
||||||
activatedScreenIndex: 0,
|
|
||||||
formBounds: new(256, 192, 512, 384),
|
|
||||||
previewStyle: previewStyle,
|
|
||||||
previewBounds: new(
|
|
||||||
outerBounds: new(0, 0, 512, 384),
|
|
||||||
marginBounds: new(0, 0, 512, 384),
|
|
||||||
borderBounds: new(0, 0, 512, 384),
|
|
||||||
paddingBounds: new(0, 0, 512, 384),
|
|
||||||
contentBounds: new(0, 0, 512, 384)
|
|
||||||
),
|
|
||||||
screenshotBounds: new()
|
|
||||||
{
|
{
|
||||||
new(
|
new(
|
||||||
outerBounds: new(0, 0, 512, 384),
|
hostname: "localhost",
|
||||||
marginBounds: new(0, 0, 512, 384),
|
localhost: true,
|
||||||
borderBounds: new(1, 1, 510, 382),
|
screens: new List<ScreenInfo>
|
||||||
paddingBounds: new(6, 6, 500, 372),
|
{
|
||||||
contentBounds: new(6, 6, 500, 372)
|
new(
|
||||||
|
handle: 0,
|
||||||
|
primary: true,
|
||||||
|
displayArea: new(0, 0, 1024, 768),
|
||||||
|
workingArea: new(0, 0, 1024, 768)),
|
||||||
|
}
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, expectedResult) };
|
activatedScreen = displayInfo.Devices[0].Screens[0];
|
||||||
|
activatedLocation = activatedScreen.DisplayArea.Midpoint;
|
||||||
|
expectedResult = new FormViewModel(
|
||||||
|
formBounds: new(256, 192, 512, 384),
|
||||||
|
canvasLayout: new(
|
||||||
|
canvasBounds: BoxBounds.CreateFromOuterBounds(
|
||||||
|
outerBounds: new(0, 0, 512, 384),
|
||||||
|
boxStyle: previewStyle.CanvasStyle),
|
||||||
|
canvasStyle: previewStyle.CanvasStyle,
|
||||||
|
deviceLayouts: new List<DeviceViewModel>()
|
||||||
|
{
|
||||||
|
new(
|
||||||
|
deviceInfo: displayInfo.Devices[0],
|
||||||
|
deviceBounds: BoxBounds.CreateFromOuterBounds(
|
||||||
|
outerBounds: new(0, 0, 512, 384),
|
||||||
|
boxStyle: BoxStyle.Empty),
|
||||||
|
deviceStyle: BoxStyle.Empty,
|
||||||
|
screenLayouts: new List<ScreenViewModel>
|
||||||
|
{
|
||||||
|
new(
|
||||||
|
screenInfo: displayInfo.Devices[0].Screens[0],
|
||||||
|
screenBounds: BoxBounds.CreateFromOuterBounds(
|
||||||
|
outerBounds: new(0, 0, 512, 384),
|
||||||
|
boxStyle: previewStyle.ScreenStyle),
|
||||||
|
screenStyle: previewStyle.ScreenStyle),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
));
|
||||||
|
yield return new object[] { new TestCase(testName, previewStyle, displayInfo, activatedScreen, activatedLocation, expectedResult) };
|
||||||
|
|
||||||
// rounding error check - single screen with 33% scaling,
|
// rounding error check - single screen with 33% scaling,
|
||||||
// no borders, check to make sure form scales to exactly
|
// no borders, check to make sure form scales to exactly
|
||||||
@@ -257,42 +303,61 @@ public static class LayoutHelperTests
|
|||||||
// | 0 |
|
// | 0 |
|
||||||
// | |
|
// | |
|
||||||
// +----------------+
|
// +----------------+
|
||||||
|
testName = "Test 3 - Rounding Error Check";
|
||||||
previewStyle = new PreviewStyle(
|
previewStyle = new PreviewStyle(
|
||||||
canvasSize: new(
|
canvasSize: new(
|
||||||
width: 300,
|
width: 300,
|
||||||
height: 200
|
height: 200
|
||||||
),
|
),
|
||||||
canvasStyle: BoxStyle.Empty,
|
canvasStyle: BoxStyle.Empty,
|
||||||
screenStyle: BoxStyle.Empty);
|
screenStyle: BoxStyle.Empty,
|
||||||
screens = new List<RectangleInfo>
|
extraColors: Array.Empty<Color>());
|
||||||
{
|
displayInfo = new DisplayInfo(
|
||||||
new(0, 0, 900, 200),
|
devices: new List<DeviceInfo>
|
||||||
};
|
|
||||||
activatedLocation = new PointInfo(450, 100);
|
|
||||||
expectedResult = new PreviewLayout(
|
|
||||||
virtualScreen: new(0, 0, 900, 200),
|
|
||||||
screens: screens,
|
|
||||||
activatedScreenIndex: 0,
|
|
||||||
formBounds: new(300, 66.5m, 300, 67),
|
|
||||||
previewStyle: previewStyle,
|
|
||||||
previewBounds: new(
|
|
||||||
outerBounds: new(0, 0, 300, 67),
|
|
||||||
marginBounds: new(0, 0, 300, 67),
|
|
||||||
borderBounds: new(0, 0, 300, 67),
|
|
||||||
paddingBounds: new(0, 0, 300, 67),
|
|
||||||
contentBounds: new(0, 0, 300, 67)
|
|
||||||
),
|
|
||||||
screenshotBounds: new()
|
|
||||||
{
|
{
|
||||||
new(
|
new(
|
||||||
outerBounds: new(0, 0, 300, 67),
|
hostname: "localhost",
|
||||||
marginBounds: new(0, 0, 300, 67),
|
localhost: true,
|
||||||
borderBounds: new(0, 0, 300, 67),
|
screens: new List<ScreenInfo>
|
||||||
paddingBounds: new(0, 0, 300, 67),
|
{
|
||||||
contentBounds: new(0, 0, 300, 67)
|
new(
|
||||||
|
handle: 0,
|
||||||
|
primary: true,
|
||||||
|
displayArea: new(0, 0, 900, 200),
|
||||||
|
workingArea: new(0, 0, 900, 200)),
|
||||||
|
}
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, expectedResult) };
|
activatedScreen = displayInfo.Devices[0].Screens[0];
|
||||||
|
activatedLocation = activatedScreen.DisplayArea.Midpoint;
|
||||||
|
expectedResult = new FormViewModel(
|
||||||
|
formBounds: new(300, 66.5m, 300, 67),
|
||||||
|
canvasLayout: new(
|
||||||
|
canvasBounds: BoxBounds.CreateFromOuterBounds(
|
||||||
|
outerBounds: new(0, 0, 300, 67),
|
||||||
|
boxStyle: previewStyle.CanvasStyle),
|
||||||
|
canvasStyle: previewStyle.CanvasStyle,
|
||||||
|
deviceLayouts: new List<DeviceViewModel>()
|
||||||
|
{
|
||||||
|
new(
|
||||||
|
deviceInfo: displayInfo.Devices[0],
|
||||||
|
deviceBounds: BoxBounds.CreateFromOuterBounds(
|
||||||
|
outerBounds: new(0, 0, 300, 67),
|
||||||
|
boxStyle: BoxStyle.Empty),
|
||||||
|
deviceStyle: BoxStyle.Empty,
|
||||||
|
screenLayouts: new List<ScreenViewModel>()
|
||||||
|
{
|
||||||
|
new(
|
||||||
|
screenInfo: displayInfo.Devices[0].Screens[0],
|
||||||
|
screenBounds: BoxBounds.CreateFromOuterBounds(
|
||||||
|
outerBounds: new(0, 0, 300, 67),
|
||||||
|
boxStyle: previewStyle.ScreenStyle),
|
||||||
|
screenStyle: previewStyle.ScreenStyle),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
));
|
||||||
|
yield return new object[] { new TestCase(testName, previewStyle, displayInfo, activatedScreen, activatedLocation, expectedResult) };
|
||||||
|
|
||||||
// primary monitor not topmost / leftmost - if there are screens
|
// primary monitor not topmost / leftmost - if there are screens
|
||||||
// that are further left or higher up than the primary monitor
|
// that are further left or higher up than the primary monitor
|
||||||
@@ -306,6 +371,7 @@ public static class LayoutHelperTests
|
|||||||
// | 1 |
|
// | 1 |
|
||||||
// | |
|
// | |
|
||||||
// +----------------+
|
// +----------------+
|
||||||
|
testName = "Test 4 - Negative Coordinates";
|
||||||
previewStyle = new PreviewStyle(
|
previewStyle = new PreviewStyle(
|
||||||
canvasSize: new(
|
canvasSize: new(
|
||||||
width: 716,
|
width: 716,
|
||||||
@@ -335,49 +401,164 @@ public static class LayoutHelperTests
|
|||||||
backgroundStyle: new(
|
backgroundStyle: new(
|
||||||
color1: Color.FromArgb(13, 87, 210), // light blue
|
color1: Color.FromArgb(13, 87, 210), // light blue
|
||||||
color2: Color.FromArgb(3, 68, 192) // darker blue
|
color2: Color.FromArgb(3, 68, 192) // darker blue
|
||||||
)
|
)),
|
||||||
));
|
extraColors: Array.Empty<Color>());
|
||||||
screens = new List<RectangleInfo>
|
displayInfo = new DisplayInfo(
|
||||||
{
|
devices: new List<DeviceInfo>
|
||||||
new(-1920, -480, 1920, 1080),
|
|
||||||
new(0, 0, 5120, 1440),
|
|
||||||
};
|
|
||||||
activatedLocation = new(-960, 60);
|
|
||||||
expectedResult = new PreviewLayout(
|
|
||||||
virtualScreen: new(-1920, -480, 7040, 1920),
|
|
||||||
screens: screens,
|
|
||||||
activatedScreenIndex: 0,
|
|
||||||
formBounds: new(-1318, -42, 716, 204),
|
|
||||||
previewStyle: previewStyle,
|
|
||||||
previewBounds: new(
|
|
||||||
outerBounds: new(0, 0, 716, 204),
|
|
||||||
marginBounds: new(0, 0, 716, 204),
|
|
||||||
borderBounds: new(0, 0, 716, 204),
|
|
||||||
paddingBounds: new(5, 5, 706, 194),
|
|
||||||
contentBounds: new(6, 6, 704, 192)
|
|
||||||
),
|
|
||||||
screenshotBounds: new()
|
|
||||||
{
|
{
|
||||||
new(
|
new(
|
||||||
outerBounds: new(6, 6, 192, 108),
|
hostname: "localhost",
|
||||||
marginBounds: new(6, 6, 192, 108),
|
localhost: true,
|
||||||
borderBounds: new(7, 7, 190, 106),
|
screens: new List<ScreenInfo>
|
||||||
paddingBounds: new(12, 12, 180, 96),
|
{
|
||||||
contentBounds: new(12, 12, 180, 96)
|
new(
|
||||||
),
|
handle: 0,
|
||||||
new(
|
primary: true,
|
||||||
outerBounds: new(198, 54, 512, 144),
|
displayArea: new(-1920, -480, 1920, 1080),
|
||||||
marginBounds: new(198, 54, 512, 144),
|
workingArea: new(-1920, -480, 1920, 1080)),
|
||||||
borderBounds: new(199, 55, 510, 142),
|
new(
|
||||||
paddingBounds: new(204, 60, 500, 132),
|
handle: 0,
|
||||||
contentBounds: new(204, 60, 500, 132)
|
primary: false,
|
||||||
|
displayArea: new(0, 0, 5120, 1440),
|
||||||
|
workingArea: new(0, 0, 5120, 1440)),
|
||||||
|
}
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, expectedResult) };
|
activatedScreen = displayInfo.Devices[0].Screens[0];
|
||||||
|
activatedLocation = activatedScreen.DisplayArea.Midpoint;
|
||||||
|
expectedResult = new FormViewModel(
|
||||||
|
formBounds: new(-1318, -42, 716, 204),
|
||||||
|
canvasLayout: new(
|
||||||
|
canvasBounds: BoxBounds.CreateFromOuterBounds(
|
||||||
|
outerBounds: new(0, 0, 716, 204),
|
||||||
|
boxStyle: previewStyle.CanvasStyle),
|
||||||
|
canvasStyle: previewStyle.CanvasStyle,
|
||||||
|
deviceLayouts: new List<DeviceViewModel>()
|
||||||
|
{
|
||||||
|
new(
|
||||||
|
deviceInfo: displayInfo.Devices[0],
|
||||||
|
deviceBounds: BoxBounds.CreateFromOuterBounds(
|
||||||
|
outerBounds: new(6, 6, 704, 192),
|
||||||
|
boxStyle: BoxStyle.Empty),
|
||||||
|
deviceStyle: BoxStyle.Empty,
|
||||||
|
screenLayouts: new List<ScreenViewModel>()
|
||||||
|
{
|
||||||
|
new(
|
||||||
|
screenInfo: displayInfo.Devices[0].Screens[0],
|
||||||
|
screenBounds: BoxBounds.CreateFromOuterBounds(
|
||||||
|
outerBounds: new(6, 6, 192, 108),
|
||||||
|
boxStyle: previewStyle.ScreenStyle),
|
||||||
|
screenStyle: previewStyle.ScreenStyle),
|
||||||
|
new(
|
||||||
|
screenInfo: displayInfo.Devices[0].Screens[1],
|
||||||
|
screenBounds: BoxBounds.CreateFromOuterBounds(
|
||||||
|
outerBounds: new(198, 54, 512, 144),
|
||||||
|
boxStyle: previewStyle.ScreenStyle),
|
||||||
|
screenStyle: previewStyle.ScreenStyle),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
));
|
||||||
|
yield return new object[] { new TestCase(testName, previewStyle, displayInfo, activatedScreen, activatedLocation, expectedResult) };
|
||||||
|
|
||||||
|
// two devices side-by-side with a single screen each
|
||||||
|
//
|
||||||
|
// device 1 device 2
|
||||||
|
// +----------------+ +----------------+
|
||||||
|
// | | | |
|
||||||
|
// | 0 | | 0 |
|
||||||
|
// | | | |
|
||||||
|
// +----------------+ +----------------+
|
||||||
|
testName = "Test 5 - Two Devices Side-by-Side";
|
||||||
|
previewStyle = new PreviewStyle(
|
||||||
|
canvasSize: new(
|
||||||
|
width: 1600,
|
||||||
|
height: 1200
|
||||||
|
),
|
||||||
|
canvasStyle: BoxStyle.Empty,
|
||||||
|
screenStyle: BoxStyle.Empty,
|
||||||
|
extraColors: Array.Empty<Color>());
|
||||||
|
displayInfo = new DisplayInfo(
|
||||||
|
devices: new List<DeviceInfo>
|
||||||
|
{
|
||||||
|
new(
|
||||||
|
hostname: "localhost",
|
||||||
|
localhost: true,
|
||||||
|
screens: new List<ScreenInfo>
|
||||||
|
{
|
||||||
|
new(
|
||||||
|
handle: 0,
|
||||||
|
primary: true,
|
||||||
|
displayArea: new(0, 0, 5120, 1440),
|
||||||
|
workingArea: new(0, 0, 5120, 1440)),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
new(
|
||||||
|
hostname: "remotehost",
|
||||||
|
localhost: false,
|
||||||
|
screens: new List<ScreenInfo>
|
||||||
|
{
|
||||||
|
new(
|
||||||
|
handle: 0,
|
||||||
|
primary: true,
|
||||||
|
displayArea: new(0, 0, 5120, 1440),
|
||||||
|
workingArea: new(0, 0, 5120, 1440)),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
});
|
||||||
|
activatedScreen = displayInfo.Devices[0].Screens[0];
|
||||||
|
activatedLocation = activatedScreen.DisplayArea.Midpoint;
|
||||||
|
expectedResult = new FormViewModel(
|
||||||
|
formBounds: new(1760, 607.5m, 1600, 225),
|
||||||
|
canvasLayout: new(
|
||||||
|
canvasBounds: BoxBounds.CreateFromOuterBounds(
|
||||||
|
outerBounds: new(0, 0, 1600, 225),
|
||||||
|
boxStyle: previewStyle.CanvasStyle),
|
||||||
|
canvasStyle: previewStyle.CanvasStyle,
|
||||||
|
deviceLayouts: new List<DeviceViewModel>()
|
||||||
|
{
|
||||||
|
new(
|
||||||
|
deviceInfo: displayInfo.Devices[0],
|
||||||
|
deviceBounds: BoxBounds.CreateFromOuterBounds(
|
||||||
|
outerBounds: new(0, 0, 800, 225),
|
||||||
|
boxStyle: BoxStyle.Empty),
|
||||||
|
deviceStyle: BoxStyle.Empty,
|
||||||
|
screenLayouts: new List<ScreenViewModel>()
|
||||||
|
{
|
||||||
|
new(
|
||||||
|
screenInfo: displayInfo.Devices[0].Screens[0],
|
||||||
|
screenBounds: BoxBounds.CreateFromOuterBounds(
|
||||||
|
outerBounds: new(0, 0, 800, 225),
|
||||||
|
boxStyle: previewStyle.ScreenStyle),
|
||||||
|
screenStyle: previewStyle.ScreenStyle),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
new(
|
||||||
|
deviceInfo: displayInfo.Devices[1],
|
||||||
|
deviceBounds: BoxBounds.CreateFromOuterBounds(
|
||||||
|
outerBounds: new(800, 0, 800, 225),
|
||||||
|
boxStyle: BoxStyle.Empty),
|
||||||
|
deviceStyle: BoxStyle.Empty,
|
||||||
|
screenLayouts: new List<ScreenViewModel>()
|
||||||
|
{
|
||||||
|
new(
|
||||||
|
screenInfo: displayInfo.Devices[1].Screens[0],
|
||||||
|
screenBounds: BoxBounds.CreateFromOuterBounds(
|
||||||
|
outerBounds: new(800, 0, 800, 225),
|
||||||
|
boxStyle: previewStyle.ScreenStyle),
|
||||||
|
screenStyle: previewStyle.ScreenStyle),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
));
|
||||||
|
yield return new object[] { new TestCase(testName, previewStyle, displayInfo, activatedScreen, activatedLocation, expectedResult) };
|
||||||
|
|
||||||
|
// TODO: add a test to make sure the form is nudged into the bounds
|
||||||
|
// of the screen if it's activated near an edge or corner
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DynamicData(nameof(GetTestCases), DynamicDataSourceType.Method)]
|
[DynamicData(nameof(GetTestCases))]
|
||||||
public void RunTestCases(TestCase data)
|
public void RunTestCases(TestCase data)
|
||||||
{
|
{
|
||||||
// note - even if values are within 0.0001M of each other they could
|
// note - even if values are within 0.0001M of each other they could
|
||||||
@@ -387,117 +568,101 @@ public static class LayoutHelperTests
|
|||||||
// (int)1280.000000000000 -> 1280
|
// (int)1280.000000000000 -> 1280
|
||||||
// so we'll compare the raw values, *and* convert to an int-based
|
// so we'll compare the raw values, *and* convert to an int-based
|
||||||
// Rectangle to compare rounded values
|
// Rectangle to compare rounded values
|
||||||
var actual = LayoutHelper.GetPreviewLayout(data.PreviewStyle, data.Screens, data.ActivatedLocation);
|
var actual = LayoutHelper.GetFormLayout(data.PreviewStyle, data.DisplayInfo, data.ActivatedScreen, data.ActivatedLocation);
|
||||||
var expected = data.ExpectedResult;
|
var expected = data.ExpectedResult;
|
||||||
var options = new JsonSerializerOptions
|
var options = new JsonSerializerOptions
|
||||||
{
|
{
|
||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
};
|
};
|
||||||
Assert.AreEqual(
|
var actualJson = JsonSerializer.Serialize(actual, options);
|
||||||
JsonSerializer.Serialize(expected, options),
|
var expectedJson = JsonSerializer.Serialize(expected, options);
|
||||||
JsonSerializer.Serialize(actual, options));
|
Assert.AreEqual(expectedJson, actualJson);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
[TestClass]
|
/// <summary>
|
||||||
public sealed class GetBoxBoundsFromContentBoundsTests
|
/// Basic performance test just to avoid any massive regressions.
|
||||||
{
|
/// </summary>
|
||||||
public sealed class TestCase
|
[TestMethod]
|
||||||
|
[Ignore("Ignore on CI runners - only run locally")]
|
||||||
|
public void BasicPerformanceTest()
|
||||||
{
|
{
|
||||||
public TestCase(RectangleInfo contentBounds, BoxStyle boxStyle, BoxBounds expectedResult)
|
// primary monitor not topmost / leftmost - if there are screens
|
||||||
|
// that are further left or higher up than the primary monitor
|
||||||
|
// they'll have negative coordinates which has caused some
|
||||||
|
// issues with calculations in the past. this test will make
|
||||||
|
// sure we handle screens with negative coordinates gracefully
|
||||||
|
//
|
||||||
|
// +-------+
|
||||||
|
// | 0 +----------------+
|
||||||
|
// +-------+ |
|
||||||
|
// | 1 |
|
||||||
|
// | |
|
||||||
|
// +----------------+
|
||||||
|
var previewStyle = new PreviewStyle(
|
||||||
|
canvasSize: new(
|
||||||
|
width: 716,
|
||||||
|
height: 204
|
||||||
|
),
|
||||||
|
canvasStyle: new(
|
||||||
|
marginStyle: MarginStyle.Empty,
|
||||||
|
borderStyle: new(
|
||||||
|
color: SystemColors.Highlight,
|
||||||
|
all: 5,
|
||||||
|
depth: 3),
|
||||||
|
paddingStyle: new(
|
||||||
|
all: 1),
|
||||||
|
backgroundStyle: new(
|
||||||
|
color1: Color.FromArgb(13, 87, 210), // light blue
|
||||||
|
color2: Color.FromArgb(3, 68, 192) // darker blue
|
||||||
|
)
|
||||||
|
),
|
||||||
|
screenStyle: new(
|
||||||
|
marginStyle: new(
|
||||||
|
all: 1),
|
||||||
|
borderStyle: new(
|
||||||
|
color: SystemColors.Highlight,
|
||||||
|
all: 5,
|
||||||
|
depth: 3),
|
||||||
|
paddingStyle: PaddingStyle.Empty,
|
||||||
|
backgroundStyle: new(
|
||||||
|
color1: Color.FromArgb(13, 87, 210), // light blue
|
||||||
|
color2: Color.FromArgb(3, 68, 192) // darker blue
|
||||||
|
)),
|
||||||
|
extraColors: Array.Empty<Color>());
|
||||||
|
var displayInfo = new DisplayInfo(
|
||||||
|
devices: new List<DeviceInfo>
|
||||||
|
{
|
||||||
|
new(
|
||||||
|
hostname: "localhost",
|
||||||
|
localhost: true,
|
||||||
|
screens: new List<ScreenInfo>
|
||||||
|
{
|
||||||
|
new(
|
||||||
|
handle: 0,
|
||||||
|
primary: true,
|
||||||
|
displayArea: new(-1920, -480, 1920, 1080),
|
||||||
|
workingArea: new(-1920, -480, 1920, 1080)),
|
||||||
|
new(
|
||||||
|
handle: 0,
|
||||||
|
primary: false,
|
||||||
|
displayArea: new(0, 0, 5120, 1440),
|
||||||
|
workingArea: new(0, 0, 5120, 1440)),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
});
|
||||||
|
var activatedScreen = displayInfo.Devices[0].Screens[0];
|
||||||
|
var activatedLocation = activatedScreen.DisplayArea.Midpoint;
|
||||||
|
|
||||||
|
var timer = Stopwatch.StartNew();
|
||||||
|
for (var i = 0; i < 10_000; i++)
|
||||||
{
|
{
|
||||||
this.ContentBounds = contentBounds;
|
var formLayout = LayoutHelper.GetFormLayout(previewStyle, displayInfo, activatedScreen, activatedLocation);
|
||||||
this.BoxStyle = boxStyle;
|
|
||||||
this.ExpectedResult = expectedResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RectangleInfo ContentBounds { get; set; }
|
timer.Stop();
|
||||||
|
|
||||||
public BoxStyle BoxStyle { get; set; }
|
// runs on my machine in about 180-200ms, so leave a bit of headroom
|
||||||
|
Assert.IsTrue(timer.ElapsedMilliseconds < 225);
|
||||||
public BoxBounds ExpectedResult { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<object[]> GetTestCases()
|
|
||||||
{
|
|
||||||
yield return new[]
|
|
||||||
{
|
|
||||||
new TestCase(
|
|
||||||
contentBounds: new(100, 100, 800, 600),
|
|
||||||
boxStyle: new(
|
|
||||||
marginStyle: new(3),
|
|
||||||
borderStyle: new(Color.Red, 5, 0),
|
|
||||||
paddingStyle: new(7),
|
|
||||||
backgroundStyle: BackgroundStyle.Empty),
|
|
||||||
expectedResult: new(
|
|
||||||
outerBounds: new(85, 85, 830, 630),
|
|
||||||
marginBounds: new(85, 85, 830, 630),
|
|
||||||
borderBounds: new(88, 88, 824, 624),
|
|
||||||
paddingBounds: new(93, 93, 814, 614),
|
|
||||||
contentBounds: new(100, 100, 800, 600))),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
[DynamicData(nameof(GetTestCases), DynamicDataSourceType.Method)]
|
|
||||||
public void RunTestCases(TestCase data)
|
|
||||||
{
|
|
||||||
var actual = LayoutHelper.GetBoxBoundsFromContentBounds(data.ContentBounds, data.BoxStyle);
|
|
||||||
var expected = data.ExpectedResult;
|
|
||||||
Assert.AreEqual(
|
|
||||||
JsonSerializer.Serialize(expected),
|
|
||||||
JsonSerializer.Serialize(actual));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestClass]
|
|
||||||
public sealed class GetBoxBoundsFromOuterBoundsTests
|
|
||||||
{
|
|
||||||
public sealed class TestCase
|
|
||||||
{
|
|
||||||
public TestCase(RectangleInfo outerBounds, BoxStyle boxStyle, BoxBounds expectedResult)
|
|
||||||
{
|
|
||||||
this.OuterBounds = outerBounds;
|
|
||||||
this.BoxStyle = boxStyle;
|
|
||||||
this.ExpectedResult = expectedResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RectangleInfo OuterBounds { get; set; }
|
|
||||||
|
|
||||||
public BoxStyle BoxStyle { get; set; }
|
|
||||||
|
|
||||||
public BoxBounds ExpectedResult { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<object[]> GetTestCases()
|
|
||||||
{
|
|
||||||
yield return new[]
|
|
||||||
{
|
|
||||||
new TestCase(
|
|
||||||
outerBounds: new(85, 85, 830, 630),
|
|
||||||
boxStyle: new(
|
|
||||||
marginStyle: new(3),
|
|
||||||
borderStyle: new(Color.Red, 5, 0),
|
|
||||||
paddingStyle: new(7),
|
|
||||||
backgroundStyle: BackgroundStyle.Empty),
|
|
||||||
expectedResult: new(
|
|
||||||
outerBounds: new(85, 85, 830, 630),
|
|
||||||
marginBounds: new(85, 85, 830, 630),
|
|
||||||
borderBounds: new(88, 88, 824, 624),
|
|
||||||
paddingBounds: new(93, 93, 814, 614),
|
|
||||||
contentBounds: new(100, 100, 800, 600))),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
[DynamicData(nameof(GetTestCases), DynamicDataSourceType.Method)]
|
|
||||||
public void RunTestCases(TestCase data)
|
|
||||||
{
|
|
||||||
var actual = LayoutHelper.GetBoxBoundsFromOuterBounds(data.OuterBounds, data.BoxStyle);
|
|
||||||
var expected = data.ExpectedResult;
|
|
||||||
Assert.AreEqual(
|
|
||||||
JsonSerializer.Serialize(expected),
|
|
||||||
JsonSerializer.Serialize(actual));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
using MouseJump.Common.Helpers;
|
using MouseJump.Common.Helpers;
|
||||||
using MouseJump.Common.Models.Drawing;
|
using MouseJump.Models.Drawing;
|
||||||
|
|
||||||
namespace MouseJump.Common.UnitTests.Helpers;
|
namespace MouseJump.Common.UnitTests.Helpers;
|
||||||
|
|
||||||
[TestClass]
|
|
||||||
public static class MouseHelperTests
|
public static class MouseHelperTests
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
@@ -58,7 +58,7 @@ public static class MouseHelperTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DynamicData(nameof(GetTestCases), DynamicDataSourceType.Method)]
|
[DynamicData(nameof(GetTestCases))]
|
||||||
public void RunTestCases(TestCase data)
|
public void RunTestCases(TestCase data)
|
||||||
{
|
{
|
||||||
var actual = MouseHelper.GetJumpLocation(
|
var actual = MouseHelper.GetJumpLocation(
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 202 KiB After Width: | Height: | Size: 202 KiB |
@@ -1,44 +1,40 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||||
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
|
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<ProjectGuid>{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}</ProjectGuid>
|
||||||
|
<AssemblyName>PowerToys.MouseJump.Common.UnitTests</AssemblyName>
|
||||||
|
<AssemblyTitle>PowerToys.MouseJump.Common.UnitTests</AssemblyTitle>
|
||||||
|
<AssemblyDescription>PowerToys MouseJump.Common.UnitTests</AssemblyDescription>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\tests\MouseJump.Common.UnitTests\</OutputPath>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||||
|
|
||||||
<ProjectGuid>{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}</ProjectGuid>
|
<Nullable>enable</Nullable>
|
||||||
<AssemblyName>PowerToys.MouseJump.Common.UnitTests</AssemblyName>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<AssemblyTitle>PowerToys.MouseJump.Common.UnitTests</AssemblyTitle>
|
</PropertyGroup>
|
||||||
<AssemblyDescription>PowerToys MouseJump.Common.UnitTests</AssemblyDescription>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<OutputPath>$(RepoRoot)$(Platform)\$(Configuration)\tests\MouseJump.Common.UnitTests\</OutputPath>
|
|
||||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
|
||||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
|
||||||
|
|
||||||
<Nullable>enable</Nullable>
|
<ItemGroup>
|
||||||
<UseWindowsForms>true</UseWindowsForms>
|
<None Remove="Helpers\_test-4grid-desktop.png" />
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<None Remove="Helpers\_test-4grid-expected.png" />
|
||||||
|
<None Remove="Helpers\_test-win11-desktop.png" />
|
||||||
|
<None Remove="Helpers\_test-win11-expected.png" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</PropertyGroup>
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="Helpers\_test-4grid-desktop.png" />
|
||||||
|
<EmbeddedResource Include="Helpers\_test-4grid-expected.png" />
|
||||||
|
<EmbeddedResource Include="Helpers\_test-win11-desktop.png" />
|
||||||
|
<EmbeddedResource Include="Helpers\_test-win11-expected.png" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="Helpers\_test-4grid-desktop.png" />
|
<ProjectReference Include="..\MouseJump.Common\MouseJump.Common.csproj" />
|
||||||
<None Remove="Helpers\_test-4grid-expected.png" />
|
</ItemGroup>
|
||||||
<None Remove="Helpers\_test-win11-desktop.png" />
|
|
||||||
<None Remove="Helpers\_test-win11-expected.png" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<EmbeddedResource Include="Helpers\_test-4grid-desktop.png" />
|
|
||||||
<EmbeddedResource Include="Helpers\_test-4grid-expected.png" />
|
|
||||||
<EmbeddedResource Include="Helpers\_test-win11-desktop.png" />
|
|
||||||
<EmbeddedResource Include="Helpers\_test-win11-expected.png" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="MSTest" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\MouseJump.Common\MouseJump.Common.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="MSTest" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
// Copyright (c) Microsoft Corporation
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Drawing;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
|
||||||
namespace MouseJump.Common.Helpers;
|
namespace MouseJump.Common.Helpers;
|
||||||
|
|
||||||
public static class ConfigHelper
|
public static class ColorHelper
|
||||||
{
|
{
|
||||||
public static Color? ToUnnamedColor(Color? value)
|
public static Color? ToUnnamedColor(Color? value)
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using MouseJump.Models.Display;
|
||||||
|
using MouseJump.Models.Drawing;
|
||||||
|
|
||||||
|
namespace MouseJump.Common.Helpers;
|
||||||
|
|
||||||
|
public static class DeviceHelper
|
||||||
|
{
|
||||||
|
public static DisplayInfo GetDisplayInfo()
|
||||||
|
{
|
||||||
|
var devices = new List<DeviceInfo>();
|
||||||
|
|
||||||
|
// add the local devices
|
||||||
|
devices.Add(
|
||||||
|
new(
|
||||||
|
hostname: Environment.MachineName,
|
||||||
|
localhost: true,
|
||||||
|
screens: ScreenHelper.GetAllScreens()));
|
||||||
|
|
||||||
|
return new DisplayInfo(
|
||||||
|
devices: devices);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ScreenInfo GetActivatedScreen(DeviceInfo deviceInfo, PointInfo activatedLocation)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(deviceInfo);
|
||||||
|
ArgumentNullException.ThrowIfNull(activatedLocation);
|
||||||
|
|
||||||
|
var activatedScreen = deviceInfo.Screens
|
||||||
|
.Single(screen => screen.DisplayArea.Contains(activatedLocation));
|
||||||
|
|
||||||
|
return activatedScreen;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
|
using Windows.Win32;
|
||||||
|
using Windows.Win32.UI.HiDpi;
|
||||||
|
|
||||||
|
namespace MouseJump.Common.Helpers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Based on WinForms Application.HighDpiMode
|
||||||
|
/// see https://github.com/dotnet/winforms/blob/1c324d074280ab5de6342d973069faa687f2c165/src/System.Windows.Forms/System/Windows/Forms/Application.cs#L18
|
||||||
|
/// https://github.com/dotnet/winforms/blob/1c324d074280ab5de6342d973069faa687f2c165/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/ScaleHelper.cs#L14
|
||||||
|
/// </summary>
|
||||||
|
public static class DpiModeHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// See https://github.com/dotnet/winforms/blob/bd91bfb26ce90ac31e950c01dcb2b6e0776453a7/src/System.Private.Windows.Core/src/System/Private/Windows/OsVersion.cs#L9
|
||||||
|
/// </summary>
|
||||||
|
private static bool IsWindows10_1607OrGreater()
|
||||||
|
=> OperatingSystem.IsWindowsVersionAtLeast(major: 10, build: 14393);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that the application is running in per-monitor v2 DPI awareness mode.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// See https://github.com/dotnet/winforms/blob/bd91bfb26ce90ac31e950c01dcb2b6e0776453a7/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/ScaleHelper.cs#L368
|
||||||
|
/// </remarks>
|
||||||
|
[SupportedOSPlatform("windows10.0.14393")]
|
||||||
|
public static void EnsurePerMonitorV2Enabled()
|
||||||
|
{
|
||||||
|
// PowerToys supports the following operating systems:
|
||||||
|
//
|
||||||
|
// * Windows 11 or Windows 10 version 2004 (code name 20H1 / build number 19041) or newer.
|
||||||
|
//
|
||||||
|
// so we'll do the same(ish)
|
||||||
|
if (!DpiModeHelper.IsWindows10_1607OrGreater())
|
||||||
|
{
|
||||||
|
throw new PlatformNotSupportedException(
|
||||||
|
"Windows 10 version1607 or higher is required to use this application.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// there's a weird problem where AreDpiAwarenessContextsEqual was returning TRUE in debug mode
|
||||||
|
// but FALSE in release mode and i couldn't work out why, so we can't do it the *right* way.
|
||||||
|
// we'll just use GetAwarenessFromDpiAwarenessContext instead as a near-enough workaround.
|
||||||
|
var dpiAwarenessContext = PInvoke.GetThreadDpiAwarenessContext();
|
||||||
|
var dpiAwareness = PInvoke.GetAwarenessFromDpiAwarenessContext(dpiAwarenessContext);
|
||||||
|
if (dpiAwareness != DPI_AWARENESS.DPI_AWARENESS_PER_MONITOR_AWARE)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"high dpi mode is not set to {nameof(DPI_AWARENESS.DPI_AWARENESS_PER_MONITOR_AWARE)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,62 +3,101 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Drawing;
|
||||||
using System.Drawing.Drawing2D;
|
using System.Drawing.Drawing2D;
|
||||||
using System.Drawing.Imaging;
|
using System.Drawing.Imaging;
|
||||||
|
|
||||||
using MouseJump.Common.Imaging;
|
using MouseJump.Common.Imaging;
|
||||||
using MouseJump.Common.Models.Drawing;
|
using MouseJump.Models.Display;
|
||||||
using MouseJump.Common.Models.Layout;
|
using MouseJump.Models.Drawing;
|
||||||
using MouseJump.Common.Models.Styles;
|
using MouseJump.Models.Styles;
|
||||||
|
using MouseJump.Models.ViewModel;
|
||||||
|
|
||||||
namespace MouseJump.Common.Helpers;
|
namespace MouseJump.Common.Helpers;
|
||||||
|
|
||||||
public static class DrawingHelper
|
public static class DrawingHelper
|
||||||
{
|
{
|
||||||
public static Bitmap RenderPreview(
|
/// <summary>
|
||||||
PreviewLayout previewLayout,
|
/// Renders a preview image of the specified canvas layout.
|
||||||
IImageRegionCopyService imageCopyService,
|
/// </summary>
|
||||||
Action<Bitmap>? previewImageCreatedCallback = null,
|
/// <param name="canvasLayout">
|
||||||
Action? previewImageUpdatedCallback = null)
|
/// The layout of the canvas, including the layout of all devices and screens.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="activatedScreen">
|
||||||
|
/// The screen that is currently activated (i.e. the one that the user is interacting with).
|
||||||
|
/// </param>
|
||||||
|
/// <param name="imageRegionCopyServices">
|
||||||
|
/// A list of IImageRegionCopyService implementations, one for each device in the canvas layout.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="previewImageCreatedCallback">
|
||||||
|
/// A callback that is invoked when the preview image is created.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="previewImageUpdatedCallback">
|
||||||
|
/// A callback that is invoked when the preview image is updated.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// A preview image of the canvas layout.
|
||||||
|
/// </returns>
|
||||||
|
public static async Task<Bitmap> RenderPreviewAsync(
|
||||||
|
CanvasViewModel canvasLayout,
|
||||||
|
ScreenInfo activatedScreen,
|
||||||
|
List<IImageRegionCopyService> imageRegionCopyServices,
|
||||||
|
Func<Bitmap, Task>? previewImageCreatedCallback = null,
|
||||||
|
Func<Bitmap, Task>? previewImageUpdatedCallback = null)
|
||||||
{
|
{
|
||||||
var stopwatch = Stopwatch.StartNew();
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
// initialize the preview image
|
// initialize the preview image
|
||||||
var previewBounds = previewLayout.PreviewBounds.OuterBounds.ToRectangle();
|
var previewBounds = canvasLayout.CanvasBounds.OuterBounds.ToRectangle();
|
||||||
var previewImage = new Bitmap(previewBounds.Width, previewBounds.Height, PixelFormat.Format32bppPArgb);
|
var previewImage = new Bitmap(previewBounds.Width, previewBounds.Height, PixelFormat.Format32bppPArgb);
|
||||||
var previewGraphics = Graphics.FromImage(previewImage);
|
using var previewGraphics = Graphics.FromImage(previewImage);
|
||||||
previewImageCreatedCallback?.Invoke(previewImage);
|
if (previewImageCreatedCallback != null)
|
||||||
|
{
|
||||||
|
await previewImageCreatedCallback(previewImage);
|
||||||
|
}
|
||||||
|
|
||||||
DrawingHelper.DrawRaisedBorder(previewGraphics, previewLayout.PreviewStyle.CanvasStyle, previewLayout.PreviewBounds);
|
DrawingHelper.DrawRaisedBorder(previewGraphics, canvasLayout.CanvasBounds, canvasLayout.CanvasStyle);
|
||||||
DrawingHelper.DrawBackgroundFill(
|
DrawingHelper.DrawBackgroundFill(
|
||||||
previewGraphics,
|
previewGraphics,
|
||||||
previewLayout.PreviewStyle.CanvasStyle,
|
canvasLayout.CanvasStyle,
|
||||||
previewLayout.PreviewBounds,
|
canvasLayout.CanvasBounds,
|
||||||
[]);
|
[]);
|
||||||
|
|
||||||
// sort the source and target screen areas into the order we want to
|
// sort the source and target screen areas into the order we want to
|
||||||
// draw them, putting the activated screen first (we need to capture
|
// draw them, putting the activated screen first (we need to capture
|
||||||
// and draw the activated screen before we show the form because
|
// and draw the activated screen before we show the form because
|
||||||
// otherwise we'll capture the form as part of the screenshot!)
|
// otherwise we'll capture the form as part of the screenshot!)
|
||||||
var sourceScreens = new List<RectangleInfo> { previewLayout.Screens[previewLayout.ActivatedScreenIndex] }
|
var screenDrawingOps = canvasLayout.DeviceLayouts
|
||||||
.Concat(previewLayout.Screens.Where((_, idx) => idx != previewLayout.ActivatedScreenIndex))
|
.SelectMany(
|
||||||
.ToList();
|
(deviceLayout, deviceIndex) => deviceLayout.ScreenLayouts.Select(
|
||||||
var targetScreens = new List<BoxBounds> { previewLayout.ScreenshotBounds[previewLayout.ActivatedScreenIndex] }
|
screenLayout => new
|
||||||
.Concat(previewLayout.ScreenshotBounds.Where((_, idx) => idx != previewLayout.ActivatedScreenIndex))
|
{
|
||||||
|
DeviceIndex = deviceIndex,
|
||||||
|
DeviceLayout = deviceLayout,
|
||||||
|
ScreenLayout = screenLayout,
|
||||||
|
CopyService = imageRegionCopyServices[deviceIndex],
|
||||||
|
}))
|
||||||
|
.OrderByDescending(
|
||||||
|
pair => object.ReferenceEquals(pair.ScreenLayout.ScreenInfo, activatedScreen))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// draw all the screenshot bezels
|
// draw all the screenshot bezels
|
||||||
foreach (var screenshotBounds in previewLayout.ScreenshotBounds)
|
foreach (var screenDrawingOp in screenDrawingOps)
|
||||||
{
|
{
|
||||||
DrawingHelper.DrawRaisedBorder(
|
DrawingHelper.DrawRaisedBorder(
|
||||||
previewGraphics, previewLayout.PreviewStyle.ScreenStyle, screenshotBounds);
|
previewGraphics, screenDrawingOp.ScreenLayout.ScreenBounds, screenDrawingOp.ScreenLayout.ScreenStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
var refreshRequired = false;
|
var refreshRequired = false;
|
||||||
var placeholdersDrawn = false;
|
var placeholdersDrawn = false;
|
||||||
for (var i = 0; i < sourceScreens.Count; i++)
|
for (var i = 0; i < screenDrawingOps.Count; i++)
|
||||||
{
|
{
|
||||||
imageCopyService.CopyImageRegion(previewGraphics, sourceScreens[i], targetScreens[i].ContentBounds);
|
var screenDrawingOp = screenDrawingOps[i];
|
||||||
|
|
||||||
|
screenDrawingOp.CopyService.CopyImageRegion(
|
||||||
|
targetGraphics: previewGraphics,
|
||||||
|
sourceBounds: screenDrawingOp.ScreenLayout.ScreenInfo.DisplayArea,
|
||||||
|
targetBounds: screenDrawingOp.ScreenLayout.ScreenBounds.ContentBounds);
|
||||||
refreshRequired = true;
|
refreshRequired = true;
|
||||||
|
|
||||||
// show the placeholder images and show the form if it looks like it might take
|
// show the placeholder images and show the form if it looks like it might take
|
||||||
@@ -70,19 +109,26 @@ public static class DrawingHelper
|
|||||||
{
|
{
|
||||||
DrawingHelper.DrawScreenPlaceholders(
|
DrawingHelper.DrawScreenPlaceholders(
|
||||||
previewGraphics,
|
previewGraphics,
|
||||||
previewLayout.PreviewStyle.ScreenStyle,
|
screenDrawingOp.ScreenLayout.ScreenStyle,
|
||||||
targetScreens.GetRange(i + 1, targetScreens.Count - i - 1));
|
screenDrawingOps
|
||||||
|
.Skip(i + 1)
|
||||||
|
.Select(drawingOp => drawingOp.ScreenLayout.ScreenBounds)
|
||||||
|
.ToList());
|
||||||
placeholdersDrawn = true;
|
placeholdersDrawn = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
previewImageUpdatedCallback?.Invoke();
|
if (previewImageUpdatedCallback is not null)
|
||||||
|
{
|
||||||
|
await previewImageUpdatedCallback(previewImage);
|
||||||
|
}
|
||||||
|
|
||||||
refreshRequired = false;
|
refreshRequired = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (refreshRequired)
|
if (refreshRequired && (previewImageUpdatedCallback is not null))
|
||||||
{
|
{
|
||||||
previewImageUpdatedCallback?.Invoke();
|
await previewImageUpdatedCallback(previewImage);
|
||||||
}
|
}
|
||||||
|
|
||||||
stopwatch.Stop();
|
stopwatch.Stop();
|
||||||
@@ -94,10 +140,10 @@ public static class DrawingHelper
|
|||||||
/// Draws a border shape with an optional raised 3d highlight and shadow effect.
|
/// Draws a border shape with an optional raised 3d highlight and shadow effect.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void DrawRaisedBorder(
|
private static void DrawRaisedBorder(
|
||||||
Graphics graphics, BoxStyle boxStyle, BoxBounds boxBounds)
|
Graphics graphics, BoxBounds boxBounds, BoxStyle boxStyle)
|
||||||
{
|
{
|
||||||
var borderStyle = boxStyle.BorderStyle;
|
var borderStyle = boxStyle.BorderStyle;
|
||||||
if ((borderStyle.Horizontal == 0) || (borderStyle.Vertical == 0))
|
if ((borderStyle.Horizontal < 1) || (borderStyle.Vertical < 1))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -109,7 +155,7 @@ public static class DrawingHelper
|
|||||||
|
|
||||||
// draw the main box border
|
// draw the main box border
|
||||||
using var borderBrush = new SolidBrush(borderStyle.Color.Value);
|
using var borderBrush = new SolidBrush(borderStyle.Color.Value);
|
||||||
var borderRegion = new Region(boxBounds.BorderBounds.ToRectangle());
|
using var borderRegion = new Region(boxBounds.BorderBounds.ToRectangle());
|
||||||
borderRegion.Exclude(boxBounds.PaddingBounds.ToRectangle());
|
borderRegion.Exclude(boxBounds.PaddingBounds.ToRectangle());
|
||||||
graphics.FillRegion(borderBrush, borderRegion);
|
graphics.FillRegion(borderBrush, borderRegion);
|
||||||
|
|
||||||
@@ -195,7 +241,7 @@ public static class DrawingHelper
|
|||||||
|
|
||||||
// it's faster to build a region with the screen areas excluded
|
// it's faster to build a region with the screen areas excluded
|
||||||
// and fill that than it is to fill the entire bounding rectangle
|
// and fill that than it is to fill the entire bounding rectangle
|
||||||
var backgroundRegion = new Region(backgroundBounds.ToRectangle());
|
using var backgroundRegion = new Region(backgroundBounds.ToRectangle());
|
||||||
foreach (var exclude in excludeBounds)
|
foreach (var exclude in excludeBounds)
|
||||||
{
|
{
|
||||||
backgroundRegion.Exclude(exclude.ToRectangle());
|
backgroundRegion.Exclude(exclude.ToRectangle());
|
||||||
@@ -208,14 +254,14 @@ public static class DrawingHelper
|
|||||||
/// Draws placeholder background images for the specified screens on the preview.
|
/// Draws placeholder background images for the specified screens on the preview.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void DrawScreenPlaceholders(
|
private static void DrawScreenPlaceholders(
|
||||||
Graphics graphics, BoxStyle screenStyle, IList<BoxBounds> screenBounds)
|
Graphics graphics, BoxStyle screenStyle, List<BoxBounds> screenBounds)
|
||||||
{
|
{
|
||||||
if (screenBounds.Count == 0)
|
if (screenBounds.Count == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (screenStyle?.BackgroundStyle?.Color1 == null)
|
if (screenStyle.BackgroundStyle.Color1 == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,152 +2,401 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using MouseJump.Common.Models.Drawing;
|
using System.Diagnostics;
|
||||||
using MouseJump.Common.Models.Layout;
|
using System.Drawing;
|
||||||
using MouseJump.Common.Models.Styles;
|
|
||||||
|
using MouseJump.Models.Display;
|
||||||
|
using MouseJump.Models.Drawing;
|
||||||
|
using MouseJump.Models.Styles;
|
||||||
|
using MouseJump.Models.ViewModel;
|
||||||
|
|
||||||
namespace MouseJump.Common.Helpers;
|
namespace MouseJump.Common.Helpers;
|
||||||
|
|
||||||
public static class LayoutHelper
|
public static class LayoutHelper
|
||||||
{
|
{
|
||||||
public static PreviewLayout GetPreviewLayout(
|
public static RectangleInfo GetCombinedScreenBounds(List<RectangleInfo> screenBounds)
|
||||||
PreviewStyle previewStyle, List<RectangleInfo> screens, PointInfo activatedLocation)
|
{
|
||||||
|
return screenBounds.Skip(1).Aggregate(
|
||||||
|
seed: screenBounds.First(),
|
||||||
|
(combined, screenBounds) => combined.Union(screenBounds));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FormViewModel GetFormLayout(
|
||||||
|
PreviewStyle previewStyle, DisplayInfo displayInfo, ScreenInfo activatedScreen, PointInfo activatedLocation)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(previewStyle);
|
ArgumentNullException.ThrowIfNull(previewStyle);
|
||||||
ArgumentNullException.ThrowIfNull(screens);
|
ArgumentNullException.ThrowIfNull(displayInfo);
|
||||||
|
ArgumentNullException.ThrowIfNull(activatedScreen);
|
||||||
|
ArgumentNullException.ThrowIfNull(activatedLocation);
|
||||||
|
|
||||||
if (screens.Count == 0)
|
/*
|
||||||
|
|
||||||
|
example layout:
|
||||||
|
|
||||||
|
+-------------------------------[form]------------------------------+
|
||||||
|
|▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒[canvas]▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒|
|
||||||
|
|▒▒+----------------------------[grid]----+----------------------+▒▒|
|
||||||
|
|▒▒| |▓▓▓▓▓▓[device 2]▓▓▓▓▓▓|▒▒|
|
||||||
|
|▒▒| |▓▓░░░░[screen 1]░░░░▓▓|▒▒|
|
||||||
|
|▒▒|▓▓▓▓▓▓▓▓▓▓▓▓▓▓[device 1]▓▓▓▓▓▓▓▓▓▓▓▓▓▓|▓▓░░ ░░▓▓|▒▒|
|
||||||
|
|▒▒|▓▓░░░░[screen 1]░░░░░░[screen 2]░░░░▓▓|▓▓░░ ░░▓▓|▒▒|
|
||||||
|
|▒▒|▓▓░░ ░░ ░░▓▓|▓▓░░ ░░▓▓|▒▒|
|
||||||
|
|▒▒|▓▓░░ ░░ ░░▓▓|▓▓░░░░░░░░░░░░░░░░░░▓▓|▒▒|
|
||||||
|
|▒▒|▓▓░░ ░░ ░░▓▓|▓▓░░░░[screen 2]░░░░▓▓|▒▒|
|
||||||
|
|▒▒|▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓|▓▓░░ ░░▓▓|▒▒|
|
||||||
|
|▒▒|▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓|▓▓░░ ░░▓▓|▒▒|
|
||||||
|
|▒▒| |▓▓░░ ░░▓▓|▒▒|
|
||||||
|
|▒▒| |▓▓░░░░░░░░░░░░░░░░░░▓▓|▒▒|
|
||||||
|
|▒▒| |▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓|▒▒|
|
||||||
|
|▒▒+--------------------------------------+----------------------+▒▒|
|
||||||
|
|▒▒| ▓▓▓▓▓▓[device 3]▓▓▓▓▓▓ | |▒▒|
|
||||||
|
|▒▒| ▓▓░░░░[screen 1]░░░░▓▓ | |▒▒|
|
||||||
|
|▒▒| ▓▓░░ ░░▓▓ | |▒▒|
|
||||||
|
|▒▒| ▓▓░░ ░░▓▓ | ▓▓▓[device 4]▓▓▓ |▒▒|
|
||||||
|
|▒▒| ▓▓░░ ░░▓▓ | ▓▓░[screen 1]░▓▓ |▒▒|
|
||||||
|
|▒▒| ▓▓░░░░░░░░░░░░░░░░░░▓▓ | ▓▓░░ ░░▓▓ |▒▒|
|
||||||
|
|▒▒| ▓▓░░░░[screen 2]░░░░▓▓ | ▓▓░░ ░░▓▓ |▒▒|
|
||||||
|
|▒▒| ▓▓░░ ░░▓▓ | ▓▓░░░░░░░░░░░░▓▓ |▒▒|
|
||||||
|
|▒▒| ▓▓░░ ░░▓▓ | ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ |▒▒|
|
||||||
|
|▒▒| ▓▓░░ ░░▓▓ | |▒▒|
|
||||||
|
|▒▒| ▓▓░░░░░░░░░░░░░░░░░░▓▓ | |▒▒|
|
||||||
|
|▒▒| ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ | |▒▒|
|
||||||
|
|▒▒+--------------------------------------+----------------------+▒▒|
|
||||||
|
|▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒|
|
||||||
|
+-------------------------------------------------------------------+
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// arrange the form, canvas, devices and screens
|
||||||
|
var formLayout = LayoutHelper.CreateInitialFormLayout(previewStyle, displayInfo, activatedScreen);
|
||||||
|
LayoutHelper.ArrangeAndScaleDeviceLayouts(formLayout);
|
||||||
|
LayoutHelper.ArrangeAndScaleScreenLayouts(formLayout);
|
||||||
|
LayoutHelper.ArrangeAndResizeCanvasLayout(formLayout);
|
||||||
|
LayoutHelper.ArrangeAndResizeFormLayout(formLayout, activatedScreen, activatedLocation);
|
||||||
|
|
||||||
|
return formLayout.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static FormViewModel.Builder CreateInitialFormLayout(
|
||||||
|
PreviewStyle previewStyle, DisplayInfo displayInfo, ScreenInfo activatedScreen)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(previewStyle);
|
||||||
|
ArgumentNullException.ThrowIfNull(displayInfo);
|
||||||
|
|
||||||
|
// check we have at least one device
|
||||||
|
if (displayInfo.Devices.Count == 0)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Value must contain at least one item.", nameof(screens));
|
throw new ArgumentException("Value must contain at least one device.", nameof(displayInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
var builder = new PreviewLayout.Builder();
|
/*
|
||||||
builder.Screens = screens.ToList();
|
// check each device has at least one screen.
|
||||||
|
for (var deviceIndex = 0; deviceIndex < displayInfo.Devices.Count; deviceIndex++)
|
||||||
// calculate the bounding rectangle for the virtual screen
|
{
|
||||||
builder.VirtualScreen = LayoutHelper.GetCombinedScreenBounds(builder.Screens);
|
var device = displayInfo.Devices[deviceIndex];
|
||||||
|
if (device.Screens.Count == 0)
|
||||||
// find the screen that contains the activated location - this is the
|
{
|
||||||
// one we'll show the preview form on
|
throw new ArgumentException($"{nameof(displayInfo)}.{nameof(displayInfo.Devices)}[{deviceIndex}] must contain at least one screen.", nameof(displayInfo));
|
||||||
var activatedScreen = builder.Screens.Single(
|
}
|
||||||
screen => screen.Contains(activatedLocation));
|
}
|
||||||
builder.ActivatedScreenIndex = builder.Screens.IndexOf(activatedScreen);
|
*/
|
||||||
|
|
||||||
// work out the maximum allowed size of the preview form:
|
// work out the maximum allowed size of the preview form:
|
||||||
// * can't be bigger than the activated screen
|
// * can't be bigger than the activated screen
|
||||||
// * can't be bigger than the configured canvas size
|
// * can't be bigger than the configured canvas size
|
||||||
var maxPreviewSize = activatedScreen.Size
|
var formMaxBounds = new RectangleInfo(
|
||||||
.Intersect(previewStyle.CanvasSize);
|
previewStyle.CanvasSize
|
||||||
|
.Clamp(activatedScreen.DisplayArea.Size));
|
||||||
|
|
||||||
// the "content area" (i.e. drawing area) for screenshots is inside the
|
var screenStyles = LayoutHelper.GetDeviceScreenStyles(previewStyle, displayInfo.Devices.Count).ToList();
|
||||||
// preview border and inside the preview padding (if any)
|
|
||||||
var maxContentSize = maxPreviewSize
|
|
||||||
.Shrink(previewStyle.CanvasStyle.MarginStyle)
|
|
||||||
.Shrink(previewStyle.CanvasStyle.BorderStyle)
|
|
||||||
.Shrink(previewStyle.CanvasStyle.PaddingStyle);
|
|
||||||
|
|
||||||
// work out the actual size of the "content area" by scaling the virtual screen
|
// create an initial form layout.
|
||||||
// to fit inside the maximum content area while maintaining its aspect ration.
|
// this is a nested structure of mutable "builder" objects that can be used to
|
||||||
// we'll also offset it to allow for any margins, borders and padding
|
// build the final immutable layout objects once all the bounds have been calculated
|
||||||
var contentBounds = builder.VirtualScreen.Size
|
var formLayout = new FormViewModel.Builder
|
||||||
.ScaleToFit(maxContentSize, out var scalingRatio)
|
{
|
||||||
.Round()
|
FormBounds = RectangleInfo.Empty,
|
||||||
.Clamp(maxContentSize)
|
CanvasLayout = new()
|
||||||
.PlaceAt(0, 0)
|
{
|
||||||
.Offset(previewStyle.CanvasStyle.MarginStyle.Left, previewStyle.CanvasStyle.MarginStyle.Top)
|
CanvasBounds = BoxBounds.CreateFromOuterBounds(
|
||||||
.Offset(previewStyle.CanvasStyle.BorderStyle.Left, previewStyle.CanvasStyle.BorderStyle.Top)
|
outerBounds: formMaxBounds,
|
||||||
.Offset(previewStyle.CanvasStyle.PaddingStyle.Left, previewStyle.CanvasStyle.PaddingStyle.Top);
|
boxStyle: previewStyle.CanvasStyle),
|
||||||
|
CanvasStyle = previewStyle.CanvasStyle,
|
||||||
|
DeviceLayouts = displayInfo.Devices.Select(
|
||||||
|
(deviceInfo, deviceIndex) => new DeviceViewModel.Builder
|
||||||
|
{
|
||||||
|
DeviceInfo = deviceInfo,
|
||||||
|
DeviceBounds = BoxBounds.Empty,
|
||||||
|
DeviceStyle = BoxStyle.Empty,
|
||||||
|
ScreenLayouts = deviceInfo.Screens.Select(
|
||||||
|
screenInfo => new ScreenViewModel.Builder
|
||||||
|
{
|
||||||
|
ScreenInfo = screenInfo,
|
||||||
|
ScreenBounds = BoxBounds.Empty,
|
||||||
|
ScreenStyle = screenStyles[deviceIndex],
|
||||||
|
}).ToList(),
|
||||||
|
}).ToList(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// now we know the actual size of the content area we can work outwards to
|
return formLayout;
|
||||||
// get the size of the background bounds including margins, borders and padding
|
}
|
||||||
builder.PreviewStyle = previewStyle;
|
|
||||||
builder.PreviewBounds = LayoutHelper.GetBoxBoundsFromContentBounds(
|
|
||||||
contentBounds,
|
|
||||||
previewStyle.CanvasStyle);
|
|
||||||
|
|
||||||
// ... and then the size and position of the preview form on the activated screen
|
internal static IEnumerable<BoxStyle> GetDeviceScreenStyles(PreviewStyle previewStyle, int screenCount)
|
||||||
// * center the form to the activated position, but nudge it back
|
{
|
||||||
// inside the visible area of the activated screen if it falls outside
|
// work out the colors to use for the screen borders for each device
|
||||||
var formBounds = builder.PreviewBounds.OuterBounds
|
// (add the default localhost color first and then loop over the extra colors until we've got enough)
|
||||||
.Center(activatedLocation)
|
var screenBorderColors = new List<Color?> { previewStyle.ScreenStyle.BorderStyle.Color };
|
||||||
.Clamp(activatedScreen);
|
while (screenBorderColors.Count < screenCount)
|
||||||
builder.FormBounds = formBounds;
|
{
|
||||||
|
if (previewStyle.ExtraColors?.Count > 0)
|
||||||
|
{
|
||||||
|
screenBorderColors.AddRange(previewStyle.ExtraColors.Cast<Color?>());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// now calculate the positions of each of the screenshot images on the preview
|
screenBorderColors.Add(previewStyle.ScreenStyle.BorderStyle.Color);
|
||||||
builder.ScreenshotBounds = builder.Screens
|
}
|
||||||
|
|
||||||
|
// convert the colors into screen styles
|
||||||
|
// note - only create a new ScreenStyle if the border color is different.
|
||||||
|
// (this is to preserve a reference to ScreenStyle.Empty - and
|
||||||
|
// "ScreenStyle.IsEmpty == true" - if the new border color is Transparent)
|
||||||
|
return screenBorderColors
|
||||||
|
.Take(screenCount)
|
||||||
.Select(
|
.Select(
|
||||||
screen => LayoutHelper.GetBoxBoundsFromOuterBounds(
|
color => previewStyle.ScreenStyle.BorderStyle.Color == color
|
||||||
screen
|
? previewStyle.ScreenStyle
|
||||||
.Offset(builder.VirtualScreen.Location.ToSize().Invert())
|
: new BoxStyle(
|
||||||
.Scale(scalingRatio)
|
previewStyle.ScreenStyle.MarginStyle,
|
||||||
.Offset(builder.PreviewBounds.ContentBounds.Location.ToSize())
|
previewStyle.ScreenStyle.BorderStyle.WithColor(color),
|
||||||
.Round(),
|
previewStyle.ScreenStyle.PaddingStyle,
|
||||||
previewStyle.ScreenStyle))
|
previewStyle.ScreenStyle.BackgroundStyle));
|
||||||
.ToList();
|
|
||||||
|
|
||||||
return builder.Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RectangleInfo GetCombinedScreenBounds(List<RectangleInfo> screens)
|
|
||||||
{
|
|
||||||
return screens.Skip(1).Aggregate(
|
|
||||||
seed: screens.First(),
|
|
||||||
(bounds, screen) => bounds.Union(screen));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the bounds of the various areas of a box, given the content bounds and the box style.
|
/// Arranges the device layouts into a non-overlapping grid and scales them to fit inside the specified content bounds.
|
||||||
/// Starts with the content bounds and works outward, enlarging the content bounds by the padding, border, and margin sizes to calculate the outer bounds of the box.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="contentBounds">The content bounds of the box.</param>
|
internal static void ArrangeAndScaleDeviceLayouts(FormViewModel.Builder formLayout)
|
||||||
/// <param name="boxStyle">The style of the box, which includes the sizes of the margin, border, and padding areas.</param>
|
|
||||||
/// <returns>A <see cref="BoxBounds"/> object that represents the bounds of the different areas of the box.</returns>
|
|
||||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="contentBounds"/> or <paramref name="boxStyle"/> is null.</exception>
|
|
||||||
/// <exception cref="ArgumentException">Thrown when any of the styles in <paramref name="boxStyle"/> is null.</exception>
|
|
||||||
public static BoxBounds GetBoxBoundsFromContentBounds(
|
|
||||||
RectangleInfo contentBounds,
|
|
||||||
BoxStyle boxStyle)
|
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(contentBounds);
|
var deviceLayouts = formLayout.CanvasLayout?.DeviceLayouts
|
||||||
ArgumentNullException.ThrowIfNull(boxStyle);
|
?? throw new InvalidOperationException();
|
||||||
if (boxStyle.PaddingStyle == null || boxStyle.BorderStyle == null || boxStyle.MarginStyle == null)
|
var contentBounds = formLayout.CanvasLayout.CanvasBounds?.ContentBounds
|
||||||
|
?? throw new InvalidOperationException();
|
||||||
|
|
||||||
|
// build an initial grid of devices at 100% scale. the grid is currently a single row
|
||||||
|
// of cells high with all the devices arranged left to right for the time being.
|
||||||
|
// we'll enhance this to allow for multiple rows later to support the
|
||||||
|
// Mouse Without Borders "square" arrangement.
|
||||||
|
var gridRowCount = 1;
|
||||||
|
var gridColumnCount = deviceLayouts.Count;
|
||||||
|
var deviceGrid = new DeviceViewModel.Builder[gridRowCount, gridColumnCount];
|
||||||
|
for (var columnIndex = 0; columnIndex < gridColumnCount; columnIndex++)
|
||||||
{
|
{
|
||||||
throw new ArgumentException(null, nameof(boxStyle));
|
deviceGrid[0, columnIndex] = deviceLayouts[columnIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
var paddingBounds = contentBounds.Enlarge(boxStyle.PaddingStyle);
|
// if a device has zero screens (or if we failed to connect and read screen layouts) we
|
||||||
var borderBounds = paddingBounds.Enlarge(boxStyle.BorderStyle);
|
// still want to allocate space in the full size grid so that it doesn't disappear into
|
||||||
var marginBounds = borderBounds.Enlarge(boxStyle.MarginStyle);
|
// a zero width or height cell.
|
||||||
var outerBounds = marginBounds;
|
var fullSizeGridCellMinSize = new Size(1024, 768);
|
||||||
return new(
|
|
||||||
outerBounds, marginBounds, borderBounds, paddingBounds, contentBounds);
|
// find the size of the tallest device in each row and the widest device in each column.
|
||||||
|
// this tells us how tall each row needs to be and how wide each column needs to be.
|
||||||
|
var fullSizeGridCellBounds = new RectangleInfo[gridRowCount, gridColumnCount];
|
||||||
|
var fullSizeRowHeights = new decimal[gridRowCount];
|
||||||
|
var fullSizeColumnWidths = new decimal[gridColumnCount];
|
||||||
|
for (var rowIndex = 0; rowIndex < gridRowCount; rowIndex++)
|
||||||
|
{
|
||||||
|
for (var columnIndex = 0; columnIndex < gridColumnCount; columnIndex++)
|
||||||
|
{
|
||||||
|
var deviceInfo = deviceGrid[rowIndex, columnIndex].DeviceInfo ?? throw new InvalidOperationException();
|
||||||
|
var deviceBounds = deviceInfo.GetCombinedDisplayArea();
|
||||||
|
|
||||||
|
// if the device bounds are empty, it probably means there aren't any
|
||||||
|
// screens available for this device so we'll use the minimum size instead
|
||||||
|
// for the grid cell size
|
||||||
|
var fullSizeGridCell = deviceBounds.IsEmpty
|
||||||
|
? deviceBounds.Resize(
|
||||||
|
width: fullSizeGridCellMinSize.Width,
|
||||||
|
height: fullSizeGridCellMinSize.Height)
|
||||||
|
: deviceBounds;
|
||||||
|
|
||||||
|
fullSizeGridCellBounds[rowIndex, columnIndex] = fullSizeGridCell;
|
||||||
|
fullSizeRowHeights[rowIndex] = Math.Max(fullSizeRowHeights[rowIndex], fullSizeGridCell.Height);
|
||||||
|
fullSizeColumnWidths[columnIndex] = Math.Max(fullSizeColumnWidths[columnIndex], fullSizeGridCell.Width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// use the row heights to calculate the absolute coordinates of each grid row
|
||||||
|
var fullSizeRowCoordinates = new decimal[gridRowCount];
|
||||||
|
fullSizeRowCoordinates[0] = 0;
|
||||||
|
for (var rowIndex = 1; rowIndex < gridRowCount; rowIndex++)
|
||||||
|
{
|
||||||
|
fullSizeRowCoordinates[rowIndex] = fullSizeRowCoordinates[rowIndex - 1] + fullSizeRowHeights[rowIndex - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// use the column widths to calculate the absolute coordinates of each grid column
|
||||||
|
var fullSizeColumnCoordinates = new decimal[gridColumnCount];
|
||||||
|
fullSizeColumnCoordinates[0] = 0;
|
||||||
|
for (var columnIndex = 1; columnIndex < gridColumnCount; columnIndex++)
|
||||||
|
{
|
||||||
|
fullSizeColumnCoordinates[columnIndex] = fullSizeColumnCoordinates[columnIndex - 1] + fullSizeColumnWidths[columnIndex - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the full size bounds for the entire grid
|
||||||
|
var fullSizeGridBounds = new RectangleInfo(
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: fullSizeColumnCoordinates[gridColumnCount - 1] + fullSizeColumnWidths[gridColumnCount - 1],
|
||||||
|
height: fullSizeRowCoordinates[gridRowCount - 1] + fullSizeRowHeights[gridRowCount - 1]);
|
||||||
|
|
||||||
|
// work out the scaling factor that will fit the full size grid into the content bounds
|
||||||
|
var scaledGridSize = fullSizeGridBounds.Size
|
||||||
|
.ScaleToFit(contentBounds.Size, out var scalingFactor);
|
||||||
|
|
||||||
|
// scale the row and column coordinates by the factor. round coordinates to ensure they
|
||||||
|
// don't overlap when rendered as pixel dimensions in the image. we'll also add a final
|
||||||
|
// "fencepost" coordinate to simplify building the end cell of each row and column in the
|
||||||
|
// scaled grid.
|
||||||
|
var scaledRowCoordinates = fullSizeRowCoordinates
|
||||||
|
.Select(fullSizeRowCoordinate => Math.Round(fullSizeRowCoordinate * scalingFactor))
|
||||||
|
.Concat([scaledGridSize.Height])
|
||||||
|
.Select(scaledRowCoordinate => scaledRowCoordinate + contentBounds.Top)
|
||||||
|
.ToArray();
|
||||||
|
var scaledColumnCoordinates = fullSizeColumnCoordinates
|
||||||
|
.Select(fullSizeColumnCoordinate => Math.Round(fullSizeColumnCoordinate * scalingFactor))
|
||||||
|
.Concat([scaledGridSize.Width])
|
||||||
|
.Select(scaledColumnCoordinate => scaledColumnCoordinate + contentBounds.Left)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
// arrange the device layouts into their scaled grid positions
|
||||||
|
for (var rowIndex = 0; rowIndex < gridRowCount; rowIndex++)
|
||||||
|
{
|
||||||
|
for (var columnIndex = 0; columnIndex < gridColumnCount; columnIndex++)
|
||||||
|
{
|
||||||
|
var fullSizeCellSize = fullSizeGridCellBounds[rowIndex, columnIndex].Size;
|
||||||
|
if ((fullSizeCellSize.Width == 0) || (fullSizeCellSize.Height == 0))
|
||||||
|
{
|
||||||
|
// if the full size bounds are zero width or height, it probably means there aren't any
|
||||||
|
// screens available for this device so we can't scale it to fit inside the grid cell
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// work out the scaled coordinates for this grid cell
|
||||||
|
var scaledGridCellBounds = new RectangleInfo(
|
||||||
|
x: scaledColumnCoordinates[columnIndex],
|
||||||
|
y: scaledRowCoordinates[rowIndex],
|
||||||
|
width: scaledColumnCoordinates[columnIndex + 1] - scaledColumnCoordinates[columnIndex],
|
||||||
|
height: scaledRowCoordinates[rowIndex + 1] - scaledRowCoordinates[rowIndex]);
|
||||||
|
|
||||||
|
// scale and center the device layout to fit inside the grid cell
|
||||||
|
var deviceLayout = deviceGrid[rowIndex, columnIndex];
|
||||||
|
deviceLayout.DeviceBounds = BoxBounds.CreateFromOuterBounds(
|
||||||
|
outerBounds: new RectangleInfo(
|
||||||
|
size: fullSizeCellSize.ScaleToFit(scaledGridCellBounds.Size))
|
||||||
|
.Center(scaledGridCellBounds.Midpoint)
|
||||||
|
.Round()
|
||||||
|
.Intersect(scaledGridCellBounds.Round()),
|
||||||
|
boxStyle: deviceLayout.DeviceStyle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify that we're pixel-perfect on the positions
|
||||||
|
var scaledDeviceBounds = RectangleInfo.Union(
|
||||||
|
deviceLayouts
|
||||||
|
.Select(deviceLayout => deviceLayout.DeviceBounds.OuterBounds));
|
||||||
|
Debug.Assert(
|
||||||
|
(scaledDeviceBounds.Width == contentBounds.Width) || (scaledDeviceBounds.Height == contentBounds.Height),
|
||||||
|
string.Join(
|
||||||
|
"\r\n",
|
||||||
|
$"{nameof(ArrangeAndScaleDeviceLayouts)} - scaled device layout does not perfectly fill content bounds:",
|
||||||
|
$"scaled grid = '{scaledDeviceBounds}'",
|
||||||
|
$"content bounds = '{contentBounds}'"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the bounds of the various areas of a box, given the outer bounds and the box style.
|
/// Arranges the screen layouts inside their respective device cells and
|
||||||
/// This method starts with the outer bounds and works inward, shrinking the outer bounds by the margin, border, and padding sizes to calculate the content bounds of the box.
|
/// scales them to fit inside their parent device layouts.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="outerBounds">The outer bounds of the box.</param>
|
internal static void ArrangeAndScaleScreenLayouts(FormViewModel.Builder formLayout)
|
||||||
/// <param name="boxStyle">The style of the box, which includes the sizes of the margin, border, and padding areas.</param>
|
|
||||||
/// <returns>A <see cref="BoxBounds"/> object that represents the bounds of the different areas of the box.</returns>
|
|
||||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="outerBounds"/> or <paramref name="boxStyle"/> is null.</exception>
|
|
||||||
/// <exception cref="ArgumentException">Thrown when any of the styles in <paramref name="boxStyle"/> is null.</exception>
|
|
||||||
public static BoxBounds GetBoxBoundsFromOuterBounds(
|
|
||||||
RectangleInfo outerBounds,
|
|
||||||
BoxStyle boxStyle)
|
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(outerBounds);
|
var deviceLayouts = formLayout.CanvasLayout?.DeviceLayouts
|
||||||
ArgumentNullException.ThrowIfNull(boxStyle);
|
?? throw new InvalidOperationException();
|
||||||
if (outerBounds == null || boxStyle.MarginStyle == null || boxStyle.BorderStyle == null || boxStyle.PaddingStyle == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentException(null, nameof(boxStyle));
|
|
||||||
}
|
|
||||||
|
|
||||||
var marginBounds = outerBounds;
|
foreach (var deviceLayout in deviceLayouts)
|
||||||
var borderBounds = marginBounds.Shrink(boxStyle.MarginStyle);
|
{
|
||||||
var paddingBounds = borderBounds.Shrink(boxStyle.BorderStyle);
|
var deviceInfo = deviceLayout.DeviceInfo ?? throw new InvalidOperationException();
|
||||||
var contentBounds = paddingBounds.Shrink(boxStyle.PaddingStyle);
|
var screenLayouts = deviceLayout.ScreenLayouts ?? throw new InvalidOperationException();
|
||||||
return new(
|
if (screenLayouts.Count == 0)
|
||||||
outerBounds, marginBounds, borderBounds, paddingBounds, contentBounds);
|
{
|
||||||
|
// nothing to arrange or scale, and we'll get a divide by zero error
|
||||||
|
// in ScaleToFitRatio if we don't quit early
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// work out the scaling factor that will fit the screen layouts into the content bounds
|
||||||
|
var fullSizeDisplayArea = deviceInfo.GetCombinedDisplayArea();
|
||||||
|
var contentBounds = deviceLayout.DeviceBounds.ContentBounds;
|
||||||
|
var scalingFactor = fullSizeDisplayArea.Size.ScaleToFitRatio(contentBounds.Size);
|
||||||
|
|
||||||
|
foreach (var screenLayout in screenLayouts)
|
||||||
|
{
|
||||||
|
var screenInfo = screenLayout.ScreenInfo ?? throw new InvalidOperationException();
|
||||||
|
screenLayout.ScreenBounds = BoxBounds.CreateFromOuterBounds(
|
||||||
|
outerBounds: screenInfo.DisplayArea
|
||||||
|
.Offset(-fullSizeDisplayArea.Left, -fullSizeDisplayArea.Top)
|
||||||
|
.Scale(scalingFactor)
|
||||||
|
.Offset(contentBounds.Left, contentBounds.Top)
|
||||||
|
.Round()
|
||||||
|
.Intersect(contentBounds),
|
||||||
|
boxStyle: screenLayout.ScreenStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify that we're pixel-perfect on the positions
|
||||||
|
var scaledScreenBounds = RectangleInfo.Union(
|
||||||
|
screenLayouts
|
||||||
|
.Select(screenLayout => screenLayout.ScreenBounds.OuterBounds));
|
||||||
|
Debug.Assert(
|
||||||
|
(scaledScreenBounds.Width == contentBounds.Width) || (scaledScreenBounds.Height == contentBounds.Height),
|
||||||
|
string.Join(
|
||||||
|
"\r\n",
|
||||||
|
$"{nameof(ArrangeAndScaleScreenLayouts)} - scaled screen layouts do not perfectly fill content bounds:",
|
||||||
|
$"scaled grid = '{scaledScreenBounds}'",
|
||||||
|
$"content bounds = '{contentBounds}'"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void ArrangeAndResizeCanvasLayout(FormViewModel.Builder formLayout)
|
||||||
|
{
|
||||||
|
var canvasLayout = formLayout.CanvasLayout ?? throw new InvalidOperationException();
|
||||||
|
|
||||||
|
// work out how big the canvas needs to be in order to contain the device layouts
|
||||||
|
var canvasContentBounds = RectangleInfo.Union(
|
||||||
|
(canvasLayout.DeviceLayouts ?? throw new InvalidOperationException())
|
||||||
|
.Select(deviceLayout => deviceLayout.DeviceBounds.OuterBounds));
|
||||||
|
var canvasOuterBounds = BoxBounds.CreateFromContentBounds(
|
||||||
|
contentBounds: canvasContentBounds,
|
||||||
|
boxStyle: formLayout.CanvasLayout.CanvasStyle);
|
||||||
|
|
||||||
|
// move the canvas into position
|
||||||
|
var positionedOuterBounds = canvasOuterBounds.OuterBounds.MoveTo(
|
||||||
|
(canvasLayout.CanvasBounds ?? throw new InvalidOperationException())
|
||||||
|
.OuterBounds.Location);
|
||||||
|
|
||||||
|
canvasLayout.CanvasBounds = BoxBounds.CreateFromOuterBounds(
|
||||||
|
outerBounds: positionedOuterBounds,
|
||||||
|
boxStyle: formLayout.CanvasLayout.CanvasStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void ArrangeAndResizeFormLayout(FormViewModel.Builder formLayout, ScreenInfo activatedScreen, PointInfo activatedLocation)
|
||||||
|
{
|
||||||
|
var canvasOuterBounds = formLayout.CanvasLayout?.CanvasBounds?.OuterBounds
|
||||||
|
?? throw new InvalidOperationException();
|
||||||
|
|
||||||
|
// resize and center the form on the activated location
|
||||||
|
formLayout.FormBounds = canvasOuterBounds
|
||||||
|
.Center(activatedLocation)
|
||||||
|
.MoveInside(activatedScreen.DisplayArea);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,12 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using MouseJump.Common.Models.Drawing;
|
using MouseJump.Common.Interop;
|
||||||
using MouseJump.Common.NativeMethods;
|
using MouseJump.Models.Drawing;
|
||||||
using static MouseJump.Common.NativeMethods.Core;
|
|
||||||
using static MouseJump.Common.NativeMethods.User32;
|
using Windows.Win32;
|
||||||
|
using Windows.Win32.UI.Input.KeyboardAndMouse;
|
||||||
|
using Windows.Win32.UI.WindowsAndMessaging;
|
||||||
|
|
||||||
namespace MouseJump.Common.Helpers;
|
namespace MouseJump.Common.Helpers;
|
||||||
|
|
||||||
@@ -35,19 +37,9 @@ public static class MouseHelper
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static PointInfo GetCursorPosition()
|
public static PointInfo GetCursorPosition()
|
||||||
{
|
{
|
||||||
var lpPoint = new LPPOINT(new POINT(0, 0));
|
var result = PInvoke.GetCursorPos(out var point);
|
||||||
var result = User32.GetCursorPos(lpPoint);
|
ResultHandler.ThrowIfZero(result: result, getLastError: true, memberName: nameof(PInvoke.GetCursorPos));
|
||||||
if (!result)
|
return new(point.X, point.Y);
|
||||||
{
|
|
||||||
throw new Win32Exception(
|
|
||||||
Marshal.GetLastWin32Error());
|
|
||||||
}
|
|
||||||
|
|
||||||
var point = lpPoint.ToStructure();
|
|
||||||
lpPoint.Free();
|
|
||||||
|
|
||||||
return new PointInfo(
|
|
||||||
point.x, point.y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -56,43 +48,52 @@ public static class MouseHelper
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// See https://github.com/mikeclayton/FancyMouse/pull/3
|
/// See https://github.com/mikeclayton/FancyMouse/pull/3
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static void SetCursorPosition(PointInfo location)
|
public static void SetCursorPosition(PointInfo position)
|
||||||
|
{
|
||||||
|
MouseHelper.SetCursorPositionInternal(position);
|
||||||
|
|
||||||
|
// temporary workaround for issue #1273
|
||||||
|
MouseHelper.SimulateMouseMovementEvent(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetCursorPositionInternal(PointInfo position)
|
||||||
{
|
{
|
||||||
// set the new cursor position *twice* - the cursor sometimes end up in
|
// set the new cursor position *twice* - the cursor sometimes end up in
|
||||||
// the wrong place if we try to cross the dead space between non-aligned
|
// the wrong place if we try to cross the dead space between non-aligned
|
||||||
// monitors - e.g. when trying to move the cursor from (a) to (b) we can
|
// monitors - e.g. when trying to move the cursor from (a) to (b) through
|
||||||
// *sometimes* - for no clear reason - end up at (c) instead.
|
// the dotted area we can *sometimes* - for no clear reason - end up at
|
||||||
|
// (c) instead.
|
||||||
//
|
//
|
||||||
// +----------------+
|
// ..........+----------------+
|
||||||
// |(c) (b) |
|
// ..........|(c) (b) |
|
||||||
// | |
|
// ..........| |
|
||||||
// | |
|
// ..........| |
|
||||||
// | |
|
// ..........| |
|
||||||
// +---------+ |
|
// +---------+ |
|
||||||
// | (a) | |
|
// | (a) | |
|
||||||
// +---------+----------------+
|
// +---------+----------------+
|
||||||
//
|
//
|
||||||
// setting the position again seems to fix this and moves the
|
// setting the position more than once seems to fix this and moves the
|
||||||
// cursor to the expected location (b)
|
// cursor to the expected location (b)
|
||||||
var target = location.ToPoint();
|
var targetPosition = position.ToPoint();
|
||||||
for (var i = 0; i < 2; i++)
|
for (var i = 0; i < 2; i++)
|
||||||
{
|
{
|
||||||
var result = User32.SetCursorPos(target.X, target.Y);
|
// SetCursorPos has been known to return zero (i.e. an error),
|
||||||
if (!result)
|
// with GetLastError also returning zero to indicate success
|
||||||
|
var result1 = PInvoke.SetCursorPos(targetPosition.X, targetPosition.Y);
|
||||||
|
if (result1 == 0)
|
||||||
{
|
{
|
||||||
throw new Win32Exception(
|
var lastError = Marshal.GetLastPInvokeError();
|
||||||
Marshal.GetLastWin32Error());
|
ResultHandler.HandleResult(result: result1, success: lastError == 0, lastError: lastError, memberName: nameof(PInvoke.SetCursorPos));
|
||||||
}
|
}
|
||||||
|
|
||||||
var current = MouseHelper.GetCursorPosition();
|
var result2 = PInvoke.GetCursorPos(out var currentPosition);
|
||||||
if ((current.X == target.X) || (current.Y == target.Y))
|
ResultHandler.ThrowIfZero(result: result2, getLastError: true, memberName: nameof(PInvoke.GetCursorPos));
|
||||||
|
if ((currentPosition.X == position.X) && (currentPosition.Y == position.Y))
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// temporary workaround for issue #1273
|
|
||||||
MouseHelper.SimulateMouseMovementEvent(location);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -104,27 +105,31 @@ public static class MouseHelper
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private static void SimulateMouseMovementEvent(PointInfo location)
|
private static void SimulateMouseMovementEvent(PointInfo location)
|
||||||
{
|
{
|
||||||
var inputs = new User32.INPUT[]
|
var inputs = new INPUT[]
|
||||||
{
|
{
|
||||||
new(
|
new()
|
||||||
type: INPUT_TYPE.INPUT_MOUSE,
|
{
|
||||||
data: new INPUT.DUMMYUNIONNAME(
|
type = INPUT_TYPE.INPUT_MOUSE,
|
||||||
mi: new MOUSEINPUT(
|
Anonymous = new()
|
||||||
dx: (int)MouseHelper.CalculateAbsoluteCoordinateX(location.X),
|
{
|
||||||
dy: (int)MouseHelper.CalculateAbsoluteCoordinateY(location.Y),
|
mi = new MOUSEINPUT
|
||||||
mouseData: 0,
|
{
|
||||||
dwFlags: MOUSE_EVENT_FLAGS.MOUSEEVENTF_MOVE | MOUSE_EVENT_FLAGS.MOUSEEVENTF_ABSOLUTE,
|
dx = (int)MouseHelper.CalculateAbsoluteCoordinateX(location.X),
|
||||||
time: 0,
|
dy = (int)MouseHelper.CalculateAbsoluteCoordinateY(location.Y),
|
||||||
dwExtraInfo: ULONG_PTR.Null))),
|
mouseData = 0,
|
||||||
|
dwFlags = MOUSE_EVENT_FLAGS.MOUSEEVENTF_MOVE | MOUSE_EVENT_FLAGS.MOUSEEVENTF_ABSOLUTE,
|
||||||
|
time = 0,
|
||||||
|
dwExtraInfo = default,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
var result = User32.SendInput(
|
|
||||||
(UINT)inputs.Length,
|
var cbSize = Marshal.SizeOf<INPUT>();
|
||||||
new LPINPUT(inputs),
|
var result = PInvoke.SendInput(inputs, cbSize);
|
||||||
INPUT.Size * inputs.Length);
|
|
||||||
if (result != inputs.Length)
|
if (result != inputs.Length)
|
||||||
{
|
{
|
||||||
throw new Win32Exception(
|
ResultHandler.HandleFailure(result: result, getLastError: true, memberName: nameof(PInvoke.SendInput));
|
||||||
Marshal.GetLastWin32Error());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,13 +137,17 @@ public static class MouseHelper
|
|||||||
{
|
{
|
||||||
// If MOUSEEVENTF_ABSOLUTE value is specified, dx and dy contain normalized absolute coordinates between 0 and 65,535.
|
// If MOUSEEVENTF_ABSOLUTE value is specified, dx and dy contain normalized absolute coordinates between 0 and 65,535.
|
||||||
// see https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-mouseinput
|
// see https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-mouseinput
|
||||||
return (x * 65535) / User32.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXSCREEN);
|
var result = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXSCREEN);
|
||||||
|
ResultHandler.ThrowIfZero(result: result, getLastError: false, memberName: nameof(PInvoke.GetSystemMetrics));
|
||||||
|
return (x * 65535) / result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static decimal CalculateAbsoluteCoordinateY(decimal y)
|
private static decimal CalculateAbsoluteCoordinateY(decimal y)
|
||||||
{
|
{
|
||||||
// If MOUSEEVENTF_ABSOLUTE value is specified, dx and dy contain normalized absolute coordinates between 0 and 65,535.
|
// If MOUSEEVENTF_ABSOLUTE value is specified, dx and dy contain normalized absolute coordinates between 0 and 65,535.
|
||||||
// see https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-mouseinput
|
// see https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-mouseinput
|
||||||
return (y * 65535) / User32.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYSCREEN);
|
var result = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYSCREEN);
|
||||||
|
ResultHandler.ThrowIfZero(result: result, getLastError: false, memberName: nameof(PInvoke.GetSystemMetrics));
|
||||||
|
return (y * 65535) / result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,16 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System.ComponentModel;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using MouseJump.Common.Models.Drawing;
|
using MouseJump.Common.Interop;
|
||||||
using MouseJump.Common.NativeMethods;
|
using MouseJump.Models.Display;
|
||||||
using static MouseJump.Common.NativeMethods.Core;
|
using MouseJump.Models.Drawing;
|
||||||
using static MouseJump.Common.NativeMethods.User32;
|
|
||||||
|
using Windows.Win32;
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
|
using Windows.Win32.Graphics.Gdi;
|
||||||
|
using Windows.Win32.UI.WindowsAndMessaging;
|
||||||
|
|
||||||
namespace MouseJump.Common.Helpers;
|
namespace MouseJump.Common.Helpers;
|
||||||
|
|
||||||
@@ -20,49 +24,47 @@ public static class ScreenHelper
|
|||||||
private static RectangleInfo GetVirtualScreen()
|
private static RectangleInfo GetVirtualScreen()
|
||||||
{
|
{
|
||||||
return new(
|
return new(
|
||||||
User32.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_XVIRTUALSCREEN),
|
PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_XVIRTUALSCREEN),
|
||||||
User32.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_YVIRTUALSCREEN),
|
PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_YVIRTUALSCREEN),
|
||||||
User32.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXVIRTUALSCREEN),
|
PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXVIRTUALSCREEN),
|
||||||
User32.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYVIRTUALSCREEN));
|
PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYVIRTUALSCREEN));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<ScreenInfo> GetAllScreens()
|
public static IEnumerable<ScreenInfo> GetAllScreens()
|
||||||
{
|
{
|
||||||
// enumerate the monitors attached to the system
|
// enumerate the monitors attached to the system
|
||||||
var hMonitors = new List<HMONITOR>();
|
var hMonitors = new List<HMONITOR>();
|
||||||
var callback = new User32.MONITORENUMPROC(
|
unsafe
|
||||||
(hMonitor, hdcMonitor, lprcMonitor, dwData) =>
|
|
||||||
{
|
|
||||||
hMonitors.Add(hMonitor);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
var result = User32.EnumDisplayMonitors(HDC.Null, LPCRECT.Null, callback, LPARAM.Null);
|
|
||||||
if (!result)
|
|
||||||
{
|
{
|
||||||
throw new Win32Exception(
|
var callback = new MONITORENUMPROC(
|
||||||
result.Value,
|
(hMonitor, hdcMonitor, lprcMonitor, dwData) =>
|
||||||
$"{nameof(User32.EnumDisplayMonitors)} failed with return code {result.Value}");
|
{
|
||||||
|
hMonitors.Add(hMonitor);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
var result = PInvoke.EnumDisplayMonitors(HDC.Null, null, callback, (LPARAM)0);
|
||||||
|
if (result == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("failed to enumerate monitors");
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevent callback from being collected during the enumeration
|
||||||
|
GC.KeepAlive(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
// get detailed info about each monitor
|
// get detailed info about each monitor
|
||||||
|
var monitorInfo = new MONITORINFO
|
||||||
|
{
|
||||||
|
cbSize = (uint)Marshal.SizeOf<MONITORINFO>(),
|
||||||
|
};
|
||||||
foreach (var hMonitor in hMonitors)
|
foreach (var hMonitor in hMonitors)
|
||||||
{
|
{
|
||||||
var monitorInfoPtr = new LPMONITORINFO(
|
var result = PInvoke.GetMonitorInfo(hMonitor, ref monitorInfo);
|
||||||
new MONITORINFO((DWORD)MONITORINFO.Size, RECT.Empty, RECT.Empty, 0));
|
ResultHandler.ThrowIfZero(result: result, getLastError: true, memberName: nameof(PInvoke.GetMonitorInfo));
|
||||||
result = User32.GetMonitorInfoW(hMonitor, monitorInfoPtr);
|
|
||||||
if (!result)
|
|
||||||
{
|
|
||||||
throw new Win32Exception(
|
|
||||||
result.Value,
|
|
||||||
$"{nameof(User32.GetMonitorInfoW)} failed with return code {result.Value}");
|
|
||||||
}
|
|
||||||
|
|
||||||
var monitorInfo = monitorInfoPtr.ToStructure();
|
|
||||||
monitorInfoPtr.Free();
|
|
||||||
|
|
||||||
yield return new ScreenInfo(
|
yield return new ScreenInfo(
|
||||||
handle: hMonitor,
|
handle: hMonitor,
|
||||||
primary: monitorInfo.dwFlags.HasFlag(User32.MONITOR_INFO_FLAGS.MONITORINFOF_PRIMARY),
|
primary: (monitorInfo.dwFlags & PInvoke.MONITORINFOF_PRIMARY) != 0,
|
||||||
displayArea: new RectangleInfo(
|
displayArea: new RectangleInfo(
|
||||||
monitorInfo.rcMonitor.left,
|
monitorInfo.rcMonitor.left,
|
||||||
monitorInfo.rcMonitor.top,
|
monitorInfo.rcMonitor.top,
|
||||||
@@ -81,9 +83,9 @@ public static class ScreenHelper
|
|||||||
PointInfo pt)
|
PointInfo pt)
|
||||||
{
|
{
|
||||||
// get the monitor handle from the point
|
// get the monitor handle from the point
|
||||||
var hMonitor = User32.MonitorFromPoint(
|
var hMonitor = PInvoke.MonitorFromPoint(
|
||||||
new((int)pt.X, (int)pt.Y),
|
new((int)pt.X, (int)pt.Y),
|
||||||
User32.MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST);
|
MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST);
|
||||||
if (hMonitor.IsNull)
|
if (hMonitor.IsNull)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"no monitor found for point {pt}");
|
throw new InvalidOperationException($"no monitor found for point {pt}");
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using MouseJump.Common.Models.Drawing;
|
using System.Drawing;
|
||||||
using MouseJump.Common.Models.Styles;
|
|
||||||
|
using MouseJump.Models.Drawing;
|
||||||
|
using MouseJump.Models.Styles;
|
||||||
|
|
||||||
namespace MouseJump.Common.Helpers;
|
namespace MouseJump.Common.Helpers;
|
||||||
|
|
||||||
@@ -46,7 +48,13 @@ public static class StyleHelper
|
|||||||
color1: Color.MidnightBlue,
|
color1: Color.MidnightBlue,
|
||||||
color2: Color.MidnightBlue
|
color2: Color.MidnightBlue
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
extraColors: new[]
|
||||||
|
{
|
||||||
|
Color.Red,
|
||||||
|
Color.Blue,
|
||||||
|
Color.Green,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -86,7 +94,13 @@ public static class StyleHelper
|
|||||||
color1: Color.MidnightBlue,
|
color1: Color.MidnightBlue,
|
||||||
color2: Color.MidnightBlue
|
color2: Color.MidnightBlue
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
extraColors: new[]
|
||||||
|
{
|
||||||
|
Color.Red,
|
||||||
|
Color.Blue,
|
||||||
|
Color.Green,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
public static PreviewStyle WithCanvasSize(this PreviewStyle previewStyle, SizeInfo canvasSize)
|
public static PreviewStyle WithCanvasSize(this PreviewStyle previewStyle, SizeInfo canvasSize)
|
||||||
@@ -96,6 +110,7 @@ public static class StyleHelper
|
|||||||
return new PreviewStyle(
|
return new PreviewStyle(
|
||||||
canvasSize: canvasSize,
|
canvasSize: canvasSize,
|
||||||
canvasStyle: previewStyle.CanvasStyle,
|
canvasStyle: previewStyle.CanvasStyle,
|
||||||
screenStyle: previewStyle.ScreenStyle);
|
screenStyle: previewStyle.ScreenStyle,
|
||||||
|
extraColors: previewStyle.ExtraColors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
111
src/modules/MouseUtils/MouseJump.Common/Helpers/Win32Helper.cs
Normal file
111
src/modules/MouseUtils/MouseJump.Common/Helpers/Win32Helper.cs
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
using MouseJump.Common.Interop;
|
||||||
|
|
||||||
|
using Windows.Win32;
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
|
using Windows.Win32.Graphics.Gdi;
|
||||||
|
using Windows.Win32.UI.WindowsAndMessaging;
|
||||||
|
|
||||||
|
namespace MouseJump.Common.Helpers;
|
||||||
|
|
||||||
|
public static class Win32Helper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new "message only" window.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// A message-only window is a window that is not visible and does not have a user interface.
|
||||||
|
/// It exists solely to receive and process messages.
|
||||||
|
/// These windows are used for background tasks that need to handle messages without displaying any graphical elements to the user.
|
||||||
|
/// </remarks>
|
||||||
|
public static Win32Window CreateMessageOnlyWindow(string className, string windowName, Win32WindowProc.WindowProcDelegate windowProcDelegate)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNullOrEmpty(className);
|
||||||
|
ArgumentNullException.ThrowIfNullOrEmpty(windowName);
|
||||||
|
ArgumentNullException.ThrowIfNull(windowProcDelegate);
|
||||||
|
|
||||||
|
var windowClass = Win32Helper.RegisterWindowClass(className, windowProcDelegate);
|
||||||
|
var window = Win32Helper.CreateWindow(windowClass, windowName);
|
||||||
|
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Win32WindowClass RegisterWindowClass(string className, Win32WindowProc.WindowProcDelegate windowProcDelegate)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNullOrEmpty(className);
|
||||||
|
ArgumentNullException.ThrowIfNull(windowProcDelegate);
|
||||||
|
|
||||||
|
using var hModule = PInvoke.GetModuleHandle(null);
|
||||||
|
var hInstance = (HINSTANCE)hModule.DangerousGetHandle();
|
||||||
|
|
||||||
|
// register the window class
|
||||||
|
// see https://stackoverflow.com/a/30992796/3156906
|
||||||
|
var windowProc = new Win32WindowProc(windowProcDelegate);
|
||||||
|
var classAtom = default(ushort);
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
fixed (char* classNamePtr = className)
|
||||||
|
{
|
||||||
|
var wndClass = new WNDCLASSEXW
|
||||||
|
{
|
||||||
|
cbSize = (uint)Marshal.SizeOf<WNDCLASSEXW>(),
|
||||||
|
style = 0,
|
||||||
|
lpfnWndProc = windowProc.WndProcDelegate,
|
||||||
|
cbClsExtra = 0,
|
||||||
|
cbWndExtra = 0,
|
||||||
|
hInstance = hInstance,
|
||||||
|
hIcon = HICON.Null,
|
||||||
|
hCursor = HCURSOR.Null,
|
||||||
|
hbrBackground = HBRUSH.Null,
|
||||||
|
lpszMenuName = default,
|
||||||
|
lpszClassName = new PCWSTR(classNamePtr),
|
||||||
|
hIconSm = HICON.Null,
|
||||||
|
};
|
||||||
|
classAtom = PInvoke.RegisterClassEx(wndClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultHandler.ThrowIfZero(result: classAtom, getLastError: true, memberName: nameof(PInvoke.RegisterClassEx));
|
||||||
|
|
||||||
|
var windowClass = new Win32WindowClass(classAtom, className, windowProc);
|
||||||
|
return windowClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Win32Window CreateWindow(Win32WindowClass windowClass, string windowName)
|
||||||
|
{
|
||||||
|
// create the window
|
||||||
|
// see https://learn.microsoft.com/en-us/windows/win32/winmsg/window-features#message-only-windows
|
||||||
|
// https://devblogs.microsoft.com/oldnewthing/20171218-00/?p=97595
|
||||||
|
// https://stackoverflow.com/a/30992796/3156906
|
||||||
|
var hWnd = default(HWND);
|
||||||
|
|
||||||
|
using var hModule = PInvoke.GetModuleHandle(null);
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
hWnd = PInvoke.CreateWindowEx(
|
||||||
|
dwExStyle: 0,
|
||||||
|
lpClassName: windowClass.ClassName,
|
||||||
|
lpWindowName: windowName,
|
||||||
|
dwStyle: 0,
|
||||||
|
X: 0,
|
||||||
|
Y: 0,
|
||||||
|
nWidth: 300,
|
||||||
|
nHeight: 400,
|
||||||
|
hWndParent: HWND.HWND_MESSAGE, // message-only window
|
||||||
|
hMenu: null,
|
||||||
|
hInstance: hModule,
|
||||||
|
lpParam: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultHandler.ThrowIfZero(result: hWnd, getLastError: true, memberName: nameof(PInvoke.CreateWindowEx));
|
||||||
|
|
||||||
|
var window = new Win32Window(windowClass, windowName, hWnd);
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,14 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Drawing;
|
||||||
|
|
||||||
using MouseJump.Common.Models.Drawing;
|
using MouseJump.Common.Interop;
|
||||||
using MouseJump.Common.NativeMethods;
|
using MouseJump.Models.Drawing;
|
||||||
using static MouseJump.Common.NativeMethods.Core;
|
|
||||||
|
using Windows.Win32;
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
|
using Windows.Win32.Graphics.Gdi;
|
||||||
|
|
||||||
namespace MouseJump.Common.Imaging;
|
namespace MouseJump.Common.Imaging;
|
||||||
|
|
||||||
@@ -28,12 +32,12 @@ public sealed class DesktopImageRegionCopyService : IImageRegionCopyService
|
|||||||
var stopwatch = Stopwatch.StartNew();
|
var stopwatch = Stopwatch.StartNew();
|
||||||
var (desktopHwnd, desktopHdc) = DesktopImageRegionCopyService.GetDesktopDeviceContext();
|
var (desktopHwnd, desktopHdc) = DesktopImageRegionCopyService.GetDesktopDeviceContext();
|
||||||
var previewHdc = DesktopImageRegionCopyService.GetGraphicsDeviceContext(
|
var previewHdc = DesktopImageRegionCopyService.GetGraphicsDeviceContext(
|
||||||
targetGraphics, Gdi32.STRETCH_BLT_MODE.STRETCH_HALFTONE);
|
targetGraphics, STRETCH_BLT_MODE.STRETCH_HALFTONE);
|
||||||
stopwatch.Stop();
|
stopwatch.Stop();
|
||||||
|
|
||||||
var source = sourceBounds.ToRectangle();
|
var source = sourceBounds.ToRectangle();
|
||||||
var target = targetBounds.ToRectangle();
|
var target = targetBounds.ToRectangle();
|
||||||
var result = Gdi32.StretchBlt(
|
var result = PInvoke.StretchBlt(
|
||||||
previewHdc,
|
previewHdc,
|
||||||
target.X,
|
target.X,
|
||||||
target.Y,
|
target.Y,
|
||||||
@@ -44,15 +48,11 @@ public sealed class DesktopImageRegionCopyService : IImageRegionCopyService
|
|||||||
source.Y,
|
source.Y,
|
||||||
source.Width,
|
source.Width,
|
||||||
source.Height,
|
source.Height,
|
||||||
Gdi32.ROP_CODE.SRCCOPY);
|
ROP_CODE.SRCCOPY);
|
||||||
if (!result)
|
ResultHandler.ThrowIfZero(result: result, getLastError: false, memberName: nameof(PInvoke.StretchBlt));
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"{nameof(Gdi32.StretchBlt)} returned {result.Value}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// we need to release the graphics device context handle before anything
|
// we need to release the graphics device context handle before anything
|
||||||
// else tries to use the Graphics object otherwise it'll give an error
|
// else tries to use the Graphics object - otherwise it'll give an error
|
||||||
// from GDI saying "Object is currently in use elsewhere"
|
// from GDI saying "Object is currently in use elsewhere"
|
||||||
DesktopImageRegionCopyService.FreeGraphicsDeviceContext(targetGraphics, ref previewHdc);
|
DesktopImageRegionCopyService.FreeGraphicsDeviceContext(targetGraphics, ref previewHdc);
|
||||||
|
|
||||||
@@ -61,14 +61,9 @@ public sealed class DesktopImageRegionCopyService : IImageRegionCopyService
|
|||||||
|
|
||||||
private static (HWND DesktopHwnd, HDC DesktopHdc) GetDesktopDeviceContext()
|
private static (HWND DesktopHwnd, HDC DesktopHdc) GetDesktopDeviceContext()
|
||||||
{
|
{
|
||||||
var desktopHwnd = User32.GetDesktopWindow();
|
var desktopHwnd = PInvoke.GetDesktopWindow();
|
||||||
var desktopHdc = User32.GetWindowDC(desktopHwnd);
|
var desktopHdc = PInvoke.GetWindowDC(desktopHwnd);
|
||||||
if (desktopHdc.IsNull)
|
ResultHandler.HandleResult(result: desktopHdc, success: !desktopHdc.IsNull, getLastError: false, memberName: nameof(PInvoke.GetWindowDC));
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"{nameof(User32.GetWindowDC)} returned null");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (desktopHwnd, desktopHdc);
|
return (desktopHwnd, desktopHdc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,12 +71,8 @@ public sealed class DesktopImageRegionCopyService : IImageRegionCopyService
|
|||||||
{
|
{
|
||||||
if (!desktopHwnd.IsNull && !desktopHdc.IsNull)
|
if (!desktopHwnd.IsNull && !desktopHdc.IsNull)
|
||||||
{
|
{
|
||||||
var result = User32.ReleaseDC(desktopHwnd, desktopHdc);
|
var result = PInvoke.ReleaseDC(desktopHwnd, desktopHdc);
|
||||||
if (result == 0)
|
ResultHandler.ThrowIfZero(result: result, getLastError: false, memberName: nameof(PInvoke.ReleaseDC));
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"{nameof(User32.ReleaseDC)} returned {result}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
desktopHwnd = HWND.Null;
|
desktopHwnd = HWND.Null;
|
||||||
@@ -92,17 +83,11 @@ public sealed class DesktopImageRegionCopyService : IImageRegionCopyService
|
|||||||
/// Checks if the target device context handle exists, and creates a new one from the
|
/// Checks if the target device context handle exists, and creates a new one from the
|
||||||
/// specified Graphics object if not.
|
/// specified Graphics object if not.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static HDC GetGraphicsDeviceContext(Graphics graphics, Gdi32.STRETCH_BLT_MODE mode)
|
private static HDC GetGraphicsDeviceContext(Graphics graphics, STRETCH_BLT_MODE mode)
|
||||||
{
|
{
|
||||||
var graphicsHdc = (HDC)graphics.GetHdc();
|
var graphicsHdc = (HDC)graphics.GetHdc();
|
||||||
|
var result = PInvoke.SetStretchBltMode(graphicsHdc, mode);
|
||||||
var result = Gdi32.SetStretchBltMode(graphicsHdc, mode);
|
ResultHandler.ThrowIfZero(result: result, getLastError: false, memberName: nameof(PInvoke.SetStretchBltMode));
|
||||||
if (result == 0)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"{nameof(Gdi32.SetStretchBltMode)} returned {result}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return graphicsHdc;
|
return graphicsHdc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +101,7 @@ public sealed class DesktopImageRegionCopyService : IImageRegionCopyService
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
graphics.ReleaseHdc(graphicsHdc.Value);
|
graphics.ReleaseHdc(graphicsHdc);
|
||||||
graphicsHdc = HDC.Null;
|
graphicsHdc = HDC.Null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using MouseJump.Common.Models.Drawing;
|
using System.Drawing;
|
||||||
|
|
||||||
|
using MouseJump.Models.Drawing;
|
||||||
|
|
||||||
namespace MouseJump.Common.Imaging;
|
namespace MouseJump.Common.Imaging;
|
||||||
|
|
||||||
@@ -10,7 +12,7 @@ public interface IImageRegionCopyService
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Copies the source region from the provider's source image (e.g. the interactive desktop,
|
/// Copies the source region from the provider's source image (e.g. the interactive desktop,
|
||||||
/// a static image, etc.) to the target region on the specified Graphics object.
|
/// a static image, etc) to the target region on the specified Graphics object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Implementations of this interface are used to capture regions of the interactive desktop
|
/// Implementations of this interface are used to capture regions of the interactive desktop
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Drawing.Drawing2D;
|
||||||
|
|
||||||
|
using MouseJump.Models.Drawing;
|
||||||
|
|
||||||
|
namespace MouseJump.Common.Imaging;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implements an IImageRegionCopyService that paints a solid block of the specified color.
|
||||||
|
/// This is used for testing the DrawingHelper rather than as part of the main application.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SolidColorRegionCopyService : IImageRegionCopyService
|
||||||
|
{
|
||||||
|
public SolidColorRegionCopyService(Color color)
|
||||||
|
{
|
||||||
|
this.Color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color Color
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Draws a region in a solid color.
|
||||||
|
/// </summary>
|
||||||
|
public void CopyImageRegion(
|
||||||
|
Graphics targetGraphics,
|
||||||
|
RectangleInfo sourceBounds,
|
||||||
|
RectangleInfo targetBounds)
|
||||||
|
{
|
||||||
|
using var brush = new SolidBrush(this.Color);
|
||||||
|
|
||||||
|
// prevent the background bleeding through into screen images
|
||||||
|
// (see https://github.com/mikeclayton/FancyMouse/issues/44)
|
||||||
|
targetGraphics.PixelOffsetMode = PixelOffsetMode.Half;
|
||||||
|
targetGraphics.InterpolationMode = InterpolationMode.NearestNeighbor;
|
||||||
|
|
||||||
|
targetGraphics.FillRectangle(
|
||||||
|
brush: brush,
|
||||||
|
rect: targetBounds.ToRectangle());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,10 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Drawing;
|
||||||
using System.Drawing.Drawing2D;
|
using System.Drawing.Drawing2D;
|
||||||
|
|
||||||
using MouseJump.Common.Models.Drawing;
|
using MouseJump.Models.Drawing;
|
||||||
|
|
||||||
namespace MouseJump.Common.Imaging;
|
namespace MouseJump.Common.Imaging;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace MouseJump.Common.Interop;
|
||||||
|
|
||||||
|
public static partial class ResultHandler
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
helpers for handling results from Win32 API calls that return an int value,
|
||||||
|
throwing exceptions when errors occur, and reading the last error code when necessary.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static void ThrowIfZero(
|
||||||
|
int result,
|
||||||
|
bool getLastError = false,
|
||||||
|
[CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
ResultHandler.HandleResult(
|
||||||
|
result,
|
||||||
|
result != 0,
|
||||||
|
getLastError,
|
||||||
|
memberName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ThrowIfNotZero(
|
||||||
|
int result,
|
||||||
|
bool getLastError = false,
|
||||||
|
[CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
ResultHandler.HandleResult(
|
||||||
|
result,
|
||||||
|
result == 0,
|
||||||
|
getLastError,
|
||||||
|
memberName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void HandleResult(
|
||||||
|
int result,
|
||||||
|
bool success,
|
||||||
|
bool getLastError = false,
|
||||||
|
[CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultHandler.HandleFailure(result, getLastError, memberName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void HandleResult(
|
||||||
|
int result,
|
||||||
|
bool success,
|
||||||
|
Func<int>? getLastError,
|
||||||
|
[CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastError = getLastError?.Invoke();
|
||||||
|
ResultHandler.HandleFailure(result, lastError, memberName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void HandleResult(
|
||||||
|
int result,
|
||||||
|
bool success,
|
||||||
|
int? lastError,
|
||||||
|
[CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultHandler.HandleFailure(result, lastError, memberName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void HandleFailure(
|
||||||
|
int result,
|
||||||
|
bool getLastError = false,
|
||||||
|
[CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
var lastError = getLastError ? (int?)Marshal.GetLastPInvokeError() : null;
|
||||||
|
ResultHandler.HandleFailure(result, lastError, memberName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void HandleFailure(
|
||||||
|
int result,
|
||||||
|
Func<int>? getLastError,
|
||||||
|
[CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
var lastError = getLastError?.Invoke();
|
||||||
|
ResultHandler.HandleFailure(result, lastError, memberName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void HandleFailure(
|
||||||
|
int result,
|
||||||
|
int? lastError,
|
||||||
|
[CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
var lines = new List<string>
|
||||||
|
{
|
||||||
|
$"{memberName} failed with result {result}.",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lastError is not null)
|
||||||
|
{
|
||||||
|
lines.Add($"last error was '{lastError}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = string.Join(Environment.NewLine, lines);
|
||||||
|
throw new Win32Exception(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace MouseJump.Common.Interop;
|
||||||
|
|
||||||
|
public static partial class ResultHandler
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
helpers for handling results from Win32 API calls that return an nint value,
|
||||||
|
throwing exceptions when errors occur, and reading the last error code when necessary.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static void ThrowIfZero(
|
||||||
|
nint result,
|
||||||
|
bool getLastError = false,
|
||||||
|
[CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
ResultHandler.HandleResult(
|
||||||
|
result,
|
||||||
|
result.ToInt64() != 0,
|
||||||
|
getLastError,
|
||||||
|
memberName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ThrowIfNotZero(
|
||||||
|
nint result,
|
||||||
|
bool getLastError = false,
|
||||||
|
[CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
ResultHandler.HandleResult(
|
||||||
|
result,
|
||||||
|
result.ToInt64() == 0,
|
||||||
|
getLastError,
|
||||||
|
memberName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void HandleResult(
|
||||||
|
nint result,
|
||||||
|
bool success,
|
||||||
|
bool getLastError = false,
|
||||||
|
[CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultHandler.HandleFailure(result, getLastError, memberName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void HandleResult(
|
||||||
|
nint result,
|
||||||
|
bool success,
|
||||||
|
Func<int>? getLastError,
|
||||||
|
[CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastError = getLastError?.Invoke();
|
||||||
|
ResultHandler.HandleFailure(result, lastError, memberName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void HandleResult(
|
||||||
|
nint result,
|
||||||
|
bool success,
|
||||||
|
int? lastError,
|
||||||
|
[CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultHandler.HandleFailure(result, lastError, memberName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void HandleFailure(
|
||||||
|
nint result,
|
||||||
|
bool getLastError = false,
|
||||||
|
[CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
var lastError = getLastError ? (int?)Marshal.GetLastPInvokeError() : null;
|
||||||
|
ResultHandler.HandleFailure(result, lastError, memberName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void HandleFailure(
|
||||||
|
nint result,
|
||||||
|
Func<int>? getLastError,
|
||||||
|
[CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
var lastError = getLastError?.Invoke();
|
||||||
|
ResultHandler.HandleFailure(result, lastError, memberName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void HandleFailure(
|
||||||
|
nint result,
|
||||||
|
int? lastError,
|
||||||
|
[CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
var lines = new List<string>
|
||||||
|
{
|
||||||
|
$"{memberName} failed with result {result}.",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lastError is not null)
|
||||||
|
{
|
||||||
|
lines.Add($"last error was '{lastError}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = string.Join(Environment.NewLine, lines);
|
||||||
|
throw new Win32Exception(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace MouseJump.Common.Interop;
|
||||||
|
|
||||||
|
public static partial class ResultHandler
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
helpers for handling results from Win32 API calls that return a uint value,
|
||||||
|
throwing exceptions when errors occur, and reading the last error code when necessary.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static void HandleFailure(
|
||||||
|
uint result,
|
||||||
|
bool getLastError = false,
|
||||||
|
[CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
var lastError = getLastError ? (int?)Marshal.GetLastPInvokeError() : null;
|
||||||
|
ResultHandler.HandleFailure(result, lastError, memberName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void HandleFailure(
|
||||||
|
uint result,
|
||||||
|
Func<int>? getLastError,
|
||||||
|
[CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
var lastError = getLastError?.Invoke();
|
||||||
|
ResultHandler.HandleFailure(result, lastError, memberName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void HandleFailure(
|
||||||
|
uint result,
|
||||||
|
int? lastError,
|
||||||
|
[CallerMemberName] string memberName = "")
|
||||||
|
{
|
||||||
|
var lines = new List<string>
|
||||||
|
{
|
||||||
|
$"{memberName} failed with result {result}.",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lastError is not null)
|
||||||
|
{
|
||||||
|
lines.Add($"last error was '{lastError}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = string.Join(Environment.NewLine, lines);
|
||||||
|
throw new Win32Exception(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace MouseJump.Common.Interop;
|
||||||
|
|
||||||
|
public sealed class Win32SafeHandle : SafeHandle
|
||||||
|
{
|
||||||
|
public Win32SafeHandle(nint handle)
|
||||||
|
: base(IntPtr.Zero, false)
|
||||||
|
{
|
||||||
|
this.SetHandle(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Win32SafeHandle(nint handle, bool ownsHandle = false)
|
||||||
|
: base(IntPtr.Zero, ownsHandle)
|
||||||
|
{
|
||||||
|
this.SetHandle(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Win32SafeHandle(nint handle, bool ownsHandle, Func<IntPtr, bool> release)
|
||||||
|
: base(IntPtr.Zero, ownsHandle)
|
||||||
|
{
|
||||||
|
this.ReleaseDelegate = release ?? throw new ArgumentNullException(nameof(release));
|
||||||
|
this.SetHandle(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Func<IntPtr, bool>? ReleaseDelegate
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool IsInvalid
|
||||||
|
=> handle == nint.Zero;
|
||||||
|
|
||||||
|
protected override bool ReleaseHandle()
|
||||||
|
{
|
||||||
|
return this.ReleaseDelegate?.Invoke(this.handle) ?? true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
|
|
||||||
|
namespace MouseJump.Common.Interop;
|
||||||
|
|
||||||
|
public sealed class Win32Window
|
||||||
|
{
|
||||||
|
internal Win32Window(Win32WindowClass windowClass, string windowName, HWND hwnd)
|
||||||
|
{
|
||||||
|
this.WindowClass = windowClass ?? throw new ArgumentNullException(nameof(windowClass));
|
||||||
|
this.WindowName = windowName ?? throw new ArgumentNullException(nameof(windowName));
|
||||||
|
this.Hwnd = hwnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Win32WindowClass WindowClass
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string WindowName
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public nint Hwnd
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
|
using Windows.Win32.UI.WindowsAndMessaging;
|
||||||
|
|
||||||
|
namespace MouseJump.Common.Interop;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wrapper around a Windows window class that can be passed to
|
||||||
|
/// external assemblies to manage WNDPROC delegate lifetime without
|
||||||
|
/// exposing internals.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class Win32WindowClass
|
||||||
|
{
|
||||||
|
internal Win32WindowClass(ushort classAtom, string className, Win32WindowProc windowProc)
|
||||||
|
{
|
||||||
|
this.ClassAtom = classAtom;
|
||||||
|
this.ClassName = className ?? throw new ArgumentNullException(nameof(className));
|
||||||
|
this.WindowProc = windowProc ?? throw new ArgumentNullException(nameof(windowProc));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ushort ClassAtom
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ClassName
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Win32WindowProc WindowProc
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
|
using Windows.Win32.UI.WindowsAndMessaging;
|
||||||
|
|
||||||
|
namespace MouseJump.Common.Interop;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wrapper around a WNDPROC delegate that can be handed to
|
||||||
|
/// external assemblies to manage native WNDPROC delegate lifetime
|
||||||
|
/// without exposing internals.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class Win32WindowProc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Public definition of WNDPROC using CLR types instead of CsWin32 generated types.
|
||||||
|
/// </summary>
|
||||||
|
public delegate nint WindowProcDelegate(nint hWnd, uint msg, nuint wParam, nint lParam);
|
||||||
|
|
||||||
|
internal Win32WindowProc(WindowProcDelegate windowProc)
|
||||||
|
{
|
||||||
|
this.WindowProcInternal = windowProc ?? throw new ArgumentNullException(nameof(windowProc));
|
||||||
|
|
||||||
|
// tl;dr...
|
||||||
|
//
|
||||||
|
// * Native code stores only the unmanaged function pointer.
|
||||||
|
// * Managed code must keep the delegate alive for native code to call.
|
||||||
|
//
|
||||||
|
// long version...
|
||||||
|
//
|
||||||
|
// A *method group* (e.g. WndProcInternal) is *not* a delegate (WNDPROC).
|
||||||
|
// When a method group is passed to anything that receives a delegate, the
|
||||||
|
// CLR creates a new temporary delegate instance based on the method group.
|
||||||
|
//
|
||||||
|
// Windows api methods such as RegisterClassEx don't store a reference to
|
||||||
|
// the managed delegate object - they only store an unmanaged function
|
||||||
|
// pointer. Once the delegate instance becomes unreferenced on the managed
|
||||||
|
// side the GC is allowed to collect it, even though the Win32 api continues
|
||||||
|
// to store the unmanaged pointer to it. If Win32 subsequently calls the
|
||||||
|
// (now invalid) unmanaged function pointer it can cause access violations
|
||||||
|
// or other runtime crashes.
|
||||||
|
//
|
||||||
|
// To prevent this, we'll cache a strongly-typed reference to the managed
|
||||||
|
// *delegate* instance in WndProcDelegate. Keeping this delegate reference
|
||||||
|
// alive ensures the underlying unmanaged function pointer remains valid
|
||||||
|
// for the entire lifetime of this Window instance.
|
||||||
|
this.WndProcDelegate = this.WndProcInternal;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ushort ClassAtom
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WindowProcDelegate WindowProcInternal
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal WNDPROC WndProcDelegate
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LRESULT WndProcInternal(HWND hWnd, uint msg, WPARAM wParam, LPARAM lParam)
|
||||||
|
{
|
||||||
|
return (LRESULT)this.WindowProcInternal(hWnd, msg, wParam, lParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
namespace MouseJump.Common.Models.Drawing;
|
|
||||||
|
|
||||||
public sealed class BoxBounds
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
|
|
||||||
see https://www.w3schools.com/css/css_boxmodel.asp
|
|
||||||
|
|
||||||
+--------------[bounds]---------------+
|
|
||||||
|▒▒▒▒▒▒▒▒▒▒▒▒▒▒[margin]▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒|
|
|
||||||
|▒▒▓▓▓▓▓▓▓▓▓▓▓▓[border]▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒|
|
|
||||||
|▒▒▓▓░░░░░░░░░░[padding]░░░░░░░░░░▓▓▒▒|
|
|
||||||
|▒▒▓▓░░ ░░▓▓▒▒|
|
|
||||||
|▒▒▓▓░░ ░░▓▓▒▒|
|
|
||||||
|▒▒▓▓░░ [content] ░░▓▓▒▒|
|
|
||||||
|▒▒▓▓░░ ░░▓▓▒▒|
|
|
||||||
|▒▒▓▓░░ ░░▓▓▒▒|
|
|
||||||
|▒▒▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓▒▒|
|
|
||||||
|▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒|
|
|
||||||
|▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒|
|
|
||||||
+-------------------------------------+
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
public BoxBounds(
|
|
||||||
RectangleInfo outerBounds,
|
|
||||||
RectangleInfo marginBounds,
|
|
||||||
RectangleInfo borderBounds,
|
|
||||||
RectangleInfo paddingBounds,
|
|
||||||
RectangleInfo contentBounds)
|
|
||||||
{
|
|
||||||
this.OuterBounds = outerBounds ?? throw new ArgumentNullException(nameof(outerBounds));
|
|
||||||
this.MarginBounds = marginBounds ?? throw new ArgumentNullException(nameof(marginBounds));
|
|
||||||
this.BorderBounds = borderBounds ?? throw new ArgumentNullException(nameof(borderBounds));
|
|
||||||
this.PaddingBounds = paddingBounds ?? throw new ArgumentNullException(nameof(paddingBounds));
|
|
||||||
this.ContentBounds = contentBounds ?? throw new ArgumentNullException(nameof(contentBounds));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the outer bounds of this layout box.
|
|
||||||
/// </summary>
|
|
||||||
public RectangleInfo OuterBounds
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RectangleInfo MarginBounds
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RectangleInfo BorderBounds
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RectangleInfo PaddingBounds
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the bounds of the content area for this layout box.
|
|
||||||
/// </summary>
|
|
||||||
public RectangleInfo ContentBounds
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
|
|
||||||
using MouseJump.Common.Models.Drawing;
|
|
||||||
using MouseJump.Common.Models.Styles;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.Models.Layout;
|
|
||||||
|
|
||||||
public sealed class PreviewLayout
|
|
||||||
{
|
|
||||||
public sealed class Builder
|
|
||||||
{
|
|
||||||
public Builder()
|
|
||||||
{
|
|
||||||
this.Screens = new();
|
|
||||||
this.ScreenshotBounds = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PreviewStyle? PreviewStyle
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RectangleInfo? VirtualScreen
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<RectangleInfo> Screens
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int ActivatedScreenIndex
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RectangleInfo? FormBounds
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BoxBounds? PreviewBounds
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<BoxBounds> ScreenshotBounds
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PreviewLayout Build()
|
|
||||||
{
|
|
||||||
return new PreviewLayout(
|
|
||||||
previewStyle: this.PreviewStyle ?? throw new InvalidOperationException($"{nameof(this.PreviewStyle)} must be initialized before calling {nameof(this.Build)}."),
|
|
||||||
virtualScreen: this.VirtualScreen ?? throw new InvalidOperationException($"{nameof(this.VirtualScreen)} must be initialized before calling {nameof(this.Build)}."),
|
|
||||||
screens: this.Screens ?? throw new InvalidOperationException($"{nameof(this.Screens)} must be initialized before calling {nameof(this.Build)}."),
|
|
||||||
activatedScreenIndex: this.ActivatedScreenIndex,
|
|
||||||
formBounds: this.FormBounds ?? throw new InvalidOperationException($"{nameof(this.FormBounds)} must be initialized before calling {nameof(this.Build)}."),
|
|
||||||
previewBounds: this.PreviewBounds ?? throw new InvalidOperationException($"{nameof(this.PreviewBounds)} must be initialized before calling {nameof(this.Build)}."),
|
|
||||||
screenshotBounds: this.ScreenshotBounds ?? throw new InvalidOperationException($"{nameof(this.ScreenshotBounds)} must be initialized before calling {nameof(this.Build)}."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PreviewLayout(
|
|
||||||
PreviewStyle previewStyle,
|
|
||||||
RectangleInfo virtualScreen,
|
|
||||||
List<RectangleInfo> screens,
|
|
||||||
int activatedScreenIndex,
|
|
||||||
RectangleInfo formBounds,
|
|
||||||
BoxBounds previewBounds,
|
|
||||||
List<BoxBounds> screenshotBounds)
|
|
||||||
{
|
|
||||||
this.PreviewStyle = previewStyle ?? throw new ArgumentNullException(nameof(previewStyle));
|
|
||||||
this.VirtualScreen = virtualScreen ?? throw new ArgumentNullException(nameof(virtualScreen));
|
|
||||||
this.Screens = (screens ?? throw new ArgumentNullException(nameof(screens)))
|
|
||||||
.ToList().AsReadOnly();
|
|
||||||
this.ActivatedScreenIndex = activatedScreenIndex;
|
|
||||||
this.FormBounds = formBounds ?? throw new ArgumentNullException(nameof(formBounds));
|
|
||||||
this.PreviewBounds = previewBounds ?? throw new ArgumentNullException(nameof(previewBounds));
|
|
||||||
this.ScreenshotBounds = (screenshotBounds ?? throw new ArgumentNullException(nameof(screenshotBounds)))
|
|
||||||
.ToList().AsReadOnly();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PreviewStyle PreviewStyle
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RectangleInfo VirtualScreen
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyCollection<RectangleInfo> Screens
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int ActivatedScreenIndex
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RectangleInfo FormBounds
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BoxBounds PreviewBounds
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyCollection<BoxBounds> ScreenshotBounds
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +1,32 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||||
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
|
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<ProjectGuid>{923DF87C-CA99-4D1C-B1D2-959174E95BFA}</ProjectGuid>
|
||||||
|
<AssemblyName>PowerToys.MouseJump.Common</AssemblyName>
|
||||||
|
<AssemblyTitle>PowerToys.MouseJump.Common</AssemblyTitle>
|
||||||
|
<AssemblyDescription>PowerToys MouseJump.Common</AssemblyDescription>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<OutputPath>$(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps</OutputPath>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||||
|
|
||||||
<ProjectGuid>{923DF87C-CA99-4D1C-B1D2-959174E95BFA}</ProjectGuid>
|
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
|
||||||
<AssemblyName>PowerToys.MouseJump.Common</AssemblyName>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<AssemblyTitle>PowerToys.MouseJump.Common</AssemblyTitle>
|
<Nullable>enable</Nullable>
|
||||||
<AssemblyDescription>PowerToys MouseJump.Common</AssemblyDescription>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<OutputType>Library</OutputType>
|
</PropertyGroup>
|
||||||
<OutputPath>$(RepoRoot)$(Platform)\$(Configuration)</OutputPath>
|
|
||||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
|
||||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
|
||||||
|
|
||||||
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
|
<ItemGroup>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<ProjectReference Include="..\MouseJump.Models\MouseJump.Models.csproj" />
|
||||||
<Nullable>enable</Nullable>
|
</ItemGroup>
|
||||||
<UseWindowsForms>true</UseWindowsForms>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="System.Drawing.Common" />
|
||||||
|
<PackageReference Include="Microsoft.Windows.CsWin32">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
69
src/modules/MouseUtils/MouseJump.Common/NativeMethods.txt
Normal file
69
src/modules/MouseUtils/MouseJump.Common/NativeMethods.txt
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
HBRUSH
|
||||||
|
HCURSOR
|
||||||
|
HICON
|
||||||
|
HINSTANCE
|
||||||
|
|
||||||
|
WM_ACTIVATE
|
||||||
|
WM_CREATE
|
||||||
|
WM_ENTERIDLE
|
||||||
|
WM_ENTERMENULOOP
|
||||||
|
WM_EXITMENULOOP
|
||||||
|
WM_KILLFOCUS
|
||||||
|
WM_GETMINMAXINFO
|
||||||
|
WM_IME_SETCONTEXT
|
||||||
|
WM_IME_NOTIFY
|
||||||
|
WM_INITMENU
|
||||||
|
WM_INITMENUPOPUP
|
||||||
|
WM_LBUTTONDOWN
|
||||||
|
WM_MENUSELECT
|
||||||
|
WM_NCACTIVATE
|
||||||
|
WM_NCCALCSIZE
|
||||||
|
WM_NCCREATE
|
||||||
|
WM_RBUTTONDOWN
|
||||||
|
WM_SETCURSOR
|
||||||
|
WM_SETFOCUS
|
||||||
|
WM_UNINITMENUPOPUP
|
||||||
|
WM_USER
|
||||||
|
WM_WINDOWPOSCHANGED
|
||||||
|
WM_WINDOWPOSCHANGING
|
||||||
|
WM_COMMAND
|
||||||
|
|
||||||
|
CreateWindowExW
|
||||||
|
DefWindowProc
|
||||||
|
GetWindowLong
|
||||||
|
RegisterClassEx
|
||||||
|
SetForegroundWindow
|
||||||
|
WNDPROC
|
||||||
|
|
||||||
|
MONITORINFOF_PRIMARY
|
||||||
|
ScreenToClient
|
||||||
|
EnumDisplayMonitors
|
||||||
|
GetMonitorInfo
|
||||||
|
GetSystemMetrics
|
||||||
|
MonitorFromPoint
|
||||||
|
SendInput
|
||||||
|
|
||||||
|
GetCursorPos
|
||||||
|
SetCursorPos
|
||||||
|
|
||||||
|
AppendMenu
|
||||||
|
CreatePopupMenu
|
||||||
|
SetMenuInfo
|
||||||
|
TrackPopupMenuEx
|
||||||
|
TRACK_POPUP_MENU_FLAGS
|
||||||
|
|
||||||
|
SetStretchBltMode
|
||||||
|
StretchBlt
|
||||||
|
|
||||||
|
GetDesktopWindow
|
||||||
|
GetWindowDC
|
||||||
|
ReleaseDC
|
||||||
|
|
||||||
|
Shell_NotifyIconW
|
||||||
|
|
||||||
|
GetAwarenessFromDpiAwarenessContext
|
||||||
|
GetThreadDpiAwarenessContext
|
||||||
|
|
||||||
|
HWND_MESSAGE
|
||||||
|
|
||||||
|
GetModuleHandleW
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The CRECT structure defines a rectangle by the coordinates of its upper-left and lower-right corners.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/windef/ns-windef-rect
|
|
||||||
/// </remarks>
|
|
||||||
[SuppressMessage("Naming Rules", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Name and value taken from Win32Api")]
|
|
||||||
internal readonly struct CRECT
|
|
||||||
{
|
|
||||||
public static readonly CRECT Empty = new(0, 0, 0, 0);
|
|
||||||
|
|
||||||
public readonly LONG left;
|
|
||||||
public readonly LONG top;
|
|
||||||
public readonly LONG right;
|
|
||||||
public readonly LONG bottom;
|
|
||||||
|
|
||||||
public CRECT(
|
|
||||||
int left, int top, int right, int bottom)
|
|
||||||
{
|
|
||||||
this.left = left;
|
|
||||||
this.top = top;
|
|
||||||
this.right = right;
|
|
||||||
this.bottom = bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CRECT(
|
|
||||||
LONG left, LONG top, LONG right, LONG bottom)
|
|
||||||
{
|
|
||||||
this.left = left;
|
|
||||||
this.top = top;
|
|
||||||
this.right = right;
|
|
||||||
this.bottom = bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int Size =>
|
|
||||||
Marshal.SizeOf(typeof(CRECT));
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"left={this.left},top={this.top},right={this.right},bottom={this.bottom}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A handle to a device context (DC).
|
|
||||||
/// This type is declared in WinDef.h as follows:
|
|
||||||
/// typedef HANDLE HDC;
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
|
|
||||||
/// </remarks>
|
|
||||||
internal readonly struct HDC
|
|
||||||
{
|
|
||||||
public static readonly HDC Null = new(IntPtr.Zero);
|
|
||||||
|
|
||||||
public readonly IntPtr Value;
|
|
||||||
|
|
||||||
public HDC(IntPtr value)
|
|
||||||
{
|
|
||||||
this.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsNull => this.Value == HDC.Null.Value;
|
|
||||||
|
|
||||||
public static implicit operator IntPtr(HDC value) => value.Value;
|
|
||||||
|
|
||||||
public static explicit operator HDC(IntPtr value) => new(value);
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{this.GetType().Name}({this.Value})";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A handle to a display monitor.
|
|
||||||
/// This type is declared in WinDef.h as follows:
|
|
||||||
/// if(WINVER >= 0x0500) typedef HANDLE HMONITOR;
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
|
|
||||||
/// </remarks>
|
|
||||||
internal readonly struct HMONITOR
|
|
||||||
{
|
|
||||||
public static readonly HMONITOR Null = new(IntPtr.Zero);
|
|
||||||
|
|
||||||
public readonly IntPtr Value;
|
|
||||||
|
|
||||||
public HMONITOR(IntPtr value)
|
|
||||||
{
|
|
||||||
this.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsNull => this.Value == HMONITOR.Null.Value;
|
|
||||||
|
|
||||||
public static implicit operator int(HMONITOR value) => value.Value.ToInt32();
|
|
||||||
|
|
||||||
public static explicit operator HMONITOR(int value) => new(value);
|
|
||||||
|
|
||||||
public static implicit operator IntPtr(HMONITOR value) => value.Value;
|
|
||||||
|
|
||||||
public static explicit operator HMONITOR(IntPtr value) => new(value);
|
|
||||||
|
|
||||||
public static implicit operator HANDLE(HMONITOR value) => new(value.Value);
|
|
||||||
|
|
||||||
public static explicit operator HMONITOR(HANDLE value) => new(value.Value);
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{this.GetType().Name}({this.Value})";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A 32-bit signed integer.The range is -2147483648 through 2147483647 decimal.
|
|
||||||
/// This type is declared in WinNT.h as follows:
|
|
||||||
/// typedef long LONG;
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
|
|
||||||
/// </remarks>
|
|
||||||
internal readonly struct LONG
|
|
||||||
{
|
|
||||||
public readonly int Value;
|
|
||||||
|
|
||||||
public LONG(int value)
|
|
||||||
{
|
|
||||||
this.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator int(LONG value) => value.Value;
|
|
||||||
|
|
||||||
public static implicit operator LONG(int value) => new(value);
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{this.GetType().Name}({this.Value})";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class Core
|
|
||||||
{
|
|
||||||
internal readonly struct LPCRECT
|
|
||||||
{
|
|
||||||
public static readonly LPCRECT Null = new(IntPtr.Zero);
|
|
||||||
|
|
||||||
public readonly IntPtr Value;
|
|
||||||
|
|
||||||
public LPCRECT(IntPtr value)
|
|
||||||
{
|
|
||||||
this.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LPCRECT(CRECT value)
|
|
||||||
{
|
|
||||||
this.Value = LPCRECT.ToPtr(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsNull => this.Value == LPCRECT.Null.Value;
|
|
||||||
|
|
||||||
private static IntPtr ToPtr(CRECT value)
|
|
||||||
{
|
|
||||||
var ptr = Marshal.AllocHGlobal(CRECT.Size);
|
|
||||||
Marshal.StructureToPtr(value, ptr, false);
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Free()
|
|
||||||
{
|
|
||||||
Marshal.FreeHGlobal(this.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator IntPtr(LPCRECT value) => value.Value;
|
|
||||||
|
|
||||||
public static explicit operator LPCRECT(IntPtr value) => new(value);
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{this.GetType().Name}({this.Value})";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class Core
|
|
||||||
{
|
|
||||||
internal readonly struct LPPOINT
|
|
||||||
{
|
|
||||||
public static readonly LPPOINT Null = new(IntPtr.Zero);
|
|
||||||
|
|
||||||
public readonly IntPtr Value;
|
|
||||||
|
|
||||||
public LPPOINT(IntPtr value)
|
|
||||||
{
|
|
||||||
this.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LPPOINT(POINT value)
|
|
||||||
{
|
|
||||||
this.Value = LPPOINT.ToPtr(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsNull => this.Value == LPPOINT.Null.Value;
|
|
||||||
|
|
||||||
private static IntPtr ToPtr(POINT value)
|
|
||||||
{
|
|
||||||
var ptr = Marshal.AllocHGlobal(POINT.Size);
|
|
||||||
Marshal.StructureToPtr(value, ptr, false);
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public POINT ToStructure()
|
|
||||||
{
|
|
||||||
return Marshal.PtrToStructure<POINT>(this.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Free()
|
|
||||||
{
|
|
||||||
Marshal.FreeHGlobal(this.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator IntPtr(LPPOINT value) => value.Value;
|
|
||||||
|
|
||||||
public static explicit operator LPPOINT(IntPtr value) => new(value);
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{this.GetType().Name}({this.Value})";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class Core
|
|
||||||
{
|
|
||||||
internal readonly struct LPRECT
|
|
||||||
{
|
|
||||||
public static readonly LPRECT Null = new(IntPtr.Zero);
|
|
||||||
|
|
||||||
public readonly IntPtr Value;
|
|
||||||
|
|
||||||
public LPRECT(IntPtr value)
|
|
||||||
{
|
|
||||||
this.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LPRECT(RECT value)
|
|
||||||
{
|
|
||||||
this.Value = LPRECT.ToPtr(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsNull => this.Value == LPRECT.Null.Value;
|
|
||||||
|
|
||||||
private static IntPtr ToPtr(RECT value)
|
|
||||||
{
|
|
||||||
var ptr = Marshal.AllocHGlobal(RECT.Size);
|
|
||||||
Marshal.StructureToPtr(value, ptr, false);
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Free()
|
|
||||||
{
|
|
||||||
Marshal.FreeHGlobal(this.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator IntPtr(LPRECT value) => value.Value;
|
|
||||||
|
|
||||||
public static explicit operator LPRECT(IntPtr value) => new(value);
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{this.GetType().Name}({this.Value})";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The POINT structure defines the x- and y-coordinates of a point.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/windef/ns-windef-point
|
|
||||||
/// </remarks>
|
|
||||||
[SuppressMessage("SA1307", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Names match Win32 api")]
|
|
||||||
internal readonly struct POINT
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Specifies the x-coordinate of the point.
|
|
||||||
/// </summary>
|
|
||||||
public readonly LONG x;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Specifies the y-coordinate of the point.
|
|
||||||
/// </summary>
|
|
||||||
public readonly LONG y;
|
|
||||||
|
|
||||||
public POINT(
|
|
||||||
int x,
|
|
||||||
int y)
|
|
||||||
{
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
public POINT(
|
|
||||||
LONG x,
|
|
||||||
LONG y)
|
|
||||||
{
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int Size =>
|
|
||||||
Marshal.SizeOf(typeof(POINT));
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"x={this.x},y={this.y}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The RECT structure defines a rectangle by the coordinates of its upper-left and lower-right corners.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/windef/ns-windef-rect
|
|
||||||
/// </remarks>
|
|
||||||
[SuppressMessage("Naming Rules", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Name and value taken from Win32Api")]
|
|
||||||
internal readonly struct RECT
|
|
||||||
{
|
|
||||||
public static readonly RECT Empty = new(0, 0, 0, 0);
|
|
||||||
|
|
||||||
public readonly LONG left;
|
|
||||||
public readonly LONG top;
|
|
||||||
public readonly LONG right;
|
|
||||||
public readonly LONG bottom;
|
|
||||||
|
|
||||||
public RECT(
|
|
||||||
int left, int top, int right, int bottom)
|
|
||||||
{
|
|
||||||
this.left = left;
|
|
||||||
this.top = top;
|
|
||||||
this.right = right;
|
|
||||||
this.bottom = bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RECT(
|
|
||||||
LONG left, LONG top, LONG right, LONG bottom)
|
|
||||||
{
|
|
||||||
this.left = left;
|
|
||||||
this.top = top;
|
|
||||||
this.right = right;
|
|
||||||
this.bottom = bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int Size =>
|
|
||||||
Marshal.SizeOf(typeof(RECT));
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"left={this.left},top={this.top},right={this.right},bottom={this.bottom}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An unsigned INT. The range is 0 through 4294967295 decimal.
|
|
||||||
/// This type is declared in WinDef.h as follows:
|
|
||||||
/// typedef unsigned int UINT;
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
|
|
||||||
/// </remarks>
|
|
||||||
internal readonly struct UINT
|
|
||||||
{
|
|
||||||
public readonly uint Value;
|
|
||||||
|
|
||||||
public UINT(uint value)
|
|
||||||
{
|
|
||||||
this.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator uint(UINT value) => value.Value;
|
|
||||||
|
|
||||||
public static implicit operator UINT(uint value) => new(value);
|
|
||||||
|
|
||||||
public static explicit operator int(UINT value) => (int)value.Value;
|
|
||||||
|
|
||||||
public static explicit operator UINT(int value) => new((uint)value);
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{this.GetType().Name}({this.Value})";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An unsigned LONG_PTR.
|
|
||||||
/// This type is declared in BaseTsd.h as follows:
|
|
||||||
/// C++
|
|
||||||
/// #if defined(_WIN64)
|
|
||||||
/// typedef unsigned __int64 ULONG_PTR;
|
|
||||||
/// #else
|
|
||||||
/// typedef unsigned long ULONG_PTR;
|
|
||||||
/// #endif
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
|
|
||||||
/// </remarks>
|
|
||||||
internal readonly struct ULONG_PTR
|
|
||||||
{
|
|
||||||
public static readonly ULONG_PTR Null = new(UIntPtr.Zero);
|
|
||||||
|
|
||||||
public readonly UIntPtr Value;
|
|
||||||
|
|
||||||
public ULONG_PTR(UIntPtr value)
|
|
||||||
{
|
|
||||||
this.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator UIntPtr(ULONG_PTR value) => value.Value;
|
|
||||||
|
|
||||||
public static explicit operator ULONG_PTR(UIntPtr value) => new(value);
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{this.GetType().Name}({this.Value})";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A 16-bit unsigned integer.The range is 0 through 65535 decimal.
|
|
||||||
/// This type is declared in WinDef.h as follows:
|
|
||||||
/// typedef unsigned short WORD;
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
|
|
||||||
/// </remarks>
|
|
||||||
internal readonly struct WORD
|
|
||||||
{
|
|
||||||
public readonly ushort Value;
|
|
||||||
|
|
||||||
public WORD(ushort value)
|
|
||||||
{
|
|
||||||
this.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator ulong(WORD value) => value.Value;
|
|
||||||
|
|
||||||
public static implicit operator WORD(ushort value) => new(value);
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{this.GetType().Name}({this.Value})";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class Gdi32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A raster-operation code. These codes define how the color data for the source
|
|
||||||
/// rectangle is to be combined with the color data for the destination rectangle
|
|
||||||
/// to achieve the final color.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-bitblt
|
|
||||||
/// </remarks>
|
|
||||||
internal enum ROP_CODE : uint
|
|
||||||
{
|
|
||||||
BLACKNESS = 0x00000042,
|
|
||||||
CAPTUREBLT = 0x40000000,
|
|
||||||
DSTINVERT = 0x00550009,
|
|
||||||
MERGECOPY = 0x00C000CA,
|
|
||||||
MERGEPAINT = 0x00BB0226,
|
|
||||||
NOMIRRORBITMAP = 0x80000000,
|
|
||||||
NOTSRCCOPY = 0x00330008,
|
|
||||||
NOTSRCERASE = 0x001100A6,
|
|
||||||
PATCOPY = 0x00F00021,
|
|
||||||
PATINVERT = 0x005A0049,
|
|
||||||
PATPAINT = 0x00FB0A09,
|
|
||||||
SRCAND = 0x008800C6,
|
|
||||||
SRCCOPY = 0x00CC0020,
|
|
||||||
SRCERASE = 0x00440328,
|
|
||||||
SRCINVERT = 0x00660046,
|
|
||||||
SRCPAINT = 0x00EE0086,
|
|
||||||
WHITENESS = 0x00FF0062,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class Gdi32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The stretching mode.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-setstretchbltmode
|
|
||||||
/// </remarks>
|
|
||||||
internal enum STRETCH_BLT_MODE : int
|
|
||||||
{
|
|
||||||
BLACKONWHITE = 1,
|
|
||||||
COLORONCOLOR = 3,
|
|
||||||
HALFTONE = 4,
|
|
||||||
WHITEONBLACK = 2,
|
|
||||||
STRETCH_ANDSCANS = STRETCH_BLT_MODE.BLACKONWHITE,
|
|
||||||
STRETCH_DELETESCANS = STRETCH_BLT_MODE.COLORONCOLOR,
|
|
||||||
STRETCH_HALFTONE = STRETCH_BLT_MODE.HALFTONE,
|
|
||||||
STRETCH_ORSCANS = STRETCH_BLT_MODE.WHITEONBLACK,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using static MouseJump.Common.NativeMethods.Core;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class Gdi32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The SetStretchBltMode function sets the bitmap stretching mode in the specified device context.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// If the function succeeds, the return value is the previous stretching mode.
|
|
||||||
/// If the function fails, the return value is zero.
|
|
||||||
/// This function can return the following value.
|
|
||||||
/// </returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-setstretchbltmode
|
|
||||||
/// </remarks>
|
|
||||||
[LibraryImport(Libraries.Gdi32)]
|
|
||||||
internal static partial int SetStretchBltMode(
|
|
||||||
HDC hdc,
|
|
||||||
STRETCH_BLT_MODE mode);
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using static MouseJump.Common.NativeMethods.Core;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class Gdi32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The StretchBlt function copies a bitmap from a source rectangle into a destination
|
|
||||||
/// rectangle, stretching or compressing the bitmap to fit the dimensions of the
|
|
||||||
/// destination rectangle, if necessary. The system stretches or compresses the bitmap
|
|
||||||
/// according to the stretching mode currently set in the destination device context.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// If the function succeeds, the return value is nonzero.
|
|
||||||
/// If the function fails, the return value is zero.
|
|
||||||
/// </returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-stretchblt
|
|
||||||
/// </remarks>
|
|
||||||
[LibraryImport(Libraries.Gdi32)]
|
|
||||||
internal static partial BOOL StretchBlt(
|
|
||||||
HDC hdcDest,
|
|
||||||
int xDest,
|
|
||||||
int yDest,
|
|
||||||
int wDest,
|
|
||||||
int hDest,
|
|
||||||
HDC hdcSrc,
|
|
||||||
int xSrc,
|
|
||||||
int ySrc,
|
|
||||||
int wSrc,
|
|
||||||
int hSrc,
|
|
||||||
ROP_CODE rop);
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using static MouseJump.Common.NativeMethods.Core;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class User32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The EnumDisplayMonitors function enumerates display monitors (including invisible
|
|
||||||
/// pseudo-monitors associated with the mirroring drivers) that intersect a region formed
|
|
||||||
/// by the intersection of a specified clipping rectangle and the visible region of a
|
|
||||||
/// device context. EnumDisplayMonitors calls an application-defined MonitorEnumProc
|
|
||||||
/// callback function once for each monitor that is enumerated. Note that
|
|
||||||
/// GetSystemMetrics (SM_CMONITORS) counts only the display monitors.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// If the function succeeds, the return value is nonzero.
|
|
||||||
/// If the function fails, the return value is zero.
|
|
||||||
/// </returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumdisplaymonitors
|
|
||||||
/// </remarks>
|
|
||||||
[LibraryImport(Libraries.User32)]
|
|
||||||
internal static partial BOOL EnumDisplayMonitors(
|
|
||||||
HDC hdc,
|
|
||||||
LPCRECT lprcClip,
|
|
||||||
MONITORENUMPROC lpfnEnum,
|
|
||||||
LPARAM dwData);
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using static MouseJump.Common.NativeMethods.Core;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class User32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The GetWindowDC function retrieves the device context (DC) for the entire window,
|
|
||||||
/// including title bar, menus, and scroll bars. A window device context permits painting
|
|
||||||
/// anywhere in a window, because the origin of the device context is the upper-left
|
|
||||||
/// corner of the window instead of the client area.
|
|
||||||
///
|
|
||||||
/// GetWindowDC assigns default attributes to the window device context each time it
|
|
||||||
/// retrieves the device context. Previous attributes are lost.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// If the function succeeds, the return value is a handle to a device context for the specified window.
|
|
||||||
/// If the function fails, the return value is NULL, indicating an error or an invalid hWnd parameter.
|
|
||||||
/// </returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowdc
|
|
||||||
/// </remarks>
|
|
||||||
[LibraryImport(Libraries.User32)]
|
|
||||||
internal static partial HDC GetWindowDC(
|
|
||||||
HWND hWnd);
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class User32
|
|
||||||
{
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput
|
|
||||||
/// </remarks>
|
|
||||||
internal readonly struct LPMONITORINFO
|
|
||||||
{
|
|
||||||
public static readonly LPMONITORINFO Null = new(IntPtr.Zero);
|
|
||||||
|
|
||||||
public readonly IntPtr Value;
|
|
||||||
|
|
||||||
public LPMONITORINFO(IntPtr value)
|
|
||||||
{
|
|
||||||
this.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LPMONITORINFO(MONITORINFO value)
|
|
||||||
{
|
|
||||||
this.Value = LPMONITORINFO.ToPtr(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MONITORINFO ToStructure()
|
|
||||||
{
|
|
||||||
return Marshal.PtrToStructure<MONITORINFO>(this.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IntPtr ToPtr(MONITORINFO value)
|
|
||||||
{
|
|
||||||
var ptr = Marshal.AllocHGlobal(MONITORINFO.Size);
|
|
||||||
Marshal.StructureToPtr(value, ptr, false);
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Free()
|
|
||||||
{
|
|
||||||
Marshal.FreeHGlobal(this.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{this.GetType().Name}({this.Value})";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using static MouseJump.Common.NativeMethods.Core;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class User32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A MonitorEnumProc function is an application-defined callback function that is called by the EnumDisplayMonitors function.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-monitorenumproc
|
|
||||||
/// </remarks>
|
|
||||||
internal delegate BOOL MONITORENUMPROC(
|
|
||||||
HMONITOR unnamedParam1,
|
|
||||||
HDC unnamedParam2,
|
|
||||||
LPRECT unnamedParam3,
|
|
||||||
LPARAM unnamedParam4);
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using static MouseJump.Common.NativeMethods.Core;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class User32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Used by SendInput to store information for synthesizing input events such as keystrokes, mouse movement, and mouse clicks.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-monitorinfo
|
|
||||||
/// </remarks>
|
|
||||||
[SuppressMessage("SA1307", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Parameter name matches Win32 api")]
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
internal readonly struct MONITORINFO
|
|
||||||
{
|
|
||||||
public readonly DWORD cbSize;
|
|
||||||
public readonly RECT rcMonitor;
|
|
||||||
public readonly RECT rcWork;
|
|
||||||
public readonly MONITOR_INFO_FLAGS dwFlags;
|
|
||||||
|
|
||||||
public MONITORINFO(DWORD cbSize, RECT rcMonitor, RECT rcWork, MONITOR_INFO_FLAGS dwFlags)
|
|
||||||
{
|
|
||||||
this.cbSize = cbSize;
|
|
||||||
this.rcMonitor = rcMonitor;
|
|
||||||
this.rcWork = rcWork;
|
|
||||||
this.dwFlags = dwFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int Size =>
|
|
||||||
Marshal.SizeOf(typeof(MONITORINFO));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
[SuppressMessage("SA1310", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "Names match Win32 api")]
|
|
||||||
internal static partial class User32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Determines the function's return value if the point is not contained within any display monitor.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfrompoint
|
|
||||||
/// </remarks>
|
|
||||||
internal enum MONITOR_FROM_FLAGS : uint
|
|
||||||
{
|
|
||||||
MONITOR_DEFAULTTONULL = 0x00000000,
|
|
||||||
MONITOR_DEFAULTTOPRIMARY = 0x00000001,
|
|
||||||
MONITOR_DEFAULTTONEAREST = 0x00000002,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
[SuppressMessage("SA1310", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "Names match Win32 api")]
|
|
||||||
internal static partial class User32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A set of flags that represent attributes of the display monitor.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-monitorinfo
|
|
||||||
/// </remarks>
|
|
||||||
internal enum MONITOR_INFO_FLAGS : uint
|
|
||||||
{
|
|
||||||
MONITORINFOF_PRIMARY = 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using static MouseJump.Common.NativeMethods.Core;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class User32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The MonitorFromPoint function retrieves a handle to the display monitor that contains a specified point.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// If the point is contained by a display monitor, the return value is an HMONITOR handle to that display monitor.
|
|
||||||
/// If the point is not contained by a display monitor, the return value depends on the value of dwFlags.
|
|
||||||
/// </returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfrompoint
|
|
||||||
/// </remarks>
|
|
||||||
[LibraryImport(Libraries.User32)]
|
|
||||||
internal static partial HMONITOR MonitorFromPoint(
|
|
||||||
POINT pt,
|
|
||||||
MONITOR_FROM_FLAGS dwFlags);
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using static MouseJump.Common.NativeMethods.Core;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class User32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The ReleaseDC function releases a device context (DC), freeing it for use by other
|
|
||||||
/// applications. The effect of the ReleaseDC function depends on the type of DC. It
|
|
||||||
/// frees only common and window DCs. It has no effect on class or private DCs.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// The return value indicates whether the DC was released. If the DC was released, the return value is 1.
|
|
||||||
/// If the DC was not released, the return value is zero.
|
|
||||||
/// </returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-releasedc
|
|
||||||
/// </remarks>
|
|
||||||
[LibraryImport(Libraries.User32)]
|
|
||||||
internal static partial int ReleaseDC(
|
|
||||||
HWND hWnd,
|
|
||||||
HDC hDC);
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using static MouseJump.Common.NativeMethods.Core;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class User32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Contains information about a simulated message generated by an input device
|
|
||||||
/// other than a keyboard or mouse.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-hardwareinput
|
|
||||||
/// </remarks>
|
|
||||||
[SuppressMessage("SA1307", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Parameter name matches Win32 api")]
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
internal readonly struct HARDWAREINPUT
|
|
||||||
{
|
|
||||||
public readonly DWORD uMsg;
|
|
||||||
public readonly WORD wParamL;
|
|
||||||
public readonly WORD wParamH;
|
|
||||||
|
|
||||||
public HARDWAREINPUT(
|
|
||||||
DWORD uMsg,
|
|
||||||
WORD wParamL,
|
|
||||||
WORD wParamH)
|
|
||||||
{
|
|
||||||
this.uMsg = uMsg;
|
|
||||||
this.wParamL = wParamL;
|
|
||||||
this.wParamH = wParamH;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class User32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Used by SendInput to store information for synthesizing input events such as keystrokes, mouse movement, and mouse clicks.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-input
|
|
||||||
/// </remarks>
|
|
||||||
[SuppressMessage("SA1307", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Parameter name matches Win32 api")]
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
internal readonly struct INPUT
|
|
||||||
{
|
|
||||||
public readonly INPUT_TYPE type;
|
|
||||||
public readonly DUMMYUNIONNAME data;
|
|
||||||
|
|
||||||
public INPUT(INPUT_TYPE type, DUMMYUNIONNAME data)
|
|
||||||
{
|
|
||||||
this.type = type;
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int Size =>
|
|
||||||
Marshal.SizeOf(typeof(INPUT));
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public readonly struct DUMMYUNIONNAME
|
|
||||||
{
|
|
||||||
[FieldOffset(0)]
|
|
||||||
public readonly MOUSEINPUT mi;
|
|
||||||
[FieldOffset(0)]
|
|
||||||
public readonly KEYBDINPUT ki;
|
|
||||||
[FieldOffset(0)]
|
|
||||||
public readonly HARDWAREINPUT hi;
|
|
||||||
|
|
||||||
public DUMMYUNIONNAME(MOUSEINPUT mi)
|
|
||||||
{
|
|
||||||
this.mi = mi;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
[SuppressMessage("SA1310", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "Names match Win32 api")]
|
|
||||||
internal static partial class User32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The type of the input event.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-input
|
|
||||||
/// </remarks>
|
|
||||||
internal enum INPUT_TYPE : uint
|
|
||||||
{
|
|
||||||
INPUT_MOUSE = 0,
|
|
||||||
INPUT_KEYBOARD = 1,
|
|
||||||
INPUT_HARDWARE = 2,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using static MouseJump.Common.NativeMethods.Core;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class User32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Contains information about a simulated keyboard event.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-keybdinput
|
|
||||||
/// </remarks>
|
|
||||||
[SuppressMessage("SA1307", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Parameter name matches Win32 api")]
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
internal readonly struct KEYBDINPUT
|
|
||||||
{
|
|
||||||
public readonly WORD wVk;
|
|
||||||
public readonly WORD wScan;
|
|
||||||
public readonly DWORD dwFlags;
|
|
||||||
public readonly DWORD time;
|
|
||||||
public readonly ULONG_PTR dwExtraInfo;
|
|
||||||
|
|
||||||
public KEYBDINPUT(
|
|
||||||
WORD wVk,
|
|
||||||
WORD wScan,
|
|
||||||
DWORD dwFlags,
|
|
||||||
DWORD time,
|
|
||||||
ULONG_PTR dwExtraInfo)
|
|
||||||
{
|
|
||||||
this.wVk = wVk;
|
|
||||||
this.wScan = wScan;
|
|
||||||
this.dwFlags = dwFlags;
|
|
||||||
this.time = time;
|
|
||||||
this.dwExtraInfo = dwExtraInfo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class User32
|
|
||||||
{
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput
|
|
||||||
/// </remarks>
|
|
||||||
internal readonly struct LPINPUT
|
|
||||||
{
|
|
||||||
public static readonly LPINPUT Null = new(IntPtr.Zero);
|
|
||||||
|
|
||||||
public readonly IntPtr Value;
|
|
||||||
|
|
||||||
public LPINPUT(IntPtr value)
|
|
||||||
{
|
|
||||||
this.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LPINPUT(INPUT[] values)
|
|
||||||
{
|
|
||||||
this.Value = LPINPUT.ToPtr(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
public INPUT ToStructure()
|
|
||||||
{
|
|
||||||
return Marshal.PtrToStructure<INPUT>(this.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<INPUT> ToStructure(int count)
|
|
||||||
{
|
|
||||||
var ptr = this.Value;
|
|
||||||
var size = INPUT.Size;
|
|
||||||
for (var i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
yield return Marshal.PtrToStructure<INPUT>(this.Value);
|
|
||||||
ptr += size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IntPtr ToPtr(INPUT[] values)
|
|
||||||
{
|
|
||||||
var mem = Marshal.AllocHGlobal(INPUT.Size * values.Length);
|
|
||||||
var ptr = mem;
|
|
||||||
var size = INPUT.Size;
|
|
||||||
foreach (var value in values)
|
|
||||||
{
|
|
||||||
Marshal.StructureToPtr(value, ptr, false);
|
|
||||||
ptr += size;
|
|
||||||
}
|
|
||||||
|
|
||||||
return mem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Free()
|
|
||||||
{
|
|
||||||
Marshal.FreeHGlobal(this.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{this.GetType().Name}({this.Value})";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using static MouseJump.Common.NativeMethods.Core;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class User32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Contains information about a simulated mouse event.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-mouseinput
|
|
||||||
/// </remarks>
|
|
||||||
[SuppressMessage("SA1307", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Parameter name matches Win32 api")]
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
internal readonly struct MOUSEINPUT
|
|
||||||
{
|
|
||||||
public readonly int dx;
|
|
||||||
public readonly int dy;
|
|
||||||
public readonly DWORD mouseData;
|
|
||||||
public readonly MOUSE_EVENT_FLAGS dwFlags;
|
|
||||||
public readonly DWORD time;
|
|
||||||
public readonly ULONG_PTR dwExtraInfo;
|
|
||||||
|
|
||||||
public MOUSEINPUT(
|
|
||||||
int dx,
|
|
||||||
int dy,
|
|
||||||
DWORD mouseData,
|
|
||||||
MOUSE_EVENT_FLAGS dwFlags,
|
|
||||||
DWORD time,
|
|
||||||
ULONG_PTR dwExtraInfo)
|
|
||||||
{
|
|
||||||
this.dx = dx;
|
|
||||||
this.dy = dy;
|
|
||||||
this.mouseData = mouseData;
|
|
||||||
this.dwFlags = dwFlags;
|
|
||||||
this.time = time;
|
|
||||||
this.dwExtraInfo = dwExtraInfo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
[SuppressMessage("SA1310", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "Names match Win32 api")]
|
|
||||||
internal static partial class User32
|
|
||||||
{
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-mouseinput
|
|
||||||
/// </remarks>
|
|
||||||
[Flags]
|
|
||||||
internal enum MOUSE_EVENT_FLAGS : uint
|
|
||||||
{
|
|
||||||
MOUSEEVENTF_MOVE = 0x0001,
|
|
||||||
MOUSEEVENTF_LEFTDOWN = 0x0002,
|
|
||||||
MOUSEEVENTF_LEFTUP = 0x0004,
|
|
||||||
MOUSEEVENTF_RIGHTDOWN = 0x0008,
|
|
||||||
MOUSEEVENTF_RIGHTUP = 0x0010,
|
|
||||||
MOUSEEVENTF_MIDDLEDOWN = 0x0020,
|
|
||||||
MOUSEEVENTF_MIDDLEUP = 0x0040,
|
|
||||||
MOUSEEVENTF_XDOWN = 0x0080,
|
|
||||||
MOUSEEVENTF_XUP = 0x0100,
|
|
||||||
MOUSEEVENTF_WHEEL = 0x0800,
|
|
||||||
MOUSEEVENTF_HWHEEL = 0x1000,
|
|
||||||
MOUSEEVENTF_MOVE_NOCOALESCE = 0x2000,
|
|
||||||
MOUSEEVENTF_VIRTUALDESK = 0x4000,
|
|
||||||
MOUSEEVENTF_ABSOLUTE = 0x8000,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using static MouseJump.Common.NativeMethods.Core;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class User32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Synthesizes keystrokes, mouse motions, and button clicks.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// The function returns the number of events that it successfully inserted into the keyboard or mouse input stream.
|
|
||||||
/// If the function returns zero, the input was already blocked by another thread.
|
|
||||||
/// To get extended error information, call GetLastError.
|
|
||||||
/// </returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput
|
|
||||||
/// </remarks>
|
|
||||||
[LibraryImport(Libraries.User32, SetLastError = true)]
|
|
||||||
internal static partial UINT SendInput(
|
|
||||||
UINT cInputs,
|
|
||||||
LPINPUT pInputs,
|
|
||||||
int cbSize);
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using static MouseJump.Common.NativeMethods.Core;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class User32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Retrieves the position of the mouse cursor, in screen coordinates.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// Returns nonzero if successful or zero otherwise.
|
|
||||||
/// To get extended error information, call GetLastError.
|
|
||||||
/// </returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getcursorpos
|
|
||||||
/// </remarks>
|
|
||||||
[LibraryImport(Libraries.User32, SetLastError = true)]
|
|
||||||
internal static partial BOOL GetCursorPos(
|
|
||||||
LPPOINT lpPoint);
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using static MouseJump.Common.NativeMethods.Core;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class User32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Retrieves a handle to the desktop window. The desktop window covers the entire
|
|
||||||
/// screen. The desktop window is the area on top of which other windows are painted.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// The return value is a handle to the desktop window.
|
|
||||||
/// </returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdesktopwindow
|
|
||||||
/// </remarks>
|
|
||||||
[LibraryImport(Libraries.User32)]
|
|
||||||
internal static partial HWND GetDesktopWindow();
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class User32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Retrieves the specified system metric or system configuration setting.
|
|
||||||
///
|
|
||||||
/// Note that all dimensions retrieved by GetSystemMetrics are in pixels.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// If the function succeeds, the return value is the requested system metric or configuration setting.
|
|
||||||
/// If the function fails, the return value is 0. GetLastError does not provide extended error information.
|
|
||||||
/// </returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics
|
|
||||||
/// </remarks>
|
|
||||||
[LibraryImport(Libraries.User32)]
|
|
||||||
internal static partial int GetSystemMetrics(
|
|
||||||
SYSTEM_METRICS_INDEX smIndex);
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
[SuppressMessage("SA1310", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "Names match Win32 api")]
|
|
||||||
internal static partial class User32
|
|
||||||
{
|
|
||||||
internal enum SYSTEM_METRICS_INDEX : uint
|
|
||||||
{
|
|
||||||
SM_ARRANGE = 56,
|
|
||||||
SM_CLEANBOOT = 67,
|
|
||||||
SM_CMONITORS = 80,
|
|
||||||
SM_CMOUSEBUTTONS = 43,
|
|
||||||
SM_CONVERTIBLESLATEMODE = 0x2003,
|
|
||||||
SM_CXBORDER = 5,
|
|
||||||
SM_CXCURSOR = 13,
|
|
||||||
SM_CXDLGFRAME = 7,
|
|
||||||
SM_CXDOUBLECLK = 36,
|
|
||||||
SM_CXDRAG = 68,
|
|
||||||
SM_CXEDGE = 45,
|
|
||||||
SM_CXFIXEDFRAME = SM_CXDLGFRAME,
|
|
||||||
SM_CXFOCUSBORDER = 83,
|
|
||||||
SM_CXFRAME = 32,
|
|
||||||
SM_CXFULLSCREEN = 16,
|
|
||||||
SM_CXHSCROLL = 21,
|
|
||||||
SM_CXHTHUMB = 10,
|
|
||||||
SM_CXICON = 11,
|
|
||||||
SM_CXICONSPACING = 38,
|
|
||||||
SM_CXMAXIMIZED = 61,
|
|
||||||
SM_CXMAXTRACK = 59,
|
|
||||||
SM_CXMENUCHECK = 71,
|
|
||||||
SM_CXMENUSIZE = 54,
|
|
||||||
SM_CXMIN = 28,
|
|
||||||
SM_CXMINIMIZED = 57,
|
|
||||||
SM_CXMINSPACING = 47,
|
|
||||||
SM_CXMINTRACK = 34,
|
|
||||||
SM_CXPADDEDBORDER = 92,
|
|
||||||
SM_CXSCREEN = 0,
|
|
||||||
SM_CXSIZE = 30,
|
|
||||||
SM_CXSIZEFRAME = SM_CXFRAME,
|
|
||||||
SM_CXSMICON = 49,
|
|
||||||
SM_CXSMSIZE = 52,
|
|
||||||
SM_CXVIRTUALSCREEN = 78,
|
|
||||||
SM_CXVSCROLL = 2,
|
|
||||||
SM_CYBORDER = 6,
|
|
||||||
SM_CYCAPTION = 4,
|
|
||||||
SM_CYCURSOR = 14,
|
|
||||||
SM_CYDLGFRAME = 8,
|
|
||||||
SM_CYDOUBLECLK = 37,
|
|
||||||
SM_CYDRAG = 69,
|
|
||||||
SM_CYEDGE = 46,
|
|
||||||
SM_CYFIXEDFRAME = SM_CYDLGFRAME,
|
|
||||||
SM_CYFOCUSBORDER = 84,
|
|
||||||
SM_CYFRAME = 33,
|
|
||||||
SM_CYFULLSCREEN = 17,
|
|
||||||
SM_CYHSCROLL = 3,
|
|
||||||
SM_CYICON = 12,
|
|
||||||
SM_CYICONSPACING = 39,
|
|
||||||
SM_CYKANJIWINDOW = 18,
|
|
||||||
SM_CYMAXIMIZED = 62,
|
|
||||||
SM_CYMAXTRACK = 60,
|
|
||||||
SM_CYMENU = 15,
|
|
||||||
SM_CYMENUCHECK = 72,
|
|
||||||
SM_CYMENUSIZE = 55,
|
|
||||||
SM_CYMIN = 29,
|
|
||||||
SM_CYMINIMIZED = 58,
|
|
||||||
SM_CYMINSPACING = 48,
|
|
||||||
SM_CYMINTRACK = 35,
|
|
||||||
SM_CYSCREEN = 1,
|
|
||||||
SM_CYSIZE = 31,
|
|
||||||
SM_CYSIZEFRAME = SM_CYFRAME,
|
|
||||||
SM_CYSMCAPTION = 51,
|
|
||||||
SM_CYSMICON = 50,
|
|
||||||
SM_CYSMSIZE = 53,
|
|
||||||
SM_CYVIRTUALSCREEN = 79,
|
|
||||||
SM_CYVSCROLL = 20,
|
|
||||||
SM_CYVTHUMB = 9,
|
|
||||||
SM_DBCSENABLED = 42,
|
|
||||||
SM_DEBUG = 22,
|
|
||||||
SM_DIGITIZER = 94,
|
|
||||||
SM_IMMENABLED = 82,
|
|
||||||
SM_MAXIMUMTOUCHES = 95,
|
|
||||||
SM_MEDIACENTER = 87,
|
|
||||||
SM_MENUDROPALIGNMENT = 40,
|
|
||||||
SM_MIDEASTENABLED = 74,
|
|
||||||
SM_MOUSEPRESENT = 19,
|
|
||||||
SM_MOUSEHORIZONTALWHEELPRESENT = 91,
|
|
||||||
SM_MOUSEWHEELPRESENT = 75,
|
|
||||||
SM_NETWORK = 63,
|
|
||||||
SM_PENWINDOWS = 41,
|
|
||||||
SM_REMOTECONTROL = 0x2001,
|
|
||||||
SM_REMOTESESSION = 0x1000,
|
|
||||||
SM_SAMEDISPLAYFORMA = 81,
|
|
||||||
SM_SECURE = 44,
|
|
||||||
SM_SERVERR2 = 89,
|
|
||||||
SM_SHOWSOUNDS = 70,
|
|
||||||
SM_SHUTTINGDOWN = 0x2000,
|
|
||||||
SM_SLOWMACHINE = 73,
|
|
||||||
SM_STARTER = 88,
|
|
||||||
SM_SWAPBUTTON = 23,
|
|
||||||
SM_SYSTEMDOCKED = 0x2004,
|
|
||||||
SM_TABLETPC = 86,
|
|
||||||
SM_XVIRTUALSCREEN = 76,
|
|
||||||
SM_YVIRTUALSCREEN = 77,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using static MouseJump.Common.NativeMethods.Core;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.NativeMethods;
|
|
||||||
|
|
||||||
internal static partial class User32
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Moves the cursor to the specified screen coordinates. If the new coordinates are not within
|
|
||||||
/// the screen rectangle set by the most recent ClipCursor function call, the system automatically
|
|
||||||
/// adjusts the coordinates so that the cursor stays within the rectangle.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// Returns nonzero if successful or zero otherwise.
|
|
||||||
/// To get extended error information, call GetLastError.
|
|
||||||
/// </returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getcursorpos
|
|
||||||
/// </remarks>
|
|
||||||
[LibraryImport(Libraries.User32, SetLastError = true)]
|
|
||||||
internal static partial BOOL SetCursorPos(
|
|
||||||
int X,
|
|
||||||
int Y);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
namespace MouseJump.HotKeys.UnitTests;
|
||||||
|
|
||||||
|
public static class KeystrokeTests
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class ParseTests
|
||||||
|
{
|
||||||
|
public sealed class TestCase
|
||||||
|
{
|
||||||
|
public TestCase(string value, Keystroke expectedResult)
|
||||||
|
{
|
||||||
|
this.Value = value;
|
||||||
|
this.ExpectedResult = expectedResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Value { get; }
|
||||||
|
|
||||||
|
public Keystroke ExpectedResult { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> GetTestCases()
|
||||||
|
{
|
||||||
|
// individual modifiers / keys
|
||||||
|
yield return new object[] { new TestCase("CTRL", new(Keys.None, KeyModifiers.Control)) };
|
||||||
|
yield return new object[] { new TestCase("ALT", new(Keys.None, KeyModifiers.Alt)) };
|
||||||
|
yield return new object[] { new TestCase("SHIFT", new(Keys.None, KeyModifiers.Shift)) };
|
||||||
|
yield return new object[] { new TestCase("WIN", new(Keys.None, KeyModifiers.Windows)) };
|
||||||
|
yield return new object[] { new TestCase("F", new(Keys.F, KeyModifiers.None)) };
|
||||||
|
|
||||||
|
// multiple modifiers
|
||||||
|
yield return new object[] { new TestCase("CTRL + ALT + SHIFT", new(Keys.None, KeyModifiers.Control | KeyModifiers.Alt | KeyModifiers.Shift)) };
|
||||||
|
yield return new object[] { new TestCase("SHIFT + ALT + CTRL", new(Keys.None, KeyModifiers.Control | KeyModifiers.Alt | KeyModifiers.Shift)) };
|
||||||
|
|
||||||
|
// modifiers and keys
|
||||||
|
yield return new object[] { new TestCase("CTRL + ALT + SHIFT + F", new(Keys.F, KeyModifiers.Control | KeyModifiers.Alt | KeyModifiers.Shift)) };
|
||||||
|
yield return new object[] { new TestCase("CTRL + ALT + SHIFT + WIN + F", new(Keys.F, KeyModifiers.Control | KeyModifiers.Alt | KeyModifiers.Shift | KeyModifiers.Windows)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[DynamicData(nameof(GetTestCases))]
|
||||||
|
public void RunTestCases(TestCase data)
|
||||||
|
{
|
||||||
|
var expected = data.ExpectedResult;
|
||||||
|
var result = Keystroke.TryParse(data.Value, out var actual);
|
||||||
|
Assert.IsTrue(result);
|
||||||
|
if (actual == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.AreEqual(expected.Key, actual.Key);
|
||||||
|
Assert.AreEqual(expected.Modifiers, actual.Modifiers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||||
|
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<ProjectGuid>{C1434F2F-D34A-4E4E-9471-4041066B8EC4}</ProjectGuid>
|
||||||
|
<AssemblyName>PowerToys.MouseJump.HotKeys.UnitTests</AssemblyName>
|
||||||
|
<AssemblyTitle>PowerToys.MouseJump.HotKeys.UnitTests</AssemblyTitle>
|
||||||
|
<AssemblyDescription>PowerToys MouseJump.HotKeys.UnitTests</AssemblyDescription>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\tests\MouseJump.HotKeys.UnitTests\</OutputPath>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||||
|
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MouseJump.HotKeys\MouseJump.HotKeys.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="MSTest" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
18
src/modules/MouseUtils/MouseJump.HotKeys/KeyModifiers.cs
Normal file
18
src/modules/MouseUtils/MouseJump.HotKeys/KeyModifiers.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using Windows.Win32.UI.Input.KeyboardAndMouse;
|
||||||
|
|
||||||
|
namespace MouseJump.HotKeys;
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum KeyModifiers
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Alt = (int)HOT_KEY_MODIFIERS.MOD_ALT,
|
||||||
|
Control = (int)HOT_KEY_MODIFIERS.MOD_CONTROL,
|
||||||
|
Shift = (int)HOT_KEY_MODIFIERS.MOD_SHIFT,
|
||||||
|
Windows = (int)HOT_KEY_MODIFIERS.MOD_WIN,
|
||||||
|
NoRepeat = (int)HOT_KEY_MODIFIERS.MOD_NOREPEAT,
|
||||||
|
}
|
||||||
1005
src/modules/MouseUtils/MouseJump.HotKeys/Keys.cs
Normal file
1005
src/modules/MouseUtils/MouseJump.HotKeys/Keys.cs
Normal file
File diff suppressed because it is too large
Load Diff
120
src/modules/MouseUtils/MouseJump.HotKeys/Keystroke.cs
Normal file
120
src/modules/MouseUtils/MouseJump.HotKeys/Keystroke.cs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace MouseJump.HotKeys;
|
||||||
|
|
||||||
|
public sealed class Keystroke
|
||||||
|
{
|
||||||
|
public Keystroke(Keys key, KeyModifiers modifiers)
|
||||||
|
{
|
||||||
|
this.Key = key;
|
||||||
|
this.Modifiers = modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Keys Key
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyModifiers Modifiers
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Keystroke Parse(string s)
|
||||||
|
{
|
||||||
|
if (!Keystroke.TryParse(s, out var result))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid argument format.", nameof(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryParse(string s, [NotNullWhen(true)] out Keystroke? result)
|
||||||
|
{
|
||||||
|
// see https://github.com/microsoft/terminal/blob/14919073a12fc0ecb4a9805cc183fdd68d30c4b6/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp#L124
|
||||||
|
// for an alternate implementation
|
||||||
|
|
||||||
|
// e.g. "CTRL + ALT + SHIFT + F"
|
||||||
|
if (string.IsNullOrEmpty(s))
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts = s
|
||||||
|
.Replace(" ", string.Empty)
|
||||||
|
.ToUpperInvariant()
|
||||||
|
.Split('+');
|
||||||
|
|
||||||
|
var keystroke = (Keys: Keys.None, Modifiers: KeyModifiers.None);
|
||||||
|
|
||||||
|
foreach (var part in parts)
|
||||||
|
{
|
||||||
|
switch (part)
|
||||||
|
{
|
||||||
|
case "CTRL":
|
||||||
|
keystroke.Modifiers |= KeyModifiers.Control;
|
||||||
|
break;
|
||||||
|
case "ALT":
|
||||||
|
keystroke.Modifiers |= KeyModifiers.Alt;
|
||||||
|
break;
|
||||||
|
case "SHIFT":
|
||||||
|
keystroke.Modifiers |= KeyModifiers.Shift;
|
||||||
|
break;
|
||||||
|
case "WIN":
|
||||||
|
keystroke.Modifiers |= KeyModifiers.Windows;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (!Enum.TryParse<Keys>(part, out var key))
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
keystroke.Keys = key;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = new Keystroke(
|
||||||
|
keystroke.Keys, keystroke.Modifiers);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var parts = new List<string>();
|
||||||
|
|
||||||
|
if (this.Modifiers.HasFlag(KeyModifiers.Control))
|
||||||
|
{
|
||||||
|
parts.Add("CTRL");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.Modifiers.HasFlag(KeyModifiers.Alt))
|
||||||
|
{
|
||||||
|
parts.Add("ALT");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.Modifiers.HasFlag(KeyModifiers.Shift))
|
||||||
|
{
|
||||||
|
parts.Add("SHIFT");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.Modifiers.HasFlag(KeyModifiers.Windows))
|
||||||
|
{
|
||||||
|
parts.Add("WIN");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.Key != Keys.None)
|
||||||
|
{
|
||||||
|
parts.Add(this.Key.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join(" + ", parts);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||||
|
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<ProjectGuid>{A8D81CE0-7E24-4079-AD45-6BCE6747FE8B}</ProjectGuid>
|
||||||
|
<AssemblyName>PowerToys.MouseJump.HotKeys</AssemblyName>
|
||||||
|
<AssemblyTitle>PowerToys.MouseJump.HotKeys</AssemblyTitle>
|
||||||
|
<AssemblyDescription>PowerToys MouseJump.HotKeys</AssemblyDescription>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<OutputPath>$(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps</OutputPath>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||||
|
|
||||||
|
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MouseJump.Common\MouseJump.Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Windows.CsWin32">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
HOT_KEY_MODIFIERS
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MouseJump.Models.Drawing;
|
||||||
|
using MouseJump.Models.Styles;
|
||||||
|
|
||||||
|
namespace MouseJump.Models.UnitTests.Drawing;
|
||||||
|
|
||||||
|
public static class BoxBoundsTests
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class CreateFromContentBounds
|
||||||
|
{
|
||||||
|
public sealed class TestCase
|
||||||
|
{
|
||||||
|
public TestCase(RectangleInfo contentBounds, BoxStyle boxStyle, BoxBounds expectedResult)
|
||||||
|
{
|
||||||
|
this.ContentBounds = contentBounds;
|
||||||
|
this.BoxStyle = boxStyle;
|
||||||
|
this.ExpectedResult = expectedResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RectangleInfo ContentBounds { get; }
|
||||||
|
|
||||||
|
public BoxStyle BoxStyle { get; }
|
||||||
|
|
||||||
|
public BoxBounds ExpectedResult { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> GetTestCases()
|
||||||
|
{
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
new TestCase(
|
||||||
|
contentBounds: new(100, 100, 800, 600),
|
||||||
|
boxStyle: new(
|
||||||
|
marginStyle: new(3),
|
||||||
|
borderStyle: new(Color.Red, 5, 0),
|
||||||
|
paddingStyle: new(7),
|
||||||
|
backgroundStyle: BackgroundStyle.Empty),
|
||||||
|
expectedResult: new(
|
||||||
|
outerBounds: new(85, 85, 830, 630),
|
||||||
|
marginBounds: new(85, 85, 830, 630),
|
||||||
|
borderBounds: new(88, 88, 824, 624),
|
||||||
|
paddingBounds: new(93, 93, 814, 614),
|
||||||
|
contentBounds: new(100, 100, 800, 600))),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[DynamicData(nameof(GetTestCases))]
|
||||||
|
public void RunTestCases(TestCase data)
|
||||||
|
{
|
||||||
|
var actual = BoxBounds.CreateFromContentBounds(data.ContentBounds, data.BoxStyle);
|
||||||
|
var expected = data.ExpectedResult;
|
||||||
|
Assert.AreEqual(
|
||||||
|
JsonSerializer.Serialize(expected),
|
||||||
|
JsonSerializer.Serialize(actual));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public sealed class CreateFromOuterBounds
|
||||||
|
{
|
||||||
|
public sealed class TestCase
|
||||||
|
{
|
||||||
|
public TestCase(RectangleInfo outerBounds, BoxStyle boxStyle, BoxBounds expectedResult)
|
||||||
|
{
|
||||||
|
this.OuterBounds = outerBounds;
|
||||||
|
this.BoxStyle = boxStyle;
|
||||||
|
this.ExpectedResult = expectedResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RectangleInfo OuterBounds { get; }
|
||||||
|
|
||||||
|
public BoxStyle BoxStyle { get; }
|
||||||
|
|
||||||
|
public BoxBounds ExpectedResult { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> GetTestCases()
|
||||||
|
{
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
new TestCase(
|
||||||
|
outerBounds: new(85, 85, 830, 630),
|
||||||
|
boxStyle: new(
|
||||||
|
marginStyle: new(3),
|
||||||
|
borderStyle: new(Color.Red, 5, 0),
|
||||||
|
paddingStyle: new(7),
|
||||||
|
backgroundStyle: BackgroundStyle.Empty),
|
||||||
|
expectedResult: new(
|
||||||
|
outerBounds: new(85, 85, 830, 630),
|
||||||
|
marginBounds: new(85, 85, 830, 630),
|
||||||
|
borderBounds: new(88, 88, 824, 624),
|
||||||
|
paddingBounds: new(93, 93, 814, 614),
|
||||||
|
contentBounds: new(100, 100, 800, 600))),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[DynamicData(nameof(GetTestCases))]
|
||||||
|
public void RunTestCases(TestCase data)
|
||||||
|
{
|
||||||
|
var actual = BoxBounds.CreateFromOuterBounds(data.OuterBounds, data.BoxStyle);
|
||||||
|
var expected = data.ExpectedResult;
|
||||||
|
Assert.AreEqual(
|
||||||
|
JsonSerializer.Serialize(expected),
|
||||||
|
JsonSerializer.Serialize(actual));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,11 +3,10 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MouseJump.Common.Models.Drawing;
|
using MouseJump.Models.Drawing;
|
||||||
|
|
||||||
namespace MouseJump.Common.UnitTests.Models.Drawing;
|
namespace MouseJump.Models.UnitTests.Drawing;
|
||||||
|
|
||||||
[TestClass]
|
|
||||||
public static class RectangleInfoTests
|
public static class RectangleInfoTests
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
@@ -49,7 +48,7 @@ public static class RectangleInfoTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DynamicData(nameof(GetTestCases), DynamicDataSourceType.Method)]
|
[DynamicData(nameof(GetTestCases))]
|
||||||
public void RunTestCases(TestCase data)
|
public void RunTestCases(TestCase data)
|
||||||
{
|
{
|
||||||
var actual = data.Rectangle.Center(data.Point);
|
var actual = data.Rectangle.Center(data.Point);
|
||||||
@@ -62,7 +61,7 @@ public static class RectangleInfoTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class ClampTests
|
public sealed class MoveInsideTests
|
||||||
{
|
{
|
||||||
public sealed class TestCase
|
public sealed class TestCase
|
||||||
{
|
{
|
||||||
@@ -126,10 +125,10 @@ public static class RectangleInfoTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DynamicData(nameof(GetTestCases), DynamicDataSourceType.Method)]
|
[DynamicData(nameof(GetTestCases))]
|
||||||
public void RunTestCases(TestCase data)
|
public void RunTestCases(TestCase data)
|
||||||
{
|
{
|
||||||
var actual = data.Inner.Clamp(data.Outer);
|
var actual = data.Inner.MoveInside(data.Outer);
|
||||||
var expected = data.ExpectedResult;
|
var expected = data.ExpectedResult;
|
||||||
Assert.AreEqual(expected.X, actual.X);
|
Assert.AreEqual(expected.X, actual.X);
|
||||||
Assert.AreEqual(expected.Y, actual.Y);
|
Assert.AreEqual(expected.Y, actual.Y);
|
||||||
@@ -3,11 +3,10 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MouseJump.Common.Models.Drawing;
|
using MouseJump.Models.Drawing;
|
||||||
|
|
||||||
namespace MouseJump.Common.UnitTests.Models.Drawing;
|
namespace MouseJump.Models.UnitTests.Drawing;
|
||||||
|
|
||||||
[TestClass]
|
|
||||||
public static class SizeInfoTests
|
public static class SizeInfoTests
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
@@ -50,7 +49,7 @@ public static class SizeInfoTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DynamicData(nameof(GetTestCases), DynamicDataSourceType.Method)]
|
[DynamicData(nameof(GetTestCases))]
|
||||||
public void RunTestCases(TestCase data)
|
public void RunTestCases(TestCase data)
|
||||||
{
|
{
|
||||||
var actual = data.Source.ScaleToFit(data.Bounds, out var scalingRatio);
|
var actual = data.Source.ScaleToFit(data.Bounds, out var scalingRatio);
|
||||||
@@ -98,7 +97,7 @@ public static class SizeInfoTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DynamicData(nameof(GetTestCases), DynamicDataSourceType.Method)]
|
[DynamicData(nameof(GetTestCases))]
|
||||||
public void RunTestCases(TestCase data)
|
public void RunTestCases(TestCase data)
|
||||||
{
|
{
|
||||||
var actual = data.Obj.ScaleToFitRatio(data.Bounds);
|
var actual = data.Obj.ScaleToFitRatio(data.Bounds);
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||||
|
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<ProjectGuid>{AB6305DB-CA33-4EF4-BA40-B0CFC10E7B9F}</ProjectGuid>
|
||||||
|
<AssemblyName>PowerToys.MouseJump.Models.UnitTests</AssemblyName>
|
||||||
|
<AssemblyTitle>PowerToys.MouseJump.Models.UnitTests</AssemblyTitle>
|
||||||
|
<AssemblyDescription>PowerToys MouseJump.Models.UnitTests</AssemblyDescription>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\tests\MouseJump.Models.UnitTests\</OutputPath>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||||
|
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MouseJump.Models\MouseJump.Models.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="MSTest" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
using MouseJump.Models.Drawing;
|
||||||
|
|
||||||
|
namespace MouseJump.Models.Display;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the entire desktop or virtual screen for a single logical device.
|
||||||
|
/// </summary>
|
||||||
|
public sealed record DesktopInfo
|
||||||
|
{
|
||||||
|
public DesktopInfo(IEnumerable<ScreenInfo> screens)
|
||||||
|
{
|
||||||
|
this.Screens = (screens ?? throw new ArgumentNullException(nameof(screens))).ToList().AsReadOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyCollection<ScreenInfo> Screens
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RectangleInfo GetCombinedDisplayArea()
|
||||||
|
{
|
||||||
|
return (this.Screens.Count == 0)
|
||||||
|
? throw new InvalidOperationException($"{nameof(GetCombinedDisplayArea)} requires one or more screens.")
|
||||||
|
: RectangleInfo.Union(this.Screens.Select(screen => screen.DisplayArea));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
using MouseJump.Models.Drawing;
|
||||||
|
|
||||||
|
namespace MouseJump.Models.Display;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a device whose screens are rendered in the preview image.
|
||||||
|
/// </summary>
|
||||||
|
public sealed record DeviceInfo
|
||||||
|
{
|
||||||
|
public DeviceInfo(string hostname, bool localhost, IEnumerable<ScreenInfo> screens)
|
||||||
|
{
|
||||||
|
this.Hostname = hostname ?? throw new ArgumentNullException(nameof(hostname));
|
||||||
|
this.Localhost = localhost;
|
||||||
|
this.Screens = (screens ?? throw new ArgumentNullException(nameof(screens))).ToList().AsReadOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Hostname
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Localhost
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyCollection<ScreenInfo> Screens
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RectangleInfo GetCombinedDisplayArea()
|
||||||
|
{
|
||||||
|
return RectangleInfo.Union(this.Screens.Select(screen => screen.DisplayArea));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace MouseJump.Models.Display;
|
||||||
|
|
||||||
|
public sealed record DisplayInfo
|
||||||
|
{
|
||||||
|
public DisplayInfo(IEnumerable<DeviceInfo> devices)
|
||||||
|
{
|
||||||
|
this.Devices = (devices ?? throw new ArgumentNullException(nameof(devices))).ToList().AsReadOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyCollection<DeviceInfo> Devices
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
namespace MouseJump.Models.Display;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// See https://github.com/dotnet/winforms/blob/main/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/HighDpiMode.cs#L26
|
||||||
|
/// </summary>
|
||||||
|
public enum HighDpiMode
|
||||||
|
{
|
||||||
|
DpiUnaware,
|
||||||
|
SystemAware,
|
||||||
|
PerMonitor,
|
||||||
|
PerMonitorV2,
|
||||||
|
DpiUnawareGdiScaled,
|
||||||
|
}
|
||||||
@@ -2,40 +2,49 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
namespace MouseJump.Common.Models.Drawing;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
using MouseJump.Models.Drawing;
|
||||||
|
|
||||||
|
namespace MouseJump.Models.Display;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Immutable version of a System.Windows.Forms.Screen object so we don't need to
|
/// Immutable version of a System.Windows.Forms.Screen object so we don't need to
|
||||||
/// take a dependency on WinForms just for screen info.
|
/// take a dependency on WinForms just for screen info.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ScreenInfo
|
public sealed record ScreenInfo
|
||||||
{
|
{
|
||||||
public ScreenInfo(int handle, bool primary, RectangleInfo displayArea, RectangleInfo workingArea)
|
public ScreenInfo(nint handle, bool primary, RectangleInfo displayArea, RectangleInfo? workingArea)
|
||||||
{
|
{
|
||||||
// this.Handle is a HMONITOR that has been cast to an int because we don't want
|
// this.Handle is a HMONITOR that has been cast to an int because we don't want
|
||||||
// to expose the HMONITOR type outside the current assembly.
|
// to expose the HMONITOR type outside the current assembly.
|
||||||
this.Handle = handle;
|
this.Handle = handle;
|
||||||
this.Primary = primary;
|
this.Primary = primary;
|
||||||
this.DisplayArea = displayArea ?? throw new ArgumentNullException(nameof(displayArea));
|
this.DisplayArea = displayArea ?? throw new ArgumentNullException(nameof(displayArea));
|
||||||
this.WorkingArea = workingArea ?? throw new ArgumentNullException(nameof(workingArea));
|
this.WorkingArea = workingArea;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Handle
|
[JsonPropertyName("handle")]
|
||||||
|
[JsonConverter(typeof(NintJsonConverter))]
|
||||||
|
public nint Handle
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("primary")]
|
||||||
public bool Primary
|
public bool Primary
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("displayArea")]
|
||||||
public RectangleInfo DisplayArea
|
public RectangleInfo DisplayArea
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RectangleInfo WorkingArea
|
[JsonPropertyName("workingArea")]
|
||||||
|
public RectangleInfo? WorkingArea
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
}
|
}
|
||||||
167
src/modules/MouseUtils/MouseJump.Models/Drawing/BoxBounds.cs
Normal file
167
src/modules/MouseUtils/MouseJump.Models/Drawing/BoxBounds.cs
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using MouseJump.Models.Styles;
|
||||||
|
|
||||||
|
namespace MouseJump.Models.Drawing;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the bounds of a layout box, including outer, margin, border, padding,
|
||||||
|
/// and content bounds. This class is used to model the box layout as described
|
||||||
|
/// in the CSS box model.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class BoxBounds
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
|
||||||
|
see https://www.w3schools.com/css/css_boxmodel.asp
|
||||||
|
|
||||||
|
+--------------[bounds]---------------+
|
||||||
|
|▒▒▒▒▒▒▒▒▒▒▒▒▒▒[margin]▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒|
|
||||||
|
|▒▒▓▓▓▓▓▓▓▓▓▓▓▓[border]▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒|
|
||||||
|
|▒▒▓▓░░░░░░░░░░[padding]░░░░░░░░░░▓▓▒▒|
|
||||||
|
|▒▒▓▓░░ ░░▓▓▒▒|
|
||||||
|
|▒▒▓▓░░ ░░▓▓▒▒|
|
||||||
|
|▒▒▓▓░░ [content] ░░▓▓▒▒|
|
||||||
|
|▒▒▓▓░░ ░░▓▓▒▒|
|
||||||
|
|▒▒▓▓░░ ░░▓▓▒▒|
|
||||||
|
|▒▒▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓▒▒|
|
||||||
|
|▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒|
|
||||||
|
|▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒|
|
||||||
|
+-------------------------------------+
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static readonly BoxBounds Empty = new(
|
||||||
|
outerBounds: RectangleInfo.Empty,
|
||||||
|
marginBounds: RectangleInfo.Empty,
|
||||||
|
borderBounds: RectangleInfo.Empty,
|
||||||
|
paddingBounds: RectangleInfo.Empty,
|
||||||
|
contentBounds: RectangleInfo.Empty,
|
||||||
|
isEmpty: true);
|
||||||
|
|
||||||
|
public BoxBounds(
|
||||||
|
RectangleInfo outerBounds,
|
||||||
|
RectangleInfo marginBounds,
|
||||||
|
RectangleInfo borderBounds,
|
||||||
|
RectangleInfo paddingBounds,
|
||||||
|
RectangleInfo contentBounds)
|
||||||
|
: this(
|
||||||
|
outerBounds,
|
||||||
|
marginBounds,
|
||||||
|
borderBounds,
|
||||||
|
paddingBounds,
|
||||||
|
contentBounds,
|
||||||
|
isEmpty: false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private BoxBounds(
|
||||||
|
RectangleInfo outerBounds,
|
||||||
|
RectangleInfo marginBounds,
|
||||||
|
RectangleInfo borderBounds,
|
||||||
|
RectangleInfo paddingBounds,
|
||||||
|
RectangleInfo contentBounds,
|
||||||
|
bool isEmpty)
|
||||||
|
{
|
||||||
|
this.OuterBounds = outerBounds ?? throw new ArgumentNullException(nameof(outerBounds));
|
||||||
|
this.MarginBounds = marginBounds ?? throw new ArgumentNullException(nameof(marginBounds));
|
||||||
|
this.BorderBounds = borderBounds ?? throw new ArgumentNullException(nameof(borderBounds));
|
||||||
|
this.PaddingBounds = paddingBounds ?? throw new ArgumentNullException(nameof(paddingBounds));
|
||||||
|
this.ContentBounds = contentBounds ?? throw new ArgumentNullException(nameof(contentBounds));
|
||||||
|
this.IsEmpty = isEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the outer bounds of this layout box.
|
||||||
|
/// </summary>
|
||||||
|
public RectangleInfo OuterBounds
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RectangleInfo MarginBounds
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RectangleInfo BorderBounds
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RectangleInfo PaddingBounds
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the bounds of the content area for this layout box.
|
||||||
|
/// </summary>
|
||||||
|
public RectangleInfo ContentBounds
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsEmpty
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the bounds of the various areas of a box, given the content bounds and the box style.
|
||||||
|
/// Starts with the content bounds and works outward, enlarging the content bounds by the padding, border, and margin sizes to calculate the outer bounds of the box.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentBounds">The content bounds of the box.</param>
|
||||||
|
/// <param name="boxStyle">The style of the box, which includes the sizes of the margin, border, and padding areas.</param>
|
||||||
|
/// <returns>A <see cref="BoxBounds"/> object that represents the bounds of the different areas of the box.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="contentBounds"/> or <paramref name="boxStyle"/> is null.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when any of the styles in <paramref name="boxStyle"/> is null.</exception>
|
||||||
|
public static BoxBounds CreateFromContentBounds(
|
||||||
|
RectangleInfo contentBounds,
|
||||||
|
BoxStyle boxStyle)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(contentBounds);
|
||||||
|
ArgumentNullException.ThrowIfNull(boxStyle);
|
||||||
|
if (boxStyle.PaddingStyle == null || boxStyle.BorderStyle == null || boxStyle.MarginStyle == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(null, nameof(boxStyle));
|
||||||
|
}
|
||||||
|
|
||||||
|
var paddingBounds = contentBounds.Enlarge(boxStyle.PaddingStyle);
|
||||||
|
var borderBounds = paddingBounds.Enlarge(boxStyle.BorderStyle);
|
||||||
|
var marginBounds = borderBounds.Enlarge(boxStyle.MarginStyle);
|
||||||
|
var outerBounds = marginBounds;
|
||||||
|
return new(
|
||||||
|
outerBounds, marginBounds, borderBounds, paddingBounds, contentBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the bounds of the various areas of a box, given the outer bounds and the box style.
|
||||||
|
/// This method starts with the outer bounds and works inward, shrinking the outer bounds by the margin, border, and padding sizes to calculate the content bounds of the box.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outerBounds">The outer bounds of the box.</param>
|
||||||
|
/// <param name="boxStyle">The style of the box, which includes the sizes of the margin, border, and padding areas.</param>
|
||||||
|
/// <returns>A <see cref="BoxBounds"/> object that represents the bounds of the different areas of the box.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="outerBounds"/> or <paramref name="boxStyle"/> is null.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when any of the styles in <paramref name="boxStyle"/> is null.</exception>
|
||||||
|
public static BoxBounds CreateFromOuterBounds(
|
||||||
|
RectangleInfo outerBounds,
|
||||||
|
BoxStyle boxStyle)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(outerBounds);
|
||||||
|
ArgumentNullException.ThrowIfNull(boxStyle);
|
||||||
|
if (outerBounds == null || boxStyle.MarginStyle == null || boxStyle.BorderStyle == null || boxStyle.PaddingStyle == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(null, nameof(boxStyle));
|
||||||
|
}
|
||||||
|
|
||||||
|
var marginBounds = outerBounds;
|
||||||
|
var borderBounds = marginBounds.Shrink(boxStyle.MarginStyle);
|
||||||
|
var paddingBounds = borderBounds.Shrink(boxStyle.BorderStyle);
|
||||||
|
var contentBounds = paddingBounds.Shrink(boxStyle.PaddingStyle);
|
||||||
|
return new(
|
||||||
|
outerBounds, marginBounds, borderBounds, paddingBounds, contentBounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,9 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
namespace MouseJump.Common.Models.Drawing;
|
using System.Drawing;
|
||||||
|
|
||||||
|
namespace MouseJump.Models.Drawing;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Immutable version of a System.Drawing.Point object with some extra utility methods.
|
/// Immutable version of a System.Drawing.Point object with some extra utility methods.
|
||||||
@@ -40,9 +42,30 @@ public sealed class PointInfo
|
|||||||
y: Math.Clamp(this.Y, outer.Y, outer.Bottom));
|
y: Math.Clamp(this.Y, outer.Y, outer.Bottom));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PointInfo Offset(PointInfo amount) => new(this.X + amount.X, this.Y + amount.Y);
|
||||||
|
|
||||||
|
public PointInfo Offset(SizeInfo amount) => new(this.X + amount.Width, this.Y + amount.Height);
|
||||||
|
|
||||||
|
public PointInfo Offset(decimal amountX, decimal amountY) => new(this.X + amountX, this.Y + amountY);
|
||||||
|
|
||||||
public PointInfo Scale(decimal scalingFactor) => new(this.X * scalingFactor, this.Y * scalingFactor);
|
public PointInfo Scale(decimal scalingFactor) => new(this.X * scalingFactor, this.Y * scalingFactor);
|
||||||
|
|
||||||
public PointInfo Offset(PointInfo amount) => new(this.X + amount.X, this.Y + amount.Y);
|
/// <summary>
|
||||||
|
/// Stretches the point to the same proportional position in targetBounds as
|
||||||
|
/// it currently is in sourceBounds
|
||||||
|
/// </summary>
|
||||||
|
public PointInfo Stretch(RectangleInfo source, RectangleInfo target) =>
|
||||||
|
new(
|
||||||
|
x: ((this.X - source.X) / source.Width * target.Width) + target.X,
|
||||||
|
y: ((this.Y - source.Y) / source.Height * target.Height) + target.Y);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subtracts the specified point from the current point.
|
||||||
|
/// </summary>
|
||||||
|
public PointInfo Subtract(PointInfo point) =>
|
||||||
|
new(
|
||||||
|
x: this.X - point.X,
|
||||||
|
y: this.Y - point.Y);
|
||||||
|
|
||||||
public Point ToPoint() => new((int)this.X, (int)this.Y);
|
public Point ToPoint() => new((int)this.X, (int)this.Y);
|
||||||
|
|
||||||
@@ -51,17 +74,6 @@ public sealed class PointInfo
|
|||||||
return new((int)this.X, (int)this.Y);
|
return new((int)this.X, (int)this.Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stretches the point to the same proportional position in targetBounds as
|
|
||||||
/// it currently is in sourceBounds
|
|
||||||
/// </summary>
|
|
||||||
public PointInfo Stretch(RectangleInfo source, RectangleInfo target)
|
|
||||||
{
|
|
||||||
return new PointInfo(
|
|
||||||
x: ((this.X - source.X) / source.Width * target.Width) + target.X,
|
|
||||||
y: ((this.Y - source.Y) / source.Height * target.Height) + target.Y);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PointInfo Truncate() =>
|
public PointInfo Truncate() =>
|
||||||
new(
|
new(
|
||||||
(int)this.X,
|
(int)this.X,
|
||||||
@@ -2,12 +2,14 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Drawing;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
using MouseJump.Common.Models.Styles;
|
using MouseJump.Models.Styles;
|
||||||
using BorderStyle = MouseJump.Common.Models.Styles.BorderStyle;
|
|
||||||
|
|
||||||
namespace MouseJump.Common.Models.Drawing;
|
using BorderStyle = MouseJump.Models.Styles.BorderStyle;
|
||||||
|
|
||||||
|
namespace MouseJump.Models.Drawing;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Immutable version of a System.Drawing.Rectangle object with some extra utility methods.
|
/// Immutable version of a System.Drawing.Rectangle object with some extra utility methods.
|
||||||
@@ -16,6 +18,7 @@ public sealed class RectangleInfo
|
|||||||
{
|
{
|
||||||
public static readonly RectangleInfo Empty = new(0, 0, 0, 0);
|
public static readonly RectangleInfo Empty = new(0, 0, 0, 0);
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
public RectangleInfo(decimal x, decimal y, decimal width, decimal height)
|
public RectangleInfo(decimal x, decimal y, decimal width, decimal height)
|
||||||
{
|
{
|
||||||
this.X = x;
|
this.X = x;
|
||||||
@@ -29,7 +32,7 @@ public sealed class RectangleInfo
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public RectangleInfo(Point location, SizeInfo size)
|
public RectangleInfo(PointInfo location, SizeInfo size)
|
||||||
: this(location.X, location.Y, size.Width, size.Height)
|
: this(location.X, location.Y, size.Width, size.Height)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -39,26 +42,34 @@ public sealed class RectangleInfo
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("x")]
|
||||||
public decimal X
|
public decimal X
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("y")]
|
||||||
public decimal Y
|
public decimal Y
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("width")]
|
||||||
public decimal Width
|
public decimal Width
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("height")]
|
||||||
public decimal Height
|
public decimal Height
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool IsEmpty =>
|
||||||
|
object.ReferenceEquals(this, RectangleInfo.Empty);
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public decimal Left =>
|
public decimal Left =>
|
||||||
this.X;
|
this.X;
|
||||||
@@ -83,6 +94,9 @@ public sealed class RectangleInfo
|
|||||||
public PointInfo Location =>
|
public PointInfo Location =>
|
||||||
new(this.X, this.Y);
|
new(this.X, this.Y);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the midpoint of the current rectangle.
|
||||||
|
/// </summary>
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public PointInfo Midpoint =>
|
public PointInfo Midpoint =>
|
||||||
new(
|
new(
|
||||||
@@ -104,27 +118,6 @@ public sealed class RectangleInfo
|
|||||||
width: this.Width,
|
width: this.Width,
|
||||||
height: this.Height);
|
height: this.Height);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a new <see cref="RectangleInfo"/> that is moved within the bounds of the specified outer rectangle.
|
|
||||||
/// If the current rectangle is larger than the outer rectangle, an exception is thrown.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="outer">The outer <see cref="RectangleInfo"/> within which to confine this rectangle.</param>
|
|
||||||
/// <returns>A new <see cref="RectangleInfo"/> that is the result of moving this rectangle within the bounds of the outer rectangle.</returns>
|
|
||||||
/// <exception cref="ArgumentException">Thrown when the current rectangle is larger than the outer rectangle.</exception>
|
|
||||||
public RectangleInfo Clamp(RectangleInfo outer)
|
|
||||||
{
|
|
||||||
if ((this.Width > outer.Width) || (this.Height > outer.Height))
|
|
||||||
{
|
|
||||||
throw new ArgumentException($"Value cannot be larger than {nameof(outer)}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new(
|
|
||||||
x: Math.Clamp(this.X, outer.X, outer.Right - this.Width),
|
|
||||||
y: Math.Clamp(this.Y, outer.Y, outer.Bottom - this.Height),
|
|
||||||
width: this.Width,
|
|
||||||
height: this.Height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Adapted from https://github.com/dotnet/runtime
|
/// Adapted from https://github.com/dotnet/runtime
|
||||||
/// See https://github.com/dotnet/runtime/blob/dfd618dc648ba9b11dd0f8034f78113d69f223cd/src/libraries/System.Drawing.Primitives/src/System/Drawing/Rectangle.cs
|
/// See https://github.com/dotnet/runtime/blob/dfd618dc648ba9b11dd0f8034f78113d69f223cd/src/libraries/System.Drawing.Primitives/src/System/Drawing/Rectangle.cs
|
||||||
@@ -186,6 +179,53 @@ public sealed class RectangleInfo
|
|||||||
this.Width + padding.Horizontal,
|
this.Width + padding.Horizontal,
|
||||||
this.Height + padding.Vertical);
|
this.Height + padding.Vertical);
|
||||||
|
|
||||||
|
public RectangleInfo MoveTo(PointInfo point) =>
|
||||||
|
new(
|
||||||
|
point.X,
|
||||||
|
point.Y,
|
||||||
|
this.Width,
|
||||||
|
this.Height);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adapted from https://github.com/dotnet/runtime
|
||||||
|
/// See https://github.com/dotnet/runtime/blob/dfd618dc648ba9b11dd0f8034f78113d69f223cd/src/libraries/System.Drawing.Primitives/src/System/Drawing/Rectangle.cs
|
||||||
|
/// </summary>
|
||||||
|
public RectangleInfo Intersect(RectangleInfo other)
|
||||||
|
{
|
||||||
|
var x1 = Math.Max(this.X, other.X);
|
||||||
|
var x2 = Math.Min(this.X + this.Width, other.X + other.Width);
|
||||||
|
var y1 = Math.Max(this.Y, other.Y);
|
||||||
|
var y2 = Math.Min(this.Y + this.Height, other.Y + other.Height);
|
||||||
|
|
||||||
|
if (x2 >= x1 && y2 >= y1)
|
||||||
|
{
|
||||||
|
return new RectangleInfo(x1, y1, x2 - x1, y2 - y1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return RectangleInfo.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a new <see cref="RectangleInfo"/> that is moved within the bounds of the specified outer rectangle.
|
||||||
|
/// If the current rectangle is larger than the outer rectangle, an exception is thrown.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outer">The outer <see cref="RectangleInfo"/> within which to confine this rectangle.</param>
|
||||||
|
/// <returns>A new <see cref="RectangleInfo"/> that is the result of moving this rectangle within the bounds of the outer rectangle.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the current rectangle is larger than the outer rectangle.</exception>
|
||||||
|
public RectangleInfo MoveInside(RectangleInfo outer)
|
||||||
|
{
|
||||||
|
if ((this.Width > outer.Width) || (this.Height > outer.Height))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Value cannot be larger than {nameof(outer)}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new(
|
||||||
|
x: Math.Clamp(this.X, outer.X, outer.Right - this.Width),
|
||||||
|
y: Math.Clamp(this.Y, outer.Y, outer.Bottom - this.Height),
|
||||||
|
width: this.Width,
|
||||||
|
height: this.Height);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a new <see cref="RectangleInfo"/> that is offset by the specified amount.
|
/// Returns a new <see cref="RectangleInfo"/> that is offset by the specified amount.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -203,6 +243,9 @@ public sealed class RectangleInfo
|
|||||||
public RectangleInfo Offset(decimal dx, decimal dy) =>
|
public RectangleInfo Offset(decimal dx, decimal dy) =>
|
||||||
new(this.X + dx, this.Y + dy, this.Width, this.Height);
|
new(this.X + dx, this.Y + dy, this.Width, this.Height);
|
||||||
|
|
||||||
|
public RectangleInfo Resize(decimal width, decimal height) =>
|
||||||
|
new(this.X, this.Y, width, height);
|
||||||
|
|
||||||
public RectangleInfo Round() =>
|
public RectangleInfo Round() =>
|
||||||
this.Round(0);
|
this.Round(0);
|
||||||
|
|
||||||
@@ -289,6 +332,16 @@ public sealed class RectangleInfo
|
|||||||
return new RectangleInfo(x1, y1, x2 - x1, y2 - y1);
|
return new RectangleInfo(x1, y1, x2 - x1, y2 - y1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static RectangleInfo Union(IEnumerable<RectangleInfo> rects)
|
||||||
|
{
|
||||||
|
var list = rects.ToList();
|
||||||
|
return (list.Count == 0)
|
||||||
|
? RectangleInfo.Empty
|
||||||
|
: list.Skip(1).Aggregate(
|
||||||
|
seed: list.First(),
|
||||||
|
(aggr, rect) => aggr.Union(rect));
|
||||||
|
}
|
||||||
|
|
||||||
public Rectangle ToRectangle() =>
|
public Rectangle ToRectangle() =>
|
||||||
new(
|
new(
|
||||||
(int)this.X,
|
(int)this.X,
|
||||||
@@ -2,16 +2,23 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using MouseJump.Common.Models.Styles;
|
using System.Drawing;
|
||||||
using BorderStyle = MouseJump.Common.Models.Styles.BorderStyle;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace MouseJump.Common.Models.Drawing;
|
using MouseJump.Models.Styles;
|
||||||
|
|
||||||
|
using BorderStyle = MouseJump.Models.Styles.BorderStyle;
|
||||||
|
|
||||||
|
namespace MouseJump.Models.Drawing;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Immutable version of a System.Drawing.Size object with some extra utility methods.
|
/// Immutable version of a System.Drawing.Size object with some extra utility methods.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class SizeInfo
|
public sealed class SizeInfo
|
||||||
{
|
{
|
||||||
|
public static readonly SizeInfo Empty = new(0, 0);
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
public SizeInfo(decimal width, decimal height)
|
public SizeInfo(decimal width, decimal height)
|
||||||
{
|
{
|
||||||
this.Width = width;
|
this.Width = width;
|
||||||
@@ -33,6 +40,9 @@ public sealed class SizeInfo
|
|||||||
get;
|
get;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsEmpty =>
|
||||||
|
object.ReferenceEquals(this, SizeInfo.Empty);
|
||||||
|
|
||||||
public SizeInfo Clamp(SizeInfo max)
|
public SizeInfo Clamp(SizeInfo max)
|
||||||
{
|
{
|
||||||
return new(
|
return new(
|
||||||
@@ -40,13 +50,6 @@ public sealed class SizeInfo
|
|||||||
height: Math.Clamp(this.Height, 0, max.Height));
|
height: Math.Clamp(this.Height, 0, max.Height));
|
||||||
}
|
}
|
||||||
|
|
||||||
public SizeInfo Clamp(decimal maxWidth, decimal maxHeight)
|
|
||||||
{
|
|
||||||
return new(
|
|
||||||
width: Math.Clamp(this.Width, 0, maxWidth),
|
|
||||||
height: Math.Clamp(this.Height, 0, maxHeight));
|
|
||||||
}
|
|
||||||
|
|
||||||
public SizeInfo Enlarge(BorderStyle border) =>
|
public SizeInfo Enlarge(BorderStyle border) =>
|
||||||
new(
|
new(
|
||||||
this.Width + border.Horizontal,
|
this.Width + border.Horizontal,
|
||||||
@@ -101,7 +104,7 @@ public sealed class SizeInfo
|
|||||||
/// <param name="x">The x-coordinate of the upper-left corner of the rectangle.</param>
|
/// <param name="x">The x-coordinate of the upper-left corner of the rectangle.</param>
|
||||||
/// <param name="y">The y-coordinate of the upper-left corner of the rectangle.</param>
|
/// <param name="y">The y-coordinate of the upper-left corner of the rectangle.</param>
|
||||||
/// <returns>A new <see cref="RectangleInfo"/> instance representing the positioned rectangle.</returns>
|
/// <returns>A new <see cref="RectangleInfo"/> instance representing the positioned rectangle.</returns>
|
||||||
public RectangleInfo PlaceAt(decimal x, decimal y) =>
|
public RectangleInfo MoveTo(decimal x, decimal y) =>
|
||||||
new(x, y, this.Width, this.Height);
|
new(x, y, this.Width, this.Height);
|
||||||
|
|
||||||
public SizeInfo Round() =>
|
public SizeInfo Round() =>
|
||||||
@@ -120,21 +123,31 @@ public sealed class SizeInfo
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="bounds">The size to fit this size into.</param>
|
/// <param name="bounds">The size to fit this size into.</param>
|
||||||
/// <returns>A new <see cref="SizeInfo"/> instance representing the scaled size.</returns>
|
/// <returns>A new <see cref="SizeInfo"/> instance representing the scaled size.</returns>
|
||||||
public SizeInfo ScaleToFit(SizeInfo bounds, out decimal scalingRatio)
|
public SizeInfo ScaleToFit(SizeInfo bounds)
|
||||||
|
{
|
||||||
|
return this.ScaleToFit(bounds, out var _);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scales this size to fit within the bounds of another size, while maintaining the aspect ratio.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bounds">The size to fit this size into.</param>
|
||||||
|
/// <returns>A new <see cref="SizeInfo"/> instance representing the scaled size.</returns>
|
||||||
|
public SizeInfo ScaleToFit(SizeInfo bounds, out decimal scalingFactor)
|
||||||
{
|
{
|
||||||
var widthRatio = bounds.Width / this.Width;
|
var widthRatio = bounds.Width / this.Width;
|
||||||
var heightRatio = bounds.Height / this.Height;
|
var heightRatio = bounds.Height / this.Height;
|
||||||
switch (widthRatio.CompareTo(heightRatio))
|
switch (widthRatio.CompareTo(heightRatio))
|
||||||
{
|
{
|
||||||
case < 0:
|
case < 0:
|
||||||
scalingRatio = widthRatio;
|
scalingFactor = widthRatio;
|
||||||
return new(bounds.Width, this.Height * widthRatio);
|
return new(bounds.Width, this.Height * widthRatio);
|
||||||
case 0:
|
case 0:
|
||||||
// widthRatio and heightRatio are the same, so just pick one
|
// widthRatio and heightRatio are the same, so just pick one
|
||||||
scalingRatio = widthRatio;
|
scalingFactor = widthRatio;
|
||||||
return bounds;
|
return bounds;
|
||||||
case > 0:
|
case > 0:
|
||||||
scalingRatio = heightRatio;
|
scalingFactor = heightRatio;
|
||||||
return new(this.Width * heightRatio, bounds.Height);
|
return new(this.Width * heightRatio, bounds.Height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||||
|
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<ProjectGuid>{478C3576-CF1C-4509-B793-AE10DF394347}</ProjectGuid>
|
||||||
|
<AssemblyName>PowerToys.MouseJump.Models</AssemblyName>
|
||||||
|
<AssemblyTitle>PowerToys.MouseJump.Models</AssemblyTitle>
|
||||||
|
<AssemblyDescription>PowerToys MouseJump.Models</AssemblyDescription>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<OutputPath>$(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps</OutputPath>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||||
|
|
||||||
|
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
23
src/modules/MouseUtils/MouseJump.Models/NintJsonConverter.cs
Normal file
23
src/modules/MouseUtils/MouseJump.Models/NintJsonConverter.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace MouseJump.Models;
|
||||||
|
|
||||||
|
public sealed class NintJsonConverter : JsonConverter<nint>
|
||||||
|
{
|
||||||
|
public override nint Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
// nint can be 32 or 64 bit, so convert to Int64
|
||||||
|
var value = reader.GetInt64();
|
||||||
|
return (nint)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, nint value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
writer.WriteNumberValue((long)value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
namespace MouseJump.Common.Models.Settings;
|
namespace MouseJump.Models.Settings;
|
||||||
|
|
||||||
public enum PreviewType
|
public enum PreviewType
|
||||||
{
|
{
|
||||||
@@ -2,7 +2,9 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
namespace MouseJump.Common.Models.Styles;
|
using System.Drawing;
|
||||||
|
|
||||||
|
namespace MouseJump.Models.Styles;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the background fill style for a drawing object.
|
/// Represents the background fill style for a drawing object.
|
||||||
@@ -2,7 +2,9 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
namespace MouseJump.Common.Models.Styles;
|
using System.Drawing;
|
||||||
|
|
||||||
|
namespace MouseJump.Models.Styles;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the border style for a drawing object.
|
/// Represents the border style for a drawing object.
|
||||||
@@ -63,6 +65,11 @@ public sealed class BorderStyle
|
|||||||
|
|
||||||
public decimal Vertical => this.Top + this.Bottom;
|
public decimal Vertical => this.Top + this.Bottom;
|
||||||
|
|
||||||
|
public BorderStyle WithColor(Color? color)
|
||||||
|
{
|
||||||
|
return new BorderStyle(color, this.Left, this.Top, this.Right, this.Bottom, this.Depth);
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return "{" +
|
return "{" +
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
namespace MouseJump.Common.Models.Styles;
|
namespace MouseJump.Models.Styles;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the styles to apply to a simple box-layout based drawing object.
|
/// Represents the styles to apply to a simple box-layout based drawing object.
|
||||||
@@ -29,18 +29,34 @@ public sealed class BoxStyle
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public static readonly BoxStyle Empty = new(MarginStyle.Empty, BorderStyle.Empty, PaddingStyle.Empty, BackgroundStyle.Empty);
|
public static readonly BoxStyle Empty = new(
|
||||||
|
MarginStyle.Empty,
|
||||||
|
BorderStyle.Empty,
|
||||||
|
PaddingStyle.Empty,
|
||||||
|
BackgroundStyle.Empty,
|
||||||
|
isEmpty: true);
|
||||||
|
|
||||||
public BoxStyle(
|
public BoxStyle(
|
||||||
MarginStyle marginStyle,
|
MarginStyle marginStyle,
|
||||||
BorderStyle borderStyle,
|
BorderStyle borderStyle,
|
||||||
PaddingStyle paddingStyle,
|
PaddingStyle paddingStyle,
|
||||||
BackgroundStyle backgroundStyle)
|
BackgroundStyle backgroundStyle)
|
||||||
|
: this(marginStyle, borderStyle, paddingStyle, backgroundStyle, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private BoxStyle(
|
||||||
|
MarginStyle marginStyle,
|
||||||
|
BorderStyle borderStyle,
|
||||||
|
PaddingStyle paddingStyle,
|
||||||
|
BackgroundStyle backgroundStyle,
|
||||||
|
bool isEmpty)
|
||||||
{
|
{
|
||||||
this.MarginStyle = marginStyle ?? throw new ArgumentNullException(nameof(marginStyle));
|
this.MarginStyle = marginStyle ?? throw new ArgumentNullException(nameof(marginStyle));
|
||||||
this.BorderStyle = borderStyle ?? throw new ArgumentNullException(nameof(borderStyle));
|
this.BorderStyle = borderStyle ?? throw new ArgumentNullException(nameof(borderStyle));
|
||||||
this.PaddingStyle = paddingStyle ?? throw new ArgumentNullException(nameof(paddingStyle));
|
this.PaddingStyle = paddingStyle ?? throw new ArgumentNullException(nameof(paddingStyle));
|
||||||
this.BackgroundStyle = backgroundStyle ?? throw new ArgumentNullException(nameof(backgroundStyle));
|
this.BackgroundStyle = backgroundStyle ?? throw new ArgumentNullException(nameof(backgroundStyle));
|
||||||
|
this.IsEmpty = isEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -74,4 +90,9 @@ public sealed class BoxStyle
|
|||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsEmpty
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
namespace MouseJump.Common.Models.Styles;
|
namespace MouseJump.Models.Styles;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the margin style for a drawing object.
|
/// Represents the margin style for a drawing object.
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
namespace MouseJump.Common.Models.Styles;
|
namespace MouseJump.Models.Styles;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the margin style for a drawing object.
|
/// Represents the margin style for a drawing object.
|
||||||
@@ -2,20 +2,25 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using MouseJump.Common.Models.Drawing;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Drawing;
|
||||||
|
|
||||||
namespace MouseJump.Common.Models.Styles;
|
using MouseJump.Models.Drawing;
|
||||||
|
|
||||||
|
namespace MouseJump.Models.Styles;
|
||||||
|
|
||||||
public sealed class PreviewStyle
|
public sealed class PreviewStyle
|
||||||
{
|
{
|
||||||
public PreviewStyle(
|
public PreviewStyle(
|
||||||
SizeInfo canvasSize,
|
SizeInfo canvasSize,
|
||||||
BoxStyle canvasStyle,
|
BoxStyle canvasStyle,
|
||||||
BoxStyle screenStyle)
|
BoxStyle screenStyle,
|
||||||
|
IEnumerable<Color> extraColors)
|
||||||
{
|
{
|
||||||
this.CanvasSize = canvasSize ?? throw new ArgumentNullException(nameof(canvasSize));
|
this.CanvasSize = canvasSize ?? throw new ArgumentNullException(nameof(canvasSize));
|
||||||
this.CanvasStyle = canvasStyle ?? throw new ArgumentNullException(nameof(canvasStyle));
|
this.CanvasStyle = canvasStyle ?? throw new ArgumentNullException(nameof(canvasStyle));
|
||||||
this.ScreenStyle = screenStyle ?? throw new ArgumentNullException(nameof(screenStyle));
|
this.ScreenStyle = screenStyle ?? throw new ArgumentNullException(nameof(screenStyle));
|
||||||
|
this.ExtraColors = (extraColors ?? throw new ArgumentNullException(nameof(extraColors))).ToList().AsReadOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SizeInfo CanvasSize
|
public SizeInfo CanvasSize
|
||||||
@@ -32,4 +37,9 @@ public sealed class PreviewStyle
|
|||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReadOnlyCollection<Color> ExtraColors
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user