refactor: migrate project and start refactor

This commit is contained in:
2025-11-11 23:03:47 +08:00
parent df68c79f28
commit fc39d16738
60 changed files with 968 additions and 212 deletions

View File

@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29418.71
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BallanceTASEditor", "BallanceTASEditor\BallanceTASEditor.csproj", "{3127A635-B9E5-4C78-8414-0B9B196EC25E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BallanceTASEditorTests", "BallanceTASEditorTests\BallanceTASEditorTests.csproj", "{EC35111D-6259-4C69-B7E4-F4E727AC0E1A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3127A635-B9E5-4C78-8414-0B9B196EC25E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3127A635-B9E5-4C78-8414-0B9B196EC25E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3127A635-B9E5-4C78-8414-0B9B196EC25E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3127A635-B9E5-4C78-8414-0B9B196EC25E}.Release|Any CPU.Build.0 = Release|Any CPU
{EC35111D-6259-4C69-B7E4-F4E727AC0E1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EC35111D-6259-4C69-B7E4-F4E727AC0E1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EC35111D-6259-4C69-B7E4-F4E727AC0E1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EC35111D-6259-4C69-B7E4-F4E727AC0E1A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {ABECA811-4FBB-4394-805E-EEBC799ECC89}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,14 @@
<Application x:Class="BallanceTASEditor.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BallanceTASEditor"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Language/DefaultLanguage.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
namespace BallanceTASEditor {
/// <summary>
/// App.xaml 的交互逻辑
/// </summary>
public partial class App : Application {
protected override void OnStartup(StartupEventArgs e) {
base.OnStartup(e);
#if DEBUG
#else
AppDomain.CurrentDomain.UnhandledException += (sender, ex) => {
if (ex.ExceptionObject is System.Exception) {
var exx = (System.Exception)ex.ExceptionObject;
UncatchedErrorHandle(exx.Message, exx.StackTrace);
}
};
#endif
// init configure manager
GlobalVariable.configManager = new Core.ConfigManager("ballance-tas-editor.cfg", new Dictionary<string, string>() {
{Core.ConfigManager.CfgNode_Language, CultureInfo.CurrentCulture.ThreeLetterWindowsLanguageName},
{Core.ConfigManager.CfgNode_ItemCount, "15"},
{Core.ConfigManager.CfgNode_IsHorizonLayout, "True"},
{Core.ConfigManager.CfgNode_IsOverwrittenPaste, "False"}
});
// init i18n
Core.I18NProcessor.ChangeLanguage(GlobalVariable.configManager.Configuration[Core.ConfigManager.CfgNode_Language]);
}
protected override void OnExit(ExitEventArgs e) {
base.OnExit(e);
GlobalVariable.configManager.Save();
}
private void UncatchedErrorHandle(string message, string stackTrace) {
try {
if (!Directory.Exists("./logs"))
Directory.CreateDirectory("./logs");
int counter = 1;
var filename = "";
var datetime = DateTime.Now;
while (true) {
filename = $"./logs/crash-{datetime.ToString("yyyyMMddHHmmss")}-{counter.ToString().PadLeft(2, '0')}.log";
if (!File.Exists(filename)) break;
}
var fs = new StreamWriter(filename, false, Encoding.UTF8);
fs.WriteLine("[SYS][ERROR] FATAL ERROR !");
fs.WriteLine(message);
fs.WriteLine(stackTrace);
fs.Close();
fs.Dispose();
} catch {
;//skip
}
MessageBox.Show("A fatal error occurs. The application should exit. Please send the error log, reproduce step and corresponding TAS file(if possible) to developer to help fixing this problem.",
"Ballance TAS Editor",
MessageBoxButton.OK, MessageBoxImage.Error
);
App.Current.Shutdown();
}
}
}

View File

@ -0,0 +1,145 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{3127A635-B9E5-4C78-8414-0B9B196EC25E}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>BallanceTASEditor</RootNamespace>
<AssemblyName>BallanceTASEditor</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>icon.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="Ionic.Zip, Version=1.9.1.8, Culture=neutral, PublicKeyToken=edbe51ad942a3f5c, processorArchitecture=MSIL">
<HintPath>..\packages\DotNetZip.1.9.1.8\lib\net20\Ionic.Zip.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualBasic" />
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="Core\ClipboardUtil.cs" />
<Compile Include="Core\ConfigManager.cs" />
<Compile Include="Core\FileOperation.cs" />
<Compile Include="Core\I18NProcessor.cs" />
<Compile Include="Core\KeyboardState.cs" />
<Compile Include="Core\LimitedStack.cs" />
<Compile Include="GlobalVariable.cs" />
<Compile Include="UI\AddItem.xaml.cs">
<DependentUpon>AddItem.xaml</DependentUpon>
</Compile>
<Compile Include="UI\OperationEnum.cs" />
<Compile Include="UI\SelectionHelp.cs" />
<Compile Include="UI\StyleConverter.cs" />
<Compile Include="UI\TASFlow.cs" />
<Compile Include="UI\TASSlider.cs" />
<Compile Include="UI\TASViewer.cs" />
<Compile Include="UI\Util.cs" />
<Page Include="Language\CHS.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Language\DefaultLanguage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="Core\TASFile.cs" />
<Compile Include="Core\TASStruct.cs" />
<Compile Include="Core\Util.cs" />
<Compile Include="Core\ZlibUtil.cs" />
<Compile Include="UI\DialogUtil.cs" />
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Page Include="UI\AddItem.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="app.config" />
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<Resource Include="icon.ico" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -0,0 +1,71 @@
using BallanceTASEditor.Core.TASStruct;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
namespace BallanceTASEditor.Core {
public class ClipboardUtil {
// comes from https://stackoverflow.com/questions/22272822/copy-binary-data-to-clipboard
private static readonly string CLIPBOARD_DATA_FORMAT = "BallanceTASFrameData";
public static bool SetFrameData(LinkedList<FrameData> ls) {
try {
DataObject data = new DataObject();
using (var mem = new MemoryStream()) {
mem.Write(BitConverter.GetBytes(ls.Count), 0, 4);
var node = ls.First;
while (node != null) {
mem.Write(BitConverter.GetBytes(node.Value.deltaTime), 0, 4);
mem.Write(BitConverter.GetBytes(node.Value.keystates), 0, 4);
node = node.Next;
}
data.SetData(CLIPBOARD_DATA_FORMAT, mem, false);
Clipboard.SetDataObject(data, true);
}
return true;
#if DEBUG
} catch (Exception e) {
#else
} catch {
#endif
return false;
}
}
public static bool GetFrameData(LinkedList<FrameData> ls) {
try {
// detect
DataObject retrievedData = Clipboard.GetDataObject() as DataObject;
if (retrievedData == null || !retrievedData.GetDataPresent(CLIPBOARD_DATA_FORMAT))
return false;
MemoryStream byteStream = retrievedData.GetData(CLIPBOARD_DATA_FORMAT) as MemoryStream;
if (byteStream == null)
return false;
// read
byteStream.Seek(0, SeekOrigin.Begin);
byte[] temp = new byte[8];
byteStream.Read(temp, 0, 4);
int count = BitConverter.ToInt32(temp, 0);
for (int i = 0; i < count; i++) {
ls.AddLast(new FrameData(byteStream));
}
return true;
#if DEBUG
} catch (Exception e) {
#else
} catch {
#endif
return false;
}
}
}
}

View File

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Newtonsoft.Json;
namespace BallanceTASEditor.Core {
public class ConfigManager {
public ConfigManager(string fileName, Dictionary<string, string> defaultValue) {
_fileName = fileName;
_defaultValue = defaultValue;
Configuration = Read();
}
string _fileName;
Dictionary<string, string> _defaultValue;
public Dictionary<string, string> Configuration;
public static readonly string CfgNode_Language = "Language";
public static readonly string CfgNode_ItemCount = "ItemCount";
public static readonly string CfgNode_IsHorizonLayout = "IsHorizonLayout";
public static readonly string CfgNode_IsOverwrittenPaste = "IsOverwrittenPaste";
Dictionary<string, string> Read() {
if (!File.Exists(Path.Combine(Environment.CurrentDirectory, _fileName)))
Init();
Dictionary<string, string> data;
using (StreamReader fs = new StreamReader(Path.Combine(Environment.CurrentDirectory, _fileName), Encoding.UTF8)) {
data = JsonConvert.DeserializeObject<Dictionary<string, string>>(fs.ReadToEnd());
fs.Close();
}
// check field to make sure each field is existed
// because version update it might be changed
foreach(var pair in _defaultValue) {
if (!data.ContainsKey(pair.Key)) {
data.Add(pair.Key, pair.Value);
}
}
return data;
}
void Init() {
using (StreamWriter fs = new StreamWriter(Path.Combine(Environment.CurrentDirectory, _fileName), false, Encoding.UTF8)) {
fs.Write(JsonConvert.SerializeObject(_defaultValue));
fs.Close();
}
}
public void Save() {
using (StreamWriter fs = new StreamWriter(Path.Combine(Environment.CurrentDirectory, _fileName), false, Encoding.UTF8)) {
fs.Write(JsonConvert.SerializeObject(this.Configuration));
fs.Close();
}
}
}
}

View File

@ -0,0 +1,437 @@
using BallanceTASEditor.Core.TASStruct;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BallanceTASEditor.Core.FileOperation {
public abstract class RevocableOperation {
public RevocableOperation() {
hasBeenDone = false;
}
private bool hasBeenDone;
public virtual void Do(ref LinkedList<FrameData> mMem, ref LinkedListNode<FrameData> mPointer, ref long mPointerIndex) {
if (hasBeenDone) throw new Exception("Try to call operation.do when the operation has been done.");
hasBeenDone = true;
}
public virtual void Undo(ref LinkedList<FrameData> mMem, ref LinkedListNode<FrameData> mPointer, ref long mPointerIndex) {
if (!hasBeenDone) throw new Exception("Try to call operation.undo when the operation has not been done.");
hasBeenDone = false;
}
}
public class SetOperation : RevocableOperation {
private SelectionRange field;
private SelectionRange absoluteRange;
private bool? isSet;
private uint internalOffset;
private List<uint> changedItems;
public SetOperation(SelectionRange _field, SelectionRange _absoluteRange, bool? _isSet) : base() {
field = _field;
absoluteRange = _absoluteRange;
isSet = _isSet;
// calc offset first for following operation
internalOffset = 0;
for (int i = (int)field.start; i <= (int)field.end; i++) {
internalOffset |= ConstValue.Mapping[(FrameDataField)i];
}
changedItems = new List<uint>();
}
public override void Do(ref LinkedList<FrameData> mMem, ref LinkedListNode<FrameData> mPointer, ref long mPointerIndex) {
base.Do(ref mMem, ref mPointer, ref mPointerIndex);
if (mPointer == null) return;
changedItems.Clear();
foreach (var item in mMem.IterateWithSelectionRange(absoluteRange, mPointer, mPointerIndex)) {
// backup item first
changedItems.Add(item.Value.keystates);
if (isSet == null) item.Value.ReverseKeyStates(internalOffset);
else if (isSet == true) item.Value.SetKeyStates(internalOffset);
else if (isSet == false) item.Value.UnsetKeyStates(internalOffset);
}
}
public override void Undo(ref LinkedList<FrameData> mMem, ref LinkedListNode<FrameData> mPointer, ref long mPointerIndex) {
base.Undo(ref mMem, ref mPointer, ref mPointerIndex);
if (mPointer == null) return;
int counter = 0;
foreach (var item in mMem.IterateWithSelectionRange(absoluteRange, mPointer, mPointerIndex)) {
// restore data
item.Value.keystates = changedItems[counter];
counter++;
}
}
}
public class RemoveOperation : RevocableOperation {
private SelectionRange absoluteRange;
private LinkedList<FrameData> removedItems;
private LinkedListNode<FrameData> oldPointer;
private long oldPointerIndex;
private LinkedListNode<FrameData> removeStartNode;
public RemoveOperation(SelectionRange _absoluteRange) : base() {
absoluteRange = _absoluteRange;
removedItems = new LinkedList<FrameData>();
}
public override void Do(ref LinkedList<FrameData> mMem, ref LinkedListNode<FrameData> mPointer, ref long mPointerIndex) {
base.Do(ref mMem, ref mPointer, ref mPointerIndex);
if (mPointer == null) return;
// init backups list and backups 2 data
// and backups remove start node(ps: if it is null, mean removed from head)
removedItems.Clear();
oldPointer = mPointer;
oldPointerIndex = mPointerIndex;
removeStartNode = absoluteRange.start == 0 ? null : mMem.FastGetNode(mPointer, mPointerIndex, absoluteRange.start - 1);
// find proper pointer after remove first. but we do not apply it in there.
// if state is true, it mean the deleted content is placed before pointer previously. we should consider pointer data and we should correct them.
LinkedListNode<FrameData> newPointer;
long newPointerIndex;
if (mPointerIndex >= absoluteRange.start) {
// if point within removed content, we need to shift it to the head of removed content,
// otherwise we only need to minus index with the length of removed content.
if (absoluteRange.Within(mPointerIndex)) {
// this contains 3 situation
// if full delete, mPointer is null and mPointerIndex is invalid(with wrong data: -1)
// if delete from head, mPointer and mPointerIndex all are valid. but it is the tail of removed content
// otherwise, just find the head of removed content and shift to it.
if (absoluteRange.start == 0 && absoluteRange.end == mMem.Count - 1) {
// fully remove
newPointer = null;
newPointerIndex = -1;
} else if (absoluteRange.start == 0) {
// remove from head
newPointerIndex = 0;
newPointer = mMem.FastGetNode(mPointer, mPointerIndex, absoluteRange.end + 1);
} else {
// simple remove
newPointerIndex = absoluteRange.start - 1;
newPointer = mMem.FastGetNode(mPointer, mPointerIndex, absoluteRange.start - 1);
}
} else {
newPointer = mPointer;
newPointerIndex = mPointerIndex - absoluteRange.GetCount();
}
} else {
// not affected situation
newPointer = mPointer;
newPointerIndex = mPointerIndex;
}
// the real remove operation
foreach (var item in mMem.IterateWithSelectionRange(absoluteRange, mPointer, mPointerIndex)) {
mMem.Remove(item);
removedItems.AddLast(item); // backups node;
}
// apply gotten new pointer
mPointer = newPointer;
mPointerIndex = newPointerIndex;
}
public override void Undo(ref LinkedList<FrameData> mMem, ref LinkedListNode<FrameData> mPointer, ref long mPointerIndex) {
base.Undo(ref mMem, ref mPointer, ref mPointerIndex);
// may recovered from empty list
//if (mPointer == null) return;
// re-insert data
foreach (var item in removedItems.IterateFullReversed()) {
removedItems.Remove(item);
if (removeStartNode == null) {
// insert at first
mMem.AddFirst(item);
} else {
// insert after this node
mMem.AddAfter(removeStartNode, item);
}
}
// reset pointer
mPointer = oldPointer;
mPointerIndex = oldPointerIndex;
}
}
public class AddOperation : RevocableOperation {
private long absolutePos;
private long count;
private float deltaTime;
private bool isAddBefore;
private LinkedListNode<FrameData> addStartNode;
private LinkedListNode<FrameData> oldPointer;
private long oldPointerIndex;
public AddOperation(long _absolutePos, long _count, float _deltaTime, bool _isAddBefore) : base() {
absolutePos = _absolutePos;
count = _count;
deltaTime = _deltaTime;
isAddBefore = _isAddBefore;
}
public override void Do(ref LinkedList<FrameData> mMem, ref LinkedListNode<FrameData> mPointer, ref long mPointerIndex) {
base.Do(ref mMem, ref mPointer, ref mPointerIndex);
if (count <= 0) return;
// backups 2 data
oldPointer = mPointer;
oldPointerIndex = mPointerIndex;
// real add operation
if (mPointer == null) {
// backups start pointer
addStartNode = null;
// add into blank list, absolutePos and isAddBefore parameters are invalid
// specially process
for (long i = 0; i < count; i++) {
mMem.AddFirst(new FrameData(deltaTime, 0));
}
mPointer = mMem.First;
mPointerIndex = 0;
} else {
// normal add
LinkedListNode<FrameData> node = mMem.FastGetNode(mPointer, mPointerIndex, absolutePos);
// backups start pointer
addStartNode = node;
if (isAddBefore) {
for (long i = 0; i < count; i++) {
mMem.AddBefore(node, new FrameData(deltaTime, 0));
}
} else {
for (long i = 0; i < count; i++) {
mMem.AddAfter(node, new FrameData(deltaTime, 0));
}
}
// if the items are added before pointer, the index should add with the count of added items
// but pointer don't need to be shifted.
if ((isAddBefore && mPointerIndex >= absolutePos) ||
(!isAddBefore && mPointerIndex > absolutePos))
mPointerIndex += count;
}
}
public override void Undo(ref LinkedList<FrameData> mMem, ref LinkedListNode<FrameData> mPointer, ref long mPointerIndex) {
base.Undo(ref mMem, ref mPointer, ref mPointerIndex);
if (count <= 0) return;
if (addStartNode == null) {
// original state is blank
// just clear mmem is ok
mMem.Clear();
} else {
if (isAddBefore) {
for (long i = 0; i < count; i++) {
mMem.Remove(addStartNode.Previous);
}
} else {
for (long i = 0; i < count; i++) {
mMem.Remove(addStartNode.Next);
}
}
}
// re-set pointer
mPointer = oldPointer;
mPointerIndex = oldPointerIndex;
}
}
public class InsertOperation : RevocableOperation {
private long absolutePos;
private LinkedList<FrameData> data;
private bool isInsertBefore;
private bool isOverwritten;
private LinkedListNode<FrameData> addStartNode;
private bool isBlankList;
private LinkedListNode<FrameData> oldPointer;
private long oldPointerIndex;
// because insert including remove oeration(overwritten mode)
// so we need include this for code re-use
private RemoveOperation internalRemoveOper;
private const long LINKEDLIST_HEAD = -1;
private const long LINKEDLIST_TAIL = -2;
public InsertOperation(long _absolutePos, LinkedList<FrameData> _data, bool _isInsertBefore, bool _isOverwritten) : base() {
absolutePos = _absolutePos;
data = _data;
isInsertBefore = _isInsertBefore;
isOverwritten = _isOverwritten;
}
public override void Do(ref LinkedList<FrameData> mMem, ref LinkedListNode<FrameData> mPointer, ref long mPointerIndex) {
base.Do(ref mMem, ref mPointer, ref mPointerIndex);
if (data.Count == 0) return;
// because this oper have internal oper, so we need backup data after potential remove oper
// so in there, no object need to be backuped
// if the list is blank, overwritten is invalid, just normal add them.
if (mPointer == null) {
// backups
oldPointer = mPointer;
oldPointerIndex = mPointerIndex;
addStartNode = null;
isBlankList = true;
foreach (var item in data.IterateFull()) {
mMem.AddFirst(item.Value);
}
mPointer = mMem.First;
mPointerIndex = 0;
} else {
LinkedListNode<FrameData> node = mMem.FastGetNode(mPointer, mPointerIndex, absolutePos);
// absolutePos is class member and shouldn't be changed.
// but in overwritten mode, this value need to be changed so we create a temp value in there
// to instead the fucntion of original variable
var modifiedAbsolutePos = absolutePos;
// if list is not a blank list, we should consider overwritten
// if in overwritten mode, we need to overwrite data from selected item.
// otherwise, not in overwritten mode, just normally add them just like add operation.
if (isOverwritten) {
// in overwritten mode, if follwoing item is not enough to fufill the count of overwritten data
// normally add them
// we use delete and add method to do this
// now, try init internal remove oper if in overwritten mode
// first, we need compare the length of remained item located in mMem and the length of added item
// then construct remove oper
long remainLength;
if (isInsertBefore) remainLength = absolutePos + 1;
else remainLength = mMem.Count - absolutePos;
long dataLength = data.Count;
long expectedLength = dataLength > remainLength ? remainLength : dataLength;
long expectedPos;
if (isInsertBefore) expectedPos = absolutePos - expectedLength + 1;
else expectedPos = absolutePos + expectedLength - 1;
if (isInsertBefore)
internalRemoveOper = new RemoveOperation(new SelectionRange(expectedPos, absolutePos));
else
internalRemoveOper = new RemoveOperation(new SelectionRange(absolutePos, expectedPos));
node = isInsertBefore ? node.Next : node.Previous;
internalRemoveOper.Do(ref mMem, ref mPointer, ref mPointerIndex);
// now, we can treat it as normal insert(without overwritten)
// but with one exception: absolutePos
// we need re calc absolutePos bucause we have called remove oper
if (isInsertBefore) {
if (node == null)
modifiedAbsolutePos = LINKEDLIST_TAIL;
else
modifiedAbsolutePos = absolutePos + 1 - expectedLength;
} else {
if (node == null)
modifiedAbsolutePos = LINKEDLIST_HEAD;
else
modifiedAbsolutePos -= 1;
}
}
// backups
oldPointer = mPointer;
oldPointerIndex = mPointerIndex;
addStartNode = node;
isBlankList = false;
if (isInsertBefore) {
foreach (var item in data.IterateFull()) {
if (node == null)
mMem.AddLast(item.Value);
else
mMem.AddBefore(node, item.Value);
}
} else {
foreach (var item in data.IterateFullReversed()) {
if (node == null)
mMem.AddFirst(item.Value);
else
mMem.AddAfter(node, item.Value);
}
}
if (modifiedAbsolutePos != LINKEDLIST_TAIL && modifiedAbsolutePos != LINKEDLIST_HEAD) {
if ((isInsertBefore && mPointerIndex >= modifiedAbsolutePos) ||
(!isInsertBefore && mPointerIndex > modifiedAbsolutePos))
mPointerIndex += data.Count;
}
else if (modifiedAbsolutePos == LINKEDLIST_HEAD)
mPointerIndex += data.Count;
// remove have chance to remove entire list
// so we need restore pointer in that situation
if (mPointer == null) {
mPointer = mMem.First;
mPointerIndex = mPointer == null ? -1 : 0;
}
}
}
public override void Undo(ref LinkedList<FrameData> mMem, ref LinkedListNode<FrameData> mPointer, ref long mPointerIndex) {
base.Undo(ref mMem, ref mPointer, ref mPointerIndex);
if (data.Count == 0) return;
if (isBlankList) {
// original state is blank
// just clear mmem is ok
mMem.Clear();
// re-set pointer
mPointer = oldPointer;
mPointerIndex = oldPointerIndex;
} else {
// in overwrite or not in overwrite mode, we all need to remove inserted data first
if (isInsertBefore) {
for (long i = 0; i < data.Count; i++) {
if (addStartNode == null)
mMem.RemoveLast();
else
mMem.Remove(addStartNode.Previous);
}
} else {
for (long i = 0; i < data.Count; i++) {
if (addStartNode == null)
mMem.RemoveFirst();
else
mMem.Remove(addStartNode.Next);
}
}
// re-set pointer
mPointer = oldPointer;
mPointerIndex = oldPointerIndex;
// if we use overwrite mode, we need re-add lost data
if (isOverwritten) {
internalRemoveOper.Undo(ref mMem, ref mPointer, ref mPointerIndex);
}
}
}
}
}

View File

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
namespace BallanceTASEditor.Core {
public static class I18NProcessor {
public static string GetI18N(string key, params string[] parameters) {
try {
var cache = (string)(App.Current.Resources[key]);
return string.Format(cache, parameters);
} catch (Exception) {
return "";
}
}
public static void ChangeLanguage(string target) {
ResourceDictionary langRd = null;
try {
langRd =
Application.LoadComponent(
new Uri(@"Language/" + target + ".xaml", UriKind.Relative))
as ResourceDictionary;
} catch {
;
}
if (langRd != null) {
if (App.Current.Resources.MergedDictionaries.Count > 0) {
App.Current.Resources.MergedDictionaries.Clear();
}
App.Current.Resources.MergedDictionaries.Add(langRd);
}
}
}
}

View File

@ -0,0 +1,223 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace BallanceTASEditor.Core {
public class KeyboardState {
[DllImport("user32.dll")]
static extern short GetKeyState(VirtualKeyStates nVirtKey);
public static bool IsKeyPressed(VirtualKeyStates testKey) {
bool keyPressed = false;
short result = GetKeyState(testKey);
switch (result) {
case 0:
// Not pressed and not toggled on.
keyPressed = false;
break;
case 1:
// Not pressed, but toggled on
keyPressed = false;
break;
default:
// Pressed (and may be toggled on)
keyPressed = true;
break;
}
return keyPressed;
}
public enum VirtualKeyStates : int {
VK_LBUTTON = 0x01,
VK_RBUTTON = 0x02,
VK_CANCEL = 0x03,
VK_MBUTTON = 0x04,
//
VK_XBUTTON1 = 0x05,
VK_XBUTTON2 = 0x06,
//
VK_BACK = 0x08,
VK_TAB = 0x09,
//
VK_CLEAR = 0x0C,
VK_RETURN = 0x0D,
//
VK_SHIFT = 0x10,
VK_CONTROL = 0x11,
VK_MENU = 0x12,
VK_PAUSE = 0x13,
VK_CAPITAL = 0x14,
//
VK_KANA = 0x15,
VK_HANGEUL = 0x15, /* old name - should be here for compatibility */
VK_HANGUL = 0x15,
VK_JUNJA = 0x17,
VK_FINAL = 0x18,
VK_HANJA = 0x19,
VK_KANJI = 0x19,
//
VK_ESCAPE = 0x1B,
//
VK_CONVERT = 0x1C,
VK_NONCONVERT = 0x1D,
VK_ACCEPT = 0x1E,
VK_MODECHANGE = 0x1F,
//
VK_SPACE = 0x20,
VK_PRIOR = 0x21,
VK_NEXT = 0x22,
VK_END = 0x23,
VK_HOME = 0x24,
VK_LEFT = 0x25,
VK_UP = 0x26,
VK_RIGHT = 0x27,
VK_DOWN = 0x28,
VK_SELECT = 0x29,
VK_PRINT = 0x2A,
VK_EXECUTE = 0x2B,
VK_SNAPSHOT = 0x2C,
VK_INSERT = 0x2D,
VK_DELETE = 0x2E,
VK_HELP = 0x2F,
//
VK_LWIN = 0x5B,
VK_RWIN = 0x5C,
VK_APPS = 0x5D,
//
VK_SLEEP = 0x5F,
//
VK_NUMPAD0 = 0x60,
VK_NUMPAD1 = 0x61,
VK_NUMPAD2 = 0x62,
VK_NUMPAD3 = 0x63,
VK_NUMPAD4 = 0x64,
VK_NUMPAD5 = 0x65,
VK_NUMPAD6 = 0x66,
VK_NUMPAD7 = 0x67,
VK_NUMPAD8 = 0x68,
VK_NUMPAD9 = 0x69,
VK_MULTIPLY = 0x6A,
VK_ADD = 0x6B,
VK_SEPARATOR = 0x6C,
VK_SUBTRACT = 0x6D,
VK_DECIMAL = 0x6E,
VK_DIVIDE = 0x6F,
VK_F1 = 0x70,
VK_F2 = 0x71,
VK_F3 = 0x72,
VK_F4 = 0x73,
VK_F5 = 0x74,
VK_F6 = 0x75,
VK_F7 = 0x76,
VK_F8 = 0x77,
VK_F9 = 0x78,
VK_F10 = 0x79,
VK_F11 = 0x7A,
VK_F12 = 0x7B,
VK_F13 = 0x7C,
VK_F14 = 0x7D,
VK_F15 = 0x7E,
VK_F16 = 0x7F,
VK_F17 = 0x80,
VK_F18 = 0x81,
VK_F19 = 0x82,
VK_F20 = 0x83,
VK_F21 = 0x84,
VK_F22 = 0x85,
VK_F23 = 0x86,
VK_F24 = 0x87,
//
VK_NUMLOCK = 0x90,
VK_SCROLL = 0x91,
//
VK_OEM_NEC_EQUAL = 0x92, // '=' key on numpad
//
VK_OEM_FJ_JISHO = 0x92, // 'Dictionary' key
VK_OEM_FJ_MASSHOU = 0x93, // 'Unregister word' key
VK_OEM_FJ_TOUROKU = 0x94, // 'Register word' key
VK_OEM_FJ_LOYA = 0x95, // 'Left OYAYUBI' key
VK_OEM_FJ_ROYA = 0x96, // 'Right OYAYUBI' key
//
VK_LSHIFT = 0xA0,
VK_RSHIFT = 0xA1,
VK_LCONTROL = 0xA2,
VK_RCONTROL = 0xA3,
VK_LMENU = 0xA4,
VK_RMENU = 0xA5,
//
VK_BROWSER_BACK = 0xA6,
VK_BROWSER_FORWARD = 0xA7,
VK_BROWSER_REFRESH = 0xA8,
VK_BROWSER_STOP = 0xA9,
VK_BROWSER_SEARCH = 0xAA,
VK_BROWSER_FAVORITES = 0xAB,
VK_BROWSER_HOME = 0xAC,
//
VK_VOLUME_MUTE = 0xAD,
VK_VOLUME_DOWN = 0xAE,
VK_VOLUME_UP = 0xAF,
VK_MEDIA_NEXT_TRACK = 0xB0,
VK_MEDIA_PREV_TRACK = 0xB1,
VK_MEDIA_STOP = 0xB2,
VK_MEDIA_PLAY_PAUSE = 0xB3,
VK_LAUNCH_MAIL = 0xB4,
VK_LAUNCH_MEDIA_SELECT = 0xB5,
VK_LAUNCH_APP1 = 0xB6,
VK_LAUNCH_APP2 = 0xB7,
//
VK_OEM_1 = 0xBA, // ';:' for US
VK_OEM_PLUS = 0xBB, // '+' any country
VK_OEM_COMMA = 0xBC, // ',' any country
VK_OEM_MINUS = 0xBD, // '-' any country
VK_OEM_PERIOD = 0xBE, // '.' any country
VK_OEM_2 = 0xBF, // '/?' for US
VK_OEM_3 = 0xC0, // '`~' for US
//
VK_OEM_4 = 0xDB, // '[{' for US
VK_OEM_5 = 0xDC, // '\|' for US
VK_OEM_6 = 0xDD, // ']}' for US
VK_OEM_7 = 0xDE, // ''"' for US
VK_OEM_8 = 0xDF,
//
VK_OEM_AX = 0xE1, // 'AX' key on Japanese AX kbd
VK_OEM_102 = 0xE2, // "<>" or "\|" on RT 102-key kbd.
VK_ICO_HELP = 0xE3, // Help key on ICO
VK_ICO_00 = 0xE4, // 00 key on ICO
//
VK_PROCESSKEY = 0xE5,
//
VK_ICO_CLEAR = 0xE6,
//
VK_PACKET = 0xE7,
//
VK_OEM_RESET = 0xE9,
VK_OEM_JUMP = 0xEA,
VK_OEM_PA1 = 0xEB,
VK_OEM_PA2 = 0xEC,
VK_OEM_PA3 = 0xED,
VK_OEM_WSCTRL = 0xEE,
VK_OEM_CUSEL = 0xEF,
VK_OEM_ATTN = 0xF0,
VK_OEM_FINISH = 0xF1,
VK_OEM_COPY = 0xF2,
VK_OEM_AUTO = 0xF3,
VK_OEM_ENLW = 0xF4,
VK_OEM_BACKTAB = 0xF5,
//
VK_ATTN = 0xF6,
VK_CRSEL = 0xF7,
VK_EXSEL = 0xF8,
VK_EREOF = 0xF9,
VK_PLAY = 0xFA,
VK_ZOOM = 0xFB,
VK_NONAME = 0xFC,
VK_PA1 = 0xFD,
VK_OEM_CLEAR = 0xFE
}
}
}

View File

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BallanceTASEditor.Core {
public class LimitedStack<T> {
private static readonly int STACK_LENGTH = 20;
public LimitedStack() {
_stack = new LinkedList<T>();
}
private LinkedList<T> _stack;
public void Push(T data) {
_stack.AddLast(data);
if (_stack.Count > STACK_LENGTH) {
_stack.RemoveFirst();
}
}
public T Pop() {
if (_stack.Last == null) return default(T);
var data = _stack.Last.Value;
_stack.RemoveLast();
return data;
}
public void Clear() {
_stack.Clear();
}
public bool IsEmpty() {
return _stack.Count == 0;
}
}
}

View File

@ -0,0 +1,256 @@
using BallanceTASEditor.Core.FileOperation;
using BallanceTASEditor.Core.TASStruct;
using BallanceTASEditor.UI;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
namespace BallanceTASEditor.Core {
public class TASFile {
public TASFile(string filename) {
mFilename = filename;
mMem = new LinkedList<FrameData>();
var fs = new FileStream(mFilename, FileMode.Open, FileAccess.Read, FileShare.Read);
ZlibUtil.DecompressTAS(mMem, fs);
fs.Close();
fs.Dispose();
mPointer = mMem.First;
mPointerIndex = mPointer == null ? -1 : 0;
mRedoStack = new LimitedStack<RevocableOperation>();
mUndoStack = new LimitedStack<RevocableOperation>();
}
public string mFilename { get; private set; }
public long mFrameCount { get { return mMem.Count; } }
LinkedList<FrameData> mMem;
LinkedListNode<FrameData> mPointer;
long mPointerIndex;
LimitedStack<RevocableOperation> mRedoStack;
LimitedStack<RevocableOperation> mUndoStack;
public bool IsEmpty() {
return (mPointer == null);
}
public long GetPointerIndex() {
// return invalid data to prevent error
if (mPointer == null) return -1;//throw new Exception("Data is not ready");
return mPointerIndex;
}
public void Shift(long absoluteIndex) {
if (mPointer == null) return;
mPointer = mMem.FastGetNode(mPointer, mPointerIndex, absoluteIndex);
mPointerIndex = absoluteIndex;
}
public void Get(List<FrameDataDisplay> container, int count) {
// no item. clean container
if (mPointer == null) {
for (int j = 0; j < count; j++) {
container[j].isEnable = false;
}
return;
}
// fill container
var cachePointer = mPointer;
var startIndex = mPointerIndex;
int i;
for (i = 0; i < count && cachePointer != null; i++, startIndex++) {
container[i].Reload(startIndex, cachePointer.Value);
container[i].isEnable = true;
cachePointer = cachePointer.Next;
}
for (; i < count; i++) {
container[i].isEnable = false;
}
}
// if isSet is null, mean flip state
public void Set(SelectionRange field, SelectionRange absoluteRange, bool? isSet) {
var oper = new SetOperation(field, absoluteRange, isSet);
oper.Do(ref mMem, ref mPointer, ref mPointerIndex);
mUndoStack.Push(oper);
mRedoStack.Clear();
/*
if (mPointer == null) return;
uint offset = 0;
for(int i = (int)field.start; i <= (int)field.end; i++) {
offset |= ConstValue.Mapping[(FrameDataField)i];
}
foreach(var item in mMem.IterateWithSelectionRange(absoluteRange, mPointer, mPointerIndex)) {
if (isSet == null) item.Value.ReverseKeyStates(offset);
else if (isSet == true) item.Value.SetKeyStates(offset);
else if (isSet == false) item.Value.UnsetKeyStates(offset);
}
*/
}
public void Remove(SelectionRange absoluteRange) {
var oper = new RemoveOperation(absoluteRange);
oper.Do(ref mMem, ref mPointer, ref mPointerIndex);
mUndoStack.Push(oper);
mRedoStack.Clear();
/*
if (mPointer == null) return;
// remove
foreach(var item in mMem.IterateWithSelectionRange(absoluteRange, mPointer, mPointerIndex)) {
mMem.Remove(item);
}
// correct index data
// if state is true, it mean the deleted content is placed before pointer previously.
// so we need shift the pointer to the head of selection range.
// and we should consider 2 situations, the full delete of LinkedList and delete from head
if (mPointerIndex >= absoluteRange.start) {
var newIndex = absoluteRange.start - 1;
if (newIndex < 0) {
// this contains 2 situation
// if full delete, mPointer is null and mPointerIndex is invalid(with wrong data: 0)
// if delete from head, mPointer and mPointerIndex all are valid.
mPointer = mMem.First;
mPointerIndex = 0;
} else {
mPointer = mMem.FastGetNode(mPointer, mPointerIndex, newIndex);
mPointerIndex = newIndex;
}
}
*/
}
public void Add(long absolutePos, long count, float deltaTime, bool isAddBefore) {
var oper = new AddOperation(absolutePos, count, deltaTime, isAddBefore);
oper.Do(ref mMem, ref mPointer, ref mPointerIndex);
mUndoStack.Push(oper);
mRedoStack.Clear();
/*
if (count <= 0) return;
if (mPointer == null) {
// add into blank list, absolutePos and isAddBefore parameters are invalid
// specially process
for(long i = 0; i < count; i++) {
mMem.AddFirst(new FrameData(deltaTime, 0));
}
mPointer = mMem.First;
mPointerIndex = 0;
} else {
// normal add
// normal add doesn't affect pointer
LinkedListNode<FrameData> node = mMem.FastGetNode(mPointer, mPointerIndex, absolutePos);
if (isAddBefore) {
for (long i = 0; i < count; i++) {
mMem.AddBefore(node, new FrameData(deltaTime, 0));
}
} else {
for (long i = 0; i < count; i++) {
mMem.AddAfter(node, new FrameData(deltaTime, 0));
}
}
}
*/
}
public void Insert(long absolutePos, LinkedList<FrameData> data, bool isInsertBefore, bool isOverwritten) {
var oper = new InsertOperation(absolutePos, data, isInsertBefore, isOverwritten);
oper.Do(ref mMem, ref mPointer, ref mPointerIndex);
mUndoStack.Push(oper);
mRedoStack.Clear();
/*
if (data.Count == 0) return;
// the same process route with add function
if (mPointer == null) {
foreach (var item in data.IterateFull()) {
mMem.AddFirst(item.Value);
}
mPointer = mMem.First;
mPointerIndex = 0;
} else {
LinkedListNode<FrameData> node = mMem.FastGetNode(mPointer, mPointerIndex, absolutePos);
if (isInsertBefore) {
foreach (var item in data.IterateFull()) {
mMem.AddBefore(node, item.Value);
}
} else {
foreach (var item in data.IterateFullReversed()) {
mMem.AddAfter(node, item.Value);
}
}
}
*/
}
public void Redo() {
if (mRedoStack.IsEmpty()) return;
var oper = mRedoStack.Pop();
oper.Do(ref mMem, ref mPointer, ref mPointerIndex);
mUndoStack.Push(oper);
}
public void Undo() {
if (mUndoStack.IsEmpty()) return;
var oper = mUndoStack.Pop();
oper.Undo(ref mMem, ref mPointer, ref mPointerIndex);
mRedoStack.Push(oper);
}
public void Copy(SelectionRange absoluteRange, LinkedList<FrameData> data) {
if (mPointer == null) return;
foreach (var item in mMem.IterateWithSelectionRange(absoluteRange, mPointer, mPointerIndex)) {
data.AddLast(item.Value);
}
}
public void Save() {
var fs = new FileStream(mFilename, FileMode.Create, FileAccess.Write, FileShare.None);
ZlibUtil.CompressTAS(mMem, fs);
fs.Close();
fs.Dispose();
}
public void SaveAs(string newfile) {
mFilename = newfile;
Save();
}
#if DEBUG
// following code only should be used in debug mode and served for testbench
public TASFile(LinkedList<FrameData> items) {
mFilename = "";
mMem = items;
mPointer = mMem.First;
mPointerIndex = mPointer == null ? -1 : 0;
mRedoStack = new LimitedStack<RevocableOperation>();
mUndoStack = new LimitedStack<RevocableOperation>();
}
public string Output2TestString() {
StringBuilder sb = new StringBuilder();
if (mPointer == null) sb.Append("null;");
else sb.Append($"{mPointer.Value.keystates.ToString()};");
sb.Append($"{mPointerIndex};");
foreach (var item in mMem.IterateFull()) {
sb.Append(item.Value.keystates.ToString());
sb.Append(",");
}
return sb.ToString();
}
#endif
}
}

View File

@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
namespace BallanceTASEditor.Core.TASStruct {
public class FrameDataDisplay {
public FrameDataDisplay(long index, FrameData fd) {
isEnable = true;
Reload(index, fd);
}
public void Reload(long index, FrameData fd) {
this.index = index;
this.deltaTime = fd.deltaTime;
this.keystates = fd.keystates;
}
public bool isEnable { get; set; }
public long index { get; set; }
public float deltaTime { get; set; }
public UInt32 keystates {
get {
UInt32 result = 0;
if (key_enter) result |= 1; result <<= 1;
if (key_esc) result |= 1; result <<= 1;
if (key_q) result |= 1; result <<= 1;
if (key_space) result |= 1; result <<= 1;
if (key_shift) result |= 1; result <<= 1;
if (key_right) result |= 1; result <<= 1;
if (key_left) result |= 1; result <<= 1;
if (key_down) result |= 1; result <<= 1;
if (key_up) result |= 1; result <<= 1;
return result;
}
set {
key_up = (value & (1 << 0)).ToBool();
key_down = (value & (1 << 1)).ToBool();
key_left = (value & (1 << 2)).ToBool();
key_right = (value & (1 << 3)).ToBool();
key_shift = (value & (1 << 4)).ToBool();
key_space = (value & (1 << 5)).ToBool();
key_q = (value & (1 << 6)).ToBool();
key_esc = (value & (1 << 7)).ToBool();
key_enter = (value & (1 << 8)).ToBool();
}
}
public bool key_up { get; set; }
public bool key_down { get; set; }
public bool key_left { get; set; }
public bool key_right { get; set; }
public bool key_shift { get; set; }
public bool key_space { get; set; }
public bool key_q { get; set; }
public bool key_esc { get; set; }
public bool key_enter { get; set; }
}
public class FrameData {
public FrameData(Stream st) {
var temp = new byte[ConstValue.FRAMEDATA_SIZE];
st.Read(temp, 0, ConstValue.FRAMEDATA_SIZE);
deltaTime = BitConverter.ToSingle(temp, ConstValue.FRAMEDATA_OFFSET_DELTATIME);
keystates = BitConverter.ToUInt32(temp, ConstValue.FRAMEDATA_OFFSET_KEY_STATES);
}
public FrameData(FrameDataDisplay fdd) {
this.deltaTime = fdd.deltaTime;
this.keystates = fdd.keystates;
}
public FrameData() {
this.deltaTime = 0f;
this.keystates = 0;
}
public FrameData(float d, UInt32 k) {
this.deltaTime = d;
this.keystates = k;
}
public void SetKeyStates(UInt32 offset) {
keystates |= offset;
}
public void UnsetKeyStates(UInt32 offset) {
keystates &= ~offset;
}
public void ReverseKeyStates(UInt32 offset) {
keystates ^= offset;
}
public float deltaTime;
public UInt32 keystates;
}
public class ConstValue {
public static readonly Dictionary<FrameDataField, UInt32> Mapping = new Dictionary<FrameDataField, UInt32>() {
{FrameDataField.Key_Up, (1 << 0)},
{FrameDataField.Key_Down, (1 << 1)},
{FrameDataField.Key_Left, (1 << 2)},
{FrameDataField.Key_Right, (1 << 3)},
{FrameDataField.Key_Shift, (1 << 4)},
{FrameDataField.Key_Space, (1 << 5)},
{FrameDataField.Key_Q, (1 << 6)},
{FrameDataField.Key_Esc, (1 << 7)},
{FrameDataField.Key_Enter, (1 << 8)}
};
public const int FRAMEDATA_SIZE = 8;
public const int FRAMEDATA_OFFSET_DELTATIME = 0;
public const int FRAMEDATA_OFFSET_KEY_STATES = 4;
}
public enum FrameDataField : int {
Key_Up = 0,
Key_Down = 1,
Key_Left = 2,
Key_Right = 3,
Key_Shift = 4,
Key_Space = 5,
Key_Q = 6,
Key_Esc = 7,
Key_Enter = 8
}
}

View File

@ -0,0 +1,161 @@
using BallanceTASEditor.Core.TASStruct;
using BallanceTASEditor.UI;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
namespace BallanceTASEditor.Core {
public static class Util {
public static Int32 ToInt32(this double value) {
return (Int32)Math.Floor(value);
}
public static Int64 ToInt64(this double value) {
return (Int64)Math.Floor(value);
}
public static int Clamp(int value, int min, int max) {
if (value < min) return min;
if (value > max) return max;
return value;
}
public static bool ToBool(this UInt32 num) {
return (num != 0);
}
public static UInt32 ToUInt32(this bool b) {
return (UInt32)(b ? 1 : 0);
}
//public static void RemoveRange(this ModifiedObservableCollection<FrameData> list, int index, int count) {
// if (index >= list.Count) return;
// if (index + count > list.Count) count = list.Count - index;
// for (int i = 0; i < count; i++) list.RemoveAt(index);
//}
// remove safety. because it store the next node.
public static IEnumerable<LinkedListNode<FrameData>> IterateFullReversed(this LinkedList<FrameData> ls) {
var pos = ls.Last;
LinkedListNode<FrameData> cacheNextNode;
while (pos != null) {
cacheNextNode = pos.Previous;
yield return pos;
pos = cacheNextNode;
}
}
// remove safety. because it store the next node.
public static IEnumerable<LinkedListNode<FrameData>> IterateFull(this LinkedList<FrameData> ls) {
var pos = ls.First;
LinkedListNode<FrameData> cacheNextNode;
while(pos != null) {
cacheNextNode = pos.Next;
yield return pos;
pos = cacheNextNode;
}
}
public static LinkedListNode<FrameData> FastGetNode(this LinkedList<FrameData> ls, LinkedListNode<FrameData> refNode, long refIndex, long targetIndex) {
long count = ls.Count - 1;
if (targetIndex > count || refIndex > count) throw new Exception("Index is invalid!");
var span = new StupidSortStruct[3] {
new StupidSortStruct() { type = 1, data = targetIndex },
new StupidSortStruct() { type = 2, data = targetIndex - count },
new StupidSortStruct() { type = 3, data = targetIndex - refIndex }
};
// sort to get the min value
StupidSortStruct tmp;
if (Math.Abs(span[0].data) < Math.Abs(span[1].data)) {
tmp = span[0];
span[0] = span[1];
span[1] = tmp;
}
if (Math.Abs(span[1].data) < Math.Abs(span[2].data)) {
tmp = span[1];
span[2] = span[1];
span[2] = tmp;
}
LinkedListNode<FrameData> iterateNode;
if (span[2].type == 1) iterateNode = ls.First;
else if (span[2].type == 2) iterateNode = ls.Last;
else if (span[2].type == 3) iterateNode = refNode;
else throw new Exception("Unknow node type");
return iterateNode.ShiftTo(span[2].data);
}
// remove safety. because it store the next node.
public static IEnumerable<LinkedListNode<FrameData>> IterateWithSelectionRange(this LinkedList<FrameData> ls, SelectionRange absoluteRange, LinkedListNode<FrameData> refNode, long refIndex) {
// goto header first
var cache = ls.FastGetNode(refNode, refIndex, absoluteRange.start);
var counter = absoluteRange.start;
LinkedListNode<FrameData> cacheNextNode;
while (counter <= absoluteRange.end) {
if (cache == null) throw new Exception("Unexpected head or tail of linked list!");
cacheNextNode = cache.Next;
yield return cache;
cache = cacheNextNode;
counter++;
}
}
public static LinkedListNode<FrameData> ShiftTo(this LinkedListNode<FrameData> node, long offset) {
var cache = node;
long realShifted = 0;
if (offset < 0) {
while (realShifted != offset) {
if (cache.Previous == null) throw new Exception("Unexpected head or tail of linked list!");
cache = cache.Previous;
realShifted--;
}
} else if (offset > 0) {
while (realShifted != offset) {
if (cache.Next == null) throw new Exception("Unexpected head or tail of linked list!");
cache = cache.Next;
realShifted++;
}
}
return cache;
}
}
public struct SelectionRange {
public SelectionRange(long value1, long value2) {
if (value1 > value2) {
start = value2;
end = value1;
} else {
start = value1;
end = value2;
}
}
public long start;
public long end;
public SelectionRange GetRelative(long refer) {
var res = new SelectionRange();
res.start = start - refer;
res.end = end - refer;
return res;
}
public bool Within(long num) {
return (num >= start && num <= end);
}
public long GetCount() {
return end - start + 1;
}
}
public struct StupidSortStruct {
public int type;
public long data;
}
}

View File

@ -0,0 +1,84 @@
using BallanceTASEditor.Core.TASStruct;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace BallanceTASEditor.Core {
public class ZlibUtil {
private const int COPY_STREAM_UNIT = 1024;
public static void CompressTAS(LinkedList<FrameData> mem, FileStream file) {
file.Write(BitConverter.GetBytes(mem.Count * ConstValue.FRAMEDATA_SIZE), 0, 4);
using (var zo = new Ionic.Zlib.ZlibStream(file, Ionic.Zlib.CompressionMode.Compress, Ionic.Zlib.CompressionLevel.Level9, true)) {
var node = mem.First;
while (node != null) {
zo.Write(BitConverter.GetBytes(node.Value.deltaTime), 0, 4);
zo.Write(BitConverter.GetBytes(node.Value.keystates), 0, 4);
node = node.Next;
}
zo.Close();
}
//var zo = new zlib.ZOutputStream(file, 9);
//var node = mem.First;
//while (node != null) {
// zo.Write(BitConverter.GetBytes(node.Value.deltaTime), 0, 4);
// zo.Write(BitConverter.GetBytes(node.Value.keystates), 0, 4);
// node = node.Next;
//}
//zo.finish();
//zo.Close();
}
public static void DecompressTAS(LinkedList<FrameData> ls, FileStream file) {
var lengthTemp = new byte[4];
file.Read(lengthTemp, 0, 4);
Int32 expectedLength = BitConverter.ToInt32(lengthTemp, 0);
long expectedCount = expectedLength / ConstValue.FRAMEDATA_SIZE;
using (var mem = new MemoryStream()) {
using (var zo = new Ionic.Zlib.ZlibStream(mem, Ionic.Zlib.CompressionMode.Decompress, true)) {
CopyStream(file, zo);
zo.Close();
}
mem.Seek(0, SeekOrigin.Begin);
for (long i = 0; i < expectedCount; i++) {
ls.AddLast(new FrameData(mem));
}
mem.Close();
}
//mem.Seek(0, SeekOrigin.Begin);
//for (long i = 0; i < expectedCount; i++) {
// ls.AddLast(new FrameData(mem));
//}
//mem.Close();
//zo.Close();
//var zo = new zlib.ZOutputStream(mem);
//CopyStream(file, zo);
//zo.finish();
//mem.Seek(0, SeekOrigin.Begin);
//for (long i = 0; i < expectedCount; i++) {
// ls.AddLast(new FrameData(mem));
//}
//mem.Close();
//zo.Close();
}
public static void CopyStream(Stream origin, Stream target) {
var buffer = new byte[COPY_STREAM_UNIT];
int len;
while ((len = origin.Read(buffer, 0, COPY_STREAM_UNIT)) > 0) {
target.Write(buffer, 0, len);
}
//target.Flush();
}
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BallanceTASEditor {
public class GlobalVariable {
public static Core.ConfigManager configManager;
}
}

View File

@ -0,0 +1,77 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BallanceTASEditor.Language"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<!-- ui data-->
<sys:String x:Key="ui_MainWindow_Title">Ballance TAS 编辑器</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_File">文件</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_File_Open">打开</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_File_Save">保存</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_File_SaveAs">另存为...</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_File_Close">关闭</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_Display">编辑</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_Display_Undo">撤销</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_Display_Redo">重做</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_Display_ItemCount">项个数</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_Display_OverwrittenPaste">覆盖式粘贴</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_Display_HorizontalLayout">横向布局</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_Help">帮助</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_Help_ReportBugs">汇报漏洞</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_Help_About">关于</sys:String>
<sys:String x:Key="ui_MainWindow_EditorNote">打开或拖入一个 TAS 文件以编辑</sys:String>
<sys:String x:Key="ui_MainWindow_Tools_Cursor">选择模式</sys:String>
<sys:String x:Key="ui_MainWindow_Tools_Fill">填充模式</sys:String>
<sys:String x:Key="ui_MainWindow_Tools_Overwritten">画笔模式</sys:String>
<sys:String x:Key="ui_MainWindow_StatusBar_Mode_Cursor">选择模式</sys:String>
<sys:String x:Key="ui_MainWindow_StatusBar_Mode_Fill">填充模式</sys:String>
<sys:String x:Key="ui_MainWindow_StatusBar_Mode_Overwritten">画笔模式</sys:String>
<sys:String x:Key="ui_MainWindow_StatusBar_OverwrittenPaste">覆盖式粘贴</sys:String>
<sys:String x:Key="ui_MainWindow_StatusBar_Selected">已选择:</sys:String>
<sys:String x:Key="ui_TASFlow_Sheet_Frame">帧</sys:String>
<sys:String x:Key="ui_TASFlow_Sheet_DeltaTime">间隔时间</sys:String>
<sys:String x:Key="ui_TASFlow_Menu_Set">设置</sys:String>
<sys:String x:Key="ui_TASFlow_Menu_Unset">不设置</sys:String>
<sys:String x:Key="ui_TASFlow_Menu_Cut">剪切</sys:String>
<sys:String x:Key="ui_TASFlow_Menu_Copy">复制</sys:String>
<sys:String x:Key="ui_TASFlow_Menu_PasteAfter">粘贴于后方</sys:String>
<sys:String x:Key="ui_TASFlow_Menu_PasteBefore">粘贴于前方</sys:String>
<sys:String x:Key="ui_TASFlow_Menu_Delete">删除</sys:String>
<sys:String x:Key="ui_TASFlow_Menu_DeleteAfter">向后删除</sys:String>
<sys:String x:Key="ui_TASFlow_Menu_DeleteBefore">向前删除</sys:String>
<sys:String x:Key="ui_TASFlow_Menu_AddAfter">在后方添加新项</sys:String>
<sys:String x:Key="ui_TASFlow_Menu_AddBefore">在前方添加新项</sys:String>
<sys:String x:Key="ui_AddItem_Title">添加项</sys:String>
<sys:String x:Key="ui_AddItem_Count">个数</sys:String>
<sys:String x:Key="ui_AddItem_FPS">帧率</sys:String>
<sys:String x:Key="ui_AddItem_DeltaTime">间隔时间</sys:String>
<sys:String x:Key="ui_AddItem_OK">好</sys:String>
<sys:String x:Key="ui_AddItem_Cancel">取消</sys:String>
<!-- code data-->
<sys:String x:Key="code_Shared_ProgramName">Ballance TAS 编辑器</sys:String>
<sys:String x:Key="code_MainWindow_Menu_Help_About" xml:space="preserve">基于 MIT 开源许可证发布
版本1.2 stable
程序yyc12345.
图标设计plAer_2</sys:String>
<sys:String x:Key="code_MainWindow_Closing">文件未关闭。您想直接退出吗?所有修改不会被保存。</sys:String>
<sys:String x:Key="code_MainWindow_Menu_File_Close">您想要关闭这个TAS文件吗</sys:String>
<sys:String x:Key="code_MainWindow_Menu_File_Open_Fail">无法打开文件文件可能不是合法的TAS文件。</sys:String>
<sys:String x:Key="code_MainWindow_Menu_Display_ItemCount">输入新的数量(&lt;=5 &amp;&amp; &gt;=30</sys:String>
<sys:String x:Key="code_DialogUtil_Warning">警告</sys:String>
<sys:String x:Key="code_DialogUtil_FileFilter">TAS 文件(*.tas)|*.tas|所有文件(*.*)|*.*</sys:String>
<sys:String x:Key="code_DialogUtil_InputNumber_Title">输入数字</sys:String>
<sys:String x:Key="code_DialogUtil_InputNumber_Wrong">无效的数字,请再试一次。</sys:String>
</ResourceDictionary>

View File

@ -0,0 +1,77 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BallanceTASEditor.Language"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<!-- ui data-->
<sys:String x:Key="ui_MainWindow_Title">Ballance TAS Editor</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_File">File</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_File_Open">Open</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_File_Save">Save</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_File_SaveAs">Save As ...</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_File_Close">Close</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_Display">Edit</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_Display_Undo">Undo</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_Display_Redo">Redo</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_Display_ItemCount">Item Count</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_Display_OverwrittenPaste">Overwritten Paste</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_Display_HorizontalLayout">Horizontal Layout</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_Help">Help</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_Help_ReportBugs">Report Bugs</sys:String>
<sys:String x:Key="ui_MainWindow_Menu_Help_About">About</sys:String>
<sys:String x:Key="ui_MainWindow_EditorNote">Open or drop a TAS file for editing</sys:String>
<sys:String x:Key="ui_MainWindow_Tools_Cursor">Select mode</sys:String>
<sys:String x:Key="ui_MainWindow_Tools_Fill">Fill mode</sys:String>
<sys:String x:Key="ui_MainWindow_Tools_Overwritten">Draw mode</sys:String>
<sys:String x:Key="ui_MainWindow_StatusBar_Mode_Cursor">Select mode</sys:String>
<sys:String x:Key="ui_MainWindow_StatusBar_Mode_Fill">Fill mode</sys:String>
<sys:String x:Key="ui_MainWindow_StatusBar_Mode_Overwritten">Draw mode</sys:String>
<sys:String x:Key="ui_MainWindow_StatusBar_OverwrittenPaste">Overwritten paste</sys:String>
<sys:String xml:space="preserve" x:Key="ui_MainWindow_StatusBar_Selected">Selected: </sys:String>
<sys:String x:Key="ui_TASFlow_Sheet_Frame">Frame</sys:String>
<sys:String x:Key="ui_TASFlow_Sheet_DeltaTime">Delta Time</sys:String>
<sys:String x:Key="ui_TASFlow_Menu_Set">Set</sys:String>
<sys:String x:Key="ui_TASFlow_Menu_Unset">Unset</sys:String>
<sys:String x:Key="ui_TASFlow_Menu_Cut">Cut</sys:String>
<sys:String x:Key="ui_TASFlow_Menu_Copy">Copy</sys:String>
<sys:String x:Key="ui_TASFlow_Menu_PasteAfter">Paste after this</sys:String>
<sys:String x:Key="ui_TASFlow_Menu_PasteBefore">Paste before this</sys:String>
<sys:String x:Key="ui_TASFlow_Menu_Delete">Delete</sys:String>
<sys:String x:Key="ui_TASFlow_Menu_DeleteAfter">Delete this frame</sys:String>
<sys:String x:Key="ui_TASFlow_Menu_DeleteBefore">Delete last frame</sys:String>
<sys:String x:Key="ui_TASFlow_Menu_AddAfter">Add blank item after this</sys:String>
<sys:String x:Key="ui_TASFlow_Menu_AddBefore">Add blank item before this</sys:String>
<sys:String x:Key="ui_AddItem_Title">Add Item</sys:String>
<sys:String x:Key="ui_AddItem_Count">Count</sys:String>
<sys:String x:Key="ui_AddItem_FPS">FPS</sys:String>
<sys:String x:Key="ui_AddItem_DeltaTime">Delta Time</sys:String>
<sys:String x:Key="ui_AddItem_OK">OK</sys:String>
<sys:String x:Key="ui_AddItem_Cancel">Cancel</sys:String>
<!-- code data-->
<sys:String x:Key="code_Shared_ProgramName">Ballance TAS Editor</sys:String>
<sys:String x:Key="code_MainWindow_Menu_Help_About" xml:space="preserve">Under MIT License
Version: 1.2 stable
Program: yyc12345.
Icon design: plAer_2</sys:String>
<sys:String x:Key="code_MainWindow_Closing">File is not closed. Do you want to just quit? All changes will not be saved.</sys:String>
<sys:String x:Key="code_MainWindow_Menu_File_Close">Do you want to close this TAS file?</sys:String>
<sys:String x:Key="code_MainWindow_Menu_File_Open_Fail">Fail to open file. This file might not a legal TAS file.</sys:String>
<sys:String x:Key="code_MainWindow_Menu_Display_ItemCount">Input new count (&lt;=5 &amp;&amp; &gt;=30)</sys:String>
<sys:String x:Key="code_DialogUtil_Warning">Warning</sys:String>
<sys:String x:Key="code_DialogUtil_FileFilter">TAS file(*.tas)|*.tas|All file(*.*)|*.*</sys:String>
<sys:String x:Key="code_DialogUtil_InputNumber_Title">Input number</sys:String>
<sys:String x:Key="code_DialogUtil_InputNumber_Wrong">Invalid number. Please input again.</sys:String>
</ResourceDictionary>

View File

@ -0,0 +1,270 @@
<Window x:Class="BallanceTASEditor.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:local="clr-namespace:BallanceTASEditor"
xmlns:controls="clr-namespace:BallanceTASEditor.UI"
xmlns:input="clr-namespace:System.Windows.Input;assembly=PresentationCore"
mc:Ignorable="d"
Title="{DynamicResource ui_MainWindow_Title}" Height="500" Width="800" KeyUp="funcWindow_KeyUp"
input:InputMethod.IsInputMethodEnabled="False" MouseWheel="funcWindow_MouseWheel" Icon="icon.ico" Closing="funcWindow_Closing">
<!-- shortcut defination-->
<Window.Resources>
<RoutedUICommand x:Key="commandMenu_File_Open"/>
<RoutedUICommand x:Key="commandMenu_File_Save"/>
<RoutedUICommand x:Key="commandMenu_Display_Undo"/>
<RoutedUICommand x:Key="commandMenu_Display_Redo"/>
<RoutedUICommand x:Key="commandDataMenu_Cut"/>
<RoutedUICommand x:Key="commandDataMenu_Copy"/>
<RoutedUICommand x:Key="commandDataMenu_PasteAfter"/>
<RoutedUICommand x:Key="commandDataMenu_DeleteAfter"/>
<RoutedUICommand x:Key="commandDataMenu_DeleteBefore"/>
</Window.Resources>
<Window.InputBindings>
<KeyBinding Gesture="Ctrl+O" Command="{StaticResource commandMenu_File_Open}"/>
<KeyBinding Gesture="Ctrl+S" Command="{StaticResource commandMenu_File_Save}"/>
<KeyBinding Gesture="Ctrl+Z" Command="{StaticResource commandMenu_Display_Undo}"/>
<KeyBinding Gesture="Ctrl+Y" Command="{StaticResource commandMenu_Display_Redo}"/>
<KeyBinding Gesture="Ctrl+X" Command="{StaticResource commandDataMenu_Cut}"/>
<KeyBinding Gesture="Ctrl+C" Command="{StaticResource commandDataMenu_Copy}"/>
<KeyBinding Gesture="Ctrl+V" Command="{StaticResource commandDataMenu_PasteAfter}"/>
<KeyBinding Gesture="Delete" Command="{StaticResource commandDataMenu_DeleteAfter}"/>
<KeyBinding Gesture="Back" Command="{StaticResource commandDataMenu_DeleteBefore}"/>
</Window.InputBindings>
<Window.CommandBindings>
<CommandBinding Command="{StaticResource commandMenu_File_Open}" Executed="funcCommand_Menu_File_Open" CanExecute="funcCanExeCmd_Menu_File_Open"/>
<CommandBinding Command="{StaticResource commandMenu_File_Save}" Executed="funcCommand_Menu_File_Save" CanExecute="funcCanExeCmd_Menu_File_Save"/>
<CommandBinding Command="{StaticResource commandMenu_Display_Undo}" Executed="funcCommand_Menu_Display_Undo" CanExecute="funcCanExeCmd_Menu_Display_Undo"/>
<CommandBinding Command="{StaticResource commandMenu_Display_Redo}" Executed="funcCommand_Menu_Display_Redo" CanExecute="funcCanExeCmd_Menu_Display_Redo"/>
<CommandBinding Command="{StaticResource commandDataMenu_Cut}" Executed="funcCommand_DataMenu_Cut" CanExecute="funcCanExeCmd_DataMenu_Cut"/>
<CommandBinding Command="{StaticResource commandDataMenu_Copy}" Executed="funcCommand_DataMenu_Copy" CanExecute="funcCanExeCmd_DataMenu_Copy"/>
<CommandBinding Command="{StaticResource commandDataMenu_PasteAfter}" Executed="funcCommand_DataMenu_PasteAfter" CanExecute="funcCanExeCmd_DataMenu_PasteAfter"/>
<CommandBinding Command="{StaticResource commandDataMenu_DeleteAfter}" Executed="funcCommand_DataMenu_DeleteAfter" CanExecute="funcCanExeCmd_DataMenu_DeleteAfter"/>
<CommandBinding Command="{StaticResource commandDataMenu_DeleteBefore}" Executed="funcCommand_DataMenu_DeleteBefore" CanExecute="funcCanExeCmd_DataMenu_DeleteBefore"/>
</Window.CommandBindings>
<!-- real ui elements-->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Menu Grid.Row="0">
<MenuItem Header="{DynamicResource ui_MainWindow_Menu_File}">
<MenuItem x:Name="uiMenu_File_Open" Header="{DynamicResource ui_MainWindow_Menu_File_Open}" Click="funcMenu_File_Open" InputGestureText="Ctrl + O"/>
<MenuItem x:Name="uiMenu_File_Save" Header="{DynamicResource ui_MainWindow_Menu_File_Save}" Click="funcMenu_File_Save" InputGestureText="Ctrl + S"/>
<MenuItem x:Name="uiMenu_File_SaveAs" Header="{DynamicResource ui_MainWindow_Menu_File_SaveAs}" Click="funcMenu_File_SaveAs"/>
<MenuItem x:Name="uiMenu_File_Close" Header="{DynamicResource ui_MainWindow_Menu_File_Close}" Click="funcMenu_File_Close"/>
</MenuItem>
<MenuItem Header="{DynamicResource ui_MainWindow_Menu_Display}">
<MenuItem x:Name="uiMenu_Display_Undo" Header="{DynamicResource ui_MainWindow_Menu_Display_Undo}" Click="funcMenu_Display_Undo" InputGestureText="Ctrl + Z"/>
<MenuItem x:Name="uiMenu_Display_Redo" Header="{DynamicResource ui_MainWindow_Menu_Display_Redo}" Click="funcMenu_Display_Redo" InputGestureText="Ctrl + Y"/>
<Separator/>
<MenuItem x:Name="uiMenu_Display_ItemCount" Header="{DynamicResource ui_MainWindow_Menu_Display_ItemCount}" Click="funcMenu_Display_ItemCount"/>
<Separator/>
<MenuItem x:Name="uiMenu_Display_OverwrittenPaste" Header="{DynamicResource ui_MainWindow_Menu_Display_OverwrittenPaste}" Click="funcMenu_Display_OverwrittenPaste"/>
<MenuItem x:Name="uiMenu_Display_HorizontalLayout" Header="{DynamicResource ui_MainWindow_Menu_Display_HorizontalLayout}" Click="funcMenu_Display_HorizontalLayout"/>
</MenuItem>
<MenuItem Header="{DynamicResource ui_MainWindow_Menu_Help}">
<MenuItem x:Name="uiMenu_Help_ReportBugs" Header="{DynamicResource ui_MainWindow_Menu_Help_ReportBugs}" Click="funcMenu_Help_ReportBugs"/>
<MenuItem x:Name="uiMenu_Help_About" Header="{DynamicResource ui_MainWindow_Menu_Help_About}" Click="funcMenu_Help_About"/>
</MenuItem>
</Menu>
<Grid x:Name="uiEditorNote" Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center" AllowDrop="True" Drop="funcDrop_Drop" DragEnter="funcDrop_DragEnter" Visibility="Collapsed">
<Rectangle StrokeThickness="4" Stroke="Gray" StrokeDashArray="4 4" Fill="#ffffff"/>
<StackPanel Orientation="Horizontal">
<Viewbox Width="24" Height="24" Margin="20, 20, 0, 20" VerticalAlignment="Center">
<Canvas Width="24" Height="24">
<Path Fill="Gray" Data="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" />
</Canvas>
</Viewbox>
<TextBlock Margin="20" Text="{DynamicResource ui_MainWindow_EditorNote}" Foreground="Gray" FontSize="16" VerticalAlignment="Center"/>
</StackPanel>
</Grid>
<Grid x:Name="uiEditorPanel" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0" Grid.ColumnSpan="2">
<Button x:Name="uiBtn_Cursor" Margin="5" Padding="5" Click="funcBtn_Cursor">
<StackPanel Orientation="Horizontal">
<Viewbox Width="24" Height="24">
<Canvas Width="24" Height="24">
<Path Fill="Black" Data="M10.07,14.27C10.57,14.03 11.16,14.25 11.4,14.75L13.7,19.74L15.5,18.89L13.19,13.91C12.95,13.41 13.17,12.81 13.67,12.58L13.95,12.5L16.25,12.05L8,5.12V15.9L9.82,14.43L10.07,14.27M13.64,21.97C13.14,22.21 12.54,22 12.31,21.5L10.13,16.76L7.62,18.78C7.45,18.92 7.24,19 7,19A1,1 0 0,1 6,18V3A1,1 0 0,1 7,2C7.24,2 7.47,2.09 7.64,2.23L7.65,2.22L19.14,11.86C19.57,12.22 19.62,12.85 19.27,13.27C19.12,13.45 18.91,13.57 18.7,13.61L15.54,14.23L17.74,18.96C18,19.46 17.76,20.05 17.26,20.28L13.64,21.97Z" />
</Canvas>
</Viewbox>
<TextBlock Text="{DynamicResource ui_MainWindow_Tools_Cursor}" VerticalAlignment="Center"/>
</StackPanel>
</Button>
<Button x:Name="uiBtn_Fill" Margin="5" Padding="5" Click="funcBtn_Fill">
<StackPanel Orientation="Horizontal">
<Viewbox Width="24" Height="24">
<Canvas Width="24" Height="24">
<Path Fill="Black" Data="M19,11.5C19,11.5 17,13.67 17,15A2,2 0 0,0 19,17A2,2 0 0,0 21,15C21,13.67 19,11.5 19,11.5M5.21,10L10,5.21L14.79,10M16.56,8.94L7.62,0L6.21,1.41L8.59,3.79L3.44,8.94C2.85,9.5 2.85,10.47 3.44,11.06L8.94,16.56C9.23,16.85 9.62,17 10,17C10.38,17 10.77,16.85 11.06,16.56L16.56,11.06C17.15,10.47 17.15,9.5 16.56,8.94Z" />
</Canvas>
</Viewbox>
<TextBlock Text="{DynamicResource ui_MainWindow_Tools_Fill}" VerticalAlignment="Center"/>
</StackPanel>
</Button>
<Button x:Name="uiBtn_Overwrite" Margin="5" Padding="5" Click="funcBtn_Overwrite">
<StackPanel Orientation="Horizontal">
<Viewbox Width="24" Height="24">
<Canvas Width="24" Height="24">
<Path Fill="Black" Data="M18.62,1.5C18.11,1.5 17.6,1.69 17.21,2.09L10.75,8.55L14.95,12.74L21.41,6.29C22.2,5.5 22.2,4.24 21.41,3.46L20.04,2.09C19.65,1.69 19.14,1.5 18.62,1.5M9.8,9.5L3.23,16.07L3.93,16.77C3.4,17.24 2.89,17.78 2.38,18.29C1.6,19.08 1.6,20.34 2.38,21.12C3.16,21.9 4.42,21.9 5.21,21.12C5.72,20.63 6.25,20.08 6.73,19.58L7.43,20.27L14,13.7" />
</Canvas>
</Viewbox>
<TextBlock Text="{DynamicResource ui_MainWindow_Tools_Overwritten}" VerticalAlignment="Center"/>
</StackPanel>
</Button>
</StackPanel>
<Grid x:Name="uiLayoutContainer" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Border x:Name="uiTASFlowContainer" BorderThickness="1" BorderBrush="Gray" Grid.Row="0" Margin="5" Background="#ffffff">
<Grid x:Name="uiTASData">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem x:Name="uiDataMenu_Set" Header="{DynamicResource ui_TASFlow_Menu_Set}" Click="funcDataMenu_Set"/>
<MenuItem x:Name="uiDataMenu_Unset" Header="{DynamicResource ui_TASFlow_Menu_Unset}" Click="funcDataMenu_Unset"/>
<Separator/>
<MenuItem x:Name="uiDataMenu_Cut" Header="{DynamicResource ui_TASFlow_Menu_Cut}" Click="funcDataMenu_Cut" InputGestureText="Ctrl + X"/>
<MenuItem x:Name="uiDataMenu_Copy" Header="{DynamicResource ui_TASFlow_Menu_Copy}" Click="funcDataMenu_Copy" InputGestureText="Ctrl + C"/>
<MenuItem x:Name="uiDataMenu_PasteAfter" Header="{DynamicResource ui_TASFlow_Menu_PasteAfter}" Click="funcDataMenu_PasteAfter" InputGestureText="Ctrl + V"/>
<MenuItem x:Name="uiDataMenu_PasteBefore" Header="{DynamicResource ui_TASFlow_Menu_PasteBefore}" Click="funcDataMenu_PasteBefore"/>
<Separator/>
<MenuItem x:Name="uiDataMenu_Delete" Header="{DynamicResource ui_TASFlow_Menu_Delete}" Click="funcDataMenu_Delete"/>
<MenuItem x:Name="uiDataMenu_DeleteAfter" Header="{DynamicResource ui_TASFlow_Menu_DeleteAfter}" Click="funcDataMenu_DeleteAfter" InputGestureText="Del"/>
<MenuItem x:Name="uiDataMenu_DeleteBefore" Header="{DynamicResource ui_TASFlow_Menu_DeleteBefore}" Click="funcDataMenu_DeleteBefore" InputGestureText="Backspace"/>
<Separator/>
<MenuItem x:Name="uiDataMenu_AddAfter" Header="{DynamicResource ui_TASFlow_Menu_AddAfter}" Click="funcDataMenu_AddAfter"/>
<MenuItem x:Name="uiDataMenu_AddBefore" Header="{DynamicResource ui_TASFlow_Menu_AddBefore}" Click="funcDataMenu_AddBefore"/>
</ContextMenu>
</Grid.ContextMenu>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
</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="*"/>
</Grid.RowDefinitions>
<TextBlock x:Name="uiFlowHeader_Frame" Padding="2" Background="#afafaf" Grid.Column="0" Grid.Row="1" Text="{DynamicResource ui_TASFlow_Sheet_Frame}"/>
<TextBlock x:Name="uiFlowHeader_DeltaTime" Padding="2" Background="#afafaf" Grid.Column="0" Grid.Row="2" Text="{DynamicResource ui_TASFlow_Sheet_DeltaTime}"/>
<TextBlock x:Name="uiFlowHeader_Up" Padding="2" Background="#afafaf" Grid.Column="0" Grid.Row="3" Text="^"/>
<TextBlock x:Name="uiFlowHeader_Down" Padding="2" Background="#afafaf" Grid.Column="0" Grid.Row="4" Text="v"/>
<TextBlock x:Name="uiFlowHeader_Left" Padding="2" Background="#afafaf" Grid.Column="0" Grid.Row="5" Text="&lt;"/>
<TextBlock x:Name="uiFlowHeader_Right" Padding="2" Background="#afafaf" Grid.Column="0" Grid.Row="6" Text="&gt;"/>
<TextBlock x:Name="uiFlowHeader_Shift" Padding="2" Background="#afafaf" Grid.Column="0" Grid.Row="7" Text="shift"/>
<TextBlock x:Name="uiFlowHeader_Space" Padding="2" Background="#afafaf" Grid.Column="0" Grid.Row="8" Text="space"/>
<TextBlock x:Name="uiFlowHeader_Q" Padding="2" Background="#afafaf" Grid.Column="0" Grid.Row="9" Text="q"/>
<TextBlock x:Name="uiFlowHeader_Esc" Padding="2" Background="#afafaf" Grid.Column="0" Grid.Row="10" Text="esc"/>
<TextBlock x:Name="uiFlowHeader_Enter" Padding="2" Background="#afafaf" Grid.Column="0" Grid.Row="11" Text="enter"/>
</Grid>
</Border>
<Grid x:Name="uiTASSliderContainer" Grid.Row="1" Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="uiBtn_FastMovePrev" Grid.Column="0" Margin="2" HorizontalAlignment="Center" Click="funcBtn_FastMovePrev" RenderTransformOrigin="0.5, 0.5">
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="Black" Data="M11.5,12L20,18V6M11,18V6L2.5,12L11,18Z" />
</Canvas>
</Viewbox>
</Button>
<Button x:Name="uiBtn_MovePrev" Grid.Column="1" Margin="2" HorizontalAlignment="Center" Click="funcBtn_MovePrev" RenderTransformOrigin="0.5, 0.5">
<Viewbox Width="16" Height="16" RenderTransformOrigin="0.5,0.5">
<Viewbox.RenderTransform>
<RotateTransform Angle="-90"/>
</Viewbox.RenderTransform>
<Canvas Width="24" Height="24">
<Path Fill="Black" Data="M7,15L12,10L17,15H7Z" />
</Canvas>
</Viewbox>
</Button>
<Button x:Name="uiBtn_MoveNext" Grid.Column="2" Margin="2" HorizontalAlignment="Center" Click="funcBtn_MoveNext" RenderTransformOrigin="0.5, 0.5">
<Viewbox Width="16" Height="16" RenderTransformOrigin="0.5,0.5">
<Viewbox.RenderTransform>
<RotateTransform Angle="-90"/>
</Viewbox.RenderTransform>
<Canvas Width="24" Height="24">
<Path Fill="Black" Data="M7,10L12,15L17,10H7Z" />
</Canvas>
</Viewbox>
</Button>
<Button x:Name="uiBtn_FastMoveNext" Grid.Column="3" Margin="2" HorizontalAlignment="Center" Click="funcBtn_FastMoveNext" RenderTransformOrigin="0.5, 0.5">
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="Black" Data="M13,6V18L21.5,12M4,18L12.5,12L4,6V18Z" />
</Canvas>
</Viewbox>
</Button>
<Slider x:Name="uiTASSlider" Margin="2" Grid.Column="4" SmallChange="1" LargeChange="10" Maximum="1" VerticalAlignment="Center" RenderTransformOrigin="0.5, 0.5">
</Slider>
</Grid>
</Grid>
</Grid>
<StatusBar x:Name="uiStatusbar" Grid.Row="2">
<StatusBarItem x:Name="uiStatusbar_Runtime_Mode">
<Grid>
<TextBlock x:Name="uiStatusbar_Mode_Cursor" Text="{DynamicResource ui_MainWindow_StatusBar_Mode_Cursor}"/>
<TextBlock x:Name="uiStatusbar_Mode_Fill" Text="{DynamicResource ui_MainWindow_StatusBar_Mode_Fill}"/>
<TextBlock x:Name="uiStatusbar_Mode_Overwrite" Text="{DynamicResource ui_MainWindow_StatusBar_Mode_Overwritten}"/>
</Grid>
</StatusBarItem>
<Separator x:Name="uiStatusbar_Runtime_Separator1"/>
<StatusBarItem x:Name="uiStatusbar_Runtime_PasteMode">
<TextBlock x:Name="uiStatusbar_OverwrittenPaste" Text="{DynamicResource ui_MainWindow_StatusBar_OverwrittenPaste}"/>
</StatusBarItem>
<Separator x:Name="uiStatusbar_Runtime_Separator2"/>
<StatusBarItem x:Name="uiStatusbar_Runtime_Selected">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{DynamicResource ui_MainWindow_StatusBar_Selected}"/>
<TextBlock x:Name="uiStatusbar_Selected" Text="-"/>
</StackPanel>
</StatusBarItem>
<StatusBarItem DockPanel.Dock="Right" HorizontalAlignment="Right">
<TextBlock Text="v1.2 stable" Foreground="Gray" FontStyle="Italic"/>
</StatusBarItem>
</StatusBar>
</Grid>
</Window>

View File

@ -0,0 +1,511 @@
using BallanceTASEditor.Core;
using BallanceTASEditor.Core.TASStruct;
using BallanceTASEditor.UI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace BallanceTASEditor {
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
mIsHorizontalLayout = true;
// init layout controller
var headers = new List<TextBlock>();
headers.Add(uiFlowHeader_Frame);
headers.Add(uiFlowHeader_DeltaTime);
headers.Add(uiFlowHeader_Up);
headers.Add(uiFlowHeader_Down);
headers.Add(uiFlowHeader_Left);
headers.Add(uiFlowHeader_Right);
headers.Add(uiFlowHeader_Shift);
headers.Add(uiFlowHeader_Space);
headers.Add(uiFlowHeader_Q);
headers.Add(uiFlowHeader_Esc);
headers.Add(uiFlowHeader_Enter);
mFlow = new TASFlow(uiTASData, headers.ToArray());
var components = new TASSliderComponents();
components.container = uiTASSliderContainer;
components.btnFastPrev = uiBtn_FastMovePrev;
components.btnPrev = uiBtn_MovePrev;
components.btnNext = uiBtn_MoveNext;
components.btnFastNext = uiBtn_FastMoveNext;
components.mSlider = uiTASSlider;
mSlider = new TASSlider(components); ;
// refresh ui and load cfg
RefreshUI(false);
ApplyConfigureManager();
}
TASFile mFile;
TASViewer mViewer;
TASFlow mFlow;
TASSlider mSlider;
bool mIsHorizontalLayout;
#region ui func
// shortcut
private void funcCommand_Menu_File_Open(object sender, ExecutedRoutedEventArgs e) => funcMenu_File_Open(sender, e);
private void funcCommand_Menu_File_Save(object sender, ExecutedRoutedEventArgs e) => funcMenu_File_Save(sender, e);
private void funcCommand_Menu_Display_Undo(object sender, ExecutedRoutedEventArgs e) => funcMenu_Display_Undo(sender, e);
private void funcCommand_Menu_Display_Redo(object sender, ExecutedRoutedEventArgs e) => funcMenu_Display_Redo(sender, e);
private void funcCommand_DataMenu_Cut(object sender, ExecutedRoutedEventArgs e) => funcDataMenu_Cut(sender, e);
private void funcCommand_DataMenu_Copy(object sender, ExecutedRoutedEventArgs e) => funcDataMenu_Copy(sender, e);
private void funcCommand_DataMenu_PasteAfter(object sender, ExecutedRoutedEventArgs e) => funcDataMenu_PasteAfter(sender, e);
private void funcCommand_DataMenu_DeleteAfter(object sender, ExecutedRoutedEventArgs e) => funcDataMenu_DeleteAfter(sender, e);
private void funcCommand_DataMenu_DeleteBefore(object sender, ExecutedRoutedEventArgs e) => funcDataMenu_DeleteBefore(sender, e);
private void funcCanExeCmd_Menu_File_Open(object sender, CanExecuteRoutedEventArgs e) => e.CanExecute = uiMenu_File_Open.IsEnabled;
private void funcCanExeCmd_Menu_File_Save(object sender, CanExecuteRoutedEventArgs e) => e.CanExecute = uiMenu_File_Save.IsEnabled;
private void funcCanExeCmd_Menu_Display_Undo(object sender, CanExecuteRoutedEventArgs e) => e.CanExecute = uiMenu_Display_Undo.IsEnabled;
private void funcCanExeCmd_Menu_Display_Redo(object sender, CanExecuteRoutedEventArgs e) => e.CanExecute = uiMenu_Display_Redo.IsEnabled;
private void funcCanExeCmd_DataMenu_Cut(object sender, CanExecuteRoutedEventArgs e) => e.CanExecute = mViewer != null && uiDataMenu_Cut.IsEnabled;
private void funcCanExeCmd_DataMenu_Copy(object sender, CanExecuteRoutedEventArgs e) => e.CanExecute = mViewer != null && uiDataMenu_Copy.IsEnabled;
private void funcCanExeCmd_DataMenu_PasteAfter(object sender, CanExecuteRoutedEventArgs e) => e.CanExecute = mViewer != null && uiDataMenu_PasteAfter.IsEnabled;
private void funcCanExeCmd_DataMenu_DeleteAfter(object sender, CanExecuteRoutedEventArgs e) => e.CanExecute = mViewer != null && uiDataMenu_DeleteAfter.IsEnabled;
private void funcCanExeCmd_DataMenu_DeleteBefore(object sender, CanExecuteRoutedEventArgs e) => e.CanExecute = mViewer != null && uiDataMenu_DeleteBefore.IsEnabled;
// =========================== menu
#region window menu
private void funcMenu_Help_ReportBugs(object sender, RoutedEventArgs e) {
System.Diagnostics.Process.Start("https://github.com/yyc12345/BallanceTASEditor/issues");
}
private void funcMenu_Help_About(object sender, RoutedEventArgs e) {
MessageBox.Show(I18NProcessor.GetI18N("code_MainWindow_Menu_Help_About"),
I18NProcessor.GetI18N("code_Shared_ProgramName"),
MessageBoxButton.OK, MessageBoxImage.Information);
}
private void funcMenu_File_Open(object sender, RoutedEventArgs e) {
var file = DialogUtil.OpenFileDialog();
if (file == "") return;
OpenFile(file);
}
private void funcMenu_File_Save(object sender, RoutedEventArgs e) {
mFile.Save();
}
private void funcMenu_File_SaveAs(object sender, RoutedEventArgs e) {
var file = DialogUtil.SaveFileDialog();
if (file == "") return;
mFile.SaveAs(file);
}
private void funcMenu_File_Close(object sender, RoutedEventArgs e) {
if (!DialogUtil.ConfirmDialog(I18NProcessor.GetI18N("code_MainWindow_Menu_File_Close"))) return;
mViewer.UpdateDataUI -= RefreshDataUI;
mViewer.UpdateSelection -= RefreshSelection;
mViewer.UpdateToolMode -= RefreshToolMode;
mViewer.Dispose();
mFile = null;
mViewer = null;
RefreshUI(false);
}
private void funcWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
if (!(mFile is null)) {
if (!DialogUtil.ConfirmDialog(I18NProcessor.GetI18N("code_MainWindow_Closing"))) {
e.Cancel = true;
}
}
}
private void funcMenu_Display_ItemCount(object sender, RoutedEventArgs e) {
int newvalue = 0;
if (DialogUtil.InputNumber(I18NProcessor.GetI18N("code_MainWindow_Menu_Display_ItemCount"), 5, 30, ref newvalue)) {
ChangeItemCount(newvalue);
}
}
private void funcMenu_Display_OverwrittenPaste(object sender, RoutedEventArgs e) {
uiMenu_Display_OverwrittenPaste.IsChecked = !uiMenu_Display_OverwrittenPaste.IsChecked;
uiStatusbar_OverwrittenPaste.Visibility = uiMenu_Display_OverwrittenPaste.IsChecked ? Visibility.Visible : Visibility.Hidden;
GlobalVariable.configManager.Configuration[ConfigManager.CfgNode_IsOverwrittenPaste] = uiMenu_Display_OverwrittenPaste.IsChecked.ToString();
if (mViewer != null)
mViewer.ChangeOverwrittenMode(uiMenu_Display_OverwrittenPaste.IsChecked);
}
private void funcMenu_Display_HorizontalLayout(object sender, RoutedEventArgs e) {
uiMenu_Display_HorizontalLayout.IsChecked = !uiMenu_Display_HorizontalLayout.IsChecked;
GlobalVariable.configManager.Configuration[ConfigManager.CfgNode_IsHorizonLayout] = uiMenu_Display_HorizontalLayout.IsChecked.ToString();
ChangeLayout(uiMenu_Display_HorizontalLayout.IsChecked);
}
private void funcMenu_Display_Redo(object sender, RoutedEventArgs e) {
mViewer.ProcessOperation(OperationEnum.Redo);
}
private void funcMenu_Display_Undo(object sender, RoutedEventArgs e) {
mViewer.ProcessOperation(OperationEnum.Undo);
}
#endregion
#region datamenu operation
private void funcDataMenu_Set(object sender, RoutedEventArgs e) {
mViewer.ProcessOperation(OperationEnum.Set);
}
private void funcDataMenu_Unset(object sender, RoutedEventArgs e) {
mViewer.ProcessOperation(OperationEnum.Unset);
}
private void funcDataMenu_Cut(object sender, RoutedEventArgs e) {
mViewer.ProcessOperation(OperationEnum.Cut);
}
private void funcDataMenu_Copy(object sender, RoutedEventArgs e) {
mViewer.ProcessOperation(OperationEnum.Copy);
}
private void funcDataMenu_PasteAfter(object sender, RoutedEventArgs e) {
mViewer.ProcessOperation(OperationEnum.PasteAfter);
}
private void funcDataMenu_PasteBefore(object sender, RoutedEventArgs e) {
mViewer.ProcessOperation(OperationEnum.PasteBefore);
}
private void funcDataMenu_Delete(object sender, RoutedEventArgs e) {
mViewer.ProcessOperation(OperationEnum.Delete);
}
private void funcDataMenu_DeleteAfter(object sender, RoutedEventArgs e) {
mViewer.ProcessOperation(OperationEnum.DeleteAfter);
}
private void funcDataMenu_DeleteBefore(object sender, RoutedEventArgs e) {
mViewer.ProcessOperation(OperationEnum.DeleteBefore);
}
private void funcDataMenu_AddAfter(object sender, RoutedEventArgs e) {
mViewer.ProcessOperation(OperationEnum.AddAfter);
}
private void funcDataMenu_AddBefore(object sender, RoutedEventArgs e) {
mViewer.ProcessOperation(OperationEnum.AddBefore);
}
#endregion
// =========================== btn
private void funcBtn_Cursor(object sender, RoutedEventArgs e) {
mViewer.ChangeToolMode(ToolMode.Cursor);
}
private void funcBtn_Fill(object sender, RoutedEventArgs e) {
mViewer.ChangeToolMode(ToolMode.Fill);
}
private void funcBtn_Overwrite(object sender, RoutedEventArgs e) {
mViewer.ChangeToolMode(ToolMode.Overwrite);
}
// move btn
private void funcBtn_FastMovePrev(object sender, RoutedEventArgs e) {
mSlider.MoveSliderManually(true, true, mViewer.GetItemCountInPage());
}
private void funcBtn_MovePrev(object sender, RoutedEventArgs e) {
mSlider.MoveSliderManually(true, false, mViewer.GetItemCountInPage());
}
private void funcBtn_MoveNext(object sender, RoutedEventArgs e) {
mSlider.MoveSliderManually(false, false, mViewer.GetItemCountInPage());
}
private void funcBtn_FastMoveNext(object sender, RoutedEventArgs e) {
mSlider.MoveSliderManually(false, true, mViewer.GetItemCountInPage());
}
// move keyboard
private void funcWindow_KeyUp(object sender, KeyEventArgs e) {
if (mFile == null || mViewer == null) return;
switch (e.Key) {
case Key.A:
mSlider.MoveSliderManually(true, true, mViewer.GetItemCountInPage());
break;
case Key.S:
mSlider.MoveSliderManually(true, false, mViewer.GetItemCountInPage());
break;
case Key.D:
mSlider.MoveSliderManually(false, false, mViewer.GetItemCountInPage());
break;
case Key.F:
mSlider.MoveSliderManually(false, true, mViewer.GetItemCountInPage());
break;
}
}
// move mouse
private void funcWindow_MouseWheel(object sender, MouseWheelEventArgs e) {
if (e.Delta > 0) {
// wheel up
if (KeyboardState.IsKeyPressed(KeyboardState.VirtualKeyStates.VK_SHIFT)) {
// move quickly
mSlider.MoveSliderManually(true, true, mViewer.GetItemCountInPage());
} else if (KeyboardState.IsKeyPressed(KeyboardState.VirtualKeyStates.VK_CONTROL)) {
// decrease item count
var newvalue = mViewer.GetItemCountInPage();
ChangeItemCount(newvalue - 1);
} else {
// normally move
mSlider.MoveSliderManually(true, false, mViewer.GetItemCountInPage());
}
} else if (e.Delta < 0) {
// wheel down
if (KeyboardState.IsKeyPressed(KeyboardState.VirtualKeyStates.VK_SHIFT)) {
// move quickly
mSlider.MoveSliderManually(false, true, mViewer.GetItemCountInPage());
} else if (KeyboardState.IsKeyPressed(KeyboardState.VirtualKeyStates.VK_CONTROL)) {
// increase item count
var newvalue = mViewer.GetItemCountInPage();
ChangeItemCount(newvalue + 1);
} else {
// normally move
mSlider.MoveSliderManually(false, false, mViewer.GetItemCountInPage());
}
}
}
// drop file to open
private void funcDrop_Drop(object sender, DragEventArgs e) {
string fileName = ((System.Array)e.Data.GetData(DataFormats.FileDrop)).GetValue(0).ToString();
OpenFile(fileName);
}
private void funcDrop_DragEnter(object sender, DragEventArgs e) {
// only accept one file
if (e.Data.GetDataPresent(DataFormats.FileDrop)) {
var arr = (System.Array)e.Data.GetData(DataFormats.FileDrop);
if (arr.Length != 1) e.Effects = DragDropEffects.None;
else e.Effects = DragDropEffects.Link;
} else e.Effects = DragDropEffects.None;
}
#endregion
private void ApplyConfigureManager() {
var isOverwrittenPaste = bool.Parse(GlobalVariable.configManager.Configuration[ConfigManager.CfgNode_IsOverwrittenPaste]);
var isHorizontalLayout = bool.Parse(GlobalVariable.configManager.Configuration[ConfigManager.CfgNode_IsHorizonLayout]);
var itemCount = int.Parse(GlobalVariable.configManager.Configuration[ConfigManager.CfgNode_ItemCount]);
uiMenu_Display_OverwrittenPaste.IsChecked = isOverwrittenPaste;
uiMenu_Display_HorizontalLayout.IsChecked = isHorizontalLayout;
uiStatusbar_OverwrittenPaste.Visibility = isOverwrittenPaste ? Visibility.Visible : Visibility.Hidden;
if (mViewer != null) {
mViewer.ChangeOverwrittenMode(isOverwrittenPaste);
mViewer.ChangeListLength(itemCount);
ChangeLayout(isHorizontalLayout);
}
}
private void OpenFile(string file) {
#if DEBUG
#else
try {
#endif
mFile = new TASFile(file);
#if DEBUG
#else
} catch {
MessageBox.Show(I18NProcessor.GetI18N("code_MainWindow_Menu_File_Open_Fail"),
I18NProcessor.GetI18N("code_Shared_ProgramName"),
MessageBoxButton.OK, MessageBoxImage.Error);
mFile = null;
return;
}
#endif
mViewer = new TASViewer(mFile, mSlider, mFlow);
mViewer.UpdateDataUI += RefreshDataUI;
mViewer.UpdateSelection += RefreshSelection;
mViewer.UpdateToolMode += RefreshToolMode;
RefreshUI(true);
ApplyConfigureManager(); // apply item count and overwritten item
mViewer.ChangeToolMode(ToolMode.Cursor);
}
private void RefreshToolMode(ToolMode mode) {
switch (mode) {
case ToolMode.Cursor:
uiBtn_Cursor.IsEnabled = false;
uiBtn_Fill.IsEnabled = true;
uiBtn_Overwrite.IsEnabled = true;
uiStatusbar_Mode_Cursor.Visibility = Visibility.Visible;
uiStatusbar_Mode_Fill.Visibility = Visibility.Collapsed;
uiStatusbar_Mode_Overwrite.Visibility = Visibility.Collapsed;
break;
case ToolMode.Fill:
uiBtn_Cursor.IsEnabled = true;
uiBtn_Fill.IsEnabled = false;
uiBtn_Overwrite.IsEnabled = true;
uiStatusbar_Mode_Cursor.Visibility = Visibility.Collapsed;
uiStatusbar_Mode_Fill.Visibility = Visibility.Visible;
uiStatusbar_Mode_Overwrite.Visibility = Visibility.Collapsed;
break;
case ToolMode.Overwrite:
uiBtn_Cursor.IsEnabled = true;
uiBtn_Fill.IsEnabled = true;
uiBtn_Overwrite.IsEnabled = false;
uiStatusbar_Mode_Cursor.Visibility = Visibility.Collapsed;
uiStatusbar_Mode_Fill.Visibility = Visibility.Collapsed;
uiStatusbar_Mode_Overwrite.Visibility = Visibility.Visible;
break;
}
}
private void RefreshUI(bool isFileOpened) {
if (isFileOpened) {
uiEditorPanel.Visibility = Visibility.Visible;
uiEditorNote.Visibility = Visibility.Collapsed;
uiMenu_File_Open.IsEnabled = false;
uiMenu_File_Save.IsEnabled = true;
uiMenu_File_SaveAs.IsEnabled = true;
uiMenu_File_Close.IsEnabled = true;
uiMenu_Display_ItemCount.IsEnabled = true;
uiMenu_Display_OverwrittenPaste.IsEnabled = true;
uiMenu_Display_HorizontalLayout.IsEnabled = true;
uiMenu_Display_Undo.IsEnabled = true;
uiMenu_Display_Redo.IsEnabled = true;
uiStatusbar_Runtime_Mode.Visibility = Visibility.Visible;
uiStatusbar_Runtime_PasteMode.Visibility = Visibility.Visible;
uiStatusbar_Runtime_Selected.Visibility = Visibility.Visible;
uiStatusbar_Runtime_Separator1.Visibility = Visibility.Visible;
uiStatusbar_Runtime_Separator2.Visibility = Visibility.Visible;
} else {
uiEditorPanel.Visibility = Visibility.Collapsed;
uiEditorNote.Visibility = Visibility.Visible;
uiMenu_File_Open.IsEnabled = true;
uiMenu_File_Save.IsEnabled = false;
uiMenu_File_SaveAs.IsEnabled = false;
uiMenu_File_Close.IsEnabled = false;
uiMenu_Display_ItemCount.IsEnabled = false;
uiMenu_Display_OverwrittenPaste.IsEnabled = false;
uiMenu_Display_HorizontalLayout.IsEnabled = false;
uiMenu_Display_Undo.IsEnabled = false;
uiMenu_Display_Redo.IsEnabled = false;
uiStatusbar_Runtime_Mode.Visibility = Visibility.Collapsed;
uiStatusbar_Runtime_PasteMode.Visibility = Visibility.Collapsed;
uiStatusbar_Runtime_Selected.Visibility = Visibility.Collapsed;
uiStatusbar_Runtime_Separator1.Visibility = Visibility.Collapsed;
uiStatusbar_Runtime_Separator2.Visibility = Visibility.Collapsed;
}
}
private void RefreshDataUI(bool showCursorPasteAddDeleteOne, bool showFill, bool showCursorCopyDelete) {
uiDataMenu_Set.IsEnabled = showFill;
uiDataMenu_Unset.IsEnabled = showFill;
uiDataMenu_Cut.IsEnabled = showCursorCopyDelete;
uiDataMenu_Copy.IsEnabled = showCursorCopyDelete;
uiDataMenu_Delete.IsEnabled = showCursorCopyDelete;
uiDataMenu_DeleteAfter.IsEnabled = showCursorPasteAddDeleteOne;
uiDataMenu_DeleteBefore.IsEnabled = showCursorPasteAddDeleteOne;
uiDataMenu_PasteAfter.IsEnabled = showCursorPasteAddDeleteOne;
uiDataMenu_PasteBefore.IsEnabled = showCursorPasteAddDeleteOne;
uiDataMenu_AddAfter.IsEnabled = showCursorPasteAddDeleteOne;
uiDataMenu_AddBefore.IsEnabled = showCursorPasteAddDeleteOne;
}
private void RefreshSelection(SelectionHelp mSelectionHelp) {
var mode = mSelectionHelp.GetToolMode();
switch (mode) {
case ToolMode.Cursor:
if (mSelectionHelp.IsDataReady()) {
var data = mSelectionHelp.GetRange();
uiStatusbar_Selected.Text = $"{data.start} - {data.end}";
} else if (mSelectionHelp.IsDataPartialReady()) {
var data2 = mSelectionHelp.GetPoint();
uiStatusbar_Selected.Text = data2.ToString();
} else uiStatusbar_Selected.Text = "-";
break;
case ToolMode.Fill:
if (mSelectionHelp.IsDataReady()) {
var data3 = mSelectionHelp.GetRange();
uiStatusbar_Selected.Text = $"{data3.start} - {data3.end}";
} else uiStatusbar_Selected.Text = "-";
break;
case ToolMode.Overwrite:
if (mSelectionHelp.IsDataReady()) {
var data4 = mSelectionHelp.GetPoint();
uiStatusbar_Selected.Text = data4.ToString();
} else uiStatusbar_Selected.Text = "-";
break;
}
}
private void ChangeItemCount(int count) {
GlobalVariable.configManager.Configuration[ConfigManager.CfgNode_ItemCount] = count.ToString();
mViewer.ChangeListLength(count);
}
private void ChangeLayout(bool isHorizontal) {
if (isHorizontal == mIsHorizontalLayout) return;
mIsHorizontalLayout = isHorizontal;
// swap window size
var swap = this.Width;
this.Width = this.Height;
this.Height = swap;
// change self layout first
uiLayoutContainer.RowDefinitions.Clear();
uiLayoutContainer.ColumnDefinitions.Clear();
if (isHorizontal) {
UI.Util.GridRowAdder(uiLayoutContainer, new GridLength(1, GridUnitType.Star));
UI.Util.GridRowAdder(uiLayoutContainer, GridLength.Auto);
} else {
UI.Util.GridColumnAdder(uiLayoutContainer, new GridLength(1, GridUnitType.Star));
UI.Util.GridColumnAdder(uiLayoutContainer, GridLength.Auto);
}
UI.Util.SwapGridItemRC(uiTASFlowContainer);
UI.Util.SwapGridItemRC(uiTASSliderContainer);
// change sub layout
mFlow.ChangeLayout(isHorizontal);
mSlider.ChangeLayout(isHorizontal);
}
}
}

View File

@ -0,0 +1,55 @@
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
// 有关程序集的一般信息由以下
// 控制。更改这些特性值可修改
// 与程序集关联的信息。
[assembly: AssemblyTitle("Ballance TAS Editor")]
[assembly: AssemblyDescription("Ballance TAS Editor")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Ballance Community")]
[assembly: AssemblyProduct("Ballance TAS Editor")]
[assembly: AssemblyCopyright("Copyright © Ballance Community 2022")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// 将 ComVisible 设置为 false 会使此程序集中的类型
//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
//请将此类型的 ComVisible 特性设置为 true。
[assembly: ComVisible(false)]
//若要开始生成可本地化的应用程序,请设置
//.csproj 文件中的 <UICulture>CultureYouAreCodingWith</UICulture>
//例如,如果您在源文件中使用的是美国英语,
//使用的是美国英语,请将 <UICulture> 设置为 en-US。 然后取消
//对以下 NeutralResourceLanguage 特性的注释。 更新
//以下行中的“en-US”以匹配项目文件中的 UICulture 设置。
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //主题特定资源词典所处位置
//(未在页面中找到资源时使用,
//或应用程序资源字典中找到时使用)
ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置
//(未在页面中找到资源时使用,
//、应用程序或任何主题专用资源字典中找到时使用)
)]
// 程序集的版本信息由下列四个值组成:
//
// 主版本
// 次版本
// 生成号
// 修订号
//
//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
//通过使用 "*",如下所示:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.1.0.0")]
[assembly: AssemblyFileVersion("1.1.0.0")]

View File

@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 此代码由工具生成。
// 运行时版本:4.0.30319.42000
//
// 对此文件的更改可能会导致不正确的行为,并且如果
// 重新生成代码,这些更改将会丢失。
// </auto-generated>
//------------------------------------------------------------------------------
namespace BallanceTASEditor.Properties {
using System;
/// <summary>
/// 一个强类型的资源类,用于查找本地化的字符串等。
/// </summary>
// 此类是由 StronglyTypedResourceBuilder
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
// (以 /str 作为命令选项),或重新生成 VS 项目。
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// 返回此类使用的缓存的 ResourceManager 实例。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("BallanceTASEditor.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// 重写当前线程的 CurrentUICulture 属性
/// 重写当前线程的 CurrentUICulture 属性。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 此代码由工具生成。
// 运行时版本:4.0.30319.42000
//
// 对此文件的更改可能会导致不正确的行为,并且如果
// 重新生成代码,这些更改将会丢失。
// </auto-generated>
//------------------------------------------------------------------------------
namespace BallanceTASEditor.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.3.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View File

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

View File

@ -0,0 +1,49 @@
<Window x:Class="BallanceTASEditor.UI.AddItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:local="clr-namespace:BallanceTASEditor.UI"
xmlns:converter="clr-namespace:BallanceTASEditor.UI"
mc:Ignorable="d"
Title="{DynamicResource ui_AddItem_Title}" Height="200" Width="400" WindowStyle="ToolWindow" WindowStartupLocation="CenterOwner" Icon="/BallanceTASEditor;component/icon.ico">
<Window.Resources>
<converter:AddItemConverter x:Key="conv_addItem"/>
<converter:FPS2DeltaTimeConverter x:Key="conv_fps2DeltaTime"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Margin="5" Grid.Column="0" Grid.Row="0" Text="{DynamicResource ui_AddItem_Count}" VerticalAlignment="Center"/>
<TextBlock Margin="5" Grid.Column="0" Grid.Row="1" Text="{DynamicResource ui_AddItem_FPS}" VerticalAlignment="Center"/>
<TextBlock Margin="5" Grid.Column="0" Grid.Row="2" Text="{DynamicResource ui_AddItem_DeltaTime}" VerticalAlignment="Center"/>
<TextBox x:Name="uiTextbox_Count" Margin="5" Padding="5" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"/>
<TextBox x:Name="uiTextbox_FPS" Margin="5" Padding="5" Grid.Row="1" Grid.Column="1" VerticalAlignment="Center"/>
<TextBlock x:Name="uiText_DeltaTime" Margin="5" Padding="5" Grid.Row="2" Grid.Column="1" VerticalAlignment="Center"
Text="{Binding Converter={StaticResource conv_fps2DeltaTime}, Mode=OneWay, ElementName=uiTextbox_FPS, Path=Text}"/>
<StackPanel Orientation="Horizontal" Margin="5" HorizontalAlignment="Right" Grid.ColumnSpan="2" Grid.Row="4">
<Button x:Name="uiBtn_OK" Margin="5" Padding="5" Content="{DynamicResource ui_AddItem_OK}" MinWidth="50" Click="funcBtn_OK">
<Button.IsEnabled>
<MultiBinding Converter="{StaticResource conv_addItem}" Mode="OneWay">
<Binding ElementName="uiTextbox_Count" Path="Text"/>
<Binding ElementName="uiTextbox_FPS" Path="Text"/>
</MultiBinding>
</Button.IsEnabled>
</Button>
<Button x:Name="uiBtn_Cancel" Margin="5" Padding="5" Content="{DynamicResource ui_AddItem_Cancel}" MinWidth="50" Click="funcBtn_Cancel"/>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace BallanceTASEditor.UI {
/// <summary>
/// AddItem.xaml 的交互逻辑
/// </summary>
public partial class AddItem : Window {
public AddItem() {
InitializeComponent();
}
public int Output_Count { get; private set; }
public float Output_DeltaTime { get; private set; }
private void funcBtn_OK(object sender, RoutedEventArgs e) {
int count;
float fps;
if (!int.TryParse(uiTextbox_Count.Text, out count)) return;
if (!float.TryParse(uiTextbox_FPS.Text, out fps)) return;
Output_Count = count;
Output_DeltaTime = 1000f / fps;
this.DialogResult = true;
}
private void funcBtn_Cancel(object sender, RoutedEventArgs e) {
this.DialogResult = false;
}
}
}

View File

@ -0,0 +1,62 @@
using Microsoft.VisualBasic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using BallanceTASEditor.Core;
namespace BallanceTASEditor.UI {
public class DialogUtil {
public static string OpenFileDialog() {
Microsoft.Win32.OpenFileDialog op = new Microsoft.Win32.OpenFileDialog();
op.RestoreDirectory = true;
op.Multiselect = false;
op.Filter = I18NProcessor.GetI18N("code_DialogUtil_FileFilter");
if (!(bool)op.ShowDialog()) return "";
return op.FileName;
}
public static string SaveFileDialog() {
Microsoft.Win32.SaveFileDialog op = new Microsoft.Win32.SaveFileDialog();
op.RestoreDirectory = true;
op.Filter = I18NProcessor.GetI18N("code_DialogUtil_FileFilter");
if (!(bool)op.ShowDialog()) return "";
return op.FileName;
}
public static bool ConfirmDialog(string str) {
var result = MessageBox.Show(str, I18NProcessor.GetI18N("code_DialogUtil_Warning"), MessageBoxButton.YesNo, MessageBoxImage.Warning);
return (result == MessageBoxResult.Yes);
}
public static bool InputNumber(string title, int min, int max, ref int result) {
while (true) {
var dialog = Interaction.InputBox(title, I18NProcessor.GetI18N("code_DialogUtil_InputNumber_Title"), "");
if (dialog == "") return false;
if (int.TryParse(dialog, out result)) {
if (result <= max && result >= min) break;
}
MessageBox.Show(I18NProcessor.GetI18N("code_DialogUtil_InputNumber_Wrong"),
I18NProcessor.GetI18N("code_DialogUtil_Warning"),
MessageBoxButton.OK, MessageBoxImage.Warning);
}
return true;
}
public static bool AddItemDialog(out int count, out float deltaTime) {
var win = new AddItem();
if (!(bool)win.ShowDialog()) {
count = 0;
deltaTime = 0f;
return false;
}
count = win.Output_Count;
deltaTime = win.Output_DeltaTime;
return true;
}
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BallanceTASEditor.UI {
public enum OperationEnum {
Set,
Unset,
Cut,
Copy,
PasteAfter,
PasteBefore,
Delete,
DeleteAfter,
DeleteBefore,
AddAfter,
AddBefore,
Undo,
Redo
}
}

View File

@ -0,0 +1,137 @@
using BallanceTASEditor.Core.TASStruct;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BallanceTASEditor.Core;
namespace BallanceTASEditor.UI {
public class SelectionHelp {
public SelectionHelp() {
SetMode(ToolMode.Cursor);
}
public event Action SelectionChanged;
ToolMode mMode;
FrameDataField mStartField;
FrameDataField mEndField;
long mStart;
bool mIsStartConfirmed;
long mEnd;
bool mIsEndConfirmed;
public void SetMode(ToolMode mode) {
switch (mode) {
case ToolMode.Cursor:
mStart = 0;
mEnd = 0;
mIsStartConfirmed = false;
mIsEndConfirmed = false;
break;
case ToolMode.Fill:
mStartField = FrameDataField.Key_Up;
mEndField = FrameDataField.Key_Up;
mStart = 0;
mEnd = 0;
mIsStartConfirmed = false;
mIsEndConfirmed = false;
break;
case ToolMode.Overwrite:
mStartField = FrameDataField.Key_Up;
mStart = 0;
mIsStartConfirmed = false;
break;
}
mMode = mode;
OnSelectionChanged();
}
public void FirstClick(long index, FrameDataField field) {
mStartField = field;
mStart = index;
mIsStartConfirmed = true;
mIsEndConfirmed = false;
OnSelectionChanged();
}
public void LastClick(long index, FrameDataField field) {
if (mMode == ToolMode.Overwrite) return;
if (!mIsStartConfirmed) return;
mEndField = field;
mEnd = index;
mIsEndConfirmed = true;
OnSelectionChanged();
}
public void Reset() {
// reuse set mode to reset
SetMode(mMode);
}
// onnly used for delete prev one / next one shift.
public void ShiftTo(bool toNext) {
if (!IsDataPartialReady()) throw new Exception("Shift operation with wrong condition.");
FirstClick(GetPoint() + (toNext ? 1 : -1), mStartField);
}
public SelectionRange GetRange() {
if (mMode == ToolMode.Overwrite) throw new Exception("Read with wrong mode.");
if (!(mIsStartConfirmed && mIsEndConfirmed)) throw new Exception("Data is not ready to read");
return new SelectionRange(mStart, mEnd);
}
public SelectionRange GetFieldRange() {
if (mMode != ToolMode.Fill) throw new Exception("Read with wrong mode.");
if (!(mIsStartConfirmed && mIsEndConfirmed)) throw new Exception("Data is not ready to read");
return new SelectionRange((int)mStartField, (int)mEndField);
}
public long GetPoint() {
if (mMode == ToolMode.Fill) throw new Exception("Read with wrong mode.");
if (!mIsStartConfirmed) throw new Exception("Data is not ready to read");
if (mMode == ToolMode.Overwrite) return mStart;
else {
// cursor mode
if (mIsStartConfirmed) return mStart;
else throw new Exception("Data is not ready to read");
}
}
public FrameDataField GetPointField() {
if (mMode != ToolMode.Overwrite) throw new Exception("Read with wrong mode.");
if (!mIsStartConfirmed) throw new Exception("Data is not ready to read");
return mStartField;
}
public bool IsDataReady() {
switch (mMode) {
case ToolMode.Cursor:
case ToolMode.Fill:
return (mIsStartConfirmed && mIsEndConfirmed);
case ToolMode.Overwrite:
return mIsStartConfirmed;
}
return false;
}
public bool IsDataPartialReady() {
return (mMode == ToolMode.Cursor && mIsStartConfirmed && !mIsEndConfirmed);
}
public ToolMode GetToolMode() {
return mMode;
}
private void OnSelectionChanged() {
SelectionChanged?.Invoke();
}
}
}

View File

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
namespace BallanceTASEditor.UI {
public class AddItemConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
try {
var textCount = values[0] as string;
var textFps = values[1] as string;
var count = int.Parse(textCount);
var fps = float.Parse(textFps);
if (count <= 0 || fps <= 0) return false;
return true;
} catch {
return false;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {
return null;
}
}
public class FPS2DeltaTimeConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var text = value as string;
if (text == null) return "0";
float data;
if (!float.TryParse(text, out data)) return "0";
return (1000f / data).ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
return null;
}
}
}

View File

@ -0,0 +1,401 @@
using BallanceTASEditor.Core;
using BallanceTASEditor.Core.TASStruct;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace BallanceTASEditor.UI {
public partial class TASFlow {
public TASFlow(Grid coreWindow, TextBlock[] headers) {
uiCoreWindow = coreWindow;
mHeaders = headers;
mItemList = new List<TASFlowUIItem>();
mRectMap = new Dictionary<Rectangle, CellPosition>();
mItemCount = 0;
mIsHorizontalLayout = true;
SetItemCount(1);
}
public event Action Click;
private readonly TextBlock[] mHeaders;
private bool mIsHorizontalLayout;
private Grid uiCoreWindow;
private int mItemCount;
private List<TASFlowUIItem> mItemList;
private Dictionary<Rectangle, CellPosition> mRectMap;
public SelectionHelp SelectionHelp { get; set; }
public List<FrameDataDisplay> DataSources { get; set; }
public void ChangeLayout(bool isHorizontal) {
if (isHorizontal == mIsHorizontalLayout) return;
// the layout changed, re-construct elements
mIsHorizontalLayout = isHorizontal;
// delete original grid layout
var lenHeader = mHeaders.Length;
uiCoreWindow.ColumnDefinitions.Clear();
uiCoreWindow.RowDefinitions.Clear();
// add header layout
if (mIsHorizontalLayout) {
// horizontal layout
// row is header count
// column is tas unit
// header
layout_row_adder(GridLength.Auto);
for (int r = 0; r < lenHeader; r++) {
layout_row_adder(GridLength.Auto);
}
layout_row_adder(new GridLength(1, GridUnitType.Star));
// tas unit
layout_column_adder(GridLength.Auto);
for (int c = 0; c < mItemCount; c++) {
layout_column_adder(new GridLength(1, GridUnitType.Star));
}
} else {
// vertical layout
// row is tas unit
// column is header count (use start to split, not auto)
// header
layout_column_adder(GridLength.Auto);
for (int r = 0; r < lenHeader; r++) {
if (r == 0 || r == 1)
layout_column_adder(GridLength.Auto);
else
layout_column_adder(new GridLength(1, GridUnitType.Star));
}
//layout_column_adder(new GridLength(1, GridUnitType.Star));
// tas unit
layout_row_adder(GridLength.Auto);
for (int c = 0; c < mItemCount; c++) {
layout_row_adder(new GridLength(1, GridUnitType.Star));
}
}
// now, change items attach prop
// just swap Grid.Row and Grid.Column's number
foreach (var item in mHeaders) {
var swap = Grid.GetColumn(item);
Grid.SetColumn(item, Grid.GetRow(item));
Grid.SetRow(item, swap);
}
foreach (var item in mItemList) {
item.FlipUnit();
}
}
private void layout_row_adder(GridLength size) {
UI.Util.GridRowAdder(uiCoreWindow, size);
}
private void layout_column_adder(GridLength size) {
UI.Util.GridColumnAdder(uiCoreWindow, size);
}
public void RefreshDataSources() {
if (DataSources == null) return;
for (int i = 0; i < mItemCount; i++) {
mItemList[i].Reload(DataSources[i]);
}
}
public void SetItemCount(int newCount) {
var offset = newCount - mItemCount;
var abs = Math.Abs(offset);
if (offset == 0) return;
// change column defination first
if (offset > 0) {
for (int i = 0; i < abs; i++) {
if (mIsHorizontalLayout) {
layout_column_adder(new GridLength(1, GridUnitType.Star));
} else {
layout_row_adder(new GridLength(1, GridUnitType.Star));
}
}
} else {
if (mIsHorizontalLayout) {
uiCoreWindow.ColumnDefinitions.RemoveRange(newCount + 1, abs); // the first col is sheet header, so add 1 additionally
} else {
uiCoreWindow.RowDefinitions.RemoveRange(newCount + 1, abs); // the first col is sheet header, so add 1 additionally
}
}
// add / remove item
if (offset > 0) {
for (int i = 0; i < abs; i++) {
var newItem = new TASFlowUIItem(mItemCount + 1 + i, mIsHorizontalLayout); // the first col is sheet header, so add 1 additionally
newItem.Add(uiCoreWindow, mRectMap, Rectangle_MouseDown);
mItemList.Add(newItem);
}
} else {
for (int i = 0; i < abs; i++) {
mItemList[newCount + i].Remove(uiCoreWindow, mRectMap, Rectangle_MouseDown);
}
mItemList.RemoveRange(newCount, abs);
}
// apply new count
mItemCount = newCount;
}
public void RefreshSelectionHighlight() {
ToolMode mode = SelectionHelp.GetToolMode();
if (mode == ToolMode.Cursor) {
if (SelectionHelp.IsDataReady()) {
var data = SelectionHelp.GetRange();
foreach (var item in mItemList) {
if (data.Within(item.rawFrame)) {
item.SelectFull();
} else {
item.Unselect();
}
}
return;
} else if (SelectionHelp.IsDataPartialReady()) {
var data = SelectionHelp.GetPoint();
foreach (var item in mItemList) {
if (data == item.rawFrame) {
item.SelectFull();
} else {
item.Unselect();
}
}
return;
}
} else if (mode == ToolMode.Fill) {
if (SelectionHelp.IsDataReady()) {
var data = SelectionHelp.GetRange();
var field = SelectionHelp.GetFieldRange();
foreach (var item in mItemList) {
if (data.Within(item.rawFrame)) {
item.SelectField(field);
} else {
item.Unselect();
}
}
return;
}
}
// fail if prog run into this position, clear
foreach (var item in mItemList) {
item.Unselect();
}
}
private void Rectangle_MouseDown(object sender, MouseButtonEventArgs e) {
if (SelectionHelp == null) return;
// because the first column is header
// so all pos.column should -1
var rect = sender as Rectangle;
var pos = mRectMap[rect];
if (!mItemList[pos.column - 1].rawIsEnable) return;
if (e.MouseDevice.LeftButton == MouseButtonState.Pressed) {
if (KeyboardState.IsKeyPressed(KeyboardState.VirtualKeyStates.VK_SHIFT)) {
SelectionHelp.LastClick(mItemList[pos.column - 1].rawFrame, pos.field);
} else {
SelectionHelp.FirstClick(mItemList[pos.column - 1].rawFrame, pos.field);
}
} else if (e.MouseDevice.RightButton == MouseButtonState.Pressed) {
// if we click right button and there are no full selection in cursor mode
// try to add a first selection
if (SelectionHelp.GetToolMode() == ToolMode.Cursor) {
if (!SelectionHelp.IsDataReady()) {
SelectionHelp.FirstClick(mItemList[pos.column - 1].rawFrame, pos.field);
}
}
}
// note main window to process it.
OnClick();
}
// only raised in overwrite mode
private void OnClick() {
if (SelectionHelp.GetToolMode() == ToolMode.Overwrite)
Click?.Invoke();
}
}
public class TASFlowUIItem {
private static readonly Thickness DEFAULT_MARGIN = new Thickness(2);
private static readonly Thickness RECT_MARGIN = new Thickness(1);
private static readonly SolidColorBrush SEL_RECT_NORMAL_BRUSH = new SolidColorBrush(Colors.White);
private static readonly SolidColorBrush SEL_RECT_SELECTED_BRUSH = new SolidColorBrush(Colors.OrangeRed);
private static readonly SolidColorBrush SEL_RECT_STROKE = new SolidColorBrush(Colors.LightGray);
private static readonly Color COLOR_SET = Color.FromRgb(30, 144, 255);
private static readonly Color COLOR_UNSET = Color.FromArgb(0, 255, 255, 255);
private static readonly Color COLOR_SELECTED = Colors.OrangeRed;
private static readonly Color COLOR_UNSELECTED = Colors.LightGray;
private const double SELECTION_HEADER_HEIGHT = 10.0f;
private const int KEY_COUNT = 9;
public TASFlowUIItem(int column, bool useHorizontalLayout) {
// basic item
sel_rect = new Rectangle();
frame = new TextBlock();
deltaTime = new TextBlock();
if (useHorizontalLayout) {
Grid.SetRow(sel_rect, 0);
Grid.SetRow(frame, 1);
Grid.SetRow(deltaTime, 2);
Grid.SetColumn(sel_rect, column);
Grid.SetColumn(frame, column);
Grid.SetColumn(deltaTime, column);
} else {
Grid.SetColumn(sel_rect, 0);
Grid.SetColumn(frame, 1);
Grid.SetColumn(deltaTime, 2);
Grid.SetRow(sel_rect, column);
Grid.SetRow(frame, column);
Grid.SetRow(deltaTime, column);
}
sel_rect.Margin = RECT_MARGIN;
frame.Margin = DEFAULT_MARGIN;
deltaTime.Margin = DEFAULT_MARGIN;
sel_rect.StrokeThickness = 2;
sel_rect.Stroke = SEL_RECT_STROKE;
if (useHorizontalLayout)
sel_rect.Height = SELECTION_HEADER_HEIGHT;
else
sel_rect.Width = SELECTION_HEADER_HEIGHT;
// keystates item
keystates = new Rectangle[KEY_COUNT];
keystatesFill = new SolidColorBrush[KEY_COUNT];
keystatesStroke = new SolidColorBrush[KEY_COUNT];
for (int i = 0; i < KEY_COUNT; i++) {
keystates[i] = new Rectangle();
keystatesFill[i] = new SolidColorBrush(COLOR_UNSET);
keystatesStroke[i] = new SolidColorBrush(COLOR_UNSELECTED);
if (useHorizontalLayout) {
Grid.SetRow(keystates[i], 3 + i);
Grid.SetColumn(keystates[i], column);
} else {
Grid.SetColumn(keystates[i], 3 + i);
Grid.SetRow(keystates[i], column);
}
keystates[i].Margin = RECT_MARGIN;
keystates[i].StrokeThickness = 3;
keystates[i].Stroke = keystatesStroke[i];
keystates[i].Fill = keystatesFill[i];
}
this.column = column;
rawFrame = 0;
rawIsEnable = false;
}
public void FlipUnit() {
UI.Util.SwapGridItemRC(sel_rect);
UI.Util.SwapGridItemRC(frame);
UI.Util.SwapGridItemRC(deltaTime);
for (int i = 0; i < KEY_COUNT; i++) {
UI.Util.SwapGridItemRC(keystates[i]);
}
// swap sel_rect height and width
var swap = sel_rect.Height;
sel_rect.Height = sel_rect.Width;
sel_rect.Width = swap;
}
public void Add(Grid target, Dictionary<Rectangle, CellPosition> map, MouseButtonEventHandler func) {
target.Children.Add(sel_rect);
target.Children.Add(frame);
target.Children.Add(deltaTime);
for (int i = 0; i < KEY_COUNT; i++) {
target.Children.Add(keystates[i]);
keystates[i].MouseDown += func;
map.Add(keystates[i], new CellPosition(column, (FrameDataField)i));
}
}
public void Remove(Grid target, Dictionary<Rectangle, CellPosition> map, MouseButtonEventHandler func) {
target.Children.Remove(sel_rect);
target.Children.Remove(frame);
target.Children.Remove(deltaTime);
for (int i = 0; i < KEY_COUNT; i++) {
target.Children.Remove(keystates[i]);
keystates[i].MouseDown -= func;
map.Remove(keystates[i]);
}
}
public void Reload(FrameDataDisplay fdd) {
var isEnable = fdd.isEnable;
frame.Text = isEnable ? fdd.index.ToString() : "";
deltaTime.Text = isEnable ? fdd.deltaTime.ToString() : "";
keystatesFill[0].Color = isEnable && fdd.key_up ? COLOR_SET : COLOR_UNSET;
keystatesFill[1].Color = isEnable && fdd.key_down ? COLOR_SET : COLOR_UNSET;
keystatesFill[2].Color = isEnable && fdd.key_left ? COLOR_SET : COLOR_UNSET;
keystatesFill[3].Color = isEnable && fdd.key_right ? COLOR_SET : COLOR_UNSET;
keystatesFill[4].Color = isEnable && fdd.key_shift ? COLOR_SET : COLOR_UNSET;
keystatesFill[5].Color = isEnable && fdd.key_space ? COLOR_SET : COLOR_UNSET;
keystatesFill[6].Color = isEnable && fdd.key_q ? COLOR_SET : COLOR_UNSET;
keystatesFill[7].Color = isEnable && fdd.key_esc ? COLOR_SET : COLOR_UNSET;
keystatesFill[8].Color = isEnable && fdd.key_enter ? COLOR_SET : COLOR_UNSET;
sel_rect.Visibility = isEnable ? Visibility.Visible : Visibility.Collapsed;
frame.Visibility = isEnable ? Visibility.Visible : Visibility.Collapsed;
deltaTime.Visibility = isEnable ? Visibility.Visible : Visibility.Collapsed;
for (int i = 0; i < KEY_COUNT; i++) {
keystates[i].Visibility = isEnable ? Visibility.Visible : Visibility.Collapsed;
}
rawIsEnable = isEnable;
rawFrame = fdd.index;
}
public void SelectFull() {
sel_rect.Fill = SEL_RECT_SELECTED_BRUSH;
}
public void SelectField(SelectionRange range) {
for (int i = 0; i < KEY_COUNT; i++) {
keystatesStroke[i].Color = range.Within(i) ? COLOR_SELECTED : COLOR_UNSELECTED;
}
}
public void Unselect() {
sel_rect.Fill = SEL_RECT_NORMAL_BRUSH;
for (int i = 0; i < KEY_COUNT; i++) {
keystatesStroke[i].Color = COLOR_UNSELECTED;
}
}
public long rawFrame;
public bool rawIsEnable;
public Rectangle sel_rect;
public TextBlock frame;
public TextBlock deltaTime;
public Rectangle[] keystates;
private SolidColorBrush[] keystatesFill;
private SolidColorBrush[] keystatesStroke;
private int column;
}
}

View File

@ -0,0 +1,117 @@
using BallanceTASEditor.Core;
using BallanceTASEditor.Core.TASStruct;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace BallanceTASEditor.UI {
public class TASSlider {
public TASSlider(TASSliderComponents _components) {
components = _components;
components.mSlider.Minimum = 0;
mIsHorizontalLayout = true;
components.mSlider.ValueChanged += func_SliderValueChanged;
}
public event Action<long> ValueChanged;
TASSliderComponents components;
bool mIsHorizontalLayout;
public void MoveSliderManually(bool isPrev, bool isFast, int fastCount) {
var step = isFast ? fastCount : 1;
components.mSlider.Value = Core.Util.Clamp(components.mSlider.Value.ToInt32() + (isPrev ? -1 : 1) * step, components.mSlider.Minimum.ToInt32(), components.mSlider.Maximum.ToInt32());
}
public void UpdateRange(TASFile mFile) {
components.mSlider.Maximum = mFile.mFrameCount == 0 ? 0 : mFile.mFrameCount - 1;
var index = mFile.GetPointerIndex();
if (index >= 0) {
components.mSlider.Value = mFile.GetPointerIndex();
components.mSlider.IsEnabled = true;
} else {
// invalid index, mean slider is useless, disable it
components.mSlider.Value = components.mSlider.Maximum;
components.mSlider.IsEnabled = false;
}
}
private void func_SliderValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {
ValueChanged?.Invoke(e.NewValue.ToInt64());
}
public void ChangeLayout(bool isHorizontal) {
if (isHorizontal == mIsHorizontalLayout) return;
// the layout changed, re-construct elements
mIsHorizontalLayout = isHorizontal;
// change container
components.container.RowDefinitions.Clear();
components.container.ColumnDefinitions.Clear();
if (mIsHorizontalLayout) {
for(int i = 0; i < 4; i++) {
UI.Util.GridColumnAdder(components.container, GridLength.Auto);
}
UI.Util.GridColumnAdder(components.container, new GridLength(1, GridUnitType.Star));
} else {
for (int i = 0; i < 4; i++) {
UI.Util.GridRowAdder(components.container, GridLength.Auto);
}
UI.Util.GridRowAdder(components.container, new GridLength(1, GridUnitType.Star));
}
// flip elements
UI.Util.SwapGridItemRC(components.btnFastPrev);
UI.Util.SwapGridItemRC(components.btnPrev);
UI.Util.SwapGridItemRC(components.btnNext);
UI.Util.SwapGridItemRC(components.btnFastNext);
UI.Util.SwapGridItemRC(components.mSlider);
// change transform
if (mIsHorizontalLayout) {
// clear all btn's transform and set slider as horizontal style
components.btnFastPrev.RenderTransform = Transform.Identity;
components.btnPrev.RenderTransform = Transform.Identity;
components.btnNext.RenderTransform = Transform.Identity;
components.btnFastNext.RenderTransform = Transform.Identity;
components.mSlider.RenderTransform = Transform.Identity;
components.mSlider.Orientation = Orientation.Horizontal;
components.mSlider.VerticalAlignment = VerticalAlignment.Center;
components.mSlider.HorizontalAlignment = HorizontalAlignment.Stretch;
} else {
components.btnFastPrev.RenderTransform = new RotateTransform(90);
components.btnPrev.RenderTransform = new RotateTransform(90);
components.btnNext.RenderTransform = new RotateTransform(90);
components.btnFastNext.RenderTransform = new RotateTransform(90);
components.mSlider.RenderTransform = new RotateTransform(180);
components.mSlider.Orientation = Orientation.Vertical;
components.mSlider.VerticalAlignment = VerticalAlignment.Stretch;
components.mSlider.HorizontalAlignment = HorizontalAlignment.Center;
}
}
}
public class TASSliderComponents {
public Grid container;
public Button btnFastPrev;
public Button btnPrev;
public Button btnNext;
public Button btnFastNext;
public Slider mSlider;
}
}

View File

@ -0,0 +1,253 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BallanceTASEditor.Core;
using BallanceTASEditor.Core.TASStruct;
using System.Collections.ObjectModel;
using System.Windows.Controls;
using System.Windows;
namespace BallanceTASEditor.UI {
public class TASViewer : IDisposable {
public TASViewer(TASFile file, TASSlider slider, TASFlow datagrid) {
mFile = file;
mSlider = slider;
mDataGrid = datagrid;
// restore slider
mSlider.UpdateRange(mFile);
// init selection
mSelectionHelp = new SelectionHelp();
mSelectionHelp.SelectionChanged += funcSelectionHelp_SelectionChanged;
// init data
INVALID_FRAME_DATA = new FrameData(-1f, 0);
mDataSource = new List<FrameDataDisplay>();
mListLength = 0;
mOverwrittenPaste = false;
// bind event and source
mDataGrid.DataSources = mDataSource;
mDataGrid.SelectionHelp = mSelectionHelp;
mDataGrid.Click += funcDataMenu_Click;
mSlider.ValueChanged += funcSlider_ValueChanged;
}
public void Dispose() {
mDataGrid.DataSources = null;
mDataGrid.Click -= funcDataMenu_Click;
mSlider.ValueChanged -= funcSlider_ValueChanged;
}
//const int DATA_LIST_LENGTH = 15;
FrameData INVALID_FRAME_DATA;
TASFile mFile;
TASSlider mSlider;
TASFlow mDataGrid;
SelectionHelp mSelectionHelp;
int mListLength;
List<FrameDataDisplay> mDataSource;
bool mOverwrittenPaste;
#region self event
public event Action<bool, bool, bool> UpdateDataUI;
public event Action<ToolMode> UpdateToolMode;
public event Action<SelectionHelp> UpdateSelection;
private void OnUpdateDataUI() {
ToolMode mode = mSelectionHelp.GetToolMode();
bool showCursorPasteAddDeleteOne = mode == ToolMode.Cursor && mSelectionHelp.IsDataPartialReady();
bool showFill = mode == ToolMode.Fill && mSelectionHelp.IsDataReady();
bool showCursorCopyDelete = mode == ToolMode.Cursor && mSelectionHelp.IsDataReady();
UpdateDataUI?.Invoke(showCursorPasteAddDeleteOne, showFill, showCursorCopyDelete);
}
private void OnUpdateToolMode() {
UpdateToolMode?.Invoke(mSelectionHelp.GetToolMode());
}
private void OnUpdateSelection() {
UpdateSelection?.Invoke(mSelectionHelp);
}
#endregion
#region process event
private void funcSlider_ValueChanged(long pos) {
mFile.Shift(pos);
RefreshDisplay();
}
private void funcSelectionHelp_SelectionChanged() {
mDataGrid.RefreshSelectionHighlight();
OnUpdateDataUI();
OnUpdateSelection();
}
private void funcDataMenu_Click() {
var data = mSelectionHelp.GetPoint();
var field = (int)mSelectionHelp.GetPointField();
mFile.Set(new SelectionRange(field, field), new SelectionRange(data, data), null);
RefreshDisplay();
}
#endregion
public void ChangeOverwrittenMode(bool isOverwritten) {
mOverwrittenPaste = isOverwritten;
}
public void ChangeListLength(int newLen) {
if (newLen < 5 || newLen > 200) return;
int offset = newLen - mListLength;
int abs = Math.Abs(offset);
if (offset == 0) return;
// change mDataSource first
if (offset > 0) {
for (int i = 0; i < abs; i++) {
mDataSource.Add(new FrameDataDisplay(0, INVALID_FRAME_DATA));
}
} else {
mDataSource.RemoveRange(newLen, abs);
}
// then change viewer control
mDataGrid.SetItemCount(newLen);
// apply new value
mListLength = newLen;
// then refresh
RefreshDisplay();
}
public void RefreshDisplay() {
mFile.Get(mDataSource, mListLength);
mDataGrid.RefreshDataSources();
mDataGrid.RefreshSelectionHighlight();
}
public void ChangeToolMode(ToolMode mode) {
mSelectionHelp.SetMode(mode);
OnUpdateToolMode();
}
public int GetItemCountInPage() {
return mListLength;
}
public void ProcessOperation(OperationEnum oper) {
switch (oper) {
case OperationEnum.Set:
case OperationEnum.Unset: {
mFile.Set(mSelectionHelp.GetFieldRange(), mSelectionHelp.GetRange(), oper == OperationEnum.Set);
RefreshDisplay();
}
break;
case OperationEnum.Cut: {
// cut is a hybrid operation, first, do copy
// then delete selected item
// due to copy is not affect TASFile and only delete oper affect it
// so this is a revocable oper
var data = new LinkedList<FrameData>();
mFile.Copy(mSelectionHelp.GetRange(), data);
if (!ClipboardUtil.SetFrameData(data)) {
MessageBox.Show("Fail to cut due to unknow reason!");
break; // if fail to cut, do not delete selected items.
}
// do delete
mFile.Remove(mSelectionHelp.GetRange());
mSelectionHelp.Reset();
mSlider.UpdateRange(mFile);
RefreshDisplay();
}
break;
case OperationEnum.Copy: {
var data = new LinkedList<FrameData>();
mFile.Copy(mSelectionHelp.GetRange(), data);
if (!ClipboardUtil.SetFrameData(data))
MessageBox.Show("Fail to copy due to unknow reason!");
}
break;
case OperationEnum.PasteAfter:
case OperationEnum.PasteBefore: {
var data = new LinkedList<FrameData>();
if (ClipboardUtil.GetFrameData(data)) {
mFile.Insert(mSelectionHelp.GetPoint(), data, oper == OperationEnum.PasteBefore, mOverwrittenPaste);
mSelectionHelp.Reset();
mSlider.UpdateRange(mFile);
RefreshDisplay();
} else MessageBox.Show("Fail to paste due to unknow reason or blank clipboard!");
}
break;
case OperationEnum.Delete: {
mFile.Remove(mSelectionHelp.GetRange());
mSelectionHelp.Reset();
mSlider.UpdateRange(mFile);
RefreshDisplay();
}
break;
case OperationEnum.DeleteAfter:
case OperationEnum.DeleteBefore: {
var pos = mSelectionHelp.GetPoint();
if (oper == OperationEnum.DeleteBefore) pos -= 1; // delete after mean delete current selected item
if (pos < 0 || pos >= mFile.mFrameCount) return;
// delete before need shift selection
// delete before couldn't cause empty list, so we just need to directly shift
if (oper == OperationEnum.DeleteBefore)
mSelectionHelp.ShiftTo(false);
// also, if we use delete after and delete the tail of item list, we also need to shift pos(use `else if` to prevent double shift)
else if (oper == OperationEnum.DeleteAfter && pos == mFile.mFrameCount - 1) {
// but delete after may cause empty list error(delete the item within only 1 item list)
// so we need prevent this situation
if (mFile.mFrameCount == 1) mSelectionHelp.Reset(); //yes, reset selection to prevent error
else mSelectionHelp.ShiftTo(false); //no, shift selection.
}
// do real operation
mFile.Remove(new SelectionRange(pos, pos));
mSlider.UpdateRange(mFile);
RefreshDisplay();
}
break;
case OperationEnum.AddAfter:
case OperationEnum.AddBefore: {
if (!DialogUtil.AddItemDialog(out int count, out float deltaTime)) return;
var pos = mSelectionHelp.GetPoint();
mFile.Add(pos, count, deltaTime, oper == OperationEnum.AddBefore);
mSelectionHelp.Reset();
mSlider.UpdateRange(mFile);
RefreshDisplay();
}
break;
case OperationEnum.Undo: {
mFile.Undo();
mSelectionHelp.Reset();
mSlider.UpdateRange(mFile);
RefreshDisplay();
}
break;
case OperationEnum.Redo: {
mFile.Redo();
mSelectionHelp.Reset();
mSlider.UpdateRange(mFile);
RefreshDisplay();
}
break;
}
}
}
}

View File

@ -0,0 +1,52 @@
using BallanceTASEditor.Core.TASStruct;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace BallanceTASEditor.UI {
public static class Util {
public static void GridRowAdder(Grid container, GridLength size) {
var item = new RowDefinition();
item.Height = size;
container.RowDefinitions.Add(item);
}
public static void GridColumnAdder(Grid container, GridLength size) {
var item = new ColumnDefinition();
item.Width = size;
container.ColumnDefinitions.Add(item);
}
public static void SwapGridItemRC(UIElement item) {
var swap = Grid.GetColumn(item);
Grid.SetColumn(item, Grid.GetRow(item));
Grid.SetRow(item, swap);
}
}
public enum ToolMode {
Cursor,
Fill,
Overwrite
}
public struct CellPosition {
public CellPosition(int column, FrameDataField field) {
this.column = column;
this.field = field;
}
public int column;
public FrameDataField field;
}
}

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/></startup></configuration>

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="DotNetZip" version="1.9.1.8" targetFramework="net45" />
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net45" />
</packages>

View File

@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\MSTest.TestAdapter.2.2.5\build\net45\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.2.2.5\build\net45\MSTest.TestAdapter.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{EC35111D-6259-4C69-B7E4-F4E727AC0E1A}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>BallanceTASEditorTests</RootNamespace>
<AssemblyName>BallanceTASEditorTests</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.2.2.5\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.2.2.5\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
</Reference>
<Reference Include="System" />
</ItemGroup>
<Choose>
<When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
</ItemGroup>
</When>
<Otherwise />
</Choose>
<ItemGroup>
<Compile Include="Core\TASFileTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BallanceTASEditor\BallanceTASEditor.csproj">
<Project>{3127A635-B9E5-4C78-8414-0B9B196EC25E}</Project>
<Name>BallanceTASEditor</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
</ItemGroup>
</When>
</Choose>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.2.2.5\build\net45\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.2.2.5\build\net45\MSTest.TestAdapter.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.2.2.5\build\net45\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.2.2.5\build\net45\MSTest.TestAdapter.targets'))" />
</Target>
<Import Project="..\packages\MSTest.TestAdapter.2.2.5\build\net45\MSTest.TestAdapter.targets" Condition="Exists('..\packages\MSTest.TestAdapter.2.2.5\build\net45\MSTest.TestAdapter.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,171 @@
using BallanceTASEditor.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BallanceTASEditor.Core.TASStruct;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace BallanceTASEditor.Core.Tests {
[TestClass()]
public class TASFileTests {
[DataTestMethod]
[DataRow("1,2,3,4,5", 0, "15;0;15,15,15,4,5,", FrameDataField.Key_Up, FrameDataField.Key_Right, 0, 2, true)]
[DataRow("1,2,3,4,5", 0, "0;0;0,0,0,4,5,", FrameDataField.Key_Up, FrameDataField.Key_Right, 0, 2, false)]
[DataRow("1,2,3,4,5", 0, "14;0;14,13,12,4,5,", FrameDataField.Key_Up, FrameDataField.Key_Right, 0, 2, null)]
public void SetTest(string originStr, long prevShift, string expectedStr, FrameDataField field_start, FrameDataField field_end, long absoluteRange_start, long absoluteRange_end, bool? isSet) {
var test = new TASFile(dataGenerator(originStr));
test.Shift(prevShift);
var originalText = test.Output2TestString();
// test function
test.Set(new SelectionRange((long)field_start, (long)field_end), new SelectionRange(absoluteRange_start, absoluteRange_end), isSet);
var changedText = test.Output2TestString();
Assert.AreEqual(expectedStr, changedText);
// test undo
test.Undo();
Assert.AreEqual(originalText, test.Output2TestString());
// test redo
test.Redo();
Assert.AreEqual(changedText, test.Output2TestString());
// test undo
test.Undo();
Assert.AreEqual(originalText, test.Output2TestString());
}
[DataTestMethod]
[DataRow("1,2,3,4,5", 0, "3;0;3,4,5,", 0, 1)]
[DataRow("1,2,3,4,5", 0, "null;-1;", 0, 4)]
[DataRow("1,2,3,4,5", 0, "1;0;1,2,3,", 3, 4)]
[DataRow("1,2,3,4,5", 0, "2;0;2,3,4,5,", 0, 0)]
[DataRow("1,2,3,4,5", 0, "1;0;1,2,3,4,", 4, 4)]
[DataRow("1,2,3,4,5", 2, "1;0;1,5,", 1, 3)]
[DataRow("1,2,3,4,5", 2, "5;0;5,", 0, 3)]
[DataRow("1,2,3,4,5", 2, "1;0;1,", 1, 4)]
[DataRow("1,2,3,4,5", 3, "4;0;4,5,", 0, 2)]
[DataRow("1,2,3,4,5", 4, "5;1;4,5,", 0, 2)]
public void RemoveTest(string originStr, long prevShift, string expectedStr, long absoluteRange_start, long absoluteRange_end) {
var test = new TASFile(dataGenerator(originStr));
test.Shift(prevShift);
var originalText = test.Output2TestString();
// test function
test.Remove(new SelectionRange(absoluteRange_start, absoluteRange_end));
var changedText = test.Output2TestString();
Assert.AreEqual(expectedStr, changedText);
// test undo
test.Undo();
Assert.AreEqual(originalText, test.Output2TestString());
// test redo
test.Redo();
Assert.AreEqual(changedText, test.Output2TestString());
// test undo
test.Undo();
Assert.AreEqual(originalText, test.Output2TestString());
}
[DataTestMethod]
[DataRow("1,2,3,4,5", 0, "1;0;1,2,0,0,0,3,4,5,", 2, 3, 240 / 1f, true)]
[DataRow("1,2,3,4,5", 0, "1;0;1,2,3,0,0,0,4,5,", 2, 3, 240 / 1f, false)]
[DataRow("1,2,3,4,5", 2, "3;5;1,2,0,0,0,3,4,5,", 2, 3, 240 / 1f, true)]
[DataRow("1,2,3,4,5", 2, "3;2;1,2,3,0,0,0,4,5,", 2, 3, 240 / 1f, false)]
[DataRow("1,2,3,4,5", 0, "1;3;0,0,0,1,2,3,4,5,", 0, 3, 240 / 1f, true)]
[DataRow("1,2,3,4,5", 0, "1;0;1,2,3,4,5,0,0,0,", 4, 3, 240 / 1f, false)]
public void AddTest(string originStr, long prevShift, string expectedStr, long absolutePos, long count, float deltaTime, bool isAddBefore) {
var test = new TASFile(dataGenerator(originStr));
test.Shift(prevShift);
var originalText = test.Output2TestString();
// test function
test.Add(absolutePos, count, deltaTime, isAddBefore);
var changedText = test.Output2TestString();
Assert.AreEqual(expectedStr, changedText);
// test undo
test.Undo();
Assert.AreEqual(originalText, test.Output2TestString());
// test redo
test.Redo();
Assert.AreEqual(changedText, test.Output2TestString());
// test undo
test.Undo();
Assert.AreEqual(originalText, test.Output2TestString());
}
[DataTestMethod]
[DataRow("1,2,3,4,5", 0, "6;0;6,7,8,9,10,", 0, "6,7,8,9,10", false, true)]
[DataRow("1,2,3,4,5", 0, "6;0;6,7,8,9,10,", 4, "6,7,8,9,10", true, true)]
[DataRow("1,2,3,4,5", 0, "1;0;1,2,6,7,8,3,4,5,", 2, "6,7,8", true, false)]
[DataRow("1,2,3,4,5", 0, "1;0;1,2,3,6,7,8,4,5,", 2, "6,7,8", false, false)]
[DataRow("1,2,3,4,5", 0, "1;0;1,2,3,4,5,6,7,", 4, "6,7", false, false)]
[DataRow("1,2,3,4,5", 0, "1;2;6,7,1,2,3,4,5,", 0, "6,7", true, false)]
[DataRow("1,2,3,4,5", 0, "1;0;1,6,7,4,5,", 2, "6,7", true, true)]
[DataRow("1,2,3,4,5", 0, "1;0;1,2,6,7,5,", 2, "6,7", false, true)]
[DataRow("1,2,3,4,5", 0, "4;3;6,7,8,4,5,", 2, "6,7,8", true, true)]
[DataRow("1,2,3,4,5", 0, "1;0;1,2,6,7,8,", 2, "6,7,8", false, true)]
[DataRow("1,2,3,4,5", 0, "4;4;6,7,8,9,4,5,", 2, "6,7,8,9", true, true)]
[DataRow("1,2,3,4,5", 0, "1;0;1,2,6,7,8,9,", 2, "6,7,8,9", false, true)]
[DataRow("1,2,3,4,5", 2, "1;0;1,6,7,4,5,", 2, "6,7", true, true)]
[DataRow("1,2,3,4,5", 2, "2;1;1,2,6,7,5,", 2, "6,7", false, true)]
[DataRow("1,2,3,4,5", 2, "4;3;6,7,8,4,5,", 2, "6,7,8", true, true)]
[DataRow("1,2,3,4,5", 2, "2;1;1,2,6,7,8,", 2, "6,7,8", false, true)]
[DataRow("1,2,3,4,5", 2, "4;4;6,7,8,9,4,5,", 2, "6,7,8,9", true, true)]
[DataRow("1,2,3,4,5", 2, "2;1;1,2,6,7,8,9,", 2, "6,7,8,9", false, true)]
[DataRow("1,2,3,4,5", 4, "5;4;1,6,7,4,5,", 2, "6,7", true, true)]
[DataRow("1,2,3,4,5", 4, "5;4;1,2,6,7,5,", 2, "6,7", false, true)]
[DataRow("1,2,3,4,5", 4, "5;4;6,7,8,4,5,", 2, "6,7,8", true, true)]
[DataRow("1,2,3,4,5", 4, "2;1;1,2,6,7,8,", 2, "6,7,8", false, true)]
[DataRow("1,2,3,4,5", 4, "5;5;6,7,8,9,4,5,", 2, "6,7,8,9", true, true)]
[DataRow("1,2,3,4,5", 4, "2;1;1,2,6,7,8,9,", 2, "6,7,8,9", false, true)]
[DataRow("1,2,3,4,5", 2, "3;5;1,2,6,7,8,3,4,5,", 2, "6,7,8", true, false)]
[DataRow("1,2,3,4,5", 2, "3;2;1,2,3,6,7,8,4,5,", 2, "6,7,8", false, false)]
public void InsertTest(string originStr, long prevShift, string expectedStr, long absolutePos, string insertedData, bool isInsertBefore, bool isOverwritten) {
var test = new TASFile(dataGenerator(originStr));
test.Shift(prevShift);
var originalText = test.Output2TestString();
// test function
test.Insert(absolutePos, dataGenerator(insertedData), isInsertBefore, isOverwritten);
var changedText = test.Output2TestString();
Assert.AreEqual(expectedStr, changedText);
// test undo
test.Undo();
Assert.AreEqual(originalText, test.Output2TestString());
// test redo
test.Redo();
Assert.AreEqual(changedText, test.Output2TestString());
// test undo
test.Undo();
Assert.AreEqual(originalText, test.Output2TestString());
}
// example input: 1,2,3,4,5
private LinkedList<FrameData> dataGenerator(string inputData) {
var ls = new LinkedList<FrameData>();
foreach (var item in inputData.Split(',')) {
ls.AddLast(new FrameData(float.Parse(item), uint.Parse(item)));
}
return ls;
}
}
}

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// 有关程序集的一般信息由以下
// 控制。更改这些特性值可修改
// 与程序集关联的信息。
[assembly: AssemblyTitle("BallanceTASEditorTests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("BallanceTASEditorTests")]
[assembly: AssemblyCopyright("Copyright © 2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
//将 ComVisible 设置为 false 将使此程序集中的类型
//对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型,
//请将此类型的 ComVisible 特性设置为 true。
[assembly: ComVisible(false)]
// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
[assembly: Guid("ec35111d-6259-4c69-b7e4-f4e727ac0e1a")]
// 程序集的版本信息由下列四个值组成:
//
// 主版本
// 次版本
// 生成号
// 修订号
//
//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
//通过使用 "*",如下所示:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="MSTest.TestAdapter" version="2.2.5" targetFramework="net45" />
<package id="MSTest.TestFramework" version="2.2.5" targetFramework="net45" />
</packages>