Win32Window.cs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. using Avalonia;
  2. using Avalonia.Controls;
  3. using Avalonia.Threading;
  4. using PicView.Avalonia.UI;
  5. using PicView.Avalonia.ViewModels;
  6. using PicView.Avalonia.WindowBehavior;
  7. using PicView.Core.Sizing;
  8. namespace PicView.Avalonia.Win32.WindowImpl;
  9. public static class Win32Window
  10. {
  11. public static async Task Fullscreen(Window window, MainViewModel vm, bool saveSettings = true)
  12. {
  13. // Save window size, so that restoring it will return to the same size and position
  14. WindowResizing.SaveSize(window);
  15. MenuManager.CloseMenus(vm);
  16. // Update view model properties
  17. vm.MainWindow.SizeToContent.Value = SizeToContent.Manual;
  18. vm.IsFullscreen = true;
  19. vm.IsMaximized = false;
  20. vm.MainWindow.CanResize.Value = false;
  21. // Update settings
  22. Settings.WindowProperties.Fullscreen = true;
  23. // Apply fullscreen state
  24. await InvokeOnUIThreadAsync(() => window.WindowState = WindowState.FullScreen);
  25. // Hide interface in fullscreen
  26. HideInterface(vm);
  27. // Center it, to make sure it is positioned correctly
  28. CenterWindowOnScreen(window);
  29. await WindowResizing.SetSizeAsync(vm);
  30. vm.PicViewer.GalleryWidth.Value = double.NaN;
  31. if (saveSettings)
  32. {
  33. await SaveSettingsAsync().ConfigureAwait(false);
  34. }
  35. }
  36. /// <summary>
  37. /// Maximizes the window
  38. /// </summary>
  39. public static async Task Maximize(Window window, MainViewModel vm, bool saveSettings = true)
  40. {
  41. await Dispatcher.UIThread.InvokeAsync(() =>
  42. {
  43. if (Settings.WindowProperties.AutoFit)
  44. {
  45. vm.MainWindow.SizeToContent.Value = SizeToContent.Manual;
  46. }
  47. // Save window size, so that restoring it will return to the same size and position
  48. WindowResizing.SaveSize(window);
  49. window.WindowState = WindowState.Maximized;
  50. Settings.WindowProperties.Maximized = true;
  51. WindowResizing.SetSize(vm);
  52. SetMargin(vm, window);
  53. });
  54. vm.IsMaximized = true;
  55. vm.IsFullscreen = false;
  56. vm.MainWindow.CanResize.Value = false;
  57. if (saveSettings)
  58. {
  59. await SaveSettingsAsync().ConfigureAwait(false);
  60. }
  61. }
  62. public static async Task Restore(Window window, MainViewModel vm, bool saveSettings = true)
  63. {
  64. // Update settings
  65. Settings.WindowProperties.Maximized = false;
  66. Settings.WindowProperties.Fullscreen = false;
  67. // Update UI state
  68. SetMargin(vm, window);
  69. vm.IsMaximized = false;
  70. vm.IsFullscreen = false;
  71. RestoreInterface(vm);
  72. // Update window state
  73. await InvokeOnUIThreadAsync(() => window.WindowState = WindowState.Normal);
  74. if (Settings.WindowProperties.AutoFit)
  75. {
  76. vm.MainWindow.SizeToContent.Value = SizeToContent.WidthAndHeight;
  77. vm.MainWindow.CanResize.Value = false;
  78. vm.IsAutoFit = true;
  79. if (Settings.WindowProperties.KeepCentered)
  80. {
  81. WindowFunctions.CenterWindowOnScreen();
  82. }
  83. else
  84. {
  85. WindowFunctions.InitializeWindowSizeAndPosition(window);
  86. }
  87. await WindowFunctions.ResizeAndFixRenderingError(vm); // Fixes incorrect render size
  88. }
  89. else
  90. {
  91. vm.MainWindow.SizeToContent.Value = SizeToContent.Manual;
  92. vm.MainWindow.CanResize.Value = true;
  93. WindowFunctions.InitializeWindowSizeAndPosition(window);
  94. }
  95. await WindowResizing.SetSizeAsync(vm);
  96. if (saveSettings)
  97. {
  98. await SaveSettingsAsync().ConfigureAwait(false);
  99. }
  100. }
  101. public static async Task ToggleFullscreen(Window window, MainViewModel vm, bool saveSettings = true)
  102. {
  103. if (Settings.WindowProperties.Fullscreen)
  104. {
  105. await Restore(window, vm, saveSettings);
  106. }
  107. else
  108. {
  109. await Fullscreen(window, vm, saveSettings);
  110. }
  111. if (saveSettings)
  112. {
  113. await SaveSettingsAsync().ConfigureAwait(false);
  114. }
  115. }
  116. public static async Task ToggleMaximize(Window window, MainViewModel vm, bool saveSettings = true)
  117. {
  118. if (Settings.WindowProperties.Maximized)
  119. {
  120. await Restore(window, vm, saveSettings);
  121. }
  122. else
  123. {
  124. await Maximize(window, vm, saveSettings);
  125. }
  126. if (saveSettings)
  127. {
  128. await SaveSettingsAsync().ConfigureAwait(false);
  129. }
  130. }
  131. #region Helpers
  132. /// <summary>
  133. /// Centers the window on the screen
  134. /// </summary>
  135. private static void CenterWindowOnScreen(Window window)
  136. {
  137. Dispatcher.UIThread.Post(() =>
  138. {
  139. // Get the screen that the window is currently on
  140. var screens = window.Screens;
  141. var screen = screens.ScreenFromVisual(window);
  142. if (screen == null)
  143. {
  144. return; // No screen found (edge case)
  145. }
  146. // Get the scaling factor of the screen (DPI scaling)
  147. var scalingFactor = screen.Scaling;
  148. // Get the current screen's bounds (in physical pixels, not adjusted for scaling)
  149. var screenBounds = screen.Bounds;
  150. // Calculate the actual bounds in logical units (adjusting for scaling)
  151. var screenWidth = screenBounds.Width / scalingFactor;
  152. var screenHeight = screenBounds.Height / scalingFactor;
  153. // Get the size of the window
  154. var windowSize = window.ClientSize;
  155. // Calculate the position to center the window on the screen
  156. var centeredX = screenBounds.X + (screenWidth - windowSize.Width) / 2;
  157. var centeredY = screenBounds.Y + (screenHeight - windowSize.Height) / 2;
  158. // Set the window's new position
  159. window.Position = new PixelPoint((int)centeredX, (int)centeredY);
  160. });
  161. }
  162. /// <summary>
  163. /// Restores the interface based on settings
  164. /// </summary>
  165. private static void RestoreInterface(MainViewModel vm)
  166. {
  167. vm.IsUIShown = Settings.UIProperties.ShowInterface;
  168. if (!Settings.UIProperties.ShowInterface)
  169. {
  170. return;
  171. }
  172. vm.IsTopToolbarShown = true;
  173. vm.MainWindow.TitlebarHeight.Value = SizeDefaults.MainTitlebarHeight;
  174. if (!Settings.UIProperties.ShowBottomNavBar)
  175. {
  176. return;
  177. }
  178. vm.IsBottomToolbarShown = true;
  179. vm.MainWindow.BottombarHeight.Value = SizeDefaults.BottombarHeight;
  180. }
  181. /// <summary>
  182. /// Hides interface elements for fullscreen mode
  183. /// </summary>
  184. private static void HideInterface(MainViewModel vm)
  185. {
  186. vm.IsTopToolbarShown = false;
  187. vm.IsBottomToolbarShown = false;
  188. vm.IsUIShown = false;
  189. }
  190. /// <summary>
  191. /// Sets margin based on window state
  192. /// </summary>
  193. private static void SetMargin(MainViewModel vm, Window window)
  194. {
  195. if (Settings.WindowProperties.Maximized)
  196. {
  197. // Sometimes margin is 0 when it's not supposed to be, so replace with 7. Not sure why.
  198. var left = window.OffScreenMargin.Left is 0 ? 7 : window.OffScreenMargin.Left;
  199. var top = window.OffScreenMargin.Top is 0 ? 7 : window.OffScreenMargin.Top;
  200. var right = window.OffScreenMargin.Right is 0 ? 7 : window.OffScreenMargin.Right;
  201. var bottom = window.OffScreenMargin.Bottom is 0 ? 7 : window.OffScreenMargin.Bottom;
  202. vm.MainWindow.TopScreenMargin.Value = new Thickness(left, top, right, 0);
  203. vm.MainWindow.BottomScreenMargin.Value = new Thickness(left, 0, right, bottom);
  204. }
  205. else
  206. {
  207. var noThickness = new Thickness(0);
  208. vm.MainWindow.TopScreenMargin.Value = noThickness;
  209. vm.MainWindow.BottomScreenMargin.Value = noThickness;
  210. }
  211. }
  212. /// <summary>
  213. /// Invokes an action on the UI thread
  214. /// </summary>
  215. private static async Task InvokeOnUIThreadAsync(Action action)
  216. {
  217. if (Dispatcher.UIThread.CheckAccess())
  218. {
  219. action();
  220. }
  221. else
  222. {
  223. await Dispatcher.UIThread.InvokeAsync(action);
  224. }
  225. }
  226. #endregion Helpers
  227. }