refactor: migrate project and start refactor

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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