Skip to content

chore: add SizedFrame for window sizing #54

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 14, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions App/Controls/SizedFrame.cs
Original file line number Diff line number Diff line change
@@ -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; }
}

/// <summary>
/// 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.
/// </summary>
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);
}
}
3 changes: 2 additions & 1 deletion App/Views/SignInWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -13,5 +14,5 @@
<DesktopAcrylicBackdrop />
</Window.SystemBackdrop>

<Frame x:Name="RootFrame" />
<controls:SizedFrame x:Name="RootFrame" />
</Window>
39 changes: 31 additions & 8 deletions App/Views/SignInWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,49 +1,72 @@
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;

/// <summary>
/// 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.
/// </summary>
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;

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();
}

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()
Expand Down
2 changes: 1 addition & 1 deletion App/Views/TrayWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@
<controls:TrayIcon x:Name="TrayIcon" />

<!-- This is where the current Page is displayed -->
<Frame x:Name="RootFrame" />
<controls:SizedFrame x:Name="RootFrame" />
</Grid>
</Window>
54 changes: 14 additions & 40 deletions App/Views/TrayWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ResizeWindow(e.NewSize.Width);
ResizeWindow(e.NewSize.Height);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦

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();
Expand Down Expand Up @@ -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;
Expand Down
Loading