更换为wpf

This commit is contained in:
ww-rm
2025-05-27 15:55:10 +08:00
parent d0f629d9ba
commit 17257a0ffe
316 changed files with 19206 additions and 76537 deletions

View File

@@ -0,0 +1,134 @@
using SFML.Graphics;
using SFML.System;
using SFML.Window;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SFMLRenderer
{
public interface ISFMLRenderer
{
/// <summary>
/// 发生在资源首次创建完成后
/// </summary>
public event EventHandler? RendererCreated;
/// <summary>
/// 发生在资源即将不可用之前
/// </summary>
public event EventHandler? RendererDisposing;
public event EventHandler<MouseMoveEventArgs>? CanvasMouseMove;
public event EventHandler<MouseButtonEventArgs>? CanvasMouseButtonPressed;
public event EventHandler<MouseButtonEventArgs>? CanvasMouseButtonReleased;
public event EventHandler<MouseWheelScrollEventArgs>? CanvasMouseWheelScrolled;
/// <summary>
/// 分辨率, 影响画面的相对比例
/// </summary>
public Vector2u Resolution { get; set; }
/// <summary>
/// 快捷设置视区中心点
/// </summary>
public Vector2f Center { get; set; }
/// <summary>
/// 快捷设置视区缩放
/// </summary>
public float Zoom { get; set; }
/// <summary>
/// 快捷设置视区旋转
/// </summary>
public float Rotation { get; set; }
/// <summary>
/// 快捷设置视区水平翻转
/// </summary>
public bool FlipX { get; set; }
/// <summary>
/// 快捷设置视区垂直翻转
/// </summary>
public bool FlipY { get; set; }
/// <summary>
/// 最大帧率, 影响 Draw 的最大调用频率, <see cref="RenderWindow.SetFramerateLimit(uint)"/>
/// </summary>
public uint MaxFps { get; set; }
/// <summary>
/// 垂直同步
/// </summary>
public bool VerticalSync { get; set; }
/// <summary>
/// <see cref="RenderWindow.SetActive(bool)"/>
/// </summary>
public void SetActive(bool active);
/// <summary>
/// <see cref="RenderWindow.GetView"/>
/// </summary>
public View GetView();
/// <summary>
/// <see cref="RenderWindow.SetView(View)"/>
/// </summary>
public void SetView(View view);
/// <summary>
/// <see cref="RenderWindow.MapPixelToCoords(Vector2i)"/>
/// </summary>
public Vector2f MapPixelToCoords(Vector2i point);
/// <summary>
/// <see cref="RenderWindow.MapCoordsToPixel(Vector2f)"/>
/// </summary>
public Vector2i MapCoordsToPixel(Vector2f point);
/// <summary>
/// <see cref="RenderWindow.Clear()"/>
/// </summary>
public void Clear();
/// <summary>
/// <see cref="RenderWindow.Clear(Color)"/>
/// </summary>
public void Clear(Color color);
/// <summary>
/// <see cref="RenderWindow.Draw(Drawable)"/>
/// </summary>
public void Draw(Drawable drawable);
/// <summary>
/// <see cref="RenderWindow.Draw(Drawable, RenderStates)"/>
/// </summary>
public void Draw(Drawable drawable, RenderStates states);
/// <summary>
/// <see cref="RenderWindow.Draw(Vertex[], PrimitiveType)"/>
/// </summary>
public void Draw(Vertex[] vertices, PrimitiveType type);
/// <summary>
/// <see cref="RenderWindow.Draw(Vertex[], PrimitiveType, RenderStates)"/>
/// </summary>
public void Draw(Vertex[] vertices, PrimitiveType type, RenderStates states);
/// <summary>
/// <see cref="RenderWindow.Draw(Vertex[], uint, uint, PrimitiveType)"/>
/// </summary>
public void Draw(Vertex[] vertices, uint start, uint count, PrimitiveType type);
/// <summary>
/// <see cref="RenderWindow.Display"/>
/// </summary>
public void Display();
}
}

3
SFMLRenderer/README.md Normal file
View File

@@ -0,0 +1,3 @@
# SFMLRenderer
这个库封装了一个用于 WPF 的 SFML 渲染控件.

View File

