From dd0490033cc217136ba85fbee0126e4b7d87340b Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Tue, 11 Mar 2025 19:01:15 +1100 Subject: [PATCH 1/2] chore: add SizedFrame for window sizing --- App/Controls/SizedFrame.cs | 65 ++++++++++++++++++++++++++++++++++ App/Views/SignInWindow.xaml | 3 +- App/Views/SignInWindow.xaml.cs | 39 +++++++++++++++----- App/Views/TrayWindow.xaml | 2 +- App/Views/TrayWindow.xaml.cs | 54 ++++++++-------------------- 5 files changed, 113 insertions(+), 50 deletions(-) create mode 100644 App/Controls/SizedFrame.cs diff --git a/App/Controls/SizedFrame.cs b/App/Controls/SizedFrame.cs new file mode 100644 index 0000000..ad065c0 --- /dev/null +++ b/App/Controls/SizedFrame.cs @@ -0,0 +1,65 @@ +using System; +using Windows.Foundation; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace Coder.Desktop.App.Controls; + +public class SizedFrameEventArgs : EventArgs +{ + public Size NewSize { get; init; } +} + +/// +/// SizedFrame extends Frame by adding a SizeChanged event. Sadly this is necessary because +/// Window.Content.SizeChanged doesn't trigger when the Page's content changes. +/// +public class SizedFrame : Frame +{ + public delegate void SizeChangeDelegate(object sender, SizedFrameEventArgs e); + + public new event SizeChangeDelegate? SizeChanged; + + private Size _lastSize; + + public void SetPage(Page page) + { + if (ReferenceEquals(page, Content)) return; + + // Set the new event listener. + if (page.Content is not FrameworkElement newElement) + throw new Exception("Failed to get Page.Content as FrameworkElement on SizedFrame navigation"); + newElement.SizeChanged += Content_SizeChanged; + + // Unset the previous event listener. + if (Content is Page { Content: FrameworkElement oldElement }) + oldElement.SizeChanged -= Content_SizeChanged; + + // We don't use RootFrame.Navigate here because it doesn't let you + // instantiate the page yourself. We also don't need forwards/backwards + // capabilities. + Content = page; + + // Fire an event. + Content_SizeChanged(newElement, null); + } + + public Size GetContentSize() + { + if (Content is not Page { Content: FrameworkElement frameworkElement }) + throw new Exception("Failed to get Content as FrameworkElement for SizedFrame"); + + frameworkElement.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + return new Size(frameworkElement.ActualWidth, frameworkElement.ActualHeight); + } + + private void Content_SizeChanged(object sender, SizeChangedEventArgs? _) + { + var size = GetContentSize(); + if (size == _lastSize) return; + _lastSize = size; + + var args = new SizedFrameEventArgs { NewSize = size }; + SizeChanged?.Invoke(this, args); + } +} diff --git a/App/Views/SignInWindow.xaml b/App/Views/SignInWindow.xaml index 299c0c1..d2c1326 100644 --- a/App/Views/SignInWindow.xaml +++ b/App/Views/SignInWindow.xaml @@ -4,6 +4,7 @@ x:Class="Coder.Desktop.App.Views.SignInWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:controls="using:Coder.Desktop.App.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" @@ -13,5 +14,5 @@ - + diff --git a/App/Views/SignInWindow.xaml.cs b/App/Views/SignInWindow.xaml.cs index 771dda0..3fe4b5c 100644 --- a/App/Views/SignInWindow.xaml.cs +++ b/App/Views/SignInWindow.xaml.cs @@ -1,18 +1,20 @@ +using System; using Windows.Graphics; +using Coder.Desktop.App.Controls; using Coder.Desktop.App.ViewModels; using Coder.Desktop.App.Views.Pages; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Media; namespace Coder.Desktop.App.Views; /// -/// The dialog window to allow the user to sign into their Coder server. +/// The dialog window to allow the user to sign in to their Coder server. /// public sealed partial class SignInWindow : Window { - private const double WIDTH = 600.0; - private const double HEIGHT = 300.0; + private const double WIDTH = 500.0; private readonly SignInUrlPage _signInUrlPage; private readonly SignInTokenPage _signInTokenPage; @@ -20,9 +22,18 @@ public sealed partial class SignInWindow : Window public SignInWindow(SignInViewModel viewModel) { InitializeComponent(); + SystemBackdrop = new DesktopAcrylicBackdrop(); + RootFrame.SizeChanged += RootFrame_SizeChanged; + _signInUrlPage = new SignInUrlPage(this, viewModel); _signInTokenPage = new SignInTokenPage(this, viewModel); + // Prevent the window from being resized. + if (AppWindow.Presenter is not OverlappedPresenter presenter) + throw new Exception("Failed to get OverlappedPresenter for window"); + presenter.IsMaximizable = false; + presenter.IsResizable = false; + NavigateToUrlPage(); ResizeWindow(); MoveWindowToCenterOfDisplay(); @@ -30,20 +41,32 @@ public SignInWindow(SignInViewModel viewModel) public void NavigateToTokenPage() { - RootFrame.Content = _signInTokenPage; + RootFrame.SetPage(_signInTokenPage); } public void NavigateToUrlPage() { - RootFrame.Content = _signInUrlPage; + RootFrame.SetPage(_signInUrlPage); + } + + private void RootFrame_SizeChanged(object sender, SizedFrameEventArgs e) + { + ResizeWindow(e.NewSize.Height); } private void ResizeWindow() { + ResizeWindow(RootFrame.GetContentSize().Height); + } + + private void ResizeWindow(double height) + { + if (height <= 0) height = 100; // will be resolved next frame typically + var scale = DisplayScale.WindowScale(this); - var height = (int)(HEIGHT * scale); - var width = (int)(WIDTH * scale); - AppWindow.Resize(new SizeInt32(width, height)); + var newWidth = (int)(WIDTH * scale); + var newHeight = (int)(height * scale); + AppWindow.ResizeClient(new SizeInt32(newWidth, newHeight)); } private void MoveWindowToCenterOfDisplay() diff --git a/App/Views/TrayWindow.xaml b/App/Views/TrayWindow.xaml index c9aa24b..0d87874 100644 --- a/App/Views/TrayWindow.xaml +++ b/App/Views/TrayWindow.xaml @@ -19,6 +19,6 @@ - + diff --git a/App/Views/TrayWindow.xaml.cs b/App/Views/TrayWindow.xaml.cs index 7fd1482..1b27fa1 100644 --- a/App/Views/TrayWindow.xaml.cs +++ b/App/Views/TrayWindow.xaml.cs @@ -1,9 +1,9 @@ using System; using System.Runtime.InteropServices; -using Windows.Foundation; using Windows.Graphics; using Windows.System; using Windows.UI.Core; +using Coder.Desktop.App.Controls; using Coder.Desktop.App.Models; using Coder.Desktop.App.Services; using Coder.Desktop.App.Views.Pages; @@ -48,6 +48,7 @@ public TrayWindow(IRpcController rpcController, ICredentialManager credentialMan AppWindow.Hide(); SystemBackdrop = new DesktopAcrylicBackdrop(); Activated += Window_Activated; + RootFrame.SizeChanged += RootFrame_SizeChanged; rpcController.StateChanged += RpcController_StateChanged; credentialManager.CredentialsChanged += CredentialManager_CredentialsChanged; @@ -120,55 +121,31 @@ public void SetRootFrame(Page page) return; } - if (ReferenceEquals(page, RootFrame.Content)) return; - - if (page.Content is not FrameworkElement newElement) - throw new Exception("Failed to get Page.Content as FrameworkElement on RootFrame navigation"); - newElement.SizeChanged += Content_SizeChanged; - - // Unset the previous event listener. - if (RootFrame.Content is Page { Content: FrameworkElement oldElement }) - oldElement.SizeChanged -= Content_SizeChanged; - - // Swap them out and reconfigure the window. - // We don't use RootFrame.Navigate here because it doesn't let you - // instantiate the page yourself. We also don't need forwards/backwards - // capabilities. - RootFrame.Content = page; - ResizeWindow(); - MoveWindow(); + RootFrame.SetPage(page); } - private void Content_SizeChanged(object sender, SizeChangedEventArgs e) + private void RootFrame_SizeChanged(object sender, SizedFrameEventArgs e) { - ResizeWindow(); + ResizeWindow(e.NewSize.Width); MoveWindow(); } private void ResizeWindow() { - if (RootFrame.Content is not Page { Content: FrameworkElement frameworkElement }) - throw new Exception("Failed to get Content as FrameworkElement for window"); - - // Measure the desired size of the content - frameworkElement.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); - - // Adjust the AppWindow size - var scale = GetDisplayScale(); - var height = (int)(frameworkElement.ActualHeight * scale); - var width = (int)(WIDTH * scale); - AppWindow.Resize(new SizeInt32(width, height)); + ResizeWindow(RootFrame.GetContentSize().Height); } - private double GetDisplayScale() + private void ResizeWindow(double height) { - var hwnd = WindowNative.GetWindowHandle(this); - var dpi = NativeApi.GetDpiForWindow(hwnd); - if (dpi == 0) return 1; // assume scale of 1 - return dpi / 96.0; // 96 DPI == 1 + if (height <= 0) height = 100; // will be resolved next frame typically + + var scale = DisplayScale.WindowScale(this); + var newWidth = (int)(WIDTH * scale); + var newHeight = (int)(height * scale); + AppWindow.Resize(new SizeInt32(newWidth, newHeight)); } - public void MoveResizeAndActivate() + private void MoveResizeAndActivate() { SaveCursorPos(); ResizeWindow(); @@ -268,9 +245,6 @@ public static class NativeApi [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hwnd); - [DllImport("user32.dll")] - public static extern int GetDpiForWindow(IntPtr hwnd); - public struct POINT { public int X; From fe567aab41b68257da9c9f7688d82347eca5d66e Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Fri, 14 Mar 2025 17:25:27 +1000 Subject: [PATCH 2/2] PR comments --- App/Controls/SizedFrame.cs | 7 +++++-- App/Views/TrayWindow.xaml.cs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/App/Controls/SizedFrame.cs b/App/Controls/SizedFrame.cs index ad065c0..a666c55 100644 --- a/App/Controls/SizedFrame.cs +++ b/App/Controls/SizedFrame.cs @@ -11,8 +11,11 @@ public class SizedFrameEventArgs : EventArgs } /// -/// SizedFrame extends Frame by adding a SizeChanged event. Sadly this is necessary because -/// Window.Content.SizeChanged doesn't trigger when the Page's content changes. +/// SizedFrame extends Frame by adding a SizeChanged event, which will be triggered when: +/// - The contained Page's content's size changes +/// - We switch to a different page. +/// +/// Sadly this is necessary because Window.Content.SizeChanged doesn't trigger when the Page's content changes. /// public class SizedFrame : Frame { diff --git a/App/Views/TrayWindow.xaml.cs b/App/Views/TrayWindow.xaml.cs index 1b27fa1..2df9693 100644 --- a/App/Views/TrayWindow.xaml.cs +++ b/App/Views/TrayWindow.xaml.cs @@ -126,7 +126,7 @@ public void SetRootFrame(Page page) private void RootFrame_SizeChanged(object sender, SizedFrameEventArgs e) { - ResizeWindow(e.NewSize.Width); + ResizeWindow(e.NewSize.Height); MoveWindow(); }