add unit test and finish operator test

This commit is contained in:
2021-07-28 14:18:39 +08:00
parent d31704951d
commit ef55d94ae9
35 changed files with 510 additions and 136 deletions

View File

@ -0,0 +1,9 @@
<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>
</Application.Resources>
</Application>

View File

@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
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
}
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(is possible) to use to help fixing this problem.", "Ballance TAS Editor", MessageBoxButton.OK, MessageBoxImage.Error);
App.Current.Shutdown();
}
}
}

View File

@ -0,0 +1,130 @@
<?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>
<ItemGroup>
<Reference Include="Microsoft.VisualBasic" />
<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" />
<Reference Include="zlib.net, Version=1.0.3.0, Culture=neutral, PublicKeyToken=47d7877cb3620160">
<HintPath>..\packages\zlib.net.1.0.4.0\lib\zlib.net.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="Core\ClipboardUtil.cs" />
<Compile Include="Core\FileOperation.cs" />
<Compile Include="Core\KeyboardState.cs" />
<Compile Include="Core\LimitedStack.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.xaml.cs">
<DependentUpon>TASFlow.xaml</DependentUpon>
</Compile>
<Compile Include="UI\TASViewer.cs" />
<Compile Include="UI\Util.cs" />
<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>
<Page Include="UI\TASFlow.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>
<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,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,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,255 @@
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() {
if (mPointer == null) 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,55 @@
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);
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];
var mem = new MemoryStream();
file.Read(lengthTemp, 0, 4);
Int32 expectedLength = BitConverter.ToInt32(lengthTemp, 0);
long expectedCount = expectedLength / ConstValue.FRAMEDATA_SIZE;
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,145 @@
<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="Ballance TAS Editor" Height="500" Width="800" KeyUp="funcWindow_KeyUp"
input:InputMethod.IsInputMethodEnabled="False">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Menu Grid.Row="0">
<MenuItem Header="File">
<MenuItem x:Name="uiMenu_File_Open" Header="Open" Click="funcMenu_File_Open"/>
<MenuItem x:Name="uiMenu_File_Save" Header="Save" Click="funcMenu_File_Save"/>
<MenuItem x:Name="uiMenu_File_SaveAs" Header="Save As..." Click="funcMenu_File_SaveAs"/>
<MenuItem x:Name="uiMenu_File_Close" Header="Close" Click="funcMenu_File_Close"/>
</MenuItem>
<MenuItem Header="Display">
<MenuItem x:Name="uiMenu_Display_Undo" Header="Undo" Click="funcMenu_Display_Undo"/>
<MenuItem x:Name="uiMenu_Display_Redo" Header="Redo" Click="funcMenu_Display_Redo"/>
<MenuItem x:Name="uiMenu_Display_ItemCount" Header="Item Count" Click="funcMenu_Display_ItemCount"/>
<MenuItem x:Name="uiMenu_Display_OverwrittenPaste" Header="Overwritten Paste" IsCheckable="True" Checked="funcMenu_Display_OverwrittenPaste" Unchecked="funcMenu_Display_OverwrittenPaste"/>
</MenuItem>
<MenuItem Header="Help">
<MenuItem x:Name="uiMenu_Help_ReportBugs" Header="Report bugs" Click="funcMenu_Help_ReportBugs"/>
<MenuItem x:Name="uiMenu_Help_About" Header="About" Click="funcMenu_Help_About"/>
</MenuItem>
</Menu>
<TextBlock x:Name="uiEditorNote" Grid.Row="1" Text="Open a TAS file for editing" Opacity="0.5" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="16"/>
<Grid x:Name="uiEditorPanel" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
</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="Select mode" 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="Fill mode" 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="Draw mode" VerticalAlignment="Center"/>
</StackPanel>
</Button>
</StackPanel>
<controls:TASFlow x:Name="uiTASData" Grid.Row="1" Margin="5" BorderThickness="1" BorderBrush="Gray">
</controls:TASFlow>
<Grid Grid.Row="2" 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">
<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">
<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">
<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">
<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="5,0,0,0" Grid.Column="4" SmallChange="1" LargeChange="10" Maximum="1" VerticalAlignment="Center">
</Slider>
</Grid>
</Grid>
<StatusBar x:Name="uiStatusbar" Grid.Row="2">
<TextBlock x:Name="uiStatusbar_Mode_Cursor" Text="Select mode"/>
<TextBlock x:Name="uiStatusbar_Mode_Fill" Text="Fill mode"/>
<TextBlock x:Name="uiStatusbar_Mode_Overwrite" Text="Draw mode"/>
<Separator/>
<TextBlock Text="Selected: "/>
<TextBlock x:Name="uiStatusbar_Selected" Text="-"/>
</StatusBar>
</Grid>
</Window>

