223 lines
7.3 KiB
C#
223 lines
7.3 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()
|
|
{
|
|
InitializeComponent();
|
|
Title = "Layers";
|
|
_hwnd = WindowNative.GetWindowHandle(this);
|
|
|
|
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));
|
|
}
|
|
|
|
// Windows App SDK 1.3+: assign a backdrop directly, no MicaController wiring needed.
|
|
SystemBackdrop = new MicaBackdrop { Kind = Microsoft.UI.Composition.SystemBackdrops.MicaKind.Base };
|
|
|
|
Activated += OnActivated;
|
|
Closed += OnClosed;
|
|
|
|
DispatcherQueue.TryEnqueue(() =>
|
|
{
|
|
PointToRust();
|
|
LayersNative.StartupIfNeeded();
|
|
CreateNativeHandle();
|
|
StartRenderLoop();
|
|
});
|
|
}
|
|
|
|
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*. Query it off the XAML element's
|
|
// IUnknown rather than passing the raw WinRT object pointer.
|
|
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;
|
|
}
|
|
}
|
|
|
|
// --- Input routing ---
|
|
|
|
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);
|
|
}
|
|
}
|