Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
580eaf990d | ||
|
|
5ab232a961 | ||
|
|
e596cd7ea4 | ||
|
|
05c47a4daa | ||
|
|
5a8783b5f4 | ||
|
|
08bc171a72 | ||
|
|
7372f5fe08 | ||
|
|
6f032bdd05 | ||
|
|
153d3603d2 | ||
|
|
95261e6907 | ||
|
|
17b344376d | ||
|
|
0ed4e44878 | ||
|
|
b42c1832f0 | ||
|
|
058534ba67 | ||
|
|
204dcd6498 | ||
|
|
2c846c0db9 | ||
|
|
2faeb044e0 | ||
|
|
09c8e4f779 | ||
|
|
6994fa6be8 | ||
|
|
cc7beb7670 | ||
|
|
510653732d | ||
|
|
93e8178d67 | ||
|
|
cebc4864cc | ||
|
|
6ad0449376 | ||
|
|
c33c977326 | ||
|
|
f0299d365a | ||
|
|
6ecdca73f5 | ||
|
|
af6a709b2c | ||
|
|
d5c27450ef | ||
|
|
d10269fb07 | ||
|
|
53d987476e | ||
|
|
8b7866d37f | ||
|
|
bb529729b6 | ||
|
|
b7735d9ba8 | ||
|
|
ce744e2b84 | ||
|
|
631c92da3f | ||
|
|
b7063804e9 | ||
|
|
75d47c8419 | ||
|
|
114fb05e80 |
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,5 +1,17 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
## v0.12.1
|
||||||
|
|
||||||
|
- 优化使用体验, 提供初始皮肤/动画空位
|
||||||
|
- 修复预览画面分辨率调整时父容器尺寸获取错误
|
||||||
|
|
||||||
|
## v0.12.0
|
||||||
|
|
||||||
|
- 支持皮肤列表 (仅 3.8.x 及以上支持)
|
||||||
|
- 支持多轨道动画
|
||||||
|
- 动画和皮肤列表多选时改为取并集
|
||||||
|
- 修复导出时没有正确处理预乘像素的问题
|
||||||
|
|
||||||
## v0.11.5
|
## v0.11.5
|
||||||
|
|
||||||
- 导出格式全面支持
|
- 导出格式全面支持
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# [SpineViewer](https://github.com/ww-rm/SpineViewer)
|
# [SpineViewer](https://github.com/ww-rm/SpineViewer)
|
||||||
|
|
||||||
[](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml)
|
[](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml)
|
||||||
|
[](https://github.com/ww-rm/SpineViewer/releases)
|
||||||
|
[](https://github.com/ww-rm/SpineViewer/releases)
|
||||||
|
|
||||||
[中文](README.md) | [English](README.en.md)
|
[中文](README.md) | [English](README.en.md)
|
||||||
|
|
||||||
@@ -10,6 +12,10 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
:sparkles: v0.12.x New Feature: Support for multi-track animations and multi-skin list management :sparkles:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Head over to the [Release](https://github.com/ww-rm/SpineViewer/releases) page to download the zip package.
|
Head over to the [Release](https://github.com/ww-rm/SpineViewer/releases) page to download the zip package.
|
||||||
@@ -23,7 +29,7 @@ Exporting video formats such as GIF requires that ffmpeg is installed locally an
|
|||||||
## Supported Export Formats
|
## Supported Export Formats
|
||||||
|
|
||||||
| Export Format | Suitable for Scenario |
|
| Export Format | Suitable for Scenario |
|
||||||
| :------------: | :------------------------------------------------------------------------------------:|
|
| ------------ | ------------------------------------------------------------------------------------|
|
||||||
| Single Frame | Supports generating high-definition model snapshots; you can manually adjust the frame. |
|
| Single Frame | Supports generating high-definition model snapshots; you can manually adjust the frame. |
|
||||||
| Frame Sequence | Supports png sequence output with transparency and lossless compression. |
|
| Frame Sequence | Supports png sequence output with transparency and lossless compression. |
|
||||||
| GIF | Ideal for generating preview animations. |
|
| GIF | Ideal for generating preview animations. |
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# [SpineViewer](https://github.com/ww-rm/SpineViewer)
|
# [SpineViewer](https://github.com/ww-rm/SpineViewer)
|
||||||
|
|
||||||
[](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml)
|
[](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml)
|
||||||
|
[](https://github.com/ww-rm/SpineViewer/releases)
|
||||||
|
[](https://github.com/ww-rm/SpineViewer/releases)
|
||||||
|
|
||||||
[中文](README.md) | [English](README.en.md)
|
[中文](README.md) | [English](README.en.md)
|
||||||
|
|
||||||
@@ -10,6 +12,10 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
:sparkles: v0.12.x 新增功能: 支持多轨道动画以及多皮肤列表管理 :sparkles:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
|
|
||||||
前往 [Release](https://github.com/ww-rm/SpineViewer/releases) 界面下载压缩包.
|
前往 [Release](https://github.com/ww-rm/SpineViewer/releases) 界面下载压缩包.
|
||||||
@@ -23,7 +29,7 @@
|
|||||||
## 导出格式支持
|
## 导出格式支持
|
||||||
|
|
||||||
| 导出格式 | 适用场景 |
|
| 导出格式 | 适用场景 |
|
||||||
| :---: | :---: |
|
| --- | --- |
|
||||||
| 单帧画面 | 支持生成高清模型画面图像, 可手动调节需要的一帧. |
|
| 单帧画面 | 支持生成高清模型画面图像, 可手动调节需要的一帧. |
|
||||||
| 帧序列 | 支持 png 格式帧序列, 可保留透明通道且无损压缩. |
|
| 帧序列 | 支持 png 格式帧序列, 可保留透明通道且无损压缩. |
|
||||||
| GIF | 适合生成预览动图. |
|
| GIF | 适合生成预览动图. |
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ namespace SpineRuntime21 {
|
|||||||
|
|
||||||
public AnimationStateData Data { get { return data; } }
|
public AnimationStateData Data { get { return data; } }
|
||||||
public float TimeScale { get { return timeScale; } set { timeScale = value; } }
|
public float TimeScale { get { return timeScale; } set { timeScale = value; } }
|
||||||
|
public List<TrackEntry> Tracks => tracks;
|
||||||
|
|
||||||
public delegate void StartEndDelegate(AnimationState state, int trackIndex);
|
public delegate void StartEndDelegate(AnimationState state, int trackIndex);
|
||||||
public event StartEndDelegate Start;
|
public event StartEndDelegate Start;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using System.Reflection;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
|
||||||
namespace SpineViewer.Controls
|
namespace SpineViewer.Controls
|
||||||
{
|
{
|
||||||
public partial class SpineListView : UserControl
|
public partial class SpineListView : UserControl
|
||||||
|
|||||||
@@ -97,8 +97,8 @@ namespace SpineViewer.Controls
|
|||||||
if (value.Width <= 0) value.Width = 100;
|
if (value.Width <= 0) value.Width = 100;
|
||||||
if (value.Height <= 0) value.Height = 100;
|
if (value.Height <= 0) value.Height = 100;
|
||||||
|
|
||||||
float parentX = Width;
|
float parentX = panel.Parent.Width;
|
||||||
float parentY = Height;
|
float parentY = panel.Parent.Height;
|
||||||
float sizeX = value.Width;
|
float sizeX = value.Width;
|
||||||
float sizeY = value.Height;
|
float sizeY = value.Height;
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ namespace SpineViewer.Controls
|
|||||||
RenderWindow.Size = new((uint)sizeX, (uint)sizeY);
|
RenderWindow.Size = new((uint)sizeX, (uint)sizeY);
|
||||||
|
|
||||||
// 将 view 的大小设置成于 resolution 相同的大小, 其余属性都不变
|
// 将 view 的大小设置成于 resolution 相同的大小, 其余属性都不变
|
||||||
var view = RenderWindow.GetView();
|
using var view = RenderWindow.GetView();
|
||||||
var signX = Math.Sign(view.Size.X);
|
var signX = Math.Sign(view.Size.X);
|
||||||
var signY = Math.Sign(view.Size.Y);
|
var signY = Math.Sign(view.Size.Y);
|
||||||
view.Size = new(value.Width * signX, value.Height * signY);
|
view.Size = new(value.Width * signX, value.Height * signY);
|
||||||
@@ -140,12 +140,13 @@ namespace SpineViewer.Controls
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var center = RenderWindow.GetView().Center;
|
using var view = RenderWindow.GetView();
|
||||||
|
var center = view.Center;
|
||||||
return new(center.X, center.Y);
|
return new(center.X, center.Y);
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
var view = RenderWindow.GetView();
|
using var view = RenderWindow.GetView();
|
||||||
view.Center = new(value.X, value.Y);
|
view.Center = new(value.X, value.Y);
|
||||||
RenderWindow.SetView(view);
|
RenderWindow.SetView(view);
|
||||||
}
|
}
|
||||||
@@ -158,11 +159,15 @@ namespace SpineViewer.Controls
|
|||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public float Zoom
|
public float Zoom
|
||||||
{
|
{
|
||||||
get => resolution.Width / Math.Abs(RenderWindow.GetView().Size.X);
|
get
|
||||||
|
{
|
||||||
|
using var view = RenderWindow.GetView();
|
||||||
|
return resolution.Width / Math.Abs(view.Size.X);
|
||||||
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
value = Math.Clamp(value, ZOOM_MIN, ZOOM_MAX);
|
value = Math.Clamp(value, ZOOM_MIN, ZOOM_MAX);
|
||||||
var view = RenderWindow.GetView();
|
using var view = RenderWindow.GetView();
|
||||||
var signX = Math.Sign(view.Size.X);
|
var signX = Math.Sign(view.Size.X);
|
||||||
var signY = Math.Sign(view.Size.Y);
|
var signY = Math.Sign(view.Size.Y);
|
||||||
view.Size = new(resolution.Width / value * signX, resolution.Height / value * signY);
|
view.Size = new(resolution.Width / value * signX, resolution.Height / value * signY);
|
||||||
@@ -177,10 +182,14 @@ namespace SpineViewer.Controls
|
|||||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||||
public float Rotation
|
public float Rotation
|
||||||
{
|
{
|
||||||
get => RenderWindow.GetView().Rotation;
|
get
|
||||||
|
{
|
||||||
|
using var view = RenderWindow.GetView();
|
||||||
|
return view.Rotation;
|
||||||
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
var view = RenderWindow.GetView();
|
using var view = RenderWindow.GetView();
|
||||||
view.Rotation = value;
|
view.Rotation = value;
|
||||||
RenderWindow.SetView(view);
|
RenderWindow.SetView(view);
|
||||||
}
|
}
|
||||||
@@ -193,10 +202,14 @@ namespace SpineViewer.Controls
|
|||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public bool FlipX
|
public bool FlipX
|
||||||
{
|
{
|
||||||
get => RenderWindow.GetView().Size.X < 0;
|
get
|
||||||
|
{
|
||||||
|
using var view = RenderWindow.GetView();
|
||||||
|
return view.Size.X < 0;
|
||||||
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
var view = RenderWindow.GetView();
|
using var view = RenderWindow.GetView();
|
||||||
var size = view.Size;
|
var size = view.Size;
|
||||||
if (size.X > 0 && value || size.X < 0 && !value)
|
if (size.X > 0 && value || size.X < 0 && !value)
|
||||||
size.X *= -1;
|
size.X *= -1;
|
||||||
@@ -212,10 +225,14 @@ namespace SpineViewer.Controls
|
|||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public bool FlipY
|
public bool FlipY
|
||||||
{
|
{
|
||||||
get => RenderWindow.GetView().Size.Y < 0;
|
get
|
||||||
|
{
|
||||||
|
using var view = RenderWindow.GetView();
|
||||||
|
return view.Size.Y < 0;
|
||||||
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
var view = RenderWindow.GetView();
|
using var view = RenderWindow.GetView();
|
||||||
var size = view.Size;
|
var size = view.Size;
|
||||||
if (size.Y > 0 && value || size.Y < 0 && !value)
|
if (size.Y > 0 && value || size.Y < 0 && !value)
|
||||||
size.Y *= -1;
|
size.Y *= -1;
|
||||||
@@ -619,7 +636,7 @@ namespace SpineViewer.Controls
|
|||||||
lock (SpineListView.Spines)
|
lock (SpineListView.Spines)
|
||||||
{
|
{
|
||||||
foreach (var spine in SpineListView.Spines)
|
foreach (var spine in SpineListView.Spines)
|
||||||
spine.Track0Animation = spine.Track0Animation; // TODO: 多轨道重置
|
spine.ResetAnimationsTime();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -631,7 +648,7 @@ namespace SpineViewer.Controls
|
|||||||
lock (SpineListView.Spines)
|
lock (SpineListView.Spines)
|
||||||
{
|
{
|
||||||
foreach (var spine in SpineListView.Spines)
|
foreach (var spine in SpineListView.Spines)
|
||||||
spine.Track0Animation = spine.Track0Animation; // TODO: 多轨道重置
|
spine.ResetAnimationsTime();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IsUpdating = true;
|
IsUpdating = true;
|
||||||
|
|||||||
153
SpineViewer/Dialogs/AnimationTracksEditorDialog.Designer.cs
generated
Normal file
153
SpineViewer/Dialogs/AnimationTracksEditorDialog.Designer.cs
generated
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
namespace SpineViewer.Dialogs
|
||||||
|
{
|
||||||
|
partial class AnimationTracksEditorDialog
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Required designer variable.
|
||||||
|
/// </summary>
|
||||||
|
private System.ComponentModel.IContainer components = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clean up any resources being used.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing && (components != null))
|
||||||
|
{
|
||||||
|
components.Dispose();
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Windows Form Designer generated code
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Required method for Designer support - do not modify
|
||||||
|
/// the contents of this method with the code editor.
|
||||||
|
/// </summary>
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
panel = new Panel();
|
||||||
|
tableLayoutPanel1 = new TableLayoutPanel();
|
||||||
|
flowLayoutPanel1 = new FlowLayoutPanel();
|
||||||
|
button_Add = new Button();
|
||||||
|
button_Delete = new Button();
|
||||||
|
button_Ok = new Button();
|
||||||
|
propertyGrid_AnimationTracks = new PropertyGrid();
|
||||||
|
panel.SuspendLayout();
|
||||||
|
tableLayoutPanel1.SuspendLayout();
|
||||||
|
flowLayoutPanel1.SuspendLayout();
|
||||||
|
SuspendLayout();
|
||||||
|
//
|
||||||
|
// panel
|
||||||
|
//
|
||||||
|
panel.Controls.Add(tableLayoutPanel1);
|
||||||
|
panel.Dock = DockStyle.Fill;
|
||||||
|
panel.Location = new Point(0, 0);
|
||||||
|
panel.Name = "panel";
|
||||||
|
panel.Padding = new Padding(50, 15, 50, 10);
|
||||||
|
panel.Size = new Size(666, 483);
|
||||||
|
panel.TabIndex = 0;
|
||||||
|
//
|
||||||
|
// tableLayoutPanel1
|
||||||
|
//
|
||||||
|
tableLayoutPanel1.ColumnCount = 1;
|
||||||
|
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||||
|
tableLayoutPanel1.Controls.Add(flowLayoutPanel1, 0, 1);
|
||||||
|
tableLayoutPanel1.Controls.Add(propertyGrid_AnimationTracks, 0, 0);
|
||||||
|
tableLayoutPanel1.Dock = DockStyle.Fill;
|
||||||
|
tableLayoutPanel1.Location = new Point(50, 15);
|
||||||
|
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||||
|
tableLayoutPanel1.RowCount = 2;
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||||
|
tableLayoutPanel1.Size = new Size(566, 458);
|
||||||
|
tableLayoutPanel1.TabIndex = 0;
|
||||||
|
//
|
||||||
|
// flowLayoutPanel1
|
||||||
|
//
|
||||||
|
flowLayoutPanel1.AutoSize = true;
|
||||||
|
flowLayoutPanel1.Controls.Add(button_Add);
|
||||||
|
flowLayoutPanel1.Controls.Add(button_Delete);
|
||||||
|
flowLayoutPanel1.Controls.Add(button_Ok);
|
||||||
|
flowLayoutPanel1.Dock = DockStyle.Fill;
|
||||||
|
flowLayoutPanel1.Location = new Point(3, 415);
|
||||||
|
flowLayoutPanel1.Name = "flowLayoutPanel1";
|
||||||
|
flowLayoutPanel1.Size = new Size(560, 40);
|
||||||
|
flowLayoutPanel1.TabIndex = 2;
|
||||||
|
//
|
||||||
|
// button_Add
|
||||||
|
//
|
||||||
|
button_Add.Location = new Point(3, 3);
|
||||||
|
button_Add.Name = "button_Add";
|
||||||
|
button_Add.Size = new Size(112, 34);
|
||||||
|
button_Add.TabIndex = 0;
|
||||||
|
button_Add.Text = "添加";
|
||||||
|
button_Add.UseVisualStyleBackColor = true;
|
||||||
|
button_Add.Click += button_Add_Click;
|
||||||
|
//
|
||||||
|
// button_Delete
|
||||||
|
//
|
||||||
|
button_Delete.Location = new Point(121, 3);
|
||||||
|
button_Delete.Name = "button_Delete";
|
||||||
|
button_Delete.Size = new Size(112, 34);
|
||||||
|
button_Delete.TabIndex = 1;
|
||||||
|
button_Delete.Text = "删除";
|
||||||
|
button_Delete.UseVisualStyleBackColor = true;
|
||||||
|
button_Delete.Click += button_Delete_Click;
|
||||||
|
//
|
||||||
|
// button_Ok
|
||||||
|
//
|
||||||
|
button_Ok.Location = new Point(239, 3);
|
||||||
|
button_Ok.Name = "button_Ok";
|
||||||
|
button_Ok.Size = new Size(112, 34);
|
||||||
|
button_Ok.TabIndex = 2;
|
||||||
|
button_Ok.Text = "确定";
|
||||||
|
button_Ok.UseVisualStyleBackColor = true;
|
||||||
|
button_Ok.Click += button_Ok_Click;
|
||||||
|
//
|
||||||
|
// propertyGrid_AnimationTracks
|
||||||
|
//
|
||||||
|
propertyGrid_AnimationTracks.Dock = DockStyle.Fill;
|
||||||
|
propertyGrid_AnimationTracks.HelpVisible = false;
|
||||||
|
propertyGrid_AnimationTracks.Location = new Point(3, 3);
|
||||||
|
propertyGrid_AnimationTracks.Name = "propertyGrid_AnimationTracks";
|
||||||
|
propertyGrid_AnimationTracks.PropertySort = PropertySort.NoSort;
|
||||||
|
propertyGrid_AnimationTracks.Size = new Size(560, 406);
|
||||||
|
propertyGrid_AnimationTracks.TabIndex = 1;
|
||||||
|
propertyGrid_AnimationTracks.ToolbarVisible = false;
|
||||||
|
//
|
||||||
|
// AnimationTracksEditorDialog
|
||||||
|
//
|
||||||
|
AcceptButton = button_Ok;
|
||||||
|
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||||
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
|
ClientSize = new Size(666, 483);
|
||||||
|
Controls.Add(panel);
|
||||||
|
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||||
|
MaximizeBox = false;
|
||||||
|
MinimizeBox = false;
|
||||||
|
Name = "AnimationTracksEditorDialog";
|
||||||
|
ShowIcon = false;
|
||||||
|
ShowInTaskbar = false;
|
||||||
|
StartPosition = FormStartPosition.CenterScreen;
|
||||||
|
Text = "多轨道动画实时编辑器";
|
||||||
|
panel.ResumeLayout(false);
|
||||||
|
tableLayoutPanel1.ResumeLayout(false);
|
||||||
|
tableLayoutPanel1.PerformLayout();
|
||||||
|
flowLayoutPanel1.ResumeLayout(false);
|
||||||
|
ResumeLayout(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private Panel panel;
|
||||||
|
private TableLayoutPanel tableLayoutPanel1;
|
||||||
|
private FlowLayoutPanel flowLayoutPanel1;
|
||||||
|
private Button button_Add;
|
||||||
|
private Button button_Delete;
|
||||||
|
private PropertyGrid propertyGrid_AnimationTracks;
|
||||||
|
private Button button_Ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
48
SpineViewer/Dialogs/AnimationTracksEditorDialog.cs
Normal file
48
SpineViewer/Dialogs/AnimationTracksEditorDialog.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using SpineViewer.Spine;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Data;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace SpineViewer.Dialogs
|
||||||
|
{
|
||||||
|
public partial class AnimationTracksEditorDialog : Form
|
||||||
|
{
|
||||||
|
private readonly Spine.Spine spine;
|
||||||
|
public AnimationTracksEditorDialog(Spine.Spine spine)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
this.spine = spine;
|
||||||
|
propertyGrid_AnimationTracks.SelectedObject = spine.AnimationTracks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void button_Add_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
spine.SetAnimation(spine.GetTrackIndices().Max() + 1, spine.AnimationNames[0]);
|
||||||
|
propertyGrid_AnimationTracks.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void button_Delete_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (propertyGrid_AnimationTracks.SelectedGridItem?.Value is TrackWrapper tr)
|
||||||
|
{
|
||||||
|
if (tr.Index == 0)
|
||||||
|
MessageBox.Info("必须保留轨道 0");
|
||||||
|
else
|
||||||
|
spine.ClearTrack(tr.Index);
|
||||||
|
}
|
||||||
|
propertyGrid_AnimationTracks.Refresh();
|
||||||
|
propertyGrid_AnimationTracks.SelectedGridItem = propertyGrid_AnimationTracks.SelectedGridItem?.Parent?.GridItems?.Cast<GridItem>().Last();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void button_Ok_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
DialogResult = DialogResult.OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
120
SpineViewer/Dialogs/AnimationTracksEditorDialog.resx
Normal file
120
SpineViewer/Dialogs/AnimationTracksEditorDialog.resx
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
</root>
|
||||||
153
SpineViewer/Dialogs/SkinManagerEditorDialog.Designer.cs
generated
Normal file
153
SpineViewer/Dialogs/SkinManagerEditorDialog.Designer.cs
generated
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
namespace SpineViewer.Dialogs
|
||||||
|
{
|
||||||
|
partial class SkinManagerEditorDialog
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Required designer variable.
|
||||||
|
/// </summary>
|
||||||
|
private System.ComponentModel.IContainer components = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clean up any resources being used.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing && (components != null))
|
||||||
|
{
|
||||||
|
components.Dispose();
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Windows Form Designer generated code
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Required method for Designer support - do not modify
|
||||||
|
/// the contents of this method with the code editor.
|
||||||
|
/// </summary>
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
panel = new Panel();
|
||||||
|
tableLayoutPanel1 = new TableLayoutPanel();
|
||||||
|
flowLayoutPanel1 = new FlowLayoutPanel();
|
||||||
|
button_Add = new Button();
|
||||||
|
button_Delete = new Button();
|
||||||
|
button_Ok = new Button();
|
||||||
|
propertyGrid_SkinManager = new PropertyGrid();
|
||||||
|
panel.SuspendLayout();
|
||||||
|
tableLayoutPanel1.SuspendLayout();
|
||||||
|
flowLayoutPanel1.SuspendLayout();
|
||||||
|
SuspendLayout();
|
||||||
|
//
|
||||||
|
// panel
|
||||||
|
//
|
||||||
|
panel.Controls.Add(tableLayoutPanel1);
|
||||||
|
panel.Dock = DockStyle.Fill;
|
||||||
|
panel.Location = new Point(0, 0);
|
||||||
|
panel.Name = "panel";
|
||||||
|
panel.Padding = new Padding(50, 15, 50, 10);
|
||||||
|
panel.Size = new Size(666, 483);
|
||||||
|
panel.TabIndex = 0;
|
||||||
|
//
|
||||||
|
// tableLayoutPanel1
|
||||||
|
//
|
||||||
|
tableLayoutPanel1.ColumnCount = 1;
|
||||||
|
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||||
|
tableLayoutPanel1.Controls.Add(flowLayoutPanel1, 0, 1);
|
||||||
|
tableLayoutPanel1.Controls.Add(propertyGrid_SkinManager, 0, 0);
|
||||||
|
tableLayoutPanel1.Dock = DockStyle.Fill;
|
||||||
|
tableLayoutPanel1.Location = new Point(50, 15);
|
||||||
|
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||||
|
tableLayoutPanel1.RowCount = 2;
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||||
|
tableLayoutPanel1.Size = new Size(566, 458);
|
||||||
|
tableLayoutPanel1.TabIndex = 0;
|
||||||
|
//
|
||||||
|
// flowLayoutPanel1
|
||||||
|
//
|
||||||
|
flowLayoutPanel1.AutoSize = true;
|
||||||
|
flowLayoutPanel1.Controls.Add(button_Add);
|
||||||
|
flowLayoutPanel1.Controls.Add(button_Delete);
|
||||||
|
flowLayoutPanel1.Controls.Add(button_Ok);
|
||||||
|
flowLayoutPanel1.Dock = DockStyle.Fill;
|
||||||
|
flowLayoutPanel1.Location = new Point(3, 415);
|
||||||
|
flowLayoutPanel1.Name = "flowLayoutPanel1";
|
||||||
|
flowLayoutPanel1.Size = new Size(560, 40);
|
||||||
|
flowLayoutPanel1.TabIndex = 2;
|
||||||
|
//
|
||||||
|
// button_Add
|
||||||
|
//
|
||||||
|
button_Add.Location = new Point(3, 3);
|
||||||
|
button_Add.Name = "button_Add";
|
||||||
|
button_Add.Size = new Size(112, 34);
|
||||||
|
button_Add.TabIndex = 0;
|
||||||
|
button_Add.Text = "添加";
|
||||||
|
button_Add.UseVisualStyleBackColor = true;
|
||||||
|
button_Add.Click += button_Add_Click;
|
||||||
|
//
|
||||||
|
// button_Delete
|
||||||
|
//
|
||||||
|
button_Delete.Location = new Point(121, 3);
|
||||||
|
button_Delete.Name = "button_Delete";
|
||||||
|
button_Delete.Size = new Size(112, 34);
|
||||||
|
button_Delete.TabIndex = 1;
|
||||||
|
button_Delete.Text = "删除";
|
||||||
|
button_Delete.UseVisualStyleBackColor = true;
|
||||||
|
button_Delete.Click += button_Delete_Click;
|
||||||
|
//
|
||||||
|
// button_Ok
|
||||||
|
//
|
||||||
|
button_Ok.Location = new Point(239, 3);
|
||||||
|
button_Ok.Name = "button_Ok";
|
||||||
|
button_Ok.Size = new Size(112, 34);
|
||||||
|
button_Ok.TabIndex = 3;
|
||||||
|
button_Ok.Text = "确定";
|
||||||
|
button_Ok.UseVisualStyleBackColor = true;
|
||||||
|
button_Ok.Click += button_Ok_Click;
|
||||||
|
//
|
||||||
|
// propertyGrid_SkinManager
|
||||||
|
//
|
||||||
|
propertyGrid_SkinManager.Dock = DockStyle.Fill;
|
||||||
|
propertyGrid_SkinManager.HelpVisible = false;
|
||||||
|
propertyGrid_SkinManager.Location = new Point(3, 3);
|
||||||
|
propertyGrid_SkinManager.Name = "propertyGrid_SkinManager";
|
||||||
|
propertyGrid_SkinManager.PropertySort = PropertySort.NoSort;
|
||||||
|
propertyGrid_SkinManager.Size = new Size(560, 406);
|
||||||
|
propertyGrid_SkinManager.TabIndex = 1;
|
||||||
|
propertyGrid_SkinManager.ToolbarVisible = false;
|
||||||
|
//
|
||||||
|
// SkinManagerEditorDialog
|
||||||
|
//
|
||||||
|
AcceptButton = button_Ok;
|
||||||
|
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||||
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
|
ClientSize = new Size(666, 483);
|
||||||
|
Controls.Add(panel);
|
||||||
|
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||||
|
MaximizeBox = false;
|
||||||
|
MinimizeBox = false;
|
||||||
|
Name = "SkinManagerEditorDialog";
|
||||||
|
ShowIcon = false;
|
||||||
|
ShowInTaskbar = false;
|
||||||
|
StartPosition = FormStartPosition.CenterScreen;
|
||||||
|
Text = "皮肤列表实时编辑器";
|
||||||
|
panel.ResumeLayout(false);
|
||||||
|
tableLayoutPanel1.ResumeLayout(false);
|
||||||
|
tableLayoutPanel1.PerformLayout();
|
||||||
|
flowLayoutPanel1.ResumeLayout(false);
|
||||||
|
ResumeLayout(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private Panel panel;
|
||||||
|
private TableLayoutPanel tableLayoutPanel1;
|
||||||
|
private FlowLayoutPanel flowLayoutPanel1;
|
||||||
|
private Button button_Add;
|
||||||
|
private Button button_Delete;
|
||||||
|
private PropertyGrid propertyGrid_SkinManager;
|
||||||
|
private Button button_Ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
50
SpineViewer/Dialogs/SkinManagerEditorDialog.cs
Normal file
50
SpineViewer/Dialogs/SkinManagerEditorDialog.cs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
using SpineViewer.Spine;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Data;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace SpineViewer.Dialogs
|
||||||
|
{
|
||||||
|
public partial class SkinManagerEditorDialog : Form
|
||||||
|
{
|
||||||
|
private readonly Spine.Spine spine;
|
||||||
|
public SkinManagerEditorDialog(Spine.Spine spine)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
this.spine = spine;
|
||||||
|
propertyGrid_SkinManager.SelectedObject = spine.SkinManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void button_Add_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (spine.SkinNames.Count <= 0)
|
||||||
|
{
|
||||||
|
MessageBox.Info($"{spine.Name} 没有可用的皮肤");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
spine.LoadSkin(spine.SkinNames[0]);
|
||||||
|
propertyGrid_SkinManager.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void button_Delete_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (propertyGrid_SkinManager.SelectedGridItem?.Value is SkinWrapper sk)
|
||||||
|
spine.UnloadSkin(sk.Index);
|
||||||
|
propertyGrid_SkinManager.Refresh();
|
||||||
|
|
||||||
|
if (propertyGrid_SkinManager.SelectedGridItem?.Parent?.GridItems?.Cast<GridItem>().Last() is GridItem gt)
|
||||||
|
propertyGrid_SkinManager.SelectedGridItem = gt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void button_Ok_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
DialogResult = DialogResult.OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
120
SpineViewer/Dialogs/SkinManagerEditorDialog.resx
Normal file
120
SpineViewer/Dialogs/SkinManagerEditorDialog.resx
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
</root>
|
||||||
@@ -8,13 +8,14 @@ using System.Linq;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using static System.Windows.Forms.VisualStyles.VisualStyleElement.StartPanel;
|
||||||
|
|
||||||
namespace SpineViewer.Exporter
|
namespace SpineViewer.Exporter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 导出参数基类
|
/// 导出参数基类
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class ExportArgs : ImplementationResolver<ExportArgs, ExportImplementationAttribute, ExportType>
|
public abstract class ExportArgs : ImplementationResolver<ExportArgs, ExportImplementationAttribute, ExportType>, IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建指定类型导出参数
|
/// 创建指定类型导出参数
|
||||||
@@ -34,6 +35,10 @@ namespace SpineViewer.Exporter
|
|||||||
RenderSelectedOnly = renderSelectedOnly;
|
RenderSelectedOnly = renderSelectedOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
~ExportArgs() { Dispose(false); }
|
||||||
|
public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
|
||||||
|
protected virtual void Dispose(bool disposing) { View?.Dispose(); }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 输出文件夹
|
/// 输出文件夹
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -72,7 +77,27 @@ namespace SpineViewer.Exporter
|
|||||||
[Editor(typeof(SFMLColorEditor), typeof(UITypeEditor))]
|
[Editor(typeof(SFMLColorEditor), typeof(UITypeEditor))]
|
||||||
[TypeConverter(typeof(SFMLColorConverter))]
|
[TypeConverter(typeof(SFMLColorConverter))]
|
||||||
[Category("[0] 导出"), DisplayName("背景颜色"), Description("要使用的背景色, 格式为 #RRGGBBAA")]
|
[Category("[0] 导出"), DisplayName("背景颜色"), Description("要使用的背景色, 格式为 #RRGGBBAA")]
|
||||||
public SFML.Graphics.Color BackgroundColor { get; set; } = SFML.Graphics.Color.Transparent;
|
public SFML.Graphics.Color BackgroundColor
|
||||||
|
{
|
||||||
|
get => backgroundColor;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
backgroundColor = value;
|
||||||
|
var bcPma = value;
|
||||||
|
var a = bcPma.A / 255f;
|
||||||
|
bcPma.R = (byte)(bcPma.R * a);
|
||||||
|
bcPma.G = (byte)(bcPma.G * a);
|
||||||
|
bcPma.B = (byte)(bcPma.B * a);
|
||||||
|
BackgroundColorPma = bcPma;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private SFML.Graphics.Color backgroundColor = SFML.Graphics.Color.Transparent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 预乘后的背景颜色
|
||||||
|
/// </summary>
|
||||||
|
[Browsable(false)]
|
||||||
|
public SFML.Graphics.Color BackgroundColorPma { get; private set; } = SFML.Graphics.Color.Transparent;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 检查参数是否合法并规范化参数值, 否则返回用户错误原因
|
/// 检查参数是否合法并规范化参数值, 否则返回用户错误原因
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ namespace SpineViewer.Exporter
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SFML.Graphics.Image 帧对象包装类
|
/// SFML.Graphics.Image 帧对象包装类, 将接管给定的 image 对象生命周期
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SFMLImageVideoFrame(SFML.Graphics.Image image) : IVideoFrame, IDisposable
|
public class SFMLImageVideoFrame(SFML.Graphics.Image image) : IVideoFrame, IDisposable
|
||||||
{
|
{
|
||||||
@@ -64,12 +64,7 @@ namespace SpineViewer.Exporter
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取 Winforms Bitmap 对象
|
/// 获取 Winforms Bitmap 对象
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Bitmap CopyToBitmap()
|
public Bitmap CopyToBitmap() => image.CopyToBitmap();
|
||||||
{
|
|
||||||
image.SaveToMemory(out var imgBuffer, "bmp");
|
|
||||||
using var stream = new MemoryStream(imgBuffer);
|
|
||||||
return new(new Bitmap(stream)); // 必须重复构造一个副本才能摆脱对流的依赖, 否则之后使用会报错
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -86,51 +81,5 @@ namespace SpineViewer.Exporter
|
|||||||
else if (imageFormat == ImageFormat.Exif) return ".jpeg";
|
else if (imageFormat == ImageFormat.Exif) return ".jpeg";
|
||||||
else return $".{imageFormat.ToString().ToLower()}";
|
else return $".{imageFormat.ToString().ToLower()}";
|
||||||
}
|
}
|
||||||
|
|
||||||
#region 包围盒辅助函数
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取某个包围盒下合适的视图
|
|
||||||
/// </summary>
|
|
||||||
public static SFML.Graphics.View GetView(this RectangleF bounds, Size resolution, Padding padding)
|
|
||||||
=> bounds.GetView((uint)resolution.Width, (uint)resolution.Height, (uint)padding.Left, (uint)padding.Right, (uint)padding.Top, (uint)padding.Bottom);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取某个包围盒下合适的视图
|
|
||||||
/// </summary>
|
|
||||||
public static SFML.Graphics.View GetView(this RectangleF bounds, uint width, uint height, Padding padding)
|
|
||||||
=> bounds.GetView(width, height, (uint)padding.Left, (uint)padding.Right, (uint)padding.Top, (uint)padding.Bottom);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取某个包围盒下合适的视图
|
|
||||||
/// </summary>
|
|
||||||
public static SFML.Graphics.View GetView(this RectangleF bounds, Size resolution, uint paddingL = 1, uint paddingR = 1, uint paddingT = 1, uint paddingB = 1)
|
|
||||||
=> bounds.GetView((uint)resolution.Width, (uint)resolution.Height, paddingL, paddingR, paddingT, paddingB);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取某个包围盒下合适的视图
|
|
||||||
/// </summary>
|
|
||||||
public static SFML.Graphics.View GetView(this RectangleF bounds, uint width, uint height, uint paddingL = 1, uint paddingR = 1, uint paddingT = 1, uint paddingB = 1)
|
|
||||||
{
|
|
||||||
float sizeX = bounds.Width;
|
|
||||||
float sizeY = bounds.Height;
|
|
||||||
float innerW = width - paddingL - paddingR;
|
|
||||||
float innerH = height - paddingT - paddingB;
|
|
||||||
|
|
||||||
float scale = 1;
|
|
||||||
if (sizeY / sizeX < innerH / innerW)
|
|
||||||
scale = sizeX / innerW; // 相同的 X, 视窗 Y 更大
|
|
||||||
else
|
|
||||||
scale = sizeY / innerH; // 相同的 Y, 视窗 X 更大
|
|
||||||
|
|
||||||
var x = bounds.X + bounds.Width / 2 + (paddingL - (float)paddingR) * scale;
|
|
||||||
var y = bounds.Y + bounds.Height / 2 + (paddingT - (float)paddingB) * scale;
|
|
||||||
var viewX = width * scale;
|
|
||||||
var viewY = height * scale;
|
|
||||||
|
|
||||||
return new(new(x, y), new(viewX, -viewY));
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using NLog;
|
using NLog;
|
||||||
using SpineViewer.Spine;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
@@ -15,6 +14,11 @@ namespace SpineViewer.Exporter
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class Exporter(ExportArgs exportArgs) : ImplementationResolver<Exporter, ExportImplementationAttribute, ExportType>
|
public abstract class Exporter(ExportArgs exportArgs) : ImplementationResolver<Exporter, ExportImplementationAttribute, ExportType>
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 仅源像素混合模式
|
||||||
|
/// </summary>
|
||||||
|
private static readonly SFML.Graphics.BlendMode SrcOnlyBlendMode = new(SFML.Graphics.BlendMode.Factor.One, SFML.Graphics.BlendMode.Factor.Zero);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建指定类型导出器
|
/// 创建指定类型导出器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -52,28 +56,55 @@ namespace SpineViewer.Exporter
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取单个模型的单帧画面
|
/// 获取单个模型的单帧画面
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected SFMLImageVideoFrame GetFrame(Spine.Spine spine)
|
protected SFMLImageVideoFrame GetFrame(Spine.Spine spine) => GetFrame([spine]);
|
||||||
{
|
|
||||||
// tex 必须临时创建, 随用随取, 防止出现跨线程的情况
|
|
||||||
using var tex = GetRenderTexture();
|
|
||||||
tex.Clear(ExportArgs.BackgroundColor);
|
|
||||||
tex.Draw(spine);
|
|
||||||
tex.Display();
|
|
||||||
return new(tex.Texture.CopyToImage());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取模型列表的单帧画面
|
/// 获取模型列表的单帧画面
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected SFMLImageVideoFrame GetFrame(Spine.Spine[] spinesToRender)
|
protected SFMLImageVideoFrame GetFrame(Spine.Spine[] spinesToRender)
|
||||||
{
|
{
|
||||||
// tex 必须临时创建, 随用随取, 防止出现跨线程的情况
|
// RenderTexture 必须临时创建, 随用随取, 防止出现跨线程的情况
|
||||||
|
using var texPma = GetRenderTexture();
|
||||||
|
|
||||||
|
// 先将预乘结果准确绘制出来, 注意背景色也应当是预乘的
|
||||||
|
texPma.Clear(ExportArgs.BackgroundColorPma);
|
||||||
|
foreach (var spine in spinesToRender) texPma.Draw(spine);
|
||||||
|
texPma.Display();
|
||||||
|
|
||||||
|
// 背景色透明度不为 1 时需要处理反预乘, 否则直接就是结果
|
||||||
|
if (ExportArgs.BackgroundColor.A < 255)
|
||||||
|
{
|
||||||
|
// 从预乘结果构造渲染对象, 并正确设置变换
|
||||||
|
using var view = texPma.GetView();
|
||||||
|
using var img = texPma.Texture.CopyToImage();
|
||||||
|
using var texSprite = new SFML.Graphics.Texture(img);
|
||||||
|
using var sp = new SFML.Graphics.Sprite(texSprite)
|
||||||
|
{
|
||||||
|
Origin = new(texPma.Size.X / 2f, texPma.Size.Y / 2f),
|
||||||
|
Position = new(view.Center.X, view.Center.Y),
|
||||||
|
Scale = new(view.Size.X / texPma.Size.X, view.Size.Y / texPma.Size.Y),
|
||||||
|
Rotation = view.Rotation
|
||||||
|
};
|
||||||
|
|
||||||
|
// 混合模式用直接覆盖的方式, 保证得到的图像区域是反预乘的颜色和透明度, 同时使用反预乘着色器
|
||||||
|
var st = SFML.Graphics.RenderStates.Default;
|
||||||
|
st.BlendMode = SrcOnlyBlendMode; // 用源的颜色和透明度直接覆盖
|
||||||
|
st.Shader = Shader.InversePma;
|
||||||
|
|
||||||
|
// 在最终结果上二次渲染非预乘画面
|
||||||
using var tex = GetRenderTexture();
|
using var tex = GetRenderTexture();
|
||||||
|
|
||||||
|
// 将非预乘结果覆盖式绘制在目标对象上, 注意背景色应该用非预乘的
|
||||||
tex.Clear(ExportArgs.BackgroundColor);
|
tex.Clear(ExportArgs.BackgroundColor);
|
||||||
foreach (var spine in spinesToRender) tex.Draw(spine);
|
tex.Draw(sp, st);
|
||||||
tex.Display();
|
tex.Display();
|
||||||
return new(tex.Texture.CopyToImage());
|
return new(tex.Texture.CopyToImage());
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new(texPma.Texture.CopyToImage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 每个模型在同一个画面进行导出
|
/// 每个模型在同一个画面进行导出
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using FFMpegCore.Enums;
|
using System;
|
||||||
using FFMpegCore;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 文件格式
|
/// 文件格式
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Category("[2] FFmpeg 基本参数"), DisplayName("文件格式"), Description("文件格式")]
|
[Category("[2] FFmpeg 基本参数"), DisplayName("文件格式"), Description("-f, 文件格式")]
|
||||||
public abstract string Format { get; }
|
public abstract string Format { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -16,9 +16,6 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
|||||||
{
|
{
|
||||||
public GifExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
|
public GifExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
|
||||||
{
|
{
|
||||||
// 给一个纯白的背景
|
|
||||||
BackgroundColor = new(255, 255, 255, 0);
|
|
||||||
|
|
||||||
// GIF 的帧率不能太高, 超过 50 帧反而会变慢
|
// GIF 的帧率不能太高, 超过 50 帧反而会变慢
|
||||||
FPS = 12;
|
FPS = 12;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
|||||||
{
|
{
|
||||||
public MkvExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
|
public MkvExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
|
||||||
{
|
{
|
||||||
BackgroundColor = new(0, 255, 0, 0);
|
BackgroundColor = new(0, 255, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string Format => "matroska";
|
public override string Format => "matroska";
|
||||||
@@ -28,7 +28,7 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[StringEnumConverter.StandardValues("libx264", "libx265", "libvpx-vp9", Customizable = true)]
|
[StringEnumConverter.StandardValues("libx264", "libx265", "libvpx-vp9", Customizable = true)]
|
||||||
[TypeConverter(typeof(StringEnumConverter))]
|
[TypeConverter(typeof(StringEnumConverter))]
|
||||||
[Category("[3] 格式参数"), DisplayName("编码器"), Description("要使用的编码器")]
|
[Category("[3] 格式参数"), DisplayName("编码器"), Description("-c:v, 要使用的编码器")]
|
||||||
public string Codec { get; set; } = "libx265";
|
public string Codec { get; set; } = "libx265";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -43,7 +43,7 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[StringEnumConverter.StandardValues("yuv420p", "yuv422p", "yuv444p", "yuva420p", Customizable = true)]
|
[StringEnumConverter.StandardValues("yuv420p", "yuv422p", "yuv444p", "yuva420p", Customizable = true)]
|
||||||
[TypeConverter(typeof(StringEnumConverter))]
|
[TypeConverter(typeof(StringEnumConverter))]
|
||||||
[Category("[3] 格式参数"), DisplayName("像素格式"), Description("要使用的像素格式")]
|
[Category("[3] 格式参数"), DisplayName("像素格式"), Description("-pix_fmt, 要使用的像素格式")]
|
||||||
public string PixelFormat { get; set; } = "yuv444p";
|
public string PixelFormat { get; set; } = "yuv444p";
|
||||||
|
|
||||||
public override void SetOutputOptions(FFMpegArgumentOptions options)
|
public override void SetOutputOptions(FFMpegArgumentOptions options)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
|||||||
{
|
{
|
||||||
public MovExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
|
public MovExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
|
||||||
{
|
{
|
||||||
BackgroundColor = new(0, 255, 0, 0);
|
BackgroundColor = new(0, 255, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string Format => "mov";
|
public override string Format => "mov";
|
||||||
@@ -28,13 +28,13 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[StringEnumConverter.StandardValues("prores_ks", Customizable = true)]
|
[StringEnumConverter.StandardValues("prores_ks", Customizable = true)]
|
||||||
[TypeConverter(typeof(StringEnumConverter))]
|
[TypeConverter(typeof(StringEnumConverter))]
|
||||||
[Category("[3] 格式参数"), DisplayName("编码器"), Description("要使用的编码器")]
|
[Category("[3] 格式参数"), DisplayName("编码器"), Description("-c:v, 要使用的编码器")]
|
||||||
public string Codec { get; set; } = "prores_ks";
|
public string Codec { get; set; } = "prores_ks";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 预设
|
/// 预设
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[StringEnumConverter.StandardValues("auto", "proxy", "lt", "standard", "hq", "4444", "444xq")]
|
[StringEnumConverter.StandardValues("auto", "proxy", "lt", "standard", "hq", "4444", "4444xq")]
|
||||||
[TypeConverter(typeof(StringEnumConverter))]
|
[TypeConverter(typeof(StringEnumConverter))]
|
||||||
[Category("[3] 格式参数"), DisplayName("预设"), Description("-profile, 预设配置")]
|
[Category("[3] 格式参数"), DisplayName("预设"), Description("-profile, 预设配置")]
|
||||||
public string Profile { get; set; } = "auto";
|
public string Profile { get; set; } = "auto";
|
||||||
@@ -44,7 +44,7 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[StringEnumConverter.StandardValues("yuv422p10le", "yuv444p10le", "yuva444p10le", Customizable = true)]
|
[StringEnumConverter.StandardValues("yuv422p10le", "yuv444p10le", "yuva444p10le", Customizable = true)]
|
||||||
[TypeConverter(typeof(StringEnumConverter))]
|
[TypeConverter(typeof(StringEnumConverter))]
|
||||||
[Category("[3] 格式参数"), DisplayName("像素格式"), Description("要使用的像素格式")]
|
[Category("[3] 格式参数"), DisplayName("像素格式"), Description("-pix_fmt, 要使用的像素格式")]
|
||||||
public string PixelFormat { get; set; } = "yuva444p10le";
|
public string PixelFormat { get; set; } = "yuva444p10le";
|
||||||
|
|
||||||
public override void SetOutputOptions(FFMpegArgumentOptions options)
|
public override void SetOutputOptions(FFMpegArgumentOptions options)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
|||||||
{
|
{
|
||||||
public Mp4ExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
|
public Mp4ExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
|
||||||
{
|
{
|
||||||
BackgroundColor = new(0, 255, 0, 0);
|
BackgroundColor = new(0, 255, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string Format => "mp4";
|
public override string Format => "mp4";
|
||||||
@@ -28,7 +28,7 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[StringEnumConverter.StandardValues("libx264", "libx265", Customizable = true)]
|
[StringEnumConverter.StandardValues("libx264", "libx265", Customizable = true)]
|
||||||
[TypeConverter(typeof(StringEnumConverter))]
|
[TypeConverter(typeof(StringEnumConverter))]
|
||||||
[Category("[3] 格式参数"), DisplayName("编码器"), Description("要使用的编码器")]
|
[Category("[3] 格式参数"), DisplayName("编码器"), Description("-c:v, 要使用的编码器")]
|
||||||
public string Codec { get; set; } = "libx264";
|
public string Codec { get; set; } = "libx264";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -43,7 +43,7 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[StringEnumConverter.StandardValues("yuv420p", "yuv422p", "yuv444p", Customizable = true)]
|
[StringEnumConverter.StandardValues("yuv420p", "yuv422p", "yuv444p", Customizable = true)]
|
||||||
[TypeConverter(typeof(StringEnumConverter))]
|
[TypeConverter(typeof(StringEnumConverter))]
|
||||||
[Category("[3] 格式参数"), DisplayName("像素格式"), Description("要使用的像素格式")]
|
[Category("[3] 格式参数"), DisplayName("像素格式"), Description("-pix_fmt, 要使用的像素格式")]
|
||||||
public string PixelFormat { get; set; } = "yuv444p";
|
public string PixelFormat { get; set; } = "yuv444p";
|
||||||
|
|
||||||
public override void SetOutputOptions(FFMpegArgumentOptions options)
|
public override void SetOutputOptions(FFMpegArgumentOptions options)
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using FFMpegCore.Enums;
|
using System;
|
||||||
using FFMpegCore;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -19,7 +17,7 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 导出时长
|
/// 导出时长
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Category("[1] 视频参数"), DisplayName("时长"), Description("可以从模型列表查看动画时长, 如果小于 0, 则在逐个导出时每个模型使用各自的当前动画时长")]
|
[Category("[1] 视频参数"), DisplayName("时长"), Description("可以从模型列表查看动画时长, 如果小于 0, 则在逐个导出时每个模型使用各自的所有轨道动画时长最大值")]
|
||||||
public float Duration
|
public float Duration
|
||||||
{
|
{
|
||||||
get => duration;
|
get => duration;
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[StringEnumConverter.StandardValues("libvpx-vp9", Customizable = true)]
|
[StringEnumConverter.StandardValues("libvpx-vp9", Customizable = true)]
|
||||||
[TypeConverter(typeof(StringEnumConverter))]
|
[TypeConverter(typeof(StringEnumConverter))]
|
||||||
[Category("[3] 格式参数"), DisplayName("编码器"), Description("要使用的编码器")]
|
[Category("[3] 格式参数"), DisplayName("编码器"), Description("-c:v, 要使用的编码器")]
|
||||||
public string Codec { get; set; } = "libvpx-vp9";
|
public string Codec { get; set; } = "libvpx-vp9";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// CRF
|
/// CRF
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Category("[3] 格式参数"), DisplayName("CRF"), Description("Constant Rate Factor, 取值范围 0-63, 建议范围 18-28, 默认取值 23, 数值越小则输出质量越高")]
|
[Category("[3] 格式参数"), DisplayName("CRF"), Description("-crf, 取值范围 0-63, 建议范围 18-28, 默认取值 23, 数值越小则输出质量越高")]
|
||||||
public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); }
|
public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); }
|
||||||
private int crf = 23;
|
private int crf = 23;
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[StringEnumConverter.StandardValues("yuv420p", "yuv422p", "yuv444p", "yuva420p", Customizable = true)]
|
[StringEnumConverter.StandardValues("yuv420p", "yuv422p", "yuv444p", "yuva420p", Customizable = true)]
|
||||||
[TypeConverter(typeof(StringEnumConverter))]
|
[TypeConverter(typeof(StringEnumConverter))]
|
||||||
[Category("[3] 格式参数"), DisplayName("像素格式"), Description("要使用的像素格式")]
|
[Category("[3] 格式参数"), DisplayName("像素格式"), Description("-pix_fmt, 要使用的像素格式")]
|
||||||
public string PixelFormat { get; set; } = "yuva420p";
|
public string PixelFormat { get; set; } = "yuva420p";
|
||||||
|
|
||||||
public override void SetOutputOptions(FFMpegArgumentOptions options)
|
public override void SetOutputOptions(FFMpegArgumentOptions options)
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ using System.ComponentModel;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using FFMpegCore.Arguments;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace SpineViewer.Exporter.Implementations.Exporter
|
namespace SpineViewer.Exporter.Implementations.Exporter
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ namespace SpineViewer.Exporter.Implementations.Exporter
|
|||||||
{
|
{
|
||||||
var args = (VideoExportArgs)ExportArgs;
|
var args = (VideoExportArgs)ExportArgs;
|
||||||
|
|
||||||
// 独立导出时如果 args.Duration 小于 0 则使用 Track0 的动画时长
|
// 独立导出时如果 args.Duration 小于 0 则使用所有轨道上动画时长最大值
|
||||||
var duration = args.Duration;
|
var duration = args.Duration;
|
||||||
if (duration < 0) duration = spine.GetAnimationDuration(spine.Track0Animation); // TODO: 也许可以使用所有轨道的最大值
|
if (duration < 0) duration = spine.GetTrackIndices().Select(i => spine.GetAnimationDuration(spine.GetAnimation(i))).Max();
|
||||||
|
|
||||||
float delta = 1f / args.FPS;
|
float delta = 1f / args.FPS;
|
||||||
int total = Math.Max(1, (int)(duration * args.FPS)); // 至少导出 1 帧
|
int total = Math.Max(1, (int)(duration * args.FPS)); // 至少导出 1 帧
|
||||||
@@ -75,7 +75,7 @@ namespace SpineViewer.Exporter.Implementations.Exporter
|
|||||||
public override void Export(Spine.Spine[] spines, BackgroundWorker? worker = null)
|
public override void Export(Spine.Spine[] spines, BackgroundWorker? worker = null)
|
||||||
{
|
{
|
||||||
// 导出视频格式需要把模型时间都重置到 0
|
// 导出视频格式需要把模型时间都重置到 0
|
||||||
foreach (var spine in spines) spine.Track0Animation = spine.Track0Animation; // TODO: 多轨道重置
|
foreach (var spine in spines) spine.ResetAnimationsTime();
|
||||||
base.Export(spines, worker);
|
base.Export(spines, worker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
66
SpineViewer/MainForm.Designer.cs
generated
66
SpineViewer/MainForm.Designer.cs
generated
@@ -39,10 +39,10 @@
|
|||||||
toolStripMenuItem_ExportFrame = new ToolStripMenuItem();
|
toolStripMenuItem_ExportFrame = new ToolStripMenuItem();
|
||||||
toolStripMenuItem_ExportFrameSequence = new ToolStripMenuItem();
|
toolStripMenuItem_ExportFrameSequence = new ToolStripMenuItem();
|
||||||
toolStripMenuItem_ExportGif = new ToolStripMenuItem();
|
toolStripMenuItem_ExportGif = new ToolStripMenuItem();
|
||||||
toolStripMenuItem_ExportMkv = new ToolStripMenuItem();
|
|
||||||
toolStripMenuItem_ExportMp4 = new ToolStripMenuItem();
|
toolStripMenuItem_ExportMp4 = new ToolStripMenuItem();
|
||||||
toolStripMenuItem_ExportMov = new ToolStripMenuItem();
|
|
||||||
toolStripMenuItem_ExportWebm = new ToolStripMenuItem();
|
toolStripMenuItem_ExportWebm = new ToolStripMenuItem();
|
||||||
|
toolStripMenuItem_ExportMkv = new ToolStripMenuItem();
|
||||||
|
toolStripMenuItem_ExportMov = new ToolStripMenuItem();
|
||||||
toolStripMenuItem_ExportCustom = new ToolStripMenuItem();
|
toolStripMenuItem_ExportCustom = new ToolStripMenuItem();
|
||||||
toolStripSeparator2 = new ToolStripSeparator();
|
toolStripSeparator2 = new ToolStripSeparator();
|
||||||
toolStripMenuItem_Exit = new ToolStripMenuItem();
|
toolStripMenuItem_Exit = new ToolStripMenuItem();
|
||||||
@@ -115,27 +115,27 @@
|
|||||||
//
|
//
|
||||||
toolStripMenuItem_Open.Name = "toolStripMenuItem_Open";
|
toolStripMenuItem_Open.Name = "toolStripMenuItem_Open";
|
||||||
toolStripMenuItem_Open.ShortcutKeys = Keys.Control | Keys.O;
|
toolStripMenuItem_Open.ShortcutKeys = Keys.Control | Keys.O;
|
||||||
toolStripMenuItem_Open.Size = new Size(270, 34);
|
toolStripMenuItem_Open.Size = new Size(254, 34);
|
||||||
toolStripMenuItem_Open.Text = "打开(&O)...";
|
toolStripMenuItem_Open.Text = "打开(&O)...";
|
||||||
toolStripMenuItem_Open.Click += toolStripMenuItem_Open_Click;
|
toolStripMenuItem_Open.Click += toolStripMenuItem_Open_Click;
|
||||||
//
|
//
|
||||||
// toolStripMenuItem_BatchOpen
|
// toolStripMenuItem_BatchOpen
|
||||||
//
|
//
|
||||||
toolStripMenuItem_BatchOpen.Name = "toolStripMenuItem_BatchOpen";
|
toolStripMenuItem_BatchOpen.Name = "toolStripMenuItem_BatchOpen";
|
||||||
toolStripMenuItem_BatchOpen.Size = new Size(270, 34);
|
toolStripMenuItem_BatchOpen.Size = new Size(254, 34);
|
||||||
toolStripMenuItem_BatchOpen.Text = "批量打开(&B)...";
|
toolStripMenuItem_BatchOpen.Text = "批量打开(&B)...";
|
||||||
toolStripMenuItem_BatchOpen.Click += toolStripMenuItem_BatchOpen_Click;
|
toolStripMenuItem_BatchOpen.Click += toolStripMenuItem_BatchOpen_Click;
|
||||||
//
|
//
|
||||||
// toolStripSeparator1
|
// toolStripSeparator1
|
||||||
//
|
//
|
||||||
toolStripSeparator1.Name = "toolStripSeparator1";
|
toolStripSeparator1.Name = "toolStripSeparator1";
|
||||||
toolStripSeparator1.Size = new Size(267, 6);
|
toolStripSeparator1.Size = new Size(251, 6);
|
||||||
//
|
//
|
||||||
// toolStripMenuItem_Export
|
// toolStripMenuItem_Export
|
||||||
//
|
//
|
||||||
toolStripMenuItem_Export.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_ExportFrame, toolStripMenuItem_ExportFrameSequence, toolStripMenuItem_ExportGif, toolStripMenuItem_ExportMp4, toolStripMenuItem_ExportWebm, toolStripMenuItem_ExportMkv, toolStripMenuItem_ExportMov, toolStripMenuItem_ExportCustom });
|
toolStripMenuItem_Export.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_ExportFrame, toolStripMenuItem_ExportFrameSequence, toolStripMenuItem_ExportGif, toolStripMenuItem_ExportMp4, toolStripMenuItem_ExportWebm, toolStripMenuItem_ExportMkv, toolStripMenuItem_ExportMov, toolStripMenuItem_ExportCustom });
|
||||||
toolStripMenuItem_Export.Name = "toolStripMenuItem_Export";
|
toolStripMenuItem_Export.Name = "toolStripMenuItem_Export";
|
||||||
toolStripMenuItem_Export.Size = new Size(270, 34);
|
toolStripMenuItem_Export.Size = new Size(254, 34);
|
||||||
toolStripMenuItem_Export.Text = "导出(&E)";
|
toolStripMenuItem_Export.Text = "导出(&E)";
|
||||||
//
|
//
|
||||||
// toolStripMenuItem_ExportFrame
|
// toolStripMenuItem_ExportFrame
|
||||||
@@ -159,13 +159,6 @@
|
|||||||
toolStripMenuItem_ExportGif.Text = "GIF...";
|
toolStripMenuItem_ExportGif.Text = "GIF...";
|
||||||
toolStripMenuItem_ExportGif.Click += toolStripMenuItem_Export_Click;
|
toolStripMenuItem_ExportGif.Click += toolStripMenuItem_Export_Click;
|
||||||
//
|
//
|
||||||
// toolStripMenuItem_ExportMkv
|
|
||||||
//
|
|
||||||
toolStripMenuItem_ExportMkv.Name = "toolStripMenuItem_ExportMkv";
|
|
||||||
toolStripMenuItem_ExportMkv.Size = new Size(288, 34);
|
|
||||||
toolStripMenuItem_ExportMkv.Text = "MKV...";
|
|
||||||
toolStripMenuItem_ExportMkv.Click += toolStripMenuItem_Export_Click;
|
|
||||||
//
|
|
||||||
// toolStripMenuItem_ExportMp4
|
// toolStripMenuItem_ExportMp4
|
||||||
//
|
//
|
||||||
toolStripMenuItem_ExportMp4.Name = "toolStripMenuItem_ExportMp4";
|
toolStripMenuItem_ExportMp4.Name = "toolStripMenuItem_ExportMp4";
|
||||||
@@ -173,13 +166,6 @@
|
|||||||
toolStripMenuItem_ExportMp4.Text = "MP4...";
|
toolStripMenuItem_ExportMp4.Text = "MP4...";
|
||||||
toolStripMenuItem_ExportMp4.Click += toolStripMenuItem_Export_Click;
|
toolStripMenuItem_ExportMp4.Click += toolStripMenuItem_Export_Click;
|
||||||
//
|
//
|
||||||
// toolStripMenuItem_ExportMov
|
|
||||||
//
|
|
||||||
toolStripMenuItem_ExportMov.Name = "toolStripMenuItem_ExportMov";
|
|
||||||
toolStripMenuItem_ExportMov.Size = new Size(288, 34);
|
|
||||||
toolStripMenuItem_ExportMov.Text = "MOV...";
|
|
||||||
toolStripMenuItem_ExportMov.Click += toolStripMenuItem_Export_Click;
|
|
||||||
//
|
|
||||||
// toolStripMenuItem_ExportWebm
|
// toolStripMenuItem_ExportWebm
|
||||||
//
|
//
|
||||||
toolStripMenuItem_ExportWebm.Name = "toolStripMenuItem_ExportWebm";
|
toolStripMenuItem_ExportWebm.Name = "toolStripMenuItem_ExportWebm";
|
||||||
@@ -187,6 +173,20 @@
|
|||||||
toolStripMenuItem_ExportWebm.Text = "WebM...";
|
toolStripMenuItem_ExportWebm.Text = "WebM...";
|
||||||
toolStripMenuItem_ExportWebm.Click += toolStripMenuItem_Export_Click;
|
toolStripMenuItem_ExportWebm.Click += toolStripMenuItem_Export_Click;
|
||||||
//
|
//
|
||||||
|
// toolStripMenuItem_ExportMkv
|
||||||
|
//
|
||||||
|
toolStripMenuItem_ExportMkv.Name = "toolStripMenuItem_ExportMkv";
|
||||||
|
toolStripMenuItem_ExportMkv.Size = new Size(288, 34);
|
||||||
|
toolStripMenuItem_ExportMkv.Text = "MKV...";
|
||||||
|
toolStripMenuItem_ExportMkv.Click += toolStripMenuItem_Export_Click;
|
||||||
|
//
|
||||||
|
// toolStripMenuItem_ExportMov
|
||||||
|
//
|
||||||
|
toolStripMenuItem_ExportMov.Name = "toolStripMenuItem_ExportMov";
|
||||||
|
toolStripMenuItem_ExportMov.Size = new Size(288, 34);
|
||||||
|
toolStripMenuItem_ExportMov.Text = "MOV...";
|
||||||
|
toolStripMenuItem_ExportMov.Click += toolStripMenuItem_Export_Click;
|
||||||
|
//
|
||||||
// toolStripMenuItem_ExportCustom
|
// toolStripMenuItem_ExportCustom
|
||||||
//
|
//
|
||||||
toolStripMenuItem_ExportCustom.Name = "toolStripMenuItem_ExportCustom";
|
toolStripMenuItem_ExportCustom.Name = "toolStripMenuItem_ExportCustom";
|
||||||
@@ -197,13 +197,13 @@
|
|||||||
// toolStripSeparator2
|
// toolStripSeparator2
|
||||||
//
|
//
|
||||||
toolStripSeparator2.Name = "toolStripSeparator2";
|
toolStripSeparator2.Name = "toolStripSeparator2";
|
||||||
toolStripSeparator2.Size = new Size(267, 6);
|
toolStripSeparator2.Size = new Size(251, 6);
|
||||||
//
|
//
|
||||||
// toolStripMenuItem_Exit
|
// toolStripMenuItem_Exit
|
||||||
//
|
//
|
||||||
toolStripMenuItem_Exit.Name = "toolStripMenuItem_Exit";
|
toolStripMenuItem_Exit.Name = "toolStripMenuItem_Exit";
|
||||||
toolStripMenuItem_Exit.ShortcutKeys = Keys.Alt | Keys.F4;
|
toolStripMenuItem_Exit.ShortcutKeys = Keys.Alt | Keys.F4;
|
||||||
toolStripMenuItem_Exit.Size = new Size(270, 34);
|
toolStripMenuItem_Exit.Size = new Size(254, 34);
|
||||||
toolStripMenuItem_Exit.Text = "退出(&X)";
|
toolStripMenuItem_Exit.Text = "退出(&X)";
|
||||||
toolStripMenuItem_Exit.Click += toolStripMenuItem_Exit_Click;
|
toolStripMenuItem_Exit.Click += toolStripMenuItem_Exit_Click;
|
||||||
//
|
//
|
||||||
@@ -271,7 +271,7 @@
|
|||||||
rtbLog.Margin = new Padding(3, 2, 3, 2);
|
rtbLog.Margin = new Padding(3, 2, 3, 2);
|
||||||
rtbLog.Name = "rtbLog";
|
rtbLog.Name = "rtbLog";
|
||||||
rtbLog.ReadOnly = true;
|
rtbLog.ReadOnly = true;
|
||||||
rtbLog.Size = new Size(1758, 146);
|
rtbLog.Size = new Size(1758, 120);
|
||||||
rtbLog.TabIndex = 0;
|
rtbLog.TabIndex = 0;
|
||||||
rtbLog.Text = "";
|
rtbLog.Text = "";
|
||||||
rtbLog.WordWrap = false;
|
rtbLog.WordWrap = false;
|
||||||
@@ -295,7 +295,7 @@
|
|||||||
splitContainer_MainForm.Panel2.Controls.Add(rtbLog);
|
splitContainer_MainForm.Panel2.Controls.Add(rtbLog);
|
||||||
splitContainer_MainForm.Panel2.Cursor = Cursors.Default;
|
splitContainer_MainForm.Panel2.Cursor = Cursors.Default;
|
||||||
splitContainer_MainForm.Size = new Size(1758, 1097);
|
splitContainer_MainForm.Size = new Size(1758, 1097);
|
||||||
splitContainer_MainForm.SplitterDistance = 943;
|
splitContainer_MainForm.SplitterDistance = 969;
|
||||||
splitContainer_MainForm.SplitterWidth = 8;
|
splitContainer_MainForm.SplitterWidth = 8;
|
||||||
splitContainer_MainForm.TabIndex = 3;
|
splitContainer_MainForm.TabIndex = 3;
|
||||||
splitContainer_MainForm.TabStop = false;
|
splitContainer_MainForm.TabStop = false;
|
||||||
@@ -319,7 +319,7 @@
|
|||||||
//
|
//
|
||||||
splitContainer_Functional.Panel2.Controls.Add(groupBox_Preview);
|
splitContainer_Functional.Panel2.Controls.Add(groupBox_Preview);
|
||||||
splitContainer_Functional.Panel2.Cursor = Cursors.Default;
|
splitContainer_Functional.Panel2.Cursor = Cursors.Default;
|
||||||
splitContainer_Functional.Size = new Size(1758, 943);
|
splitContainer_Functional.Size = new Size(1758, 969);
|
||||||
splitContainer_Functional.SplitterDistance = 759;
|
splitContainer_Functional.SplitterDistance = 759;
|
||||||
splitContainer_Functional.SplitterWidth = 8;
|
splitContainer_Functional.SplitterWidth = 8;
|
||||||
splitContainer_Functional.TabIndex = 2;
|
splitContainer_Functional.TabIndex = 2;
|
||||||
@@ -343,7 +343,7 @@
|
|||||||
//
|
//
|
||||||
splitContainer_Information.Panel2.Controls.Add(splitContainer_Config);
|
splitContainer_Information.Panel2.Controls.Add(splitContainer_Config);
|
||||||
splitContainer_Information.Panel2.Cursor = Cursors.Default;
|
splitContainer_Information.Panel2.Cursor = Cursors.Default;
|
||||||
splitContainer_Information.Size = new Size(759, 943);
|
splitContainer_Information.Size = new Size(759, 969);
|
||||||
splitContainer_Information.SplitterDistance = 354;
|
splitContainer_Information.SplitterDistance = 354;
|
||||||
splitContainer_Information.SplitterWidth = 8;
|
splitContainer_Information.SplitterWidth = 8;
|
||||||
splitContainer_Information.TabIndex = 1;
|
splitContainer_Information.TabIndex = 1;
|
||||||
@@ -357,7 +357,7 @@
|
|||||||
groupBox_SkelList.Dock = DockStyle.Fill;
|
groupBox_SkelList.Dock = DockStyle.Fill;
|
||||||
groupBox_SkelList.Location = new Point(0, 0);
|
groupBox_SkelList.Location = new Point(0, 0);
|
||||||
groupBox_SkelList.Name = "groupBox_SkelList";
|
groupBox_SkelList.Name = "groupBox_SkelList";
|
||||||
groupBox_SkelList.Size = new Size(354, 943);
|
groupBox_SkelList.Size = new Size(354, 969);
|
||||||
groupBox_SkelList.TabIndex = 0;
|
groupBox_SkelList.TabIndex = 0;
|
||||||
groupBox_SkelList.TabStop = false;
|
groupBox_SkelList.TabStop = false;
|
||||||
groupBox_SkelList.Text = "模型列表";
|
groupBox_SkelList.Text = "模型列表";
|
||||||
@@ -368,7 +368,7 @@
|
|||||||
spineListView.Location = new Point(3, 26);
|
spineListView.Location = new Point(3, 26);
|
||||||
spineListView.Name = "spineListView";
|
spineListView.Name = "spineListView";
|
||||||
spineListView.PropertyGrid = propertyGrid_Spine;
|
spineListView.PropertyGrid = propertyGrid_Spine;
|
||||||
spineListView.Size = new Size(348, 914);
|
spineListView.Size = new Size(348, 940);
|
||||||
spineListView.TabIndex = 0;
|
spineListView.TabIndex = 0;
|
||||||
//
|
//
|
||||||
// propertyGrid_Spine
|
// propertyGrid_Spine
|
||||||
@@ -377,7 +377,7 @@
|
|||||||
propertyGrid_Spine.HelpVisible = false;
|
propertyGrid_Spine.HelpVisible = false;
|
||||||
propertyGrid_Spine.Location = new Point(3, 26);
|
propertyGrid_Spine.Location = new Point(3, 26);
|
||||||
propertyGrid_Spine.Name = "propertyGrid_Spine";
|
propertyGrid_Spine.Name = "propertyGrid_Spine";
|
||||||
propertyGrid_Spine.Size = new Size(391, 580);
|
propertyGrid_Spine.Size = new Size(391, 606);
|
||||||
propertyGrid_Spine.TabIndex = 0;
|
propertyGrid_Spine.TabIndex = 0;
|
||||||
propertyGrid_Spine.ToolbarVisible = false;
|
propertyGrid_Spine.ToolbarVisible = false;
|
||||||
propertyGrid_Spine.PropertyValueChanged += propertyGrid_PropertyValueChanged;
|
propertyGrid_Spine.PropertyValueChanged += propertyGrid_PropertyValueChanged;
|
||||||
@@ -400,7 +400,7 @@
|
|||||||
//
|
//
|
||||||
splitContainer_Config.Panel2.Controls.Add(groupBox_SkelConfig);
|
splitContainer_Config.Panel2.Controls.Add(groupBox_SkelConfig);
|
||||||
splitContainer_Config.Panel2.Cursor = Cursors.Default;
|
splitContainer_Config.Panel2.Cursor = Cursors.Default;
|
||||||
splitContainer_Config.Size = new Size(397, 943);
|
splitContainer_Config.Size = new Size(397, 969);
|
||||||
splitContainer_Config.SplitterDistance = 326;
|
splitContainer_Config.SplitterDistance = 326;
|
||||||
splitContainer_Config.SplitterWidth = 8;
|
splitContainer_Config.SplitterWidth = 8;
|
||||||
splitContainer_Config.TabIndex = 0;
|
splitContainer_Config.TabIndex = 0;
|
||||||
@@ -436,7 +436,7 @@
|
|||||||
groupBox_SkelConfig.Dock = DockStyle.Fill;
|
groupBox_SkelConfig.Dock = DockStyle.Fill;
|
||||||
groupBox_SkelConfig.Location = new Point(0, 0);
|
groupBox_SkelConfig.Location = new Point(0, 0);
|
||||||
groupBox_SkelConfig.Name = "groupBox_SkelConfig";
|
groupBox_SkelConfig.Name = "groupBox_SkelConfig";
|
||||||
groupBox_SkelConfig.Size = new Size(397, 609);
|
groupBox_SkelConfig.Size = new Size(397, 635);
|
||||||
groupBox_SkelConfig.TabIndex = 0;
|
groupBox_SkelConfig.TabIndex = 0;
|
||||||
groupBox_SkelConfig.TabStop = false;
|
groupBox_SkelConfig.TabStop = false;
|
||||||
groupBox_SkelConfig.Text = "模型参数";
|
groupBox_SkelConfig.Text = "模型参数";
|
||||||
@@ -447,7 +447,7 @@
|
|||||||
groupBox_Preview.Dock = DockStyle.Fill;
|
groupBox_Preview.Dock = DockStyle.Fill;
|
||||||
groupBox_Preview.Location = new Point(0, 0);
|
groupBox_Preview.Location = new Point(0, 0);
|
||||||
groupBox_Preview.Name = "groupBox_Preview";
|
groupBox_Preview.Name = "groupBox_Preview";
|
||||||
groupBox_Preview.Size = new Size(991, 943);
|
groupBox_Preview.Size = new Size(991, 969);
|
||||||
groupBox_Preview.TabIndex = 1;
|
groupBox_Preview.TabIndex = 1;
|
||||||
groupBox_Preview.TabStop = false;
|
groupBox_Preview.TabStop = false;
|
||||||
groupBox_Preview.Text = "预览画面";
|
groupBox_Preview.Text = "预览画面";
|
||||||
@@ -458,7 +458,7 @@
|
|||||||
spinePreviewer.Location = new Point(3, 26);
|
spinePreviewer.Location = new Point(3, 26);
|
||||||
spinePreviewer.Name = "spinePreviewer";
|
spinePreviewer.Name = "spinePreviewer";
|
||||||
spinePreviewer.PropertyGrid = propertyGrid_Previewer;
|
spinePreviewer.PropertyGrid = propertyGrid_Previewer;
|
||||||
spinePreviewer.Size = new Size(985, 914);
|
spinePreviewer.Size = new Size(985, 940);
|
||||||
spinePreviewer.SpineListView = spineListView;
|
spinePreviewer.SpineListView = spineListView;
|
||||||
spinePreviewer.TabIndex = 0;
|
spinePreviewer.TabIndex = 0;
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
using FFMpegCore.Pipes;
|
using NLog;
|
||||||
using FFMpegCore;
|
|
||||||
using NLog;
|
|
||||||
using SFML.System;
|
|
||||||
using SpineViewer.Spine;
|
using SpineViewer.Spine;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Nodes;
|
|
||||||
using FFMpegCore.Enums;
|
|
||||||
using SpineViewer.Exporter;
|
using SpineViewer.Exporter;
|
||||||
|
|
||||||
namespace SpineViewer
|
namespace SpineViewer
|
||||||
@@ -34,7 +28,7 @@ namespace SpineViewer
|
|||||||
// 执行一些初始化工作
|
// 执行一些初始化工作
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Spine.Shader.Init();
|
Shader.Init();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -133,9 +127,47 @@ namespace SpineViewer
|
|||||||
progressDialog.ShowDialog();
|
progressDialog.ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//private System.Windows.Forms.Timer timer = new();
|
||||||
|
//private PetForm pet = new PetForm();
|
||||||
|
//private IntPtr screenDC;
|
||||||
|
//private IntPtr memDC;
|
||||||
private void toolStripMenuItem_ManageResource_Click(object sender, EventArgs e)
|
private void toolStripMenuItem_ManageResource_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
// screenDC = Win32.GetDC(IntPtr.Zero);
|
||||||
|
// memDC = Win32.CreateCompatibleDC(screenDC);
|
||||||
|
// pet.Show();
|
||||||
|
// timer.Tick += Timer_Tick;
|
||||||
|
// timer.Enabled = true;
|
||||||
|
// timer.Interval = 50;
|
||||||
|
// timer.Start();
|
||||||
|
//}
|
||||||
|
|
||||||
|
//private void Timer_Tick(object? sender, EventArgs e)
|
||||||
|
//{
|
||||||
|
// using var tex = new SFML.Graphics.RenderTexture((uint)pet.Width, (uint)pet.Height);
|
||||||
|
// var v = spinePreviewer.GetView();
|
||||||
|
// tex.SetView(v);
|
||||||
|
// tex.Clear(new SFML.Graphics.Color(0, 0, 0, 0));
|
||||||
|
// lock (spineListView.Spines)
|
||||||
|
// {
|
||||||
|
// foreach (var sp in spineListView.Spines)
|
||||||
|
// tex.Draw(sp);
|
||||||
|
// }
|
||||||
|
// tex.Display();
|
||||||
|
// using var frame = new SFMLImageVideoFrame(tex.Texture.CopyToImage());
|
||||||
|
// using var bitmap = frame.CopyToBitmap();
|
||||||
|
|
||||||
|
// var newBitmap = bitmap.GetHbitmap(Color.FromArgb(0));
|
||||||
|
// var oldBitmap = Win32.SelectObject(memDC, newBitmap);
|
||||||
|
|
||||||
|
// Win32.SIZE size = new Win32.SIZE { cx = pet.Width, cy = pet.Height };
|
||||||
|
// Win32.POINT srcPos = new Win32.POINT { x = 0, y = 0 };
|
||||||
|
// Win32.BLENDFUNCTION blend = new Win32.BLENDFUNCTION { BlendOp = 0, BlendFlags = 0, SourceConstantAlpha = 255, AlphaFormat = Win32.AC_SRC_ALPHA };
|
||||||
|
|
||||||
|
// Win32.UpdateLayeredWindow(pet.Handle, screenDC, IntPtr.Zero, ref size, memDC, ref srcPos, 0, ref blend, Win32.ULW_ALPHA);
|
||||||
|
|
||||||
|
// Win32.SelectObject(memDC, oldBitmap);
|
||||||
|
// Win32.DeleteObject(newBitmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toolStripMenuItem_About_Click(object sender, EventArgs e)
|
private void toolStripMenuItem_About_Click(object sender, EventArgs e)
|
||||||
|
|||||||
50
SpineViewer/PetForm.Designer.cs
generated
Normal file
50
SpineViewer/PetForm.Designer.cs
generated
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
namespace SpineViewer
|
||||||
|
{
|
||||||
|
partial class PetForm
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Required designer variable.
|
||||||
|
/// </summary>
|
||||||
|
private System.ComponentModel.IContainer components = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clean up any resources being used.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing && (components != null))
|
||||||
|
{
|
||||||
|
components.Dispose();
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Windows Form Designer generated code
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Required method for Designer support - do not modify
|
||||||
|
/// the contents of this method with the code editor.
|
||||||
|
/// </summary>
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
SuspendLayout();
|
||||||
|
//
|
||||||
|
// PetForm
|
||||||
|
//
|
||||||
|
AutoScaleMode = AutoScaleMode.None;
|
||||||
|
ClientSize = new Size(490, 456);
|
||||||
|
ControlBox = false;
|
||||||
|
MaximizeBox = false;
|
||||||
|
MinimizeBox = false;
|
||||||
|
Name = "PetForm";
|
||||||
|
ShowIcon = false;
|
||||||
|
ShowInTaskbar = false;
|
||||||
|
StartPosition = FormStartPosition.Manual;
|
||||||
|
Text = "PetForm";
|
||||||
|
ResumeLayout(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
48
SpineViewer/PetForm.cs
Normal file
48
SpineViewer/PetForm.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Data;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace SpineViewer
|
||||||
|
{
|
||||||
|
public partial class PetForm: Form
|
||||||
|
{
|
||||||
|
public PetForm()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override CreateParams CreateParams
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
//var style = Win32.GetWindowLong(hWnd, Win32.GWL_STYLE) | Win32.WS_POPUP;
|
||||||
|
//var exStyle = Win32.GetWindowLong(hWnd, Win32.GWL_EXSTYLE) | Win32.WS_EX_LAYERED | Win32.WS_EX_TOOLWINDOW | Win32.WS_EX_TOPMOST;
|
||||||
|
//Win32.SetWindowLong(hWnd, Win32.GWL_STYLE, style);
|
||||||
|
//Win32.SetWindowLong(hWnd, Win32.GWL_EXSTYLE, exStyle);
|
||||||
|
//Win32.SetLayeredWindowAttributes(hWnd, crKey, 255, Win32.LWA_COLORKEY | Win32.LWA_ALPHA);
|
||||||
|
//Win32.SetWindowPos(hWnd, Win32.HWND_TOPMOST, 0, 0, 0, 0, Win32.SWP_NOMOVE | Win32.SWP_NOSIZE);
|
||||||
|
var cp = base.CreateParams;
|
||||||
|
cp.ExStyle = Win32.WS_EX_LAYERED | Win32.WS_EX_TOPMOST;
|
||||||
|
cp.Style = Win32.WS_POPUP;
|
||||||
|
//cp.ExStyle |= Win32.WS_EX_LAYERED | Win32.WS_EX_TOOLWINDOW | Win32.WS_EX_TOPMOST;
|
||||||
|
return cp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPaint(PaintEventArgs e)
|
||||||
|
{
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPaintBackground(PaintEventArgs e)
|
||||||
|
{
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
120
SpineViewer/PetForm.resx
Normal file
120
SpineViewer/PetForm.resx
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
</root>
|
||||||
73
SpineViewer/SFMLExtension.cs
Normal file
73
SpineViewer/SFMLExtension.cs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SpineViewer
|
||||||
|
{
|
||||||
|
public static class SFMLExtension
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取 Winforms Bitmap 对象, 需要使用 Dispose 释放对象
|
||||||
|
/// </summary>
|
||||||
|
public static Bitmap CopyToBitmap(this SFML.Graphics.Image image)
|
||||||
|
{
|
||||||
|
image.SaveToMemory(out var imgBuffer, "bmp");
|
||||||
|
using var stream = new MemoryStream(imgBuffer);
|
||||||
|
using var bitmap = new Bitmap(stream);
|
||||||
|
return new(bitmap); // 必须重复构造一个副本才能摆脱对流的依赖, 否则之后使用会报错
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取 Winforms Bitmap 对象, 需要使用 Dispose 释放对象
|
||||||
|
/// </summary>
|
||||||
|
public static Bitmap CopyToBitmap(this SFML.Graphics.Texture texture)
|
||||||
|
{
|
||||||
|
using var image = texture.CopyToImage();
|
||||||
|
return CopyToBitmap(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取某个包围盒下合适的视图
|
||||||
|
/// </summary>
|
||||||
|
public static SFML.Graphics.View GetView(this RectangleF bounds, Size resolution, Padding padding)
|
||||||
|
=> bounds.GetView((uint)resolution.Width, (uint)resolution.Height, (uint)padding.Left, (uint)padding.Right, (uint)padding.Top, (uint)padding.Bottom);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取某个包围盒下合适的视图
|
||||||
|
/// </summary>
|
||||||
|
public static SFML.Graphics.View GetView(this RectangleF bounds, uint width, uint height, Padding padding)
|
||||||
|
=> bounds.GetView(width, height, (uint)padding.Left, (uint)padding.Right, (uint)padding.Top, (uint)padding.Bottom);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取某个包围盒下合适的视图
|
||||||
|
/// </summary>
|
||||||
|
public static SFML.Graphics.View GetView(this RectangleF bounds, Size resolution, uint paddingL = 1, uint paddingR = 1, uint paddingT = 1, uint paddingB = 1)
|
||||||
|
=> bounds.GetView((uint)resolution.Width, (uint)resolution.Height, paddingL, paddingR, paddingT, paddingB);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取某个包围盒下合适的视图
|
||||||
|
/// </summary>
|
||||||
|
public static SFML.Graphics.View GetView(this RectangleF bounds, uint width, uint height, uint paddingL = 1, uint paddingR = 1, uint paddingT = 1, uint paddingB = 1)
|
||||||
|
{
|
||||||
|
float sizeX = bounds.Width;
|
||||||
|
float sizeY = bounds.Height;
|
||||||
|
float innerW = width - paddingL - paddingR;
|
||||||
|
float innerH = height - paddingT - paddingB;
|
||||||
|
|
||||||
|
float scale = 1;
|
||||||
|
if (sizeY / sizeX < innerH / innerW)
|
||||||
|
scale = sizeX / innerW; // 相同的 X, 视窗 Y 更大
|
||||||
|
else
|
||||||
|
scale = sizeY / innerH; // 相同的 Y, 视窗 X 更大
|
||||||
|
|
||||||
|
var x = bounds.X + bounds.Width / 2 + (paddingL - (float)paddingR) * scale;
|
||||||
|
var y = bounds.Y + bounds.Height / 2 + (paddingT - (float)paddingB) * scale;
|
||||||
|
var viewX = width * scale;
|
||||||
|
var viewY = height * scale;
|
||||||
|
|
||||||
|
return new(new(x, y), new(viewX, -viewY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
80
SpineViewer/Shader.cs
Normal file
80
SpineViewer/Shader.cs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SpineViewer
|
||||||
|
{
|
||||||
|
public static class Shader
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 用于非预乘纹理的 fragment shader, 乘上了插值后的透明度用于实现透明度变化(插值预乘), 并且输出预乘后的像素值
|
||||||
|
/// </summary>
|
||||||
|
private const string FRAGMENT_VertexAlpha =
|
||||||
|
"uniform sampler2D t;" +
|
||||||
|
"void main() { vec4 p = texture(t, gl_TexCoord[0].xy);" +
|
||||||
|
"p.rgb *= p.a * gl_Color.a;" +
|
||||||
|
"gl_FragColor = gl_Color * p; }"
|
||||||
|
;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用于预乘纹理的 fragment shader, 乘上了插值后的透明度用于实现透明度变化(插值预乘)
|
||||||
|
/// </summary>
|
||||||
|
private const string FRAGMENT_VertexAlphaPma =
|
||||||
|
"uniform sampler2D t;" +
|
||||||
|
"void main() { vec4 p = texture(t, gl_TexCoord[0].xy);" +
|
||||||
|
"p.rgb *= gl_Color.a;" +
|
||||||
|
"gl_FragColor = gl_Color * p; }"
|
||||||
|
;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 预乘转非预乘 fragment shader
|
||||||
|
/// </summary>
|
||||||
|
private const string FRAGMENT_InvPma =
|
||||||
|
"uniform sampler2D t;" +
|
||||||
|
"void main() { vec4 p = texture(t, gl_TexCoord[0].xy);" +
|
||||||
|
"if (p.a > 0) p.rgb /= max(max(max(p.r, p.g), p.b), p.a);" +
|
||||||
|
"gl_FragColor = p; }"
|
||||||
|
;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 考虑了顶点透明度变化的着色器, 输入是非预乘纹理像素, 输出是预乘像素
|
||||||
|
/// </summary>
|
||||||
|
private static SFML.Graphics.Shader? VertexAlpha = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 考虑了顶点透明度变化的着色器, 输入和输出均是预乘像素值
|
||||||
|
/// </summary>
|
||||||
|
private static SFML.Graphics.Shader? VertexAlphaPma = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 反预乘着色器, 用于得到正确透明度的非预乘画面
|
||||||
|
/// </summary>
|
||||||
|
public static SFML.Graphics.Shader? InversePma { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 加载 Shader, 可能会存在异常导致着色器加载失败
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="SFML.LoadingFailedException"></exception>
|
||||||
|
public static void Init()
|
||||||
|
{
|
||||||
|
VertexAlpha = SFML.Graphics.Shader.FromString(null, null, FRAGMENT_VertexAlpha);
|
||||||
|
VertexAlphaPma = SFML.Graphics.Shader.FromString(null, null, FRAGMENT_VertexAlphaPma);
|
||||||
|
InversePma = SFML.Graphics.Shader.FromString(null, null, FRAGMENT_InvPma);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取绘制 Spine 的着色器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pma">纹理是否是预乘的</param>
|
||||||
|
/// <param name="twoColor">是否是双色着色的(TODO)</param>
|
||||||
|
public static SFML.Graphics.Shader? GetSpineShader(bool pma, bool twoColor = false)
|
||||||
|
{
|
||||||
|
if (pma)
|
||||||
|
return VertexAlphaPma;
|
||||||
|
else
|
||||||
|
return VertexAlpha;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
143
SpineViewer/Spine/AnimationTracks.cs
Normal file
143
SpineViewer/Spine/AnimationTracks.cs
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SpineViewer.Spine
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 对轨道索引的包装类, 能够在面板上显示例如时长的属性, 但是处理该属性时按字符串去处理, 例如 ToString 和判断对象相等都是用动画名称实现逻辑
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="spine"></param>
|
||||||
|
/// <param name="i"></param>
|
||||||
|
[TypeConverter(typeof(TrackWrapperConverter))]
|
||||||
|
public class TrackWrapper(Spine spine, int i)
|
||||||
|
{
|
||||||
|
private readonly Spine spine = spine;
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
public int Index { get; } = i;
|
||||||
|
|
||||||
|
[DisplayName("时长")]
|
||||||
|
public float Duration => spine.GetAnimationDuration(spine.GetAnimation(Index));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 实现了默认的转为字符串的方式
|
||||||
|
/// </summary>
|
||||||
|
public override string ToString() => spine.GetAnimation(Index);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 影响了属性面板的判断, 当动画名称相同的时候认为两个对象是相同的, 这样属性面板可以在多选的时候正确显示相同取值的内容
|
||||||
|
/// </summary>
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (obj is TrackWrapper) return ToString() == obj.ToString();
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 哈希码需要和 Equals 行为类似
|
||||||
|
/// </summary>
|
||||||
|
public override int GetHashCode() => (typeof(TrackWrapper).FullName + ToString()).GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 轨道属性描述符, 实现对属性的读取和赋值
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="i">轨道索引</param>
|
||||||
|
public class TrackWrapperPropertyDescriptor(int i) : PropertyDescriptor($"Track{i}", [new DisplayNameAttribute($"轨道 {i}")])
|
||||||
|
{
|
||||||
|
private readonly int idx = i;
|
||||||
|
|
||||||
|
public override Type ComponentType => typeof(AnimationTracks);
|
||||||
|
public override bool IsReadOnly => false;
|
||||||
|
public override Type PropertyType => typeof(TrackWrapper);
|
||||||
|
public override bool CanResetValue(object component) => false;
|
||||||
|
public override void ResetValue(object component) { }
|
||||||
|
public override bool ShouldSerializeValue(object component) => false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 得到一个轨道包装类, 允许用户查看或者修改具体的属性值, 这个地方决定了在面板上看到的是一个对象及其属性
|
||||||
|
/// </summary>
|
||||||
|
public override object? GetValue(object? component)
|
||||||
|
{
|
||||||
|
if (component is AnimationTracks tracks)
|
||||||
|
return tracks.GetTrackWrapper(idx);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 允许通过字符串赋值修改该轨道的动画, 这里决定了当其他地方的调用 (比如 Converter) 通过 value 来设置属性值的时候应该怎么处理
|
||||||
|
/// </summary>
|
||||||
|
public override void SetValue(object? component, object? value)
|
||||||
|
{
|
||||||
|
if (component is AnimationTracks tracks)
|
||||||
|
{
|
||||||
|
if (value is string s)
|
||||||
|
tracks.Spine.SetAnimation(idx, s); // tracks.SetTrackWrapper(idx, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AnimationTracks 动态类型包装类, 用于提供对 Spine 对象多轨道动画的访问能力, 不同轨道将动态生成属性
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="spine">关联的 Spine 对象</param>
|
||||||
|
public class AnimationTracks(Spine spine) : ICustomTypeDescriptor
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<int, TrackWrapperPropertyDescriptor> pdCache = [];
|
||||||
|
|
||||||
|
public Spine Spine { get; } = spine;
|
||||||
|
private readonly Dictionary<int, TrackWrapper> trackWrapperProperties = [];
|
||||||
|
|
||||||
|
// XXX: 必须实现 ICustomTypeDescriptor 接口, 不能继承 CustomTypeDescriptor, 似乎继承下来的东西会有问题, 导致某些调用不正确
|
||||||
|
|
||||||
|
public AttributeCollection GetAttributes() => TypeDescriptor.GetAttributes(this, true);
|
||||||
|
public string? GetClassName() => TypeDescriptor.GetClassName(this, true);
|
||||||
|
public string? GetComponentName() => TypeDescriptor.GetComponentName(this, true);
|
||||||
|
public TypeConverter? GetConverter() => TypeDescriptor.GetConverter(this, true);
|
||||||
|
public EventDescriptor? GetDefaultEvent() => TypeDescriptor.GetDefaultEvent(this, true);
|
||||||
|
public PropertyDescriptor? GetDefaultProperty() => TypeDescriptor.GetDefaultProperty(this, true);
|
||||||
|
public object? GetEditor(Type editorBaseType) => TypeDescriptor.GetEditor(this, editorBaseType, true);
|
||||||
|
public EventDescriptorCollection GetEvents() => TypeDescriptor.GetEvents(this, true);
|
||||||
|
public EventDescriptorCollection GetEvents(Attribute[]? attributes) => TypeDescriptor.GetEvents(this, attributes, true);
|
||||||
|
public object? GetPropertyOwner(PropertyDescriptor? pd) => this;
|
||||||
|
public PropertyDescriptorCollection GetProperties() => GetProperties(null);
|
||||||
|
public PropertyDescriptorCollection GetProperties(Attribute[]? attributes)
|
||||||
|
{
|
||||||
|
var props = new List<TrackWrapperPropertyDescriptor>();
|
||||||
|
foreach (var i in Spine.GetTrackIndices())
|
||||||
|
{
|
||||||
|
if (!pdCache.ContainsKey(i))
|
||||||
|
pdCache[i] = new TrackWrapperPropertyDescriptor(i);
|
||||||
|
props.Add(pdCache[i]);
|
||||||
|
}
|
||||||
|
return new PropertyDescriptorCollection(props.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 访问 TrackWrapper 属性 <c>AnimationTracks.Track{i}</c>
|
||||||
|
/// </summary>
|
||||||
|
public TrackWrapper GetTrackWrapper(int i)
|
||||||
|
{
|
||||||
|
if (!trackWrapperProperties.ContainsKey(i))
|
||||||
|
trackWrapperProperties[i] = new TrackWrapper(Spine, i);
|
||||||
|
return trackWrapperProperties[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 在属性面板悬停可以按轨道顺序显示动画名称
|
||||||
|
/// </summary>
|
||||||
|
public override string ToString() => $"[{string.Join(", ", Spine.GetTrackIndices().Select(Spine.GetAnimation))}]";
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (obj is AnimationTracks tracks) return ToString() == tracks.ToString();
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode() => (typeof(AnimationTracks).FullName + ToString()).GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,36 +7,86 @@ using System.Threading.Tasks;
|
|||||||
namespace SpineViewer.Spine
|
namespace SpineViewer.Spine
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SFML 混合模式
|
/// SFML 混合模式, 预乘模式下输入和输出的像素值都是预乘的
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class BlendModeSFML
|
public static class BlendModeSFML
|
||||||
{
|
{
|
||||||
|
///// <summary>
|
||||||
|
///// Normal Blend, 无预乘, 仅在 dst.a 是 1 时得到正确结果, 其余情况是有偏结果
|
||||||
|
///// <para>当 <c>src.c < dst.c</c> 时, 结果偏大, 例如 src 是半透明纯黑, dst 是全透明纯白</para>
|
||||||
|
///// <para>当 <c>src.c > dst.c</c> 时, 结果偏小, 例如 src 是半透明纯白, dst 是全透明纯黑</para>
|
||||||
|
///// <code>
|
||||||
|
///// res.c = src.c * src.a + dst.c * (1 - src.a)
|
||||||
|
///// res.a = src.a * 1 + dst.a * (1 - src.a)
|
||||||
|
///// </code>
|
||||||
|
///// </summary>
|
||||||
|
//public static readonly SFML.Graphics.BlendMode Normal = new(
|
||||||
|
// SFML.Graphics.BlendMode.Factor.SrcAlpha,
|
||||||
|
// SFML.Graphics.BlendMode.Factor.OneMinusSrcAlpha,
|
||||||
|
// SFML.Graphics.BlendMode.Equation.Add,
|
||||||
|
// SFML.Graphics.BlendMode.Factor.One,
|
||||||
|
// SFML.Graphics.BlendMode.Factor.OneMinusSrcAlpha,
|
||||||
|
// SFML.Graphics.BlendMode.Equation.Add
|
||||||
|
//);
|
||||||
|
|
||||||
|
///// <summary>
|
||||||
|
///// Additive Blend, 无预乘, 仅在 dst.a 是 1 时得到正确结果, 其余情况是有偏结果
|
||||||
|
///// <para>当 <c>src.a + dst.a >= 1</c> 时, 结果偏大, 例如 src 是不透明纯黑, dst 是全透明纯白</para>
|
||||||
|
///// <para>当 <c>src.a + dst.a < 1</c> 时, 结果偏差方式类似 <see cref="Normal"/>, 均可假设 dst 是全透明纯白进行判断</para>
|
||||||
|
///// <code>
|
||||||
|
///// res.c = src.c * src.a + dst.c * 1
|
||||||
|
///// res.a = src.a * 1 + dst.a * 1
|
||||||
|
///// </code>
|
||||||
|
///// </summary>
|
||||||
|
//public static readonly SFML.Graphics.BlendMode Additive = new(
|
||||||
|
// SFML.Graphics.BlendMode.Factor.SrcAlpha,
|
||||||
|
// SFML.Graphics.BlendMode.Factor.One,
|
||||||
|
// SFML.Graphics.BlendMode.Equation.Add,
|
||||||
|
// SFML.Graphics.BlendMode.Factor.One,
|
||||||
|
// SFML.Graphics.BlendMode.Factor.One,
|
||||||
|
// SFML.Graphics.BlendMode.Equation.Add
|
||||||
|
//);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Alpha Blend
|
/// Normal Blend with PremultipliedAlpha
|
||||||
/// <code>
|
/// <code>
|
||||||
/// res.c = src.c * src.a + dst.c * (1 - src.a)
|
/// [res.c * res.a] = [src.c * src.a] * 1 + [dst.c * dst.a] * (1 - src.a)
|
||||||
/// res.a = src.a * 1 + dst.a * (1 - src.a)
|
/// res.a = src.a * 1 + dst.a * (1 - src.a)
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static SFML.Graphics.BlendMode Normal = SFML.Graphics.BlendMode.Alpha;
|
public static readonly SFML.Graphics.BlendMode NormalPma = new(
|
||||||
|
SFML.Graphics.BlendMode.Factor.One,
|
||||||
|
SFML.Graphics.BlendMode.Factor.OneMinusSrcAlpha,
|
||||||
|
SFML.Graphics.BlendMode.Equation.Add,
|
||||||
|
SFML.Graphics.BlendMode.Factor.One,
|
||||||
|
SFML.Graphics.BlendMode.Factor.OneMinusSrcAlpha,
|
||||||
|
SFML.Graphics.BlendMode.Equation.Add
|
||||||
|
);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Additive Blend
|
/// Additive Blend with PremultipliedAlpha
|
||||||
/// <code>
|
/// <code>
|
||||||
/// res.c = src.c * src.a + dst.c * 1
|
/// [res.c * res.a] = [src.c * src.a] * 1 + [dst.c * dst.a] * 1
|
||||||
/// res.a = src.a * 1 + dst.a * 1
|
/// res.a = src.a * 1 + dst.a * 1
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static SFML.Graphics.BlendMode Additive = SFML.Graphics.BlendMode.Add;
|
public static readonly SFML.Graphics.BlendMode AdditivePma = new(
|
||||||
|
SFML.Graphics.BlendMode.Factor.One,
|
||||||
|
SFML.Graphics.BlendMode.Factor.One,
|
||||||
|
SFML.Graphics.BlendMode.Equation.Add,
|
||||||
|
SFML.Graphics.BlendMode.Factor.One,
|
||||||
|
SFML.Graphics.BlendMode.Factor.One,
|
||||||
|
SFML.Graphics.BlendMode.Equation.Add
|
||||||
|
);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Multiply Blend (PremultipliedAlpha Only)
|
/// Multiply Blend with PremultipliedAlpha
|
||||||
/// <code>
|
/// <code>
|
||||||
/// res.c = src.c * dst.c + dst.c * (1 - src.a)
|
/// res.c = src.c * dst.c + dst.c * (1 - src.a)
|
||||||
/// res.a = src.a * 1 + dst.a * (1 - src.a)
|
/// res.a = src.a * 1 + dst.a * (1 - src.a)
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static SFML.Graphics.BlendMode Multiply = new(
|
public static readonly SFML.Graphics.BlendMode MultiplyPma = new(
|
||||||
SFML.Graphics.BlendMode.Factor.DstColor,
|
SFML.Graphics.BlendMode.Factor.DstColor,
|
||||||
SFML.Graphics.BlendMode.Factor.OneMinusSrcAlpha,
|
SFML.Graphics.BlendMode.Factor.OneMinusSrcAlpha,
|
||||||
SFML.Graphics.BlendMode.Equation.Add,
|
SFML.Graphics.BlendMode.Equation.Add,
|
||||||
@@ -46,13 +96,13 @@ namespace SpineViewer.Spine
|
|||||||
);
|
);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Screen Blend (PremultipliedAlpha Only)
|
/// Screen Blend with PremultipliedAlpha Only
|
||||||
/// <code>
|
/// <code>
|
||||||
/// res.c = src.c * 1 + dst.c * (1 - src.c) = 1 - [(1 - src.c)(1 - dst.c)]
|
/// res.c = src.c * 1 + dst.c * (1 - src.c) = 1 - [(1 - src.c)(1 - dst.c)]
|
||||||
/// res.a = src.a * 1 + dst.a * (1 - src.a)
|
/// res.a = src.a * 1 + dst.a * (1 - src.a)
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static SFML.Graphics.BlendMode Screen = new(
|
public static readonly SFML.Graphics.BlendMode ScreenPma = new(
|
||||||
SFML.Graphics.BlendMode.Factor.One,
|
SFML.Graphics.BlendMode.Factor.One,
|
||||||
SFML.Graphics.BlendMode.Factor.OneMinusSrcColor,
|
SFML.Graphics.BlendMode.Factor.OneMinusSrcColor,
|
||||||
SFML.Graphics.BlendMode.Equation.Add,
|
SFML.Graphics.BlendMode.Equation.Add,
|
||||||
|
|||||||
@@ -109,18 +109,16 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
var pos = position;
|
var pos = position;
|
||||||
var fX = flipX;
|
var fX = flipX;
|
||||||
var fY = flipY;
|
var fY = flipY;
|
||||||
var animation = track0Animation; // TODO: 适配多轨道
|
var animations = animationState.Tracks.Where(te => te is not null).Select(te => te.Animation.Name).ToArray();
|
||||||
var sk = skin;
|
|
||||||
|
|
||||||
var val = Math.Max(value, SCALE_MIN);
|
|
||||||
if (skeletonBinary is not null)
|
if (skeletonBinary is not null)
|
||||||
{
|
{
|
||||||
skeletonBinary.Scale = val;
|
skeletonBinary.Scale = value;
|
||||||
skeletonData = skeletonBinary.ReadSkeletonData(SkelPath);
|
skeletonData = skeletonBinary.ReadSkeletonData(SkelPath);
|
||||||
}
|
}
|
||||||
else if (skeletonJson is not null)
|
else if (skeletonJson is not null)
|
||||||
{
|
{
|
||||||
skeletonJson.Scale = val;
|
skeletonJson.Scale = value;
|
||||||
skeletonData = skeletonJson.ReadSkeletonData(SkelPath);
|
skeletonData = skeletonJson.ReadSkeletonData(SkelPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,8 +131,8 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
position = pos;
|
position = pos;
|
||||||
flipX = fX;
|
flipX = fX;
|
||||||
flipY = fY;
|
flipY = fY;
|
||||||
track0Animation = animation; // TODO: 适配多轨道
|
foreach (var s in loadedSkins) addSkin(s);
|
||||||
skin = sk;
|
for (int i = 0; i < animations.Length; i++) setAnimation(i, animations[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,29 +158,35 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
set => skeleton.FlipY = value;
|
set => skeleton.FlipY = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string skin
|
protected override void addSkin(string name)
|
||||||
{
|
{
|
||||||
get => skeleton.Skin?.Name ?? "default";
|
if (!skinNames.Contains(name)) return;
|
||||||
set
|
skeleton.SetSkin(name); // XXX: 3.7 及以下不支持 AddSkin
|
||||||
{
|
|
||||||
if (!skinNames.Contains(value)) return;
|
|
||||||
skeleton.SetSkin(value);
|
|
||||||
skeleton.SetSlotsToSetupPose();
|
skeleton.SetSlotsToSetupPose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void clearSkin()
|
||||||
|
{
|
||||||
|
skeleton.SetSkin(skeletonData.DefaultSkin);
|
||||||
|
skeleton.SetSlotsToSetupPose();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string track0Animation
|
protected override int[] getTrackIndices() => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks[i] is not null).ToArray();
|
||||||
|
|
||||||
|
protected override string getAnimation(int track) => animationState.GetCurrent(track)?.Animation.Name ?? EMPTY_ANIMATION;
|
||||||
|
|
||||||
|
protected override void setAnimation(int track, string name)
|
||||||
{
|
{
|
||||||
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
|
if (name == EMPTY_ANIMATION)
|
||||||
set
|
animationState.SetAnimation(track, EmptyAnimation, false);
|
||||||
{
|
else if (animationNames.Contains(name))
|
||||||
if (value == EMPTY_ANIMATION)
|
animationState.SetAnimation(track, name, true);
|
||||||
animationState.SetAnimation(0, EmptyAnimation, false);
|
|
||||||
else if (animationNames.Contains(value))
|
|
||||||
animationState.SetAnimation(0, value, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void clearTrack(int i) => animationState.ClearTrack(i);
|
||||||
|
|
||||||
|
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
|
||||||
|
|
||||||
protected override RectangleF bounds
|
protected override RectangleF bounds
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -233,8 +237,6 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
|
|
||||||
|
|
||||||
protected override void update(float delta)
|
protected override void update(float delta)
|
||||||
{
|
{
|
||||||
animationState.Update(delta);
|
animationState.Update(delta);
|
||||||
@@ -259,6 +261,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
vertexArray.Clear();
|
vertexArray.Clear();
|
||||||
states.Texture = null;
|
states.Texture = null;
|
||||||
|
states.Shader = Shader.GetSpineShader(usePremultipliedAlpha);
|
||||||
|
|
||||||
// 要用 DrawOrder 而不是 Slots
|
// 要用 DrawOrder 而不是 Slots
|
||||||
foreach (var slot in skeleton.DrawOrder)
|
foreach (var slot in skeleton.DrawOrder)
|
||||||
@@ -320,18 +323,13 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 似乎 2.1.x 也没有 BlendMode
|
// 似乎 2.1.x 也没有 BlendMode
|
||||||
SFML.Graphics.BlendMode blendMode = slot.Data.AdditiveBlending ? BlendModeSFML.Additive : BlendModeSFML.Normal;
|
SFML.Graphics.BlendMode blendMode = slot.Data.AdditiveBlending ? BlendModeSFML.AdditivePma : BlendModeSFML.NormalPma;
|
||||||
|
|
||||||
states.Texture ??= texture;
|
states.Texture ??= texture;
|
||||||
if (states.BlendMode != blendMode || states.Texture != texture)
|
if (states.BlendMode != blendMode || states.Texture != texture)
|
||||||
{
|
{
|
||||||
if (vertexArray.VertexCount > 0)
|
if (vertexArray.VertexCount > 0)
|
||||||
{
|
{
|
||||||
if (usePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
|
||||||
states.Shader = Shader.FragmentShader;
|
|
||||||
else
|
|
||||||
states.Shader = null;
|
|
||||||
|
|
||||||
// 调试纹理
|
// 调试纹理
|
||||||
if (!isDebug || debugTexture)
|
if (!isDebug || debugTexture)
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
@@ -375,11 +373,6 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
|
|
||||||
//clipping.ClipEnd(slot);
|
//clipping.ClipEnd(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (usePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
|
||||||
states.Shader = Shader.FragmentShader;
|
|
||||||
else
|
|
||||||
states.Shader = null;
|
|
||||||
//clipping.ClipEnd();
|
//clipping.ClipEnd();
|
||||||
|
|
||||||
// 调试纹理
|
// 调试纹理
|
||||||
|
|||||||
@@ -108,18 +108,16 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
var pos = position;
|
var pos = position;
|
||||||
var fX = flipX;
|
var fX = flipX;
|
||||||
var fY = flipY;
|
var fY = flipY;
|
||||||
var animation = track0Animation; // TODO: 适配多轨道
|
var animations = animationState.Tracks.Where(te => te is not null).Select(te => te.Animation.Name).ToArray();
|
||||||
var sk = skin;
|
|
||||||
|
|
||||||
var val = Math.Max(value, SCALE_MIN);
|
|
||||||
if (skeletonBinary is not null)
|
if (skeletonBinary is not null)
|
||||||
{
|
{
|
||||||
skeletonBinary.Scale = val;
|
skeletonBinary.Scale = value;
|
||||||
skeletonData = skeletonBinary.ReadSkeletonData(SkelPath);
|
skeletonData = skeletonBinary.ReadSkeletonData(SkelPath);
|
||||||
}
|
}
|
||||||
else if (skeletonJson is not null)
|
else if (skeletonJson is not null)
|
||||||
{
|
{
|
||||||
skeletonJson.Scale = val;
|
skeletonJson.Scale = value;
|
||||||
skeletonData = skeletonJson.ReadSkeletonData(SkelPath);
|
skeletonData = skeletonJson.ReadSkeletonData(SkelPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,8 +130,8 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
position = pos;
|
position = pos;
|
||||||
flipX = fX;
|
flipX = fX;
|
||||||
flipY = fY;
|
flipY = fY;
|
||||||
track0Animation = animation; // TODO: 适配多轨道
|
foreach (var s in loadedSkins) addSkin(s);
|
||||||
skin = sk;
|
for (int i = 0; i < animations.Length; i++) setAnimation(i, animations[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,29 +157,35 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
set => skeleton.FlipY = value;
|
set => skeleton.FlipY = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string skin
|
protected override void addSkin(string name)
|
||||||
{
|
{
|
||||||
get => skeleton.Skin?.Name ?? "default";
|
if (!skinNames.Contains(name)) return;
|
||||||
set
|
skeleton.SetSkin(name); // XXX: 3.7 及以下不支持 AddSkin
|
||||||
{
|
|
||||||
if (!skinNames.Contains(value)) return;
|
|
||||||
skeleton.SetSkin(value);
|
|
||||||
skeleton.SetSlotsToSetupPose();
|
skeleton.SetSlotsToSetupPose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void clearSkin()
|
||||||
|
{
|
||||||
|
skeleton.SetSkin(skeletonData.DefaultSkin);
|
||||||
|
skeleton.SetSlotsToSetupPose();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string track0Animation
|
protected override int[] getTrackIndices() => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
|
||||||
|
|
||||||
|
protected override string getAnimation(int track) => animationState.GetCurrent(track)?.Animation.Name ?? EMPTY_ANIMATION;
|
||||||
|
|
||||||
|
protected override void setAnimation(int track, string name)
|
||||||
{
|
{
|
||||||
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
|
if (name == EMPTY_ANIMATION)
|
||||||
set
|
animationState.SetAnimation(track, EmptyAnimation, false);
|
||||||
{
|
else if (animationNames.Contains(name))
|
||||||
if (value == EMPTY_ANIMATION)
|
animationState.SetAnimation(track, name, true);
|
||||||
animationState.SetAnimation(0, EmptyAnimation, false);
|
|
||||||
else if (animationNames.Contains(value))
|
|
||||||
animationState.SetAnimation(0, value, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void clearTrack(int i) => animationState.ClearTrack(i);
|
||||||
|
|
||||||
|
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
|
||||||
|
|
||||||
protected override RectangleF bounds
|
protected override RectangleF bounds
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -192,8 +196,6 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
|
|
||||||
|
|
||||||
protected override void update(float delta)
|
protected override void update(float delta)
|
||||||
{
|
{
|
||||||
animationState.Update(delta);
|
animationState.Update(delta);
|
||||||
@@ -206,10 +208,10 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
return spineBlendMode switch
|
return spineBlendMode switch
|
||||||
{
|
{
|
||||||
BlendMode.Normal => BlendModeSFML.Normal,
|
BlendMode.Normal => BlendModeSFML.NormalPma,
|
||||||
BlendMode.Additive => BlendModeSFML.Additive,
|
BlendMode.Additive => BlendModeSFML.AdditivePma,
|
||||||
BlendMode.Multiply => BlendModeSFML.Multiply,
|
BlendMode.Multiply => BlendModeSFML.MultiplyPma,
|
||||||
BlendMode.Screen => BlendModeSFML.Screen,
|
BlendMode.Screen => BlendModeSFML.ScreenPma,
|
||||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -218,6 +220,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
vertexArray.Clear();
|
vertexArray.Clear();
|
||||||
states.Texture = null;
|
states.Texture = null;
|
||||||
|
states.Shader = Shader.GetSpineShader(usePremultipliedAlpha);
|
||||||
|
|
||||||
// 要用 DrawOrder 而不是 Slots
|
// 要用 DrawOrder 而不是 Slots
|
||||||
foreach (var slot in skeleton.DrawOrder)
|
foreach (var slot in skeleton.DrawOrder)
|
||||||
@@ -284,11 +287,6 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
if (vertexArray.VertexCount > 0)
|
if (vertexArray.VertexCount > 0)
|
||||||
{
|
{
|
||||||
if (usePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
|
||||||
states.Shader = Shader.FragmentShader;
|
|
||||||
else
|
|
||||||
states.Shader = null;
|
|
||||||
|
|
||||||
// 调试纹理
|
// 调试纹理
|
||||||
if (!isDebug || debugTexture)
|
if (!isDebug || debugTexture)
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
@@ -334,11 +332,6 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
clipping.ClipEnd();
|
clipping.ClipEnd();
|
||||||
|
|
||||||
if (usePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
|
||||||
states.Shader = Shader.FragmentShader;
|
|
||||||
else
|
|
||||||
states.Shader = null;
|
|
||||||
|
|
||||||
// 调试纹理
|
// 调试纹理
|
||||||
if (!isDebug || debugTexture)
|
if (!isDebug || debugTexture)
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
|
|||||||
@@ -129,29 +129,35 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string skin
|
protected override void addSkin(string name)
|
||||||
{
|
{
|
||||||
get => skeleton.Skin?.Name ?? "default";
|
if (!skinNames.Contains(name)) return;
|
||||||
set
|
skeleton.SetSkin(name); // XXX: 3.7 及以下不支持 AddSkin
|
||||||
{
|
|
||||||
if (!skinNames.Contains(value)) return;
|
|
||||||
skeleton.SetSkin(value);
|
|
||||||
skeleton.SetSlotsToSetupPose();
|
skeleton.SetSlotsToSetupPose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void clearSkin()
|
||||||
|
{
|
||||||
|
skeleton.SetSkin(skeletonData.DefaultSkin);
|
||||||
|
skeleton.SetSlotsToSetupPose();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string track0Animation
|
protected override int[] getTrackIndices() => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
|
||||||
|
|
||||||
|
protected override string getAnimation(int track) => animationState.GetCurrent(track)?.Animation.Name ?? EMPTY_ANIMATION;
|
||||||
|
|
||||||
|
protected override void setAnimation(int track, string name)
|
||||||
{
|
{
|
||||||
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
|
if (name == EMPTY_ANIMATION)
|
||||||
set
|
animationState.SetAnimation(track, EmptyAnimation, false);
|
||||||
{
|
else if (animationNames.Contains(name))
|
||||||
if (value == EMPTY_ANIMATION)
|
animationState.SetAnimation(track, name, true);
|
||||||
animationState.SetAnimation(0, EmptyAnimation, false);
|
|
||||||
else if (animationNames.Contains(value))
|
|
||||||
animationState.SetAnimation(0, value, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void clearTrack(int i) => animationState.ClearTrack(i);
|
||||||
|
|
||||||
|
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
|
||||||
|
|
||||||
protected override RectangleF bounds
|
protected override RectangleF bounds
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -162,8 +168,6 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
|
|
||||||
|
|
||||||
protected override void update(float delta)
|
protected override void update(float delta)
|
||||||
{
|
{
|
||||||
animationState.Update(delta);
|
animationState.Update(delta);
|
||||||
@@ -176,10 +180,10 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
return spineBlendMode switch
|
return spineBlendMode switch
|
||||||
{
|
{
|
||||||
BlendMode.Normal => BlendModeSFML.Normal,
|
BlendMode.Normal => BlendModeSFML.NormalPma,
|
||||||
BlendMode.Additive => BlendModeSFML.Additive,
|
BlendMode.Additive => BlendModeSFML.AdditivePma,
|
||||||
BlendMode.Multiply => BlendModeSFML.Multiply,
|
BlendMode.Multiply => BlendModeSFML.MultiplyPma,
|
||||||
BlendMode.Screen => BlendModeSFML.Screen,
|
BlendMode.Screen => BlendModeSFML.ScreenPma,
|
||||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -188,6 +192,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
vertexArray.Clear();
|
vertexArray.Clear();
|
||||||
states.Texture = null;
|
states.Texture = null;
|
||||||
|
states.Shader = Shader.GetSpineShader(usePremultipliedAlpha);
|
||||||
|
|
||||||
// 要用 DrawOrder 而不是 Slots
|
// 要用 DrawOrder 而不是 Slots
|
||||||
foreach (var slot in skeleton.DrawOrder)
|
foreach (var slot in skeleton.DrawOrder)
|
||||||
@@ -254,12 +259,6 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
if (vertexArray.VertexCount > 0)
|
if (vertexArray.VertexCount > 0)
|
||||||
{
|
{
|
||||||
// XXX: 实测不用设置 sampler2D 的值也正确
|
|
||||||
if (usePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
|
||||||
states.Shader = Shader.FragmentShader;
|
|
||||||
else
|
|
||||||
states.Shader = null;
|
|
||||||
|
|
||||||
// 调试纹理
|
// 调试纹理
|
||||||
if (!isDebug || debugTexture)
|
if (!isDebug || debugTexture)
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
@@ -305,11 +304,6 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
clipping.ClipEnd();
|
clipping.ClipEnd();
|
||||||
|
|
||||||
if (usePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
|
||||||
states.Shader = Shader.FragmentShader;
|
|
||||||
else
|
|
||||||
states.Shader = null;
|
|
||||||
|
|
||||||
// 调试纹理
|
// 调试纹理
|
||||||
if (!isDebug || debugTexture)
|
if (!isDebug || debugTexture)
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
foreach (var anime in skeletonData.Animations)
|
foreach (var anime in skeletonData.Animations)
|
||||||
animationNames.Add(anime.Name);
|
animationNames.Add(anime.Name);
|
||||||
|
|
||||||
skeleton = new Skeleton(skeletonData);
|
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
|
||||||
animationStateData = new AnimationStateData(skeletonData);
|
animationStateData = new AnimationStateData(skeletonData);
|
||||||
animationState = new AnimationState(animationStateData);
|
animationState = new AnimationState(animationStateData);
|
||||||
}
|
}
|
||||||
@@ -135,29 +135,37 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string skin
|
protected override void addSkin(string name)
|
||||||
{
|
{
|
||||||
get => skeleton.Skin?.Name ?? "default";
|
if (skeletonData.FindSkin(name) is Skin sk)
|
||||||
set
|
|
||||||
{
|
{
|
||||||
if (!skinNames.Contains(value)) return;
|
skeleton.Skin.AddSkin(sk);
|
||||||
skeleton.SetSkin(value);
|
|
||||||
skeleton.SetSlotsToSetupPose();
|
skeleton.SetSlotsToSetupPose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string track0Animation
|
protected override void clearSkin()
|
||||||
{
|
{
|
||||||
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
|
skeleton.Skin.Clear();
|
||||||
set
|
skeleton.SetSlotsToSetupPose();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int[] getTrackIndices() => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
|
||||||
|
|
||||||
|
protected override string getAnimation(int track) => animationState.GetCurrent(track)?.Animation.Name ?? EMPTY_ANIMATION;
|
||||||
|
|
||||||
|
protected override void setAnimation(int track, string name)
|
||||||
{
|
{
|
||||||
if (value == EMPTY_ANIMATION)
|
if (name == EMPTY_ANIMATION)
|
||||||
animationState.SetAnimation(0, EmptyAnimation, false);
|
animationState.SetAnimation(track, EmptyAnimation, false);
|
||||||
else if (animationNames.Contains(value))
|
else if (animationNames.Contains(name))
|
||||||
animationState.SetAnimation(0, value, true);
|
animationState.SetAnimation(track, name, true);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void clearTrack(int i) => animationState.ClearTrack(i);
|
||||||
|
|
||||||
|
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
|
||||||
|
|
||||||
protected override RectangleF bounds
|
protected override RectangleF bounds
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -168,8 +176,6 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
|
|
||||||
|
|
||||||
protected override void update(float delta)
|
protected override void update(float delta)
|
||||||
{
|
{
|
||||||
animationState.Update(delta);
|
animationState.Update(delta);
|
||||||
@@ -182,10 +188,10 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
return spineBlendMode switch
|
return spineBlendMode switch
|
||||||
{
|
{
|
||||||
BlendMode.Normal => BlendModeSFML.Normal,
|
BlendMode.Normal => BlendModeSFML.NormalPma,
|
||||||
BlendMode.Additive => BlendModeSFML.Additive,
|
BlendMode.Additive => BlendModeSFML.AdditivePma,
|
||||||
BlendMode.Multiply => BlendModeSFML.Multiply,
|
BlendMode.Multiply => BlendModeSFML.MultiplyPma,
|
||||||
BlendMode.Screen => BlendModeSFML.Screen,
|
BlendMode.Screen => BlendModeSFML.ScreenPma,
|
||||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -194,6 +200,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
vertexArray.Clear();
|
vertexArray.Clear();
|
||||||
states.Texture = null;
|
states.Texture = null;
|
||||||
|
states.Shader = Shader.GetSpineShader(usePremultipliedAlpha);
|
||||||
|
|
||||||
// 要用 DrawOrder 而不是 Slots
|
// 要用 DrawOrder 而不是 Slots
|
||||||
foreach (var slot in skeleton.DrawOrder)
|
foreach (var slot in skeleton.DrawOrder)
|
||||||
@@ -260,12 +267,6 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
if (vertexArray.VertexCount > 0)
|
if (vertexArray.VertexCount > 0)
|
||||||
{
|
{
|
||||||
// XXX: 实测不用设置 sampler2D 的值也正确
|
|
||||||
if (usePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
|
||||||
states.Shader = Shader.FragmentShader;
|
|
||||||
else
|
|
||||||
states.Shader = null;
|
|
||||||
|
|
||||||
// 调试纹理
|
// 调试纹理
|
||||||
if (!isDebug || debugTexture)
|
if (!isDebug || debugTexture)
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
@@ -311,11 +312,6 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
clipping.ClipEnd();
|
clipping.ClipEnd();
|
||||||
|
|
||||||
if (usePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
|
||||||
states.Shader = Shader.FragmentShader;
|
|
||||||
else
|
|
||||||
states.Shader = null;
|
|
||||||
|
|
||||||
// 调试纹理
|
// 调试纹理
|
||||||
if (!isDebug || debugTexture)
|
if (!isDebug || debugTexture)
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
foreach (var anime in skeletonData.Animations)
|
foreach (var anime in skeletonData.Animations)
|
||||||
animationNames.Add(anime.Name);
|
animationNames.Add(anime.Name);
|
||||||
|
|
||||||
skeleton = new Skeleton(skeletonData);
|
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
|
||||||
animationStateData = new AnimationStateData(skeletonData);
|
animationStateData = new AnimationStateData(skeletonData);
|
||||||
animationState = new AnimationState(animationStateData);
|
animationState = new AnimationState(animationStateData);
|
||||||
}
|
}
|
||||||
@@ -131,29 +131,37 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string skin
|
protected override void addSkin(string name)
|
||||||
{
|
{
|
||||||
get => skeleton.Skin?.Name ?? "default";
|
if (skeletonData.FindSkin(name) is Skin sk)
|
||||||
set
|
|
||||||
{
|
{
|
||||||
if (!skinNames.Contains(value)) return;
|
skeleton.Skin.AddSkin(sk);
|
||||||
skeleton.SetSkin(value);
|
|
||||||
skeleton.SetSlotsToSetupPose();
|
skeleton.SetSlotsToSetupPose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string track0Animation
|
protected override void clearSkin()
|
||||||
{
|
{
|
||||||
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
|
skeleton.Skin.Clear();
|
||||||
set
|
skeleton.SetSlotsToSetupPose();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int[] getTrackIndices() => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
|
||||||
|
|
||||||
|
protected override string getAnimation(int track) => animationState.GetCurrent(track)?.Animation.Name ?? EMPTY_ANIMATION;
|
||||||
|
|
||||||
|
protected override void setAnimation(int track, string name)
|
||||||
{
|
{
|
||||||
if (value == EMPTY_ANIMATION)
|
if (name == EMPTY_ANIMATION)
|
||||||
animationState.SetAnimation(0, EmptyAnimation, false);
|
animationState.SetAnimation(track, EmptyAnimation, false);
|
||||||
else if (animationNames.Contains(value))
|
else if (animationNames.Contains(name))
|
||||||
animationState.SetAnimation(0, value, true);
|
animationState.SetAnimation(track, name, true);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void clearTrack(int i) => animationState.ClearTrack(i);
|
||||||
|
|
||||||
|
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
|
||||||
|
|
||||||
protected override RectangleF bounds
|
protected override RectangleF bounds
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -164,8 +172,6 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
|
|
||||||
|
|
||||||
protected override void update(float delta)
|
protected override void update(float delta)
|
||||||
{
|
{
|
||||||
animationState.Update(delta);
|
animationState.Update(delta);
|
||||||
@@ -178,10 +184,10 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
return spineBlendMode switch
|
return spineBlendMode switch
|
||||||
{
|
{
|
||||||
BlendMode.Normal => BlendModeSFML.Normal,
|
BlendMode.Normal => BlendModeSFML.NormalPma,
|
||||||
BlendMode.Additive => BlendModeSFML.Additive,
|
BlendMode.Additive => BlendModeSFML.AdditivePma,
|
||||||
BlendMode.Multiply => BlendModeSFML.Multiply,
|
BlendMode.Multiply => BlendModeSFML.MultiplyPma,
|
||||||
BlendMode.Screen => BlendModeSFML.Screen,
|
BlendMode.Screen => BlendModeSFML.ScreenPma,
|
||||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -190,6 +196,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
vertexArray.Clear();
|
vertexArray.Clear();
|
||||||
states.Texture = null;
|
states.Texture = null;
|
||||||
|
states.Shader = Shader.GetSpineShader(usePremultipliedAlpha);
|
||||||
|
|
||||||
// 要用 DrawOrder 而不是 Slots
|
// 要用 DrawOrder 而不是 Slots
|
||||||
foreach (var slot in skeleton.DrawOrder)
|
foreach (var slot in skeleton.DrawOrder)
|
||||||
@@ -256,12 +263,6 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
if (vertexArray.VertexCount > 0)
|
if (vertexArray.VertexCount > 0)
|
||||||
{
|
{
|
||||||
// XXX: 实测不用设置 sampler2D 的值也正确
|
|
||||||
if (usePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
|
||||||
states.Shader = Shader.FragmentShader;
|
|
||||||
else
|
|
||||||
states.Shader = null;
|
|
||||||
|
|
||||||
// 调试纹理
|
// 调试纹理
|
||||||
if (!isDebug || debugTexture)
|
if (!isDebug || debugTexture)
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
@@ -307,11 +308,6 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
clipping.ClipEnd();
|
clipping.ClipEnd();
|
||||||
|
|
||||||
if (usePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
|
||||||
states.Shader = Shader.FragmentShader;
|
|
||||||
else
|
|
||||||
states.Shader = null;
|
|
||||||
|
|
||||||
// 调试纹理
|
// 调试纹理
|
||||||
if (!isDebug || debugTexture)
|
if (!isDebug || debugTexture)
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
foreach (var anime in skeletonData.Animations)
|
foreach (var anime in skeletonData.Animations)
|
||||||
animationNames.Add(anime.Name);
|
animationNames.Add(anime.Name);
|
||||||
|
|
||||||
skeleton = new Skeleton(skeletonData);
|
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
|
||||||
animationStateData = new AnimationStateData(skeletonData);
|
animationStateData = new AnimationStateData(skeletonData);
|
||||||
animationState = new AnimationState(animationStateData);
|
animationState = new AnimationState(animationStateData);
|
||||||
}
|
}
|
||||||
@@ -131,29 +131,37 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string skin
|
protected override void addSkin(string name)
|
||||||
{
|
{
|
||||||
get => skeleton.Skin?.Name ?? "default";
|
if (skeletonData.FindSkin(name) is Skin sk)
|
||||||
set
|
|
||||||
{
|
{
|
||||||
if (!skinNames.Contains(value)) return;
|
skeleton.Skin.AddSkin(sk);
|
||||||
skeleton.SetSkin(value);
|
|
||||||
skeleton.SetSlotsToSetupPose();
|
skeleton.SetSlotsToSetupPose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string track0Animation
|
protected override void clearSkin()
|
||||||
{
|
{
|
||||||
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
|
skeleton.Skin.Clear();
|
||||||
set
|
skeleton.SetSlotsToSetupPose();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int[] getTrackIndices() => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
|
||||||
|
|
||||||
|
protected override string getAnimation(int track) => animationState.GetCurrent(track)?.Animation.Name ?? EMPTY_ANIMATION;
|
||||||
|
|
||||||
|
protected override void setAnimation(int track, string name)
|
||||||
{
|
{
|
||||||
if (value == EMPTY_ANIMATION)
|
if (name == EMPTY_ANIMATION)
|
||||||
animationState.SetAnimation(0, EmptyAnimation, false);
|
animationState.SetAnimation(track, EmptyAnimation, false);
|
||||||
else if (animationNames.Contains(value))
|
else if (animationNames.Contains(name))
|
||||||
animationState.SetAnimation(0, value, true);
|
animationState.SetAnimation(track, name, true);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void clearTrack(int i) => animationState.ClearTrack(i);
|
||||||
|
|
||||||
|
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
|
||||||
|
|
||||||
protected override RectangleF bounds
|
protected override RectangleF bounds
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -164,8 +172,6 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
|
|
||||||
|
|
||||||
protected override void update(float delta)
|
protected override void update(float delta)
|
||||||
{
|
{
|
||||||
animationState.Update(delta);
|
animationState.Update(delta);
|
||||||
@@ -178,10 +184,10 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
return spineBlendMode switch
|
return spineBlendMode switch
|
||||||
{
|
{
|
||||||
BlendMode.Normal => BlendModeSFML.Normal,
|
BlendMode.Normal => BlendModeSFML.NormalPma,
|
||||||
BlendMode.Additive => BlendModeSFML.Additive,
|
BlendMode.Additive => BlendModeSFML.AdditivePma,
|
||||||
BlendMode.Multiply => BlendModeSFML.Multiply,
|
BlendMode.Multiply => BlendModeSFML.MultiplyPma,
|
||||||
BlendMode.Screen => BlendModeSFML.Screen,
|
BlendMode.Screen => BlendModeSFML.ScreenPma,
|
||||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -190,6 +196,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
vertexArray.Clear();
|
vertexArray.Clear();
|
||||||
states.Texture = null;
|
states.Texture = null;
|
||||||
|
states.Shader = Shader.GetSpineShader(usePremultipliedAlpha);
|
||||||
|
|
||||||
// 要用 DrawOrder 而不是 Slots
|
// 要用 DrawOrder 而不是 Slots
|
||||||
foreach (var slot in skeleton.DrawOrder)
|
foreach (var slot in skeleton.DrawOrder)
|
||||||
@@ -256,12 +263,6 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
if (vertexArray.VertexCount > 0)
|
if (vertexArray.VertexCount > 0)
|
||||||
{
|
{
|
||||||
// XXX: 实测不用设置 sampler2D 的值也正确
|
|
||||||
if (usePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
|
||||||
states.Shader = Shader.FragmentShader;
|
|
||||||
else
|
|
||||||
states.Shader = null;
|
|
||||||
|
|
||||||
// 调试纹理
|
// 调试纹理
|
||||||
if (!isDebug || debugTexture)
|
if (!isDebug || debugTexture)
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
@@ -307,11 +308,6 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
clipping.ClipEnd();
|
clipping.ClipEnd();
|
||||||
|
|
||||||
if (usePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
|
||||||
states.Shader = Shader.FragmentShader;
|
|
||||||
else
|
|
||||||
states.Shader = null;
|
|
||||||
|
|
||||||
// 调试纹理
|
// 调试纹理
|
||||||
if (!isDebug || debugTexture)
|
if (!isDebug || debugTexture)
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
foreach (var anime in skeletonData.Animations)
|
foreach (var anime in skeletonData.Animations)
|
||||||
animationNames.Add(anime.Name);
|
animationNames.Add(anime.Name);
|
||||||
|
|
||||||
skeleton = new Skeleton(skeletonData);
|
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
|
||||||
animationStateData = new AnimationStateData(skeletonData);
|
animationStateData = new AnimationStateData(skeletonData);
|
||||||
animationState = new AnimationState(animationStateData);
|
animationState = new AnimationState(animationStateData);
|
||||||
}
|
}
|
||||||
@@ -131,29 +131,37 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string skin
|
protected override void addSkin(string name)
|
||||||
{
|
{
|
||||||
get => skeleton.Skin?.Name ?? "default";
|
if (skeletonData.FindSkin(name) is Skin sk)
|
||||||
set
|
|
||||||
{
|
{
|
||||||
if (!skinNames.Contains(value)) return;
|
skeleton.Skin.AddSkin(sk);
|
||||||
skeleton.SetSkin(value);
|
|
||||||
skeleton.SetSlotsToSetupPose();
|
skeleton.SetSlotsToSetupPose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string track0Animation
|
protected override void clearSkin()
|
||||||
{
|
{
|
||||||
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
|
skeleton.Skin.Clear();
|
||||||
set
|
skeleton.SetSlotsToSetupPose();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int[] getTrackIndices() => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
|
||||||
|
|
||||||
|
protected override string getAnimation(int track) => animationState.GetCurrent(track)?.Animation.Name ?? EMPTY_ANIMATION;
|
||||||
|
|
||||||
|
protected override void setAnimation(int track, string name)
|
||||||
{
|
{
|
||||||
if (value == EMPTY_ANIMATION)
|
if (name == EMPTY_ANIMATION)
|
||||||
animationState.SetAnimation(0, EmptyAnimation, false);
|
animationState.SetAnimation(track, EmptyAnimation, false);
|
||||||
else if (animationNames.Contains(value))
|
else if (animationNames.Contains(name))
|
||||||
animationState.SetAnimation(0, value, true);
|
animationState.SetAnimation(track, name, true);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void clearTrack(int i) => animationState.ClearTrack(i);
|
||||||
|
|
||||||
|
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
|
||||||
|
|
||||||
protected override RectangleF bounds
|
protected override RectangleF bounds
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -164,8 +172,6 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
|
|
||||||
|
|
||||||
protected override void update(float delta)
|
protected override void update(float delta)
|
||||||
{
|
{
|
||||||
animationState.Update(delta);
|
animationState.Update(delta);
|
||||||
@@ -178,10 +184,10 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
return spineBlendMode switch
|
return spineBlendMode switch
|
||||||
{
|
{
|
||||||
BlendMode.Normal => BlendModeSFML.Normal,
|
BlendMode.Normal => BlendModeSFML.NormalPma,
|
||||||
BlendMode.Additive => BlendModeSFML.Additive,
|
BlendMode.Additive => BlendModeSFML.AdditivePma,
|
||||||
BlendMode.Multiply => BlendModeSFML.Multiply,
|
BlendMode.Multiply => BlendModeSFML.MultiplyPma,
|
||||||
BlendMode.Screen => BlendModeSFML.Screen,
|
BlendMode.Screen => BlendModeSFML.ScreenPma,
|
||||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -190,6 +196,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
vertexArray.Clear();
|
vertexArray.Clear();
|
||||||
states.Texture = null;
|
states.Texture = null;
|
||||||
|
states.Shader = Shader.GetSpineShader(usePremultipliedAlpha);
|
||||||
|
|
||||||
// 要用 DrawOrder 而不是 Slots
|
// 要用 DrawOrder 而不是 Slots
|
||||||
foreach (var slot in skeleton.DrawOrder)
|
foreach (var slot in skeleton.DrawOrder)
|
||||||
@@ -256,12 +263,6 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
{
|
{
|
||||||
if (vertexArray.VertexCount > 0)
|
if (vertexArray.VertexCount > 0)
|
||||||
{
|
{
|
||||||
// XXX: 实测不用设置 sampler2D 的值也正确
|
|
||||||
if (usePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
|
||||||
states.Shader = Shader.FragmentShader;
|
|
||||||
else
|
|
||||||
states.Shader = null;
|
|
||||||
|
|
||||||
// 调试纹理
|
// 调试纹理
|
||||||
if (!isDebug || debugTexture)
|
if (!isDebug || debugTexture)
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
@@ -307,11 +308,6 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
clipping.ClipEnd();
|
clipping.ClipEnd();
|
||||||
|
|
||||||
if (usePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
|
||||||
states.Shader = Shader.FragmentShader;
|
|
||||||
else
|
|
||||||
states.Shader = null;
|
|
||||||
|
|
||||||
// 调试纹理
|
// 调试纹理
|
||||||
if (!isDebug || debugTexture)
|
if (!isDebug || debugTexture)
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace SpineViewer.Spine
|
|
||||||
{
|
|
||||||
public static class Shader
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 用于解决 PMA 和渐变动画问题的片段着色器
|
|
||||||
/// </summary>
|
|
||||||
private const string FRAGMENT_SHADER = (
|
|
||||||
"uniform sampler2D t;" +
|
|
||||||
"void main() { vec4 p = texture2D(t, gl_TexCoord[0].xy);" +
|
|
||||||
"if (p.a > 0) p.rgb /= max(max(max(p.r, p.g), p.b), p.a);" +
|
|
||||||
"gl_FragColor = gl_Color * p; }"
|
|
||||||
);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 针对预乘 Alpha 通道的片段着色器
|
|
||||||
/// </summary>
|
|
||||||
public static SFML.Graphics.Shader? FragmentShader { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 加载 Shader, 可能会存在异常导致着色器加载失败
|
|
||||||
/// </summary>
|
|
||||||
/// <exception cref="SFML.LoadingFailedException"></exception>
|
|
||||||
public static void Init()
|
|
||||||
{
|
|
||||||
FragmentShader = SFML.Graphics.Shader.FromString(null, null, FRAGMENT_SHADER);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
135
SpineViewer/Spine/SkinManager.cs
Normal file
135
SpineViewer/Spine/SkinManager.cs
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SpineViewer.Spine
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 对皮肤的包装类
|
||||||
|
/// </summary>
|
||||||
|
[TypeConverter(typeof(SkinWrapperConverter))]
|
||||||
|
public class SkinWrapper(Spine spine, int i)
|
||||||
|
{
|
||||||
|
private readonly Spine spine = spine;
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
public int Index { get; } = i;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var loadedSkins = spine.GetLoadedSkins();
|
||||||
|
if (Index >= 0 && Index < loadedSkins.Length)
|
||||||
|
return loadedSkins[Index];
|
||||||
|
return "!NULL"; // XXX: 预期应该不会发生
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (obj is SkinWrapper) return ToString() == obj.ToString();
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode() => (typeof(SkinWrapper).FullName + ToString()).GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 皮肤属性描述符, 实现对属性的读取和赋值
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="spine">关联的 Spine 对象</param>
|
||||||
|
public class SkinWrapperPropertyDescriptor(int i) : PropertyDescriptor($"Skin{i}", [new DisplayNameAttribute($"皮肤 {i}")])
|
||||||
|
{
|
||||||
|
private readonly int idx = i;
|
||||||
|
|
||||||
|
public override Type ComponentType => typeof(SkinManager);
|
||||||
|
public override bool IsReadOnly => false;
|
||||||
|
public override Type PropertyType => typeof(SkinWrapper);
|
||||||
|
public override bool CanResetValue(object component) => false;
|
||||||
|
public override void ResetValue(object component) { }
|
||||||
|
public override bool ShouldSerializeValue(object component) => false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 得到一个 SkinWrapper, 允许用户查看或者修改具体的属性值, 这个地方决定了在面板上看到的是一个对象及其属性
|
||||||
|
/// </summary>
|
||||||
|
public override object? GetValue(object? component)
|
||||||
|
{
|
||||||
|
if (component is SkinManager manager)
|
||||||
|
return manager.GetSkinWrapper(idx);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 允许通过字符串赋值修改该位置的皮肤
|
||||||
|
/// </summary>
|
||||||
|
public override void SetValue(object? component, object? value)
|
||||||
|
{
|
||||||
|
if (component is SkinManager manager)
|
||||||
|
{
|
||||||
|
if (value is string s)
|
||||||
|
manager.Spine.ReplaceSkin(idx, s); // manager.SetSkinWrapper(idx, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SkinManager 动态类型包装类, 用于提供对 Spine 皮肤列表的管理能力
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="spine">关联的 Spine 对象</param>
|
||||||
|
public class SkinManager(Spine spine) : ICustomTypeDescriptor
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<int, SkinWrapperPropertyDescriptor> pdCache = [];
|
||||||
|
|
||||||
|
public Spine Spine { get; } = spine;
|
||||||
|
private readonly Dictionary<int, SkinWrapper> skinWrapperProperties = [];
|
||||||
|
|
||||||
|
// XXX: 必须实现 ICustomTypeDescriptor 接口, 不能继承 CustomTypeDescriptor, 似乎继承下来的东西会有问题, 导致某些调用不正确
|
||||||
|
|
||||||
|
public AttributeCollection GetAttributes() => TypeDescriptor.GetAttributes(this, true);
|
||||||
|
public string? GetClassName() => TypeDescriptor.GetClassName(this, true);
|
||||||
|
public string? GetComponentName() => TypeDescriptor.GetComponentName(this, true);
|
||||||
|
public TypeConverter? GetConverter() => TypeDescriptor.GetConverter(this, true);
|
||||||
|
public EventDescriptor? GetDefaultEvent() => TypeDescriptor.GetDefaultEvent(this, true);
|
||||||
|
public PropertyDescriptor? GetDefaultProperty() => TypeDescriptor.GetDefaultProperty(this, true);
|
||||||
|
public object? GetEditor(Type editorBaseType) => TypeDescriptor.GetEditor(this, editorBaseType, true);
|
||||||
|
public EventDescriptorCollection GetEvents() => TypeDescriptor.GetEvents(this, true);
|
||||||
|
public EventDescriptorCollection GetEvents(Attribute[]? attributes) => TypeDescriptor.GetEvents(this, attributes, true);
|
||||||
|
public object? GetPropertyOwner(PropertyDescriptor? pd) => this;
|
||||||
|
public PropertyDescriptorCollection GetProperties() => GetProperties(null);
|
||||||
|
public PropertyDescriptorCollection GetProperties(Attribute[]? attributes)
|
||||||
|
{
|
||||||
|
var props = new List<SkinWrapperPropertyDescriptor>();
|
||||||
|
for (var i = 0; i < Spine.GetLoadedSkins().Length; i++)
|
||||||
|
{
|
||||||
|
if (!pdCache.ContainsKey(i))
|
||||||
|
pdCache[i] = new SkinWrapperPropertyDescriptor(i);
|
||||||
|
props.Add(pdCache[i]);
|
||||||
|
}
|
||||||
|
return new PropertyDescriptorCollection(props.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 访问 SkinWrapper 属性 <c>SkinManager.Skin{i}</c>
|
||||||
|
/// </summary>
|
||||||
|
public SkinWrapper GetSkinWrapper(int i)
|
||||||
|
{
|
||||||
|
if (!skinWrapperProperties.ContainsKey(i))
|
||||||
|
skinWrapperProperties[i] = new SkinWrapper(Spine, i);
|
||||||
|
return skinWrapperProperties[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 在属性面板悬停可以显示已加载的皮肤列表
|
||||||
|
/// </summary>
|
||||||
|
public override string ToString() => $"[{string.Join(", ", Spine.GetLoadedSkins())}]";
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (obj is SkinManager manager) return ToString() == manager.ToString();
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode() => (typeof(SkinManager).FullName + ToString()).GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Drawing.Design;
|
||||||
using System.Globalization;
|
using NLog;
|
||||||
using System.Text.Json.Nodes;
|
using System.Xml.Linq;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using SpineViewer.Exporter;
|
|
||||||
|
|
||||||
namespace SpineViewer.Spine
|
namespace SpineViewer.Spine
|
||||||
{
|
{
|
||||||
@@ -23,6 +13,9 @@ namespace SpineViewer.Spine
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class Spine : ImplementationResolver<Spine, SpineImplementationAttribute, SpineVersion>, SFML.Graphics.Drawable, IDisposable
|
public abstract class Spine : ImplementationResolver<Spine, SpineImplementationAttribute, SpineVersion>, SFML.Graphics.Drawable, IDisposable
|
||||||
{
|
{
|
||||||
|
private readonly Logger logger = LogManager.GetCurrentClassLogger();
|
||||||
|
private bool skinLoggerWarned = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 空动画标记
|
/// 空动画标记
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -38,11 +31,6 @@ namespace SpineViewer.Spine
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected const uint PREVIEW_HEIGHT = 256;
|
protected const uint PREVIEW_HEIGHT = 256;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 缩放最小值
|
|
||||||
/// </summary>
|
|
||||||
protected const float SCALE_MIN = 0.001f;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建特定版本的 Spine
|
/// 创建特定版本的 Spine
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -76,7 +64,9 @@ namespace SpineViewer.Spine
|
|||||||
private Spine PostInit()
|
private Spine PostInit()
|
||||||
{
|
{
|
||||||
SkinNames = skinNames.AsReadOnly();
|
SkinNames = skinNames.AsReadOnly();
|
||||||
|
SkinManager = new(this);
|
||||||
AnimationNames = animationNames.AsReadOnly();
|
AnimationNames = animationNames.AsReadOnly();
|
||||||
|
AnimationTracks = new(this);
|
||||||
|
|
||||||
// 必须 Update 一次否则包围盒还没有值
|
// 必须 Update 一次否则包围盒还没有值
|
||||||
update(0);
|
update(0);
|
||||||
@@ -87,25 +77,20 @@ namespace SpineViewer.Spine
|
|||||||
// 除此之外, 似乎还和 tex 的 Dispose 有关
|
// 除此之外, 似乎还和 tex 的 Dispose 有关
|
||||||
// 如果不对 tex 进行 Dispose, 那么不管是否 Draw 都正常不会死锁
|
// 如果不对 tex 进行 Dispose, 那么不管是否 Draw 都正常不会死锁
|
||||||
var tex = new SFML.Graphics.RenderTexture(PREVIEW_WIDTH, PREVIEW_HEIGHT);
|
var tex = new SFML.Graphics.RenderTexture(PREVIEW_WIDTH, PREVIEW_HEIGHT);
|
||||||
tex.SetView(bounds.GetView(PREVIEW_WIDTH, PREVIEW_HEIGHT));
|
using var view = bounds.GetView(PREVIEW_WIDTH, PREVIEW_HEIGHT);
|
||||||
|
tex.SetView(view);
|
||||||
tex.Clear(SFML.Graphics.Color.Transparent);
|
tex.Clear(SFML.Graphics.Color.Transparent);
|
||||||
tex.Draw(this);
|
tex.Draw(this);
|
||||||
tex.Display();
|
tex.Display();
|
||||||
|
Preview = tex.Texture.CopyToBitmap();
|
||||||
|
|
||||||
using (var img = tex.Texture.CopyToImage())
|
// 默认初始化10个空位
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
{
|
{
|
||||||
if (img.SaveToMemory(out var imgBuffer, "bmp"))
|
setAnimation(i, AnimationNames.First());
|
||||||
{
|
loadedSkins.Add(SkinNames.First());
|
||||||
// 必须重复构造一个副本才能摆脱对流的依赖, 否则之后使用会报错
|
|
||||||
using var stream = new MemoryStream(imgBuffer);
|
|
||||||
using var bitmap = new Bitmap(stream);
|
|
||||||
Preview = new Bitmap(bitmap);
|
|
||||||
}
|
}
|
||||||
}
|
reloadSkins();
|
||||||
|
|
||||||
// 取最后一个作为初始, 尽可能去显示非默认的内容
|
|
||||||
skin = SkinNames.Last();
|
|
||||||
track0Animation = AnimationNames.Last();
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -177,7 +162,7 @@ namespace SpineViewer.Spine
|
|||||||
get { lock (_lock) return usePremultipliedAlpha; }
|
get { lock (_lock) return usePremultipliedAlpha; }
|
||||||
set { lock (_lock) usePremultipliedAlpha = value; }
|
set { lock (_lock) usePremultipliedAlpha = value; }
|
||||||
}
|
}
|
||||||
protected bool usePremultipliedAlpha = true;
|
protected bool usePremultipliedAlpha = false;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -190,7 +175,7 @@ namespace SpineViewer.Spine
|
|||||||
public float Scale
|
public float Scale
|
||||||
{
|
{
|
||||||
get { lock (_lock) return scale; }
|
get { lock (_lock) return scale; }
|
||||||
set { lock (_lock) { scale = value; update(0); } }
|
set { lock (_lock) { scale = Math.Max(value, 0.001f); update(0); } }
|
||||||
}
|
}
|
||||||
protected abstract float scale { get; set; }
|
protected abstract float scale { get; set; }
|
||||||
|
|
||||||
@@ -232,49 +217,157 @@ namespace SpineViewer.Spine
|
|||||||
|
|
||||||
#region 属性 | [3] 动画
|
#region 属性 | [3] 动画
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 已加载皮肤列表
|
||||||
|
/// </summary>
|
||||||
|
[Editor(typeof(SkinManagerEditor), typeof(UITypeEditor))]
|
||||||
|
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||||
|
[Category("[3] 动画"), DisplayName("已加载皮肤列表")]
|
||||||
|
public SkinManager SkinManager { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 默认轨道动画名称, 如果设置的动画不存在则忽略
|
||||||
|
/// </summary>
|
||||||
|
[Browsable(false)]
|
||||||
|
public string Track0Animation
|
||||||
|
{
|
||||||
|
get { lock (_lock) return getAnimation(0); }
|
||||||
|
set { lock (_lock) { setAnimation(0, value); update(0); } }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 全轨道动画最大时长
|
||||||
|
/// </summary>
|
||||||
|
[Category("[3] 动画"), DisplayName("全轨道最大时长")]
|
||||||
|
public float AnimationTracksMaxDuration { get { lock (_lock) return getTrackIndices().Select(i => GetAnimationDuration(getAnimation(i))).Max(); } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 默认轨道动画时长
|
||||||
|
/// </summary>
|
||||||
|
[Editor(typeof(AnimationTracksEditor), typeof(UITypeEditor))]
|
||||||
|
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||||
|
[Category("[3] 动画"), DisplayName("多轨道动画管理")]
|
||||||
|
public AnimationTracks AnimationTracks { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 包含的所有皮肤名称
|
/// 包含的所有皮肤名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public ReadOnlyCollection<string> SkinNames { get; private set; }
|
public ReadOnlyCollection<string> SkinNames { get; private set; }
|
||||||
protected List<string> skinNames = [];
|
protected readonly List<string> skinNames = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 使用的皮肤名称, 如果设置的皮肤不存在则忽略
|
/// 获取已加载的皮肤列表快照, 允许出现重复值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TypeConverter(typeof(SkinConverter))]
|
public string[] GetLoadedSkins() { lock (_lock) return loadedSkins.ToArray(); }
|
||||||
[Category("[3] 动画"), DisplayName("皮肤")]
|
protected readonly List<string> loadedSkins = [];
|
||||||
public string Skin
|
|
||||||
|
/// <summary>
|
||||||
|
/// 加载指定皮肤, 添加至列表末尾, 如果不存在则忽略, 允许加载重复的值
|
||||||
|
/// </summary>
|
||||||
|
public void LoadSkin(string name)
|
||||||
{
|
{
|
||||||
get { lock (_lock) return skin; }
|
if (!skinNames.Contains(name)) return;
|
||||||
set { lock (_lock) { skin = value; update(0); } }
|
lock (_lock)
|
||||||
|
{
|
||||||
|
loadedSkins.Add(name);
|
||||||
|
reloadSkins();
|
||||||
|
|
||||||
|
if (!skinLoggerWarned && Version <= SpineVersion.V37 && loadedSkins.Count > 1)
|
||||||
|
{
|
||||||
|
logger.Warn($"Multiplt skins not supported in SpineVersion {Version.GetName()}");
|
||||||
|
skinLoggerWarned = true;
|
||||||
}
|
}
|
||||||
protected abstract string skin { get; set; }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 卸载列表指定位置皮肤, 如果超出范围则忽略
|
||||||
|
/// </summary>
|
||||||
|
public void UnloadSkin(int idx)
|
||||||
|
{
|
||||||
|
if (idx < 0 || idx >= loadedSkins.Count) return;
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
loadedSkins.RemoveAt(idx);
|
||||||
|
reloadSkins();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 替换皮肤列表指定位置皮肤, 超出范围或者皮肤不存在则忽略
|
||||||
|
/// </summary>
|
||||||
|
public void ReplaceSkin(int idx, string name)
|
||||||
|
{
|
||||||
|
if (idx < 0 || idx >= loadedSkins.Count || !skinNames.Contains(name)) return;
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
loadedSkins[idx] = name;
|
||||||
|
reloadSkins();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重新加载现有皮肤列表, 用于刷新等操作
|
||||||
|
/// </summary>
|
||||||
|
public void ReloadSkins() { lock (_lock) reloadSkins(); }
|
||||||
|
private void reloadSkins()
|
||||||
|
{
|
||||||
|
clearSkin();
|
||||||
|
foreach (var s in loadedSkins.Distinct()) addSkin(s);
|
||||||
|
update(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 加载皮肤, 如果不存在则忽略
|
||||||
|
/// </summary>
|
||||||
|
protected abstract void addSkin(string name);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清空加载的所有皮肤
|
||||||
|
/// </summary>
|
||||||
|
protected abstract void clearSkin();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 包含的所有动画名称
|
/// 包含的所有动画名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public ReadOnlyCollection<string> AnimationNames { get; private set; }
|
public ReadOnlyCollection<string> AnimationNames { get; private set; }
|
||||||
protected List<string> animationNames = [EMPTY_ANIMATION];
|
protected readonly List<string> animationNames = [EMPTY_ANIMATION];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 默认轨道动画名称, 如果设置的动画不存在则忽略
|
/// 获取所有非 null 的轨道索引快照
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TypeConverter(typeof(AnimationConverter))]
|
public int[] GetTrackIndices() { lock (_lock) return getTrackIndices(); }
|
||||||
[Category("[3] 动画"), DisplayName("默认轨道动画")]
|
protected abstract int[] getTrackIndices();
|
||||||
public string Track0Animation
|
|
||||||
{
|
|
||||||
get { lock (_lock) return track0Animation; }
|
|
||||||
set { lock (_lock) { track0Animation = value; update(0); } }
|
|
||||||
}
|
|
||||||
protected abstract string track0Animation { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 默认轨道动画时长
|
/// 获取指定轨道的当前动画, 如果没有, 应当返回空动画名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Category("[3] 动画"), DisplayName("默认轨道动画时长")]
|
public string GetAnimation(int track) { lock (_lock) return getAnimation(track); }
|
||||||
public float Track0AnimationDuration { get => GetAnimationDuration(Track0Animation); } // TODO: 动画时长变成伪属性在面板显示
|
protected abstract string getAnimation(int track);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置某个轨道动画
|
||||||
|
/// </summary>
|
||||||
|
public void SetAnimation(int track, string name) { lock (_lock) { setAnimation(track, name); update(0); } }
|
||||||
|
protected abstract void setAnimation(int track, string name);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清除某个轨道, 与设置空动画不同, 是彻底删除轨道内的东西
|
||||||
|
/// </summary>
|
||||||
|
public void ClearTrack(int i) { lock (_lock) { clearTrack(i); update(0); } }
|
||||||
|
protected abstract void clearTrack(int i); // XXX: 清除轨道之后被加载的附件还是会保留, 不会自动卸下, 除非使用 SetSlotsToSetupPose
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取动画时长, 如果动画不存在则返回 0
|
||||||
|
/// </summary>
|
||||||
|
public abstract float GetAnimationDuration(string name);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重置所有轨道上的动画时间
|
||||||
|
/// </summary>
|
||||||
|
public void ResetAnimationsTime() { lock (_lock) { foreach (var i in getTrackIndices()) setAnimation(i, getAnimation(i)); update(0); } }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -350,16 +443,11 @@ namespace SpineViewer.Spine
|
|||||||
protected abstract RectangleF bounds { get; }
|
protected abstract RectangleF bounds { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 骨骼预览图
|
/// 骨骼预览图, 并没有去除预乘, 画面可能偏暗
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public Image Preview { get; private set; }
|
public Image Preview { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取动画时长, 如果动画不存在则返回 0
|
|
||||||
/// </summary>
|
|
||||||
public abstract float GetAnimationDuration(string name);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 更新内部状态
|
/// 更新内部状态
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -390,10 +478,18 @@ namespace SpineViewer.Spine
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SFML.Graphics.Drawable 接口实现
|
/// SFML.Graphics.Drawable 接口实现
|
||||||
|
/// <para>这个渲染实现绘制出来的像素将是预乘的, 当渲染的背景透明度是 1 时, 则等价于非预乘的结果, 即正常画面, 否则画面偏暗</para>
|
||||||
|
/// <para>可以用于 <see cref="SFML.Graphics.RenderWindow"/> 的渲染, 因为直接在窗口上绘制时窗口始终是不透明的</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states) { lock (_lock) draw(target, states); }
|
public void Draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states) { lock (_lock) draw(target, states); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 这个渲染实现绘制出来的像素将是预乘的, 当渲染的背景透明度是 1 时, 则等价于非预乘的结果, 即正常画面, 否则画面偏暗
|
||||||
|
/// <para>可以用于 <see cref="SFML.Graphics.RenderWindow"/> 的渲染, 因为直接在窗口上绘制时窗口始终是不透明的</para>
|
||||||
|
/// </summary>
|
||||||
protected abstract void draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states);
|
protected abstract void draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
@@ -21,32 +22,6 @@ namespace SpineViewer.Spine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AnimationConverter : StringConverter
|
|
||||||
{
|
|
||||||
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true;
|
|
||||||
|
|
||||||
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context) => true;
|
|
||||||
|
|
||||||
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
|
|
||||||
{
|
|
||||||
if (context?.Instance is Spine obj)
|
|
||||||
{
|
|
||||||
return new StandardValuesCollection(obj.AnimationNames);
|
|
||||||
}
|
|
||||||
else if (context?.Instance is Spine[] spines)
|
|
||||||
{
|
|
||||||
if (spines.Length > 0)
|
|
||||||
{
|
|
||||||
IEnumerable<string> common = spines[0].AnimationNames;
|
|
||||||
foreach (var spine in spines.Skip(1))
|
|
||||||
common = common.Intersect(spine.AnimationNames);
|
|
||||||
return new StandardValuesCollection(common.ToArray());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return base.GetStandardValues(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SkinConverter : StringConverter
|
public class SkinConverter : StringConverter
|
||||||
{
|
{
|
||||||
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true;
|
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true;
|
||||||
@@ -65,7 +40,100 @@ namespace SpineViewer.Spine
|
|||||||
{
|
{
|
||||||
IEnumerable<string> common = spines[0].SkinNames;
|
IEnumerable<string> common = spines[0].SkinNames;
|
||||||
foreach (var spine in spines.Skip(1))
|
foreach (var spine in spines.Skip(1))
|
||||||
common = common.Intersect(spine.SkinNames);
|
common = common.Union(spine.SkinNames);
|
||||||
|
return new StandardValuesCollection(common.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return base.GetStandardValues(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AnimationConverter : StringConverter
|
||||||
|
{
|
||||||
|
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true;
|
||||||
|
|
||||||
|
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context) => true;
|
||||||
|
|
||||||
|
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
|
||||||
|
{
|
||||||
|
if (context?.Instance is Spine obj)
|
||||||
|
{
|
||||||
|
return new StandardValuesCollection(obj.AnimationNames);
|
||||||
|
}
|
||||||
|
else if (context?.Instance is Spine[] spines)
|
||||||
|
{
|
||||||
|
if (spines.Length > 0)
|
||||||
|
{
|
||||||
|
IEnumerable<string> common = spines[0].AnimationNames;
|
||||||
|
foreach (var spine in spines.Skip(1))
|
||||||
|
common = common.Union(spine.AnimationNames);
|
||||||
|
return new StandardValuesCollection(common.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return base.GetStandardValues(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 轨道索引包装类转换器, 实现字符串和包装类的互相转换, 并且提供标准值列表对属性进行设置, 同时还提供在面板上显示包装类属性的能力
|
||||||
|
/// </summary>
|
||||||
|
public class TrackWrapperConverter : ExpandableObjectConverter
|
||||||
|
{
|
||||||
|
// NOTE: 可以不用实现 ConvertTo/ConvertFrom, 因为属性实现了与字符串之间的互转
|
||||||
|
// ToString 实现了 ConvertTo
|
||||||
|
// SetValue 实现了从字符串设置属性
|
||||||
|
|
||||||
|
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true;
|
||||||
|
|
||||||
|
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context) => true;
|
||||||
|
|
||||||
|
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
|
||||||
|
{
|
||||||
|
if (context.Instance is AnimationTracks tracks)
|
||||||
|
{
|
||||||
|
return new StandardValuesCollection(tracks.Spine.AnimationNames);
|
||||||
|
}
|
||||||
|
else if (context.Instance is object[] instances && instances.All(x => x is AnimationTracks))
|
||||||
|
{
|
||||||
|
// XXX: 这里不知道为啥总是会得到 object[] 类型而不是具体的类型
|
||||||
|
var animTracks = instances.Cast<AnimationTracks>().ToArray();
|
||||||
|
if (animTracks.Length > 0)
|
||||||
|
{
|
||||||
|
IEnumerable<string> common = animTracks[0].Spine.AnimationNames;
|
||||||
|
foreach (var t in animTracks.Skip(1))
|
||||||
|
common = common.Union(t.Spine.AnimationNames);
|
||||||
|
return new StandardValuesCollection(common.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return base.GetStandardValues(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SkinWrapperConverter : StringConverter
|
||||||
|
{
|
||||||
|
// NOTE: 可以不用实现 ConvertTo/ConvertFrom, 因为属性实现了与字符串之间的互转
|
||||||
|
// ToString 实现了 ConvertTo
|
||||||
|
// SetValue 实现了从字符串设置属性
|
||||||
|
|
||||||
|
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true;
|
||||||
|
|
||||||
|
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context) => true;
|
||||||
|
|
||||||
|
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
|
||||||
|
{
|
||||||
|
if (context.Instance is SkinManager manager)
|
||||||
|
{
|
||||||
|
return new StandardValuesCollection(manager.Spine.SkinNames);
|
||||||
|
}
|
||||||
|
else if (context.Instance is object[] instances && instances.All(x => x is SkinManager))
|
||||||
|
{
|
||||||
|
// XXX: 这里不知道为啥总是会得到 object[] 类型而不是具体的 SkinManager[] 类型
|
||||||
|
var managers = instances.Cast<SkinManager>().ToArray();
|
||||||
|
if (managers.Length > 0)
|
||||||
|
{
|
||||||
|
IEnumerable<string> common = managers[0].Spine.SkinNames;
|
||||||
|
foreach (var t in managers.Skip(1))
|
||||||
|
common = common.Union(t.Spine.SkinNames);
|
||||||
return new StandardValuesCollection(common.ToArray());
|
return new StandardValuesCollection(common.ToArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using SpineViewer.Dialogs;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Drawing.Design;
|
using System.Drawing.Design;
|
||||||
@@ -36,4 +37,52 @@ namespace SpineViewer.Spine
|
|||||||
openFileDialog.Filter = "atlas 文件 (*.atlas)|*.atlas|所有文件 (*.*)|*.*";
|
openFileDialog.Filter = "atlas 文件 (*.atlas)|*.atlas|所有文件 (*.*)|*.*";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 多轨道动画编辑器
|
||||||
|
/// </summary>
|
||||||
|
public class AnimationTracksEditor : UITypeEditor
|
||||||
|
{
|
||||||
|
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext? context) => UITypeEditorEditStyle.Modal;
|
||||||
|
|
||||||
|
public override object? EditValue(ITypeDescriptorContext? context, IServiceProvider provider, object? value)
|
||||||
|
{
|
||||||
|
if (provider == null || context == null || context.Instance is not Spine)
|
||||||
|
return value;
|
||||||
|
|
||||||
|
IWindowsFormsEditorService editorService = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
|
||||||
|
if (editorService == null)
|
||||||
|
return value;
|
||||||
|
|
||||||
|
using (var dialog = new AnimationTracksEditorDialog((Spine)context.Instance))
|
||||||
|
editorService.ShowDialog(dialog);
|
||||||
|
|
||||||
|
TypeDescriptor.Refresh(context.Instance);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 多轨道动画编辑器
|
||||||
|
/// </summary>
|
||||||
|
public class SkinManagerEditor : UITypeEditor
|
||||||
|
{
|
||||||
|
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext? context) => UITypeEditorEditStyle.Modal;
|
||||||
|
|
||||||
|
public override object? EditValue(ITypeDescriptorContext? context, IServiceProvider provider, object? value)
|
||||||
|
{
|
||||||
|
if (provider == null || context == null || context.Instance is not Spine)
|
||||||
|
return value;
|
||||||
|
|
||||||
|
IWindowsFormsEditorService editorService = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
|
||||||
|
if (editorService == null)
|
||||||
|
return value;
|
||||||
|
|
||||||
|
using (var dialog = new SkinManagerEditorDialog((Spine)context.Instance))
|
||||||
|
editorService.ShowDialog(dialog);
|
||||||
|
|
||||||
|
TypeDescriptor.Refresh(context.Instance);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<TargetFramework>net8.0-windows</TargetFramework>
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||||
<Version>0.11.5</Version>
|
<Version>0.12.1</Version>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<UseWindowsForms>true</UseWindowsForms>
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
<ApplicationIcon>appicon.ico</ApplicationIcon>
|
<ApplicationIcon>appicon.ico</ApplicationIcon>
|
||||||
|
|||||||
177
SpineViewer/Win32.cs
Normal file
177
SpineViewer/Win32.cs
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SpineViewer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Win32 Sdk 包装类
|
||||||
|
/// </summary>
|
||||||
|
public static class Win32
|
||||||
|
{
|
||||||
|
public const int GWL_STYLE = -16;
|
||||||
|
public const int WS_SIZEBOX = 0x40000;
|
||||||
|
public const int WS_BORDER = 0x800000;
|
||||||
|
public const int WS_POPUP = unchecked((int)0x80000000);
|
||||||
|
|
||||||
|
public const int GWL_EXSTYLE = -20;
|
||||||
|
public const int WS_EX_TOPMOST = 0x8;
|
||||||
|
public const int WS_EX_TRANSPARENT = 0x20;
|
||||||
|
public const int WS_EX_TOOLWINDOW = 0x80;
|
||||||
|
public const int WS_EX_WINDOWEDGE = 0x100;
|
||||||
|
public const int WS_EX_CLIENTEDGE = 0x200;
|
||||||
|
public const int WS_EX_LAYERED = 0x80000;
|
||||||
|
public const int WS_EX_OVERLAPPEDWINDOW = WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE;
|
||||||
|
|
||||||
|
public const uint LWA_COLORKEY = 0x1;
|
||||||
|
public const uint LWA_ALPHA = 0x2;
|
||||||
|
|
||||||
|
public const byte AC_SRC_OVER = 0x00;
|
||||||
|
public const byte AC_SRC_ALPHA = 0x01;
|
||||||
|
|
||||||
|
public const int ULW_COLORKEY = 0x00000001;
|
||||||
|
public const int ULW_ALPHA = 0x00000002;
|
||||||
|
public const int ULW_OPAQUE = 0x00000004;
|
||||||
|
|
||||||
|
public const IntPtr HWND_TOPMOST = -1;
|
||||||
|
|
||||||
|
public const uint SWP_NOSIZE = 0x0001;
|
||||||
|
public const uint SWP_NOMOVE = 0x0002;
|
||||||
|
public const uint SWP_NOZORDER = 0x0004;
|
||||||
|
public const uint SWP_FRAMECHANGED = 0x0020;
|
||||||
|
public const uint SWP_REFRESHLONG = SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED;
|
||||||
|
|
||||||
|
public const int WM_SPAWN_WORKER = 0x052C; // 一个未公开的神秘消息
|
||||||
|
|
||||||
|
public const uint SMTO_NORMAL = 0x0000;
|
||||||
|
public const uint SMTO_BLOCK = 0x0001;
|
||||||
|
public const uint SMTO_ABORTIFHUNG = 0x0002;
|
||||||
|
public const uint SMTO_NOTIMEOUTIFNOTHUNG = 0x0008;
|
||||||
|
|
||||||
|
public const uint GA_PARENT = 1;
|
||||||
|
public const uint GW_OWNER = 4;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct POINT
|
||||||
|
{
|
||||||
|
public int x;
|
||||||
|
public int y;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct SIZE
|
||||||
|
{
|
||||||
|
public int cx;
|
||||||
|
public int cy;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct BLENDFUNCTION
|
||||||
|
{
|
||||||
|
public byte BlendOp;
|
||||||
|
public byte BlendFlags;
|
||||||
|
public byte SourceConstantAlpha;
|
||||||
|
public byte AlphaFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct LASTINPUTINFO
|
||||||
|
{
|
||||||
|
public uint cbSize;
|
||||||
|
public uint dwTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
public static extern IntPtr GetDC(IntPtr hWnd);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
public static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
public static extern bool GetLayeredWindowAttributes(IntPtr hWnd, ref uint crKey, ref byte bAlpha, ref uint dwFlags);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
public static extern bool SetLayeredWindowAttributes(IntPtr hWnd, uint pcrKey, byte pbAlpha, uint pdwFlags);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
public static extern bool UpdateLayeredWindow(IntPtr hWnd, IntPtr hdcDst, IntPtr pptDst, ref SIZE psize, IntPtr hdcSrc, ref POINT pptSrc, int crKey, ref BLENDFUNCTION pblend, int dwFlags);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
public static extern uint GetDoubleClickTime();
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam, uint fuFlags, uint uTimeout, out IntPtr lpdwResult);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, string windowTitle);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
public static extern IntPtr GetParent(IntPtr hWnd);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
public static extern IntPtr GetAncestor(IntPtr hWnd, uint gaFlags);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
public static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
|
||||||
|
|
||||||
|
[DllImport("gdi32.dll", SetLastError = true)]
|
||||||
|
public static extern IntPtr CreateCompatibleDC(IntPtr hdc);
|
||||||
|
|
||||||
|
[DllImport("gdi32.dll", SetLastError = true)]
|
||||||
|
public static extern bool DeleteDC(IntPtr hdc);
|
||||||
|
|
||||||
|
[DllImport("gdi32.dll", SetLastError = true)]
|
||||||
|
public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
|
||||||
|
|
||||||
|
[DllImport("gdi32.dll", SetLastError = true)]
|
||||||
|
public static extern bool DeleteObject(IntPtr hObject);
|
||||||
|
|
||||||
|
public static TimeSpan GetLastInputElapsedTime()
|
||||||
|
{
|
||||||
|
LASTINPUTINFO lastInputInfo = new();
|
||||||
|
lastInputInfo.cbSize = (uint)Marshal.SizeOf(lastInputInfo);
|
||||||
|
|
||||||
|
uint idleTimeMillis = 1000;
|
||||||
|
if (GetLastInputInfo(ref lastInputInfo))
|
||||||
|
{
|
||||||
|
uint tickCount = (uint)Environment.TickCount;
|
||||||
|
uint lastInputTick = lastInputInfo.dwTime;
|
||||||
|
idleTimeMillis = tickCount - lastInputTick;
|
||||||
|
}
|
||||||
|
return TimeSpan.FromMilliseconds(idleTimeMillis);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IntPtr GetWorkerW()
|
||||||
|
{
|
||||||
|
var progman = FindWindow("Progman", null);
|
||||||
|
if (progman == IntPtr.Zero)
|
||||||
|
return IntPtr.Zero;
|
||||||
|
IntPtr hWnd = FindWindowEx(progman, 0, "WorkerW", null);
|
||||||
|
Debug.WriteLine($"{hWnd:x8}");
|
||||||
|
return hWnd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
img/preview.webp
BIN
img/preview.webp
Binary file not shown.
|
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 126 KiB |
Reference in New Issue
Block a user