Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7ace4dfe9 | ||
|
|
7ce2bd5629 | ||
|
|
41716df7b2 | ||
|
|
fe9b9829e2 | ||
|
|
4365c2a008 | ||
|
|
7896e072e7 | ||
|
|
940397c673 | ||
|
|
d57ea781f0 | ||
|
|
b74f2811a7 | ||
|
|
66223f952b | ||
|
|
0443d5e3d5 | ||
|
|
0a0b6a08e9 | ||
|
|
63af4a19e6 | ||
|
|
51f0446c18 | ||
|
|
e965223284 | ||
|
|
dbc15952cc | ||
|
|
93b70509ec | ||
|
|
798883d4e0 | ||
|
|
3e88e65bbd | ||
|
|
0906817cd3 | ||
|
|
37235fa7d0 | ||
|
|
72935d8f2b | ||
|
|
8a4095dae1 | ||
|
|
b59f228f2e | ||
|
|
a28cb3f424 | ||
|
|
05bb797a91 | ||
|
|
eb0029a877 | ||
|
|
ef0bfa85aa | ||
|
|
b5721e30a0 | ||
|
|
2c3b076b58 | ||
|
|
01e12f4524 | ||
|
|
a814d3d99a | ||
|
|
6a4508dceb | ||
|
|
b7d7274a5a | ||
|
|
71359a4328 | ||
|
|
3a3691bcca | ||
|
|
3d649e36cc | ||
|
|
a24db3c447 | ||
|
|
699a055707 | ||
|
|
1f8ed1c31c | ||
|
|
e2fc27663c | ||
|
|
b3cd0b9349 | ||
|
|
1c545b8c37 | ||
|
|
d660dd1c4a | ||
|
|
9c0acf7302 | ||
|
|
415df555c7 |
34
CHANGELOG.md
34
CHANGELOG.md
@@ -1,5 +1,39 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
## v0.15.19
|
||||||
|
|
||||||
|
- 模型重载后选中最后一个重载模型
|
||||||
|
- 修复 3.4 版本可能的奇数顶点数组导致的越界崩溃问题
|
||||||
|
- 移除参数自动记录中的背景图片路径
|
||||||
|
- 增加测试性桌面投影功能
|
||||||
|
|
||||||
|
## v0.15.18
|
||||||
|
|
||||||
|
- 完善窗口日志颜色标记
|
||||||
|
- 修复预览图背景颜色为透明
|
||||||
|
- 修复面板高度首次还原错误
|
||||||
|
- 增加托盘图标
|
||||||
|
- 增加可选预览背景画面和填充模式
|
||||||
|
- 增强支持的纹理格式(例如 webp)
|
||||||
|
|
||||||
|
## v0.15.17
|
||||||
|
|
||||||
|
- 修改图标配色
|
||||||
|
|
||||||
|
## v0.15.16
|
||||||
|
|
||||||
|
- 修改模型添加顺序, 每次向顶层添加
|
||||||
|
- 添加模型后自动选中最近添加的模型S
|
||||||
|
- 点击预览画面或者选中项发生变化时转移焦点至列表
|
||||||
|
- 增加移除全部菜单项
|
||||||
|
- 增加单例模式和命令行文件参数
|
||||||
|
- 增加文件关联设置
|
||||||
|
|
||||||
|
## v0.15.15
|
||||||
|
|
||||||
|
- 增加报错信息
|
||||||
|
- 导入后自动选中最后一项
|
||||||
|
|
||||||
## v0.15.14
|
## v0.15.14
|
||||||
|
|
||||||
- 将预览画面的首选项移动至上一次状态参数中
|
- 将预览画面的首选项移动至上一次状态参数中
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
@@ -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.15.4</Version>
|
<Version>0.15.18</Version>
|
||||||
<UseWPF>true</UseWPF>
|
<UseWPF>true</UseWPF>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -1,92 +1,53 @@
|
|||||||
//
|
using NLog;
|
||||||
// Copyright (c) 2004-2011 Jaroslaw Kowalski <jaak@jkowalski.net>
|
|
||||||
//
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
|
||||||
// modification, are permitted provided that the following conditions
|
|
||||||
// are met:
|
|
||||||
//
|
|
||||||
// * Redistributions of source code must retain the above copyright notice,
|
|
||||||
// this list of conditions and the following disclaimer.
|
|
||||||
//
|
|
||||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
// this list of conditions and the following disclaimer in the documentation
|
|
||||||
// and/or other materials provided with the distribution.
|
|
||||||
//
|
|
||||||
// * Neither the name of Jaroslaw Kowalski nor the names of its
|
|
||||||
// contributors may be used to endorse or promote products derived from this
|
|
||||||
// software without specific prior written permission.
|
|
||||||
//
|
|
||||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
||||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
||||||
// THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
//
|
|
||||||
|
|
||||||
using NLog.Conditions;
|
using NLog.Conditions;
|
||||||
using NLog.Config;
|
using NLog.Config;
|
||||||
using NLog;
|
using NLog.Layouts;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
|
||||||
namespace NLog.Windows.Wpf
|
namespace NLog.Windows.Wpf
|
||||||
{
|
{
|
||||||
[NLogConfigurationItem]
|
[NLogConfigurationItem]
|
||||||
public class RichTextBoxRowColoringRule
|
public class RichTextBoxRowColoringRule
|
||||||
{
|
{
|
||||||
static RichTextBoxRowColoringRule()
|
|
||||||
{
|
|
||||||
Default = new RichTextBoxRowColoringRule();
|
|
||||||
}
|
|
||||||
|
|
||||||
public RichTextBoxRowColoringRule()
|
|
||||||
: this(null, "Empty", "Empty", FontStyles.Normal, FontWeights.Normal)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public RichTextBoxRowColoringRule(string condition, string fontColor, string backColor, FontStyle fontStyle, FontWeight fontWeight)
|
|
||||||
{
|
|
||||||
Condition = condition;
|
|
||||||
FontColor = fontColor;
|
|
||||||
BackgroundColor = backColor;
|
|
||||||
Style = fontStyle;
|
|
||||||
Weight = fontWeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RichTextBoxRowColoringRule(string condition, string fontColor, string backColor)
|
|
||||||
{
|
|
||||||
Condition = condition;
|
|
||||||
FontColor = fontColor;
|
|
||||||
BackgroundColor = backColor;
|
|
||||||
Style = FontStyles.Normal;
|
|
||||||
Weight = FontWeights.Normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RichTextBoxRowColoringRule Default { get; private set; }
|
public static RichTextBoxRowColoringRule Default { get; private set; }
|
||||||
|
|
||||||
[RequiredParameter]
|
[RequiredParameter]
|
||||||
public ConditionExpression Condition { get; set; }
|
public ConditionExpression Condition { get; set; }
|
||||||
|
|
||||||
[DefaultValue("Empty")]
|
public Layout FontColor { get; set; }
|
||||||
public string FontColor { get; set; }
|
public Layout BackgroundColor { get; set; }
|
||||||
|
|
||||||
[DefaultValue("Empty")]
|
public FontStyle FontStyle { get; set; }
|
||||||
public string BackgroundColor { get; set; }
|
public FontWeight FontWeight { get; set; }
|
||||||
|
|
||||||
public FontStyle Style { get; set; }
|
static RichTextBoxRowColoringRule()
|
||||||
|
{
|
||||||
|
RichTextBoxRowColoringRule.Default = new RichTextBoxRowColoringRule();
|
||||||
|
}
|
||||||
|
|
||||||
public FontWeight Weight { get; set; }
|
public RichTextBoxRowColoringRule() : this(null, "Empty", "Empty", FontStyles.Normal, FontWeights.Normal) { }
|
||||||
|
|
||||||
|
public RichTextBoxRowColoringRule(string condition, string fontColor, string backColor)
|
||||||
|
{
|
||||||
|
this.Condition = (ConditionExpression)condition;
|
||||||
|
this.FontColor = Layout.FromString(fontColor);
|
||||||
|
this.BackgroundColor = Layout.FromString(backColor);
|
||||||
|
this.FontStyle = FontStyles.Normal;
|
||||||
|
this.FontWeight = FontWeights.Normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RichTextBoxRowColoringRule(string condition, string fontColor, string backColor, FontStyle fontStyle, FontWeight fontWeight)
|
||||||
|
{
|
||||||
|
this.Condition = (ConditionExpression)condition;
|
||||||
|
this.FontColor = Layout.FromString(fontColor);
|
||||||
|
this.BackgroundColor = Layout.FromString(backColor);
|
||||||
|
this.FontStyle = fontStyle;
|
||||||
|
this.FontWeight = fontWeight;
|
||||||
|
}
|
||||||
|
|
||||||
public bool CheckCondition(LogEventInfo logEvent)
|
public bool CheckCondition(LogEventInfo logEvent)
|
||||||
{
|
{
|
||||||
return true.Equals(Condition.Evaluate(logEvent));
|
return true.Equals(this.Condition.Evaluate(logEvent));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,27 @@
|
|||||||
using NLog.Config;
|
using NLog;
|
||||||
using NLog.Layouts;
|
using NLog.Common;
|
||||||
|
using NLog.Config;
|
||||||
using NLog.Targets;
|
using NLog.Targets;
|
||||||
using NLog;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Documents;
|
using System.Windows.Documents;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows;
|
|
||||||
|
|
||||||
namespace NLog.Windows.Wpf
|
namespace NLog.Windows.Wpf
|
||||||
{
|
{
|
||||||
// TODO: 完善日志实现
|
|
||||||
[Target("RichTextBox")]
|
[Target("RichTextBox")]
|
||||||
public sealed class RichTextBoxTarget : TargetWithLayout
|
public sealed class RichTextBoxTarget : TargetWithLayout
|
||||||
{
|
{
|
||||||
private int lineCount;
|
public static ReadOnlyCollection<RichTextBoxRowColoringRule> DefaultRowColoringRules { get; } = CreateDefaultColoringRules();
|
||||||
private int _width = 500;
|
|
||||||
private int _height = 500;
|
|
||||||
private static readonly TypeConverter colorConverter = new ColorConverter();
|
|
||||||
|
|
||||||
static RichTextBoxTarget()
|
private static ReadOnlyCollection<RichTextBoxRowColoringRule> CreateDefaultColoringRules()
|
||||||
{
|
{
|
||||||
var rules = new List<RichTextBoxRowColoringRule>()
|
return new List<RichTextBoxRowColoringRule>()
|
||||||
{
|
{
|
||||||
new RichTextBoxRowColoringRule("level == LogLevel.Fatal", "White", "Red", FontStyles.Normal, FontWeights.Bold),
|
new RichTextBoxRowColoringRule("level == LogLevel.Fatal", "White", "Red", FontStyles.Normal, FontWeights.Bold),
|
||||||
new RichTextBoxRowColoringRule("level == LogLevel.Error", "Red", "Empty", FontStyles.Italic, FontWeights.Bold),
|
new RichTextBoxRowColoringRule("level == LogLevel.Error", "Red", "Empty", FontStyles.Italic, FontWeights.Bold),
|
||||||
@@ -36,221 +29,253 @@ namespace NLog.Windows.Wpf
|
|||||||
new RichTextBoxRowColoringRule("level == LogLevel.Info", "Black", "Empty"),
|
new RichTextBoxRowColoringRule("level == LogLevel.Info", "Black", "Empty"),
|
||||||
new RichTextBoxRowColoringRule("level == LogLevel.Debug", "Gray", "Empty"),
|
new RichTextBoxRowColoringRule("level == LogLevel.Debug", "Gray", "Empty"),
|
||||||
new RichTextBoxRowColoringRule("level == LogLevel.Trace", "DarkGray", "Empty", FontStyles.Italic, FontWeights.Normal),
|
new RichTextBoxRowColoringRule("level == LogLevel.Trace", "DarkGray", "Empty", FontStyles.Italic, FontWeights.Normal),
|
||||||
};
|
}.AsReadOnly();
|
||||||
|
|
||||||
DefaultRowColoringRules = rules.AsReadOnly();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RichTextBoxTarget()
|
public RichTextBoxTarget() { }
|
||||||
{
|
|
||||||
WordColoringRules = new List<RichTextBoxWordColoringRule>();
|
|
||||||
RowColoringRules = new List<RichTextBoxRowColoringRule>();
|
|
||||||
ToolWindow = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private delegate void DelSendTheMessageToRichTextBox(string logMessage, RichTextBoxRowColoringRule rule);
|
|
||||||
|
|
||||||
private delegate void FormCloseDelegate();
|
|
||||||
|
|
||||||
public static ReadOnlyCollection<RichTextBoxRowColoringRule> DefaultRowColoringRules { get; private set; }
|
|
||||||
|
|
||||||
public string ControlName { get; set; }
|
public string ControlName { get; set; }
|
||||||
|
|
||||||
public string FormName { get; set; }
|
public string WindowName { get; set; }
|
||||||
|
|
||||||
[DefaultValue(false)]
|
|
||||||
public bool UseDefaultRowColoringRules { get; set; }
|
public bool UseDefaultRowColoringRules { get; set; }
|
||||||
|
|
||||||
[ArrayParameter(typeof(RichTextBoxRowColoringRule), "row-coloring")]
|
|
||||||
public IList<RichTextBoxRowColoringRule> RowColoringRules { get; private set; }
|
|
||||||
|
|
||||||
[ArrayParameter(typeof(RichTextBoxWordColoringRule), "word-coloring")]
|
|
||||||
public IList<RichTextBoxWordColoringRule> WordColoringRules { get; private set; }
|
|
||||||
|
|
||||||
[DefaultValue(true)]
|
|
||||||
public bool ToolWindow { get; set; }
|
|
||||||
|
|
||||||
public bool ShowMinimized { get; set; }
|
|
||||||
|
|
||||||
public int Width
|
|
||||||
{
|
|
||||||
get { return _width; }
|
|
||||||
set { _width = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Height
|
|
||||||
{
|
|
||||||
get { return _height; }
|
|
||||||
set { _height = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool AutoScroll { get; set; }
|
public bool AutoScroll { get; set; }
|
||||||
|
|
||||||
public int MaxLines { get; set; }
|
public int MaxLines { get; set; }
|
||||||
|
|
||||||
internal Window TargetForm { get; set; }
|
[ArrayParameter(typeof(RichTextBoxRowColoringRule), "row-coloring")]
|
||||||
|
public IList<RichTextBoxRowColoringRule> RowColoringRules { get; } = new List<RichTextBoxRowColoringRule>();
|
||||||
|
|
||||||
internal RichTextBox TargetRichTextBox { get; set; }
|
[ArrayParameter(typeof(RichTextBoxWordColoringRule), "word-coloring")]
|
||||||
|
public IList<RichTextBoxWordColoringRule> WordColoringRules { get; } = new List<RichTextBoxWordColoringRule>();
|
||||||
|
|
||||||
internal bool CreatedForm { get; set; }
|
[NLogConfigurationIgnoreProperty]
|
||||||
|
public Window TargetWindow { get; set; }
|
||||||
|
|
||||||
|
[NLogConfigurationIgnoreProperty]
|
||||||
|
public RichTextBox TargetRichTextBox { get; set; }
|
||||||
|
|
||||||
protected override void InitializeTarget()
|
protected override void InitializeTarget()
|
||||||
{
|
{
|
||||||
TargetRichTextBox = Application.Current.MainWindow.FindName(ControlName) as RichTextBox;
|
base.InitializeTarget();
|
||||||
|
if (TargetRichTextBox != null)
|
||||||
|
return;
|
||||||
|
|
||||||
if (TargetRichTextBox != null) return;
|
if (WindowName == null)
|
||||||
//this.TargetForm = FormHelper.CreateForm(this.FormName, this.Width, this.Height, false, this.ShowMinimized, this.ToolWindow);
|
|
||||||
//this.CreatedForm = true;
|
|
||||||
|
|
||||||
var openFormByName = Application.Current.Windows.Cast<Window>().FirstOrDefault(x => x.GetType().Name == FormName);
|
|
||||||
if (openFormByName != null)
|
|
||||||
{
|
{
|
||||||
TargetForm = openFormByName;
|
HandleError("WindowName should be specified for {0}.{1}", GetType().Name, Name);
|
||||||
if (string.IsNullOrEmpty(ControlName))
|
return;
|
||||||
{
|
|
||||||
// throw new NLogConfigurationException("Rich text box control name must be specified for " + GetType().Name + ".");
|
|
||||||
Trace.WriteLine("Rich text box control name must be specified for " + GetType().Name + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
CreatedForm = false;
|
|
||||||
TargetRichTextBox = TargetForm.FindName(ControlName) as RichTextBox;
|
|
||||||
|
|
||||||
if (TargetRichTextBox == null)
|
|
||||||
{
|
|
||||||
// throw new NLogConfigurationException("Rich text box control '" + ControlName + "' cannot be found on form '" + FormName + "'.");
|
|
||||||
Trace.WriteLine("Rich text box control '" + ControlName + "' cannot be found on form '" + FormName + "'.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TargetRichTextBox == null)
|
if (string.IsNullOrEmpty(ControlName))
|
||||||
{
|
{
|
||||||
TargetForm = new Window
|
HandleError("Rich text box control name must be specified for {0}.{1}", GetType().Name, Name);
|
||||||
{
|
return;
|
||||||
Name = FormName,
|
|
||||||
Width = Width,
|
|
||||||
Height = Height,
|
|
||||||
WindowStyle = ToolWindow ? WindowStyle.ToolWindow : WindowStyle.None,
|
|
||||||
WindowState = ShowMinimized ? WindowState.Minimized : WindowState.Normal,
|
|
||||||
Title = "NLog Messages"
|
|
||||||
};
|
|
||||||
TargetForm.Show();
|
|
||||||
|
|
||||||
TargetRichTextBox = new RichTextBox { Name = ControlName };
|
|
||||||
var style = new Style(typeof(Paragraph));
|
|
||||||
TargetRichTextBox.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
|
|
||||||
style.Setters.Add(new Setter(Block.MarginProperty, new Thickness(0, 0, 0, 0)));
|
|
||||||
TargetRichTextBox.Resources.Add(typeof(Paragraph), style);
|
|
||||||
TargetForm.Content = TargetRichTextBox;
|
|
||||||
|
|
||||||
CreatedForm = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var targetWindow = Application.Current.Windows.OfType<Window>().FirstOrDefault(w => w.Name == WindowName);
|
||||||
|
if (targetWindow == null)
|
||||||
|
{
|
||||||
|
InternalLogger.Info("{0}: WindowName '{1}' not found", this, WindowName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetControl = targetWindow.FindName(ControlName) as RichTextBox;
|
||||||
|
if (targetControl == null)
|
||||||
|
{
|
||||||
|
InternalLogger.Info("{0}: WIndowName '{1}' does not contain ControlName '{2}'", this, WindowName, ControlName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachToControl(targetWindow, targetControl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void HandleError(string message, params object[] args)
|
||||||
|
{
|
||||||
|
if (LogManager.ThrowExceptions)
|
||||||
|
{
|
||||||
|
throw new NLogConfigurationException(string.Format(message, args));
|
||||||
|
}
|
||||||
|
InternalLogger.Error(message, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AttachToControl(Window window, RichTextBox textboxControl)
|
||||||
|
{
|
||||||
|
InternalLogger.Info("{0}: Attaching target to textbox {1}.{2}", this, window.Name, textboxControl.Name);
|
||||||
|
DetachFromControl();
|
||||||
|
TargetWindow = window;
|
||||||
|
TargetRichTextBox = textboxControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DetachFromControl()
|
||||||
|
{
|
||||||
|
TargetWindow = null;
|
||||||
|
TargetRichTextBox = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void CloseTarget()
|
protected override void CloseTarget()
|
||||||
{
|
{
|
||||||
if (CreatedForm)
|
DetachFromControl();
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
TargetForm.Dispatcher.Invoke(() =>
|
|
||||||
{
|
|
||||||
TargetForm.Close();
|
|
||||||
TargetForm = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Write(LogEventInfo logEvent)
|
protected override void Write(LogEventInfo logEvent)
|
||||||
{
|
{
|
||||||
RichTextBoxRowColoringRule matchingRule = RowColoringRules.FirstOrDefault(rr => rr.CheckCondition(logEvent));
|
RichTextBox textbox = TargetRichTextBox;
|
||||||
|
if (textbox == null || textbox.Dispatcher.HasShutdownStarted || textbox.Dispatcher.HasShutdownFinished)
|
||||||
if (UseDefaultRowColoringRules && matchingRule == null)
|
|
||||||
{
|
{
|
||||||
foreach (var rr in DefaultRowColoringRules.Where(rr => rr.CheckCondition(logEvent)))
|
//no last logged textbox
|
||||||
{
|
InternalLogger.Trace("{0}: Attached Textbox is {1}, skipping logging", this, textbox == null ? "null" : "disposed");
|
||||||
matchingRule = rr;
|
return;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchingRule == null)
|
string logMessage = RenderLogEvent(Layout, logEvent);
|
||||||
{
|
RichTextBoxRowColoringRule matchingRule = FindMatchingRule(logEvent);
|
||||||
matchingRule = RichTextBoxRowColoringRule.Default;
|
_ = DoSendMessageToTextbox(logMessage, matchingRule, logEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
var logMessage = Layout.Render(logEvent);
|
|
||||||
|
|
||||||
if (Application.Current == null) return;
|
|
||||||
|
|
||||||
|
private bool DoSendMessageToTextbox(string logMessage, RichTextBoxRowColoringRule rule, LogEventInfo logEvent)
|
||||||
|
{
|
||||||
|
RichTextBox textbox = TargetRichTextBox;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Application.Current.Dispatcher.CheckAccess() == false)
|
if (textbox != null && !textbox.Dispatcher.HasShutdownStarted && !textbox.Dispatcher.HasShutdownFinished)
|
||||||
{
|
{
|
||||||
Application.Current.Dispatcher.Invoke(() => SendTheMessageToRichTextBox(logMessage, matchingRule));
|
if (!textbox.Dispatcher.CheckAccess())
|
||||||
}
|
{
|
||||||
else
|
textbox.Dispatcher.BeginInvoke(() => SendTheMessageToRichTextBox(textbox, logMessage, rule, logEvent));
|
||||||
{
|
}
|
||||||
SendTheMessageToRichTextBox(logMessage, matchingRule);
|
else
|
||||||
|
{
|
||||||
|
SendTheMessageToRichTextBox(textbox, logMessage, rule, logEvent);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.WriteLine(ex);
|
InternalLogger.Warn(ex, "{0}: Failed to append RichTextBox", this);
|
||||||
}
|
|
||||||
|
|
||||||
}
|
if (LogManager.ThrowExceptions)
|
||||||
|
|
||||||
|
|
||||||
private static Color GetColorFromString(string color, Brush defaultColor)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (color == "Empty")
|
|
||||||
{
|
|
||||||
return defaultColor is SolidColorBrush solidBrush ? solidBrush.Color : Colors.White;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (Color)colorConverter.ConvertFromString(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void SendTheMessageToRichTextBox(string logMessage, RichTextBoxRowColoringRule rule)
|
|
||||||
{
|
|
||||||
RichTextBox rtbx = TargetRichTextBox;
|
|
||||||
|
|
||||||
var tr = new TextRange(rtbx.Document.ContentEnd, rtbx.Document.ContentEnd);
|
|
||||||
tr.Text = logMessage + "\n";
|
|
||||||
tr.ApplyPropertyValue(TextElement.ForegroundProperty,
|
|
||||||
new SolidColorBrush(GetColorFromString(rule.FontColor, (Brush)tr.GetPropertyValue(TextElement.ForegroundProperty)))
|
|
||||||
);
|
|
||||||
tr.ApplyPropertyValue(TextElement.BackgroundProperty,
|
|
||||||
new SolidColorBrush(GetColorFromString(rule.BackgroundColor, (Brush)tr.GetPropertyValue(TextElement.BackgroundProperty)))
|
|
||||||
);
|
|
||||||
tr.ApplyPropertyValue(TextElement.FontStyleProperty, rule.Style);
|
|
||||||
tr.ApplyPropertyValue(TextElement.FontWeightProperty, rule.Weight);
|
|
||||||
|
|
||||||
|
|
||||||
if (MaxLines > 0)
|
|
||||||
{
|
|
||||||
lineCount++;
|
|
||||||
if (lineCount > MaxLines)
|
|
||||||
{
|
{
|
||||||
tr = new TextRange(rtbx.Document.ContentStart, rtbx.Document.ContentEnd);
|
throw;
|
||||||
tr.Text.Remove(0, tr.Text.IndexOf('\n'));
|
}
|
||||||
lineCount--;
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RichTextBoxRowColoringRule FindMatchingRule(LogEventInfo logEvent)
|
||||||
|
{
|
||||||
|
//custom rules first
|
||||||
|
if (RowColoringRules.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (RichTextBoxRowColoringRule coloringRule in RowColoringRules)
|
||||||
|
{
|
||||||
|
if (coloringRule.CheckCondition(logEvent))
|
||||||
|
{
|
||||||
|
return coloringRule;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (UseDefaultRowColoringRules && DefaultRowColoringRules != null)
|
||||||
|
{
|
||||||
|
foreach (RichTextBoxRowColoringRule coloringRule in DefaultRowColoringRules)
|
||||||
|
{
|
||||||
|
if (coloringRule.CheckCondition(logEvent))
|
||||||
|
{
|
||||||
|
return coloringRule;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return RichTextBoxRowColoringRule.Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendTheMessageToRichTextBox(RichTextBox textBox, string logMessage, RichTextBoxRowColoringRule rule, LogEventInfo logEvent)
|
||||||
|
{
|
||||||
|
if (textBox == null) return;
|
||||||
|
|
||||||
|
var document = textBox.Document;
|
||||||
|
|
||||||
|
// 插入文本(带换行)
|
||||||
|
var tr = new TextRange(document.ContentEnd, document.ContentEnd)
|
||||||
|
{
|
||||||
|
Text = logMessage + Environment.NewLine
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置行级样式
|
||||||
|
var fgColor = rule.FontColor?.Render(logEvent);
|
||||||
|
var bgColor = rule.BackgroundColor?.Render(logEvent);
|
||||||
|
|
||||||
|
tr.ApplyPropertyValue(TextElement.ForegroundProperty,
|
||||||
|
string.IsNullOrEmpty(fgColor) || fgColor == "Empty"
|
||||||
|
? textBox.Foreground
|
||||||
|
: new SolidColorBrush((Color)ColorConverter.ConvertFromString(fgColor)));
|
||||||
|
|
||||||
|
tr.ApplyPropertyValue(TextElement.BackgroundProperty,
|
||||||
|
string.IsNullOrEmpty(bgColor) || bgColor == "Empty"
|
||||||
|
? Brushes.Transparent
|
||||||
|
: new SolidColorBrush((Color)ColorConverter.ConvertFromString(bgColor)));
|
||||||
|
|
||||||
|
tr.ApplyPropertyValue(TextElement.FontStyleProperty, rule.FontStyle);
|
||||||
|
tr.ApplyPropertyValue(TextElement.FontWeightProperty, rule.FontWeight);
|
||||||
|
|
||||||
|
// Word coloring(在刚插入的范围内做匹配)
|
||||||
|
if (WordColoringRules.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var wordRule in WordColoringRules)
|
||||||
|
{
|
||||||
|
var pattern = wordRule.Regex?.Render(logEvent) ?? string.Empty;
|
||||||
|
var text = wordRule.Text?.Render(logEvent) ?? string.Empty;
|
||||||
|
var wholeWords = wordRule.WholeWords.RenderValue(logEvent);
|
||||||
|
var ignoreCase = wordRule.IgnoreCase.RenderValue(logEvent);
|
||||||
|
|
||||||
|
var regex = wordRule.ResolveRegEx(pattern, text, wholeWords, ignoreCase);
|
||||||
|
var matches = regex.Matches(tr.Text);
|
||||||
|
|
||||||
|
foreach (Match match in matches)
|
||||||
|
{
|
||||||
|
// 匹配到的部分范围
|
||||||
|
var start = tr.Start.GetPositionAtOffset(match.Index, LogicalDirection.Forward);
|
||||||
|
var endPos = tr.Start.GetPositionAtOffset(match.Index + match.Length, LogicalDirection.Backward);
|
||||||
|
if (start == null || endPos == null) continue;
|
||||||
|
|
||||||
|
var wordRange = new TextRange(start, endPos);
|
||||||
|
|
||||||
|
var wordFg = wordRule.FontColor?.Render(logEvent);
|
||||||
|
var wordBg = wordRule.BackgroundColor?.Render(logEvent);
|
||||||
|
|
||||||
|
wordRange.ApplyPropertyValue(TextElement.ForegroundProperty,
|
||||||
|
string.IsNullOrEmpty(wordFg) || wordFg == "Empty"
|
||||||
|
? tr.GetPropertyValue(TextElement.ForegroundProperty)
|
||||||
|
: new SolidColorBrush((Color)ColorConverter.ConvertFromString(wordFg)));
|
||||||
|
|
||||||
|
wordRange.ApplyPropertyValue(TextElement.BackgroundProperty,
|
||||||
|
string.IsNullOrEmpty(wordBg) || wordBg == "Empty"
|
||||||
|
? tr.GetPropertyValue(TextElement.BackgroundProperty)
|
||||||
|
: new SolidColorBrush((Color)ColorConverter.ConvertFromString(wordBg)));
|
||||||
|
|
||||||
|
wordRange.ApplyPropertyValue(TextElement.FontStyleProperty, wordRule.FontStyle);
|
||||||
|
wordRange.ApplyPropertyValue(TextElement.FontWeightProperty, wordRule.FontWeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 限制最大行数
|
||||||
|
if (MaxLines > 0)
|
||||||
|
{
|
||||||
|
while (document.Blocks.Count > MaxLines)
|
||||||
|
{
|
||||||
|
document.Blocks.Remove(document.Blocks.FirstBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自动滚动到最后
|
||||||
if (AutoScroll)
|
if (AutoScroll)
|
||||||
{
|
{
|
||||||
rtbx.ScrollToEnd();
|
textBox.ScrollToEnd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,119 +1,59 @@
|
|||||||
//
|
using NLog.Config;
|
||||||
// Copyright (c) 2004-2011 Jaroslaw Kowalski <jaak@jkowalski.net>
|
using NLog.Layouts;
|
||||||
//
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
|
||||||
// modification, are permitted provided that the following conditions
|
|
||||||
// are met:
|
|
||||||
//
|
|
||||||
// * Redistributions of source code must retain the above copyright notice,
|
|
||||||
// this list of conditions and the following disclaimer.
|
|
||||||
//
|
|
||||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
// this list of conditions and the following disclaimer in the documentation
|
|
||||||
// and/or other materials provided with the distribution.
|
|
||||||
//
|
|
||||||
// * Neither the name of Jaroslaw Kowalski nor the names of its
|
|
||||||
// contributors may be used to endorse or promote products derived from this
|
|
||||||
// software without specific prior written permission.
|
|
||||||
//
|
|
||||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
||||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
||||||
// THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using NLog.Config;
|
|
||||||
|
|
||||||
namespace NLog.Windows.Wpf
|
namespace NLog.Windows.Wpf
|
||||||
{
|
{
|
||||||
[NLogConfigurationItem]
|
[NLogConfigurationItem]
|
||||||
public class RichTextBoxWordColoringRule
|
public class RichTextBoxWordColoringRule
|
||||||
{
|
{
|
||||||
private Regex compiledRegex;
|
public Layout Regex { get; set; }
|
||||||
|
public Layout Text { get; set; }
|
||||||
|
public Layout<bool> WholeWords { get; set; }
|
||||||
|
public Layout<bool> IgnoreCase { get; set; }
|
||||||
|
|
||||||
public RichTextBoxWordColoringRule()
|
public Layout FontColor { get; set; }
|
||||||
|
public Layout BackgroundColor { get; set; }
|
||||||
|
|
||||||
|
public FontStyle FontStyle { get; set; }
|
||||||
|
public FontWeight FontWeight { get; set; }
|
||||||
|
|
||||||
|
internal Regex ResolveRegEx(string pattern, string text, bool wholeWords, bool ignoreCase)
|
||||||
{
|
{
|
||||||
FontColor = "Empty";
|
if (string.IsNullOrEmpty(pattern) && text != null)
|
||||||
BackgroundColor = "Empty";
|
{
|
||||||
|
pattern = System.Text.RegularExpressions.Regex.Escape(text);
|
||||||
|
if (wholeWords)
|
||||||
|
pattern = "\b" + pattern + "\b";
|
||||||
|
}
|
||||||
|
|
||||||
|
RegexOptions options = RegexOptions.None;
|
||||||
|
if (ignoreCase)
|
||||||
|
options |= RegexOptions.IgnoreCase;
|
||||||
|
|
||||||
|
return new Regex(pattern, options); // RegEx-Cache
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RichTextBoxWordColoringRule() : this(null, "Empty", "Empty", FontStyles.Normal, FontWeights.Normal) { }
|
||||||
|
|
||||||
public RichTextBoxWordColoringRule(string text, string fontColor, string backgroundColor)
|
public RichTextBoxWordColoringRule(string text, string fontColor, string backgroundColor)
|
||||||
{
|
{
|
||||||
Text = text;
|
this.Text = text;
|
||||||
FontColor = fontColor;
|
this.FontColor = Layout.FromString(fontColor);
|
||||||
BackgroundColor = backgroundColor;
|
this.BackgroundColor = Layout.FromString(backgroundColor);
|
||||||
Style = FontStyles.Normal;
|
this.FontStyle = FontStyles.Normal;
|
||||||
Weight = FontWeights.Normal;
|
this.FontWeight = FontWeights.Normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RichTextBoxWordColoringRule(string text, string textColor, string backgroundColor, FontStyle fontStyle, FontWeight fontWeight)
|
public RichTextBoxWordColoringRule(string text, string textColor, string backgroundColor, FontStyle fontStyle, FontWeight fontWeight)
|
||||||
{
|
{
|
||||||
Text = text;
|
this.Text = text;
|
||||||
FontColor = textColor;
|
this.FontColor = Layout.FromString(textColor);
|
||||||
BackgroundColor = backgroundColor;
|
this.BackgroundColor = Layout.FromString(backgroundColor);
|
||||||
Style = fontStyle;
|
this.FontStyle = fontStyle;
|
||||||
Weight = fontWeight;
|
this.FontWeight = fontWeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Regex { get; set; }
|
|
||||||
|
|
||||||
public string Text { get; set; }
|
|
||||||
|
|
||||||
[DefaultValue(false)]
|
|
||||||
public bool WholeWords { get; set; }
|
|
||||||
|
|
||||||
[DefaultValue(false)]
|
|
||||||
public bool IgnoreCase { get; set; }
|
|
||||||
|
|
||||||
public FontStyle Style { get; set; }
|
|
||||||
|
|
||||||
public FontWeight Weight { get; set; }
|
|
||||||
|
|
||||||
public Regex CompiledRegex
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (compiledRegex == null)
|
|
||||||
{
|
|
||||||
string regexpression = Regex;
|
|
||||||
if (regexpression == null && Text != null)
|
|
||||||
{
|
|
||||||
regexpression = System.Text.RegularExpressions.Regex.Escape(Text);
|
|
||||||
if (WholeWords)
|
|
||||||
{
|
|
||||||
regexpression = "\b" + regexpression + "\b";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RegexOptions regexOptions = RegexOptions.Compiled;
|
|
||||||
if (IgnoreCase)
|
|
||||||
{
|
|
||||||
regexOptions |= RegexOptions.IgnoreCase;
|
|
||||||
}
|
|
||||||
|
|
||||||
compiledRegex = new Regex(regexpression, regexOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
return compiledRegex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DefaultValue("Empty")]
|
|
||||||
public string FontColor { get; set; }
|
|
||||||
|
|
||||||
[DefaultValue("Empty")]
|
|
||||||
public string BackgroundColor { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ A simple and user-friendly Spine file viewer and exporter with multi-language su
|
|||||||
* Automatic resolution batch export.
|
* Automatic resolution batch export.
|
||||||
* FFmpeg custom export support.
|
* FFmpeg custom export support.
|
||||||
* Program parameter saving.
|
* Program parameter saving.
|
||||||
|
* File name extension association.
|
||||||
|
* Supports texture image formats other than PNG.
|
||||||
* ...
|
* ...
|
||||||
|
|
||||||
### Supported Spine Versions
|
### Supported Spine Versions
|
||||||
|
|||||||
@@ -28,6 +28,8 @@
|
|||||||
- 支持自动分辨率批量导出
|
- 支持自动分辨率批量导出
|
||||||
- 支持 FFmpeg 自定义导出
|
- 支持 FFmpeg 自定义导出
|
||||||
- 支持程序参数保存
|
- 支持程序参数保存
|
||||||
|
- 支持文件后缀关联
|
||||||
|
- 支持非 png 格式的纹理图片格式
|
||||||
- ......
|
- ......
|
||||||
|
|
||||||
### Spine 版本支持
|
### Spine 版本支持
|
||||||
|
|||||||
@@ -64,10 +64,10 @@ namespace SFMLRenderer
|
|||||||
hs?.Dispose();
|
hs?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private nint HwndMessageHook(nint hwnd, int msg, nint wParam, nint lParam, ref bool handled)
|
private IntPtr HwndMessageHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
||||||
{
|
{
|
||||||
_renderWindow?.DispatchEvents();
|
_renderWindow?.DispatchEvents();
|
||||||
return nint.Zero;
|
return IntPtr.Zero;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
171
SFMLRenderer/SFMLRenderWindow.cs
Normal file
171
SFMLRenderer/SFMLRenderWindow.cs
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
using SFML.Graphics;
|
||||||
|
using SFML.System;
|
||||||
|
using SFML.Window;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
|
namespace SFMLRenderer
|
||||||
|
{
|
||||||
|
public class SFMLRenderWindow : RenderWindow, ISFMLRenderer
|
||||||
|
{
|
||||||
|
private readonly DispatcherTimer _timer = new() { Interval = TimeSpan.FromMilliseconds(10) };
|
||||||
|
|
||||||
|
public SFMLRenderWindow(VideoMode mode, string title, Styles style) : base(mode, title, style)
|
||||||
|
{
|
||||||
|
SetActive(false);
|
||||||
|
_timer.Tick += (s, e) => DispatchEvents();
|
||||||
|
_timer.Start();
|
||||||
|
RendererCreated?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler? RendererCreated;
|
||||||
|
|
||||||
|
public event EventHandler? RendererDisposing
|
||||||
|
{
|
||||||
|
add => throw new NotImplementedException();
|
||||||
|
remove => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<MouseMoveEventArgs>? CanvasMouseMove
|
||||||
|
{
|
||||||
|
add { MouseMoved += value; }
|
||||||
|
remove { MouseMoved -= value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<MouseButtonEventArgs>? CanvasMouseButtonPressed
|
||||||
|
{
|
||||||
|
add { MouseButtonPressed += value; }
|
||||||
|
remove { MouseButtonPressed -= value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<MouseButtonEventArgs>? CanvasMouseButtonReleased
|
||||||
|
{
|
||||||
|
add { MouseButtonReleased += value; }
|
||||||
|
remove { MouseButtonReleased -= value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<MouseWheelScrollEventArgs>? CanvasMouseWheelScrolled
|
||||||
|
{
|
||||||
|
add { MouseWheelScrolled += value; }
|
||||||
|
remove { MouseWheelScrolled -= value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2u Resolution
|
||||||
|
{
|
||||||
|
get => Size;
|
||||||
|
set => Size = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2f Center
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
using var view = GetView();
|
||||||
|
return view.Center;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
using var view = GetView();
|
||||||
|
view.Center = value;
|
||||||
|
SetView(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float Zoom
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
using var view = GetView();
|
||||||
|
return Math.Abs(Size.X / view.Size.X); // XXX: 仅使用宽度进行缩放计算
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
value = Math.Abs(value);
|
||||||
|
if (value <= 0) return;
|
||||||
|
using var view = GetView();
|
||||||
|
var signX = Math.Sign(view.Size.X);
|
||||||
|
var signY = Math.Sign(view.Size.Y);
|
||||||
|
var resolution = Size;
|
||||||
|
view.Size = new(resolution.X / value * signX, resolution.Y / value * signY);
|
||||||
|
SetView(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float Rotation
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
using var view = GetView();
|
||||||
|
return view.Rotation;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
using var view = GetView();
|
||||||
|
view.Rotation = value;
|
||||||
|
SetView(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FlipX
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
using var view = GetView();
|
||||||
|
return view.Size.X < 0;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
using var view = GetView();
|
||||||
|
var size = view.Size;
|
||||||
|
if (size.X > 0 && value || size.X < 0 && !value)
|
||||||
|
size.X *= -1;
|
||||||
|
view.Size = size;
|
||||||
|
SetView(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FlipY
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
using var view = GetView();
|
||||||
|
return view.Size.Y < 0;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
using var view = GetView();
|
||||||
|
var size = view.Size;
|
||||||
|
if (size.Y > 0 && value || size.Y < 0 && !value)
|
||||||
|
size.Y *= -1;
|
||||||
|
view.Size = size;
|
||||||
|
SetView(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint MaxFps
|
||||||
|
{
|
||||||
|
get => _maxFps;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetFramerateLimit(value);
|
||||||
|
_maxFps = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private uint _maxFps = 0;
|
||||||
|
|
||||||
|
public bool VerticalSync
|
||||||
|
{
|
||||||
|
get => _verticalSync;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetVerticalSyncEnabled(value);
|
||||||
|
_verticalSync = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private bool _verticalSync = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.15.4</Version>
|
<Version>0.15.19</Version>
|
||||||
<UseWPF>true</UseWPF>
|
<UseWPF>true</UseWPF>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -30,8 +30,15 @@ namespace Spine.Implementations.SpineWrappers.V21
|
|||||||
: base(skelPath, atlasPath, textureLoader)
|
: base(skelPath, atlasPath, textureLoader)
|
||||||
{
|
{
|
||||||
// 加载 atlas
|
// 加载 atlas
|
||||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
try
|
||||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
{
|
||||||
|
_atlas = new Atlas(atlasPath, textureLoader);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
|
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -41,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V21
|
|||||||
{
|
{
|
||||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V21
|
|||||||
{
|
{
|
||||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V21
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_atlas.Dispose();
|
_atlas.Dispose();
|
||||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
_logger.Trace(ex.ToString());
|
||||||
|
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载动画数据
|
// 加载动画数据
|
||||||
|
|||||||
@@ -30,8 +30,15 @@ namespace Spine.Implementations.SpineWrappers.V34
|
|||||||
: base(skelPath, atlasPath, textureLoader)
|
: base(skelPath, atlasPath, textureLoader)
|
||||||
{
|
{
|
||||||
// 加载 atlas
|
// 加载 atlas
|
||||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
try
|
||||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
{
|
||||||
|
_atlas = new Atlas(atlasPath, textureLoader);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
|
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -41,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V34
|
|||||||
{
|
{
|
||||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V34
|
|||||||
{
|
{
|
||||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V34
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_atlas.Dispose();
|
_atlas.Dispose();
|
||||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
_logger.Trace(ex.ToString());
|
||||||
|
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载动画数据
|
// 加载动画数据
|
||||||
|
|||||||
@@ -30,8 +30,15 @@ namespace Spine.Implementations.SpineWrappers.V35
|
|||||||
: base(skelPath, atlasPath, textureLoader)
|
: base(skelPath, atlasPath, textureLoader)
|
||||||
{
|
{
|
||||||
// 加载 atlas
|
// 加载 atlas
|
||||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
try
|
||||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
{
|
||||||
|
_atlas = new Atlas(atlasPath, textureLoader);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
|
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -41,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V35
|
|||||||
{
|
{
|
||||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V35
|
|||||||
{
|
{
|
||||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V35
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_atlas.Dispose();
|
_atlas.Dispose();
|
||||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
_logger.Trace(ex.ToString());
|
||||||
|
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载动画数据
|
// 加载动画数据
|
||||||
|
|||||||
@@ -30,8 +30,15 @@ namespace Spine.Implementations.SpineWrappers.V36
|
|||||||
: base(skelPath, atlasPath, textureLoader)
|
: base(skelPath, atlasPath, textureLoader)
|
||||||
{
|
{
|
||||||
// 加载 atlas
|
// 加载 atlas
|
||||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
try
|
||||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
{
|
||||||
|
_atlas = new Atlas(atlasPath, textureLoader);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
|
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -41,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V36
|
|||||||
{
|
{
|
||||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V36
|
|||||||
{
|
{
|
||||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V36
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_atlas.Dispose();
|
_atlas.Dispose();
|
||||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
_logger.Trace(ex.ToString());
|
||||||
|
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载动画数据
|
// 加载动画数据
|
||||||
|
|||||||
@@ -30,8 +30,15 @@ namespace Spine.Implementations.SpineWrappers.V37
|
|||||||
: base(skelPath, atlasPath, textureLoader)
|
: base(skelPath, atlasPath, textureLoader)
|
||||||
{
|
{
|
||||||
// 加载 atlas
|
// 加载 atlas
|
||||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
try
|
||||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
{
|
||||||
|
_atlas = new Atlas(atlasPath, textureLoader);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
|
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -41,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V37
|
|||||||
{
|
{
|
||||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V37
|
|||||||
{
|
{
|
||||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V37
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_atlas.Dispose();
|
_atlas.Dispose();
|
||||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
_logger.Trace(ex.ToString());
|
||||||
|
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载动画数据
|
// 加载动画数据
|
||||||
|
|||||||
@@ -31,8 +31,15 @@ namespace Spine.Implementations.SpineWrappers.V38
|
|||||||
: base(skelPath, atlasPath, textureLoader)
|
: base(skelPath, atlasPath, textureLoader)
|
||||||
{
|
{
|
||||||
// 加载 atlas
|
// 加载 atlas
|
||||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
try
|
||||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
{
|
||||||
|
_atlas = new Atlas(atlasPath, textureLoader);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
|
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -42,8 +49,9 @@ namespace Spine.Implementations.SpineWrappers.V38
|
|||||||
{
|
{
|
||||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,8 +61,9 @@ namespace Spine.Implementations.SpineWrappers.V38
|
|||||||
{
|
{
|
||||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,7 +71,8 @@ namespace Spine.Implementations.SpineWrappers.V38
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_atlas.Dispose();
|
_atlas.Dispose();
|
||||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
_logger.Trace(ex.ToString());
|
||||||
|
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载动画数据
|
// 加载动画数据
|
||||||
|
|||||||
@@ -30,10 +30,16 @@ namespace Spine.Implementations.SpineWrappers.V40
|
|||||||
: base(skelPath, atlasPath, textureLoader)
|
: base(skelPath, atlasPath, textureLoader)
|
||||||
{
|
{
|
||||||
// 加载 atlas
|
// 加载 atlas
|
||||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
try
|
||||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
{
|
||||||
|
_atlas = new Atlas(atlasPath, textureLoader);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
|
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||||
|
}
|
||||||
|
|
||||||
// 加载 skel
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Utf8Validator.IsUtf8(skelPath))
|
if (Utf8Validator.IsUtf8(skelPath))
|
||||||
@@ -42,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V40
|
|||||||
{
|
{
|
||||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V40
|
|||||||
{
|
{
|
||||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V40
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_atlas.Dispose();
|
_atlas.Dispose();
|
||||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
_logger.Trace(ex.ToString());
|
||||||
|
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载动画数据
|
// 加载动画数据
|
||||||
|
|||||||
@@ -30,10 +30,16 @@ namespace Spine.Implementations.SpineWrappers.V41
|
|||||||
: base(skelPath, atlasPath, textureLoader)
|
: base(skelPath, atlasPath, textureLoader)
|
||||||
{
|
{
|
||||||
// 加载 atlas
|
// 加载 atlas
|
||||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
try
|
||||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
{
|
||||||
|
_atlas = new Atlas(atlasPath, textureLoader);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
|
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||||
|
}
|
||||||
|
|
||||||
// 加载 skel
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Utf8Validator.IsUtf8(skelPath))
|
if (Utf8Validator.IsUtf8(skelPath))
|
||||||
@@ -42,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V41
|
|||||||
{
|
{
|
||||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V41
|
|||||||
{
|
{
|
||||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V41
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_atlas.Dispose();
|
_atlas.Dispose();
|
||||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
_logger.Trace(ex.ToString());
|
||||||
|
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载动画数据
|
// 加载动画数据
|
||||||
|
|||||||
@@ -30,10 +30,16 @@ namespace Spine.Implementations.SpineWrappers.V42
|
|||||||
: base(skelPath, atlasPath, textureLoader)
|
: base(skelPath, atlasPath, textureLoader)
|
||||||
{
|
{
|
||||||
// 加载 atlas
|
// 加载 atlas
|
||||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
try
|
||||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
{
|
||||||
|
_atlas = new Atlas(atlasPath, textureLoader);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
|
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||||
|
}
|
||||||
|
|
||||||
// 加载 skel
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Utf8Validator.IsUtf8(skelPath))
|
if (Utf8Validator.IsUtf8(skelPath))
|
||||||
@@ -42,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V42
|
|||||||
{
|
{
|
||||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V42
|
|||||||
{
|
{
|
||||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V42
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_atlas.Dispose();
|
_atlas.Dispose();
|
||||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
_logger.Trace(ex.ToString());
|
||||||
|
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载动画数据
|
// 加载动画数据
|
||||||
|
|||||||
@@ -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.15.14</Version>
|
<Version>0.15.19</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|||||||
@@ -115,8 +115,9 @@ namespace Spine
|
|||||||
{
|
{
|
||||||
_data = SpineObjectData.New(Version, skelPath, atlasPath, textureLoader);
|
_data = SpineObjectData.New(Version, skelPath, atlasPath, textureLoader);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
throw new InvalidDataException($"Failed to load spine with version '{version}'");
|
throw new InvalidDataException($"Failed to load spine with version '{version}'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Collections.Immutable;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
using Spine.SpineWrappers.Attachments;
|
using Spine.SpineWrappers.Attachments;
|
||||||
|
|
||||||
namespace Spine.SpineWrappers
|
namespace Spine.SpineWrappers
|
||||||
@@ -17,6 +18,8 @@ namespace Spine.SpineWrappers
|
|||||||
ISpineObjectData,
|
ISpineObjectData,
|
||||||
IDisposable
|
IDisposable
|
||||||
{
|
{
|
||||||
|
protected static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构建版本对象
|
/// 构建版本对象
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
using System;
|
using NLog;
|
||||||
|
using SFML.Graphics;
|
||||||
|
using SkiaSharp;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -20,6 +23,8 @@ namespace Spine.SpineWrappers
|
|||||||
SpineRuntime41.TextureLoader,
|
SpineRuntime41.TextureLoader,
|
||||||
SpineRuntime42.TextureLoader
|
SpineRuntime42.TextureLoader
|
||||||
{
|
{
|
||||||
|
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 默认的全局纹理加载器
|
/// 默认的全局纹理加载器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -40,37 +45,38 @@ namespace Spine.SpineWrappers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ForceMipmap { get; set; }
|
public bool ForceMipmap { get; set; }
|
||||||
|
|
||||||
private SFML.Graphics.Texture ReadTexture(string path)
|
private Texture ReadTexture(string path)
|
||||||
{
|
{
|
||||||
if (ForcePremul)
|
if (!File.Exists(path))
|
||||||
{
|
{
|
||||||
using var image = new SFML.Graphics.Image(path);
|
_logger.Error($"Texture file not found, {path}");
|
||||||
var width = image.Size.X;
|
throw new FileNotFoundException("Texture file not found", path);
|
||||||
var height = image.Size.Y;
|
|
||||||
var pixels = image.Pixels;
|
|
||||||
var size = width * height * 4;
|
|
||||||
for (int i = 0; i < size; i += 4)
|
|
||||||
{
|
|
||||||
byte a = pixels[i + 3];
|
|
||||||
if (a == 0)
|
|
||||||
{
|
|
||||||
pixels[i + 0] = 0;
|
|
||||||
pixels[i + 1] = 0;
|
|
||||||
pixels[i + 2] = 0;
|
|
||||||
}
|
|
||||||
else if (a != 255)
|
|
||||||
{
|
|
||||||
float f = a / 255f;
|
|
||||||
pixels[i + 0] = (byte)(pixels[i + 0] * f);
|
|
||||||
pixels[i + 1] = (byte)(pixels[i + 1] * f);
|
|
||||||
pixels[i + 2] = (byte)(pixels[i + 2] * f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var tex = new SFML.Graphics.Texture(width, height);
|
|
||||||
tex.Update(pixels);
|
|
||||||
return tex;
|
|
||||||
}
|
}
|
||||||
return new(path);
|
|
||||||
|
using var codec = SKCodec.Create(path, out var result);
|
||||||
|
if (codec is null || result != SKCodecResult.Success)
|
||||||
|
{
|
||||||
|
_logger.Error($"Failed to create codec '{path}', {result}");
|
||||||
|
throw new InvalidOperationException($"Failed to create codec '{path}', {result}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var width = codec.Info.Width;
|
||||||
|
var height = codec.Info.Height;
|
||||||
|
|
||||||
|
// 判断是否需要强制预乘
|
||||||
|
var alphaType = ForcePremul ? SKAlphaType.Premul : SKAlphaType.Unpremul;
|
||||||
|
var info = new SKImageInfo(width, height, SKColorType.Rgba8888, alphaType);
|
||||||
|
|
||||||
|
result = codec.GetPixels(info, out var pixels);
|
||||||
|
if (result != SKCodecResult.Success)
|
||||||
|
{
|
||||||
|
_logger.Error($"Failed to decode image '{path}', {result}");
|
||||||
|
throw new InvalidOperationException($"Failed to decode image '{path}', {result}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture tex = new((uint)width, (uint)height);
|
||||||
|
tex.Update(pixels);
|
||||||
|
return tex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Load(SpineRuntime21.AtlasPage page, string path)
|
public virtual void Load(SpineRuntime21.AtlasPage page, string path)
|
||||||
@@ -394,7 +400,7 @@ namespace Spine.SpineWrappers
|
|||||||
|
|
||||||
public virtual void Unload(object texture)
|
public virtual void Unload(object texture)
|
||||||
{
|
{
|
||||||
((SFML.Graphics.Texture)texture).Dispose();
|
((Texture)texture).Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ namespace SpineRuntime21 {
|
|||||||
|
|
||||||
if (vertices != null)
|
if (vertices != null)
|
||||||
{
|
{
|
||||||
for (int ii = 0; ii < verticesLength; ii += 2)
|
for (int ii = 0; ii + 1 < verticesLength; ii += 2)
|
||||||
{
|
{
|
||||||
float vx = vertices[ii], vy = vertices[ii + 1];
|
float vx = vertices[ii], vy = vertices[ii + 1];
|
||||||
minX = Math.Min(minX, vx);
|
minX = Math.Min(minX, vx);
|
||||||
|
|||||||
@@ -489,7 +489,7 @@ namespace SpineRuntime34 {
|
|||||||
|
|
||||||
if (vertices != null)
|
if (vertices != null)
|
||||||
{
|
{
|
||||||
for (int ii = 0; ii < verticesLength; ii += 2)
|
for (int ii = 0; ii + 1 < verticesLength; ii += 2)
|
||||||
{
|
{
|
||||||
float vx = vertices[ii], vy = vertices[ii + 1];
|
float vx = vertices[ii], vy = vertices[ii + 1];
|
||||||
minX = Math.Min(minX, vx);
|
minX = Math.Min(minX, vx);
|
||||||
|
|||||||
@@ -521,7 +521,7 @@ namespace SpineRuntime35 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (vertices != null) {
|
if (vertices != null) {
|
||||||
for (int ii = 0; ii < verticesLength; ii += 2) {
|
for (int ii = 0; ii + 1 < verticesLength; ii += 2) {
|
||||||
float vx = vertices[ii], vy = vertices[ii + 1];
|
float vx = vertices[ii], vy = vertices[ii + 1];
|
||||||
minX = Math.Min(minX, vx);
|
minX = Math.Min(minX, vx);
|
||||||
minY = Math.Min(minY, vy);
|
minY = Math.Min(minY, vy);
|
||||||
|
|||||||
@@ -528,7 +528,7 @@ namespace SpineRuntime36 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (vertices != null) {
|
if (vertices != null) {
|
||||||
for (int ii = 0; ii < verticesLength; ii += 2) {
|
for (int ii = 0; ii + 1 < verticesLength; ii += 2) {
|
||||||
float vx = vertices[ii], vy = vertices[ii + 1];
|
float vx = vertices[ii], vy = vertices[ii + 1];
|
||||||
minX = Math.Min(minX, vx);
|
minX = Math.Min(minX, vx);
|
||||||
minY = Math.Min(minY, vy);
|
minY = Math.Min(minY, vy);
|
||||||
|
|||||||
@@ -580,7 +580,7 @@ namespace SpineRuntime37 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (vertices != null) {
|
if (vertices != null) {
|
||||||
for (int ii = 0; ii < verticesLength; ii += 2) {
|
for (int ii = 0; ii + 1 < verticesLength; ii += 2) {
|
||||||
float vx = vertices[ii], vy = vertices[ii + 1];
|
float vx = vertices[ii], vy = vertices[ii + 1];
|
||||||
minX = Math.Min(minX, vx);
|
minX = Math.Min(minX, vx);
|
||||||
minY = Math.Min(minY, vy);
|
minY = Math.Min(minY, vy);
|
||||||
|
|||||||
@@ -617,7 +617,7 @@ namespace SpineRuntime38 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (vertices != null) {
|
if (vertices != null) {
|
||||||
for (int ii = 0; ii < verticesLength; ii += 2) {
|
for (int ii = 0; ii + 1 < verticesLength; ii += 2) {
|
||||||
float vx = vertices[ii], vy = vertices[ii + 1];
|
float vx = vertices[ii], vy = vertices[ii + 1];
|
||||||
minX = Math.Min(minX, vx);
|
minX = Math.Min(minX, vx);
|
||||||
minY = Math.Min(minY, vy);
|
minY = Math.Min(minY, vy);
|
||||||
|
|||||||
@@ -595,7 +595,7 @@ namespace SpineRuntime40 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (vertices != null) {
|
if (vertices != null) {
|
||||||
for (int ii = 0; ii < verticesLength; ii += 2) {
|
for (int ii = 0; ii + 1 < verticesLength; ii += 2) {
|
||||||
float vx = vertices[ii], vy = vertices[ii + 1];
|
float vx = vertices[ii], vy = vertices[ii + 1];
|
||||||
minX = Math.Min(minX, vx);
|
minX = Math.Min(minX, vx);
|
||||||
minY = Math.Min(minY, vy);
|
minY = Math.Min(minY, vy);
|
||||||
|
|||||||
@@ -641,7 +641,7 @@ namespace SpineRuntime41 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (vertices != null) {
|
if (vertices != null) {
|
||||||
for (int ii = 0; ii < verticesLength; ii += 2) {
|
for (int ii = 0; ii + 1 < verticesLength; ii += 2) {
|
||||||
float vx = vertices[ii], vy = vertices[ii + 1];
|
float vx = vertices[ii], vy = vertices[ii + 1];
|
||||||
minX = Math.Min(minX, vx);
|
minX = Math.Min(minX, vx);
|
||||||
minY = Math.Min(minY, vy);
|
minY = Math.Min(minY, vy);
|
||||||
|
|||||||
@@ -742,7 +742,7 @@ namespace SpineRuntime42 {
|
|||||||
verticesLength = clipper.ClippedVertices.Count;
|
verticesLength = clipper.ClippedVertices.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int ii = 0; ii < verticesLength; ii += 2) {
|
for (int ii = 0; ii + 1 < verticesLength; ii += 2) {
|
||||||
float vx = vertices[ii], vy = vertices[ii + 1];
|
float vx = vertices[ii], vy = vertices[ii + 1];
|
||||||
minX = Math.Min(minX, vx);
|
minX = Math.Min(minX, vx);
|
||||||
minY = Math.Min(minY, vy);
|
minY = Math.Min(minY, vy);
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
using NLog;
|
using NLog;
|
||||||
|
using SpineViewer.Natives;
|
||||||
using SpineViewer.Views;
|
using SpineViewer.Views;
|
||||||
using System.Collections.Frozen;
|
using System.Collections.Frozen;
|
||||||
using System.Configuration;
|
using System.Configuration;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Pipes;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
|
||||||
@@ -15,9 +18,18 @@ namespace SpineViewer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class App : Application
|
public partial class App : Application
|
||||||
{
|
{
|
||||||
public static string Version => Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
public const string ProgId = "SpineViewer.skel";
|
||||||
|
|
||||||
|
public static readonly string ProcessPath = Environment.ProcessPath;
|
||||||
|
public static readonly string ProcessDirectory = Path.GetDirectoryName(Environment.ProcessPath);
|
||||||
|
public static readonly string ProcessName = Process.GetCurrentProcess().ProcessName;
|
||||||
|
public static readonly string Version = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||||
|
|
||||||
|
private const string MutexName = "SpineViewerInstance";
|
||||||
|
private const string PipeName = "SpineViewerPipe";
|
||||||
|
|
||||||
private static readonly Logger _logger;
|
private static readonly Logger _logger;
|
||||||
|
private static readonly Mutex _instanceMutex;
|
||||||
|
|
||||||
static App()
|
static App()
|
||||||
{
|
{
|
||||||
@@ -35,6 +47,17 @@ namespace SpineViewer
|
|||||||
_logger.Error("Unobserved task exception: {0}", e.Exception.Message);
|
_logger.Error("Unobserved task exception: {0}", e.Exception.Message);
|
||||||
e.SetObserved();
|
e.SetObserved();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 单例模式加 IPC 通信
|
||||||
|
_instanceMutex = new Mutex(true, MutexName, out var createdNew);
|
||||||
|
if (!createdNew)
|
||||||
|
{
|
||||||
|
ShowExistedInstance();
|
||||||
|
SendCommandLineArgs();
|
||||||
|
Environment.Exit(0); // 不再启动新实例
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StartPipeServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void InitializeLogConfiguration()
|
private static void InitializeLogConfiguration()
|
||||||
@@ -50,7 +73,9 @@ namespace SpineViewer
|
|||||||
ArchiveNumbering = NLog.Targets.ArchiveNumberingMode.Rolling,
|
ArchiveNumbering = NLog.Targets.ArchiveNumberingMode.Rolling,
|
||||||
ArchiveAboveSize = 1048576,
|
ArchiveAboveSize = 1048576,
|
||||||
MaxArchiveFiles = 5,
|
MaxArchiveFiles = 5,
|
||||||
Layout = "${date:format=yyyy-MM-dd HH\\:mm\\:ss} - ${level:uppercase=true} - ${callsite-filename:includeSourcePath=false}:${callsite-linenumber} - ${message}"
|
Layout = "${date:format=yyyy-MM-dd HH\\:mm\\:ss} - ${level:uppercase=true} - ${processid} - ${callsite-filename:includeSourcePath=false}:${callsite-linenumber} - ${message}",
|
||||||
|
ConcurrentWrites = true,
|
||||||
|
KeepFileOpen = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
config.AddTarget(fileTarget);
|
config.AddTarget(fileTarget);
|
||||||
@@ -58,20 +83,113 @@ namespace SpineViewer
|
|||||||
LogManager.Configuration = config;
|
LogManager.Configuration = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ShowExistedInstance()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 2. 遍历同名进程
|
||||||
|
var processes = Process.GetProcessesByName(ProcessName);
|
||||||
|
foreach (var p in processes)
|
||||||
|
{
|
||||||
|
// 跳过当前进程
|
||||||
|
if (p.Id == Process.GetCurrentProcess().Id)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
IntPtr hWnd = p.MainWindowHandle;
|
||||||
|
if (hWnd != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
// 3. 显示并置顶窗口
|
||||||
|
if (User32.IsIconic(hWnd))
|
||||||
|
{
|
||||||
|
User32.ShowWindow(hWnd, User32.SW_RESTORE);
|
||||||
|
}
|
||||||
|
User32.SetForegroundWindow(hWnd);
|
||||||
|
break; // 找到一个就可以退出
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 忽略异常,不影响当前进程退出
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SendCommandLineArgs()
|
||||||
|
{
|
||||||
|
var args = Environment.GetCommandLineArgs().Skip(1).ToArray();
|
||||||
|
if (args.Length <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_logger.Info("Send command line args to existed instance, \"{0}\"", string.Join(", ", args));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 已有实例在运行,把参数通过命名管道发过去
|
||||||
|
using (var client = new NamedPipeClientStream(".", PipeName, PipeDirection.Out))
|
||||||
|
{
|
||||||
|
client.Connect(10000); // 10 秒超时
|
||||||
|
using (var writer = new StreamWriter(client))
|
||||||
|
{
|
||||||
|
foreach (var v in args)
|
||||||
|
{
|
||||||
|
writer.WriteLine(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
|
_logger.Error("Failed to pass command line args to existed instance, {0}", ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void StartPipeServer()
|
||||||
|
{
|
||||||
|
var t = new Task(() =>
|
||||||
|
{
|
||||||
|
while (Current is null) Thread.Sleep(10);
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var windowCreated = false;
|
||||||
|
Current.Dispatcher.Invoke(() => windowCreated = Current.MainWindow is MainWindow);
|
||||||
|
if (windowCreated)
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
Thread.Sleep(100);
|
||||||
|
}
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
using (var server = new NamedPipeServerStream(PipeName, PipeDirection.In))
|
||||||
|
{
|
||||||
|
server.WaitForConnection();
|
||||||
|
using (var reader = new StreamReader(server))
|
||||||
|
{
|
||||||
|
var args = new List<string>();
|
||||||
|
string? line;
|
||||||
|
while ((line = reader.ReadLine()) != null)
|
||||||
|
args.Add(line);
|
||||||
|
|
||||||
|
if (args.Count > 0)
|
||||||
|
{
|
||||||
|
Current.Dispatcher.Invoke(() => ((MainWindow)Current.MainWindow).OpenFiles(args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, default, TaskCreationOptions.LongRunning);
|
||||||
|
t.Start();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnStartup(StartupEventArgs e)
|
protected override void OnStartup(StartupEventArgs e)
|
||||||
{
|
{
|
||||||
|
// 正式启动窗口
|
||||||
base.OnStartup(e);
|
base.OnStartup(e);
|
||||||
|
|
||||||
var dict = new ResourceDictionary();
|
|
||||||
|
|
||||||
var uiCulture = CultureInfo.CurrentUICulture.Name.ToLowerInvariant();
|
var uiCulture = CultureInfo.CurrentUICulture.Name.ToLowerInvariant();
|
||||||
_logger.Info("Current UI Culture: {0}", uiCulture);
|
_logger.Info("Current UI Culture: {0}", uiCulture);
|
||||||
|
|
||||||
if (uiCulture.StartsWith("zh")) { } // 默认就是中文, 无需操作
|
if (uiCulture.StartsWith("zh")) { } // 默认就是中文, 无需操作
|
||||||
else if (uiCulture.StartsWith("ja")) Language = AppLanguage.JA;
|
else if (uiCulture.StartsWith("ja")) Language = AppLanguage.JA;
|
||||||
else Language = AppLanguage.EN;
|
else Language = AppLanguage.EN;
|
||||||
|
|
||||||
Resources.MergedDictionaries.Add(dict);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
|
private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ namespace SpineViewer.Models
|
|||||||
public float Speed { get; set; } = 1f;
|
public float Speed { get; set; } = 1f;
|
||||||
public bool ShowAxis { get; set; } = true;
|
public bool ShowAxis { get; set; } = true;
|
||||||
public Color BackgroundColor { get; set; } = Color.FromRgb(105, 105, 105);
|
public Color BackgroundColor { get; set; } = Color.FromRgb(105, 105, 105);
|
||||||
|
public Stretch BackgroundImageMode { get; set; } = Stretch.Uniform;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
@@ -73,9 +73,15 @@ namespace SpineViewer.Models
|
|||||||
|
|
||||||
#region 程序选项
|
#region 程序选项
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _wallpaperView;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _renderSelectedOnly;
|
private bool _renderSelectedOnly;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _associateFileSuffix;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private AppLanguage _appLanguage;
|
private AppLanguage _appLanguage;
|
||||||
|
|
||||||
|
|||||||
@@ -43,10 +43,9 @@ namespace SpineViewer.Models
|
|||||||
|
|
||||||
public Color BackgroundColor { get; set; }
|
public Color BackgroundColor { get; set; }
|
||||||
|
|
||||||
// TODO: 背景图片
|
public string BackgroundImagePath { get; set; }
|
||||||
//public string? BackgroundImagePath { get; set; }
|
|
||||||
|
|
||||||
//public ? BackgroundImageDisplayMode { get; set; }
|
public Stretch BackgroundImageMode { get; set; } = Stretch.Uniform;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SpineObjectWorkspaceConfigModel
|
public class SpineObjectWorkspaceConfigModel
|
||||||
|
|||||||
29
SpineViewer/Natives/Gdi32.cs
Normal file
29
SpineViewer/Natives/Gdi32.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace SpineViewer.Natives
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// gdi32.dll 包装类
|
||||||
|
/// </summary>
|
||||||
|
public static class Gdi32
|
||||||
|
{
|
||||||
|
[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);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
SpineViewer/Natives/Shell32.cs
Normal file
28
SpineViewer/Natives/Shell32.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace SpineViewer.Natives
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// shell32.dll 包装类
|
||||||
|
/// </summary>
|
||||||
|
public static class Shell32
|
||||||
|
{
|
||||||
|
private const uint SHCNE_ASSOCCHANGED = 0x08000000;
|
||||||
|
private const uint SHCNF_IDLIST = 0x0000;
|
||||||
|
|
||||||
|
[DllImport("shell32.dll")]
|
||||||
|
private static extern void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);
|
||||||
|
|
||||||
|
public static void NotifyAssociationChanged()
|
||||||
|
{
|
||||||
|
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, IntPtr.Zero, IntPtr.Zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
357
SpineViewer/Natives/User32.cs
Normal file
357
SpineViewer/Natives/User32.cs
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace SpineViewer.Natives
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// user32.dll 包装类
|
||||||
|
/// </summary>
|
||||||
|
public static class User32
|
||||||
|
{
|
||||||
|
public const int GWL_STYLE = -16;
|
||||||
|
public const int WS_OVERLAPPED = 0x00000000;
|
||||||
|
public const int WS_POPUP = unchecked((int)0x80000000);
|
||||||
|
public const int WS_CHILD = 0x40000000;
|
||||||
|
public const int WS_MINIMIZE = 0x20000000;
|
||||||
|
public const int WS_VISIBLE = 0x10000000;
|
||||||
|
public const int WS_DISABLED = 0x08000000;
|
||||||
|
public const int WS_CLIPSIBLINGS = 0x04000000;
|
||||||
|
public const int WS_CLIPCHILDREN = 0x02000000;
|
||||||
|
public const int WS_MAXIMIZE = 0x01000000;
|
||||||
|
public const int WS_BORDER = 0x00800000;
|
||||||
|
public const int WS_DLGFRAME = 0x00400000;
|
||||||
|
public const int WS_VSCROLL = 0x00200000;
|
||||||
|
public const int WS_HSCROLL = 0x00100000;
|
||||||
|
public const int WS_SYSMENU = 0x00080000;
|
||||||
|
public const int WS_THICKFRAME = 0x00040000;
|
||||||
|
public const int WS_GROUP = 0x00020000;
|
||||||
|
public const int WS_TABSTOP = 0x00010000;
|
||||||
|
public const int WS_MINIMIZEBOX = 0x00020000;
|
||||||
|
public const int WS_MAXIMIZEBOX = 0x00010000;
|
||||||
|
public const int WS_CHILDWINDOW = WS_CHILD;
|
||||||
|
public const int WS_CAPTION = WS_BORDER | WS_DLGFRAME;
|
||||||
|
public const int WS_TILED = WS_OVERLAPPED;
|
||||||
|
public const int WS_ICONIC = WS_MINIMIZE;
|
||||||
|
public const int WS_SIZEBOX = WS_THICKFRAME;
|
||||||
|
public const int WS_TILEDWINDOW = WS_OVERLAPPEDWINDOW;
|
||||||
|
public const int WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
|
||||||
|
public const int WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU;
|
||||||
|
|
||||||
|
public const int GWL_EXSTYLE = -20;
|
||||||
|
public const int WS_EX_DLGMODALFRAME = 0x00000001;
|
||||||
|
public const int WS_EX_NOPARENTNOTIFY = 0x00000004;
|
||||||
|
public const int WS_EX_TOPMOST = 0x00000008;
|
||||||
|
public const int WS_EX_ACCEPTFILES = 0x00000010;
|
||||||
|
public const int WS_EX_TRANSPARENT = 0x00000020;
|
||||||
|
public const int WS_EX_MDICHILD = 0x00000040;
|
||||||
|
public const int WS_EX_TOOLWINDOW = 0x00000080;
|
||||||
|
public const int WS_EX_WINDOWEDGE = 0x00000100;
|
||||||
|
public const int WS_EX_CLIENTEDGE = 0x00000200;
|
||||||
|
public const int WS_EX_CONTEXTHELP = 0x00000400;
|
||||||
|
public const int WS_EX_RIGHT = 0x00001000;
|
||||||
|
public const int WS_EX_LEFT = 0x00000000;
|
||||||
|
public const int WS_EX_RTLREADING = 0x00002000;
|
||||||
|
public const int WS_EX_LTRREADING = 0x00000000;
|
||||||
|
public const int WS_EX_LEFTSCROLLBAR = 0x00004000;
|
||||||
|
public const int WS_EX_RIGHTSCROLLBAR = 0x00000000;
|
||||||
|
public const int WS_EX_CONTROLPARENT = 0x00010000;
|
||||||
|
public const int WS_EX_STATICEDGE = 0x00020000;
|
||||||
|
public const int WS_EX_APPWINDOW = 0x00040000;
|
||||||
|
public const int WS_EX_OVERLAPPEDWINDOW = WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE;
|
||||||
|
public const int WS_EX_PALETTEWINDOW = WS_EX_WINDOWEDGE | WS_EX_TOOLWINDOW | WS_EX_TOPMOST;
|
||||||
|
public const int WS_EX_LAYERED = 0x00080000;
|
||||||
|
public const int WS_EX_NOINHERITLAYOUT = 0x00100000;
|
||||||
|
public const int WS_EX_LAYOUTRTL = 0x00400000;
|
||||||
|
public const int WS_EX_COMPOSITED = 0x02000000;
|
||||||
|
public const int WS_EX_NOACTIVATE = 0x08000000;
|
||||||
|
|
||||||
|
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_ASYNCWINDOWPOS = 0x4000;
|
||||||
|
public const uint SWP_DEFERERASE = 0x2000;
|
||||||
|
public const uint SWP_NOSENDCHANGING = 0x0400;
|
||||||
|
public const uint SWP_NOOWNERZORDER = 0x0200;
|
||||||
|
public const uint SWP_NOREPOSITION = 0x0200;
|
||||||
|
public const uint SWP_NOCOPYBITS = 0x0100;
|
||||||
|
public const uint SWP_HIDEWINDOW = 0x0080;
|
||||||
|
public const uint SWP_SHOWWINDOW = 0x0040;
|
||||||
|
public const uint SWP_DRAWFRAME = 0x0020;
|
||||||
|
public const uint SWP_FRAMECHANGED = 0x0020;
|
||||||
|
public const uint SWP_NOACTIVATE = 0x0010;
|
||||||
|
public const uint SWP_NOREDRAW = 0x0008;
|
||||||
|
public const uint SWP_NOZORDER = 0x0004;
|
||||||
|
public const uint SWP_NOMOVE = 0x0002;
|
||||||
|
public const uint SWP_NOSIZE = 0x0001;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
public const int SW_HIDE = 0;
|
||||||
|
public const int SW_SHOWNORMAL = 1;
|
||||||
|
public const int SW_SHOWMINIMIZED = 2;
|
||||||
|
public const int SW_SHOWMAXIMIZED = 3;
|
||||||
|
public const int SW_SHOWNOACTIVATE = 4;
|
||||||
|
public const int SW_SHOW = 5;
|
||||||
|
public const int SW_MINIMIZE = 6;
|
||||||
|
public const int SW_SHOWMINNOACTIVE = 7;
|
||||||
|
public const int SW_SHOWNA = 8;
|
||||||
|
public const int SW_RESTORE = 9;
|
||||||
|
public const int SW_SHOWDEFAULT = 10;
|
||||||
|
|
||||||
|
public const uint MONITOR_DEFAULTTONULL = 0;
|
||||||
|
public const uint MONITOR_DEFAULTTOPRIMARY = 1;
|
||||||
|
public const uint MONITOR_DEFAULTTONEAREST = 2;
|
||||||
|
|
||||||
|
[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;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct RECT
|
||||||
|
{
|
||||||
|
public int Left;
|
||||||
|
public int Top;
|
||||||
|
public int Right;
|
||||||
|
public int Bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct MONITORINFOEX
|
||||||
|
{
|
||||||
|
public uint cbSize;
|
||||||
|
public RECT rcMonitor;
|
||||||
|
public RECT rcWork;
|
||||||
|
public uint dwFlags;
|
||||||
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
|
||||||
|
public string szDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
|
||||||
|
|
||||||
|
[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 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("user32.dll", SetLastError = true)]
|
||||||
|
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
public static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam);
|
||||||
|
|
||||||
|
[DllImport("User32.dll")]
|
||||||
|
public static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
public static extern bool IsIconic(IntPtr hWnd);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
public static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFOEX lpmi);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
public static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
// NOTE: Codes borrowed from @rocksdanister/lively
|
||||||
|
|
||||||
|
var progman = FindWindow("Progman", null);
|
||||||
|
if (progman == IntPtr.Zero)
|
||||||
|
return IntPtr.Zero;
|
||||||
|
|
||||||
|
// Send 0x052C to Progman. This message directs Progman to spawn a
|
||||||
|
// WorkerW behind the desktop icons. If it is already there, nothing
|
||||||
|
// happens.
|
||||||
|
SendMessageTimeout(progman, WM_SPAWN_WORKER, 0, 0, SMTO_NORMAL, 1000, out _);
|
||||||
|
|
||||||
|
// Spy++ output
|
||||||
|
// .....
|
||||||
|
// 0x00010190 "" WorkerW
|
||||||
|
// ...
|
||||||
|
// 0x000100EE "" SHELLDLL_DefView
|
||||||
|
// 0x000100F0 "FolderView" SysListView32
|
||||||
|
// 0x00100B8A "" WorkerW <-- This is the WorkerW instance we are after!
|
||||||
|
// 0x000100EC "Program Manager" Progman
|
||||||
|
var workerw = IntPtr.Zero;
|
||||||
|
|
||||||
|
// We enumerate all Windows, until we find one, that has the SHELLDLL_DefView
|
||||||
|
// as a child.
|
||||||
|
// If we found that window, we take its next sibling and assign it to workerw.
|
||||||
|
EnumWindows(new EnumWindowsProc((tophandle, topparamhandle) =>
|
||||||
|
{
|
||||||
|
IntPtr p = FindWindowEx(tophandle, IntPtr.Zero, "SHELLDLL_DefView", null);
|
||||||
|
|
||||||
|
if (p != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
// Gets the WorkerW Window after the current one.
|
||||||
|
workerw = FindWindowEx(IntPtr.Zero, tophandle, "WorkerW", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}), IntPtr.Zero);
|
||||||
|
|
||||||
|
// Some Windows 11 builds have a different Progman window layout.
|
||||||
|
// If the above code failed to find WorkerW, we should try this.
|
||||||
|
// Spy++ output
|
||||||
|
// 0x000100EC "Program Manager" Progman
|
||||||
|
// 0x000100EE "" SHELLDLL_DefView
|
||||||
|
// 0x000100F0 "FolderView" SysListView32
|
||||||
|
// 0x00100B8A "" WorkerW <-- This is the WorkerW instance we are after!
|
||||||
|
if (workerw == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
workerw = FindWindowEx(progman, IntPtr.Zero, "WorkerW", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.WriteLine($"HWND(WorkerW): {workerw:x8}");
|
||||||
|
return workerw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool GetScreenResolution(IntPtr hwnd, out uint width, out uint height)
|
||||||
|
{
|
||||||
|
IntPtr hMon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
||||||
|
|
||||||
|
var mi = new MONITORINFOEX { cbSize = (uint)Marshal.SizeOf<MONITORINFOEX>() };
|
||||||
|
if (GetMonitorInfo(hMon, ref mi))
|
||||||
|
{
|
||||||
|
int widthPx = mi.rcMonitor.Right - mi.rcMonitor.Left;
|
||||||
|
int heightPx = mi.rcMonitor.Bottom - mi.rcMonitor.Top;
|
||||||
|
width = (uint)widthPx;
|
||||||
|
height = (uint)heightPx;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
width = height = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool GetPrimaryScreenResolution(out uint width, out uint height)
|
||||||
|
{
|
||||||
|
IntPtr hMon = MonitorFromWindow(IntPtr.Zero, MONITOR_DEFAULTTOPRIMARY);
|
||||||
|
|
||||||
|
var mi = new MONITORINFOEX { cbSize = (uint)Marshal.SizeOf<MONITORINFOEX>() };
|
||||||
|
if (GetMonitorInfo(hMon, ref mi))
|
||||||
|
{
|
||||||
|
int widthPx = mi.rcMonitor.Right - mi.rcMonitor.Left;
|
||||||
|
int heightPx = mi.rcMonitor.Bottom - mi.rcMonitor.Top;
|
||||||
|
width = (uint)widthPx;
|
||||||
|
height = (uint)heightPx;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
width = height = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,241 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows;
|
|
||||||
|
|
||||||
namespace SpineViewer.Natives
|
|
||||||
{
|
|
||||||
/// <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_VISIBLE = 0x10000000;
|
|
||||||
public const int WS_CHILD = 0x40000000;
|
|
||||||
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_APPWINDOW = 0x40000;
|
|
||||||
public const int WS_EX_LAYERED = 0x80000;
|
|
||||||
public const int WS_EX_OVERLAPPEDWINDOW = WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE;
|
|
||||||
public const int WS_EX_NOACTIVATE = 0x8000000;
|
|
||||||
|
|
||||||
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 nint 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 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;
|
|
||||||
|
|
||||||
public const int SW_HIDE = 0;
|
|
||||||
public const int SW_SHOWNORMAL = 1;
|
|
||||||
public const int SW_SHOWMINIMIZED = 2;
|
|
||||||
public const int SW_SHOWMAXIMIZED = 3;
|
|
||||||
public const int SW_SHOWNOACTIVATE = 4;
|
|
||||||
public const int SW_SHOW = 5;
|
|
||||||
public const int SW_MINIMIZE = 6;
|
|
||||||
public const int SW_SHOWMINNOACTIVE = 7;
|
|
||||||
public const int SW_SHOWNA = 8;
|
|
||||||
public const int SW_RESTORE = 9;
|
|
||||||
public const int SW_SHOWDEFAULT = 10;
|
|
||||||
|
|
||||||
public const uint MONITOR_DEFAULTTONEAREST = 2;
|
|
||||||
|
|
||||||
[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;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
public struct RECT
|
|
||||||
{
|
|
||||||
public int Left;
|
|
||||||
public int Top;
|
|
||||||
public int Right;
|
|
||||||
public int Bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
private struct MONITORINFOEX
|
|
||||||
{
|
|
||||||
public uint cbSize;
|
|
||||||
public RECT rcMonitor;
|
|
||||||
public RECT rcWork;
|
|
||||||
public uint dwFlags;
|
|
||||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
|
|
||||||
public string szDevice;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
|
||||||
public static extern nint GetDC(nint hWnd);
|
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
|
||||||
public static extern int ReleaseDC(nint hWnd, nint hDC);
|
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
|
||||||
public static extern int SetWindowLong(nint hWnd, int nIndex, int dwNewLong);
|
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
|
||||||
public static extern int GetWindowLong(nint hWnd, int nIndex);
|
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
|
||||||
public static extern bool GetLayeredWindowAttributes(nint hWnd, ref uint crKey, ref byte bAlpha, ref uint dwFlags);
|
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
|
||||||
public static extern bool SetLayeredWindowAttributes(nint hWnd, uint pcrKey, byte pbAlpha, uint pdwFlags);
|
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
|
||||||
public static extern bool UpdateLayeredWindow(nint hWnd, nint hdcDst, nint pptDst, ref SIZE psize, nint hdcSrc, ref POINT pptSrc, int crKey, ref BLENDFUNCTION pblend, int dwFlags);
|
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
|
||||||
public static extern bool SetWindowPos(nint hWnd, nint 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 nint FindWindow(string lpClassName, string lpWindowName);
|
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
|
||||||
public static extern nint SendMessageTimeout(nint hWnd, uint Msg, nint wParam, nint lParam, uint fuFlags, uint uTimeout, out nint lpdwResult);
|
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
|
||||||
public static extern nint FindWindowEx(nint parentHandle, nint childAfter, string className, string windowTitle);
|
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
|
||||||
public static extern nint SetParent(nint hWndChild, nint hWndNewParent);
|
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
|
||||||
public static extern nint GetParent(nint hWnd);
|
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
|
||||||
public static extern nint GetAncestor(nint hWnd, uint gaFlags);
|
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
|
||||||
public static extern nint GetWindow(nint hWnd, uint uCmd);
|
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
|
||||||
public static extern bool ShowWindow(nint hWnd, int nCmdShow);
|
|
||||||
|
|
||||||
[DllImport("gdi32.dll", SetLastError = true)]
|
|
||||||
public static extern nint CreateCompatibleDC(nint hdc);
|
|
||||||
|
|
||||||
[DllImport("gdi32.dll", SetLastError = true)]
|
|
||||||
public static extern bool DeleteDC(nint hdc);
|
|
||||||
|
|
||||||
[DllImport("gdi32.dll", SetLastError = true)]
|
|
||||||
public static extern nint SelectObject(nint hdc, nint hgdiobj);
|
|
||||||
|
|
||||||
[DllImport("gdi32.dll", SetLastError = true)]
|
|
||||||
public static extern bool DeleteObject(nint hObject);
|
|
||||||
|
|
||||||
[DllImport("user32.dll")]
|
|
||||||
private static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags);
|
|
||||||
|
|
||||||
[DllImport("user32.dll")]
|
|
||||||
private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFOEX lpmi);
|
|
||||||
|
|
||||||
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 nint GetWorkerW()
|
|
||||||
{
|
|
||||||
var progman = FindWindow("Progman", null);
|
|
||||||
if (progman == nint.Zero)
|
|
||||||
return nint.Zero;
|
|
||||||
nint hWnd = FindWindowEx(progman, 0, "WorkerW", null);
|
|
||||||
Debug.WriteLine($"HWND(Progman.WorkerW): {hWnd:x8}");
|
|
||||||
return hWnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool GetScreenResolution(IntPtr hwnd, out uint width, out uint height)
|
|
||||||
{
|
|
||||||
IntPtr hMon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
|
||||||
|
|
||||||
var mi = new MONITORINFOEX { cbSize = (uint)Marshal.SizeOf<MONITORINFOEX>() };
|
|
||||||
if (GetMonitorInfo(hMon, ref mi))
|
|
||||||
{
|
|
||||||
int widthPx = mi.rcMonitor.Right - mi.rcMonitor.Left;
|
|
||||||
int heightPx = mi.rcMonitor.Bottom - mi.rcMonitor.Top;
|
|
||||||
width = (uint)widthPx;
|
|
||||||
height = (uint)heightPx;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
width = height = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,6 +19,8 @@ namespace SpineViewer.Resources
|
|||||||
public static string Str_GeneratePreviewsTitle => Get<string>("Str_GeneratePreviewsTitle");
|
public static string Str_GeneratePreviewsTitle => Get<string>("Str_GeneratePreviewsTitle");
|
||||||
public static string Str_DeletePreviewsTitle => Get<string>("Str_DeletePreviewsTitle");
|
public static string Str_DeletePreviewsTitle => Get<string>("Str_DeletePreviewsTitle");
|
||||||
public static string Str_AddSpineObjectsTitle => Get<string>("Str_AddSpineObjectsTitle");
|
public static string Str_AddSpineObjectsTitle => Get<string>("Str_AddSpineObjectsTitle");
|
||||||
|
public static string Str_OpenSkelFileTitle => Get<string>("Str_OpenSkelFileTitle");
|
||||||
|
public static string Str_OpenAtlasFileTitle => Get<string>("Str_OpenAtlasFileTitle");
|
||||||
public static string Str_ReloadSpineObjectsTitle => Get<string>("Str_ReloadSpineObjectsTitle");
|
public static string Str_ReloadSpineObjectsTitle => Get<string>("Str_ReloadSpineObjectsTitle");
|
||||||
public static string Str_CustomFFmpegExporterTitle => Get<string>("Str_CustomFFmpegExporterTitle");
|
public static string Str_CustomFFmpegExporterTitle => Get<string>("Str_CustomFFmpegExporterTitle");
|
||||||
|
|
||||||
|
|||||||
BIN
SpineViewer/Resources/Images/skel.ico
Normal file
BIN
SpineViewer/Resources/Images/skel.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 190 KiB |
BIN
SpineViewer/Resources/Images/skel.png
Normal file
BIN
SpineViewer/Resources/Images/skel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 188 KiB |
BIN
SpineViewer/Resources/Images/spineviewer.png
Normal file
BIN
SpineViewer/Resources/Images/spineviewer.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
@@ -37,8 +37,11 @@
|
|||||||
<s:String x:Key="Str_Show">Show</s:String>
|
<s:String x:Key="Str_Show">Show</s:String>
|
||||||
<s:String x:Key="Str_ListViewStatusBar">{0} items, {1} selected</s:String>
|
<s:String x:Key="Str_ListViewStatusBar">{0} items, {1} selected</s:String>
|
||||||
<s:String x:Key="Str_AddSpineObject">Add...</s:String>
|
<s:String x:Key="Str_AddSpineObject">Add...</s:String>
|
||||||
<s:String x:Key="Str_RemoveSpineObject">Remove</s:String>
|
<s:String x:Key="Str_OpenSkelFileTitle">Select Skeleton File (skel)</s:String>
|
||||||
|
<s:String x:Key="Str_OpenAtlasFileTitle">Select Atlas File (atlas)</s:String>
|
||||||
<s:String x:Key="Str_AddSpineObjectFromClipboard">Add from Clipboard</s:String>
|
<s:String x:Key="Str_AddSpineObjectFromClipboard">Add from Clipboard</s:String>
|
||||||
|
<s:String x:Key="Str_RemoveSpineObject">Remove</s:String>
|
||||||
|
<s:String x:Key="Str_RemoveAllSpineObject">Remove All</s:String>
|
||||||
<s:String x:Key="Str_Reload">Reload</s:String>
|
<s:String x:Key="Str_Reload">Reload</s:String>
|
||||||
<s:String x:Key="Str_MoveUpSpineObject">Move Up</s:String>
|
<s:String x:Key="Str_MoveUpSpineObject">Move Up</s:String>
|
||||||
<s:String x:Key="Str_MoveDownSpineObject">Move Down</s:String>
|
<s:String x:Key="Str_MoveDownSpineObject">Move Down</s:String>
|
||||||
@@ -115,10 +118,12 @@
|
|||||||
<s:String x:Key="Str_MaxFps">Max FPS</s:String>
|
<s:String x:Key="Str_MaxFps">Max FPS</s:String>
|
||||||
<s:String x:Key="Str_MaxFpsTooltip">Maximum frame rate of the preview. Set to 0 for no limit.</s:String>
|
<s:String x:Key="Str_MaxFpsTooltip">Maximum frame rate of the preview. Set to 0 for no limit.</s:String>
|
||||||
<s:String x:Key="Str_PlaySpeed">Playback Speed</s:String>
|
<s:String x:Key="Str_PlaySpeed">Playback Speed</s:String>
|
||||||
|
<s:String x:Key="Str_WallpaperView">Wallpaper View</s:String>
|
||||||
<s:String x:Key="Str_RenderSelectedOnly">Render Selected Only</s:String>
|
<s:String x:Key="Str_RenderSelectedOnly">Render Selected Only</s:String>
|
||||||
<s:String x:Key="Str_ShowAxis">Show Axis</s:String>
|
<s:String x:Key="Str_ShowAxis">Show Axis</s:String>
|
||||||
<s:String x:Key="Str_BackgroundColor">Background Color</s:String>
|
<s:String x:Key="Str_BackgroundColor">Background Color</s:String>
|
||||||
<s:String x:Key="Str_BackgroundImage">Background Image</s:String>
|
<s:String x:Key="Str_BackgroundImagePath">Background Image Path</s:String>
|
||||||
|
<s:String x:Key="Str_BackgroundImageMode">Background Image Mode</s:String>
|
||||||
|
|
||||||
<!-- 渲染画面按钮组 -->
|
<!-- 渲染画面按钮组 -->
|
||||||
<s:String x:Key="Str_StopTooltip">Stop</s:String>
|
<s:String x:Key="Str_StopTooltip">Stop</s:String>
|
||||||
@@ -232,6 +237,7 @@
|
|||||||
<s:String x:Key="Str_RendererPreference">Preview Options</s:String>
|
<s:String x:Key="Str_RendererPreference">Preview Options</s:String>
|
||||||
|
|
||||||
<s:String x:Key="Str_AppPreference">Application Options</s:String>
|
<s:String x:Key="Str_AppPreference">Application Options</s:String>
|
||||||
|
<s:String x:Key="Str_AssociateFileSuffix">Associate File Extension</s:String>
|
||||||
<s:String x:Key="Str_Language">Language</s:String>
|
<s:String x:Key="Str_Language">Language</s:String>
|
||||||
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -37,8 +37,11 @@
|
|||||||
<s:String x:Key="Str_Show">表示</s:String>
|
<s:String x:Key="Str_Show">表示</s:String>
|
||||||
<s:String x:Key="Str_ListViewStatusBar">全{0}件、選択中{1}件</s:String>
|
<s:String x:Key="Str_ListViewStatusBar">全{0}件、選択中{1}件</s:String>
|
||||||
<s:String x:Key="Str_AddSpineObject">追加...</s:String>
|
<s:String x:Key="Str_AddSpineObject">追加...</s:String>
|
||||||
<s:String x:Key="Str_RemoveSpineObject">削除</s:String>
|
<s:String x:Key="Str_OpenSkelFileTitle">スケルトンファイルを選択(skel)</s:String>
|
||||||
|
<s:String x:Key="Str_OpenAtlasFileTitle">アトラスファイルを選択(atlas)</s:String>
|
||||||
<s:String x:Key="Str_AddSpineObjectFromClipboard">クリップボードから追加</s:String>
|
<s:String x:Key="Str_AddSpineObjectFromClipboard">クリップボードから追加</s:String>
|
||||||
|
<s:String x:Key="Str_RemoveSpineObject">削除</s:String>
|
||||||
|
<s:String x:Key="Str_RemoveAllSpineObject">すべて削除</s:String>
|
||||||
<s:String x:Key="Str_Reload">再読み込み</s:String>
|
<s:String x:Key="Str_Reload">再読み込み</s:String>
|
||||||
<s:String x:Key="Str_MoveUpSpineObject">上へ移動</s:String>
|
<s:String x:Key="Str_MoveUpSpineObject">上へ移動</s:String>
|
||||||
<s:String x:Key="Str_MoveDownSpineObject">下へ移動</s:String>
|
<s:String x:Key="Str_MoveDownSpineObject">下へ移動</s:String>
|
||||||
@@ -115,10 +118,12 @@
|
|||||||
<s:String x:Key="Str_MaxFps">最大FPS</s:String>
|
<s:String x:Key="Str_MaxFps">最大FPS</s:String>
|
||||||
<s:String x:Key="Str_MaxFpsTooltip">プレビュー画面の最大フレームレート。0 に設定すると制限なし。</s:String>
|
<s:String x:Key="Str_MaxFpsTooltip">プレビュー画面の最大フレームレート。0 に設定すると制限なし。</s:String>
|
||||||
<s:String x:Key="Str_PlaySpeed">再生速度</s:String>
|
<s:String x:Key="Str_PlaySpeed">再生速度</s:String>
|
||||||
|
<s:String x:Key="Str_WallpaperView">壁紙表示</s:String>
|
||||||
<s:String x:Key="Str_RenderSelectedOnly">選択のみレンダリング</s:String>
|
<s:String x:Key="Str_RenderSelectedOnly">選択のみレンダリング</s:String>
|
||||||
<s:String x:Key="Str_ShowAxis">座標軸を表示</s:String>
|
<s:String x:Key="Str_ShowAxis">座標軸を表示</s:String>
|
||||||
<s:String x:Key="Str_BackgroundColor">背景色</s:String>
|
<s:String x:Key="Str_BackgroundColor">背景色</s:String>
|
||||||
<s:String x:Key="Str_BackgroundImage">背景画像</s:String>
|
<s:String x:Key="Str_BackgroundImagePath">背景画像のパス</s:String>
|
||||||
|
<s:String x:Key="Str_BackgroundImageMode">背景画像のモード</s:String>
|
||||||
|
|
||||||
<!-- 渲染画面按钮组 -->
|
<!-- 渲染画面按钮组 -->
|
||||||
<s:String x:Key="Str_StopTooltip">停止</s:String>
|
<s:String x:Key="Str_StopTooltip">停止</s:String>
|
||||||
@@ -232,6 +237,7 @@
|
|||||||
<s:String x:Key="Str_RendererPreference">プレビュー画面オプション</s:String>
|
<s:String x:Key="Str_RendererPreference">プレビュー画面オプション</s:String>
|
||||||
|
|
||||||
<s:String x:Key="Str_AppPreference">アプリケーションプション</s:String>
|
<s:String x:Key="Str_AppPreference">アプリケーションプション</s:String>
|
||||||
|
<s:String x:Key="Str_AssociateFileSuffix">ファイル拡張子を関連付ける</s:String>
|
||||||
<s:String x:Key="Str_Language">言語</s:String>
|
<s:String x:Key="Str_Language">言語</s:String>
|
||||||
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -37,8 +37,11 @@
|
|||||||
<s:String x:Key="Str_Show">显示</s:String>
|
<s:String x:Key="Str_Show">显示</s:String>
|
||||||
<s:String x:Key="Str_ListViewStatusBar">共 {0} 项,已选择 {1} 项</s:String>
|
<s:String x:Key="Str_ListViewStatusBar">共 {0} 项,已选择 {1} 项</s:String>
|
||||||
<s:String x:Key="Str_AddSpineObject">添加...</s:String>
|
<s:String x:Key="Str_AddSpineObject">添加...</s:String>
|
||||||
<s:String x:Key="Str_RemoveSpineObject">移除</s:String>
|
<s:String x:Key="Str_OpenSkelFileTitle">选择骨骼文件(skel)</s:String>
|
||||||
|
<s:String x:Key="Str_OpenAtlasFileTitle">选择图集文件(atlas)</s:String>
|
||||||
<s:String x:Key="Str_AddSpineObjectFromClipboard">从剪贴板添加</s:String>
|
<s:String x:Key="Str_AddSpineObjectFromClipboard">从剪贴板添加</s:String>
|
||||||
|
<s:String x:Key="Str_RemoveSpineObject">移除</s:String>
|
||||||
|
<s:String x:Key="Str_RemoveAllSpineObject">移除全部</s:String>
|
||||||
<s:String x:Key="Str_Reload">重新加载</s:String>
|
<s:String x:Key="Str_Reload">重新加载</s:String>
|
||||||
<s:String x:Key="Str_MoveUpSpineObject">上移</s:String>
|
<s:String x:Key="Str_MoveUpSpineObject">上移</s:String>
|
||||||
<s:String x:Key="Str_MoveDownSpineObject">下移</s:String>
|
<s:String x:Key="Str_MoveDownSpineObject">下移</s:String>
|
||||||
@@ -115,10 +118,12 @@
|
|||||||
<s:String x:Key="Str_MaxFps">最大帧率</s:String>
|
<s:String x:Key="Str_MaxFps">最大帧率</s:String>
|
||||||
<s:String x:Key="Str_MaxFpsTooltip">预览画面的最大帧率,设置为 0 时则无帧率限制</s:String>
|
<s:String x:Key="Str_MaxFpsTooltip">预览画面的最大帧率,设置为 0 时则无帧率限制</s:String>
|
||||||
<s:String x:Key="Str_PlaySpeed">播放速度</s:String>
|
<s:String x:Key="Str_PlaySpeed">播放速度</s:String>
|
||||||
|
<s:String x:Key="Str_WallpaperView">桌面投影</s:String>
|
||||||
<s:String x:Key="Str_RenderSelectedOnly">仅渲染选中</s:String>
|
<s:String x:Key="Str_RenderSelectedOnly">仅渲染选中</s:String>
|
||||||
<s:String x:Key="Str_ShowAxis">显示坐标轴</s:String>
|
<s:String x:Key="Str_ShowAxis">显示坐标轴</s:String>
|
||||||
<s:String x:Key="Str_BackgroundColor">背景颜色</s:String>
|
<s:String x:Key="Str_BackgroundColor">背景颜色</s:String>
|
||||||
<s:String x:Key="Str_BackgroundImage">背景图片</s:String>
|
<s:String x:Key="Str_BackgroundImagePath">背景图片路径</s:String>
|
||||||
|
<s:String x:Key="Str_BackgroundImageMode">背景图片模式</s:String>
|
||||||
|
|
||||||
<!-- 渲染画面按钮组 -->
|
<!-- 渲染画面按钮组 -->
|
||||||
<s:String x:Key="Str_StopTooltip">停止</s:String>
|
<s:String x:Key="Str_StopTooltip">停止</s:String>
|
||||||
@@ -232,6 +237,7 @@
|
|||||||
<s:String x:Key="Str_RendererPreference">预览画面选项</s:String>
|
<s:String x:Key="Str_RendererPreference">预览画面选项</s:String>
|
||||||
|
|
||||||
<s:String x:Key="Str_AppPreference">应用程序选项</s:String>
|
<s:String x:Key="Str_AppPreference">应用程序选项</s:String>
|
||||||
|
<s:String x:Key="Str_AssociateFileSuffix">关联文件后缀</s:String>
|
||||||
<s:String x:Key="Str_Language">语言</s:String>
|
<s:String x:Key="Str_Language">语言</s:String>
|
||||||
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
@@ -61,6 +61,18 @@ namespace SpineViewer.Services
|
|||||||
return dialog.ShowDialog() ?? false;
|
return dialog.ShowDialog() ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool ShowOpenFileDialog(out string? fileName, string title = null, string filter = "")
|
||||||
|
{
|
||||||
|
var dialog = new OpenFileDialog() { Title = title, Filter = filter };
|
||||||
|
if (dialog.ShowDialog() is true)
|
||||||
|
{
|
||||||
|
fileName = dialog.FileName;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
fileName = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取用户选择的文件夹
|
/// 获取用户选择的文件夹
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -78,6 +90,22 @@ namespace SpineViewer.Services
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool ShowOpenSFMLImageDialog(out string? fileName, string initialDirectory = "")
|
||||||
|
{
|
||||||
|
var dialog = new OpenFileDialog()
|
||||||
|
{
|
||||||
|
InitialDirectory = initialDirectory,
|
||||||
|
Filter = "SFML Image|*.png;*.jpg;*.jpeg;*.bmp;*.tga|All|*.*"
|
||||||
|
};
|
||||||
|
if (dialog.ShowDialog() is true)
|
||||||
|
{
|
||||||
|
fileName = dialog.FileName;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
fileName = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public static bool ShowOpenJsonDialog(out string? fileName, string initialDirectory = "")
|
public static bool ShowOpenJsonDialog(out string? fileName, string initialDirectory = "")
|
||||||
{
|
{
|
||||||
var dialog = new OpenFileDialog()
|
var dialog = new OpenFileDialog()
|
||||||
|
|||||||
@@ -7,19 +7,24 @@
|
|||||||
<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.15.14</Version>
|
<Version>0.15.19</Version>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<UseWPF>true</UseWPF>
|
<UseWPF>true</UseWPF>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<NoWarn>$(NoWarn);NETSDK1206</NoWarn>
|
<NoWarn>$(NoWarn);NETSDK1206</NoWarn>
|
||||||
<ApplicationIcon>appicon.ico</ApplicationIcon>
|
<ApplicationIcon>Resources\Images\spineviewer.ico</ApplicationIcon>
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="appicon.ico" />
|
<Content Include="Resources\Images\skel.ico">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="Resources\Images\spineviewer.ico">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
Size = new(bounds.Width, -bounds.Height),
|
Size = new(bounds.Width, -bounds.Height),
|
||||||
Format = SkiaSharp.SKEncodedImageFormat.Webp,
|
Format = SkiaSharp.SKEncodedImageFormat.Webp,
|
||||||
Quality = PreviewQuality,
|
Quality = PreviewQuality,
|
||||||
|
BackgroundColor = SFML.Graphics.Color.Transparent,
|
||||||
};
|
};
|
||||||
exporter.Export(m.PreviewFilePath, sp);
|
exporter.Export(m.PreviewFilePath, sp);
|
||||||
}
|
}
|
||||||
@@ -199,6 +200,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
{
|
{
|
||||||
Format = SkiaSharp.SKEncodedImageFormat.Webp,
|
Format = SkiaSharp.SKEncodedImageFormat.Webp,
|
||||||
Quality = PreviewQuality,
|
Quality = PreviewQuality,
|
||||||
|
BackgroundColor = SFML.Graphics.Color.Transparent,
|
||||||
};
|
};
|
||||||
for (int i = 0; i < totalCount; i++)
|
for (int i = 0; i < totalCount; i++)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using SFMLRenderer;
|
|||||||
using SpineViewer.Models;
|
using SpineViewer.Models;
|
||||||
using SpineViewer.Services;
|
using SpineViewer.Services;
|
||||||
using SpineViewer.Utils;
|
using SpineViewer.Utils;
|
||||||
|
using System.Windows;
|
||||||
using System.Windows.Shell;
|
using System.Windows.Shell;
|
||||||
|
|
||||||
namespace SpineViewer.ViewModels.MainWindow
|
namespace SpineViewer.ViewModels.MainWindow
|
||||||
@@ -16,9 +17,10 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
{
|
{
|
||||||
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
public MainWindowViewModel(ISFMLRenderer sfmlRenderer)
|
public MainWindowViewModel(ISFMLRenderer sfmlRenderer, ISFMLRenderer wallpaperRenderer)
|
||||||
{
|
{
|
||||||
_sfmlRenderer = sfmlRenderer;
|
_sfmlRenderer = sfmlRenderer;
|
||||||
|
_wallpaperRenderer = wallpaperRenderer;
|
||||||
_explorerListViewModel = new(this);
|
_explorerListViewModel = new(this);
|
||||||
_spineObjectListViewModel = new(this);
|
_spineObjectListViewModel = new(this);
|
||||||
_sfmlRendererViewModel = new(this);
|
_sfmlRendererViewModel = new(this);
|
||||||
@@ -33,6 +35,9 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
public ISFMLRenderer SFMLRenderer => _sfmlRenderer;
|
public ISFMLRenderer SFMLRenderer => _sfmlRenderer;
|
||||||
private readonly ISFMLRenderer _sfmlRenderer;
|
private readonly ISFMLRenderer _sfmlRenderer;
|
||||||
|
|
||||||
|
public ISFMLRenderer WallpaperRenderer => _wallpaperRenderer;
|
||||||
|
private readonly ISFMLRenderer _wallpaperRenderer;
|
||||||
|
|
||||||
public TaskbarItemProgressState ProgressState { get => _progressState; set => SetProperty(ref _progressState, value); }
|
public TaskbarItemProgressState ProgressState { get => _progressState; set => SetProperty(ref _progressState, value); }
|
||||||
private TaskbarItemProgressState _progressState = TaskbarItemProgressState.None;
|
private TaskbarItemProgressState _progressState = TaskbarItemProgressState.None;
|
||||||
|
|
||||||
@@ -72,6 +77,16 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
public SFMLRendererViewModel SFMLRendererViewModel => _sfmlRendererViewModel;
|
public SFMLRendererViewModel SFMLRendererViewModel => _sfmlRendererViewModel;
|
||||||
private readonly SFMLRendererViewModel _sfmlRendererViewModel;
|
private readonly SFMLRendererViewModel _sfmlRendererViewModel;
|
||||||
|
|
||||||
|
public RelayCommand Cmd_SwitchWallpaperView => _cmd_SwitchWallpaperView ??= new(() =>
|
||||||
|
{
|
||||||
|
_preferenceViewModel.WallpaperView = !_preferenceViewModel.WallpaperView;
|
||||||
|
_preferenceViewModel.SavePreference();
|
||||||
|
});
|
||||||
|
private RelayCommand _cmd_SwitchWallpaperView;
|
||||||
|
|
||||||
|
public RelayCommand Cmd_Exit => _cmd_Exit ??= new(App.Current.Shutdown);
|
||||||
|
private RelayCommand? _cmd_Exit;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 打开工作区
|
/// 打开工作区
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -131,18 +146,5 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 调试命令
|
|
||||||
/// </summary>
|
|
||||||
public RelayCommand Cmd_Debug => _cmd_Debug ??= new(Debug_Execute);
|
|
||||||
private RelayCommand? _cmd_Debug;
|
|
||||||
|
|
||||||
private void Debug_Execute()
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
MessagePopupService.Quest("测试一下");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using Microsoft.Win32;
|
||||||
using NLog;
|
using NLog;
|
||||||
using Spine.SpineWrappers;
|
using Spine.SpineWrappers;
|
||||||
using SpineViewer.Models;
|
using SpineViewer.Models;
|
||||||
|
using SpineViewer.Natives;
|
||||||
using SpineViewer.Services;
|
using SpineViewer.Services;
|
||||||
using SpineViewer.Utils;
|
using SpineViewer.Utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -23,6 +26,10 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly string PreferenceFilePath = Path.Combine(Path.GetDirectoryName(Environment.ProcessPath), "preference.json");
|
public static readonly string PreferenceFilePath = Path.Combine(Path.GetDirectoryName(Environment.ProcessPath), "preference.json");
|
||||||
|
|
||||||
|
private static readonly string SkelFileDescription = "SpineViewer File";
|
||||||
|
private static readonly string SkelIconFilePath = Path.Combine(App.ProcessDirectory, "Resources\\Images\\skel.ico");
|
||||||
|
private static readonly string ShellOpenCommand = $"\"{App.ProcessPath}\" \"%1\"";
|
||||||
|
|
||||||
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
private readonly MainWindowViewModel _vmMain;
|
private readonly MainWindowViewModel _vmMain;
|
||||||
@@ -63,8 +70,19 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void LoadPreference()
|
public void LoadPreference()
|
||||||
{
|
{
|
||||||
if (JsonHelper.Deserialize<PreferenceModel>(PreferenceFilePath, out var obj, true))
|
if (JsonHelper.Deserialize<PreferenceModel>(PreferenceFilePath, out var obj, true))
|
||||||
Preference = obj;
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Preference = obj;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
|
_logger.Error("Failed to load some prefereneces, {0}", ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -93,7 +111,9 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
DebugPoints = DebugPoints,
|
DebugPoints = DebugPoints,
|
||||||
DebugClippings = DebugClippings,
|
DebugClippings = DebugClippings,
|
||||||
|
|
||||||
|
WallpaperView = WallpaperView,
|
||||||
RenderSelectedOnly = RenderSelectedOnly,
|
RenderSelectedOnly = RenderSelectedOnly,
|
||||||
|
AssociateFileSuffix = AssociateFileSuffix,
|
||||||
AppLanguage = AppLanguage,
|
AppLanguage = AppLanguage,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -117,7 +137,9 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
DebugPoints = value.DebugPoints;
|
DebugPoints = value.DebugPoints;
|
||||||
DebugClippings = value.DebugClippings;
|
DebugClippings = value.DebugClippings;
|
||||||
|
|
||||||
|
WallpaperView = value.WallpaperView;
|
||||||
RenderSelectedOnly = value.RenderSelectedOnly;
|
RenderSelectedOnly = value.RenderSelectedOnly;
|
||||||
|
AssociateFileSuffix = value.AssociateFileSuffix;
|
||||||
AppLanguage = value.AppLanguage;
|
AppLanguage = value.AppLanguage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,12 +246,90 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
|
|
||||||
public static ImmutableArray<AppLanguage> AppLanguageOptions { get; } = Enum.GetValues<AppLanguage>().ToImmutableArray();
|
public static ImmutableArray<AppLanguage> AppLanguageOptions { get; } = Enum.GetValues<AppLanguage>().ToImmutableArray();
|
||||||
|
|
||||||
|
public bool AutoRun
|
||||||
|
{
|
||||||
|
get => throw new NotImplementedException();
|
||||||
|
set => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool WallpaperView
|
||||||
|
{
|
||||||
|
get => _wallpaperView;
|
||||||
|
set => SetProperty(ref _wallpaperView, value);
|
||||||
|
}
|
||||||
|
private bool _wallpaperView; // UI 变化通过 PropertyChanged 事件交由 View 层处理
|
||||||
|
|
||||||
public bool RenderSelectedOnly
|
public bool RenderSelectedOnly
|
||||||
{
|
{
|
||||||
get => _vmMain.SFMLRendererViewModel.RenderSelectedOnly;
|
get => _vmMain.SFMLRendererViewModel.RenderSelectedOnly;
|
||||||
set => SetProperty(_vmMain.SFMLRendererViewModel.RenderSelectedOnly, value, v => _vmMain.SFMLRendererViewModel.RenderSelectedOnly = v);
|
set => SetProperty(_vmMain.SFMLRendererViewModel.RenderSelectedOnly, value, v => _vmMain.SFMLRendererViewModel.RenderSelectedOnly = v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool AssociateFileSuffix
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 检查 .skel 的 ProgID
|
||||||
|
using (var key = Registry.CurrentUser.OpenSubKey(@"Software\Classes\.skel"))
|
||||||
|
{
|
||||||
|
var progIdValue = key?.GetValue("") as string;
|
||||||
|
if (!string.Equals(progIdValue, App.ProgId, StringComparison.OrdinalIgnoreCase))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 command 指令是否相同
|
||||||
|
using (var key = Registry.CurrentUser.OpenSubKey($@"Software\Classes\{App.ProgId}\shell\open\command"))
|
||||||
|
{
|
||||||
|
var command = key?.GetValue("") as string;
|
||||||
|
if (string.IsNullOrWhiteSpace(command))
|
||||||
|
return false;
|
||||||
|
return command == ShellOpenCommand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetProperty(AssociateFileSuffix, value, v =>
|
||||||
|
{
|
||||||
|
if (v)
|
||||||
|
{
|
||||||
|
// 文件关联
|
||||||
|
using (var key = Registry.CurrentUser.CreateSubKey(@"Software\Classes\.skel"))
|
||||||
|
{
|
||||||
|
key?.SetValue("", App.ProgId);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var key = Registry.CurrentUser.CreateSubKey($@"Software\Classes\{App.ProgId}"))
|
||||||
|
{
|
||||||
|
key?.SetValue("", SkelFileDescription);
|
||||||
|
using (var iconKey = key?.CreateSubKey("DefaultIcon"))
|
||||||
|
{
|
||||||
|
iconKey?.SetValue("", $"\"{SkelIconFilePath}\"");
|
||||||
|
}
|
||||||
|
using (var shellKey = key?.CreateSubKey(@"shell\open\command"))
|
||||||
|
{
|
||||||
|
shellKey?.SetValue("", ShellOpenCommand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 删除关联
|
||||||
|
Registry.CurrentUser.DeleteSubKeyTree(@"Software\Classes\.skel", false);
|
||||||
|
Registry.CurrentUser.DeleteSubKeyTree($@"Software\Classes\{App.ProgId}", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Shell32.NotifyAssociationChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public AppLanguage AppLanguage
|
public AppLanguage AppLanguage
|
||||||
{
|
{
|
||||||
get => ((App)App.Current).Language;
|
get => ((App)App.Current).Language;
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ using SpineViewer.Services;
|
|||||||
using SpineViewer.Utils;
|
using SpineViewer.Utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -23,6 +25,8 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
{
|
{
|
||||||
public class SFMLRendererViewModel : ObservableObject
|
public class SFMLRendererViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
|
public ImmutableArray<Stretch> StretchOptions { get; } = Enum.GetValues<Stretch>().ToImmutableArray();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 日志器
|
/// 日志器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -31,6 +35,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
private readonly MainWindowViewModel _vmMain;
|
private readonly MainWindowViewModel _vmMain;
|
||||||
private readonly ObservableCollectionWithLock<SpineObjectModel> _models;
|
private readonly ObservableCollectionWithLock<SpineObjectModel> _models;
|
||||||
private readonly ISFMLRenderer _renderer;
|
private readonly ISFMLRenderer _renderer;
|
||||||
|
private readonly ISFMLRenderer _wallpaperRenderer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 被选中对象的背景颜色
|
/// 被选中对象的背景颜色
|
||||||
@@ -69,6 +74,13 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
private float _forwardDelta = 0;
|
private float _forwardDelta = 0;
|
||||||
private readonly object _forwardDeltaLock = new();
|
private readonly object _forwardDeltaLock = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 背景图片
|
||||||
|
/// </summary>
|
||||||
|
private SFML.Graphics.Sprite? _backgroundImageSprite; // XXX: 暂时未使用 Dispose 释放
|
||||||
|
private SFML.Graphics.Texture? _backgroundImageTexture; // XXX: 暂时未使用 Dispose 释放
|
||||||
|
private readonly object _bgLock = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 临时变量, 记录拖放世界源点
|
/// 临时变量, 记录拖放世界源点
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -79,6 +91,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
_vmMain = vmMain;
|
_vmMain = vmMain;
|
||||||
_models = _vmMain.SpineObjects;
|
_models = _vmMain.SpineObjects;
|
||||||
_renderer = _vmMain.SFMLRenderer;
|
_renderer = _vmMain.SFMLRenderer;
|
||||||
|
_wallpaperRenderer = _vmMain.WallpaperRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -169,6 +182,64 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
}
|
}
|
||||||
private SFML.Graphics.Color _backgroundColor = new(105, 105, 105);
|
private SFML.Graphics.Color _backgroundColor = new(105, 105, 105);
|
||||||
|
|
||||||
|
public string BackgroundImagePath
|
||||||
|
{
|
||||||
|
get => _backgroundImagePath;
|
||||||
|
set => SetProperty(_backgroundImagePath, value, v =>
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(v))
|
||||||
|
{
|
||||||
|
lock (_bgLock)
|
||||||
|
{
|
||||||
|
_backgroundImageSprite?.Dispose();
|
||||||
|
_backgroundImageTexture?.Dispose();
|
||||||
|
_backgroundImageTexture = null;
|
||||||
|
_backgroundImageSprite = null;
|
||||||
|
}
|
||||||
|
_backgroundImagePath = v;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!File.Exists(v))
|
||||||
|
{
|
||||||
|
_logger.Warn("Omit non-existed background image path, {0}", v);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SFML.Graphics.Texture tex = null;
|
||||||
|
SFML.Graphics.Sprite sprite = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
tex = new(v);
|
||||||
|
sprite = new(tex) { Origin = new(tex.Size.X / 2f, tex.Size.Y / 2f) };
|
||||||
|
lock (_bgLock)
|
||||||
|
{
|
||||||
|
_backgroundImageSprite?.Dispose();
|
||||||
|
_backgroundImageTexture?.Dispose();
|
||||||
|
_backgroundImageTexture = tex;
|
||||||
|
_backgroundImageSprite = sprite;
|
||||||
|
}
|
||||||
|
_backgroundImagePath = v;
|
||||||
|
_logger.Info("Load background image from {0}", v);
|
||||||
|
_logger.LogCurrentProcessMemoryUsage();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
sprite?.Dispose();
|
||||||
|
tex?.Dispose();
|
||||||
|
_logger.Error("Failed to load background image from path: {0}, {1}", v, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
private string _backgroundImagePath;
|
||||||
|
|
||||||
|
public Stretch BackgroundImageMode
|
||||||
|
{
|
||||||
|
get => _backgroundImageMode;
|
||||||
|
set => SetProperty(ref _backgroundImageMode, value);
|
||||||
|
}
|
||||||
|
private Stretch _backgroundImageMode = Stretch.Uniform;
|
||||||
|
|
||||||
public bool RenderSelectedOnly
|
public bool RenderSelectedOnly
|
||||||
{
|
{
|
||||||
get => _renderSelectedOnly;
|
get => _renderSelectedOnly;
|
||||||
@@ -189,6 +260,14 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
}
|
}
|
||||||
private bool _isUpdating = true;
|
private bool _isUpdating = true;
|
||||||
|
|
||||||
|
public RelayCommand Cmd_SelectBackgroundImage => _cmd_SelectBackgroundImage ??= new(() =>
|
||||||
|
{
|
||||||
|
if (!DialogService.ShowOpenSFMLImageDialog(out var fileName))
|
||||||
|
return;
|
||||||
|
BackgroundImagePath = fileName;
|
||||||
|
});
|
||||||
|
private RelayCommand? _cmd_SelectBackgroundImage;
|
||||||
|
|
||||||
public RelayCommand Cmd_Stop => _cmd_Stop ??= new(() =>
|
public RelayCommand Cmd_Stop => _cmd_Stop ??= new(() =>
|
||||||
{
|
{
|
||||||
IsUpdating = false;
|
IsUpdating = false;
|
||||||
@@ -366,6 +445,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_wallpaperRenderer.SetActive(true);
|
||||||
_renderer.SetActive(true);
|
_renderer.SetActive(true);
|
||||||
|
|
||||||
float delta;
|
float delta;
|
||||||
@@ -384,7 +464,44 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
_forwardDelta = 0;
|
_forwardDelta = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using var v = _renderer.GetView();
|
||||||
|
_wallpaperRenderer.SetView(v);
|
||||||
|
|
||||||
_renderer.Clear(_backgroundColor);
|
_renderer.Clear(_backgroundColor);
|
||||||
|
_wallpaperRenderer.Clear(_backgroundColor);
|
||||||
|
|
||||||
|
// 渲染背景
|
||||||
|
lock (_bgLock)
|
||||||
|
{
|
||||||
|
if (_backgroundImageSprite is not null)
|
||||||
|
{
|
||||||
|
using var view = _renderer.GetView();
|
||||||
|
var bg = _backgroundImageSprite;
|
||||||
|
var viewSize = view.Size;
|
||||||
|
var bgSize = bg.Texture.Size;
|
||||||
|
var scaleX = Math.Abs(viewSize.X / bgSize.X);
|
||||||
|
var scaleY = Math.Abs(viewSize.Y / bgSize.Y);
|
||||||
|
var signX = Math.Sign(viewSize.X);
|
||||||
|
var signY = Math.Sign(viewSize.Y);
|
||||||
|
if (_backgroundImageMode == Stretch.None)
|
||||||
|
{
|
||||||
|
scaleX = scaleY = 1f / _renderer.Zoom;
|
||||||
|
}
|
||||||
|
else if (_backgroundImageMode == Stretch.Uniform)
|
||||||
|
{
|
||||||
|
scaleX = scaleY = Math.Min(scaleX, scaleY);
|
||||||
|
}
|
||||||
|
else if (_backgroundImageMode == Stretch.UniformToFill)
|
||||||
|
{
|
||||||
|
scaleX = scaleY = Math.Max(scaleX, scaleY);
|
||||||
|
}
|
||||||
|
bg.Scale = new(signX * scaleX, signY * scaleY);
|
||||||
|
bg.Position = view.Center;
|
||||||
|
bg.Rotation = view.Rotation;
|
||||||
|
_renderer.Draw(bg);
|
||||||
|
_wallpaperRenderer.Draw(bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_showAxis)
|
if (_showAxis)
|
||||||
{
|
{
|
||||||
@@ -422,10 +539,12 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
sp.EnableDebug = true;
|
sp.EnableDebug = true;
|
||||||
_renderer.Draw(sp);
|
_renderer.Draw(sp);
|
||||||
sp.EnableDebug = false;
|
sp.EnableDebug = false;
|
||||||
|
_wallpaperRenderer.Draw(sp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderer.Display();
|
_renderer.Display();
|
||||||
|
_wallpaperRenderer.Display();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -437,12 +556,12 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_renderer.SetActive(false);
|
_renderer.SetActive(false);
|
||||||
|
_wallpaperRenderer.SetActive(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public RendererWorkspaceConfigModel WorkspaceConfig
|
public RendererWorkspaceConfigModel WorkspaceConfig
|
||||||
{
|
{
|
||||||
// TODO: 背景图片
|
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return new()
|
return new()
|
||||||
@@ -459,6 +578,8 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
Speed = Speed,
|
Speed = Speed,
|
||||||
ShowAxis = ShowAxis,
|
ShowAxis = ShowAxis,
|
||||||
BackgroundColor = BackgroundColor,
|
BackgroundColor = BackgroundColor,
|
||||||
|
BackgroundImagePath = BackgroundImagePath,
|
||||||
|
BackgroundImageMode = BackgroundImageMode,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
@@ -474,6 +595,8 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
Speed = value.Speed;
|
Speed = value.Speed;
|
||||||
ShowAxis = value.ShowAxis;
|
ShowAxis = value.ShowAxis;
|
||||||
BackgroundColor = value.BackgroundColor;
|
BackgroundColor = value.BackgroundColor;
|
||||||
|
BackgroundImagePath = value.BackgroundImagePath;
|
||||||
|
BackgroundImageMode = value.BackgroundImageMode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using SpineViewer.ViewModels.Exporters;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -45,6 +46,11 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
_customFFmpegExporterViewModel = new(_vmMain);
|
_customFFmpegExporterViewModel = new(_vmMain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 请求选中项发生变化
|
||||||
|
/// </summary>
|
||||||
|
public event NotifyCollectionChangedEventHandler? RequestSelectionChanging;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 单帧导出 ViewModel
|
/// 单帧导出 ViewModel
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -101,7 +107,12 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
|
|
||||||
private void AddSpineObject_Execute()
|
private void AddSpineObject_Execute()
|
||||||
{
|
{
|
||||||
MessagePopupService.Info("Not Implemented, please drag files into here or add them from clipboard :)");
|
if (!DialogService.ShowOpenFileDialog(out var skelFileName, AppResource.Str_OpenSkelFileTitle))
|
||||||
|
return;
|
||||||
|
if (!DialogService.ShowOpenFileDialog(out var atlasFileName, AppResource.Str_OpenAtlasFileTitle))
|
||||||
|
return;
|
||||||
|
AddSpineObject(skelFileName, atlasFileName);
|
||||||
|
_logger.LogCurrentProcessMemoryUsage();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -138,6 +149,34 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 移除全部模型
|
||||||
|
/// </summary>
|
||||||
|
public RelayCommand<IList?> Cmd_RemoveAllSpineObject => _cmd_RemoveAllSpineObject ??= new(RemoveAllSpineObject_Execute, RemoveAllSpineObject_CanExecute);
|
||||||
|
private RelayCommand<IList?>? _cmd_RemoveAllSpineObject;
|
||||||
|
|
||||||
|
private void RemoveAllSpineObject_Execute(IList? args)
|
||||||
|
{
|
||||||
|
if (!RemoveAllSpineObject_CanExecute(args)) return;
|
||||||
|
|
||||||
|
if (!MessagePopupService.Quest(string.Format(AppResource.Str_RemoveItemsQuest, args.Count)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
lock (_spineObjectModels.Lock)
|
||||||
|
{
|
||||||
|
foreach (var sp in _spineObjectModels)
|
||||||
|
sp.Dispose();
|
||||||
|
_spineObjectModels.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool RemoveAllSpineObject_CanExecute(IList? args)
|
||||||
|
{
|
||||||
|
if (args is null) return false;
|
||||||
|
if (args.Count <= 0) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 从剪贴板文件列表添加模型
|
/// 从剪贴板文件列表添加模型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -174,6 +213,8 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
spNew.ObjectConfig = sp.ObjectConfig;
|
spNew.ObjectConfig = sp.ObjectConfig;
|
||||||
_spineObjectModels[idx] = spNew;
|
_spineObjectModels[idx] = spNew;
|
||||||
sp.Dispose();
|
sp.Dispose();
|
||||||
|
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
|
||||||
|
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, spNew));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -229,6 +270,11 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
_spineObjectModels[idx] = spNew;
|
_spineObjectModels[idx] = spNew;
|
||||||
sp.Dispose();
|
sp.Dispose();
|
||||||
success++;
|
success++;
|
||||||
|
Application.Current.Dispatcher.BeginInvoke(() =>
|
||||||
|
{
|
||||||
|
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
|
||||||
|
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, spNew));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -457,7 +503,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
{
|
{
|
||||||
if (ct.IsCancellationRequested) break;
|
if (ct.IsCancellationRequested) break;
|
||||||
|
|
||||||
var skelPath = paths[i];
|
var skelPath = paths[totalCount - 1 - i]; // 从后往前添加, 每次插入到列表的第一个
|
||||||
reporter.ProgressText = $"[{i}/{totalCount}] {skelPath}";
|
reporter.ProgressText = $"[{i}/{totalCount}] {skelPath}";
|
||||||
|
|
||||||
if (AddSpineObject(skelPath))
|
if (AddSpineObject(skelPath))
|
||||||
@@ -480,7 +526,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 安全地在末尾添加一个模型, 发生错误会输出日志
|
/// 安全地在列表头添加一个模型, 发生错误会输出日志
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>是否添加成功</returns>
|
/// <returns>是否添加成功</returns>
|
||||||
private bool AddSpineObject(string skelPath, string? atlasPath = null)
|
private bool AddSpineObject(string skelPath, string? atlasPath = null)
|
||||||
@@ -488,7 +534,20 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var sp = new SpineObjectModel(skelPath, atlasPath);
|
var sp = new SpineObjectModel(skelPath, atlasPath);
|
||||||
lock (_spineObjectModels.Lock) _spineObjectModels.Add(sp);
|
lock (_spineObjectModels.Lock) _spineObjectModels.Insert(0, sp);
|
||||||
|
if (Application.Current.Dispatcher.CheckAccess())
|
||||||
|
{
|
||||||
|
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
|
||||||
|
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
|
||||||
|
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp));
|
||||||
|
});
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -499,22 +558,6 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool AddSpineObject(SpineObjectWorkspaceConfigModel cfg)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var sp = new SpineObjectModel(cfg);
|
|
||||||
lock (_spineObjectModels.Lock) _spineObjectModels.Add(sp);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.Trace(ex.ToString());
|
|
||||||
_logger.Error("Failed to load: {0}, {1}", cfg.SkelPath, ex.Message);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<SpineObjectWorkspaceConfigModel> LoadedSpineObjects
|
public List<SpineObjectWorkspaceConfigModel> LoadedSpineObjects
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -577,7 +620,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
{
|
{
|
||||||
if (ct.IsCancellationRequested) break;
|
if (ct.IsCancellationRequested) break;
|
||||||
|
|
||||||
var cfg = models[i];
|
var cfg = models[totalCount - 1 - i]; // 从后往前添加, 每次插入到列表的第一个
|
||||||
reporter.ProgressText = $"[{i}/{totalCount}] {cfg}";
|
reporter.ProgressText = $"[{i}/{totalCount}] {cfg}";
|
||||||
|
|
||||||
if (AddSpineObject(cfg))
|
if (AddSpineObject(cfg))
|
||||||
@@ -605,5 +648,38 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
sp.ResetAnimationsTime();
|
sp.ResetAnimationsTime();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 安全地在列表头添加一个模型, 发生错误会输出日志
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>是否添加成功</returns>
|
||||||
|
private bool AddSpineObject(SpineObjectWorkspaceConfigModel cfg)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var sp = new SpineObjectModel(cfg);
|
||||||
|
lock (_spineObjectModels.Lock) _spineObjectModels.Insert(0, sp);
|
||||||
|
if (Application.Current.Dispatcher.CheckAccess())
|
||||||
|
{
|
||||||
|
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
|
||||||
|
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
|
||||||
|
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Trace(ex.ToString());
|
||||||
|
_logger.Error("Failed to load: {0}, {1}", cfg.SkelPath, ex.Message);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -334,27 +334,31 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
|
|
||||||
public ObservableCollection<SkinViewModel> Skins => _skins;
|
public ObservableCollection<SkinViewModel> Skins => _skins;
|
||||||
|
|
||||||
public RelayCommand<IList?> Cmd_EnableSkins { get; } = new(
|
public RelayCommand<IList?> Cmd_EnableSkins => _cmd_EnableSkins ??= new (
|
||||||
args => { if (args is null) return; foreach (var s in args.OfType<SkinViewModel>()) s.Status = true; },
|
args => { if (args is null) return; foreach (var s in args.OfType<SkinViewModel>()) s.Status = true; },
|
||||||
args => { return args is not null && args.OfType<SkinViewModel>().Any(); }
|
args => { return args is not null && args.OfType<SkinViewModel>().Any(); }
|
||||||
);
|
);
|
||||||
|
private RelayCommand<IList?> _cmd_EnableSkins;
|
||||||
|
|
||||||
public RelayCommand<IList?> Cmd_DisableSkins { get; } = new(
|
public RelayCommand<IList?> Cmd_DisableSkins => _cmd_DisableSkins ??= new (
|
||||||
args => { if (args is null) return; foreach (var s in args.OfType<SkinViewModel>()) s.Status = false; },
|
args => { if (args is null) return; foreach (var s in args.OfType<SkinViewModel>()) s.Status = false; },
|
||||||
args => { return args is not null && args.OfType<SkinViewModel>().Any(); }
|
args => { return args is not null && args.OfType<SkinViewModel>().Any(); }
|
||||||
);
|
);
|
||||||
|
private RelayCommand<IList?> _cmd_DisableSkins;
|
||||||
|
|
||||||
public ObservableCollection<SlotViewModel> Slots => _slots;
|
public ObservableCollection<SlotViewModel> Slots => _slots;
|
||||||
|
|
||||||
public RelayCommand<IList?> Cmd_EnableSlots { get; } = new(
|
public RelayCommand<IList?> Cmd_EnableSlots => _cmd_EnableSlots ??= new (
|
||||||
args => { if (args is null) return; foreach (var s in args.OfType<SlotViewModel>()) s.Visible = true; },
|
args => { if (args is null) return; foreach (var s in args.OfType<SlotViewModel>()) s.Visible = true; },
|
||||||
args => { return args is not null && args.OfType<SlotViewModel>().Any(); }
|
args => { return args is not null && args.OfType<SlotViewModel>().Any(); }
|
||||||
);
|
);
|
||||||
|
private RelayCommand<IList?> _cmd_EnableSlots;
|
||||||
|
|
||||||
public RelayCommand<IList?> Cmd_DisableSlots { get; } = new(
|
public RelayCommand<IList?> Cmd_DisableSlots => _cmd_DisableSlots ??= new (
|
||||||
args => { if (args is null) return; foreach (var s in args.OfType<SlotViewModel>()) s.Visible = false; },
|
args => { if (args is null) return; foreach (var s in args.OfType<SlotViewModel>()) s.Visible = false; },
|
||||||
args => { return args is not null && args.OfType<SlotViewModel>().Any(); }
|
args => { return args is not null && args.OfType<SlotViewModel>().Any(); }
|
||||||
);
|
);
|
||||||
|
private RelayCommand<IList?> _cmd_DisableSlots;
|
||||||
|
|
||||||
public ObservableCollection<AnimationTrackViewModel> AnimationTracks => _animationTracks;
|
public ObservableCollection<AnimationTrackViewModel> AnimationTracks => _animationTracks;
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
xmlns:utils="clr-namespace:SpineViewer.Utils"
|
xmlns:utils="clr-namespace:SpineViewer.Utils"
|
||||||
xmlns:SFMLRenderer="clr-namespace:SFMLRenderer;assembly=SFMLRenderer"
|
xmlns:SFMLRenderer="clr-namespace:SFMLRenderer;assembly=SFMLRenderer"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
|
x:Name="_mainWindow"
|
||||||
Title="{Binding Title}"
|
Title="{Binding Title}"
|
||||||
Width="1500"
|
Width="1500"
|
||||||
Height="800"
|
Height="800"
|
||||||
@@ -68,7 +69,7 @@
|
|||||||
<MenuItem Header="{DynamicResource Str_Diagnostics}" Command="{Binding Cmd_ShowDiagnosticsDialog}"/>
|
<MenuItem Header="{DynamicResource Str_Diagnostics}" Command="{Binding Cmd_ShowDiagnosticsDialog}"/>
|
||||||
<Separator/>
|
<Separator/>
|
||||||
<MenuItem Header="{DynamicResource Str_Abount}" Command="{Binding Cmd_ShowAboutDialog}"/>
|
<MenuItem Header="{DynamicResource Str_Abount}" Command="{Binding Cmd_ShowAboutDialog}"/>
|
||||||
<MenuItem Header="{DynamicResource Str_Debug}" Command="{Binding Cmd_Debug}"/>
|
<MenuItem Header="{DynamicResource Str_Debug}" Click="DebugMenuItem_Click"/>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<!--<MenuItem Header="{DynamicResource Str_Experiment}"/>-->
|
<!--<MenuItem Header="{DynamicResource Str_Experiment}"/>-->
|
||||||
</Menu>
|
</Menu>
|
||||||
@@ -147,13 +148,16 @@
|
|||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<MenuItem Header="{DynamicResource Str_AddSpineObject}"
|
<MenuItem Header="{DynamicResource Str_AddSpineObject}"
|
||||||
Command="{Binding Cmd_AddSpineObject}"/>
|
Command="{Binding Cmd_AddSpineObject}"/>
|
||||||
|
<MenuItem Header="{DynamicResource Str_AddSpineObjectFromClipboard}"
|
||||||
|
InputGestureText="Ctrl+V"
|
||||||
|
Command="{Binding Cmd_AddSpineObjectFromClipboard}"/>
|
||||||
<MenuItem Header="{DynamicResource Str_RemoveSpineObject}"
|
<MenuItem Header="{DynamicResource Str_RemoveSpineObject}"
|
||||||
InputGestureText="Delete"
|
InputGestureText="Delete"
|
||||||
Command="{Binding Cmd_RemoveSpineObject}"
|
Command="{Binding Cmd_RemoveSpineObject}"
|
||||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||||
<MenuItem Header="{DynamicResource Str_AddSpineObjectFromClipboard}"
|
<MenuItem Header="{DynamicResource Str_RemoveAllSpineObject}"
|
||||||
InputGestureText="Ctrl+V"
|
Command="{Binding Cmd_RemoveAllSpineObject}"
|
||||||
Command="{Binding Cmd_AddSpineObjectFromClipboard}"/>
|
CommandParameter="{Binding PlacementTarget.Items, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||||
<MenuItem Header="{DynamicResource Str_Reload}"
|
<MenuItem Header="{DynamicResource Str_Reload}"
|
||||||
InputGestureText="Ctrl+R"
|
InputGestureText="Ctrl+R"
|
||||||
Command="{Binding Cmd_ReloadSpineObject}"
|
Command="{Binding Cmd_ReloadSpineObject}"
|
||||||
@@ -476,7 +480,7 @@
|
|||||||
<ColumnDefinition Width="Auto" SharedSizeGroup="ColAniTime"/>
|
<ColumnDefinition Width="Auto" SharedSizeGroup="ColAniTime"/>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<Label Grid.Column="0" Content="{Binding TrackIndex}" HorizontalContentAlignment="Left" VerticalAlignment="Top" Background="#bfffffff"/>
|
<Label Grid.Column="0" Content="{Binding TrackIndex}" HorizontalContentAlignment="Left" VerticalAlignment="Top" Background="#bfffffff"/>
|
||||||
<Label Grid.Column="1" Content="{Binding AnimationDuration}" VerticalAlignment="Top" ContentStringFormat="{}{0:F3} s"/>
|
<Label Grid.Column="1" Content="{Binding AnimationDuration}" VerticalAlignment="Top" ContentStringFormat="{}{0:F3} s"/>
|
||||||
|
|
||||||
@@ -723,6 +727,8 @@
|
|||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<!-- 水平分辨率 -->
|
<!-- 水平分辨率 -->
|
||||||
@@ -776,7 +782,15 @@
|
|||||||
<TextBox Grid.Row="12" Grid.Column="1" Text="{Binding BackgroundColor}" ToolTip="#AARRGGBB"/>
|
<TextBox Grid.Row="12" Grid.Column="1" Text="{Binding BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||||
|
|
||||||
<!-- 背景图案 -->
|
<!-- 背景图案 -->
|
||||||
|
<Label Grid.Row="13" Grid.Column="0" Content="{DynamicResource Str_BackgroundImagePath}"/>
|
||||||
|
<DockPanel Grid.Row="13" Grid.Column="1" >
|
||||||
|
<Button DockPanel.Dock="Right" Content="..." Command="{Binding Cmd_SelectBackgroundImage}"/>
|
||||||
|
<TextBox Text="{Binding BackgroundImagePath}"/>
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
<!-- 背景图案模式 -->
|
<!-- 背景图案模式 -->
|
||||||
|
<Label Grid.Row="14" Grid.Column="0" Content="{DynamicResource Str_BackgroundImageMode}"/>
|
||||||
|
<ComboBox Grid.Row="14" Grid.Column="1" SelectedValue="{Binding BackgroundImageMode}" ItemsSource="{Binding StretchOptions}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
</TabControl>
|
</TabControl>
|
||||||
@@ -918,6 +932,21 @@
|
|||||||
Opened="BottomPopup_Opened"
|
Opened="BottomPopup_Opened"
|
||||||
MouseLeave="PopupContainer_MouseLeave"/>
|
MouseLeave="PopupContainer_MouseLeave"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<!-- 非可视元素通知栏图标 -->
|
||||||
|
<hc:NotifyIcon x:Name="_notifyIcon"
|
||||||
|
Icon="/Resources/Images/spineviewer.ico"
|
||||||
|
Click="_notifyIcon_Click"
|
||||||
|
MouseDoubleClick="_notifyIcon_MouseDoubleClick">
|
||||||
|
<hc:NotifyIcon.ContextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<MenuItem Header="{DynamicResource Str_WallpaperView}" Command="{Binding Cmd_SwitchWallpaperView}" IsChecked="{Binding PreferenceViewModel.WallpaperView}"/>
|
||||||
|
<Separator/>
|
||||||
|
<MenuItem Header="{DynamicResource Str_Exit}" Command="{Binding Cmd_Exit}"/>
|
||||||
|
</ContextMenu>
|
||||||
|
</hc:NotifyIcon.ContextMenu>
|
||||||
|
</hc:NotifyIcon>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using Microsoft.Win32;
|
using NLog;
|
||||||
using NLog;
|
|
||||||
using NLog.Layouts;
|
using NLog.Layouts;
|
||||||
using NLog.Targets;
|
using NLog.Targets;
|
||||||
|
using SFMLRenderer;
|
||||||
using Spine;
|
using Spine;
|
||||||
using SpineViewer.Models;
|
using SpineViewer.Models;
|
||||||
using SpineViewer.Natives;
|
using SpineViewer.Natives;
|
||||||
@@ -12,6 +12,7 @@ using System.Collections.Specialized;
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Reflection.Metadata;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
@@ -30,57 +31,45 @@ namespace SpineViewer.Views;
|
|||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 布局文件保存路径
|
/// 上一次状态文件保存路径
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly string LastStateFilePath = Path.Combine(Path.GetDirectoryName(Environment.ProcessPath), "laststate.json");
|
public static readonly string LastStateFilePath = Path.Combine(Path.GetDirectoryName(Environment.ProcessPath), "laststate.json");
|
||||||
|
|
||||||
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
private ListViewItem? _listViewDragSourceItem = null;
|
private ListViewItem? _listViewDragSourceItem = null;
|
||||||
private Point _listViewDragSourcePoint;
|
private Point _listViewDragSourcePoint;
|
||||||
|
|
||||||
|
private readonly SFMLRenderWindow _wallpaperRenderWindow;
|
||||||
private readonly MainWindowViewModel _vm;
|
private readonly MainWindowViewModel _vm;
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
InitializeLogConfiguration();
|
InitializeLogConfiguration();
|
||||||
_vm = new (_renderPanel);
|
|
||||||
DataContext = _vm;
|
// Initialize Wallpaper RenderWindow
|
||||||
_vm.SFMLRendererViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging;
|
_wallpaperRenderWindow = new(new(1, 1), "SpineViewerWallpaper", SFML.Window.Styles.None);
|
||||||
|
_wallpaperRenderWindow.SetVisible(false);
|
||||||
|
var handle = _wallpaperRenderWindow.SystemHandle;
|
||||||
|
var style = User32.GetWindowLong(handle, User32.GWL_STYLE) | User32.WS_POPUP;
|
||||||
|
var exStyle = User32.GetWindowLong(handle, User32.GWL_EXSTYLE) | User32.WS_EX_LAYERED | User32.WS_EX_TOOLWINDOW;
|
||||||
|
User32.SetWindowLong(handle, User32.GWL_STYLE, style);
|
||||||
|
User32.SetWindowLong(handle, User32.GWL_EXSTYLE, exStyle);
|
||||||
|
User32.SetLayeredWindowAttributes(handle, 0, byte.MaxValue, User32.LWA_ALPHA);
|
||||||
|
|
||||||
|
DataContext = _vm = new(_renderPanel, _wallpaperRenderWindow);
|
||||||
|
|
||||||
|
// XXX: hc 的 NotifyIcon 的 Text 似乎没法双向绑定
|
||||||
|
_notifyIcon.Text = _vm.Title;
|
||||||
|
|
||||||
Loaded += MainWindow_Loaded;
|
Loaded += MainWindow_Loaded;
|
||||||
|
ContentRendered += MainWindow_ContentRendered;
|
||||||
Closed += MainWindow_Closed;
|
Closed += MainWindow_Closed;
|
||||||
}
|
|
||||||
|
|
||||||
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
|
_vm.SpineObjectListViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging;
|
||||||
{
|
_vm.SFMLRendererViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging;
|
||||||
var vm = _vm.SFMLRendererViewModel;
|
_vm.PreferenceViewModel.PropertyChanged += PreferenceViewModel_PropertyChanged;
|
||||||
_renderPanel.CanvasMouseWheelScrolled += vm.CanvasMouseWheelScrolled;
|
|
||||||
_renderPanel.CanvasMouseButtonPressed += vm.CanvasMouseButtonPressed;
|
|
||||||
_renderPanel.CanvasMouseMove += vm.CanvasMouseMove;
|
|
||||||
_renderPanel.CanvasMouseButtonReleased += vm.CanvasMouseButtonReleased;
|
|
||||||
|
|
||||||
// 设置默认参数并启动渲染
|
|
||||||
vm.SetResolution(1500, 1000);
|
|
||||||
vm.Zoom = 0.75f;
|
|
||||||
vm.CenterX = 0;
|
|
||||||
vm.CenterY = 0;
|
|
||||||
vm.FlipY = true;
|
|
||||||
vm.MaxFps = 30;
|
|
||||||
vm.StartRender();
|
|
||||||
|
|
||||||
// 加载首选项
|
|
||||||
_vm.PreferenceViewModel.LoadPreference();
|
|
||||||
|
|
||||||
LoadLastState();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void MainWindow_Closed(object? sender, EventArgs e)
|
|
||||||
{
|
|
||||||
SaveLastState();
|
|
||||||
|
|
||||||
var vm = _vm.SFMLRendererViewModel;
|
|
||||||
vm.StopRender();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -92,14 +81,13 @@ public partial class MainWindow : Window
|
|||||||
var rtbTarget = new NLog.Windows.Wpf.RichTextBoxTarget
|
var rtbTarget = new NLog.Windows.Wpf.RichTextBoxTarget
|
||||||
{
|
{
|
||||||
Name = "rtbTarget",
|
Name = "rtbTarget",
|
||||||
FormName = GetType().Name,
|
WindowName = _mainWindow.Name,
|
||||||
ControlName = _loggerRichTextBox.Name,
|
ControlName = _loggerRichTextBox.Name,
|
||||||
AutoScroll = true,
|
AutoScroll = true,
|
||||||
MaxLines = 3000,
|
MaxLines = 3000,
|
||||||
Layout = "[${level:format=OneLetter}]${date:format=yyyy-MM-dd HH\\:mm\\:ss} - ${message}"
|
Layout = "[${level:format=OneLetter}]${date:format=yyyy-MM-dd HH\\:mm\\:ss} - ${message}",
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: 完善日志实现
|
|
||||||
rtbTarget.WordColoringRules.Add(new("[D]", "Gray", "Empty"));
|
rtbTarget.WordColoringRules.Add(new("[D]", "Gray", "Empty"));
|
||||||
rtbTarget.WordColoringRules.Add(new("[I]", "DimGray", "Empty"));
|
rtbTarget.WordColoringRules.Add(new("[I]", "DimGray", "Empty"));
|
||||||
rtbTarget.WordColoringRules.Add(new("[W]", "DarkOrange", "Empty"));
|
rtbTarget.WordColoringRules.Add(new("[W]", "DarkOrange", "Empty"));
|
||||||
@@ -130,7 +118,7 @@ public partial class MainWindow : Window
|
|||||||
|
|
||||||
_rootGrid.ColumnDefinitions[0].Width = new(m.RootGridCol0Width);
|
_rootGrid.ColumnDefinitions[0].Width = new(m.RootGridCol0Width);
|
||||||
_modelListGrid.RowDefinitions[0].Height = new(m.ModelListRow0Height);
|
_modelListGrid.RowDefinitions[0].Height = new(m.ModelListRow0Height);
|
||||||
_explorerGrid.RowDefinitions[0].Height = new(m.ExplorerGridRow0Height);
|
if (m.ExplorerGridRow0Height > 0) _explorerGrid.RowDefinitions[0].Height = new(m.ExplorerGridRow0Height);
|
||||||
_rightPanelGrid.RowDefinitions[0].Height = new(m.RightPanelGridRow0Height);
|
_rightPanelGrid.RowDefinitions[0].Height = new(m.RightPanelGridRow0Height);
|
||||||
|
|
||||||
_vm.SFMLRendererViewModel.SetResolution(m.ResolutionX, m.ResolutionY);
|
_vm.SFMLRendererViewModel.SetResolution(m.ResolutionX, m.ResolutionY);
|
||||||
@@ -138,8 +126,8 @@ public partial class MainWindow : Window
|
|||||||
_vm.SFMLRendererViewModel.Speed = m.Speed;
|
_vm.SFMLRendererViewModel.Speed = m.Speed;
|
||||||
_vm.SFMLRendererViewModel.ShowAxis = m.ShowAxis;
|
_vm.SFMLRendererViewModel.ShowAxis = m.ShowAxis;
|
||||||
_vm.SFMLRendererViewModel.BackgroundColor = m.BackgroundColor;
|
_vm.SFMLRendererViewModel.BackgroundColor = m.BackgroundColor;
|
||||||
|
_vm.SFMLRendererViewModel.BackgroundImageMode = m.BackgroundImageMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SaveLastState()
|
private void SaveLastState()
|
||||||
@@ -163,11 +151,101 @@ public partial class MainWindow : Window
|
|||||||
Speed = _vm.SFMLRendererViewModel.Speed,
|
Speed = _vm.SFMLRendererViewModel.Speed,
|
||||||
ShowAxis = _vm.SFMLRendererViewModel.ShowAxis,
|
ShowAxis = _vm.SFMLRendererViewModel.ShowAxis,
|
||||||
BackgroundColor = _vm.SFMLRendererViewModel.BackgroundColor,
|
BackgroundColor = _vm.SFMLRendererViewModel.BackgroundColor,
|
||||||
|
BackgroundImageMode = _vm.SFMLRendererViewModel.BackgroundImageMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
JsonHelper.Serialize(m, LastStateFilePath);
|
JsonHelper.Serialize(m, LastStateFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 给管道通信提供的打开文件外部调用方法
|
||||||
|
/// </summary>
|
||||||
|
public void OpenFiles(IEnumerable<string> filePaths)
|
||||||
|
{
|
||||||
|
_vm.SpineObjectListViewModel.AddSpineObjectFromFileList(filePaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region MainWindow 事件处理
|
||||||
|
|
||||||
|
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var vm = _vm.SFMLRendererViewModel;
|
||||||
|
_renderPanel.CanvasMouseWheelScrolled += vm.CanvasMouseWheelScrolled;
|
||||||
|
_renderPanel.CanvasMouseButtonPressed += (s, e) => { vm.CanvasMouseButtonPressed(s, e); _spinesListView.Focus(); }; // 用户点击画布后强制转移焦点至列表
|
||||||
|
_renderPanel.CanvasMouseMove += vm.CanvasMouseMove;
|
||||||
|
_renderPanel.CanvasMouseButtonReleased += vm.CanvasMouseButtonReleased;
|
||||||
|
|
||||||
|
// 设置默认参数并启动渲染
|
||||||
|
vm.SetResolution(1500, 1000);
|
||||||
|
vm.Zoom = 0.75f;
|
||||||
|
vm.CenterX = 0;
|
||||||
|
vm.CenterY = 0;
|
||||||
|
vm.FlipY = true;
|
||||||
|
vm.MaxFps = 30;
|
||||||
|
vm.StartRender();
|
||||||
|
|
||||||
|
// 加载首选项
|
||||||
|
_vm.PreferenceViewModel.LoadPreference();
|
||||||
|
|
||||||
|
LoadLastState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MainWindow_ContentRendered(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
string[] args = Environment.GetCommandLineArgs();
|
||||||
|
if (args.Length > 1)
|
||||||
|
{
|
||||||
|
string[] filePaths = args.Skip(1).ToArray();
|
||||||
|
_vm.SpineObjectListViewModel.AddSpineObjectFromFileList(filePaths);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MainWindow_Closed(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
SaveLastState();
|
||||||
|
|
||||||
|
var vm = _vm.SFMLRendererViewModel;
|
||||||
|
vm.StopRender();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region PreferenceViewModel 事件处理
|
||||||
|
|
||||||
|
private void PreferenceViewModel_PropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(PreferenceViewModel.WallpaperView))
|
||||||
|
{
|
||||||
|
if (_vm.PreferenceViewModel.WallpaperView)
|
||||||
|
{
|
||||||
|
var workerw = User32.GetWorkerW();
|
||||||
|
if (workerw == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
_logger.Error("Failed to enable wallpaper view, WorkerW not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var wnd = _wallpaperRenderWindow;
|
||||||
|
var handle = wnd.SystemHandle;
|
||||||
|
|
||||||
|
User32.GetPrimaryScreenResolution(out var sw, out var sh);
|
||||||
|
|
||||||
|
User32.SetParent(handle, workerw);
|
||||||
|
User32.SetLayeredWindowAttributes(handle, 0, byte.MaxValue, User32.LWA_ALPHA);
|
||||||
|
|
||||||
|
wnd.Position = new(0, 0);
|
||||||
|
wnd.Size = new(sw + 1, sh);
|
||||||
|
wnd.Size = new(sw, sh);
|
||||||
|
wnd.SetVisible(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_wallpaperRenderWindow.SetVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region _spinesListView 事件处理
|
#region _spinesListView 事件处理
|
||||||
|
|
||||||
private void SpinesListView_RequestSelectionChanging(object? sender, NotifyCollectionChangedEventArgs e)
|
private void SpinesListView_RequestSelectionChanging(object? sender, NotifyCollectionChangedEventArgs e)
|
||||||
@@ -193,6 +271,9 @@ public partial class MainWindow : Window
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果选中项发生变化也强制转移焦点
|
||||||
|
_spinesListView.Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SpinesListView_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
private void SpinesListView_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||||
@@ -293,6 +374,31 @@ public partial class MainWindow : Window
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region _spineFilesListBox 事件
|
||||||
|
|
||||||
|
private void SpineFilesListBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
var list = (ListBox)sender;
|
||||||
|
if (VisualUpwardSearch<ListBoxItem>(e.OriginalSource as DependencyObject) is null)
|
||||||
|
list.SelectedItems.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region _nofityIcon 事件处理
|
||||||
|
|
||||||
|
private void _notifyIcon_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _notifyIcon_MouseDoubleClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region 切换全屏布局事件处理
|
#region 切换全屏布局事件处理
|
||||||
|
|
||||||
private void SwitchToFullScreenLayout()
|
private void SwitchToFullScreenLayout()
|
||||||
@@ -302,7 +408,7 @@ public partial class MainWindow : Window
|
|||||||
if (_fullScreenLayout.Visibility == Visibility.Visible) return;
|
if (_fullScreenLayout.Visibility == Visibility.Visible) return;
|
||||||
|
|
||||||
IntPtr hwnd = new WindowInteropHelper(this).Handle;
|
IntPtr hwnd = new WindowInteropHelper(this).Handle;
|
||||||
if (Win32.GetScreenResolution(hwnd, out var resX, out var resY))
|
if (User32.GetScreenResolution(hwnd, out var resX, out var resY))
|
||||||
{
|
{
|
||||||
_vm.SFMLRendererViewModel.SetResolution(resX, resY);
|
_vm.SFMLRendererViewModel.SetResolution(resX, resY);
|
||||||
}
|
}
|
||||||
@@ -551,10 +657,10 @@ public partial class MainWindow : Window
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private void SpineFilesListBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
private void DebugMenuItem_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var list = (ListBox)sender;
|
#if DEBUG
|
||||||
if (VisualUpwardSearch<ListBoxItem>(e.OriginalSource as DependencyObject) is null)
|
|
||||||
list.SelectedItems.Clear();
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,13 +143,21 @@
|
|||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_RenderSelectedOnly}"/>
|
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_WallpaperView}"/>
|
||||||
<ToggleButton Grid.Row="0" Grid.Column="1" IsChecked="{Binding RenderSelectedOnly}"/>
|
<ToggleButton Grid.Row="0" Grid.Column="1" IsChecked="{Binding WallpaperView}"/>
|
||||||
|
|
||||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_Language}"/>
|
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_RenderSelectedOnly}"/>
|
||||||
<ComboBox Grid.Row="1" Grid.Column="1"
|
<ToggleButton Grid.Row="1" Grid.Column="1" IsChecked="{Binding RenderSelectedOnly}"/>
|
||||||
|
|
||||||
|
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_AssociateFileSuffix}"/>
|
||||||
|
<ToggleButton Grid.Row="2" Grid.Column="1" IsChecked="{Binding AssociateFileSuffix}"/>
|
||||||
|
|
||||||
|
<Label Grid.Row="3" Grid.Column="0" Content="{DynamicResource Str_Language}"/>
|
||||||
|
<ComboBox Grid.Row="3" Grid.Column="1"
|
||||||
SelectedItem="{Binding AppLanguage}"
|
SelectedItem="{Binding AppLanguage}"
|
||||||
ItemsSource="{x:Static vm:PreferenceViewModel.AppLanguageOptions}"/>
|
ItemsSource="{x:Static vm:PreferenceViewModel.AppLanguageOptions}"/>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user