@@ -5,6 +5,7 @@ import UWP
5
5
import WinAppSDK
6
6
import WinUI
7
7
import WindowsFoundation
8
+ import WinSDK
8
9
9
10
// Many force tries are required for the WinUI backend but we don't really want them
10
11
// anywhere else so just disable them for this file.
@@ -85,6 +86,9 @@ public final class WinUIBackend: AppBackend {
85
86
// print a warning anyway.
86
87
print ( " Warning: Failed to attach to parent console: \( error. localizedDescription) " )
87
88
}
89
+
90
+ // Ensure that the app's windows adapt to DPI changes at runtime
91
+ SetThreadDpiAwarenessContext ( DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
88
92
89
93
WinUIApplication . callback = { application in
90
94
// Toggle Switch has annoying default 'internal margins' (not Control
@@ -112,7 +116,7 @@ public final class WinUIBackend: AppBackend {
112
116
WinUIApplication . main ( )
113
117
}
114
118
115
- public func createWindow( withDefaultSize size: SIMD2 < Int > ? ) -> Window {
119
+ public func createWindow( withDefaultSize size: SIMD2 < Int > ? ) -> Window {
116
120
let window = CustomWindow ( )
117
121
windows. append ( window)
118
122
window. closed. addHandler { _, _ in
@@ -177,9 +181,12 @@ public final class WinUIBackend: AppBackend {
177
181
}
178
182
179
183
public func setSize( ofWindow window: Window , to newSize: SIMD2 < Int > ) {
184
+ let scaleFactor = window. scaleFactor
185
+ let width = scaleFactor * Double( newSize. x)
186
+ let height = scaleFactor * Double( newSize. y + CustomWindow. menuBarHeight)
180
187
let size = UWP . SizeInt32 (
181
- width: Int32 ( newSize . x ) ,
182
- height: Int32 ( newSize . y + CustomWindow . menuBarHeight )
188
+ width: Int32 ( width . rounded ( . towardZero ) ) ,
189
+ height: Int32 ( height . rounded ( . towardZero ) )
183
190
)
184
191
try ! window. appWindow. resizeClient ( size)
185
192
}
@@ -304,8 +311,9 @@ public final class WinUIBackend: AppBackend {
304
311
public func computeWindowEnvironment(
305
312
window: Window ,
306
313
rootEnvironment: EnvironmentValues
307
- ) -> EnvironmentValues {
308
- // TODO: Record window scale factor in here
314
+ ) -> EnvironmentValues {
315
+ // TODO: Compute window scale factor (easy enough, but we would also have to keep
316
+ // it up-to-date then, which is kinda annoying for now)
309
317
rootEnvironment
310
318
}
311
319
@@ -990,49 +998,49 @@ public final class WinUIBackend: AppBackend {
990
998
}
991
999
}
992
1000
993
- // public func showOpenDialog(
994
- // fileDialogOptions: FileDialogOptions,
995
- // openDialogOptions: OpenDialogOptions,
996
- // window: Window?,
997
- // resultHandler handleResult: @escaping (DialogResult<[URL]>) -> Void
998
- // ) {
999
- // let picker = FileOpenPicker()
1000
- // // TODO: Associate the picker with a window. Requires some janky WinUI
1001
- // // Win32 interop kinda stuff I believe.
1002
- // if openDialogOptions.allowMultipleSelections {
1003
- // let promise = try! picker.pickMultipleFilesAsync()!
1004
- // promise.completed = { operation, status in
1005
- // guard
1006
- // status == .completed,
1007
- // let operation,
1008
- // let result = try? operation.getResults()
1009
- // else {
1010
- // return
1011
- // }
1012
- // print(result)
1013
- // }
1014
- // } else {
1015
- // let promise = try! picker.pickSingleFileAsync()!
1016
- // promise.completed = { operation, status in
1017
- // guard
1018
- // status == .completed,
1019
- // let operation,
1020
- // let result = try? operation.getResults()
1021
- // else {
1022
- // return
1023
- // }
1024
- // print(result)
1025
- // }
1026
- // }
1027
- // }
1001
+ public func showOpenDialog(
1002
+ fileDialogOptions: FileDialogOptions ,
1003
+ openDialogOptions: OpenDialogOptions ,
1004
+ window: Window ? ,
1005
+ resultHandler handleResult: @escaping ( DialogResult < [ URL ] > ) -> Void
1006
+ ) {
1007
+ let picker = FileOpenPicker ( )
1008
+ // TODO: Associate the picker with a window. Requires some janky WinUI
1009
+ // Win32 interop kinda stuff I believe.
1010
+ if openDialogOptions. allowMultipleSelections {
1011
+ let promise = try ! picker. pickMultipleFilesAsync ( ) !
1012
+ promise. completed = { operation, status in
1013
+ guard
1014
+ status == . completed,
1015
+ let operation,
1016
+ let result = try ? operation. getResults ( )
1017
+ else {
1018
+ return
1019
+ }
1020
+ print ( result)
1021
+ }
1022
+ } else {
1023
+ let promise = try ! picker. pickSingleFileAsync ( ) !
1024
+ promise. completed = { operation, status in
1025
+ guard
1026
+ status == . completed,
1027
+ let operation,
1028
+ let result = try ? operation. getResults ( )
1029
+ else {
1030
+ return
1031
+ }
1032
+ print ( result)
1033
+ }
1034
+ }
1035
+ }
1028
1036
1029
- // public func showSaveDialog(
1030
- // fileDialogOptions: FileDialogOptions,
1031
- // saveDialogOptions: SaveDialogOptions,
1032
- // window: Window?,
1033
- // resultHandler handleResult: @escaping (DialogResult<URL>) -> Void
1034
- // ) {
1035
- // }
1037
+ public func showSaveDialog(
1038
+ fileDialogOptions: FileDialogOptions ,
1039
+ saveDialogOptions: SaveDialogOptions ,
1040
+ window: Window ? ,
1041
+ resultHandler handleResult: @escaping ( DialogResult < URL > ) -> Void
1042
+ ) {
1043
+ }
1036
1044
1037
1045
public func createTapGestureTarget( wrapping child: Widget , gesture: TapGesture ) -> Widget {
1038
1046
if gesture != . primary {
@@ -1215,6 +1223,34 @@ public class CustomWindow: WinUI.Window {
1215
1223
var menuBar = WinUI . MenuBar ( )
1216
1224
var child : WinUIBackend . Widget ?
1217
1225
var grid : WinUI . Grid
1226
+ var cachedAppWindow : WinAppSDK . AppWindow !
1227
+
1228
+ var scaleFactor : Double {
1229
+ // I'm leaving this code here for future travellers. Be warned that this always
1230
+ // seems to return 100% even if the scale factor is set to 125% in settings.
1231
+ // Perhaps it's only the device's built-in default scaling? But that seems pretty
1232
+ // useless, and isn't what the docs seem to imply.
1233
+ //
1234
+ // var deviceScaleFactor = SCALE_125_PERCENT
1235
+ // _ = GetScaleFactorForMonitor(monitor, &deviceScaleFactor)
1236
+
1237
+ let hwnd = cachedAppWindow. getHWND ( ) !
1238
+ let monitor = MonitorFromWindow ( hwnd, DWORD ( bitPattern: MONITOR_DEFAULTTONEAREST) ) !
1239
+
1240
+ var x : UINT = 0
1241
+ var y : UINT = 0
1242
+ let result = GetDpiForMonitor ( monitor, MDT_EFFECTIVE_DPI, & x, & y) ;
1243
+
1244
+ let windowScaleFactor : Double
1245
+ if result == S_OK {
1246
+ windowScaleFactor = Double ( x) / Double( USER_DEFAULT_SCREEN_DPI)
1247
+ } else {
1248
+ print ( " Warning: Failed to get window scale factor, defaulting to 1.0 " )
1249
+ windowScaleFactor = 1
1250
+ }
1251
+
1252
+ return windowScaleFactor
1253
+ }
1218
1254
1219
1255
public override init ( ) {
1220
1256
grid = WinUI . Grid ( )
@@ -1232,6 +1268,10 @@ public class CustomWindow: WinUI.Window {
1232
1268
grid. children. append ( menuBar)
1233
1269
WinUI . Grid. setRow ( menuBar, 0 )
1234
1270
self . content = grid
1271
+
1272
+ // Caching appWindow is apparently a good idea in terms of performance:
1273
+ // https://github.com/thebrowsercompany/swift-winrt/issues/199#issuecomment-2611006020
1274
+ cachedAppWindow = appWindow
1235
1275
}
1236
1276
1237
1277
public func setChild( _ child: WinUIBackend . Widget ) {
0 commit comments