SukiWindow.axaml.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. using Avalonia;
  2. using Avalonia.Controls;
  3. using Avalonia.Controls.Primitives;
  4. using Avalonia.Input;
  5. using Avalonia.Media;
  6. using System;
  7. using Avalonia.Collections;
  8. using Avalonia.Controls.ApplicationLifetimes;
  9. using Avalonia.Interactivity;
  10. using SukiUI.Enums;
  11. namespace SukiUI.Controls;
  12. public class SukiWindow : Window
  13. {
  14. protected override Type StyleKeyOverride => typeof(SukiWindow);
  15. public static readonly StyledProperty<double> TitleFontSizeProperty =
  16. AvaloniaProperty.Register<SukiWindow, double>(nameof(TitleFontSize), defaultValue: 13);
  17. public double TitleFontSize
  18. {
  19. get => GetValue(TitleFontSizeProperty);
  20. set => SetValue(TitleFontSizeProperty, value);
  21. }
  22. public static readonly StyledProperty<FontWeight> TitleFontWeightProperty =
  23. AvaloniaProperty.Register<SukiWindow, FontWeight>(nameof(TitleFontWeight), defaultValue: FontWeight.Bold);
  24. public FontWeight TitleFontWeight
  25. {
  26. get => GetValue(TitleFontWeightProperty);
  27. set => SetValue(TitleFontWeightProperty, value);
  28. }
  29. public static readonly StyledProperty<Control?> LogoContentProperty =
  30. AvaloniaProperty.Register<SukiWindow, Control?>(nameof(LogoContent));
  31. public Control? LogoContent
  32. {
  33. get => GetValue(LogoContentProperty);
  34. set => SetValue(LogoContentProperty, value);
  35. }
  36. public static readonly StyledProperty<bool> ShowBottomBorderProperty =
  37. AvaloniaProperty.Register<SukiWindow, bool>(nameof(ShowBottomBorder), defaultValue: true);
  38. public bool ShowBottomBorder
  39. {
  40. get => GetValue(ShowBottomBorderProperty);
  41. set => SetValue(ShowBottomBorderProperty, value);
  42. }
  43. public static readonly StyledProperty<bool> IsTitleBarVisibleProperty =
  44. AvaloniaProperty.Register<SukiWindow, bool>(nameof(IsTitleBarVisible), defaultValue: true);
  45. public bool IsTitleBarVisible
  46. {
  47. get => GetValue(IsTitleBarVisibleProperty);
  48. set => SetValue(IsTitleBarVisibleProperty, value);
  49. }
  50. public static readonly StyledProperty<bool> TitleBarAnimationEnabledProperty =
  51. AvaloniaProperty.Register<SukiWindow, bool>(nameof(TitleBarAnimationEnabled), defaultValue: true);
  52. public bool TitleBarAnimationEnabled
  53. {
  54. get => GetValue(TitleBarAnimationEnabledProperty);
  55. set => SetValue(TitleBarAnimationEnabledProperty, value);
  56. }
  57. public static readonly StyledProperty<bool> IsMenuVisibleProperty =
  58. AvaloniaProperty.Register<SukiWindow, bool>(nameof(IsMenuVisible), defaultValue: false);
  59. public bool IsMenuVisible
  60. {
  61. get => GetValue(IsMenuVisibleProperty);
  62. set => SetValue(IsMenuVisibleProperty, value);
  63. }
  64. public static readonly StyledProperty<AvaloniaList<MenuItem>?> MenuItemsProperty =
  65. AvaloniaProperty.Register<SukiWindow, AvaloniaList<MenuItem>?>(nameof(MenuItems));
  66. public AvaloniaList<MenuItem>? MenuItems
  67. {
  68. get => GetValue(MenuItemsProperty);
  69. set => SetValue(MenuItemsProperty, value);
  70. }
  71. public static readonly StyledProperty<bool> CanMinimizeProperty =
  72. AvaloniaProperty.Register<SukiWindow, bool>(nameof(CanMinimize), defaultValue: true);
  73. public bool CanMinimize
  74. {
  75. get => GetValue(CanMinimizeProperty);
  76. set => SetValue(CanMinimizeProperty, value);
  77. }
  78. public static readonly StyledProperty<bool> CanMaximizeProperty =
  79. AvaloniaProperty.Register<SukiWindow, bool>(nameof(CanMaximize), defaultValue: true);
  80. public bool CanMaximize
  81. {
  82. get => GetValue(CanMaximizeProperty);
  83. set => SetValue(CanMaximizeProperty, value);
  84. }
  85. public static readonly StyledProperty<bool> CanMoveProperty =
  86. AvaloniaProperty.Register<SukiWindow, bool>(nameof(CanMove), defaultValue: true);
  87. public bool CanMove
  88. {
  89. get => GetValue(CanMoveProperty);
  90. set => SetValue(CanMoveProperty, value);
  91. }
  92. // Background properties
  93. public static readonly StyledProperty<bool> BackgroundAnimationEnabledProperty =
  94. AvaloniaProperty.Register<SukiWindow, bool>(nameof(BackgroundAnimationEnabled), defaultValue: false);
  95. /// <inheritdoc cref="SukiBackground.AnimationEnabled"/>
  96. public bool BackgroundAnimationEnabled
  97. {
  98. get => GetValue(BackgroundAnimationEnabledProperty);
  99. set => SetValue(BackgroundAnimationEnabledProperty, value);
  100. }
  101. public static readonly StyledProperty<SukiBackgroundStyle> BackgroundStyleProperty =
  102. AvaloniaProperty.Register<SukiWindow, SukiBackgroundStyle>(nameof(BackgroundStyle),
  103. defaultValue: SukiBackgroundStyle.GradientSoft);
  104. /// <inheritdoc cref="SukiBackground.Style"/>
  105. public SukiBackgroundStyle BackgroundStyle
  106. {
  107. get => GetValue(BackgroundStyleProperty);
  108. set => SetValue(BackgroundStyleProperty, value);
  109. }
  110. public static readonly StyledProperty<string?> BackgroundShaderFileProperty =
  111. AvaloniaProperty.Register<SukiWindow, string?>(nameof(BackgroundShaderFile));
  112. /// <inheritdoc cref="SukiBackground.ShaderFile"/>
  113. public string? BackgroundShaderFile
  114. {
  115. get => GetValue(BackgroundShaderFileProperty);
  116. set => SetValue(BackgroundShaderFileProperty, value);
  117. }
  118. public static readonly StyledProperty<string?> BackgroundShaderCodeProperty =
  119. AvaloniaProperty.Register<SukiWindow, string?>(nameof(BackgroundShaderCode));
  120. /// <inheritdoc cref="SukiBackground.ShaderCode"/>
  121. public string? BackgroundShaderCode
  122. {
  123. get => GetValue(BackgroundShaderCodeProperty);
  124. set => SetValue(BackgroundShaderCodeProperty, value);
  125. }
  126. public static readonly StyledProperty<bool> BackgroundTransitionsEnabledProperty =
  127. AvaloniaProperty.Register<SukiBackground, bool>(nameof(BackgroundTransitionsEnabled), defaultValue: false);
  128. /// <inheritdoc cref="SukiBackground.TransitionsEnabled"/>
  129. public bool BackgroundTransitionsEnabled
  130. {
  131. get => GetValue(BackgroundTransitionsEnabledProperty);
  132. set => SetValue(BackgroundTransitionsEnabledProperty, value);
  133. }
  134. public static readonly StyledProperty<double> BackgroundTransitionTimeProperty =
  135. AvaloniaProperty.Register<SukiBackground, double>(nameof(BackgroundTransitionTime), defaultValue: 1.0);
  136. /// <inheritdoc cref="SukiBackground.TransitionTime"/>
  137. public double BackgroundTransitionTime
  138. {
  139. get => GetValue(BackgroundTransitionTimeProperty);
  140. set => SetValue(BackgroundTransitionTimeProperty, value);
  141. }
  142. public static readonly StyledProperty<Avalonia.Controls.Controls> RightWindowTitleBarControlsProperty =
  143. AvaloniaProperty.Register<SukiWindow, Avalonia.Controls.Controls>(nameof(RightWindowTitleBarControls),
  144. defaultValue: new Avalonia.Controls.Controls());
  145. public static readonly StyledProperty<bool> BackgroundForceSoftwareRenderingProperty =
  146. AvaloniaProperty.Register<SukiWindow, bool>(nameof(BackgroundForceSoftwareRendering));
  147. /// <summary>
  148. /// Forces the background of the window to utilise software rendering.
  149. /// This prevents use of any advanced effects or animations and provides only a flat background colour that changes with the theme.
  150. /// </summary>
  151. public bool BackgroundForceSoftwareRendering
  152. {
  153. get => GetValue(BackgroundForceSoftwareRenderingProperty);
  154. set => SetValue(BackgroundForceSoftwareRenderingProperty, value);
  155. }
  156. /// <summary>
  157. /// Controls that are displayed on the right side of the title bar,
  158. /// to the left of the normal window control buttons. (Displays provided controls right-to-left)
  159. /// </summary>
  160. public Avalonia.Controls.Controls RightWindowTitleBarControls
  161. {
  162. get => GetValue(RightWindowTitleBarControlsProperty);
  163. set => SetValue(RightWindowTitleBarControlsProperty, value);
  164. }
  165. public static readonly StyledProperty<Avalonia.Controls.Controls> HostsProperty =
  166. AvaloniaProperty.Register<SukiWindow, Avalonia.Controls.Controls>(nameof(Hosts),
  167. defaultValue: new Avalonia.Controls.Controls());
  168. /// <summary>
  169. /// These controls are displayed above all others and fill the entire window.
  170. /// You can include <see cref="SukiDialogHost"/> and <see cref="SukiToastHost"/> or create your own custom implementations.
  171. /// </summary>
  172. public Avalonia.Controls.Controls Hosts
  173. {
  174. get => GetValue(HostsProperty);
  175. set => SetValue(HostsProperty, value);
  176. }
  177. public SukiWindow()
  178. {
  179. MenuItems = new AvaloniaList<MenuItem>();
  180. RightWindowTitleBarControls = new Avalonia.Controls.Controls();
  181. Hosts = new Avalonia.Controls.Controls();
  182. }
  183. protected override void OnLoaded(RoutedEventArgs e)
  184. {
  185. base.OnLoaded(e);
  186. if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
  187. return;
  188. if (desktop.MainWindow is SukiWindow window && window != this)
  189. {
  190. Icon ??= window.Icon;
  191. // This would be nice to do, but obviously LogoContent is a control and you can't attach it twice.
  192. // if (LogoContent is null) LogoContent = s.LogoContent;
  193. }
  194. }
  195. protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
  196. {
  197. base.OnPropertyChanged(change);
  198. if (change.Property == WindowStateProperty && change.NewValue is WindowState windowState)
  199. OnWindowStateChanged(windowState);
  200. }
  201. protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
  202. {
  203. base.OnApplyTemplate(e);
  204. OnWindowStateChanged(WindowState);
  205. try
  206. {
  207. // Create handlers for buttons
  208. if (e.NameScope.Get<Button>("PART_MaximizeButton") is { } maximize)
  209. {
  210. maximize.Click += OnMaximizeButtonClicked;
  211. EnableWindowsSnapLayout(maximize);
  212. }
  213. if (e.NameScope.Get<Button>("PART_MinimizeButton") is { } minimize)
  214. minimize.Click += (_, _) => WindowState = WindowState.Minimized;
  215. if (e.NameScope.Get<Button>("PART_CloseButton") is { } close)
  216. close.Click += (_, _) => Close();
  217. if (e.NameScope.Get<GlassCard>("PART_TitleBarBackground") is { } titleBar)
  218. {
  219. titleBar.PointerPressed += OnTitleBarPointerPressed;
  220. titleBar.DoubleTapped += OnMaximizeButtonClicked;
  221. }
  222. }
  223. catch
  224. {
  225. // ignored
  226. }
  227. }
  228. private void OnMaximizeButtonClicked(object? sender, RoutedEventArgs args)
  229. {
  230. if (!CanMaximize) return;
  231. WindowState = WindowState == WindowState.Maximized
  232. ? WindowState.Normal
  233. : WindowState.Maximized;
  234. }
  235. private void EnableWindowsSnapLayout(Button maximize)
  236. {
  237. var pointerOnMaxButton = false;
  238. var setter = typeof(Button).GetProperty("IsPointerOver");
  239. var proc = (IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam, ref bool handled) =>
  240. {
  241. switch (msg)
  242. {
  243. case 533:
  244. if (!pointerOnMaxButton) break;
  245. if (!CanMaximize) break;
  246. WindowState = WindowState == WindowState.Maximized
  247. ? WindowState.Normal
  248. : WindowState.Maximized;
  249. break;
  250. case 0x0084:
  251. var point = new PixelPoint(
  252. (short)(ToInt32(lParam) & 0xffff),
  253. (short)(ToInt32(lParam) >> 16));
  254. var desiredSize = maximize.DesiredSize;
  255. var buttonLeftTop = maximize.PointToScreen(FlowDirection == FlowDirection.LeftToRight
  256. ? new Point(desiredSize.Width, 0)
  257. : new Point(0, 0));
  258. var x = (buttonLeftTop.X - point.X) / RenderScaling;
  259. var y = (point.Y - buttonLeftTop.Y) / RenderScaling;
  260. if (new Rect(0, 0,
  261. desiredSize.Width,
  262. desiredSize.Height)
  263. .Contains(new Point(x, y)))
  264. {
  265. setter?.SetValue(maximize, true);
  266. pointerOnMaxButton = true;
  267. handled = true;
  268. return (IntPtr)9;
  269. }
  270. pointerOnMaxButton = false;
  271. setter?.SetValue(maximize, false);
  272. break;
  273. }
  274. return IntPtr.Zero;
  275. static int ToInt32(IntPtr ptr) => IntPtr.Size == 4
  276. ? ptr.ToInt32()
  277. : (int)(ptr.ToInt64() & 0xffffffff);
  278. };
  279. Win32Properties.AddWndProcHookCallback(this, new Win32Properties.CustomWndProcHookCallback(proc));
  280. }
  281. private void OnWindowStateChanged(WindowState state)
  282. {
  283. if (state == WindowState.FullScreen)
  284. CanMaximize = CanResize = CanMove = false;
  285. if (state == WindowState.Maximized)
  286. Margin = new Thickness(7);
  287. else
  288. Margin = new Thickness(0);
  289. }
  290. private void OnTitleBarPointerPressed(object? sender, PointerPressedEventArgs e)
  291. {
  292. base.OnPointerPressed(e);
  293. BeginMoveDrag(e);
  294. }
  295. }