@@ -5,6 +5,7 @@ import UWP
55import WinAppSDK
66import WinUI
77import WindowsFoundation
8+ import WinSDK
89
910// Many force tries are required for the WinUI backend but we don't really want them
1011// anywhere else so just disable them for this file.
@@ -85,6 +86,9 @@ public final class WinUIBackend: AppBackend {
8586 // print a warning anyway.
8687 print ( " Warning: Failed to attach to parent console: \( error. localizedDescription) " )
8788 }
89+
90+ // Ensure that the app's windows adapt to DPI changes at runtime
91+ SetThreadDpiAwarenessContext ( DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
8892
8993 WinUIApplication . callback = { application in
9094 // Toggle Switch has annoying default 'internal margins' (not Control
@@ -112,7 +116,7 @@ public final class WinUIBackend: AppBackend {
112116 WinUIApplication . main ( )
113117 }
114118
115- public func createWindow( withDefaultSize size: SIMD2 < Int > ? ) -> Window {
119+ public func createWindow( withDefaultSize size: SIMD2 < Int > ? ) -> Window {
116120 let window = CustomWindow ( )
117121 windows. append ( window)
118122 window. closed. addHandler { _, _ in
@@ -177,9 +181,12 @@ public final class WinUIBackend: AppBackend {
177181 }
178182
179183 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)
180187 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 ) )
183190 )
184191 try ! window. appWindow. resizeClient ( size)
185192 }
@@ -304,8 +311,9 @@ public final class WinUIBackend: AppBackend {
304311 public func computeWindowEnvironment(
305312 window: Window ,
306313 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)
309317 rootEnvironment
310318 }
311319
@@ -990,49 +998,49 @@ public final class WinUIBackend: AppBackend {
990998 }
991999 }
9921000
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+ }
10281036
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+ }
10361044
10371045 public func createTapGestureTarget( wrapping child: Widget , gesture: TapGesture ) -> Widget {
10381046 if gesture != . primary {
@@ -1215,6 +1223,34 @@ public class CustomWindow: WinUI.Window {
12151223 var menuBar = WinUI . MenuBar ( )
12161224 var child : WinUIBackend . Widget ?
12171225 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+ }
12181254
12191255 public override init ( ) {
12201256 grid = WinUI . Grid ( )
@@ -1232,6 +1268,10 @@ public class CustomWindow: WinUI.Window {
12321268 grid. children. append ( menuBar)
12331269 WinUI . Grid. setRow ( menuBar, 0 )
12341270 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
12351275 }
12361276
12371277 public func setChild( _ child: WinUIBackend . Widget ) {
0 commit comments