Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
5ef13239da | ||
|
|
13ef873650 | ||
|
|
78b9834f6c | ||
|
|
672a695b20 | ||
|
|
e9951ed79a | ||
|
|
0c16b2f104 | ||
|
|
7628075420 | ||
|
|
6f896bdaad | ||
|
|
98930db4b6 | ||
|
|
c7493372e9 | ||
|
|
707aa7f570 | ||
|
|
99ff6f9f0a | ||
|
|
be8193e235 | ||
|
|
21b6dbee4c | ||
|
|
f60418fecb | ||
|
|
1180c735c8 | ||
|
|
3d8f6547e0 | ||
|
|
99ec2704fe | ||
|
|
dbd2cef766 | ||
|
|
212ecc2ff3 | ||
|
|
7806f9298d | ||
|
|
3bc57a8990 | ||
|
|
67c9ea9291 | ||
|
|
f404acc834 | ||
|
|
8e1f586d4f | ||
|
|
5dd1b84943 | ||
|
|
ad190d8952 | ||
|
|
ddb11808a7 | ||
|
|
9b1e26b2ac | ||
|
|
c8e35a9aed | ||
|
|
4786b0434c | ||
|
|
40bde84648 | ||
|
|
ebb2593526 | ||
|
|
6a74204ba1 | ||
|
|
78c6c47300 | ||
|
|
746a3decc8 | ||
|
|
6dfd25b760 |
18
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
18
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: 问题报告/Bug report
|
||||
about: 报告可能的程序错误/Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## 问题描述/Describe the bug
|
||||
清晰完整的描述问题是什么以及如何发生的。/A clear and concise description of what the bug is.
|
||||
|
||||
## 复现方式(可选)/To Reproduce (Optional)
|
||||
|
||||
## 截图(可选)/Screenshots (Optional)**
|
||||
如果有必要,提供报错时的有关截图。/If applicable, add screenshots to help explain your problem.
|
||||
|
||||
## 附件(可选)/Attachments (Optional)
|
||||
请将会**出现问题的文件**以及**日志文件**打包成一个 ZIP 后作为附件贴在 issue 内。/Please compress the problematic files and the log files into a single ZIP archive and attach it to this issue.
|
||||
55
CHANGELOG.md
55
CHANGELOG.md
@@ -1,5 +1,60 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v0.15.18
|
||||
|
||||
- 完善窗口日志颜色标记
|
||||
- 修复预览图背景颜色为透明
|
||||
- 修复面板高度首次还原错误
|
||||
- 增加托盘图标
|
||||
- 增加可选预览背景画面和填充模式
|
||||
- 增强支持的纹理格式(例如 webp)
|
||||
|
||||
## v0.15.17
|
||||
|
||||
- 修改图标配色
|
||||
|
||||
## v0.15.16
|
||||
|
||||
- 修改模型添加顺序, 每次向顶层添加
|
||||
- 添加模型后自动选中最近添加的模型S
|
||||
- 点击预览画面或者选中项发生变化时转移焦点至列表
|
||||
- 增加移除全部菜单项
|
||||
- 增加单例模式和命令行文件参数
|
||||
- 增加文件关联设置
|
||||
|
||||
## v0.15.15
|
||||
|
||||
- 增加报错信息
|
||||
- 导入后自动选中最后一项
|
||||
|
||||
## v0.15.14
|
||||
|
||||
- 将预览画面的首选项移动至上一次状态参数中
|
||||
- 增加预览画面像素的自动保存和恢复
|
||||
- 增加日志启动时的版本号输出
|
||||
|
||||
## v0.15.13
|
||||
|
||||
- 增加程序布局自动存储和还原
|
||||
- 增加部分预览画面首选项
|
||||
|
||||
## v0.15.12
|
||||
|
||||
- 增加单个模型和单个轨道的时间因子
|
||||
- 增加单个轨道的 Alpha 混合参数
|
||||
- 调整轨道清除命令至右键菜单
|
||||
- 设置默认标签页为模型
|
||||
- 完善导入时的报错信息
|
||||
|
||||
## v0.15.11
|
||||
|
||||
- 修复自定义导出中参数构造错误
|
||||
- 增加 mov 格式及参数说明
|
||||
|
||||
## v0.15.10
|
||||
|
||||
- 增加插槽可见性参数, 允许任何情况下对插槽启用和禁用对插槽的渲染
|
||||
|
||||
## v0.15.9
|
||||
|
||||
- 添加 V34 和 V35 版本支持
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
@@ -7,7 +7,7 @@
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>0.15.4</Version>
|
||||
<Version>0.15.18</Version>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -1,92 +1,53 @@
|
||||
//
|
||||
// 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;
|
||||
using NLog.Conditions;
|
||||
using NLog.Config;
|
||||
using NLog;
|
||||
using System.ComponentModel;
|
||||
using NLog.Layouts;
|
||||
using System.Windows;
|
||||
|
||||
namespace NLog.Windows.Wpf
|
||||
{
|
||||
[NLogConfigurationItem]
|
||||
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; }
|
||||
|
||||
[RequiredParameter]
|
||||
public ConditionExpression Condition { get; set; }
|
||||
|
||||
[DefaultValue("Empty")]
|
||||
public string FontColor { get; set; }
|
||||
public Layout FontColor { get; set; }
|
||||
public Layout BackgroundColor { get; set; }
|
||||
|
||||
[DefaultValue("Empty")]
|
||||
public string BackgroundColor { get; set; }
|
||||
public FontStyle FontStyle { 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)
|
||||
{
|
||||
return true.Equals(Condition.Evaluate(logEvent));
|
||||
return true.Equals(this.Condition.Evaluate(logEvent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,27 @@
|
||||
using NLog.Config;
|
||||
using NLog.Layouts;
|
||||
using NLog;
|
||||
using NLog.Common;
|
||||
using NLog.Config;
|
||||
using NLog.Targets;
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Media;
|
||||
using System.Windows;
|
||||
|
||||
namespace NLog.Windows.Wpf
|
||||
{
|
||||
// TODO: 完善日志实现
|
||||
[Target("RichTextBox")]
|
||||
public sealed class RichTextBoxTarget : TargetWithLayout
|
||||
{
|
||||
private int lineCount;
|
||||
private int _width = 500;
|
||||
private int _height = 500;
|
||||
private static readonly TypeConverter colorConverter = new ColorConverter();
|
||||
public static ReadOnlyCollection<RichTextBoxRowColoringRule> DefaultRowColoringRules { get; } = CreateDefaultColoringRules();
|
||||
|
||||
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.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.Debug", "Gray", "Empty"),
|
||||
new RichTextBoxRowColoringRule("level == LogLevel.Trace", "DarkGray", "Empty", FontStyles.Italic, FontWeights.Normal),
|
||||
};
|
||||
|
||||
DefaultRowColoringRules = rules.AsReadOnly();
|
||||
}.AsReadOnly();
|
||||
}
|
||||
|
||||
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 RichTextBoxTarget() { }
|
||||
|
||||
public string ControlName { get; set; }
|
||||
|
||||
public string FormName { get; set; }
|
||||
public string WindowName { get; set; }
|
||||
|
||||
[DefaultValue(false)]
|
||||
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 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()
|
||||
{
|
||||
TargetRichTextBox = Application.Current.MainWindow.FindName(ControlName) as RichTextBox;
|
||||
base.InitializeTarget();
|
||||
if (TargetRichTextBox != null)
|
||||
return;
|
||||
|
||||
if (TargetRichTextBox != null) return;
|
||||
//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)
|
||||
if (WindowName == null)
|
||||
{
|
||||
TargetForm = openFormByName;
|
||||
if (string.IsNullOrEmpty(ControlName))
|
||||
{
|
||||
// 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 + "'.");
|
||||
}
|
||||
HandleError("WindowName should be specified for {0}.{1}", GetType().Name, Name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (TargetRichTextBox == null)
|
||||
if (string.IsNullOrEmpty(ControlName))
|
||||
{
|
||||
TargetForm = new Window
|
||||
{
|
||||
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;
|
||||
HandleError("Rich text box control name must be specified for {0}.{1}", GetType().Name, Name);
|
||||
return;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if (CreatedForm)
|
||||
{
|
||||
try
|
||||
{
|
||||
TargetForm.Dispatcher.Invoke(() =>
|
||||
{
|
||||
TargetForm.Close();
|
||||
TargetForm = null;
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
DetachFromControl();
|
||||
}
|
||||
|
||||
protected override void Write(LogEventInfo logEvent)
|
||||
{
|
||||
RichTextBoxRowColoringRule matchingRule = RowColoringRules.FirstOrDefault(rr => rr.CheckCondition(logEvent));
|
||||
|
||||
if (UseDefaultRowColoringRules && matchingRule == null)
|
||||
RichTextBox textbox = TargetRichTextBox;
|
||||
if (textbox == null || textbox.Dispatcher.HasShutdownStarted || textbox.Dispatcher.HasShutdownFinished)
|
||||
{
|
||||
foreach (var rr in DefaultRowColoringRules.Where(rr => rr.CheckCondition(logEvent)))
|
||||
{
|
||||
matchingRule = rr;
|
||||
break;
|
||||
}
|
||||
//no last logged textbox
|
||||
InternalLogger.Trace("{0}: Attached Textbox is {1}, skipping logging", this, textbox == null ? "null" : "disposed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (matchingRule == null)
|
||||
{
|
||||
matchingRule = RichTextBoxRowColoringRule.Default;
|
||||
}
|
||||
|
||||
var logMessage = Layout.Render(logEvent);
|
||||
|
||||
if (Application.Current == null) return;
|
||||
string logMessage = RenderLogEvent(Layout, logEvent);
|
||||
RichTextBoxRowColoringRule matchingRule = FindMatchingRule(logEvent);
|
||||
_ = DoSendMessageToTextbox(logMessage, matchingRule, logEvent);
|
||||
}
|
||||
|
||||
private bool DoSendMessageToTextbox(string logMessage, RichTextBoxRowColoringRule rule, LogEventInfo logEvent)
|
||||
{
|
||||
RichTextBox textbox = TargetRichTextBox;
|
||||
try
|
||||
{
|
||||
if (Application.Current.Dispatcher.CheckAccess() == false)
|
||||
if (textbox != null && !textbox.Dispatcher.HasShutdownStarted && !textbox.Dispatcher.HasShutdownFinished)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() => SendTheMessageToRichTextBox(logMessage, matchingRule));
|
||||
}
|
||||
else
|
||||
{
|
||||
SendTheMessageToRichTextBox(logMessage, matchingRule);
|
||||
if (!textbox.Dispatcher.CheckAccess())
|
||||
{
|
||||
textbox.Dispatcher.BeginInvoke(() => SendTheMessageToRichTextBox(textbox, logMessage, rule, logEvent));
|
||||
}
|
||||
else
|
||||
{
|
||||
SendTheMessageToRichTextBox(textbox, logMessage, rule, logEvent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex);
|
||||
}
|
||||
InternalLogger.Warn(ex, "{0}: Failed to append RichTextBox", this);
|
||||
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
if (LogManager.ThrowExceptions)
|
||||
{
|
||||
tr = new TextRange(rtbx.Document.ContentStart, rtbx.Document.ContentEnd);
|
||||
tr.Text.Remove(0, tr.Text.IndexOf('\n'));
|
||||
lineCount--;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
rtbx.ScrollToEnd();
|
||||
textBox.ScrollToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,119 +1,59 @@
|
||||
//
|
||||
// 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.Config;
|
||||
using NLog.Layouts;
|
||||
using System.ComponentModel;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows;
|
||||
using NLog.Config;
|
||||
|
||||
namespace NLog.Windows.Wpf
|
||||
{
|
||||
[NLogConfigurationItem]
|
||||
[NLogConfigurationItem]
|
||||
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";
|
||||
BackgroundColor = "Empty";
|
||||
if (string.IsNullOrEmpty(pattern) && text != null)
|
||||
{
|
||||
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)
|
||||
{
|
||||
Text = text;
|
||||
FontColor = fontColor;
|
||||
BackgroundColor = backgroundColor;
|
||||
Style = FontStyles.Normal;
|
||||
Weight = FontWeights.Normal;
|
||||
this.Text = text;
|
||||
this.FontColor = Layout.FromString(fontColor);
|
||||
this.BackgroundColor = Layout.FromString(backgroundColor);
|
||||
this.FontStyle = FontStyles.Normal;
|
||||
this.FontWeight = FontWeights.Normal;
|
||||
}
|
||||
|
||||
public RichTextBoxWordColoringRule(string text, string textColor, string backgroundColor, FontStyle fontStyle, FontWeight fontWeight)
|
||||
{
|
||||
Text = text;
|
||||
FontColor = textColor;
|
||||
BackgroundColor = backgroundColor;
|
||||
Style = fontStyle;
|
||||
Weight = fontWeight;
|
||||
this.Text = text;
|
||||
this.FontColor = Layout.FromString(textColor);
|
||||
this.BackgroundColor = Layout.FromString(backgroundColor);
|
||||
this.FontStyle = fontStyle;
|
||||
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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,17 @@ A simple and user-friendly Spine file viewer and exporter with multi-language su
|
||||
* Batch adjustment of skeleton parameters using multi-selection.
|
||||
* Multi-track animation settings.
|
||||
* Skin and custom slot attachment settings.
|
||||
* Custom slot visibility settings.
|
||||
* Debug rendering support.
|
||||
* View/model/track time scale adjustment.
|
||||
* Track alpha blending parameter settings.
|
||||
* Fullscreen preview mode.
|
||||
* Export to single frame/image sequence/animated GIF/video formats.
|
||||
* Automatic resolution batch export.
|
||||
* FFmpeg custom export support.
|
||||
* Program parameter saving.
|
||||
* File name extension association.
|
||||
* Supports texture image formats other than PNG.
|
||||
* ...
|
||||
|
||||
### Supported Spine Versions
|
||||
|
||||
@@ -19,12 +19,17 @@
|
||||
- 支持列表多选批量设置骨骼参数
|
||||
- 支持多轨道动画设置
|
||||
- 支持皮肤/自定义插槽附件设置
|
||||
- 支持自定义插槽可见性
|
||||
- 支持调试渲染
|
||||
- 支持画面/模型/轨道时间倍速设置
|
||||
- 支持设置轨道 Alpha 混合参数
|
||||
- 支持全屏预览
|
||||
- 支持单帧/动图/视频文件导出
|
||||
- 支持自动分辨率批量导出
|
||||
- 支持 FFmpeg 自定义导出
|
||||
- 支持程序参数保存
|
||||
- 支持文件后缀关联
|
||||
- 支持非 png 格式的纹理图片格式
|
||||
- ......
|
||||
|
||||
### Spine 版本支持
|
||||
|
||||
@@ -60,8 +60,9 @@ namespace Spine.Exporters
|
||||
if (!string.IsNullOrEmpty(_codec)) options.WithVideoCodec(_codec);
|
||||
if (!string.IsNullOrEmpty(_pixelFormat)) options.ForcePixelFormat(_pixelFormat);
|
||||
if (!string.IsNullOrEmpty(_bitrate)) options.WithCustomArgument($"-b:v {_bitrate}");
|
||||
if (!string.IsNullOrEmpty(_filter)) options.WithCustomArgument($"-vf unpremultiply=inplace=1, {_customArgs}");
|
||||
if (!string.IsNullOrEmpty(_filter)) options.WithCustomArgument($"-vf \"unpremultiply=inplace=1, {_filter}\"");
|
||||
else options.WithCustomArgument("-vf unpremultiply=inplace=1");
|
||||
if (!string.IsNullOrEmpty(_customArgs)) options.WithCustomArgument($"{_customArgs}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace Spine.Exporters
|
||||
Mp4,
|
||||
Webm,
|
||||
Mkv,
|
||||
Mov,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -40,29 +41,35 @@ namespace Spine.Exporters
|
||||
private VideoFormat _format = VideoFormat.Mp4;
|
||||
|
||||
/// <summary>
|
||||
/// 动图是否循环
|
||||
/// 动图是否循环 [Gif/Webp]
|
||||
/// </summary>
|
||||
public bool Loop { get => _loop; set => _loop = value; }
|
||||
private bool _loop = true;
|
||||
|
||||
/// <summary>
|
||||
/// 质量
|
||||
/// 质量 [Webp]
|
||||
/// </summary>
|
||||
public int Quality { get => _quality; set => _quality = Math.Clamp(value, 0, 100); }
|
||||
private int _quality = 75;
|
||||
|
||||
/// <summary>
|
||||
/// 无损压缩 (Webp)
|
||||
/// 无损压缩 [Webp]
|
||||
/// </summary>
|
||||
public bool Lossless { get => _lossless; set => _lossless = value; }
|
||||
private bool _lossless = false;
|
||||
|
||||
/// <summary>
|
||||
/// CRF
|
||||
/// CRF [Mp4/Webm/Mkv]
|
||||
/// </summary>
|
||||
public int Crf { get => _crf; set => _crf = Math.Clamp(value, 0, 63); }
|
||||
private int _crf = 23;
|
||||
|
||||
/// <summary>
|
||||
/// prores_ks 编码器的配置等级, -1 是自动, 越高质量越好, 只有 4 及以上才有透明通道 [Mov]
|
||||
/// </summary>
|
||||
public int Profile { get => _profile; set => _profile = Math.Clamp(value, -1, 5); }
|
||||
private int _profile = 5;
|
||||
|
||||
/// <summary>
|
||||
/// 获取的一帧, 结果是预乘的
|
||||
/// </summary>
|
||||
@@ -89,6 +96,7 @@ namespace Spine.Exporters
|
||||
VideoFormat.Mp4 => SetMp4Options,
|
||||
VideoFormat.Webm => SetWebmOptions,
|
||||
VideoFormat.Mkv => SetMkvOptions,
|
||||
VideoFormat.Mov => SetMovOptions,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
|
||||
@@ -110,8 +118,8 @@ namespace Spine.Exporters
|
||||
{
|
||||
// Gif 固定使用 256 调色板和 128 透明度阈值
|
||||
var v = "split [s0][s1]";
|
||||
var s0 = "[s0] palettegen=reserve_transparent=1:max_colors=256 [p]";
|
||||
var s1 = "[s1][p] paletteuse=dither=bayer:alpha_threshold=128";
|
||||
var s0 = "[s0] palettegen=max_colors=256 [p]";
|
||||
var s1 = "[s1][p] paletteuse=alpha_threshold=128";
|
||||
var customArgs = $"-vf \"unpremultiply=inplace=1, {v};{s0};{s1}\" -loop {(_loop ? 0 : -1)}";
|
||||
options.ForceFormat("gif")
|
||||
.WithCustomArgument(customArgs);
|
||||
@@ -151,5 +159,13 @@ namespace Spine.Exporters
|
||||
.WithCustomArgument(customArgs);
|
||||
}
|
||||
|
||||
private void SetMovOptions(FFMpegArgumentOptions options)
|
||||
{
|
||||
var customArgs = "-vf unpremultiply=inplace=1";
|
||||
options.ForceFormat("mov").WithVideoCodec("prores_ks").ForcePixelFormat("yuva444p10le")
|
||||
.WithFastStart()
|
||||
.WithCustomArgument($"-profile {_profile}")
|
||||
.WithCustomArgument(customArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,8 @@ namespace Spine.Implementations.SpineWrappers.V21
|
||||
}
|
||||
}
|
||||
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
public override string ToString() => _o.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,15 @@ namespace Spine.Implementations.SpineWrappers.V21
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -41,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V21
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -52,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V21
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -61,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V21
|
||||
catch (Exception ex)
|
||||
{
|
||||
_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}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V34
|
||||
}
|
||||
}
|
||||
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
public override string ToString() => _o.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,15 @@ namespace Spine.Implementations.SpineWrappers.V34
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -41,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V34
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -52,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V34
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -61,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V34
|
||||
catch (Exception ex)
|
||||
{
|
||||
_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}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V35
|
||||
}
|
||||
}
|
||||
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
public override string ToString() => _o.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,15 @@ namespace Spine.Implementations.SpineWrappers.V35
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -41,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V35
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -52,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V35
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -61,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V35
|
||||
catch (Exception ex)
|
||||
{
|
||||
_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}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V36
|
||||
}
|
||||
}
|
||||
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
public override string ToString() => _o.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,15 @@ namespace Spine.Implementations.SpineWrappers.V36
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -41,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V36
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -52,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V36
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -61,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V36
|
||||
catch (Exception ex)
|
||||
{
|
||||
_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}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V37
|
||||
}
|
||||
}
|
||||
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
public override string ToString() => _o.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,15 @@ namespace Spine.Implementations.SpineWrappers.V37
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -41,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V37
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -52,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V37
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -61,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V37
|
||||
catch (Exception ex)
|
||||
{
|
||||
_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}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -74,6 +74,8 @@ namespace Spine.Implementations.SpineWrappers.V38
|
||||
}
|
||||
}
|
||||
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
public override string ToString() => _o.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,15 @@ namespace Spine.Implementations.SpineWrappers.V38
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -42,8 +49,9 @@ namespace Spine.Implementations.SpineWrappers.V38
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -53,8 +61,9 @@ namespace Spine.Implementations.SpineWrappers.V38
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -62,7 +71,8 @@ namespace Spine.Implementations.SpineWrappers.V38
|
||||
catch (Exception ex)
|
||||
{
|
||||
_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}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V40
|
||||
}
|
||||
}
|
||||
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
public override string ToString() => _o.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,16 @@ namespace Spine.Implementations.SpineWrappers.V40
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
// 加载 skel
|
||||
try
|
||||
{
|
||||
if (Utf8Validator.IsUtf8(skelPath))
|
||||
@@ -42,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V40
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -53,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V40
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -62,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V40
|
||||
catch (Exception ex)
|
||||
{
|
||||
_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}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V41
|
||||
}
|
||||
}
|
||||
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
public override string ToString() => _o.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,16 @@ namespace Spine.Implementations.SpineWrappers.V41
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
// 加载 skel
|
||||
try
|
||||
{
|
||||
if (Utf8Validator.IsUtf8(skelPath))
|
||||
@@ -42,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V41
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -53,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V41
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -62,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V41
|
||||
catch (Exception ex)
|
||||
{
|
||||
_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}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V42
|
||||
}
|
||||
}
|
||||
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
public override string ToString() => _o.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,16 @@ namespace Spine.Implementations.SpineWrappers.V42
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
// 加载 skel
|
||||
try
|
||||
{
|
||||
if (Utf8Validator.IsUtf8(skelPath))
|
||||
@@ -42,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V42
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -53,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V42
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -62,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V42
|
||||
catch (Exception ex)
|
||||
{
|
||||
_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>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>0.15.9</Version>
|
||||
<Version>0.15.18</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -66,10 +66,10 @@ namespace Spine
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
throw new KeyNotFoundException($"Unrecognized skel suffix '{skelPath}'");
|
||||
throw new KeyNotFoundException($"Unrecognized skel file suffix");
|
||||
}
|
||||
}
|
||||
else if (!File.Exists(atlasPath)) throw new FileNotFoundException($"{nameof(atlasPath)} not found", skelPath);
|
||||
else if (!File.Exists(atlasPath)) throw new FileNotFoundException($"{nameof(atlasPath)} not found", atlasPath);
|
||||
AtlasPath = Path.GetFullPath(atlasPath);
|
||||
|
||||
// 自动检测版本, 可能会抛出异常
|
||||
@@ -105,13 +105,21 @@ namespace Spine
|
||||
|
||||
// 依然加载不成功就只能报错
|
||||
if (_data is null || Version is null)
|
||||
throw new InvalidDataException($"Failed to load spine by existed versions: '{skelPath}', '{atlasPath}'");
|
||||
throw new InvalidDataException($"Failed to load spine by existed versions");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 根据版本实例化对象
|
||||
Version = version;
|
||||
_data = SpineObjectData.New(Version, skelPath, atlasPath, textureLoader);
|
||||
try
|
||||
{
|
||||
_data = SpineObjectData.New(Version, skelPath, atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load spine with version '{version}'");
|
||||
}
|
||||
}
|
||||
|
||||
// 创建状态实例
|
||||
@@ -167,14 +175,18 @@ namespace Spine
|
||||
// 拷贝渲染设置
|
||||
UsePma = other.UsePma;
|
||||
Physics = other.Physics;
|
||||
_animationState.TimeScale = other._animationState.TimeScale;
|
||||
|
||||
// 拷贝皮肤加载情况
|
||||
_skinLoadStatus = other._skinLoadStatus.ToDictionary();
|
||||
ReloadSkins();
|
||||
|
||||
// 拷贝自定义插槽附件加载情况
|
||||
// 拷贝插槽属性值
|
||||
for (int i = 0; i < other._skeleton.Slots.Length; i++)
|
||||
{
|
||||
_skeleton.Slots[i].Attachment = other._skeleton.Slots[i].Attachment;
|
||||
_skeleton.Slots[i].Disabled = other._skeleton.Slots[i].Disabled;
|
||||
}
|
||||
|
||||
// 拷贝调试属性
|
||||
EnableDebug = other.EnableDebug;
|
||||
@@ -299,6 +311,30 @@ namespace Spine
|
||||
/// </summary>
|
||||
public bool DebugClippings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取插槽可见性, 如果不存在则默认返回 false
|
||||
/// </summary>
|
||||
public bool GetSlotVisible(string slotName)
|
||||
{
|
||||
if (_skeleton.SlotsByName.TryGetValue(slotName, out var slot))
|
||||
return !slot.Disabled;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置插槽可见性, 插槽不可见后将不会在任何渲染中出现, 插槽不存在则忽略操作
|
||||
/// </summary>
|
||||
/// <returns>操作是否成功, 插槽不存在则返回 false</returns>
|
||||
public bool SetSlotVisible(string slotName, bool visible)
|
||||
{
|
||||
if (_skeleton.SlotsByName.TryGetValue(slotName, out var slot))
|
||||
{
|
||||
slot.Disabled = !visible;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取某个插槽上的附件名, 插槽不存在或者无附件均返回 null
|
||||
/// </summary>
|
||||
@@ -310,7 +346,7 @@ namespace Spine
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置某个插槽的附件, 如果不存在则忽略, 可以使用 null 来清除附件
|
||||
/// 设置某个插槽的附件, 如果不存在则忽略, 可以使用 null 来尝试清除附件
|
||||
/// </summary>
|
||||
/// <returns>是否操作成功</returns>
|
||||
public bool SetAttachment(string slotName, string? attachmentName)
|
||||
@@ -471,7 +507,7 @@ namespace Spine
|
||||
|
||||
foreach (var slot in _skeleton.IterDrawOrder())
|
||||
{
|
||||
if (slot.A <= 0 || !slot.Bone.Active)
|
||||
if (slot.A <= 0 || !slot.Bone.Active || slot.Disabled)
|
||||
{
|
||||
_clipping.ClipEnd(slot);
|
||||
continue;
|
||||
@@ -602,7 +638,7 @@ namespace Spine
|
||||
if (DebugRegions)
|
||||
{
|
||||
vt.Color = AttachmentLineColor;
|
||||
foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active))
|
||||
foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active && !s.Disabled))
|
||||
{
|
||||
if (slot.Attachment is IRegionAttachment regionAttachment)
|
||||
{
|
||||
@@ -634,7 +670,7 @@ namespace Spine
|
||||
if (DebugMeshes)
|
||||
{
|
||||
vt.Color = MeshLineColor;
|
||||
foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active))
|
||||
foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active && !s.Disabled))
|
||||
{
|
||||
if (slot.Attachment is IMeshAttachment meshAttachment)
|
||||
{
|
||||
@@ -698,7 +734,7 @@ namespace Spine
|
||||
if (DebugMeshHulls)
|
||||
{
|
||||
vt.Color = AttachmentLineColor;
|
||||
foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active))
|
||||
foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active && !s.Disabled))
|
||||
{
|
||||
if (slot.Attachment is IMeshAttachment meshAttachment)
|
||||
{
|
||||
@@ -767,7 +803,7 @@ namespace Spine
|
||||
if (DebugClippings)
|
||||
{
|
||||
vt.Color = ClippingLineColor;
|
||||
foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active))
|
||||
foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active && !s.Disabled))
|
||||
{
|
||||
if (slot.Attachment is IClippingAttachment clippingAttachment)
|
||||
{
|
||||
|
||||
@@ -53,5 +53,10 @@ namespace Spine.SpineWrappers
|
||||
/// 使用的附件, 可以设置为 null 清空附件
|
||||
/// </summary>
|
||||
public IAttachment? Attachment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否已禁用渲染该插槽
|
||||
/// </summary>
|
||||
public bool Disabled { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using Spine.SpineWrappers.Attachments;
|
||||
|
||||
namespace Spine.SpineWrappers
|
||||
@@ -17,6 +18,8 @@ namespace Spine.SpineWrappers
|
||||
ISpineObjectData,
|
||||
IDisposable
|
||||
{
|
||||
protected static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
/// <summary>
|
||||
/// 构建版本对象
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using SFML.Graphics;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -40,37 +42,26 @@ namespace Spine.SpineWrappers
|
||||
/// </summary>
|
||||
public bool ForceMipmap { get; set; }
|
||||
|
||||
private SFML.Graphics.Texture ReadTexture(string path)
|
||||
private Texture ReadTexture(string path)
|
||||
{
|
||||
if (ForcePremul)
|
||||
{
|
||||
using var image = new SFML.Graphics.Image(path);
|
||||
var width = image.Size.X;
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
@@ -394,7 +385,7 @@ namespace Spine.SpineWrappers
|
||||
|
||||
public virtual void Unload(object texture)
|
||||
{
|
||||
((SFML.Graphics.Texture)texture).Dispose();
|
||||
((Texture)texture).Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
using NLog;
|
||||
using SpineViewer.Natives;
|
||||
using SpineViewer.Views;
|
||||
using System.Collections.Frozen;
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
|
||||
@@ -15,15 +18,24 @@ namespace SpineViewer
|
||||
/// </summary>
|
||||
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 Mutex _instanceMutex;
|
||||
|
||||
static App()
|
||||
{
|
||||
InitializeLogConfiguration();
|
||||
_logger = LogManager.GetCurrentClassLogger();
|
||||
_logger.Info("Application Started");
|
||||
_logger.Info("Application Started, v{0}", Version);
|
||||
|
||||
AppDomain.CurrentDomain.UnhandledException += (s, e) =>
|
||||
{
|
||||
@@ -35,6 +47,17 @@ namespace SpineViewer
|
||||
_logger.Error("Unobserved task exception: {0}", e.Exception.Message);
|
||||
e.SetObserved();
|
||||
};
|
||||
|
||||
// 单例模式加 IPC 通信
|
||||
_instanceMutex = new Mutex(true, MutexName, out var createdNew);
|
||||
if (!createdNew)
|
||||
{
|
||||
ShowExistedInstance();
|
||||
SendCommandLineArgs();
|
||||
Environment.Exit(0); // 不再启动新实例
|
||||
return;
|
||||
}
|
||||
StartPipeServer();
|
||||
}
|
||||
|
||||
private static void InitializeLogConfiguration()
|
||||
@@ -50,7 +73,9 @@ namespace SpineViewer
|
||||
ArchiveNumbering = NLog.Targets.ArchiveNumberingMode.Rolling,
|
||||
ArchiveAboveSize = 1048576,
|
||||
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);
|
||||
@@ -58,20 +83,113 @@ namespace SpineViewer
|
||||
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)
|
||||
{
|
||||
// 正式启动窗口
|
||||
base.OnStartup(e);
|
||||
|
||||
var dict = new ResourceDictionary();
|
||||
|
||||
var uiCulture = CultureInfo.CurrentUICulture.Name.ToLowerInvariant();
|
||||
_logger.Info("Current UI Culture: {0}", uiCulture);
|
||||
|
||||
if (uiCulture.StartsWith("zh")) { } // 默认就是中文, 无需操作
|
||||
else if (uiCulture.StartsWith("ja")) Language = AppLanguage.JA;
|
||||
else Language = AppLanguage.EN;
|
||||
|
||||
Resources.MergedDictionaries.Add(dict);
|
||||
}
|
||||
|
||||
private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
|
||||
|
||||
@@ -21,6 +21,8 @@ namespace SpineViewer.Extensions
|
||||
foreach (var tr in self.AnimationState.IterTracks().Where(t => t is not null))
|
||||
{
|
||||
var t = spineObject.AnimationState.SetAnimation(tr!.TrackIndex, tr.Animation, tr.Loop);
|
||||
t.TimeScale = tr.TimeScale;
|
||||
t.Alpha = tr.Alpha;
|
||||
if (keepTrackTime)
|
||||
t.TrackTime = tr.TrackTime;
|
||||
}
|
||||
@@ -38,7 +40,8 @@ namespace SpineViewer.Extensions
|
||||
foreach (var e in self.AnimationState.IterTracks())
|
||||
{
|
||||
if (e is not null)
|
||||
self.AnimationState.SetAnimation(e.TrackIndex, e.Animation, e.Loop);
|
||||
e.TrackTime = 0; // 直接重置时间能保留原本的 TrackEntry
|
||||
//self.AnimationState.SetAnimation(e.TrackIndex, e.Animation, e.Loop);
|
||||
}
|
||||
self.Update(0);
|
||||
}
|
||||
@@ -65,7 +68,7 @@ namespace SpineViewer.Extensions
|
||||
/// <summary>
|
||||
/// 按给定的帧率获取所有轨道第一个条目动画全时长包围盒大小, 是一个耗时操作, 如果可能的话最好缓存结果
|
||||
/// </summary>
|
||||
public static Rect GetAnimationBounds(this SpineObject self, float fps = 10)
|
||||
public static Rect GetAnimationBounds(this SpineObject self, float fps = 30)
|
||||
{
|
||||
using var copy = self.Copy();
|
||||
var bounds = copy.GetCurrentBounds();
|
||||
|
||||
42
SpineViewer/Models/LastStateModel.cs
Normal file
42
SpineViewer/Models/LastStateModel.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SpineViewer.Models
|
||||
{
|
||||
public class LastStateModel
|
||||
{
|
||||
#region 画面布局状态
|
||||
|
||||
public double WindowLeft { get; set; }
|
||||
public double WindowTop { get; set; }
|
||||
public double WindowWidth { get; set; }
|
||||
public double WindowHeight { get; set; }
|
||||
public WindowState WindowState { get; set; }
|
||||
|
||||
public double RootGridCol0Width { get; set; }
|
||||
public double ModelListRow0Height { get; set; }
|
||||
public double ExplorerGridRow0Height { get; set; }
|
||||
public double RightPanelGridRow0Height { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 预览画面状态
|
||||
|
||||
public uint ResolutionX { get; set; } = 1500;
|
||||
public uint ResolutionY { get; set; } = 1000;
|
||||
public uint MaxFps { get; set; } = 30;
|
||||
public float Speed { get; set; } = 1f;
|
||||
public bool ShowAxis { get; set; } = true;
|
||||
public Color BackgroundColor { get; set; } = Color.FromRgb(105, 105, 105);
|
||||
public string BackgroundImagePath { get; set; }
|
||||
public Stretch BackgroundImageMode { get; set; } = Stretch.Uniform;
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,9 @@ namespace SpineViewer.Models
|
||||
[ObservableProperty]
|
||||
private bool _renderSelectedOnly;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _associateFileSuffix;
|
||||
|
||||
[ObservableProperty]
|
||||
private AppLanguage _appLanguage;
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace SpineViewer.Models
|
||||
|
||||
public string Physics { get; set; } = ISkeleton.Physics.Update.ToString();
|
||||
|
||||
public float TimeScale { get; set; } = 1f;
|
||||
|
||||
public float Scale { get; set; } = 1f;
|
||||
|
||||
public bool FlipX { get; set; }
|
||||
@@ -31,7 +33,9 @@ namespace SpineViewer.Models
|
||||
|
||||
public Dictionary<string, string?> SlotAttachment { get; set; } = [];
|
||||
|
||||
public List<string?> Animations { get; set; } = [];
|
||||
public List<string> DisabledSlots { get; set; } = [];
|
||||
|
||||
public List<TrackConfigModel?> Animations { get; set; } = [];
|
||||
|
||||
public bool DebugTexture { get; set; } = true;
|
||||
|
||||
@@ -52,5 +56,15 @@ namespace SpineViewer.Models
|
||||
public bool DebugPoints { get; set; }
|
||||
|
||||
public bool DebugClippings { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class TrackConfigModel
|
||||
{
|
||||
public string AnimationName { get; set; } = "";
|
||||
|
||||
public float TimeScale { get; set; } = 1f;
|
||||
|
||||
public float Alpha { get; set; } = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,9 +85,11 @@ namespace SpineViewer.Models
|
||||
|
||||
public event EventHandler<SkinStatusChangedEventArgs>? SkinStatusChanged;
|
||||
|
||||
public event EventHandler<SlotVisibleChangedEventArgs>? SlotVisibleChanged;
|
||||
|
||||
public event EventHandler<SlotAttachmentChangedEventArgs>? SlotAttachmentChanged;
|
||||
|
||||
public event EventHandler<AnimationChangedEventArgs>? AnimationChanged;
|
||||
public event EventHandler<TrackPropertyChangedEventArgs>? TrackPropertyChanged;
|
||||
|
||||
public SpineVersion Version => _spineObject.Version;
|
||||
|
||||
@@ -127,6 +129,12 @@ namespace SpineViewer.Models
|
||||
set { lock (_lock) SetProperty(_spineObject.Physics, value, v => _spineObject.Physics = v); }
|
||||
}
|
||||
|
||||
public float TimeScale
|
||||
{
|
||||
get { lock (_lock) return _spineObject.AnimationState.TimeScale; }
|
||||
set { lock (_lock) SetProperty(_spineObject.AnimationState.TimeScale, Math.Clamp(value, 0.01f, 100f), v => _spineObject.AnimationState.TimeScale = v); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 缩放倍数, 绝对值大小, 两个方向大小不一致时返回 -1, 设置时不会影响正负号
|
||||
/// </summary>
|
||||
@@ -200,6 +208,19 @@ namespace SpineViewer.Models
|
||||
|
||||
public FrozenDictionary<string, ImmutableArray<string>> SlotAttachments => _slotAttachments;
|
||||
|
||||
public bool GetSlotVisible(string slotName)
|
||||
{
|
||||
lock (_lock) return _spineObject.GetSlotVisible(slotName);
|
||||
}
|
||||
|
||||
public bool SetSlotVisible(string slotName, bool visible)
|
||||
{
|
||||
bool changed = false;
|
||||
lock (_lock) changed = _spineObject.SetSlotVisible(slotName, visible);
|
||||
if (changed) SlotVisibleChanged?.Invoke(this, new(slotName, visible));
|
||||
return changed;
|
||||
}
|
||||
|
||||
public string? GetAttachment(string slotName)
|
||||
{
|
||||
lock (_lock) return _spineObject.GetAttachment(slotName);
|
||||
@@ -233,15 +254,59 @@ namespace SpineViewer.Models
|
||||
public void SetAnimation(int index, string name)
|
||||
{
|
||||
bool changed = false;
|
||||
float lastTimeScale = 1f;
|
||||
float lastAlpha = 1f;
|
||||
lock (_lock)
|
||||
{
|
||||
if (_spineObject.Data.AnimationsByName.ContainsKey(name))
|
||||
{
|
||||
_spineObject.AnimationState.SetAnimation(index, name, true);
|
||||
// 需要记录之前的轨道属性值并还原
|
||||
if (_spineObject.AnimationState.GetCurrent(index) is ITrackEntry entry)
|
||||
{
|
||||
lastTimeScale = entry.TimeScale;
|
||||
lastAlpha = entry.Alpha;
|
||||
}
|
||||
entry = _spineObject.AnimationState.SetAnimation(index, name, true);
|
||||
entry.TimeScale = lastTimeScale;
|
||||
entry.Alpha = lastAlpha;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) AnimationChanged?.Invoke(this, new(index, name));
|
||||
if (changed) TrackPropertyChanged?.Invoke(this, new(index, nameof(TrackPropertyChangedEventArgs.AnimationName)));
|
||||
}
|
||||
|
||||
public float GetTrackTimeScale(int index)
|
||||
{
|
||||
lock (_lock) return _spineObject.AnimationState.GetCurrent(index)?.TimeScale ?? 1;
|
||||
}
|
||||
|
||||
public void SetTrackTimeScale(int index, float scale)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_spineObject.AnimationState.GetCurrent(index) is ITrackEntry entry)
|
||||
{
|
||||
entry.TimeScale = Math.Clamp(scale, 0.01f, 100f);
|
||||
TrackPropertyChanged?.Invoke(this, new(index, nameof(TrackPropertyChangedEventArgs.TimeScale)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float GetTrackAlpha(int index)
|
||||
{
|
||||
lock (_lock) return _spineObject.AnimationState.GetCurrent(index)?.Alpha ?? 1;
|
||||
}
|
||||
|
||||
public void SetTrackAlpha(int index, float alpha)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_spineObject.AnimationState.GetCurrent(index) is ITrackEntry entry)
|
||||
{
|
||||
entry.Alpha = Math.Clamp(alpha, 0f, 1f);
|
||||
TrackPropertyChanged?.Invoke(this, new(index, nameof(TrackPropertyChangedEventArgs.Alpha)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int[] GetTrackIndices()
|
||||
@@ -262,7 +327,7 @@ namespace SpineViewer.Models
|
||||
public void ClearTrack(int index)
|
||||
{
|
||||
lock (_lock) _spineObject.AnimationState.ClearTrack(index);
|
||||
AnimationChanged?.Invoke(this, new(index, null));
|
||||
TrackPropertyChanged?.Invoke(this, new(index, nameof(TrackPropertyChangedEventArgs.AnimationName)));
|
||||
}
|
||||
|
||||
public void ResetAnimationsTime()
|
||||
@@ -373,6 +438,7 @@ namespace SpineViewer.Models
|
||||
|
||||
UsePma = _spineObject.UsePma,
|
||||
Physics = _spineObject.Physics.ToString(),
|
||||
TimeScale = _spineObject.AnimationState.TimeScale,
|
||||
|
||||
DebugTexture = _spineObject.DebugTexture,
|
||||
DebugBounds = _spineObject.DebugBounds,
|
||||
@@ -390,8 +456,25 @@ namespace SpineViewer.Models
|
||||
|
||||
foreach (var slot in _spineObject.Skeleton.Slots) config.SlotAttachment[slot.Name] = slot.Attachment?.Name;
|
||||
|
||||
config.DisabledSlots = _spineObject.Skeleton.Slots.Where(it => it.Disabled).Select(it => it.Name).ToList();
|
||||
|
||||
// XXX: 处理空动画
|
||||
config.Animations.AddRange(_spineObject.AnimationState.IterTracks().Select(tr => tr?.Animation.Name));
|
||||
foreach (var tr in _spineObject.AnimationState.IterTracks())
|
||||
{
|
||||
if (tr is not null)
|
||||
{
|
||||
config.Animations.Add(new()
|
||||
{
|
||||
AnimationName = tr.Animation.Name,
|
||||
TimeScale = tr.TimeScale,
|
||||
Alpha = tr.Alpha
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
config.Animations.Add(null);
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
@@ -410,6 +493,7 @@ namespace SpineViewer.Models
|
||||
SetProperty(_spineObject.Skeleton.Y, value.Y, v => _spineObject.Skeleton.Y = v, nameof(Y));
|
||||
SetProperty(_spineObject.UsePma, value.UsePma, v => _spineObject.UsePma = v, nameof(UsePma));
|
||||
SetProperty(_spineObject.Physics, Enum.Parse<ISkeleton.Physics>(value.Physics ?? "Update", true), v => _spineObject.Physics = v, nameof(Physics));
|
||||
SetProperty(_spineObject.AnimationState.TimeScale, value.TimeScale, v => _spineObject.AnimationState.TimeScale = v, nameof(TimeScale));
|
||||
|
||||
foreach (var name in _spineObject.Data.Skins.Select(v => v.Name).Except(value.LoadedSkins))
|
||||
if (_spineObject.SetSkinStatus(name, false))
|
||||
@@ -422,14 +506,22 @@ namespace SpineViewer.Models
|
||||
if (_spineObject.SetAttachment(slotName, attachmentName))
|
||||
SlotAttachmentChanged?.Invoke(this, new(slotName, attachmentName));
|
||||
|
||||
foreach (var slotName in value.DisabledSlots)
|
||||
if (_spineObject.SetSlotVisible(slotName, false))
|
||||
SlotVisibleChanged?.Invoke(this, new(slotName, false));
|
||||
|
||||
// XXX: 处理空动画
|
||||
_spineObject.AnimationState.ClearTracks();
|
||||
int trackIndex = 0;
|
||||
foreach (var name in value.Animations)
|
||||
foreach (var trConfig in value.Animations)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
_spineObject.AnimationState.SetAnimation(trackIndex, name, true);
|
||||
AnimationChanged?.Invoke(this, new(trackIndex, name));
|
||||
if (trConfig is not null && !string.IsNullOrEmpty(trConfig.AnimationName))
|
||||
{
|
||||
var tr = _spineObject.AnimationState.SetAnimation(trackIndex, trConfig.AnimationName, true);
|
||||
tr.TimeScale = trConfig.TimeScale;
|
||||
tr.Alpha = trConfig.Alpha;
|
||||
TrackPropertyChanged?.Invoke(this, new(trackIndex, nameof(TrackPropertyChangedEventArgs.AnimationName)));
|
||||
}
|
||||
trackIndex++;
|
||||
}
|
||||
|
||||
@@ -507,16 +599,35 @@ namespace SpineViewer.Models
|
||||
public bool Status { get; } = status;
|
||||
}
|
||||
|
||||
public class SlotVisibleChangedEventArgs(string slotName, bool visible) : EventArgs
|
||||
{
|
||||
public string SlotName { get; } = slotName;
|
||||
public bool Visible { get; } = visible;
|
||||
}
|
||||
|
||||
public class SlotAttachmentChangedEventArgs(string slotName, string? attachmentName) : EventArgs
|
||||
{
|
||||
public string SlotName { get; } = slotName;
|
||||
public string? AttachmentName { get; } = attachmentName;
|
||||
}
|
||||
|
||||
public class AnimationChangedEventArgs(int trackIndex, string? animationName) : EventArgs
|
||||
/// <summary>
|
||||
/// 模型动画轨道属性变化事件参数, 需要检索 <c><see cref="PropertyName"/></c> 来确定发生变化的属性是什么
|
||||
/// </summary>
|
||||
/// <param name="trackIndex">发生属性变化的轨道索引</param>
|
||||
/// <param name="propertyName">使用 <c>nameof</c> 设置发生改变的属性名</param>
|
||||
public class TrackPropertyChangedEventArgs(int trackIndex, string propertyName) : EventArgs
|
||||
{
|
||||
public int TrackIndex { get; } = trackIndex;
|
||||
public string? AnimationName { get; } = animationName;
|
||||
|
||||
/// <summary>
|
||||
/// 发生变化的属性名, 将会使用 <c>nameof</c> 设置为属性名称字符串
|
||||
/// </summary>
|
||||
public string PropertyName { get; } = propertyName;
|
||||
|
||||
public string? AnimationName { get; }
|
||||
public float TimeScale { get; } = 1f;
|
||||
public float Alpha { get; } = 1f;
|
||||
}
|
||||
|
||||
public class SpineObjectLoadOptions
|
||||
|
||||
@@ -43,10 +43,9 @@ namespace SpineViewer.Models
|
||||
|
||||
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
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,9 @@ using System.Windows;
|
||||
namespace SpineViewer.Natives
|
||||
{
|
||||
/// <summary>
|
||||
/// Win32 Sdk 包装类
|
||||
/// user32.dll 包装类
|
||||
/// </summary>
|
||||
public static class Win32
|
||||
public static class User32
|
||||
{
|
||||
public const int GWL_STYLE = -16;
|
||||
public const int WS_SIZEBOX = 0x40000;
|
||||
@@ -178,17 +178,11 @@ namespace SpineViewer.Natives
|
||||
[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("user32.dll")]
|
||||
public static extern bool IsIconic(IntPtr hWnd);
|
||||
|
||||
[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")]
|
||||
public static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags);
|
||||
@@ -19,6 +19,8 @@ namespace SpineViewer.Resources
|
||||
public static string Str_GeneratePreviewsTitle => Get<string>("Str_GeneratePreviewsTitle");
|
||||
public static string Str_DeletePreviewsTitle => Get<string>("Str_DeletePreviewsTitle");
|
||||
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_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_ListViewStatusBar">{0} items, {1} selected</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_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_MoveUpSpineObject">Move Up</s:String>
|
||||
<s:String x:Key="Str_MoveDownSpineObject">Move Down</s:String>
|
||||
@@ -64,6 +67,8 @@
|
||||
<s:String x:Key="Str_IsShown">Show</s:String>
|
||||
<s:String x:Key="Str_UsePma">Premultiply Alpha</s:String>
|
||||
<s:String x:Key="Str_Physics">Physics</s:String>
|
||||
<s:String x:Key="Str_TimeScale">Time Scale</s:String>
|
||||
<s:String x:Key="Str_TimeScaleTootltip">Time scale for a single model, must be positive.</s:String>
|
||||
|
||||
<s:String x:Key="Str_Transform">Transform</s:String>
|
||||
<s:String x:Key="Str_Scale">Scale</s:String>
|
||||
@@ -78,10 +83,17 @@
|
||||
|
||||
<s:String x:Key="Str_Slot">Slot</s:String>
|
||||
<s:String x:Key="Str_ClearSlotsAttachment">Clear Slots Attachment</s:String>
|
||||
<s:String x:Key="Str_EnableSlots">Enable Slots</s:String>
|
||||
<s:String x:Key="Str_DisableSlots">Disable Slots</s:String>
|
||||
|
||||
<s:String x:Key="Str_Animation">Animation</s:String>
|
||||
<s:String x:Key="Str_AppendTrack">Add</s:String>
|
||||
<s:String x:Key="Str_InsertTrack">Insert</s:String>
|
||||
<s:String x:Key="Str_ClearTrack">Clear</s:String>
|
||||
<s:String x:Key="Str_TrackTimeScale">Time Scale</s:String>
|
||||
<s:String x:Key="Str_TrackTimeScaleTooltip">Time scale for a single track, must be positive.</s:String>
|
||||
<s:String x:Key="Str_TrackAlpha">Alpha Blending</s:String>
|
||||
<s:String x:Key="Str_TrackAlphaTooltip">Value range: 0–1. Similar to image blending, controls how animations from higher-index tracks mix into lower-index tracks.</s:String>
|
||||
|
||||
<s:String x:Key="Str_Debug">Debug</s:String>
|
||||
<s:String x:Key="Str_DebugTexture">Texture</s:String>
|
||||
@@ -104,11 +116,13 @@
|
||||
<s:String x:Key="Str_Zoom">Zoom</s:String>
|
||||
<s:String x:Key="Str_Rotation">Rotation (Degrees)</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_PlaySpeed">Playback Speed</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_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>
|
||||
@@ -147,6 +161,10 @@
|
||||
<s:String x:Key="Str_InvalidMaxResolution">Valid max resolution required when using auto resolution</s:String>
|
||||
<s:String x:Key="Str_FFmpegFormatRequired">FFmpeg export format is required</s:String>
|
||||
|
||||
<s:String x:Key="Str_ExportBaseArgs">Base Parameters</s:String>
|
||||
<s:String x:Key="Str_ExportVideoArgs">Video Parameters</s:String>
|
||||
<s:String x:Key="Str_ExportOtherArgs">Other Parameters</s:String>
|
||||
|
||||
<s:String x:Key="Str_ResolutionTooltip">Screen resolution; adjust related parameters in the render settings panel</s:String>
|
||||
<s:String x:Key="Str_ExportSingle">Export Single</s:String>
|
||||
<s:String x:Key="Str_ExportSingleTooltip">When checked, export selected models in a single frame; output folder is required</s:String>
|
||||
@@ -174,13 +192,15 @@
|
||||
|
||||
<s:String x:Key="Str_VideoFormat">Video Format</s:String>
|
||||
<s:String x:Key="Str_LoopPlay">Loop Play</s:String>
|
||||
<s:String x:Key="Str_LoopPlayTooltip">Loop animation; only effective for GIF/WebP formats</s:String>
|
||||
<s:String x:Key="Str_LoopPlayTooltip" xml:space="preserve">[Gif/Webp]
Whether the animation loops</s:String>
|
||||
<s:String x:Key="Str_QualityParameter">Quality Parameter</s:String>
|
||||
<s:String x:Key="Str_QualityParameterTooltip">Range 0–100; higher is better; only for WebP format</s:String>
|
||||
<s:String x:Key="Str_QualityParameterTooltip" xml:space="preserve">[Webp]
Quality parameter, range 0-100, higher value means better quality</s:String>
|
||||
<s:String x:Key="Str_LosslessParam">Lossless Compression</s:String>
|
||||
<s:String x:Key="Str_LosslessParamTooltip">Lossless compression. Ignores the quality parameter and only applies to WebP format.</s:String>
|
||||
<s:String x:Key="Str_LosslessParamTooltip" xml:space="preserve">[Webp]
Lossless compression, quality parameter will be ignored</s:String>
|
||||
<s:String x:Key="Str_CrfParameter">CRF Parameter</s:String>
|
||||
<s:String x:Key="Str_CrfParameterTooltip">Range 0–63; lower is higher quality; only for MP4/WebM/MKV formats</s:String>
|
||||
<s:String x:Key="Str_CrfParameterTooltip" xml:space="preserve">[Mp4/Webm/Mkv]
CRF parameter, range 0-63, lower value means higher quality</s:String>
|
||||
<s:String x:Key="Str_ProfileParameter">Profile Parameter</s:String>
|
||||
<s:String x:Key="Str_ProfileParameterTooltip" xml:space="preserve">[Mov]
Profile parameter, integer between -1 and 5,
-1 means automatic, higher values indicate higher quality,
Alpha channel encoding is only available when value is 4 or higher</s:String>
|
||||
|
||||
<s:String x:Key="Str_FFmpegFormat">Export Format</s:String>
|
||||
<s:String x:Key="Str_FFmpegFormatTooltip">FFmpeg export format (equivalent to "-f"), e.g. "mp4", "webm"</s:String>
|
||||
@@ -213,7 +233,10 @@
|
||||
|
||||
<s:String x:Key="Str_SpineLoadPreference">Model Loading 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_AssociateFileSuffix">Associate File Extension</s:String>
|
||||
<s:String x:Key="Str_Language">Language</s:String>
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -37,8 +37,11 @@
|
||||
<s:String x:Key="Str_Show">表示</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_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_RemoveSpineObject">削除</s:String>
|
||||
<s:String x:Key="Str_RemoveAllSpineObject">すべて削除</s:String>
|
||||
<s:String x:Key="Str_Reload">再読み込み</s:String>
|
||||
<s:String x:Key="Str_MoveUpSpineObject">上へ移動</s:String>
|
||||
<s:String x:Key="Str_MoveDownSpineObject">下へ移動</s:String>
|
||||
@@ -64,6 +67,8 @@
|
||||
<s:String x:Key="Str_IsShown">表示</s:String>
|
||||
<s:String x:Key="Str_UsePma">プレマルチプライドアルファ</s:String>
|
||||
<s:String x:Key="Str_Physics">物理</s:String>
|
||||
<s:String x:Key="Str_TimeScale">時間スケール</s:String>
|
||||
<s:String x:Key="Str_TimeScaleTootltip">単一モデルの時間スケール。正の値のみ指定可能です。</s:String>
|
||||
|
||||
<s:String x:Key="Str_Transform">変換</s:String>
|
||||
<s:String x:Key="Str_Scale">スケール</s:String>
|
||||
@@ -78,10 +83,17 @@
|
||||
|
||||
<s:String x:Key="Str_Slot">スロット</s:String>
|
||||
<s:String x:Key="Str_ClearSlotsAttachment">アタッチメントをクリア</s:String>
|
||||
<s:String x:Key="Str_EnableSlots">有効</s:String>
|
||||
<s:String x:Key="Str_DisableSlots">無効</s:String>
|
||||
|
||||
<s:String x:Key="Str_Animation">アニメーション</s:String>
|
||||
<s:String x:Key="Str_AppendTrack">追加</s:String>
|
||||
<s:String x:Key="Str_InsertTrack">挿入</s:String>
|
||||
<s:String x:Key="Str_ClearTrack">削除</s:String>
|
||||
<s:String x:Key="Str_TrackTimeScale">時間スケール</s:String>
|
||||
<s:String x:Key="Str_TrackTimeScaleTooltip">単一トラックの時間スケール。正の値のみ指定可能です。</s:String>
|
||||
<s:String x:Key="Str_TrackAlpha">アルファ合成</s:String>
|
||||
<s:String x:Key="Str_TrackAlphaTooltip">値の範囲:0~1。画像の合成と同様に、高インデックストラックのアニメーションが低インデックストラックにどの程度混合されるかを制御します。</s:String>
|
||||
|
||||
<s:String x:Key="Str_Debug">デバッグ</s:String>
|
||||
<s:String x:Key="Str_DebugTexture">テクスチャ</s:String>
|
||||
@@ -104,11 +116,13 @@
|
||||
<s:String x:Key="Str_Zoom">ズーム</s:String>
|
||||
<s:String x:Key="Str_Rotation">回転(度)</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_PlaySpeed">再生速度</s:String>
|
||||
<s:String x:Key="Str_RenderSelectedOnly">選択のみレンダリング</s:String>
|
||||
<s:String x:Key="Str_ShowAxis">座標軸を表示</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>
|
||||
@@ -147,6 +161,10 @@
|
||||
<s:String x:Key="Str_InvalidMaxResolution">自動解像度使用時は有効な最大解像度を指定する必要があります</s:String>
|
||||
<s:String x:Key="Str_FFmpegFormatRequired">FFmpegエクスポートフォーマットを指定する必要があります</s:String>
|
||||
|
||||
<s:String x:Key="Str_ExportBaseArgs">基本パラメータ</s:String>
|
||||
<s:String x:Key="Str_ExportVideoArgs">ビデオパラメータ</s:String>
|
||||
<s:String x:Key="Str_ExportOtherArgs">その他のパラメータ</s:String>
|
||||
|
||||
<s:String x:Key="Str_ResolutionTooltip">画面解像度。関連パラメーターは画面パネルで調整してください</s:String>
|
||||
<s:String x:Key="Str_ExportSingle">単一エクスポート</s:String>
|
||||
<s:String x:Key="Str_ExportSingleTooltip">チェックすると、選択モデルを同一画面でエクスポートし、出力フォルダーの指定が必要になります</s:String>
|
||||
@@ -174,13 +192,15 @@
|
||||
|
||||
<s:String x:Key="Str_VideoFormat">ビデオフォーマット</s:String>
|
||||
<s:String x:Key="Str_LoopPlay">ループ再生</s:String>
|
||||
<s:String x:Key="Str_LoopPlayTooltip">アニメーションをループ再生するか。Gif/Webp形式のみ有効です</s:String>
|
||||
<s:String x:Key="Str_QualityParameter">品質パラメーター</s:String>
|
||||
<s:String x:Key="Str_QualityParameterTooltip">品質パラメーター。値の範囲は0-100。数値が大きいほど品質が高くなります。Webp形式のみ有効です</s:String>
|
||||
<s:String x:Key="Str_LosslessParam">可逆圧縮</s:String>
|
||||
<s:String x:Key="Str_LosslessParamTooltip">可逆圧縮を行います。品質パラメータは無視され、WebP形式にのみ適用されます。</s:String>
|
||||
<s:String x:Key="Str_CrfParameter">CRFパラメーター</s:String>
|
||||
<s:String x:Key="Str_CrfParameterTooltip">CRFパラメーター。値の範囲は0-63。数値が小さいほど品質が高くなります。Mp4/Webm/Mkv形式のみ有効です</s:String>
|
||||
<s:String x:Key="Str_LoopPlayTooltip" xml:space="preserve">[Gif/Webp]
アニメーションをループ再生するかどうか</s:String>
|
||||
<s:String x:Key="Str_QualityParameter">品質パラメータ</s:String>
|
||||
<s:String x:Key="Str_QualityParameterTooltip" xml:space="preserve">[Webp]
品質パラメータ、範囲は0-100。値が高いほど品質が良い</s:String>
|
||||
<s:String x:Key="Str_LosslessParam">無損失圧縮</s:String>
|
||||
<s:String x:Key="Str_LosslessParamTooltip" xml:space="preserve">[Webp]
無損失圧縮、品質パラメータは無視されます</s:String>
|
||||
<s:String x:Key="Str_CrfParameter">CRF パラメータ</s:String>
|
||||
<s:String x:Key="Str_CrfParameterTooltip" xml:space="preserve">[Mp4/Webm/Mkv]
CRF パラメータ、範囲0-63。値が小さいほど品質が高い</s:String>
|
||||
<s:String x:Key="Str_ProfileParameter">プロファイルパラメータ</s:String>
|
||||
<s:String x:Key="Str_ProfileParameterTooltip" xml:space="preserve">[Mov]
プロファイルパラメータ、-1から5の整数、
-1は自動、値が大きいほど品質が高い、
値が4以上の場合のみアルファチャンネルをエンコード可能</s:String>
|
||||
|
||||
<s:String x:Key="Str_FFmpegFormat">エクスポートフォーマット</s:String>
|
||||
<s:String x:Key="Str_FFmpegFormatTooltip">FFmpegエクスポートフォーマット。パラメーター“-f”に相当します。例: “mp4”、“webm”</s:String>
|
||||
@@ -213,7 +233,10 @@
|
||||
|
||||
<s:String x:Key="Str_SpineLoadPreference">モデル読み込みオプション</s:String>
|
||||
|
||||
<s:String x:Key="Str_RendererPreference">プレビュー画面オプション</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>
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -37,8 +37,11 @@
|
||||
<s:String x:Key="Str_Show">显示</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_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_RemoveSpineObject">移除</s:String>
|
||||
<s:String x:Key="Str_RemoveAllSpineObject">移除全部</s:String>
|
||||
<s:String x:Key="Str_Reload">重新加载</s:String>
|
||||
<s:String x:Key="Str_MoveUpSpineObject">上移</s:String>
|
||||
<s:String x:Key="Str_MoveDownSpineObject">下移</s:String>
|
||||
@@ -64,6 +67,8 @@
|
||||
<s:String x:Key="Str_IsShown">显示</s:String>
|
||||
<s:String x:Key="Str_UsePma">预乘Alpha通道</s:String>
|
||||
<s:String x:Key="Str_Physics">物理</s:String>
|
||||
<s:String x:Key="Str_TimeScale">时间因子</s:String>
|
||||
<s:String x:Key="Str_TimeScaleTootltip">单个模型的时间因子,只能取正数</s:String>
|
||||
|
||||
<s:String x:Key="Str_Transform">变换</s:String>
|
||||
<s:String x:Key="Str_Scale">缩放</s:String>
|
||||
@@ -78,10 +83,17 @@
|
||||
|
||||
<s:String x:Key="Str_Slot">插槽</s:String>
|
||||
<s:String x:Key="Str_ClearSlotsAttachment">清除附件</s:String>
|
||||
<s:String x:Key="Str_EnableSlots">启用</s:String>
|
||||
<s:String x:Key="Str_DisableSlots">禁用</s:String>
|
||||
|
||||
<s:String x:Key="Str_Animation">动画</s:String>
|
||||
<s:String x:Key="Str_AppendTrack">添加</s:String>
|
||||
<s:String x:Key="Str_InsertTrack">插入</s:String>
|
||||
<s:String x:Key="Str_ClearTrack">删除</s:String>
|
||||
<s:String x:Key="Str_TrackTimeScale">时间因子</s:String>
|
||||
<s:String x:Key="Str_TrackTimeScaleTooltip">单个轨道的时间因子,只能取正数</s:String>
|
||||
<s:String x:Key="Str_TrackAlpha">Alpha 混合</s:String>
|
||||
<s:String x:Key="Str_TrackAlphaTooltip">取值范围 0-1,与图像混合类似,可以控制高索引轨道在低索引轨道中的动画混合比例</s:String>
|
||||
|
||||
<s:String x:Key="Str_Debug">调试</s:String>
|
||||
<s:String x:Key="Str_DebugTexture">Texture</s:String>
|
||||
@@ -104,11 +116,13 @@
|
||||
<s:String x:Key="Str_Zoom">缩放</s:String>
|
||||
<s:String x:Key="Str_Rotation">旋转(角度)</s:String>
|
||||
<s:String x:Key="Str_MaxFps">最大帧率</s:String>
|
||||
<s:String x:Key="Str_MaxFpsTooltip">预览画面的最大帧率,设置为 0 时则无帧率限制</s:String>
|
||||
<s:String x:Key="Str_PlaySpeed">播放速度</s:String>
|
||||
<s:String x:Key="Str_RenderSelectedOnly">仅渲染选中</s:String>
|
||||
<s:String x:Key="Str_ShowAxis">显示坐标轴</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>
|
||||
@@ -147,6 +161,10 @@
|
||||
<s:String x:Key="Str_InvalidMaxResolution">使用自动分辨率时需要提供有效的最大分辨率</s:String>
|
||||
<s:String x:Key="Str_FFmpegFormatRequired">必须指定 FFmpeg 导出格式</s:String>
|
||||
|
||||
<s:String x:Key="Str_ExportBaseArgs">基本参数</s:String>
|
||||
<s:String x:Key="Str_ExportVideoArgs">视频参数</s:String>
|
||||
<s:String x:Key="Str_ExportOtherArgs">其他参数</s:String>
|
||||
|
||||
<s:String x:Key="Str_ResolutionTooltip">画面分辨率,相关参数请在画面参数面板进行调整</s:String>
|
||||
<s:String x:Key="Str_ExportSingle">导出单个</s:String>
|
||||
<s:String x:Key="Str_ExportSingleTooltip">勾选后将所选模型在同一个画面上进行导出,且必须提供输出文件夹</s:String>
|
||||
@@ -168,19 +186,21 @@
|
||||
|
||||
<s:String x:Key="Str_Fps">帧率</s:String>
|
||||
<s:String x:Key="Str_ExportSpeed">导出速度</s:String>
|
||||
<s:String x:Key="Str_ExportSpeedTooltip">导出速度因子, 仅影响模型的动作速度, 不影响导出时长和帧率等参数</s:String>
|
||||
<s:String x:Key="Str_ExportSpeedTooltip">导出速度因子,仅影响模型的动作速度,不影响导出时长和帧率等参数</s:String>
|
||||
<s:String x:Key="Str_KeepLastFrame">保留最后一帧</s:String>
|
||||
<s:String x:Key="Str_KeepLastFrameTooltip">当设置保留最后一帧时,动图会更为连贯,但是帧数可能比预期帧数多 1</s:String>
|
||||
|
||||
<s:String x:Key="Str_VideoFormat">视频格式</s:String>
|
||||
<s:String x:Key="Str_LoopPlay">循环播放</s:String>
|
||||
<s:String x:Key="Str_LoopPlayTooltip">动图是否循环播放,仅对 Gif/Webp 格式生效</s:String>
|
||||
<s:String x:Key="Str_LoopPlayTooltip" xml:space="preserve">[Gif/Webp]
动图是否循环播放</s:String>
|
||||
<s:String x:Key="Str_QualityParameter">质量参数</s:String>
|
||||
<s:String x:Key="Str_QualityParameterTooltip">质量参数,取值范围 0-100,越高质量越好, 仅对 Webp 格式生效</s:String>
|
||||
<s:String x:Key="Str_QualityParameterTooltip" xml:space="preserve">[Webp]
质量参数,取值范围 0-100,越高质量越好</s:String>
|
||||
<s:String x:Key="Str_LosslessParam">无损压缩</s:String>
|
||||
<s:String x:Key="Str_LosslessParamTooltip">无损压缩, 会忽略质量参数, 仅对 Webp 格式生效</s:String>
|
||||
<s:String x:Key="Str_LosslessParamTooltip" xml:space="preserve">[Webp]
无损压缩,会忽略质量参数</s:String>
|
||||
<s:String x:Key="Str_CrfParameter">CRF 参数</s:String>
|
||||
<s:String x:Key="Str_CrfParameterTooltip">CRF 参数,取值范围 0-63,越小质量越高,仅对 Mp4/Webm/Mkv 格式生效</s:String>
|
||||
<s:String x:Key="Str_CrfParameterTooltip" xml:space="preserve">[Mp4/Webm/Mkv]
CRF 参数,取值范围 0-63,越小质量越高</s:String>
|
||||
<s:String x:Key="Str_ProfileParameter">Profile 参数</s:String>
|
||||
<s:String x:Key="Str_ProfileParameterTooltip" xml:space="preserve">[Mov]
Profile 参数,取值集合为 -1 到 5 之间的整数,
-1 表示自动,0-5 取值越高质量越高,
仅在取值大于等于 4 时可以编码透明度通道</s:String>
|
||||
|
||||
<s:String x:Key="Str_FFmpegFormat">导出格式</s:String>
|
||||
<s:String x:Key="Str_FFmpegFormatTooltip">FFmpeg 导出格式,等价于参数 “-f”,例如 “mp4”、“webm”</s:String>
|
||||
@@ -213,7 +233,10 @@
|
||||
|
||||
<s:String x:Key="Str_SpineLoadPreference">模型加载选项</s:String>
|
||||
|
||||
<s:String x:Key="Str_RendererPreference">预览画面选项</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>
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -61,6 +61,18 @@ namespace SpineViewer.Services
|
||||
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>
|
||||
@@ -78,6 +90,22 @@ namespace SpineViewer.Services
|
||||
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 = "")
|
||||
{
|
||||
var dialog = new OpenFileDialog()
|
||||
|
||||
@@ -7,19 +7,24 @@
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>0.15.9</Version>
|
||||
<Version>0.15.18</Version>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<NoWarn>$(NoWarn);NETSDK1206</NoWarn>
|
||||
<ApplicationIcon>appicon.ico</ApplicationIcon>
|
||||
<ApplicationIcon>Resources\Images\spineviewer.ico</ApplicationIcon>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<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>
|
||||
|
||||
@@ -35,6 +35,9 @@ namespace SpineViewer.ViewModels.Exporters
|
||||
public int Crf { get => _crf; set => SetProperty(ref _crf, Math.Clamp(value, 0, 63)); }
|
||||
protected int _crf = 23;
|
||||
|
||||
public int Profile { get => _profile; set => SetProperty(ref _profile, Math.Clamp(value, -1, 5)); }
|
||||
protected int _profile = 5;
|
||||
|
||||
private string FormatSuffix => $".{_format.ToString().ToLower()}";
|
||||
|
||||
protected override void Export(SpineObjectModel[] models)
|
||||
@@ -60,7 +63,8 @@ namespace SpineViewer.ViewModels.Exporters
|
||||
Loop = _loop,
|
||||
Quality = _quality,
|
||||
Lossless = _lossless,
|
||||
Crf = _crf
|
||||
Crf = _crf,
|
||||
Profile = _profile,
|
||||
};
|
||||
|
||||
// 非自动分辨率则直接用预览画面的视区参数
|
||||
|
||||
@@ -163,6 +163,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
Size = new(bounds.Width, -bounds.Height),
|
||||
Format = SkiaSharp.SKEncodedImageFormat.Webp,
|
||||
Quality = PreviewQuality,
|
||||
BackgroundColor = SFML.Graphics.Color.Transparent,
|
||||
};
|
||||
exporter.Export(m.PreviewFilePath, sp);
|
||||
}
|
||||
@@ -199,6 +200,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
{
|
||||
Format = SkiaSharp.SKEncodedImageFormat.Webp,
|
||||
Quality = PreviewQuality,
|
||||
BackgroundColor = SFML.Graphics.Color.Transparent,
|
||||
};
|
||||
for (int i = 0; i < totalCount; i++)
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using SFMLRenderer;
|
||||
using SpineViewer.Models;
|
||||
using SpineViewer.Services;
|
||||
using SpineViewer.Utils;
|
||||
using System.Windows;
|
||||
using System.Windows.Shell;
|
||||
|
||||
namespace SpineViewer.ViewModels.MainWindow
|
||||
@@ -72,6 +73,8 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
public SFMLRendererViewModel SFMLRendererViewModel => _sfmlRendererViewModel;
|
||||
private readonly SFMLRendererViewModel _sfmlRendererViewModel;
|
||||
|
||||
public RelayCommand Cmd_Exit => new(App.Current.Shutdown);
|
||||
|
||||
/// <summary>
|
||||
/// 打开工作区
|
||||
/// </summary>
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Microsoft.Win32;
|
||||
using NLog;
|
||||
using Spine.SpineWrappers;
|
||||
using SpineViewer.Models;
|
||||
using SpineViewer.Natives;
|
||||
using SpineViewer.Services;
|
||||
using SpineViewer.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -23,6 +26,10 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
/// </summary>
|
||||
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 readonly MainWindowViewModel _vmMain;
|
||||
@@ -63,8 +70,19 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
/// </summary>
|
||||
public void LoadPreference()
|
||||
{
|
||||
if (JsonHelper.Deserialize<PreferenceModel>(PreferenceFilePath, out var obj, true))
|
||||
Preference = obj;
|
||||
if (JsonHelper.Deserialize<PreferenceModel>(PreferenceFilePath, out var obj, true))
|
||||
{
|
||||
try
|
||||
{
|
||||
Preference = obj;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
_logger.Trace(ex.ToString());
|
||||
_logger.Error("Failed to load some prefereneces, {0}", ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -94,6 +112,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
DebugClippings = DebugClippings,
|
||||
|
||||
RenderSelectedOnly = RenderSelectedOnly,
|
||||
AssociateFileSuffix = AssociateFileSuffix,
|
||||
AppLanguage = AppLanguage,
|
||||
};
|
||||
}
|
||||
@@ -118,6 +137,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
DebugClippings = value.DebugClippings;
|
||||
|
||||
RenderSelectedOnly = value.RenderSelectedOnly;
|
||||
AssociateFileSuffix = value.AssociateFileSuffix;
|
||||
AppLanguage = value.AppLanguage;
|
||||
}
|
||||
}
|
||||
@@ -230,6 +250,71 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
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
|
||||
{
|
||||
get => ((App)App.Current).Language;
|
||||
|
||||
@@ -10,8 +10,10 @@ using SpineViewer.Services;
|
||||
using SpineViewer.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@@ -23,6 +25,8 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
{
|
||||
public class SFMLRendererViewModel : ObservableObject
|
||||
{
|
||||
public ImmutableArray<Stretch> StretchOptions { get; } = Enum.GetValues<Stretch>().ToImmutableArray();
|
||||
|
||||
/// <summary>
|
||||
/// 日志器
|
||||
/// </summary>
|
||||
@@ -69,6 +73,13 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
private float _forwardDelta = 0;
|
||||
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>
|
||||
@@ -86,6 +97,14 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
/// </summary>
|
||||
public event NotifyCollectionChangedEventHandler? RequestSelectionChanging;
|
||||
|
||||
public void SetResolution(uint x, uint y)
|
||||
{
|
||||
var lastRes = _renderer.Resolution;
|
||||
_renderer.Resolution = new(x, y);
|
||||
if (lastRes.X != x) OnPropertyChanged(nameof(ResolutionX));
|
||||
if (lastRes.Y != y) OnPropertyChanged(nameof(ResolutionY));
|
||||
}
|
||||
|
||||
public uint ResolutionX
|
||||
{
|
||||
get => _renderer.Resolution.X;
|
||||
@@ -161,6 +180,64 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
}
|
||||
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
|
||||
{
|
||||
get => _renderSelectedOnly;
|
||||
@@ -181,6 +258,14 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
}
|
||||
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(() =>
|
||||
{
|
||||
IsUpdating = false;
|
||||
@@ -378,6 +463,38 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
|
||||
_renderer.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);
|
||||
}
|
||||
}
|
||||
|
||||
if (_showAxis)
|
||||
{
|
||||
// 画一个很长的坐标轴, 用 1e9 比较合适
|
||||
@@ -434,7 +551,6 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
|
||||
public RendererWorkspaceConfigModel WorkspaceConfig
|
||||
{
|
||||
// TODO: 背景图片
|
||||
get
|
||||
{
|
||||
return new()
|
||||
@@ -451,12 +567,13 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
Speed = Speed,
|
||||
ShowAxis = ShowAxis,
|
||||
BackgroundColor = BackgroundColor,
|
||||
BackgroundImagePath = BackgroundImagePath,
|
||||
BackgroundImageMode = BackgroundImageMode,
|
||||
};
|
||||
}
|
||||
set
|
||||
{
|
||||
ResolutionX = value.ResolutionX;
|
||||
ResolutionY = value.ResolutionY;
|
||||
SetResolution(value.ResolutionX, value.ResolutionY);
|
||||
CenterX = value.CenterX;
|
||||
CenterY = value.CenterY;
|
||||
Zoom = value.Zoom;
|
||||
@@ -467,6 +584,8 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
Speed = value.Speed;
|
||||
ShowAxis = value.ShowAxis;
|
||||
BackgroundColor = value.BackgroundColor;
|
||||
BackgroundImagePath = value.BackgroundImagePath;
|
||||
BackgroundImageMode = value.BackgroundImageMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using SpineViewer.ViewModels.Exporters;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -45,6 +46,11 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
_customFFmpegExporterViewModel = new(_vmMain);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求选中项发生变化
|
||||
/// </summary>
|
||||
public event NotifyCollectionChangedEventHandler? RequestSelectionChanging;
|
||||
|
||||
/// <summary>
|
||||
/// 单帧导出 ViewModel
|
||||
/// </summary>
|
||||
@@ -101,7 +107,12 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
|
||||
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>
|
||||
@@ -138,6 +149,34 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
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>
|
||||
@@ -457,7 +496,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
{
|
||||
if (ct.IsCancellationRequested) break;
|
||||
|
||||
var skelPath = paths[i];
|
||||
var skelPath = paths[totalCount - 1 - i]; // 从后往前添加, 每次插入到列表的第一个
|
||||
reporter.ProgressText = $"[{i}/{totalCount}] {skelPath}";
|
||||
|
||||
if (AddSpineObject(skelPath))
|
||||
@@ -480,7 +519,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安全地在末尾添加一个模型, 发生错误会输出日志
|
||||
/// 安全地在列表头添加一个模型, 发生错误会输出日志
|
||||
/// </summary>
|
||||
/// <returns>是否添加成功</returns>
|
||||
private bool AddSpineObject(string skelPath, string? atlasPath = null)
|
||||
@@ -488,7 +527,20 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
try
|
||||
{
|
||||
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;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -499,22 +551,6 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
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
|
||||
{
|
||||
get
|
||||
@@ -577,7 +613,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
{
|
||||
if (ct.IsCancellationRequested) break;
|
||||
|
||||
var cfg = models[i];
|
||||
var cfg = models[totalCount - 1 - i]; // 从后往前添加, 每次插入到列表的第一个
|
||||
reporter.ProgressText = $"[{i}/{totalCount}] {cfg}";
|
||||
|
||||
if (AddSpineObject(cfg))
|
||||
@@ -605,5 +641,38 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
{
|
||||
private SpineObjectModel[] _selectedObjects = [];
|
||||
private readonly ObservableCollection<SkinViewModel> _skins = [];
|
||||
private readonly ObservableCollection<SlotAttachmentViewModel> _slots = [];
|
||||
private readonly ObservableCollection<SlotViewModel> _slots = [];
|
||||
private readonly ObservableCollection<AnimationTrackViewModel> _animationTracks = [];
|
||||
|
||||
public ImmutableArray<ISkeleton.Physics> PhysicsOptions { get; } = Enum.GetValues<ISkeleton.Physics>().ToImmutableArray();
|
||||
@@ -31,7 +31,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
foreach (var obj in _selectedObjects)
|
||||
{
|
||||
obj.PropertyChanged -= SingleModel_PropertyChanged;
|
||||
obj.AnimationChanged -= SingleModel_AnimationChanged;
|
||||
obj.TrackPropertyChanged -= SingleModel_TrackPropChanged;
|
||||
}
|
||||
_skins.Clear();
|
||||
_slots.Clear();
|
||||
@@ -44,7 +44,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
foreach (var obj in _selectedObjects)
|
||||
{
|
||||
obj.PropertyChanged += SingleModel_PropertyChanged;
|
||||
obj.AnimationChanged += SingleModel_AnimationChanged;
|
||||
obj.TrackPropertyChanged += SingleModel_TrackPropChanged;
|
||||
}
|
||||
|
||||
IEnumerable<string> commonSkinNames = _selectedObjects[0].Skins;
|
||||
@@ -74,6 +74,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
OnPropertyChanged(nameof(IsShown));
|
||||
OnPropertyChanged(nameof(UsePma));
|
||||
OnPropertyChanged(nameof(Physics));
|
||||
OnPropertyChanged(nameof(TimeScale));
|
||||
|
||||
OnPropertyChanged(nameof(Scale));
|
||||
OnPropertyChanged(nameof(FlipX));
|
||||
@@ -217,6 +218,25 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
}
|
||||
}
|
||||
|
||||
public float? TimeScale
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_selectedObjects.Length <= 0) return null;
|
||||
var val = _selectedObjects[0].TimeScale;
|
||||
if (_selectedObjects.Skip(1).Any(it => it.TimeScale != val)) return null;
|
||||
return val;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_selectedObjects.Length <= 0) return;
|
||||
if (value is null) return;
|
||||
foreach (var sp in _selectedObjects) sp.TimeScale = (float)value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public float? Scale
|
||||
{
|
||||
get
|
||||
@@ -324,11 +344,16 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
args => { return args is not null && args.OfType<SkinViewModel>().Any(); }
|
||||
);
|
||||
|
||||
public ObservableCollection<SlotAttachmentViewModel> Slots => _slots;
|
||||
public ObservableCollection<SlotViewModel> Slots => _slots;
|
||||
|
||||
public RelayCommand<IList?> Cmd_ClearSlotsAttachment { get; } = new(
|
||||
args => { if (args is null) return; foreach (var s in args.OfType<SlotAttachmentViewModel>()) s.AttachmentName = null; },
|
||||
args => { return args is not null && args.OfType<SlotAttachmentViewModel>().Any(); }
|
||||
public RelayCommand<IList?> Cmd_EnableSlots { get; } = new(
|
||||
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(); }
|
||||
);
|
||||
|
||||
public RelayCommand<IList?> Cmd_DisableSlots { get; } = new(
|
||||
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(); }
|
||||
);
|
||||
|
||||
public ObservableCollection<AnimationTrackViewModel> AnimationTracks => _animationTracks;
|
||||
@@ -379,6 +404,27 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
);
|
||||
private RelayCommand<IList?>? _cmd_InsertTrack;
|
||||
|
||||
public RelayCommand<IList?>? Cmd_ClearTrack => _cmd_ClearTrack ??= new(
|
||||
args =>
|
||||
{
|
||||
if (_selectedObjects.Length <= 0) return;
|
||||
if (args is null) return;
|
||||
if (args.Count <= 0) return;
|
||||
|
||||
foreach (var vm in args.OfType<AnimationTrackViewModel>())
|
||||
foreach (var sp in _selectedObjects)
|
||||
sp.ClearTrack(vm.TrackIndex);
|
||||
},
|
||||
args =>
|
||||
{
|
||||
if (_selectedObjects.Length <= 0) return false;
|
||||
if (args is null) return false;
|
||||
if (args.Count <= 0) return false;
|
||||
return true;
|
||||
}
|
||||
);
|
||||
private RelayCommand<IList?>? _cmd_ClearTrack;
|
||||
|
||||
public bool? DebugTexture
|
||||
{
|
||||
get
|
||||
@@ -569,58 +615,67 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 监听单个模型属性发生变化, 则更新聚合属性值
|
||||
/// </summary>
|
||||
private void SingleModel_PropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
private static readonly Dictionary<string, string> _singleModelPropertyMap = new()
|
||||
{
|
||||
if (e.PropertyName == nameof(SpineObjectModel.IsShown)) OnPropertyChanged(nameof(IsShown));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.UsePma)) OnPropertyChanged(nameof(UsePma));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.Physics)) OnPropertyChanged(nameof(Physics));
|
||||
{ nameof(SpineObjectModel.IsShown), nameof(IsShown) },
|
||||
{ nameof(SpineObjectModel.UsePma), nameof(UsePma) },
|
||||
{ nameof(SpineObjectModel.Physics), nameof(Physics) },
|
||||
{ nameof(SpineObjectModel.TimeScale), nameof(TimeScale) },
|
||||
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.Scale)) OnPropertyChanged(nameof(Scale));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.FlipX)) OnPropertyChanged(nameof(FlipX));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.FlipY)) OnPropertyChanged(nameof(FlipY));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.X)) OnPropertyChanged(nameof(X));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.Y)) OnPropertyChanged(nameof(Y));
|
||||
{ nameof(SpineObjectModel.Scale), nameof(Scale) },
|
||||
{ nameof(SpineObjectModel.FlipX), nameof(FlipX) },
|
||||
{ nameof(SpineObjectModel.FlipY), nameof(FlipY) },
|
||||
{ nameof(SpineObjectModel.X), nameof(X) },
|
||||
{ nameof(SpineObjectModel.Y), nameof(Y) },
|
||||
|
||||
// Skins 变化在 SkinViewModel 中监听
|
||||
// Slots 变化在 SlotAttachmentViewModel 中监听
|
||||
// AnimationTracks 变化在 AnimationTrackViewModel 中监听
|
||||
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.DebugTexture)) OnPropertyChanged(nameof(DebugTexture));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.DebugBounds)) OnPropertyChanged(nameof(DebugBounds));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.DebugBones)) OnPropertyChanged(nameof(DebugBones));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.DebugRegions)) OnPropertyChanged(nameof(DebugRegions));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.DebugMeshHulls)) OnPropertyChanged(nameof(DebugMeshHulls));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.DebugMeshes)) OnPropertyChanged(nameof(DebugMeshes));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.DebugBoundingBoxes)) OnPropertyChanged(nameof(DebugBoundingBoxes));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.DebugPaths)) OnPropertyChanged(nameof(DebugPaths));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.DebugPoints)) OnPropertyChanged(nameof(DebugPoints));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.DebugClippings)) OnPropertyChanged(nameof(DebugClippings));
|
||||
{ nameof(SpineObjectModel.DebugTexture), nameof(DebugTexture) },
|
||||
{ nameof(SpineObjectModel.DebugBounds), nameof(DebugBounds) },
|
||||
{ nameof(SpineObjectModel.DebugBones), nameof(DebugBones) },
|
||||
{ nameof(SpineObjectModel.DebugRegions), nameof(DebugRegions) },
|
||||
{ nameof(SpineObjectModel.DebugMeshHulls), nameof(DebugMeshHulls) },
|
||||
{ nameof(SpineObjectModel.DebugMeshes), nameof(DebugMeshes) },
|
||||
{ nameof(SpineObjectModel.DebugBoundingBoxes), nameof(DebugBoundingBoxes) },
|
||||
{ nameof(SpineObjectModel.DebugPaths), nameof(DebugPaths) },
|
||||
{ nameof(SpineObjectModel.DebugPoints), nameof(DebugPoints) },
|
||||
{ nameof(SpineObjectModel.DebugClippings), nameof(DebugClippings) },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 监听单个模型属性发生变化, 则更新聚合属性值
|
||||
/// </summary>
|
||||
private void SingleModel_PropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (_singleModelPropertyMap.TryGetValue(e.PropertyName, out var targetProperty))
|
||||
{
|
||||
OnPropertyChanged(targetProperty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 监听单个模型动画轨道发生变化, 则重建聚合后的动画列表
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void SingleModel_AnimationChanged(object? sender, AnimationChangedEventArgs e)
|
||||
private void SingleModel_TrackPropChanged(object? sender, TrackPropertyChangedEventArgs e)
|
||||
{
|
||||
// XXX: 这里应该有更好的实现, 当 e.AnimationName == null 的时候代表删除轨道需要重新构建列表
|
||||
// 但是目前无法识别是否增加了轨道, 因此总是重建列表
|
||||
|
||||
// 由于某些原因, 直接使用 Clear 会和 UI 逻辑冲突产生报错, 因此需要放到 Dispatcher 里延迟执行
|
||||
Application.Current.Dispatcher.BeginInvoke(
|
||||
() =>
|
||||
{
|
||||
_animationTracks.Clear();
|
||||
IEnumerable<int> commonTrackIndices = _selectedObjects[0].GetTrackIndices();
|
||||
foreach (var obj in _selectedObjects.Skip(1)) commonTrackIndices = commonTrackIndices.Intersect(obj.GetTrackIndices());
|
||||
foreach (var idx in commonTrackIndices) _animationTracks.Add(new(idx, _selectedObjects));
|
||||
}
|
||||
);
|
||||
if (e.PropertyName == nameof(TrackPropertyChangedEventArgs.AnimationName))
|
||||
{
|
||||
// XXX: 这里应该有更好的实现, 当 e.AnimationName == null 的时候代表删除轨道需要重新构建列表
|
||||
// 但是目前无法识别是否增加了轨道, 因此总是重建列表
|
||||
|
||||
// 由于某些原因, 直接使用 Clear 会和 UI 逻辑冲突产生报错, 因此需要放到 Dispatcher 里延迟执行
|
||||
Application.Current.Dispatcher.BeginInvoke(
|
||||
() =>
|
||||
{
|
||||
_animationTracks.Clear();
|
||||
IEnumerable<int> commonTrackIndices = _selectedObjects[0].GetTrackIndices();
|
||||
foreach (var obj in _selectedObjects.Skip(1)) commonTrackIndices = commonTrackIndices.Intersect(obj.GetTrackIndices());
|
||||
foreach (var idx in commonTrackIndices) _animationTracks.Add(new(idx, _selectedObjects));
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public class SkinViewModel : ObservableObject
|
||||
@@ -672,13 +727,13 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
}
|
||||
}
|
||||
|
||||
public class SlotAttachmentViewModel : ObservableObject
|
||||
public class SlotViewModel : ObservableObject
|
||||
{
|
||||
private readonly SpineObjectModel[] _spines;
|
||||
private readonly string[] _attachmentNames = [];
|
||||
private readonly string _slotName;
|
||||
|
||||
public SlotAttachmentViewModel(string slotName, SpineObjectModel[] spines)
|
||||
public SlotViewModel(string slotName, SpineObjectModel[] spines)
|
||||
{
|
||||
_spines = spines;
|
||||
_slotName = slotName;
|
||||
@@ -694,6 +749,11 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
// 使用弱引用, 则此 ViewModel 被释放时无需显式退订事件
|
||||
foreach (var sp in _spines)
|
||||
{
|
||||
WeakEventManager<SpineObjectModel, SlotVisibleChangedEventArgs>.AddHandler(
|
||||
sp,
|
||||
nameof(sp.SlotVisibleChanged),
|
||||
SingleModel_SlotVisibleChanged
|
||||
);
|
||||
WeakEventManager<SpineObjectModel, SlotAttachmentChangedEventArgs>.AddHandler(
|
||||
sp,
|
||||
nameof(sp.SlotAttachmentChanged),
|
||||
@@ -707,9 +767,6 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
}
|
||||
}
|
||||
|
||||
public RelayCommand Cmd_ClearAttachment => _cmd_ClearAttachment ??= new(() => AttachmentName = null);
|
||||
private RelayCommand? _cmd_ClearAttachment;
|
||||
|
||||
public ReadOnlyCollection<string> AttachmentNames => _attachmentNames.AsReadOnly();
|
||||
|
||||
public string SlotName => _slotName;
|
||||
@@ -733,6 +790,30 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
}
|
||||
}
|
||||
|
||||
public bool? Visible
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_spines.Length <= 0) return null;
|
||||
var val = _spines[0].GetSlotVisible(_slotName);
|
||||
if (_spines.Skip(1).Any(it => it.GetSlotVisible(_slotName) != val)) return null;
|
||||
return val;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_spines.Length <= 0) return;
|
||||
if (value is null) return;
|
||||
foreach (var sp in _spines) sp.SetSlotVisible(_slotName, (bool)value);
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void SingleModel_SlotVisibleChanged(object? sender, SlotVisibleChangedEventArgs e)
|
||||
{
|
||||
if (e.SlotName == _slotName) OnPropertyChanged(nameof(Visible));
|
||||
}
|
||||
|
||||
private void SingleModel_SlotAttachmentChanged(object? sender, SlotAttachmentChangedEventArgs e)
|
||||
{
|
||||
if (e.SlotName == _slotName) OnPropertyChanged(nameof(AttachmentName));
|
||||
@@ -767,21 +848,36 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
// 使用弱引用, 则此 ViewModel 被释放时无需显式退订事件
|
||||
foreach (var sp in _spines)
|
||||
{
|
||||
WeakEventManager<SpineObjectModel, AnimationChangedEventArgs>.AddHandler(
|
||||
WeakEventManager<SpineObjectModel, TrackPropertyChangedEventArgs>.AddHandler(
|
||||
sp,
|
||||
nameof(sp.AnimationChanged),
|
||||
SingleModel_AnimationChanged
|
||||
nameof(sp.TrackPropertyChanged),
|
||||
SingleModel_TrackPropChanged
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public RelayCommand Cmd_ClearTrack => _cmd_ClearTrack ??= new(() => { foreach (var sp in _spines) sp.ClearTrack(_trackIndex); });
|
||||
private RelayCommand? _cmd_ClearTrack;
|
||||
|
||||
public ReadOnlyCollection<string> AnimationNames => _animationNames.AsReadOnly();
|
||||
|
||||
public int TrackIndex => _trackIndex;
|
||||
|
||||
public float? AnimationDuration
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_spines.Length <= 0) return null;
|
||||
var ani = _spines[0].GetAnimation(_trackIndex);
|
||||
if (ani is null) return null;
|
||||
var val = _spines[0].GetAnimationDuration(ani);
|
||||
foreach (var sp in _spines.Skip(1))
|
||||
{
|
||||
var a = sp.GetAnimation(_trackIndex);
|
||||
if (a is null) return null;
|
||||
if (sp.GetAnimationDuration(a) != val) return null;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
public string? AnimationName
|
||||
{
|
||||
get
|
||||
@@ -803,27 +899,54 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
}
|
||||
}
|
||||
|
||||
public float? AnimationDuration
|
||||
public float? TrackTimeScale
|
||||
{
|
||||
get
|
||||
{
|
||||
// XXX: 空轨道和多选不相同都会返回 null
|
||||
if (_spines.Length <= 0) return null;
|
||||
var ani = _spines[0].GetAnimation(_trackIndex);
|
||||
if (ani is null) return null;
|
||||
var val = _spines[0].GetAnimationDuration(ani);
|
||||
foreach (var sp in _spines.Skip(1))
|
||||
{
|
||||
var a = sp.GetAnimation(_trackIndex);
|
||||
if (a is null) return null;
|
||||
if (sp.GetAnimationDuration(a) != val) return null;
|
||||
}
|
||||
var val = _spines[0].GetTrackTimeScale(_trackIndex);
|
||||
if (_spines.Skip(1).Any(it => it.GetTrackTimeScale(_trackIndex) != val)) return null;
|
||||
return val;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_spines.Length <= 0) return;
|
||||
if (value is null) return;
|
||||
foreach (var sp in _spines) sp.SetTrackTimeScale(_trackIndex, (float)value);
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void SingleModel_AnimationChanged(object? sender, AnimationChangedEventArgs e)
|
||||
public float? TrackAlpha
|
||||
{
|
||||
if (e.TrackIndex == _trackIndex) OnPropertyChanged(nameof(AnimationName));
|
||||
get
|
||||
{
|
||||
// XXX: 空轨道和多选不相同都会返回 null
|
||||
if (_spines.Length <= 0) return null;
|
||||
var val = _spines[0].GetTrackAlpha(_trackIndex);
|
||||
if (_spines.Skip(1).Any(it => it.GetTrackAlpha(_trackIndex) != val)) return null;
|
||||
return val;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_spines.Length <= 0) return;
|
||||
if (value is null) return;
|
||||
foreach (var sp in _spines) sp.SetTrackAlpha(_trackIndex, (float)value);
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void SingleModel_TrackPropChanged(object? sender, TrackPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.TrackIndex == _trackIndex)
|
||||
{
|
||||
if (e.PropertyName == nameof(TrackPropertyChangedEventArgs.AnimationName)) OnPropertyChanged(nameof(AnimationName));
|
||||
else if (e.PropertyName == nameof(TrackPropertyChangedEventArgs.TimeScale)) OnPropertyChanged(nameof(TrackTimeScale));
|
||||
else if (e.PropertyName == nameof(TrackPropertyChangedEventArgs.Alpha)) OnPropertyChanged(nameof(TrackAlpha));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:hc="https://handyorg.github.io/handycontrol"
|
||||
xmlns:local="clr-namespace:SpineViewer.Views.ExporterDialogs"
|
||||
xmlns:exporters="clr-namespace:SpineViewer.ViewModels.Exporters"
|
||||
d:DataContext="{d:DesignInstance Type=exporters:CustomFFmpegExporterViewModel}"
|
||||
mc:Ignorable="d"
|
||||
Title="{DynamicResource Str_CustomFFmpegExporterTitle}"
|
||||
Width="450"
|
||||
Height="650"
|
||||
Height="800"
|
||||
ShowInTaskbar="False"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<DockPanel>
|
||||
@@ -27,145 +28,186 @@
|
||||
</Border>
|
||||
|
||||
<Border>
|
||||
<Border.Resources>
|
||||
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource TextBoxBaseStyle}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource ComboBoxBaseStyle}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource MyToggleButton}">
|
||||
<Setter Property="HorizontalAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type GroupBox}" BasedOn="{StaticResource GroupBoxTab}">
|
||||
<Setter Property="BorderBrush" Value="LightGray"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="hc:TitleElement.Background" Value="Transparent"/>
|
||||
<Setter Property="Margin" Value="0 5 0 10"/>
|
||||
</Style>
|
||||
</Border.Resources>
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<Grid Margin="30 10">
|
||||
<Grid.Resources>
|
||||
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource TextBoxBaseStyle}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource ComboBoxBaseStyle}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource MyToggleButton}">
|
||||
<Setter Property="HorizontalAlignment" Value="Right"/>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="60"/>
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Grid.IsSharedSizeScope="True">
|
||||
<GroupBox Header="{DynamicResource Str_ExportBaseArgs}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Col1"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<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>
|
||||
|
||||
<!-- 水平分辨率 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_ResolutionX}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" IsReadOnly="True" Text="{Binding ResolutionX, Mode=OneWay}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<!-- 水平分辨率 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_ResolutionX}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" IsReadOnly="True" Text="{Binding ResolutionX, Mode=OneWay}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
|
||||
<!-- 垂直分辨率 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_ResolutionY}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" IsReadOnly="True" Text="{Binding ResolutionY, Mode=OneWay}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<!-- 垂直分辨率 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_ResolutionY}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" IsReadOnly="True" Text="{Binding ResolutionY, Mode=OneWay}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
|
||||
<!-- 是否导出单个 -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_ExportSingle}" ToolTip="{DynamicResource Str_ExportSingleTooltip}"/>
|
||||
<ToggleButton Grid.Row="2" Grid.Column="1" IsChecked="{Binding ExportSingle}" ToolTip="{DynamicResource Str_ExportSingleTooltip}"/>
|
||||
<!-- 是否导出单个 -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_ExportSingle}" ToolTip="{DynamicResource Str_ExportSingleTooltip}"/>
|
||||
<ToggleButton Grid.Row="2" Grid.Column="1" IsChecked="{Binding ExportSingle}" ToolTip="{DynamicResource Str_ExportSingleTooltip}"/>
|
||||
|
||||
<!-- 输出文件夹 -->
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="{DynamicResource Str_OutputDir}" ToolTip="{DynamicResource Str_OutputDirTooltip}"/>
|
||||
<DockPanel Grid.Row="3" Grid.Column="1">
|
||||
<Button DockPanel.Dock="Right" Content="..." Click="ButtonSelectOutputDir_Click"/>
|
||||
<TextBox Text="{Binding OutputDir}" ToolTip="{DynamicResource Str_OutputDirTooltip}"/>
|
||||
</DockPanel>
|
||||
<!-- 输出文件夹 -->
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="{DynamicResource Str_OutputDir}" ToolTip="{DynamicResource Str_OutputDirTooltip}"/>
|
||||
<DockPanel Grid.Row="3" Grid.Column="1">
|
||||
<Button DockPanel.Dock="Right" Content="..." Click="ButtonSelectOutputDir_Click"/>
|
||||
<TextBox Text="{Binding OutputDir}" ToolTip="{DynamicResource Str_OutputDirTooltip}"/>
|
||||
</DockPanel>
|
||||
|
||||
<!-- 背景颜色 -->
|
||||
<Label Grid.Row="4" Grid.Column="0" Content="{DynamicResource Str_BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||
<DockPanel Grid.Row="4" Grid.Column="1">
|
||||
<Button DockPanel.Dock="Right" Content="..." Click="ButtonPickColor_Click">
|
||||
<Button.Background>
|
||||
<SolidColorBrush Color="{Binding BackgroundColor}"/>
|
||||
</Button.Background>
|
||||
</Button>
|
||||
<TextBox x:Name="_colorTextBox" Text="{Binding BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||
</DockPanel>
|
||||
<!-- 背景颜色 -->
|
||||
<Label Grid.Row="4" Grid.Column="0" Content="{DynamicResource Str_BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||
<DockPanel Grid.Row="4" Grid.Column="1">
|
||||
<Button DockPanel.Dock="Right" Content="..." Click="ButtonPickColor_Click">
|
||||
<Button.Background>
|
||||
<SolidColorBrush Color="{Binding BackgroundColor}"/>
|
||||
</Button.Background>
|
||||
</Button>
|
||||
<TextBox x:Name="_colorTextBox" Text="{Binding BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||
</DockPanel>
|
||||
|
||||
<!-- 四周边距 -->
|
||||
<Label Grid.Row="5" Grid.Column="0" Content="{DynamicResource Str_Margin}" ToolTip="{DynamicResource Str_MarginTooltip}"/>
|
||||
<TextBox Grid.Row="5" Grid.Column="1" Text="{Binding Margin}" ToolTip="{DynamicResource Str_MarginTooltip}"/>
|
||||
<!-- 四周边距 -->
|
||||
<Label Grid.Row="5" Grid.Column="0" Content="{DynamicResource Str_Margin}" ToolTip="{DynamicResource Str_MarginTooltip}"/>
|
||||
<TextBox Grid.Row="5" Grid.Column="1" Text="{Binding Margin}" ToolTip="{DynamicResource Str_MarginTooltip}"/>
|
||||
|
||||
<!-- 自动分辨率 -->
|
||||
<Label Grid.Row="6" Grid.Column="0" Content="{DynamicResource Str_AutoResolution}" ToolTip="{DynamicResource Str_AutoResolutionTooltip}"/>
|
||||
<ToggleButton Grid.Row="6" Grid.Column="1" IsChecked="{Binding AutoResolution}" ToolTip="{DynamicResource Str_AutoResolutionTooltip}"/>
|
||||
<!-- 自动分辨率 -->
|
||||
<Label Grid.Row="6" Grid.Column="0" Content="{DynamicResource Str_AutoResolution}" ToolTip="{DynamicResource Str_AutoResolutionTooltip}"/>
|
||||
<ToggleButton Grid.Row="6" Grid.Column="1" IsChecked="{Binding AutoResolution}" ToolTip="{DynamicResource Str_AutoResolutionTooltip}"/>
|
||||
|
||||
<!-- 最大分辨率 -->
|
||||
<Label Grid.Row="7" Grid.Column="0" Content="{DynamicResource Str_MaxResolution}" ToolTip="{DynamicResource Str_MaxResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="7" Grid.Column="1" Text="{Binding MaxResolution}" ToolTip="{DynamicResource Str_MaxResolutionTooltip}"/>
|
||||
<!-- 最大分辨率 -->
|
||||
<Label Grid.Row="7" Grid.Column="0" Content="{DynamicResource Str_MaxResolution}" ToolTip="{DynamicResource Str_MaxResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="7" Grid.Column="1" Text="{Binding MaxResolution}" ToolTip="{DynamicResource Str_MaxResolutionTooltip}"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<Separator Grid.Row="8" Grid.ColumnSpan="2" Height="10"/>
|
||||
<GroupBox Header="{DynamicResource Str_ExportVideoArgs}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Col1"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 导出时长 -->
|
||||
<Label Grid.Row="9" Grid.Column="0" Content="{DynamicResource Str_Duration}" ToolTip="{DynamicResource Str_ExportDurationTooltip}"/>
|
||||
<TextBox Grid.Row="9" Grid.Column="1" Text="{Binding Duration}" ToolTip="{DynamicResource Str_ExportDurationTooltip}"/>
|
||||
<!-- 导出时长 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_Duration}" ToolTip="{DynamicResource Str_ExportDurationTooltip}"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Duration}" ToolTip="{DynamicResource Str_ExportDurationTooltip}"/>
|
||||
|
||||
<!-- 导出帧率 -->
|
||||
<Label Grid.Row="10" Grid.Column="0" Content="{DynamicResource Str_Fps}"/>
|
||||
<TextBox Grid.Row="10" Grid.Column="1" Text="{Binding Fps}"/>
|
||||
<!-- 导出帧率 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_Fps}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Fps}"/>
|
||||
|
||||
<!-- 导出速度 -->
|
||||
<Label Grid.Row="11" Grid.Column="0" Content="{DynamicResource Str_ExportSpeed}" ToolTip="{DynamicResource Str_ExportSpeedTooltip}"/>
|
||||
<TextBox Grid.Row="11" Grid.Column="1" Text="{Binding Speed}" ToolTip="{DynamicResource Str_ExportSpeedTooltip}"/>
|
||||
<!-- 导出速度 -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_ExportSpeed}" ToolTip="{DynamicResource Str_ExportSpeedTooltip}"/>
|
||||
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Speed}" ToolTip="{DynamicResource Str_ExportSpeedTooltip}"/>
|
||||
|
||||
<!-- 是否保留最后一帧 -->
|
||||
<Label Grid.Row="12" Grid.Column="0" Content="{DynamicResource Str_KeepLastFrame}" ToolTip="{DynamicResource Str_KeepLastFrameTooltip}"/>
|
||||
<ToggleButton Grid.Row="12" Grid.Column="1" IsChecked="{Binding KeepLast}" ToolTip="{DynamicResource Str_KeelLastFrameTooltip}"/>
|
||||
<!-- 是否保留最后一帧 -->
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="{DynamicResource Str_KeepLastFrame}" ToolTip="{DynamicResource Str_KeepLastFrameTooltip}"/>
|
||||
<ToggleButton Grid.Row="3" Grid.Column="1" IsChecked="{Binding KeepLast}" ToolTip="{DynamicResource Str_KeelLastFrameTooltip}"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<Separator Grid.Row="13" Grid.ColumnSpan="2" Height="10"/>
|
||||
<GroupBox Header="{DynamicResource Str_ExportOtherArgs}">
|
||||
<Grid>
|
||||
<Grid.Resources>
|
||||
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource TextBoxBaseStyle}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource ComboBoxBaseStyle}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource MyToggleButton}">
|
||||
<Setter Property="HorizontalAlignment" Value="Right"/>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Col1"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="60"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 导出格式 -->
|
||||
<Label Grid.Row="14" Grid.Column="0" Content="{DynamicResource Str_FFmpegFormat}" ToolTip="{DynamicResource Str_FFmpegFormatTooltip}"/>
|
||||
<TextBox Grid.Row="14" Grid.Column="1" Text="{Binding Format}" ToolTip="{DynamicResource Str_FFmpegFormatTooltip}"/>
|
||||
<!-- 导出格式 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_FFmpegFormat}" ToolTip="{DynamicResource Str_FFmpegFormatTooltip}"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Format}" ToolTip="{DynamicResource Str_FFmpegFormatTooltip}"/>
|
||||
|
||||
<!-- 编码器 -->
|
||||
<Label Grid.Row="15" Grid.Column="0" Content="{DynamicResource Str_FFmpegCodec}" ToolTip="{DynamicResource Str_FFmpegCodecTooltip}"/>
|
||||
<TextBox Grid.Row="15" Grid.Column="1" Text="{Binding Codec}" ToolTip="{DynamicResource Str_FFmpegCodecTooltip}"/>
|
||||
<!-- 编码器 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_FFmpegCodec}" ToolTip="{DynamicResource Str_FFmpegCodecTooltip}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Codec}" ToolTip="{DynamicResource Str_FFmpegCodecTooltip}"/>
|
||||
|
||||
<!-- 像素格式 -->
|
||||
<Label Grid.Row="16" Grid.Column="0" Content="{DynamicResource Str_FFmpegPixelFormat}" ToolTip="{DynamicResource Str_FFmpegPixelFormatTooltip}"/>
|
||||
<TextBox Grid.Row="16" Grid.Column="1" Text="{Binding PixelFormat}" ToolTip="{DynamicResource Str_FFmpegPixelFormatTooltip}"/>
|
||||
<!-- 像素格式 -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_FFmpegPixelFormat}" ToolTip="{DynamicResource Str_FFmpegPixelFormatTooltip}"/>
|
||||
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding PixelFormat}" ToolTip="{DynamicResource Str_FFmpegPixelFormatTooltip}"/>
|
||||
|
||||
<!-- 比特率 -->
|
||||
<Label Grid.Row="17" Grid.Column="0" Content="{DynamicResource Str_FFmpegBitrate}" ToolTip="{DynamicResource Str_FFmpegBitrateTooltip}"/>
|
||||
<TextBox Grid.Row="17" Grid.Column="1" Text="{Binding Bitrate}" ToolTip="{DynamicResource Str_FFmpegBitrateTooltip}"/>
|
||||
<!-- 比特率 -->
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="{DynamicResource Str_FFmpegBitrate}" ToolTip="{DynamicResource Str_FFmpegBitrateTooltip}"/>
|
||||
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Bitrate}" ToolTip="{DynamicResource Str_FFmpegBitrateTooltip}"/>
|
||||
|
||||
<!-- 滤镜 -->
|
||||
<Label Grid.Row="18" Grid.Column="0" Content="{DynamicResource Str_FFmpegFilter}" ToolTip="{DynamicResource Str_FFmpegFilterTooltip}"/>
|
||||
<TextBox Grid.Row="18" Grid.Column="1" Text="{Binding Filter}" ToolTip="{DynamicResource Str_FFmpegFilterTooltip}"/>
|
||||
<!-- 滤镜 -->
|
||||
<Label Grid.Row="4" Grid.Column="0" Content="{DynamicResource Str_FFmpegFilter}" ToolTip="{DynamicResource Str_FFmpegFilterTooltip}"/>
|
||||
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding Filter}" ToolTip="{DynamicResource Str_FFmpegFilterTooltip}"/>
|
||||
|
||||
<!-- 自定义参数 -->
|
||||
<Label Grid.Row="19" Grid.Column="0"
|
||||
VerticalAlignment="Top"
|
||||
Content="{DynamicResource Str_FFmpegCustomArgs}"
|
||||
ToolTip="{DynamicResource Str_FFmpegCustomArgsTooltip}"/>
|
||||
<TextBox Grid.Row="19" Grid.Column="1"
|
||||
HorizontalContentAlignment="Left"
|
||||
VerticalContentAlignment="Top"
|
||||
TextWrapping="Wrap"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
Text="{Binding CustomArgs}"
|
||||
ToolTip="{DynamicResource Str_FFmpegCustomArgsTooltip}"/>
|
||||
</Grid>
|
||||
<!-- 自定义参数 -->
|
||||
<Label Grid.Row="5" Grid.Column="0"
|
||||
VerticalAlignment="Top"
|
||||
Content="{DynamicResource Str_FFmpegCustomArgs}"
|
||||
ToolTip="{DynamicResource Str_FFmpegCustomArgsTooltip}"/>
|
||||
<TextBox Grid.Row="5" Grid.Column="1"
|
||||
HorizontalContentAlignment="Left"
|
||||
VerticalContentAlignment="Top"
|
||||
TextWrapping="Wrap"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
Text="{Binding CustomArgs}"
|
||||
ToolTip="{DynamicResource Str_FFmpegCustomArgsTooltip}"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</DockPanel>
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:hc="https://handyorg.github.io/handycontrol"
|
||||
xmlns:local="clr-namespace:SpineViewer.Views.ExporterDialogs"
|
||||
xmlns:exporters="clr-namespace:SpineViewer.ViewModels.Exporters"
|
||||
d:DataContext="{d:DesignInstance Type=exporters:FFmpegVideoExporterViewModel}"
|
||||
mc:Ignorable="d"
|
||||
Title="{DynamicResource Str_FFmpegVideoExporterTitle}"
|
||||
Width="450"
|
||||
Height="580"
|
||||
Height="750"
|
||||
ShowInTaskbar="False"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<DockPanel>
|
||||
@@ -27,133 +28,165 @@
|
||||
</Border>
|
||||
|
||||
<Border>
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<Grid Margin="30 10">
|
||||
<Grid.Resources>
|
||||
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource TextBoxBaseStyle}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource ComboBoxBaseStyle}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource MyToggleButton}">
|
||||
<Setter Property="HorizontalAlignment" Value="Right"/>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<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>
|
||||
<Border.Resources>
|
||||
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource TextBoxBaseStyle}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource ComboBoxBaseStyle}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource MyToggleButton}">
|
||||
<Setter Property="HorizontalAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type GroupBox}" BasedOn="{StaticResource GroupBoxTab}">
|
||||
<Setter Property="BorderBrush" Value="LightGray"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="hc:TitleElement.Background" Value="Transparent"/>
|
||||
<Setter Property="Margin" Value="0 5 0 10"/>
|
||||
</Style>
|
||||
</Border.Resources>
|
||||
|
||||
<!-- 水平分辨率 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_ResolutionX}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" IsReadOnly="True" Text="{Binding ResolutionX, Mode=OneWay}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Grid.IsSharedSizeScope="True">
|
||||
<GroupBox Header="{DynamicResource Str_ExportBaseArgs}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Col1"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<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>
|
||||
|
||||
<!-- 垂直分辨率 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_ResolutionY}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" IsReadOnly="True" Text="{Binding ResolutionY, Mode=OneWay}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<!-- 水平分辨率 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_ResolutionX}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" IsReadOnly="True" Text="{Binding ResolutionX, Mode=OneWay}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
|
||||
<!-- 是否导出单个 -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_ExportSingle}" ToolTip="{DynamicResource Str_ExportSingleTooltip}"/>
|
||||
<ToggleButton Grid.Row="2" Grid.Column="1" IsChecked="{Binding ExportSingle}" ToolTip="{DynamicResource Str_ExportSingleTooltip}"/>
|
||||
<!-- 垂直分辨率 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_ResolutionY}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" IsReadOnly="True" Text="{Binding ResolutionY, Mode=OneWay}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
|
||||
<!-- 输出文件夹 -->
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="{DynamicResource Str_OutputDir}" ToolTip="{DynamicResource Str_OutputDirTooltip}"/>
|
||||
<DockPanel Grid.Row="3" Grid.Column="1">
|
||||
<Button DockPanel.Dock="Right" Content="..." Click="ButtonSelectOutputDir_Click"/>
|
||||
<TextBox Text="{Binding OutputDir}" ToolTip="{DynamicResource Str_OutputDirTooltip}"/>
|
||||
</DockPanel>
|
||||
<!-- 是否导出单个 -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_ExportSingle}" ToolTip="{DynamicResource Str_ExportSingleTooltip}"/>
|
||||
<ToggleButton Grid.Row="2" Grid.Column="1" IsChecked="{Binding ExportSingle}" ToolTip="{DynamicResource Str_ExportSingleTooltip}"/>
|
||||
|
||||
<!-- 背景颜色 -->
|
||||
<Label Grid.Row="4" Grid.Column="0" Content="{DynamicResource Str_BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||
<DockPanel Grid.Row="4" Grid.Column="1">
|
||||
<Button DockPanel.Dock="Right" Content="..." Click="ButtonPickColor_Click">
|
||||
<Button.Background>
|
||||
<SolidColorBrush Color="{Binding BackgroundColor}"/>
|
||||
</Button.Background>
|
||||
</Button>
|
||||
<TextBox x:Name="_colorTextBox" Text="{Binding BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||
</DockPanel>
|
||||
<!-- 输出文件夹 -->
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="{DynamicResource Str_OutputDir}" ToolTip="{DynamicResource Str_OutputDirTooltip}"/>
|
||||
<DockPanel Grid.Row="3" Grid.Column="1">
|
||||
<Button DockPanel.Dock="Right" Content="..." Click="ButtonSelectOutputDir_Click"/>
|
||||
<TextBox Text="{Binding OutputDir}" ToolTip="{DynamicResource Str_OutputDirTooltip}"/>
|
||||
</DockPanel>
|
||||
|
||||
<!-- 四周边距 -->
|
||||
<Label Grid.Row="5" Grid.Column="0" Content="{DynamicResource Str_Margin}" ToolTip="{DynamicResource Str_MarginTooltip}"/>
|
||||
<TextBox Grid.Row="5" Grid.Column="1" Text="{Binding Margin}" ToolTip="{DynamicResource Str_MarginTooltip}"/>
|
||||
<!-- 背景颜色 -->
|
||||
<Label Grid.Row="4" Grid.Column="0" Content="{DynamicResource Str_BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||
<DockPanel Grid.Row="4" Grid.Column="1">
|
||||
<Button DockPanel.Dock="Right" Content="..." Click="ButtonPickColor_Click">
|
||||
<Button.Background>
|
||||
<SolidColorBrush Color="{Binding BackgroundColor}"/>
|
||||
</Button.Background>
|
||||
</Button>
|
||||
<TextBox x:Name="_colorTextBox" Text="{Binding BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||
</DockPanel>
|
||||
|
||||
<!-- 自动分辨率 -->
|
||||
<Label Grid.Row="6" Grid.Column="0" Content="{DynamicResource Str_AutoResolution}" ToolTip="{DynamicResource Str_AutoResolutionTooltip}"/>
|
||||
<ToggleButton Grid.Row="6" Grid.Column="1" IsChecked="{Binding AutoResolution}" ToolTip="{DynamicResource Str_AutoResolutionTooltip}"/>
|
||||
<!-- 四周边距 -->
|
||||
<Label Grid.Row="5" Grid.Column="0" Content="{DynamicResource Str_Margin}" ToolTip="{DynamicResource Str_MarginTooltip}"/>
|
||||
<TextBox Grid.Row="5" Grid.Column="1" Text="{Binding Margin}" ToolTip="{DynamicResource Str_MarginTooltip}"/>
|
||||
|
||||
<!-- 最大分辨率 -->
|
||||
<Label Grid.Row="7" Grid.Column="0" Content="{DynamicResource Str_MaxResolution}" ToolTip="{DynamicResource Str_MaxResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="7" Grid.Column="1" Text="{Binding MaxResolution}" ToolTip="{DynamicResource Str_MaxResolutionTooltip}"/>
|
||||
<!-- 自动分辨率 -->
|
||||
<Label Grid.Row="6" Grid.Column="0" Content="{DynamicResource Str_AutoResolution}" ToolTip="{DynamicResource Str_AutoResolutionTooltip}"/>
|
||||
<ToggleButton Grid.Row="6" Grid.Column="1" IsChecked="{Binding AutoResolution}" ToolTip="{DynamicResource Str_AutoResolutionTooltip}"/>
|
||||
|
||||
<Separator Grid.Row="8" Grid.ColumnSpan="2" Height="10"/>
|
||||
<!-- 最大分辨率 -->
|
||||
<Label Grid.Row="7" Grid.Column="0" Content="{DynamicResource Str_MaxResolution}" ToolTip="{DynamicResource Str_MaxResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="7" Grid.Column="1" Text="{Binding MaxResolution}" ToolTip="{DynamicResource Str_MaxResolutionTooltip}"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 导出时长 -->
|
||||
<Label Grid.Row="9" Grid.Column="0" Content="{DynamicResource Str_Duration}" ToolTip="{DynamicResource Str_ExportDurationTooltip}"/>
|
||||
<TextBox Grid.Row="9" Grid.Column="1" Text="{Binding Duration}" ToolTip="{DynamicResource Str_ExportDurationTooltip}"/>
|
||||
<GroupBox Header="{DynamicResource Str_ExportVideoArgs}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Col1"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 导出帧率 -->
|
||||
<Label Grid.Row="10" Grid.Column="0" Content="{DynamicResource Str_Fps}"/>
|
||||
<TextBox Grid.Row="10" Grid.Column="1" Text="{Binding Fps}"/>
|
||||
<!-- 导出时长 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_Duration}" ToolTip="{DynamicResource Str_ExportDurationTooltip}"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Duration}" ToolTip="{DynamicResource Str_ExportDurationTooltip}"/>
|
||||
|
||||
<!-- 导出速度 -->
|
||||
<Label Grid.Row="11" Grid.Column="0" Content="{DynamicResource Str_ExportSpeed}" ToolTip="{DynamicResource Str_ExportSpeedTooltip}"/>
|
||||
<TextBox Grid.Row="11" Grid.Column="1" Text="{Binding Speed}" ToolTip="{DynamicResource Str_ExportSpeedTooltip}"/>
|
||||
<!-- 导出帧率 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_Fps}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Fps}"/>
|
||||
|
||||
<!-- 是否保留最后一帧 -->
|
||||
<Label Grid.Row="12" Grid.Column="0" Content="{DynamicResource Str_KeepLastFrame}" ToolTip="{DynamicResource Str_KeepLastFrameTooltip}"/>
|
||||
<ToggleButton Grid.Row="12" Grid.Column="1" IsChecked="{Binding KeepLast}" ToolTip="{DynamicResource Str_KeelLastFrameTooltip}"/>
|
||||
<!-- 导出速度 -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_ExportSpeed}" ToolTip="{DynamicResource Str_ExportSpeedTooltip}"/>
|
||||
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Speed}" ToolTip="{DynamicResource Str_ExportSpeedTooltip}"/>
|
||||
|
||||
<Separator Grid.Row="13" Grid.ColumnSpan="2" Height="10"/>
|
||||
<!-- 是否保留最后一帧 -->
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="{DynamicResource Str_KeepLastFrame}" ToolTip="{DynamicResource Str_KeepLastFrameTooltip}"/>
|
||||
<ToggleButton Grid.Row="3" Grid.Column="1" IsChecked="{Binding KeepLast}" ToolTip="{DynamicResource Str_KeelLastFrameTooltip}"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 视频格式 -->
|
||||
<Label Grid.Row="14" Grid.Column="0" Content="{DynamicResource Str_VideoFormat}"/>
|
||||
<ComboBox Grid.Row="14" Grid.Column="1" SelectedItem="{Binding Format}" ItemsSource="{Binding VideoFormatOptions}"/>
|
||||
<GroupBox Header="{DynamicResource Str_ExportOtherArgs}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Col1"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 动图是否循环 -->
|
||||
<Label Grid.Row="15" Grid.Column="0" Content="{DynamicResource Str_LoopPlay}" ToolTip="{DynamicResource Str_LoopPlayTooltip}"/>
|
||||
<ToggleButton Grid.Row="15" Grid.Column="1" IsChecked="{Binding Loop}" ToolTip="{DynamicResource Str_LoopPlayTooltip}"/>
|
||||
<!-- 视频格式 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_VideoFormat}"/>
|
||||
<ComboBox Grid.Row="0" Grid.Column="1" SelectedItem="{Binding Format}" ItemsSource="{Binding VideoFormatOptions}"/>
|
||||
|
||||
<!-- 质量参数 -->
|
||||
<Label Grid.Row="16" Grid.Column="0" Content="{DynamicResource Str_QualityParameter}" ToolTip="{DynamicResource Str_QualityParameterTooltip}"/>
|
||||
<TextBox Grid.Row="16" Grid.Column="1" Text="{Binding Quality}" ToolTip="{DynamicResource Str_QualityParameterTooltip}"/>
|
||||
<!-- 动图是否循环 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_LoopPlay}" ToolTip="{DynamicResource Str_LoopPlayTooltip}"/>
|
||||
<ToggleButton Grid.Row="1" Grid.Column="1" IsChecked="{Binding Loop}" ToolTip="{DynamicResource Str_LoopPlayTooltip}"/>
|
||||
|
||||
<!-- 无损压缩 -->
|
||||
<Label Grid.Row="17" Grid.Column="0" Content="{DynamicResource Str_LosslessParam}" ToolTip="{DynamicResource Str_LosslessParamTooltip}"/>
|
||||
<ToggleButton Grid.Row="17" Grid.Column="1" IsChecked="{Binding Lossless}" ToolTip="{DynamicResource Str_LosslessParamTooltip}"/>
|
||||
<!-- 质量参数 -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_QualityParameter}" ToolTip="{DynamicResource Str_QualityParameterTooltip}"/>
|
||||
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Quality}" ToolTip="{DynamicResource Str_QualityParameterTooltip}"/>
|
||||
|
||||
<!-- CRF 参数 -->
|
||||
<Label Grid.Row="18" Grid.Column="0" Content="{DynamicResource Str_CrfParameter}" ToolTip="{DynamicResource Str_CrfParameterTooltip}"/>
|
||||
<TextBox Grid.Row="18" Grid.Column="1" Text="{Binding Crf}" ToolTip="{DynamicResource Str_CrfParameterTooltip}"/>
|
||||
</Grid>
|
||||
<!-- 无损压缩 -->
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="{DynamicResource Str_LosslessParam}" ToolTip="{DynamicResource Str_LosslessParamTooltip}"/>
|
||||
<ToggleButton Grid.Row="3" Grid.Column="1" IsChecked="{Binding Lossless}" ToolTip="{DynamicResource Str_LosslessParamTooltip}"/>
|
||||
|
||||
<!-- CRF 参数 -->
|
||||
<Label Grid.Row="4" Grid.Column="0" Content="{DynamicResource Str_CrfParameter}" ToolTip="{DynamicResource Str_CrfParameterTooltip}"/>
|
||||
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding Crf}" ToolTip="{DynamicResource Str_CrfParameterTooltip}"/>
|
||||
|
||||
<!-- Profile 参数 -->
|
||||
<Label Grid.Row="5" Grid.Column="0" Content="{DynamicResource Str_ProfileParameter}" ToolTip="{DynamicResource Str_ProfileParameterTooltip}"/>
|
||||
<TextBox Grid.Row="5" Grid.Column="1" Text="{Binding Profile}" ToolTip="{DynamicResource Str_ProfileParameterTooltip}"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
|
||||
</Border>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
mc:Ignorable="d"
|
||||
Title="{DynamicResource Str_FrameExporterTitle}"
|
||||
Width="450"
|
||||
Height="400"
|
||||
Height="480"
|
||||
ShowInTaskbar="False"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<DockPanel>
|
||||
@@ -28,94 +28,112 @@
|
||||
</Border>
|
||||
|
||||
<Border>
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<Border.Resources>
|
||||
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource TextBoxBaseStyle}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource ComboBoxBaseStyle}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource MyToggleButton}">
|
||||
<Setter Property="HorizontalAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type GroupBox}" BasedOn="{StaticResource GroupBoxTab}">
|
||||
<Setter Property="BorderBrush" Value="LightGray"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="hc:TitleElement.Background" Value="Transparent"/>
|
||||
<Setter Property="Margin" Value="0 5 0 10"/>
|
||||
</Style>
|
||||
</Border.Resources>
|
||||
|
||||
<Grid Margin="30 10">
|
||||
<Grid.Resources>
|
||||
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource TextBoxBaseStyle}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource ComboBoxBaseStyle}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource MyToggleButton}">
|
||||
<Setter Property="HorizontalAlignment" Value="Right"/>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<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>
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Grid.IsSharedSizeScope="True">
|
||||
<GroupBox Header="{DynamicResource Str_ExportBaseArgs}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Col1"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<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>
|
||||
|
||||
<!-- 水平分辨率 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_ResolutionX}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" IsReadOnly="True" Text="{Binding ResolutionX, Mode=OneWay}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<!-- 水平分辨率 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_ResolutionX}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" IsReadOnly="True" Text="{Binding ResolutionX, Mode=OneWay}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
|
||||
<!-- 垂直分辨率 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_ResolutionY}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" IsReadOnly="True" Text="{Binding ResolutionY, Mode=OneWay}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<!-- 垂直分辨率 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_ResolutionY}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" IsReadOnly="True" Text="{Binding ResolutionY, Mode=OneWay}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
|
||||
<!-- 是否导出单个 -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_ExportSingle}" ToolTip="{DynamicResource Str_ExportSingleTooltip}"/>
|
||||
<ToggleButton Grid.Row="2" Grid.Column="1" IsChecked="{Binding ExportSingle}" ToolTip="{DynamicResource Str_ExportSingleTooltip}"/>
|
||||
<!-- 是否导出单个 -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_ExportSingle}" ToolTip="{DynamicResource Str_ExportSingleTooltip}"/>
|
||||
<ToggleButton Grid.Row="2" Grid.Column="1" IsChecked="{Binding ExportSingle}" ToolTip="{DynamicResource Str_ExportSingleTooltip}"/>
|
||||
|
||||
<!-- 输出文件夹 -->
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="{DynamicResource Str_OutputDir}" ToolTip="{DynamicResource Str_OutputDirTooltip}"/>
|
||||
<DockPanel Grid.Row="3" Grid.Column="1">
|
||||
<Button DockPanel.Dock="Right" Content="..." Click="ButtonSelectOutputDir_Click"/>
|
||||
<TextBox Text="{Binding OutputDir}" ToolTip="{DynamicResource Str_OutputDirTooltip}"/>
|
||||
</DockPanel>
|
||||
<!-- 输出文件夹 -->
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="{DynamicResource Str_OutputDir}" ToolTip="{DynamicResource Str_OutputDirTooltip}"/>
|
||||
<DockPanel Grid.Row="3" Grid.Column="1">
|
||||
<Button DockPanel.Dock="Right" Content="..." Click="ButtonSelectOutputDir_Click"/>
|
||||
<TextBox Text="{Binding OutputDir}" ToolTip="{DynamicResource Str_OutputDirTooltip}"/>
|
||||
</DockPanel>
|
||||
|
||||
<!-- 背景颜色 -->
|
||||
<Label Grid.Row="4" Grid.Column="0" Content="{DynamicResource Str_BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||
<DockPanel Grid.Row="4" Grid.Column="1">
|
||||
<Button DockPanel.Dock="Right" Content="..." Click="ButtonPickColor_Click">
|
||||
<Button.Background>
|
||||
<SolidColorBrush Color="{Binding BackgroundColor}"/>
|
||||
</Button.Background>
|
||||
</Button>
|
||||
<TextBox x:Name="_colorTextBox" Text="{Binding BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||
</DockPanel>
|
||||
<!-- 背景颜色 -->
|
||||
<Label Grid.Row="4" Grid.Column="0" Content="{DynamicResource Str_BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||
<DockPanel Grid.Row="4" Grid.Column="1">
|
||||
<Button DockPanel.Dock="Right" Content="..." Click="ButtonPickColor_Click">
|
||||
<Button.Background>
|
||||
<SolidColorBrush Color="{Binding BackgroundColor}"/>
|
||||
</Button.Background>
|
||||
</Button>
|
||||
<TextBox x:Name="_colorTextBox" Text="{Binding BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||
</DockPanel>
|
||||
|
||||
<!-- 四周边距 -->
|
||||
<Label Grid.Row="5" Grid.Column="0" Content="{DynamicResource Str_Margin}" ToolTip="{DynamicResource Str_MarginTooltip}"/>
|
||||
<TextBox Grid.Row="5" Grid.Column="1" Text="{Binding Margin}" ToolTip="{DynamicResource Str_MarginTooltip}"/>
|
||||
<!-- 四周边距 -->
|
||||
<Label Grid.Row="5" Grid.Column="0" Content="{DynamicResource Str_Margin}" ToolTip="{DynamicResource Str_MarginTooltip}"/>
|
||||
<TextBox Grid.Row="5" Grid.Column="1" Text="{Binding Margin}" ToolTip="{DynamicResource Str_MarginTooltip}"/>
|
||||
|
||||
<!-- 自动分辨率 -->
|
||||
<Label Grid.Row="6" Grid.Column="0" Content="{DynamicResource Str_AutoResolution}" ToolTip="{DynamicResource Str_AutoResolutionTooltip}"/>
|
||||
<ToggleButton Grid.Row="6" Grid.Column="1" IsChecked="{Binding AutoResolution}" ToolTip="{DynamicResource Str_AutoResolutionTooltip}"/>
|
||||
<!-- 自动分辨率 -->
|
||||
<Label Grid.Row="6" Grid.Column="0" Content="{DynamicResource Str_AutoResolution}" ToolTip="{DynamicResource Str_AutoResolutionTooltip}"/>
|
||||
<ToggleButton Grid.Row="6" Grid.Column="1" IsChecked="{Binding AutoResolution}" ToolTip="{DynamicResource Str_AutoResolutionTooltip}"/>
|
||||
|
||||
<!-- 最大分辨率 -->
|
||||
<Label Grid.Row="7" Grid.Column="0" Content="{DynamicResource Str_MaxResolution}" ToolTip="{DynamicResource Str_MaxResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="7" Grid.Column="1" Text="{Binding MaxResolution}" ToolTip="{DynamicResource Str_MaxResolutionTooltip}"/>
|
||||
<!-- 最大分辨率 -->
|
||||
<Label Grid.Row="7" Grid.Column="0" Content="{DynamicResource Str_MaxResolution}" ToolTip="{DynamicResource Str_MaxResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="7" Grid.Column="1" Text="{Binding MaxResolution}" ToolTip="{DynamicResource Str_MaxResolutionTooltip}"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<Separator Grid.Row="8" Grid.ColumnSpan="2" Height="10"/>
|
||||
<GroupBox Header="{DynamicResource Str_ExportOtherArgs}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Col1"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 图像格式 -->
|
||||
<Label Grid.Row="9" Grid.Column="0" Content="{DynamicResource Str_ImageFormat}"/>
|
||||
<ComboBox Grid.Row="9" Grid.Column="1" SelectedItem="{Binding Format}" ItemsSource="{Binding FrameFormatOptions}"/>
|
||||
<!-- 图像格式 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_ImageFormat}"/>
|
||||
<ComboBox Grid.Row="0" Grid.Column="1" SelectedItem="{Binding Format}" ItemsSource="{Binding FrameFormatOptions}"/>
|
||||
|
||||
<!-- 图像质量 -->
|
||||
<Label Grid.Row="10" Grid.Column="0" Content="{DynamicResource Str_ImageQuality}" ToolTip="{DynamicResource Str_ImageQualityTooltip}"/>
|
||||
<TextBox Grid.Row="10" Grid.Column="1" Text="{Binding Quality}" ToolTip="{DynamicResource Str_ImageQualityTooltip}"/>
|
||||
</Grid>
|
||||
<!-- 图像质量 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_ImageQuality}" ToolTip="{DynamicResource Str_ImageQualityTooltip}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Quality}" ToolTip="{DynamicResource Str_ImageQualityTooltip}"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</DockPanel>
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:hc="https://handyorg.github.io/handycontrol"
|
||||
xmlns:local="clr-namespace:SpineViewer.Views"
|
||||
xmlns:vmexp="clr-namespace:SpineViewer.ViewModels.Exporters"
|
||||
d:DataContext="{d:DesignInstance Type=vmexp:FrameSequenceExporterViewModel}"
|
||||
mc:Ignorable="d"
|
||||
Title="{DynamicResource Str_FrameSequenceExporterTitle}"
|
||||
Width="450"
|
||||
Height="450"
|
||||
Height="550"
|
||||
ShowInTaskbar="False"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<DockPanel>
|
||||
@@ -27,104 +28,122 @@
|
||||
</Border>
|
||||
|
||||
<Border>
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<Border.Resources>
|
||||
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource TextBoxBaseStyle}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource ComboBoxBaseStyle}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource MyToggleButton}">
|
||||
<Setter Property="HorizontalAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type GroupBox}" BasedOn="{StaticResource GroupBoxTab}">
|
||||
<Setter Property="BorderBrush" Value="LightGray"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="hc:TitleElement.Background" Value="Transparent"/>
|
||||
<Setter Property="Margin" Value="0 5 0 10"/>
|
||||
</Style>
|
||||
</Border.Resources>
|
||||
|
||||
<Grid Margin="30 10">
|
||||
<Grid.Resources>
|
||||
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource TextBoxBaseStyle}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource ComboBoxBaseStyle}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource MyToggleButton}">
|
||||
<Setter Property="HorizontalAlignment" Value="Right"/>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<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>
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Grid.IsSharedSizeScope="True">
|
||||
<GroupBox Header="{DynamicResource Str_ExportBaseArgs}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Col1"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<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>
|
||||
|
||||
<!-- 水平分辨率 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_ResolutionX}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" IsReadOnly="True" Text="{Binding ResolutionX, Mode=OneWay}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<!-- 水平分辨率 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_ResolutionX}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" IsReadOnly="True" Text="{Binding ResolutionX, Mode=OneWay}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
|
||||
<!-- 垂直分辨率 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_ResolutionY}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" IsReadOnly="True" Text="{Binding ResolutionY, Mode=OneWay}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<!-- 垂直分辨率 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_ResolutionY}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" IsReadOnly="True" Text="{Binding ResolutionY, Mode=OneWay}" ToolTip="{DynamicResource Str_ResolutionTooltip}"/>
|
||||
|
||||
<!-- 是否导出单个 -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_ExportSingle}" ToolTip="{DynamicResource Str_ExportSingleTooltip}"/>
|
||||
<ToggleButton Grid.Row="2" Grid.Column="1" IsChecked="{Binding ExportSingle}" ToolTip="{DynamicResource Str_ExportSingleTooltip}"/>
|
||||
<!-- 是否导出单个 -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_ExportSingle}" ToolTip="{DynamicResource Str_ExportSingleTooltip}"/>
|
||||
<ToggleButton Grid.Row="2" Grid.Column="1" IsChecked="{Binding ExportSingle}" ToolTip="{DynamicResource Str_ExportSingleTooltip}"/>
|
||||
|
||||
<!-- 输出文件夹 -->
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="{DynamicResource Str_OutputDir}" ToolTip="{DynamicResource Str_OutputDirTooltip}"/>
|
||||
<DockPanel Grid.Row="3" Grid.Column="1">
|
||||
<Button DockPanel.Dock="Right" Content="..." Click="ButtonSelectOutputDir_Click"/>
|
||||
<TextBox Text="{Binding OutputDir}" ToolTip="{DynamicResource Str_OutputDirTooltip}"/>
|
||||
</DockPanel>
|
||||
<!-- 输出文件夹 -->
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="{DynamicResource Str_OutputDir}" ToolTip="{DynamicResource Str_OutputDirTooltip}"/>
|
||||
<DockPanel Grid.Row="3" Grid.Column="1">
|
||||
<Button DockPanel.Dock="Right" Content="..." Click="ButtonSelectOutputDir_Click"/>
|
||||
<TextBox Text="{Binding OutputDir}" ToolTip="{DynamicResource Str_OutputDirTooltip}"/>
|
||||
</DockPanel>
|
||||
|
||||
<!-- 背景颜色 -->
|
||||
<Label Grid.Row="4" Grid.Column="0" Content="{DynamicResource Str_BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||
<DockPanel Grid.Row="4" Grid.Column="1">
|
||||
<Button DockPanel.Dock="Right" Content="..." Click="ButtonPickColor_Click">
|
||||
<Button.Background>
|
||||
<SolidColorBrush Color="{Binding BackgroundColor}"/>
|
||||
</Button.Background>
|
||||
</Button>
|
||||
<TextBox x:Name="_colorTextBox" Text="{Binding BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||
</DockPanel>
|
||||
<!-- 背景颜色 -->
|
||||
<Label Grid.Row="4" Grid.Column="0" Content="{DynamicResource Str_BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||
<DockPanel Grid.Row="4" Grid.Column="1">
|
||||
<Button DockPanel.Dock="Right" Content="..." Click="ButtonPickColor_Click">
|
||||
<Button.Background>
|
||||
<SolidColorBrush Color="{Binding BackgroundColor}"/>
|
||||
</Button.Background>
|
||||
</Button>
|
||||
<TextBox x:Name="_colorTextBox" Text="{Binding BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||
</DockPanel>
|
||||
|
||||
<!-- 四周边距 -->
|
||||
<Label Grid.Row="5" Grid.Column="0" Content="{DynamicResource Str_Margin}" ToolTip="{DynamicResource Str_MarginTooltip}"/>
|
||||
<TextBox Grid.Row="5" Grid.Column="1" Text="{Binding Margin}" ToolTip="{DynamicResource Str_MarginTooltip}"/>
|
||||
<!-- 四周边距 -->
|
||||
<Label Grid.Row="5" Grid.Column="0" Content="{DynamicResource Str_Margin}" ToolTip="{DynamicResource Str_MarginTooltip}"/>
|
||||
<TextBox Grid.Row="5" Grid.Column="1" Text="{Binding Margin}" ToolTip="{DynamicResource Str_MarginTooltip}"/>
|
||||
|
||||
<!-- 自动分辨率 -->
|
||||
<Label Grid.Row="6" Grid.Column="0" Content="{DynamicResource Str_AutoResolution}" ToolTip="{DynamicResource Str_AutoResolutionTooltip}"/>
|
||||
<ToggleButton Grid.Row="6" Grid.Column="1" IsChecked="{Binding AutoResolution}" ToolTip="{DynamicResource Str_AutoResolutionTooltip}"/>
|
||||
<!-- 自动分辨率 -->
|
||||
<Label Grid.Row="6" Grid.Column="0" Content="{DynamicResource Str_AutoResolution}" ToolTip="{DynamicResource Str_AutoResolutionTooltip}"/>
|
||||
<ToggleButton Grid.Row="6" Grid.Column="1" IsChecked="{Binding AutoResolution}" ToolTip="{DynamicResource Str_AutoResolutionTooltip}"/>
|
||||
|
||||
<!-- 最大分辨率 -->
|
||||
<Label Grid.Row="7" Grid.Column="0" Content="{DynamicResource Str_MaxResolution}" ToolTip="{DynamicResource Str_MaxResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="7" Grid.Column="1" Text="{Binding MaxResolution}" ToolTip="{DynamicResource Str_MaxResolutionTooltip}"/>
|
||||
<!-- 最大分辨率 -->
|
||||
<Label Grid.Row="7" Grid.Column="0" Content="{DynamicResource Str_MaxResolution}" ToolTip="{DynamicResource Str_MaxResolutionTooltip}"/>
|
||||
<TextBox Grid.Row="7" Grid.Column="1" Text="{Binding MaxResolution}" ToolTip="{DynamicResource Str_MaxResolutionTooltip}"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<Separator Grid.Row="8" Grid.ColumnSpan="2" Height="10"/>
|
||||
<GroupBox Header="{DynamicResource Str_ExportVideoArgs}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Col1"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 导出时长 -->
|
||||
<Label Grid.Row="9" Grid.Column="0" Content="{DynamicResource Str_Duration}" ToolTip="{DynamicResource Str_ExportDurationTooltip}"/>
|
||||
<TextBox Grid.Row="9" Grid.Column="1" Text="{Binding Duration}" ToolTip="{DynamicResource Str_ExportDurationTooltip}"/>
|
||||
<!-- 导出时长 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_Duration}" ToolTip="{DynamicResource Str_ExportDurationTooltip}"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Duration}" ToolTip="{DynamicResource Str_ExportDurationTooltip}"/>
|
||||
|
||||
<!-- 导出帧率 -->
|
||||
<Label Grid.Row="10" Grid.Column="0" Content="{DynamicResource Str_Fps}"/>
|
||||
<TextBox Grid.Row="10" Grid.Column="1" Text="{Binding Fps}"/>
|
||||
<!-- 导出帧率 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_Fps}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Fps}"/>
|
||||
|
||||
<!-- 导出速度 -->
|
||||
<Label Grid.Row="11" Grid.Column="0" Content="{DynamicResource Str_ExportSpeed}" ToolTip="{DynamicResource Str_ExportSpeedTooltip}"/>
|
||||
<TextBox Grid.Row="11" Grid.Column="1" Text="{Binding Speed}" ToolTip="{DynamicResource Str_ExportSpeedTooltip}"/>
|
||||
<!-- 导出速度 -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_ExportSpeed}" ToolTip="{DynamicResource Str_ExportSpeedTooltip}"/>
|
||||
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Speed}" ToolTip="{DynamicResource Str_ExportSpeedTooltip}"/>
|
||||
|
||||
<!-- 是否保留最后一帧 -->
|
||||
<Label Grid.Row="12" Grid.Column="0" Content="{DynamicResource Str_KeepLastFrame}" ToolTip="{DynamicResource Str_KeepLastFrameTooltip}"/>
|
||||
<ToggleButton Grid.Row="12" Grid.Column="1" IsChecked="{Binding KeepLast}" ToolTip="{DynamicResource Str_KeelLastFrameTooltip}"/>
|
||||
</Grid>
|
||||
<!-- 是否保留最后一帧 -->
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="{DynamicResource Str_KeepLastFrame}" ToolTip="{DynamicResource Str_KeepLastFrameTooltip}"/>
|
||||
<ToggleButton Grid.Row="3" Grid.Column="1" IsChecked="{Binding KeepLast}" ToolTip="{DynamicResource Str_KeelLastFrameTooltip}"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</DockPanel>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
xmlns:utils="clr-namespace:SpineViewer.Utils"
|
||||
xmlns:SFMLRenderer="clr-namespace:SFMLRenderer;assembly=SFMLRenderer"
|
||||
mc:Ignorable="d"
|
||||
x:Name="_mainWindow"
|
||||
Title="{Binding Title}"
|
||||
Width="1500"
|
||||
Height="800"
|
||||
@@ -76,7 +77,7 @@
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="1">
|
||||
<Grid>
|
||||
<Grid x:Name="_rootGrid">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
@@ -89,120 +90,9 @@
|
||||
<!-- 功能页 -->
|
||||
<TabControl x:Name="_mainTabControl" TabStripPlacement="Left">
|
||||
|
||||
<!-- 浏览页 -->
|
||||
<TabItem Header="{DynamicResource Str_Explorer}" DataContext="{Binding ExplorerListViewModel}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<DockPanel>
|
||||
<Grid DockPanel.Dock="Top">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<hc:TextBox hc:InfoElement.Placeholder="{StaticResource Str_Filter}"
|
||||
Text="{Binding FilterString, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Grid.Column="1"
|
||||
hc:IconElement.Geometry="{StaticResource Geo_Folder}"
|
||||
Command="{Binding Cmd_ChangeCurrentDirectory}"
|
||||
ToolTip="{DynamicResource Str_ChangeCurrentDirectoryTooltip}"/>
|
||||
<Button Grid.Column="2"
|
||||
hc:IconElement.Geometry="{StaticResource Geo_ArrowRotateRight}"
|
||||
Command="{Binding Cmd_RefreshItems}"
|
||||
ToolTip="{DynamicResource Str_RefreshItemsTooltip}"/>
|
||||
</Grid>
|
||||
|
||||
<StatusBar DockPanel.Dock="Bottom">
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource StrFmtCvter}" ConverterParameter="Str_ListViewStatusBar">
|
||||
<Binding Path="Items.Count" ElementName="_spineFilesListBox"/>
|
||||
<Binding Path="SelectedItems.Count" ElementName="_spineFilesListBox"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</StatusBar>
|
||||
|
||||
<ListBox x:Name="_spineFilesListBox"
|
||||
VirtualizingPanel.IsVirtualizing="True"
|
||||
ItemsSource="{Binding ShownItems}"
|
||||
DisplayMemberPath="FileName"
|
||||
MouseLeftButtonDown="SpineFilesListBox_MouseLeftButtonDown">
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="SelectionChanged">
|
||||
<i:InvokeCommandAction Command="{Binding Cmd_SelectionChanged}"
|
||||
CommandParameter="{Binding SelectedItems, ElementName=_spineFilesListBox}"/>
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
|
||||
<ListBox.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="{DynamicResource Str_AddSelectedItems}"
|
||||
Command="{Binding Cmd_AddSelectedItems}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<Separator/>
|
||||
<MenuItem Header="{DynamicResource Str_GeneratePreviewForSelected}"
|
||||
Command="{Binding Cmd_GeneratePreviews}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<MenuItem Header="{StaticResource Str_DeletePreviewsForSelected}"
|
||||
Command="{Binding Cmd_DeletePreviews}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
</ContextMenu>
|
||||
</ListBox.ContextMenu>
|
||||
</ListBox>
|
||||
</DockPanel>
|
||||
|
||||
<GridSplitter Grid.Row="1" ResizeDirection="Rows"/>
|
||||
|
||||
<Grid Grid.Row="2" DataContext="{Binding SelectedItem}">
|
||||
<Grid.Resources>
|
||||
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource TextBoxBaseStyle}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 文件目录 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_FileDirectory}"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1"
|
||||
Text="{Binding FileDirectory, Mode=OneWay}"
|
||||
IsReadOnly="True"
|
||||
ToolTip="{Binding Text, RelativeSource={RelativeSource Mode=Self}}"/>
|
||||
|
||||
<!-- 文件名 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_FileName}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1"
|
||||
Text="{Binding FileName, Mode=OneWay}"
|
||||
IsReadOnly="True"/>
|
||||
|
||||
<!-- 预览图 -->
|
||||
<Border Grid.Row="2" Grid.ColumnSpan="2" Background="#a0a0a0">
|
||||
<Image Source="{Binding PreviewImage, Mode=OneWay}" Stretch="Uniform"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<!-- 模型列表页 -->
|
||||
<TabItem Header="{DynamicResource Str_SpineObject}">
|
||||
<Grid>
|
||||
<Grid x:Name="_modelListGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@@ -258,13 +148,16 @@
|
||||
<ContextMenu>
|
||||
<MenuItem Header="{DynamicResource Str_AddSpineObject}"
|
||||
Command="{Binding Cmd_AddSpineObject}"/>
|
||||
<MenuItem Header="{DynamicResource Str_AddSpineObjectFromClipboard}"
|
||||
InputGestureText="Ctrl+V"
|
||||
Command="{Binding Cmd_AddSpineObjectFromClipboard}"/>
|
||||
<MenuItem Header="{DynamicResource Str_RemoveSpineObject}"
|
||||
InputGestureText="Delete"
|
||||
Command="{Binding Cmd_RemoveSpineObject}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<MenuItem Header="{DynamicResource Str_AddSpineObjectFromClipboard}"
|
||||
InputGestureText="Ctrl+V"
|
||||
Command="{Binding Cmd_AddSpineObjectFromClipboard}"/>
|
||||
<MenuItem Header="{DynamicResource Str_RemoveAllSpineObject}"
|
||||
Command="{Binding Cmd_RemoveAllSpineObject}"
|
||||
CommandParameter="{Binding PlacementTarget.Items, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<MenuItem Header="{DynamicResource Str_Reload}"
|
||||
InputGestureText="Ctrl+R"
|
||||
Command="{Binding Cmd_ReloadSpineObject}"
|
||||
@@ -421,6 +314,7 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 显示 -->
|
||||
@@ -434,6 +328,10 @@
|
||||
<!-- 物理 -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_Physics}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="1" SelectedValue="{Binding Physics}" ItemsSource="{Binding PhysicsOptions}"/>
|
||||
|
||||
<!-- 时间因子 -->
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="{DynamicResource Str_TimeScale}" ToolTip="{DynamicResource Str_TimeScaleTootltip}"/>
|
||||
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding TimeScale}" ToolTip="{DynamicResource Str_TimeScaleTootltip}"/>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
@@ -503,7 +401,7 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label Grid.Column="0" Content="{Binding Name}"/>
|
||||
<Label Grid.Column="0" Content="{Binding Name}" Background="#bfffffff"/>
|
||||
<ToggleButton Grid.Column="1" IsChecked="{Binding Status}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
@@ -524,8 +422,11 @@
|
||||
|
||||
<ListBox.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="{DynamicResource Str_ClearSlotsAttachment}"
|
||||
Command="{Binding Cmd_ClearSlotsAttachment}"
|
||||
<MenuItem Header="{DynamicResource Str_EnableSlots}"
|
||||
Command="{Binding Cmd_EnableSlots}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<MenuItem Header="{DynamicResource Str_DisableSlots}"
|
||||
Command="{Binding Cmd_DisableSlots}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
</ContextMenu>
|
||||
</ListBox.ContextMenu>
|
||||
@@ -538,11 +439,9 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label Grid.Column="0" Content="{Binding SlotName}" HorizontalAlignment="Left"/>
|
||||
<Label Grid.Column="0" Content="{Binding SlotName}" HorizontalAlignment="Left" Background="#bfffffff"/>
|
||||
<ComboBox Grid.Column="1" SelectedValue="{Binding AttachmentName}" ItemsSource="{Binding AttachmentNames}"/>
|
||||
<Button Grid.Column="2"
|
||||
Command="{Binding Cmd_ClearAttachment}"
|
||||
hc:IconElement.Geometry="{StaticResource Geo_Ban}"/>
|
||||
<ToggleButton Grid.Column="2" IsChecked="{Binding Visible}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
@@ -567,6 +466,9 @@
|
||||
<MenuItem Header="{DynamicResource Str_InsertTrack}"
|
||||
Command="{Binding Cmd_InsertTrack}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<MenuItem Header="{DynamicResource Str_ClearTrack}"
|
||||
Command="{Binding Cmd_ClearTrack}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
</ContextMenu>
|
||||
</ListBox.ContextMenu>
|
||||
|
||||
@@ -574,19 +476,38 @@
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Col0"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="ColTrackIdx"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="ColAniTime"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Col2"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label Grid.Column="0" Content="{Binding TrackIndex}" HorizontalContentAlignment="Left"/>
|
||||
<ComboBox Grid.Column="1" SelectedValue="{Binding AnimationName}" ItemsSource="{Binding AnimationNames}"/>
|
||||
<Label Grid.Column="2"
|
||||
Content="{Binding AnimationDuration}"
|
||||
ContentStringFormat="{}{0:F3} s"/>
|
||||
<Button Grid.Column="3"
|
||||
Command="{Binding Cmd_ClearTrack}"
|
||||
hc:IconElement.Geometry="{StaticResource Geo_TrashXmark}"/>
|
||||
|
||||
<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"/>
|
||||
|
||||
<Expander Grid.Column="2" HorizontalContentAlignment="Stretch">
|
||||
<Expander.Header>
|
||||
<!-- hc 的模板自带左侧 10 的 padding, 此处用 -10 的 margin 来抵消去除 -->
|
||||
<ComboBox Margin="-10 0 0 0" Grid.Column="2" SelectedValue="{Binding AnimationName}" ItemsSource="{Binding AnimationNames}"/>
|
||||
</Expander.Header>
|
||||
<Grid Margin="1 0 0 0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 时间因子 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_TrackTimeScale}" ToolTip="{DynamicResource Str_TrackTimeScaleTooltip}"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding TrackTimeScale, StringFormat='{}{0:F3}'}" ToolTip="{DynamicResource Str_TrackTimeScaleTooltip}"/>
|
||||
|
||||
<!-- Alpha 混合 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_TrackAlpha}" ToolTip="{DynamicResource Str_TrackAlphaTooltip}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding TrackAlpha, StringFormat='{}{0:F3}'}" ToolTip="{DynamicResource Str_TrackAlphaTooltip}"/>
|
||||
</Grid>
|
||||
</Expander>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
@@ -659,6 +580,117 @@
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<!-- 浏览页 -->
|
||||
<TabItem Header="{DynamicResource Str_Explorer}" DataContext="{Binding ExplorerListViewModel}">
|
||||
<Grid x:Name="_explorerGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<DockPanel>
|
||||
<Grid DockPanel.Dock="Top">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<hc:TextBox hc:InfoElement.Placeholder="{StaticResource Str_Filter}"
|
||||
Text="{Binding FilterString, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Grid.Column="1"
|
||||
hc:IconElement.Geometry="{StaticResource Geo_Folder}"
|
||||
Command="{Binding Cmd_ChangeCurrentDirectory}"
|
||||
ToolTip="{DynamicResource Str_ChangeCurrentDirectoryTooltip}"/>
|
||||
<Button Grid.Column="2"
|
||||
hc:IconElement.Geometry="{StaticResource Geo_ArrowRotateRight}"
|
||||
Command="{Binding Cmd_RefreshItems}"
|
||||
ToolTip="{DynamicResource Str_RefreshItemsTooltip}"/>
|
||||
</Grid>
|
||||
|
||||
<StatusBar DockPanel.Dock="Bottom">
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource StrFmtCvter}" ConverterParameter="Str_ListViewStatusBar">
|
||||
<Binding Path="Items.Count" ElementName="_spineFilesListBox"/>
|
||||
<Binding Path="SelectedItems.Count" ElementName="_spineFilesListBox"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</StatusBar>
|
||||
|
||||
<ListBox x:Name="_spineFilesListBox"
|
||||
VirtualizingPanel.IsVirtualizing="True"
|
||||
ItemsSource="{Binding ShownItems}"
|
||||
DisplayMemberPath="FileName"
|
||||
MouseLeftButtonDown="SpineFilesListBox_MouseLeftButtonDown">
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="SelectionChanged">
|
||||
<i:InvokeCommandAction Command="{Binding Cmd_SelectionChanged}"
|
||||
CommandParameter="{Binding SelectedItems, ElementName=_spineFilesListBox}"/>
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
|
||||
<ListBox.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="{DynamicResource Str_AddSelectedItems}"
|
||||
Command="{Binding Cmd_AddSelectedItems}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<Separator/>
|
||||
<MenuItem Header="{DynamicResource Str_GeneratePreviewForSelected}"
|
||||
Command="{Binding Cmd_GeneratePreviews}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<MenuItem Header="{StaticResource Str_DeletePreviewsForSelected}"
|
||||
Command="{Binding Cmd_DeletePreviews}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
</ContextMenu>
|
||||
</ListBox.ContextMenu>
|
||||
</ListBox>
|
||||
</DockPanel>
|
||||
|
||||
<GridSplitter Grid.Row="1" ResizeDirection="Rows"/>
|
||||
|
||||
<Grid Grid.Row="2" DataContext="{Binding SelectedItem}">
|
||||
<Grid.Resources>
|
||||
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource TextBoxBaseStyle}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 文件目录 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_FileDirectory}"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1"
|
||||
Text="{Binding FileDirectory, Mode=OneWay}"
|
||||
IsReadOnly="True"
|
||||
ToolTip="{Binding Text, RelativeSource={RelativeSource Mode=Self}}"/>
|
||||
|
||||
<!-- 文件名 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_FileName}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1"
|
||||
Text="{Binding FileName, Mode=OneWay}"
|
||||
IsReadOnly="True"/>
|
||||
|
||||
<!-- 预览图 -->
|
||||
<Border Grid.Row="2" Grid.ColumnSpan="2" Background="#a0a0a0">
|
||||
<Image Source="{Binding PreviewImage, Mode=OneWay}" Stretch="Uniform"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<!-- 画面参数页 -->
|
||||
<TabItem Header="{DynamicResource Str_Canvas}" DataContext="{Binding SFMLRendererViewModel}">
|
||||
<TabItem.Resources>
|
||||
@@ -694,6 +726,9 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 水平分辨率 -->
|
||||
@@ -728,24 +763,34 @@
|
||||
<Label Grid.Row="7" Grid.Column="0" Content="{DynamicResource Str_FlipY}"/>
|
||||
<ToggleButton Grid.Row="7" Grid.Column="1" IsChecked="{Binding FlipY}"/>
|
||||
|
||||
<Separator Grid.Row="8" Grid.Column="0" Grid.ColumnSpan="2" Margin="0 5"/>
|
||||
|
||||
<!-- 最大帧率 -->
|
||||
<Label Grid.Row="8" Grid.Column="0" Content="{DynamicResource Str_MaxFps}"/>
|
||||
<TextBox Grid.Row="8" Grid.Column="1" Text="{Binding MaxFps}"/>
|
||||
<Label Grid.Row="9" Grid.Column="0" Content="{DynamicResource Str_MaxFps}" ToolTip="{DynamicResource Str_MaxFpsTooltip}"/>
|
||||
<TextBox Grid.Row="9" Grid.Column="1" Text="{Binding MaxFps}" ToolTip="{DynamicResource Str_MaxFpsTooltip}"/>
|
||||
|
||||
<!-- 播放速度 -->
|
||||
<Label Grid.Row="9" Grid.Column="0" Content="{DynamicResource Str_PlaySpeed}"/>
|
||||
<TextBox Grid.Row="9" Grid.Column="1" Text="{Binding Speed}"/>
|
||||
<Label Grid.Row="10" Grid.Column="0" Content="{DynamicResource Str_PlaySpeed}"/>
|
||||
<TextBox Grid.Row="10" Grid.Column="1" Text="{Binding Speed}"/>
|
||||
|
||||
<!-- 显示坐标轴 -->
|
||||
<Label Grid.Row="10" Grid.Column="0" Content="{DynamicResource Str_ShowAxis}"/>
|
||||
<ToggleButton Grid.Row="10" Grid.Column="1" IsChecked="{Binding ShowAxis}"/>
|
||||
<Label Grid.Row="11" Grid.Column="0" Content="{DynamicResource Str_ShowAxis}"/>
|
||||
<ToggleButton Grid.Row="11" Grid.Column="1" IsChecked="{Binding ShowAxis}"/>
|
||||
|
||||
<!-- 背景颜色 -->
|
||||
<Label Grid.Row="11" Grid.Column="0" Content="{DynamicResource Str_BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||
<TextBox Grid.Row="11" Grid.Column="1" Text="{Binding BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||
|
||||
<Label Grid.Row="12" Grid.Column="0" Content="{DynamicResource Str_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>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
@@ -754,14 +799,14 @@
|
||||
<GridSplitter Grid.Column="1" ResizeDirection="Columns"/>
|
||||
|
||||
<Border Grid.Column="2">
|
||||
<Grid>
|
||||
<Grid x:Name="_rightPanelGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="5*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid >
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@@ -887,6 +932,21 @@
|
||||
Opened="BottomPopup_Opened"
|
||||
MouseLeave="PopupContainer_MouseLeave"/>
|
||||
</Grid>
|
||||
|
||||
<!-- 非可视元素通知栏图标 -->
|
||||
<hc:NotifyIcon x:Name="_notifyIcon"
|
||||
Icon="/Resources/Images/spineviewer.ico"
|
||||
Click="_notifyIcon_Click"
|
||||
MouseDoubleClick="_notifyIcon_MouseDoubleClick">
|
||||
<hc:NotifyIcon.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="There may be a funtion :)" IsChecked="True"/>
|
||||
<Separator/>
|
||||
<MenuItem Header="{DynamicResource Str_Exit}" Command="{Binding Cmd_Exit}"/>
|
||||
</ContextMenu>
|
||||
</hc:NotifyIcon.ContextMenu>
|
||||
</hc:NotifyIcon>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Window>
|
||||
|
||||
@@ -3,12 +3,15 @@ using NLog;
|
||||
using NLog.Layouts;
|
||||
using NLog.Targets;
|
||||
using Spine;
|
||||
using SpineViewer.Models;
|
||||
using SpineViewer.Natives;
|
||||
using SpineViewer.Resources;
|
||||
using SpineViewer.Utils;
|
||||
using SpineViewer.ViewModels.MainWindow;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
@@ -26,6 +29,11 @@ namespace SpineViewer.Views;
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 上一次状态文件保存路径
|
||||
/// </summary>
|
||||
public static readonly string LastStateFilePath = Path.Combine(Path.GetDirectoryName(Environment.ProcessPath), "laststate.json");
|
||||
|
||||
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
private ListViewItem? _listViewDragSourceItem = null;
|
||||
private Point _listViewDragSourcePoint;
|
||||
@@ -36,11 +44,13 @@ public partial class MainWindow : Window
|
||||
{
|
||||
InitializeComponent();
|
||||
InitializeLogConfiguration();
|
||||
_vm = new (_renderPanel);
|
||||
DataContext = _vm;
|
||||
_vm.SFMLRendererViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging;
|
||||
DataContext = _vm = new(_renderPanel);
|
||||
_notifyIcon.Text = _vm.Title; // XXX: hc 的 NotifyIcon 的 Text 似乎没法双向绑定
|
||||
|
||||
_vm.SpineObjectListViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging;
|
||||
_vm.SFMLRendererViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging;
|
||||
Loaded += MainWindow_Loaded;
|
||||
ContentRendered += MainWindow_ContentRendered;
|
||||
Closed += MainWindow_Closed;
|
||||
}
|
||||
|
||||
@@ -48,13 +58,12 @@ public partial class MainWindow : Window
|
||||
{
|
||||
var vm = _vm.SFMLRendererViewModel;
|
||||
_renderPanel.CanvasMouseWheelScrolled += vm.CanvasMouseWheelScrolled;
|
||||
_renderPanel.CanvasMouseButtonPressed += vm.CanvasMouseButtonPressed;
|
||||
_renderPanel.CanvasMouseButtonPressed += (s, e) => { vm.CanvasMouseButtonPressed(s, e); _spinesListView.Focus(); }; // 用户点击画布后强制转移焦点至列表
|
||||
_renderPanel.CanvasMouseMove += vm.CanvasMouseMove;
|
||||
_renderPanel.CanvasMouseButtonReleased += vm.CanvasMouseButtonReleased;
|
||||
|
||||
// 设置默认参数并启动渲染
|
||||
vm.ResolutionX = 1500;
|
||||
vm.ResolutionY = 1000;
|
||||
vm.SetResolution(1500, 1000);
|
||||
vm.Zoom = 0.75f;
|
||||
vm.CenterX = 0;
|
||||
vm.CenterY = 0;
|
||||
@@ -64,14 +73,36 @@ public partial class MainWindow : Window
|
||||
|
||||
// 加载首选项
|
||||
_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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 给管道通信提供的打开文件外部调用方法
|
||||
/// </summary>
|
||||
public void OpenFiles(IEnumerable<string> filePaths)
|
||||
{
|
||||
_vm.SpineObjectListViewModel.AddSpineObjectFromFileList(filePaths);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化窗口日志器
|
||||
/// </summary>
|
||||
@@ -81,14 +112,13 @@ public partial class MainWindow : Window
|
||||
var rtbTarget = new NLog.Windows.Wpf.RichTextBoxTarget
|
||||
{
|
||||
Name = "rtbTarget",
|
||||
FormName = GetType().Name,
|
||||
WindowName = _mainWindow.Name,
|
||||
ControlName = _loggerRichTextBox.Name,
|
||||
AutoScroll = true,
|
||||
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("[I]", "DimGray", "Empty"));
|
||||
rtbTarget.WordColoringRules.Add(new("[W]", "DarkOrange", "Empty"));
|
||||
@@ -100,6 +130,66 @@ public partial class MainWindow : Window
|
||||
LogManager.ReconfigExistingLoggers();
|
||||
}
|
||||
|
||||
private void LoadLastState()
|
||||
{
|
||||
if (JsonHelper.Deserialize<LastStateModel>(LastStateFilePath, out var m, true))
|
||||
{
|
||||
Left = m.WindowLeft;
|
||||
Top = m.WindowTop;
|
||||
Width = m.WindowWidth;
|
||||
Height = m.WindowHeight;
|
||||
if (m.WindowState == WindowState.Maximized)
|
||||
{
|
||||
WindowState = WindowState.Maximized;
|
||||
}
|
||||
else
|
||||
{
|
||||
WindowState = WindowState.Normal;
|
||||
}
|
||||
|
||||
_rootGrid.ColumnDefinitions[0].Width = new(m.RootGridCol0Width);
|
||||
_modelListGrid.RowDefinitions[0].Height = new(m.ModelListRow0Height);
|
||||
if (m.ExplorerGridRow0Height > 0) _explorerGrid.RowDefinitions[0].Height = new(m.ExplorerGridRow0Height);
|
||||
_rightPanelGrid.RowDefinitions[0].Height = new(m.RightPanelGridRow0Height);
|
||||
|
||||
_vm.SFMLRendererViewModel.SetResolution(m.ResolutionX, m.ResolutionY);
|
||||
_vm.SFMLRendererViewModel.MaxFps = m.MaxFps;
|
||||
_vm.SFMLRendererViewModel.Speed = m.Speed;
|
||||
_vm.SFMLRendererViewModel.ShowAxis = m.ShowAxis;
|
||||
_vm.SFMLRendererViewModel.BackgroundColor = m.BackgroundColor;
|
||||
_vm.SFMLRendererViewModel.BackgroundImagePath = m.BackgroundImagePath;
|
||||
_vm.SFMLRendererViewModel.BackgroundImageMode = m.BackgroundImageMode;
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveLastState()
|
||||
{
|
||||
var m = new LastStateModel()
|
||||
{
|
||||
WindowLeft = Left,
|
||||
WindowTop = Top,
|
||||
WindowWidth = Width,
|
||||
WindowHeight = Height,
|
||||
WindowState = WindowState,
|
||||
|
||||
RootGridCol0Width = _rootGrid.ColumnDefinitions[0].ActualWidth,
|
||||
ModelListRow0Height = _modelListGrid.RowDefinitions[0].ActualHeight,
|
||||
ExplorerGridRow0Height = _explorerGrid.RowDefinitions[0].ActualHeight,
|
||||
RightPanelGridRow0Height = _rightPanelGrid.RowDefinitions[0].ActualHeight,
|
||||
|
||||
ResolutionX = _vm.SFMLRendererViewModel.ResolutionX,
|
||||
ResolutionY = _vm.SFMLRendererViewModel.ResolutionY,
|
||||
MaxFps = _vm.SFMLRendererViewModel.MaxFps,
|
||||
Speed = _vm.SFMLRendererViewModel.Speed,
|
||||
ShowAxis = _vm.SFMLRendererViewModel.ShowAxis,
|
||||
BackgroundColor = _vm.SFMLRendererViewModel.BackgroundColor,
|
||||
BackgroundImagePath = _vm.SFMLRendererViewModel.BackgroundImagePath,
|
||||
BackgroundImageMode = _vm.SFMLRendererViewModel.BackgroundImageMode,
|
||||
};
|
||||
|
||||
JsonHelper.Serialize(m, LastStateFilePath);
|
||||
}
|
||||
|
||||
#region _spinesListView 事件处理
|
||||
|
||||
private void SpinesListView_RequestSelectionChanging(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
@@ -125,6 +215,9 @@ public partial class MainWindow : Window
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 如果选中项发生变化也强制转移焦点
|
||||
_spinesListView.Focus();
|
||||
}
|
||||
|
||||
private void SpinesListView_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
@@ -225,6 +318,31 @@ public partial class MainWindow : Window
|
||||
|
||||
#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 切换全屏布局事件处理
|
||||
|
||||
private void SwitchToFullScreenLayout()
|
||||
@@ -234,11 +352,9 @@ public partial class MainWindow : Window
|
||||
if (_fullScreenLayout.Visibility == Visibility.Visible) return;
|
||||
|
||||
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))
|
||||
{
|
||||
var vm = _vm.SFMLRendererViewModel;
|
||||
vm.ResolutionX = resX;
|
||||
vm.ResolutionY = resY;
|
||||
_vm.SFMLRendererViewModel.SetResolution(resX, resY);
|
||||
}
|
||||
|
||||
HandyControl.Controls.IconElement.SetGeometry(_fullScreenButton, AppResource.Geo_ArrowsMinimize);
|
||||
@@ -484,11 +600,4 @@ public partial class MainWindow : Window
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void SpineFilesListBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var list = (ListBox)sender;
|
||||
if (VisualUpwardSearch<ListBoxItem>(e.OriginalSource as DependencyObject) is null)
|
||||
list.SelectedItems.Clear();
|
||||
}
|
||||
}
|
||||
@@ -143,13 +143,17 @@
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_RenderSelectedOnly}"/>
|
||||
<ToggleButton Grid.Row="0" Grid.Column="1" IsChecked="{Binding RenderSelectedOnly}"/>
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_Language}"/>
|
||||
<ComboBox Grid.Row="1" Grid.Column="1"
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_AssociateFileSuffix}"/>
|
||||
<ToggleButton Grid.Row="1" Grid.Column="1" IsChecked="{Binding AssociateFileSuffix}"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_Language}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="1"
|
||||
SelectedItem="{Binding AppLanguage}"
|
||||
ItemsSource="{x:Static vm:PreferenceViewModel.AppLanguageOptions}"/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user