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,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;
}
}