View File

@ -0,0 +1,220 @@
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();
RefreshUI(false);
}
TASFile mFile;
TASViewer mViewer;
#region ui func
// =========================== 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("Under MIT License\nVersion: 1.0 alpha\nyyc12345.", "Ballance TAS Editor");
}
private void funcMenu_File_Open(object sender, RoutedEventArgs e) {
var file = DialogUtil.OpenFileDialog();
if (file == "") return;
mFile = new TASFile(file);
mViewer = new TASViewer(mFile, uiTASSlider, uiTASData, uiStatusbar_Selected);
RefreshUI(true);
ChangeToolMode(ToolMode.Cursor);
mViewer.ChangeOverwrittenMode(uiMenu_Display_OverwrittenPaste.IsChecked);
}
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("Do you want to close this TAS file?")) return;
mViewer.Dispose();
mFile = null;
mViewer = null;
RefreshUI(false);
}
private void funcMenu_Display_ItemCount(object sender, RoutedEventArgs e) {
int newvalue = 0;
if (DialogUtil.InputNumber("Input new count (>=5 && <=30)", 5, 30, ref newvalue)) {
mViewer.ChangeListLength(newvalue);
}
}
private void funcMenu_Display_OverwrittenPaste(object sender, RoutedEventArgs e) {
//uiMenu_Display_OverwrittenPaste.IsChecked = !uiMenu_Display_OverwrittenPaste.IsChecked;
if (mViewer != null)
mViewer.ChangeOverwrittenMode(uiMenu_Display_OverwrittenPaste.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);
}
// =========================== btn
private void funcBtn_Cursor(object sender, RoutedEventArgs e) {
ChangeToolMode(ToolMode.Cursor);
}
private void funcBtn_Fill(object sender, RoutedEventArgs e) {
ChangeToolMode(ToolMode.Fill);
}
private void funcBtn_Overwrite(object sender, RoutedEventArgs e) {
ChangeToolMode(ToolMode.Overwrite);
}
// move btn
private void funcBtn_FastMovePrev(object sender, RoutedEventArgs e) {
MoveSliderManually(true, true);
}
private void funcBtn_MovePrev(object sender, RoutedEventArgs e) {
MoveSliderManually(true, false);
}
private void funcBtn_MoveNext(object sender, RoutedEventArgs e) {
MoveSliderManually(false, false);
}
private void funcBtn_FastMoveNext(object sender, RoutedEventArgs e) {
MoveSliderManually(false, true);
}
// move keyboard
private void funcWindow_KeyUp(object sender, KeyEventArgs e) {
if (mFile == null || mViewer == null) return;
switch(e.Key) {
case Key.A:
MoveSliderManually(true, true);
break;
case Key.S:
MoveSliderManually(true, false);
break;
case Key.D:
MoveSliderManually(false, false);
break;
case Key.F:
MoveSliderManually(false, true);
break;
}
}
#endregion
private void ChangeToolMode(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;
}
mViewer.ChangeToolMode(mode);
}
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_Undo.IsEnabled = true;
uiMenu_Display_Redo.IsEnabled = true;
uiStatusbar.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_Undo.IsEnabled = false;
uiMenu_Display_Redo.IsEnabled = false;
uiStatusbar.Visibility = Visibility.Collapsed;
}
}
private void MoveSliderManually(bool isPrev, bool isFast) {
var step = isFast ? mViewer.GetItemCountInPage() : 1;
uiTASSlider.Value = Util.Clamp(uiTASSlider.Value.ToInt32() + (isPrev ? -1 : 1) * step, uiTASSlider.Minimum.ToInt32(), uiTASSlider.Maximum.ToInt32());
}
}
}

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("BallanceTASEditor")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("BallanceTASEditor")]
[assembly: AssemblyCopyright("Copyright © 2021")]
[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.0.0.0")]
[assembly: AssemblyFileVersion("1.0.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="Add Item" Height="200" Width="400" WindowStyle="ToolWindow" WindowStartupLocation="CenterOwner">
<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="Count" VerticalAlignment="Center"/>
<TextBlock Margin="5" Grid.Column="0" Grid.Row="1" Text="FPS" VerticalAlignment="Center"/>
<TextBlock Margin="5" Grid.Column="0" Grid.Row="2" Text="Delta Time" 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="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="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,59 @@
using Microsoft.VisualBasic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
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 = "TAS file(*.tas)|*.tas|All file(*.*)|*.*";
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 = "TAS file(*.tas)|*.tas|All file(*.*)|*.*";
if (!(bool)op.ShowDialog()) return "";
return op.FileName;
}
public static bool ConfirmDialog(string str) {
var result = MessageBox.Show(str, "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, "Input number", "");
if (dialog == "") return false;
if (int.TryParse(dialog, out result)) {
if (result <= max && result >= min) break;
}
MessageBox.Show("Invalid number. Please input again", "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,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BallanceTASEditor.UI {
public enum OperationEnum {
Set,
Unset,
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,59 @@
<UserControl x:Class="BallanceTASEditor.UI.TASFlow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:BallanceTASEditor.UI"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="800">
<Grid x:Name="uiCoreWindow" Background="#ffffff">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem x:Name="uiDataMenu_Set" Header="Set" Click="uiDataMenu_Set_Click"/>
<MenuItem x:Name="uiDataMenu_Unset" Header="Unset" Click="uiDataMenu_Unset_Click"/>
<Separator/>
<MenuItem x:Name="uiDataMenu_Copy" Header="Copy" Click="uiDataMenu_Copy_Click"/>
<MenuItem x:Name="uiDataMenu_PasteAfter" Header="Paste after this" Click="uiDataMenu_PasteAfter_Click"/>
<MenuItem x:Name="uiDataMenu_PasteBefore" Header="Paste before this" Click="uiDataMenu_PasteBefore_Click"/>
<Separator/>
<MenuItem x:Name="uiDataMenu_Delete" Header="Delete" Click="uiDataMenu_Delete_Click"/>
<MenuItem x:Name="uiDataMenu_DeleteAfter" Header="Delete next frame" Click="uiDataMenu_DeleteAfter_Click"/>
<MenuItem x:Name="uiDataMenu_DeleteBefore" Header="Delete last frame" Click="uiDataMenu_DeleteBefore_Click"/>
<Separator/>
<MenuItem x:Name="uiDataMenu_AddAfter" Header="Add blank item after this" Click="uiDataMenu_AddAfter_Click"/>
<MenuItem x:Name="uiDataMenu_AddBefore" Header="Add blank item before this" Click="uiDataMenu_AddBefore_Click"/>
</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 Padding="2" Background="#afafaf" Grid.Column="0" Grid.Row="1" Text="Frame"/>
<TextBlock Padding="2" Background="#afafaf" Grid.Column="0" Grid.Row="2" Text="Delta Time"/>
<TextBlock Padding="2" Background="#afafaf" Grid.Column="0" Grid.Row="3" Text="^"/>
<TextBlock Padding="2" Background="#afafaf" Grid.Column="0" Grid.Row="4" Text="v"/>
<TextBlock Padding="2" Background="#afafaf" Grid.Column="0" Grid.Row="5" Text="&lt;"/>
<TextBlock Padding="2" Background="#afafaf" Grid.Column="0" Grid.Row="6" Text="&gt;"/>
<TextBlock Padding="2" Background="#afafaf" Grid.Column="0" Grid.Row="7" Text="shift"/>
<TextBlock Padding="2" Background="#afafaf" Grid.Column="0" Grid.Row="8" Text="space"/>
<TextBlock Padding="2" Background="#afafaf" Grid.Column="0" Grid.Row="9" Text="q"/>
<TextBlock Padding="2" Background="#afafaf" Grid.Column="0" Grid.Row="10" Text="esc"/>
<TextBlock Padding="2" Background="#afafaf" Grid.Column="0" Grid.Row="11" Text="enter"/>
</Grid>
</UserControl>

View File

@ -0,0 +1,349 @@
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 {
/// <summary>
/// TASFlow.xaml 的交互逻辑
/// </summary>
public partial class TASFlow : UserControl {
public TASFlow() {
InitializeComponent();
mItemList = new List<TASFlowUIItem>();
mRectMap = new Dictionary<Rectangle, CellPosition>();
mItemCount = 0;
SetItemCount(1);
}
public event Action Click;
public event Action<OperationEnum> NewOperation;
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 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++) {
var item = new ColumnDefinition();
item.Width = GridLength.Auto;
uiCoreWindow.ColumnDefinitions.Add(item);
}
} else {
uiCoreWindow.ColumnDefinitions.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); // 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 RefreshDataMenu() {
if (SelectionHelp == null) return;
ToolMode mode = SelectionHelp.GetToolMode();
bool showCursorPasteAddDeleteOne = mode == ToolMode.Cursor && SelectionHelp.IsDataPartialReady();
bool showFill = mode == ToolMode.Fill && SelectionHelp.IsDataReady();
bool showCursorCopyDelete = mode == ToolMode.Cursor && SelectionHelp.IsDataReady();
uiDataMenu_Set.IsEnabled = showFill;
uiDataMenu_Unset.IsEnabled = showFill;
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;
}
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);
}
}
// note main window to process it.
OnClick();
}
// only raised in overwrite mode
private void OnClick() {
if (SelectionHelp.GetToolMode() == ToolMode.Overwrite)
Click?.Invoke();
}
#region menu operation
private void uiDataMenu_Set_Click(object sender, RoutedEventArgs e) {
NewOperation?.Invoke(OperationEnum.Set);
}
private void uiDataMenu_Unset_Click(object sender, RoutedEventArgs e) {
NewOperation?.Invoke(OperationEnum.Unset);
}
private void uiDataMenu_Copy_Click(object sender, RoutedEventArgs e) {
NewOperation?.Invoke(OperationEnum.Copy);
}
private void uiDataMenu_PasteAfter_Click(object sender, RoutedEventArgs e) {
NewOperation?.Invoke(OperationEnum.PasteAfter);
}
private void uiDataMenu_PasteBefore_Click(object sender, RoutedEventArgs e) {
NewOperation?.Invoke(OperationEnum.PasteBefore);
}
private void uiDataMenu_Delete_Click(object sender, RoutedEventArgs e) {
NewOperation?.Invoke(OperationEnum.Delete);
}
private void uiDataMenu_DeleteAfter_Click(object sender, RoutedEventArgs e) {
NewOperation?.Invoke(OperationEnum.DeleteAfter);
}
private void uiDataMenu_DeleteBefore_Click(object sender, RoutedEventArgs e) {
NewOperation?.Invoke(OperationEnum.DeleteBefore);
}
private void uiDataMenu_AddAfter_Click(object sender, RoutedEventArgs e) {
NewOperation?.Invoke(OperationEnum.AddAfter);
}
private void uiDataMenu_AddBefore_Click(object sender, RoutedEventArgs e) {
NewOperation?.Invoke(OperationEnum.AddBefore);
}
#endregion
}
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 int KEY_COUNT = 9;
private const double SELECTION_HEADER_HEIGHT = 10.0f;
public TASFlowUIItem(int column) {
// basic item
sel_rect = new Rectangle();
frame = new TextBlock();
deltaTime = new TextBlock();
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);
sel_rect.Margin = RECT_MARGIN;
frame.Margin = DEFAULT_MARGIN;
deltaTime.Margin = DEFAULT_MARGIN;
sel_rect.StrokeThickness = 2;
sel_rect.Stroke = SEL_RECT_STROKE;
sel_rect.Height = 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);
Grid.SetRow(keystates[i], 3 + i);
Grid.SetColumn(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 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,259 @@
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, Slider slider, TASFlow datagrid, TextBlock statusbar) {
mFile = file;
mSlider = slider;
mDataGrid = datagrid;
mStatusbar = statusbar;
// restore slider
mSlider.Minimum = 0;
updateSliderRange();
// 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;
mDataGrid.NewOperation += funcDataMenu_NewOperation;
mSlider.ValueChanged += sliderValueChanged;
// display data
ChangeListLength(DATA_LIST_LENGTH);
}
public void Dispose() {
mDataGrid.DataSources = null;
mDataGrid.Click -= funcDataMenu_Click;
mDataGrid.NewOperation -= funcDataMenu_NewOperation;
mSlider.ValueChanged -= sliderValueChanged;
}
const int DATA_LIST_LENGTH = 15;
FrameData INVALID_FRAME_DATA;
TASFile mFile;
Slider mSlider;
TextBlock mStatusbar;
TASFlow mDataGrid;
SelectionHelp mSelectionHelp;
int mListLength;
List<FrameDataDisplay> mDataSource;
bool mOverwrittenPaste;
private void sliderValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {
long pos = e.NewValue.ToInt64();
mFile.Shift(pos);
RefreshDisplay();
}
private void updateSliderRange() {
long newSize = mFile.mFrameCount - 1;
if (mSlider.Value > newSize)
mSlider.Value = newSize;
mSlider.Maximum = newSize;
}
private void funcSelectionHelp_SelectionChanged() {
mDataGrid.RefreshDataMenu();
mDataGrid.RefreshSelectionHighlight();
OnStatusbarSelectionChanged();
}
private void OnStatusbarSelectionChanged() {
var mode = mSelectionHelp.GetToolMode();
switch (mode) {
case ToolMode.Cursor:
if (mSelectionHelp.IsDataReady()) {
var data = mSelectionHelp.GetRange();
mStatusbar.Text = $"{data.start} - {data.end}";
} else if (mSelectionHelp.IsDataPartialReady()) {
var data2 = mSelectionHelp.GetPoint();
mStatusbar.Text = data2.ToString();
} else mStatusbar.Text = "-";
break;
case ToolMode.Fill:
if (mSelectionHelp.IsDataReady()) {
var data3 = mSelectionHelp.GetRange();
mStatusbar.Text = $"{data3.start} - {data3.end}";
} else mStatusbar.Text = "-";
break;
case ToolMode.Overwrite:
if (mSelectionHelp.IsDataReady()) {
var data4 = mSelectionHelp.GetPoint();
mStatusbar.Text = data4.ToString();
} else mStatusbar.Text = "-";
break;
}
}
public void ChangeOverwrittenMode(bool isOverwritten) {
mOverwrittenPaste = isOverwritten;
}
public void ChangeListLength(int newLen) {
if (newLen < 5 || newLen > 30) 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);
}
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.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();
updateSliderRange();
RefreshDisplay();
} else MessageBox.Show("Fail to paste due to unknow reason or blank clipboard!");
}
break;
case OperationEnum.Delete: {
mFile.Remove(mSelectionHelp.GetRange());
mSelectionHelp.Reset();
updateSliderRange();
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;
// only 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) {
// 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));
updateSliderRange();
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();
updateSliderRange();
RefreshDisplay();
}
break;
case OperationEnum.Undo: {
mFile.Undo();
mSelectionHelp.Reset();
updateSliderRange();
RefreshDisplay();
}
break;
case OperationEnum.Redo: {
mFile.Redo();
mSelectionHelp.Reset();
updateSliderRange();
RefreshDisplay();
}
break;
}
}
#region data menu
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();
}
private void funcDataMenu_NewOperation(OperationEnum obj) {
ProcessOperation(obj);
}
#endregion
}
}

View File

@ -0,0 +1,24 @@
using BallanceTASEditor.Core.TASStruct;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BallanceTASEditor.UI {
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>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="zlib.net" version="1.0.4.0" targetFramework="net40" />
</packages>