@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
namespace SFMLRenderer
{
public class SFMLHwndHost : HwndHost
{
private HwndSource? _hwndSource;
private SFML.Graphics.RenderWindow? _renderWindow;
public SFML.Graphics.RenderWindow? RenderWindow => _renderWindow;
public event EventHandler? RenderWindowBuilded;
public event EventHandler? RenderWindowDestroying;
protected override HandleRef BuildWindowCore(HandleRef hwndParent)
{
var ps = new HwndSourceParameters(GetType().Name, (int)Width, (int)Height)
{
ParentWindow = hwndParent.Handle,
WindowStyle = 0x40000000 | 0x10000000, // WS_CHILD | WS_VISIBLE
HwndSourceHook = HwndMessageHook
};
_hwndSource = new HwndSource(ps);
_renderWindow = new(_hwndSource.Handle);
_renderWindow.SetActive(false);
RenderWindowBuilded?.Invoke(this, EventArgs.Empty);
return new HandleRef(this, _hwndSource.Handle);
}
protected override void DestroyWindowCore(HandleRef hwnd)
{
RenderWindowDestroying?.Invoke(this, EventArgs.Empty);
_renderWindow?.Close();
var rw = _renderWindow;
_renderWindow = null;
rw?.Dispose();
var hs = _hwndSource;
_hwndSource = null;
hs?.Dispose();
}
private nint HwndMessageHook(nint hwnd, int msg, nint wParam, nint lParam, ref bool handled)
{
_renderWindow?.DispatchEvents();
return nint.Zero;
}
}
}

View File

@@ -0,0 +1,14 @@
<UserControl x:Class="SFMLRenderer.SFMLRenderPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SFMLRenderer"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<local:SFMLHwndHost x:Name="_hwndHost"
Width="100"
Height="100"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</UserControl>

View File

