Layers/shell/windows/LayersShell/MainWindow.xaml.cs

238 lines
7.8 KiB
C#

using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.UI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Windows.Graphics;
using WinRT.Interop;
namespace LayersShell;
public sealed partial class MainWindow : Window
{
private IntPtr _handle = IntPtr.Zero;
private IntPtr _hwnd = IntPtr.Zero;
private const double DefaultLogicalWidth = 480;
private const double DefaultLogicalHeight = 640;
private bool _isFocused = true;
private bool _isHovered = false;
public MainWindow()
{
App.Log("MainWindow ctor enter");
try
{
InitializeComponent();
}
catch (Exception ex) { App.Log($"InitializeComponent FAILED: {ex}"); throw; }
Title = "Layers";
_hwnd = WindowNative.GetWindowHandle(this);
App.Log($"hwnd = {_hwnd:X}");
var appWindow = AppWindow.GetFromWindowId(Win32Interop.GetWindowIdFromWindow(_hwnd));
if (appWindow is not null)
{
appWindow.SetPresenter(AppWindowPresenterKind.Overlapped);
if (appWindow.Presenter is OverlappedPresenter op)
{
op.IsAlwaysOnTop = true;
op.IsResizable = true;
op.IsMaximizable = false;
op.IsMinimizable = true;
op.SetBorderAndTitleBar(hasBorder: false, hasTitleBar: false);
}
appWindow.ResizeClient(new SizeInt32((int)DefaultLogicalWidth, (int)DefaultLogicalHeight));
}
SystemBackdrop = new MicaBackdrop { Kind = Microsoft.UI.Composition.SystemBackdrops.MicaKind.Base };
Activated += OnActivated;
Closed += OnClosed;
DispatcherQueue.TryEnqueue(() =>
{
try
{
App.Log("deferred init: PointToRust");
PointToRust();
App.Log("deferred init: StartupIfNeeded");
LayersNative.StartupIfNeeded();
App.Log("deferred init: CreateNativeHandle");
CreateNativeHandle();
App.Log($"deferred init: _handle = {_handle:X}");
StartRenderLoop();
App.Log("deferred init: render loop started");
}
catch (Exception ex)
{
App.Log($"deferred init FAILED: {ex}");
throw;
}
});
}
private void OnActivated(object sender, WindowActivatedEventArgs e)
{
_isFocused = e.WindowActivationState != WindowActivationState.Deactivated;
ApplyFade();
}
private void OnClosed(object sender, WindowEventArgs args)
{
_renderTimer?.Stop();
_renderTimer = null;
if (_handle != IntPtr.Zero)
{
LayersNative.layers_destroy(_handle);
_handle = IntPtr.Zero;
}
}
private void PointToRust()
{
var exe = System.Reflection.Assembly.GetEntryAssembly()?.Location;
if (string.IsNullOrEmpty(exe)) return;
var binDir = Path.GetDirectoryName(exe) ?? string.Empty;
var pluginRoot = Path.GetDirectoryName(binDir) ?? string.Empty;
if (string.IsNullOrEmpty(pluginRoot)) return;
LayersNative.layers_set_plugin_root(pluginRoot);
}
private void CreateNativeHandle()
{
if (_handle != IntPtr.Zero) return;
var scale = (float)RenderSurface.CompositionScaleX;
var width = (float)RenderSurface.ActualWidth;
var height = (float)RenderSurface.ActualHeight;
if (width <= 0 || height <= 0 || scale <= 0) return;
// wgpu's dx12 backend wants an ISwapChainPanelNative*, not the raw IUnknown.
var iid = typeof(ISwapChainPanelNative).GUID;
var unknown = Marshal.GetIUnknownForObject(RenderSurface);
IntPtr native = IntPtr.Zero;
try
{
int hr = Marshal.QueryInterface(unknown, in iid, out native);
if (hr < 0 || native == IntPtr.Zero) return;
_handle = LayersNative.layers_create_from_swap_chain_panel(native, width, height, scale);
}
finally
{
if (native != IntPtr.Zero) Marshal.Release(native);
Marshal.Release(unknown);
}
}
private DispatcherTimer? _renderTimer;
private void StartRenderLoop()
{
_renderTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(1000.0 / 60.0),
};
_renderTimer.Tick += (_, __) =>
{
if (_handle != IntPtr.Zero)
{
LayersNative.layers_render(_handle);
}
};
_renderTimer.Start();
}
private void ApplyFade()
{
double target = (_isFocused && _isHovered) ? 1.0
: (_isFocused || _isHovered) ? 0.5
: 0.1;
if (Content is FrameworkElement root)
{
root.Opacity = target;
}
}
private void RenderSurface_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (_handle == IntPtr.Zero)
{
CreateNativeHandle();
return;
}
var scale = (float)RenderSurface.CompositionScaleX;
LayersNative.layers_resize(_handle, (float)e.NewSize.Width, (float)e.NewSize.Height, scale);
}
private void RenderSurface_PointerMoved(object sender, PointerRoutedEventArgs e)
{
if (_handle == IntPtr.Zero) return;
var p = e.GetCurrentPoint(RenderSurface).Position;
LayersNative.layers_mouse_move(_handle, (float)p.X, (float)p.Y);
}
private void RenderSurface_PointerPressed(object sender, PointerRoutedEventArgs e)
{
RenderSurface.Focus(FocusState.Programmatic);
PushMouseButton(e, pressed: true);
}
private void RenderSurface_PointerReleased(object sender, PointerRoutedEventArgs e)
{
PushMouseButton(e, pressed: false);
}
private void PushMouseButton(PointerRoutedEventArgs e, bool pressed)
{
if (_handle == IntPtr.Zero) return;
var p = e.GetCurrentPoint(RenderSurface);
uint btn;
var props = p.Properties;
if (props.IsLeftButtonPressed || (!pressed && !props.IsRightButtonPressed && !props.IsMiddleButtonPressed)) btn = 0;
else if (props.IsRightButtonPressed) btn = 1;
else if (props.IsMiddleButtonPressed) btn = 2;
else btn = 0;
LayersNative.layers_mouse_button(_handle, (float)p.Position.X, (float)p.Position.Y, btn, pressed);
}
private void RenderSurface_PointerWheel(object sender, PointerRoutedEventArgs e)
{
if (_handle == IntPtr.Zero) return;
var p = e.GetCurrentPoint(RenderSurface);
var dy = (float)p.Properties.MouseWheelDelta / 3.0f;
LayersNative.layers_mouse_scroll(_handle, (float)p.Position.X, (float)p.Position.Y, 0.0f, dy);
}
private void RenderSurface_PointerEntered(object sender, PointerRoutedEventArgs e)
{
_isHovered = true;
ApplyFade();
}
private void RenderSurface_PointerExited(object sender, PointerRoutedEventArgs e)
{
_isHovered = false;
if (_handle != IntPtr.Zero)
{
LayersNative.layers_mouse_left(_handle);
}
ApplyFade();
}
private void RenderSurface_KeyDown(object sender, KeyRoutedEventArgs e) => DispatchKey(e, pressed: true);
private void RenderSurface_KeyUp(object sender, KeyRoutedEventArgs e) => DispatchKey(e, pressed: false);
private void DispatchKey(KeyRoutedEventArgs e, bool pressed)
{
if (_handle == IntPtr.Zero) return;
uint named = WinKeyMap.MapVirtualKey(e.Key);
string? text = named == 0 ? WinKeyMap.TextForKey(e.Key) : null;
uint mods = WinKeyMap.CurrentModifiers();
LayersNative.layers_key_event(_handle, named, text, mods, (ushort)e.Key, pressed);
}
}