@@ -0,0 +1,253 @@
using SFML.Graphics;
using SFML.System;
using SFML.Window;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SFMLRenderer
{
/// <summary>
/// SFMLRenderPanel.xaml 的交互逻辑
/// </summary>
public partial class SFMLRenderPanel : System.Windows.Controls.UserControl, ISFMLRenderer
{
private RenderWindow? RenderWindow => _hwndHost.RenderWindow;
public SFMLRenderPanel()
{
InitializeComponent();
}
public event EventHandler? RendererCreated
{
add => _hwndHost.RenderWindowBuilded += value;
remove => _hwndHost.RenderWindowBuilded -= value;
}
public event EventHandler? RendererDisposing
{
add => _hwndHost.RenderWindowDestroying += value;
remove => _hwndHost.RenderWindowDestroying -= value;
}
public event EventHandler<MouseMoveEventArgs>? CanvasMouseMove
{
add { if (RenderWindow is RenderWindow w) w.MouseMoved += value; }
remove { if (RenderWindow is RenderWindow w) w.MouseMoved -= value; }
}
public event EventHandler<MouseButtonEventArgs>? CanvasMouseButtonPressed
{
add { if (RenderWindow is RenderWindow w) w.MouseButtonPressed += value; }
remove { if (RenderWindow is RenderWindow w) w.MouseButtonPressed -= value; }
}
public event EventHandler<MouseButtonEventArgs>? CanvasMouseButtonReleased
{
add { if (RenderWindow is RenderWindow w) w.MouseButtonReleased += value; }
remove { if (RenderWindow is RenderWindow w) w.MouseButtonReleased -= value; }
}
public event EventHandler<MouseWheelScrollEventArgs>? CanvasMouseWheelScrolled
{
add { if (RenderWindow is RenderWindow w) w.MouseWheelScrolled += value; }
remove { if (RenderWindow is RenderWindow w) w.MouseWheelScrolled -= value; }
}
public Vector2u Resolution
{
get => _resolution;
set
{
if (RenderWindow is null) return;
if (value == _resolution) return;
if (value.X <= 0 || value.Y <= 0) return;
var zoom = Zoom;
float parentW = (float)ActualWidth;
float parentH = (float)ActualHeight;
float renderW = value.X;
float renderH = value.Y;
float scale = Math.Min(parentW / renderW, parentH / renderH); // 两方向取较小值, 保证 parent 覆盖 render
renderW *= scale;
renderH *= scale;
_hwndHost.Width = renderW;
_hwndHost.Height = renderH;
_resolution = value;
// 设置完 resolution 后还原缩放比例
Zoom = zoom;
}
}
private Vector2u _resolution = new(100, 100);
public Vector2f Center
{
get
{
if (RenderWindow is null) return default;
using var view = RenderWindow.GetView();
return view.Center;
}
set
{
if (RenderWindow is null) return;
using var view = RenderWindow.GetView();
view.Center = value;
RenderWindow.SetView(view);
}
}
public float Zoom
{
get
{
if (RenderWindow is null) return 1;
using var view = RenderWindow.GetView();
return Math.Abs(_resolution.X / view.Size.X); // XXX: 仅使用宽度进行缩放计算
}
set
{
value = Math.Abs(value);
if (RenderWindow is null || value <= 0) return;
using var view = RenderWindow.GetView();
var signX = Math.Sign(view.Size.X);
var signY = Math.Sign(view.Size.Y);
view.Size = new(_resolution.X / value * signX, _resolution.Y / value * signY);
RenderWindow.SetView(view);
}
}
public float Rotation
{
get
{
if (RenderWindow is null) return default;
using var view = RenderWindow.GetView();
return view.Rotation;
}
set
{
if (RenderWindow is null) return;
using var view = RenderWindow.GetView();
view.Rotation = value;
RenderWindow.SetView(view);
}
}
public bool FlipX
{
get
{
if (RenderWindow is null) return false;
using var view = RenderWindow.GetView();
return view.Size.X < 0;
}
set
{
if (RenderWindow is null) return;
using var view = RenderWindow.GetView();
var size = view.Size;
if (size.X > 0 && value || size.X < 0 && !value)
size.X *= -1;
view.Size = size;
RenderWindow.SetView(view);
}
}
public bool FlipY
{
get
{
if (RenderWindow is null) return false;
using var view = RenderWindow.GetView();
return view.Size.Y < 0;
}
set
{
if (RenderWindow is null) return;
using var view = RenderWindow.GetView();
var size = view.Size;
if (size.Y > 0 && value || size.Y < 0 && !value)
size.Y *= -1;
view.Size = size;
RenderWindow.SetView(view);
}
}
public uint MaxFps
{
get => _maxFps;
set
{
if (RenderWindow is null) return;
RenderWindow.SetFramerateLimit(value);
_maxFps = value;
}
}
private uint _maxFps = 0;
public bool VerticalSync
{
get => _verticalSync;
set
{
if (RenderWindow is null) return;
RenderWindow.SetVerticalSyncEnabled(value);
_verticalSync = value;
}
}
private bool _verticalSync = false;
public void Clear() => RenderWindow?.Clear();
public void Clear(Color color) => RenderWindow?.Clear(color);
public void Display() => RenderWindow?.Display();
public void Draw(Drawable drawable) => RenderWindow?.Draw(drawable);
public void Draw(Drawable drawable, RenderStates states) => RenderWindow?.Draw(drawable, states);
public void Draw(Vertex[] vertices, PrimitiveType type) => RenderWindow?.Draw(vertices, type);
public void Draw(Vertex[] vertices, PrimitiveType type, RenderStates states) => RenderWindow?.Draw(vertices, type, states);
public void Draw(Vertex[] vertices, uint start, uint count, PrimitiveType type) => RenderWindow?.Draw(vertices, start, count, type);
public View GetView() => RenderWindow?.GetView() ?? new();
public Vector2i MapCoordsToPixel(Vector2f point) => RenderWindow?.MapCoordsToPixel(point) ?? default;
public Vector2f MapPixelToCoords(Vector2i point) => RenderWindow?.MapPixelToCoords(point) ?? default;
public void SetActive(bool active) => RenderWindow?.SetActive(active);
public void SetView(View view) => RenderWindow?.SetView(view);
protected override void OnRenderSizeChanged(System.Windows.SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
if (RenderWindow is null) return;
float parentW = (float)sizeInfo.NewSize.Width;
float parentH = (float)sizeInfo.NewSize.Height;
float renderW = (float)_hwndHost.ActualWidth;
float renderH = (float)_hwndHost.ActualHeight;
float scale = Math.Min(parentW / renderW, parentH / renderH); // 两方向取较小值, 保证 parent 覆盖 render
renderW *= scale;
renderH *= scale;
_hwndHost.Width = renderW;
_hwndHost.Height = renderH;
}
}
}

View File

@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>
<TargetFramework>net8.0-windows</TargetFramework>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>0.15.0</Version>
<UseWPF>true</UseWPF>
</PropertyGroup>
<PropertyGroup>
<NoWarn>$(NoWarn);NETSDK1206</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SFML.Net" Version="2.6.1" />
</ItemGroup>
</Project>