1
0

8 Commits

20 changed files with 1066 additions and 136 deletions

View File

@@ -3,7 +3,7 @@
viewBox="0 0 24 24" viewBox="0 0 24 24"
version="1.1" version="1.1"
id="svg1" id="svg1"
sodipodi:docname="Cancel.svg" sodipodi:docname="FieldError.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)" inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
@@ -31,11 +31,15 @@
inkscape:window-maximized="1" inkscape:window-maximized="1"
inkscape:current-layer="svg1" /> inkscape:current-layer="svg1" />
<path <path
d="M 12,2 C 17.5,2 22,6.5 22,12 22,17.5 17.5,22 12,22 6.5,22 2,17.5 2,12 2,6.5 6.5,2 12,2 m 0,2 C 10.1,4 8.4,4.6 7.1,5.7 L 18.3,16.9 C 19.3,15.5 20,13.8 20,12 20,7.6 16.4,4 12,4 M 16.9,18.3 5.7,7.1 C 4.6,8.4 4,10.1 4,12 c 0,4.4 3.6,8 8,8 1.9,0 3.6,-0.6 4.9,-1.7 z" d="M 12,20 C 7.59,20 4,16.41 4,12 4,7.59 7.59,4 12,4 c 4.41,0 8,3.59 8,8 0,4.41 -3.59,8 -8,8 M 12,2 C 6.47,2 2,6.47 2,12 2,17.53 6.47,22 12,22 17.53,22 22,17.53 22,12 22,6.47 17.53,2 12,2 M 14.59,8 12,10.59 9.41,8 8,9.41 10.59,12 8,14.59 9.41,16 12,13.41 14.59,16 16,14.59 13.41,12 16,9.41 Z"
id="path1" id="path1"
style="display:none" /> style="display:none" />
<path <path
style="fill:#f44336;fill-opacity:1;stroke-width:0.000911392" style="fill:#f44336;stroke-width:0.000911392;fill-opacity:1"
d="M 11.529114,21.976736 C 10.22238,21.889141 9.0970988,21.622033 8.0114321,21.141743 5.0411839,19.827731 2.8549818,17.127924 2.2192797,13.988839 2.0648176,13.226108 2.0373265,12.925617 2.0373265,12 c 0,-0.925617 0.027491,-1.226108 0.1819532,-1.988839 C 2.9672398,6.3177478 5.8643146,3.2688861 9.5104706,2.3379617 10.411349,2.1079524 11.074436,2.0255288 12.01519,2.026618 c 1.996985,0.00231 3.807406,0.5499071 5.467923,1.6538737 2.455605,1.6325675 4.010624,4.1204917 4.426934,7.0827903 0.07357,0.523513 0.07357,1.949923 0,2.473436 -0.326722,2.324832 -1.359557,4.372036 -2.997659,5.94173 -1.596575,1.5299 -3.611736,2.485613 -5.773541,2.738166 -0.389906,0.04555 -1.312862,0.08002 -1.609733,0.06012 z m 1.563357,-2.032544 c 0.925767,-0.122293 1.765668,-0.379382 2.5417,-0.778003 0.341195,-0.175259 0.942023,-0.568381 1.150639,-0.752863 l 0.136709,-0.120893 -5.605032,-5.606976 C 8.2337189,9.6016202 5.704605,7.0785217 5.6962335,7.0785715 5.6878621,7.0786214 5.5669726,7.2324188 5.4275903,7.4203437 4.7111807,8.3862562 4.2640759,9.494702 4.0680851,10.790778 c -0.065181,0.431036 -0.090079,1.53472 -0.044835,1.987446 0.2406062,2.407596 1.546148,4.562332 3.5868762,5.919974 0.4303689,0.286313 1.2614491,0.698786 1.7409864,0.864068 0.6315864,0.217689 1.3326383,0.369549 1.9653433,0.425729 0.425722,0.0378 1.326427,0.01559 1.776015,-0.0438 z m 5.537897,-3.505461 c 0.686116,-1.101759 1.081879,-2.107709 1.286142,-3.269111 0.0819,-0.465656 0.111841,-1.431584 0.06035,-1.946773 C 19.735881,8.811741 18.433266,6.6619006 16.388891,5.3012482 15.724197,4.8588544 14.767203,4.4347242 13.990518,4.2383123 12.090514,3.75783 10.037364,3.9752015 8.3658289,4.8338107 8.0246342,5.0090703 7.4238064,5.4021922 7.2151899,5.5866738 L 7.078481,5.707567 l 5.605063,5.608114 5.605064,5.608114 0.07595,-0.09009 c 0.04177,-0.04955 0.161387,-0.227288 0.265811,-0.394971 z" d="M 11.498734,21.97371 C 10.538494,21.927417 9.4258037,21.695256 8.4759494,21.34301 8.0845522,21.197863 7.2207324,20.778082 6.8506329,20.553172 6.0186267,20.047561 5.2728592,19.437005 4.6411761,18.744304 2.5685315,16.471448 1.6637184,13.447187 2.1463612,10.405597 2.5810326,7.6663212 4.1740918,5.2042016 6.5063119,3.6671662 7.8220107,2.8000629 9.2792591,2.2701711 10.875949,2.0782573 c 0.574874,-0.069097 1.673228,-0.069097 2.248102,0 3.266567,0.3926243 6.042908,2.2588082 7.635954,5.13269 C 21.54949,8.63519 21.979539,10.323974 21.979539,12 c 0,3.054298 -1.366007,5.885584 -3.773974,7.822213 -0.621166,0.499579 -1.583784,1.066107 -2.374062,1.397204 -1.3282,0.556467 -2.869699,0.824827 -4.332769,0.754293 z m 1.112044,-1.98475 c 3.561075,-0.284458 6.493751,-2.850098 7.226887,-6.322415 C 20.26315,11.651338 19.880556,9.4662 18.803847,7.7620253 17.492724,5.6868267 15.384677,4.3473261 12.941772,4.0371303 12.549756,3.9873527 11.449156,3.9874453 11.058228,4.0372887 9.198254,4.2744355 7.5140166,5.1092443 6.254174,6.4184649 4.9923609,7.7297335 4.2458673,9.3098233 4.0364135,11.112759 c -0.059996,0.516438 -0.042475,1.588281 0.033872,2.072051 0.2243066,1.421314 0.78997,2.713286 1.6759437,3.827848 0.2493782,0.31372 0.8842563,0.953423 1.195543,1.204629 1.6091067,1.298537 3.6413238,1.933644 5.6690058,1.771673 z"
id="path2" /> id="path2" />
<path
style="fill:#f44336;fill-opacity:1;stroke-width:0.000911392"
d="M 8.7189889,15.281014 8.0206349,14.582165 9.3114492,13.291082 10.602264,12 9.3114492,10.708918 8.0206349,9.4178352 8.7189889,8.7189858 9.4173428,8.0201364 10.708804,9.3113356 12.000265,10.602535 13.291164,9.3113678 14.582063,8.0202008 15.280797,8.718935 15.979531,9.4176691 14.688634,10.708835 13.397736,12 l 1.290898,1.291165 1.290897,1.291166 -0.698734,0.698734 -0.698734,0.698734 -1.290899,-1.291167 -1.290899,-1.291167 -1.291461,1.291199 -1.2914612,1.291199 z"
id="path3" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 24 24"
version="1.1"
id="svg1"
sodipodi:docname="SequenceLayout.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="32.916667"
inkscape:cx="12"
inkscape:cy="12"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
d="m 15,7 v 9 H 10 V 7 h 5 m 6,-2 h -3 v 13 h 3 V 5 M 17,5 H 8 v 13 h 9 V 5 M 7,5 H 4 v 13 h 3 z"
id="path1"
style="display:none" />
<path
style="fill:#607d8b;fill-opacity:1;stroke-width:0.000911392"
d="M 4.0253165,11.498734 V 5.0126582 H 5.4987342 6.9721519 V 11.498734 17.98481 H 5.4987342 4.0253165 Z"
id="path2" />
<path
style="fill:#607d8b;fill-opacity:1;stroke-width:0.000911392"
d="M 8.0050633,11.498734 V 5.0126582 h 4.4962027 4.496202 V 11.498734 17.98481 H 12.501266 8.0050633 Z m 7.0177217,0 V 6.9873418 H 12.501266 9.9797468 v 4.5113922 4.511393 h 2.5215192 2.521519 z"
id="path3" />
<path
style="fill:#607d8b;fill-opacity:1;stroke-width:0.000911392"
d="M 18,11.498734 V 5.0126582 h 1.488608 1.488607 V 11.498734 17.98481 H 19.488608 18 Z"
id="path4" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -15,13 +15,9 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="CommunityToolkit.HighPerformance" Version="8.4.0" /> <PackageReference Include="CommunityToolkit.HighPerformance" Version="8.4.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="OneOf" Version="3.0.271" /> <PackageReference Include="OneOf" Version="3.0.271" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="10.0.5" />
</ItemGroup>
<ItemGroup>
<Folder Include="Frontend\Models\" />
</ItemGroup> </ItemGroup>
</Project> </Project>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View File

@@ -60,4 +60,29 @@ namespace BallanceTasEditor.Frontend.Converters {
} }
} }
[ValueConversion(typeof(bool), typeof(Brush))]
public class HeaderIsSelectedToColorConverter : IValueConverter {
public static readonly HeaderIsSelectedToColorConverter Instance = new HeaderIsSelectedToColorConverter();
private static readonly SolidColorBrush SELECTED_BRUSH = new(Color.FromRgb(30, 144, 255));
private static readonly SolidColorBrush DESELECTED_BRUSH = new(Color.FromRgb(255, 255, 255));
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var isSelected = value as bool?;
if (isSelected is null) {
return Binding.DoNothing;
} else {
if (isSelected.Value) {
return SELECTED_BRUSH;
} else {
return DESELECTED_BRUSH;
}
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
return Binding.DoNothing;
}
}
} }

View File

@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
namespace BallanceTasEditor.Frontend.Converters {
internal class GenericEnumRadioButtonConverter<T> where T: struct {
public object Convert(object value, object parameter) {
var susValue = value as T?;
var susParam = parameter as T?;
if (susValue is null || susParam is null) {
return Binding.DoNothing;
} else {
return susValue.Value.Equals(susParam.Value);
}
}
public object ConvertBack(object value, object parameter) {
var susValue = value as bool?;
var susParam = parameter as T?;
if (susValue is null || susParam is null) {
return Binding.DoNothing;
} else {
return susValue.Value ? susParam.Value : Binding.DoNothing;
}
}
}
[ValueConversion(typeof(Shared.SequenceKind), typeof(bool))]
public class SequenceKindRadioButtonConverter : IValueConverter {
public static readonly SequenceKindRadioButtonConverter Instance = new();
private readonly GenericEnumRadioButtonConverter<Shared.SequenceKind> m_Inner = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
return m_Inner.Convert(value, parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
return m_Inner.ConvertBack(value, parameter);
}
}
[ValueConversion(typeof(Shared.EditorLayoutKind), typeof(bool))]
public class EditorLayoutKindRadioButtonConverter : IValueConverter {
public static readonly EditorLayoutKindRadioButtonConverter Instance = new();
private readonly GenericEnumRadioButtonConverter<Shared.EditorLayoutKind> m_Inner = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
return m_Inner.Convert(value, parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
return m_Inner.ConvertBack(value, parameter);
}
}
[ValueConversion(typeof(Shared.EditorPasteBehavior), typeof(bool))]
public class EditorPasteBehaviorRadioButtonConverter : IValueConverter {
public static readonly EditorPasteBehaviorRadioButtonConverter Instance = new();
private readonly GenericEnumRadioButtonConverter<Shared.EditorPasteBehavior> m_Inner = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
return m_Inner.Convert(value, parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
return m_Inner.ConvertBack(value, parameter);
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
namespace BallanceTasEditor.Frontend.Converters {
[ValueConversion(typeof(bool), typeof(Visibility))]
public class IsVisibleToVisibilityConverter : IValueConverter {
public static readonly IsVisibleToVisibilityConverter Instance = new IsVisibleToVisibilityConverter();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var susBool = value as bool?;
if (susBool is null) {
return Binding.DoNothing;
} else {
return susBool.Value ? Visibility.Visible : Visibility.Collapsed;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
return Binding.DoNothing;
}
}
}

View File

@@ -0,0 +1,67 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BallanceTasEditor.Frontend.Models {
public partial class EditorSetting : ObservableObject {
public EditorSetting() {
// YYC MARK:
// Due to the shitty conflict between CommunityToolkit.Mvvm and Nullable aware,
// I was forcely set this field to clear all warning.
GamePath = "";
// Initialize from singleton.
FromSingleton();
}
[ObservableProperty]
private Shared.SequenceKind sequenceKind;
[ObservableProperty]
private Shared.EditorLayoutKind editorLayout;
[ObservableProperty]
private Shared.EditorPasteBehavior pasteBehavior;
[ObservableProperty]
private int frameCount;
[ObservableProperty]
private string gamePath;
[MemberNotNull(nameof(SequenceKind))]
[MemberNotNull(nameof(EditorLayout))]
[MemberNotNull(nameof(PasteBehavior))]
[MemberNotNull(nameof(FrameCount))]
[MemberNotNull(nameof(GamePath))]
private void FromSingleton() {
var singleton = Shared.EditorConfiguration.Instance;
SequenceKind = singleton.SequenceKind;
EditorLayout = singleton.EditorLayout;
PasteBehavior = singleton.PasteBehavior;
FrameCount = singleton.FrameCount;
GamePath = singleton.GamePath;
}
private void ToSingleton() {
var singleton = Shared.EditorConfiguration.Instance;
singleton.SequenceKind = SequenceKind;
singleton.EditorLayout = EditorLayout;
singleton.PasteBehavior = PasteBehavior;
singleton.FrameCount = FrameCount;
singleton.GamePath = GamePath;
}
public void Save() {
ToSingleton();
Shared.EditorConfiguration.Instance.Save();
}
}
}

View File

@@ -0,0 +1,84 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BallanceTasEditor.Frontend.Models {
public partial class TasFile : ObservableObject {
public TasFile() {
FileBody = null;
}
#region File Load and Save
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsFileLoaded))]
[NotifyPropertyChangedFor(nameof(IsFileNotLoaded))]
private Backend.ITasSequence? fileBody;
[MemberNotNullWhen(true, nameof(FileBody))]
public bool IsFileLoaded {
get => FileBody is not null;
}
[MemberNotNullWhen(false, nameof(FileBody))]
public bool IsFileNotLoaded {
get => FileBody is null;
}
public void NewFile(Shared.SequenceKind kind, int count, uint fps) {
// Check status
if (IsFileLoaded) {
throw new InvalidOperationException();
}
// Initialize sequence
var seq = Shared.SequenceKindHelper.CreateSequenceByKind(kind);
// Initialize items
Backend.TasStorage.Init(seq, count, fps);
// Set members
FileBody = seq;
}
public void LoadFile(Shared.SequenceKind kind, string path) {
// Check status
if (IsFileLoaded) {
throw new InvalidOperationException();
}
// Initialize sequence
var seq = Shared.SequenceKindHelper.CreateSequenceByKind(kind);
// Load into sequence
Backend.TasStorage.Load(path, seq);
// Set members
FileBody = seq;
}
public void SaveFile(string path) {
// Check status
if (IsFileNotLoaded) {
throw new InvalidOperationException();
}
// Save sequence
Backend.TasStorage.Save(path, FileBody);
}
public void CloseFile() {
// Check status
if (IsFileNotLoaded) {
throw new InvalidOperationException();
}
// Set member
FileBody = null;
}
#endregion
}
}

View File

@@ -17,7 +17,9 @@ namespace BallanceTasEditor.Frontend.Shared {
bool ShowConfirmExitWhenOpeningFileDialog(); bool ShowConfirmExitWhenOpeningFileDialog();
bool ShowFileChangedDialog(); bool ShowFileChangedDialog();
GotoDialogResult? ShowGotoDialog(); GotoDialogResult? ShowGotoDialog();
bool ShowConfirmClearKeysDialog();
EditFpsDialogResult? ShowEditFpsDialog(); EditFpsDialogResult? ShowEditFpsDialog();
bool ShowConfirmUniformFpsDialog();
AddFrameDialogResult? ShowAddFrameDialog(); AddFrameDialogResult? ShowAddFrameDialog();
PreferenceDialogResult? ShowPreferenceDialog(); PreferenceDialogResult? ShowPreferenceDialog();
void ShowManuallyReportBugDialog(); void ShowManuallyReportBugDialog();
@@ -133,6 +135,14 @@ namespace BallanceTasEditor.Frontend.Shared {
} }
} }
public bool ShowConfirmClearKeysDialog() {
var rv = MessageBox.Show(
"Do you really want to clear keys for all frames?\nThis operation can not be revoked.",
"Clear Keys",
MessageBoxButton.YesNo, MessageBoxImage.Question);
return rv == MessageBoxResult.Yes;
}
public EditFpsDialogResult? ShowEditFpsDialog() { public EditFpsDialogResult? ShowEditFpsDialog() {
var dialog = new Views.EditFpsDialog(); var dialog = new Views.EditFpsDialog();
dialog.Owner = m_Parent; dialog.Owner = m_Parent;
@@ -144,6 +154,14 @@ namespace BallanceTasEditor.Frontend.Shared {
} }
} }
public bool ShowConfirmUniformFpsDialog() {
var rv = MessageBox.Show(
"Do you really want to set an uniform FPS value for all frames?\nThis operation can not be revoked.",
"Uniform FPS",
MessageBoxButton.YesNo, MessageBoxImage.Question);
return rv == MessageBoxResult.Yes;
}
public AddFrameDialogResult? ShowAddFrameDialog() { public AddFrameDialogResult? ShowAddFrameDialog() {
var dialog = new Views.AddFrameDialog(); var dialog = new Views.AddFrameDialog();
dialog.Owner = m_Parent; dialog.Owner = m_Parent;

View File

@@ -0,0 +1,185 @@
using BallanceTasEditor.Frontend.Models;
using CommunityToolkit.Mvvm.ComponentModel;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BallanceTasEditor.Frontend.Shared {
public class EditorConfiguration {
public static readonly EditorConfiguration Instance = new EditorConfiguration();
private EditorConfiguration() {
Init();
}
public SequenceKind SequenceKind { get; set; }
public EditorLayoutKind EditorLayout { get; set; }
public EditorPasteBehavior PasteBehavior { get; set; }
public int FrameCount { get; set; }
public string GamePath { get; set; }
[MemberNotNull(nameof(SequenceKind))]
[MemberNotNull(nameof(EditorLayout))]
[MemberNotNull(nameof(PasteBehavior))]
[MemberNotNull(nameof(FrameCount))]
[MemberNotNull(nameof(GamePath))]
private void FromRaw(RawEditorConfiguration raw) {
SequenceKind = raw.SequenceKind;
EditorLayout = raw.EditorLayout;
PasteBehavior = raw.PasteBehavior;
FrameCount = raw.FrameCount;
GamePath = raw.GamePath;
}
private RawEditorConfiguration ToRaw() {
return new RawEditorConfiguration {
SequenceKind = SequenceKind,
EditorLayout = EditorLayout,
PasteBehavior = PasteBehavior,
FrameCount = FrameCount,
GamePath = GamePath
};
}
private string GetSettingFilePath() {
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string selfAppDataPath = Path.Combine(appDataPath, "BallanceTasEditor");
return Path.Combine(selfAppDataPath, "BallanceTasEditor.cfg");
}
private bool Load() {
try {
using (var fs = new StreamReader(GetSettingFilePath(), Encoding.UTF8)) {
// Read semester object.
var raw = JsonConvert.DeserializeObject<RawEditorConfiguration>(fs.ReadToEnd()).Unwrap();
// Apply to self.
FromRaw(raw);
}
return true;
} catch (Exception) {
return false;
}
}
[MemberNotNull(nameof(SequenceKind))]
[MemberNotNull(nameof(EditorLayout))]
[MemberNotNull(nameof(PasteBehavior))]
[MemberNotNull(nameof(FrameCount))]
[MemberNotNull(nameof(GamePath))]
private void Init() {
var defaultRaw = new RawEditorConfiguration();
FromRaw(defaultRaw);
}
public void LoadOrInit() {
if (!Load()) {
Init();
}
}
public void Save() {
try {
// Check file path directory.
var settingFilePath = GetSettingFilePath();
var settingFileDirectory = Path.GetDirectoryName(settingFilePath).Unwrap();
if (!Directory.Exists(settingFileDirectory)) {
Directory.CreateDirectory(settingFileDirectory);
}
// Write it.
using (var fs = new StreamWriter(GetSettingFilePath(), false, Encoding.UTF8)) {
fs.Write(JsonConvert.SerializeObject(ToRaw()));
}
} catch (Exception) {
// Do nothing.
}
}
private class RawEditorConfiguration {
[JsonProperty("sequence_kind", Required = Required.Always)]
[JsonConverter(typeof(TasSequenceKindConverter))]
public SequenceKind SequenceKind { get; set; } = SequenceKind.Array;
[JsonProperty("editor_layout", Required = Required.Always)]
[JsonConverter(typeof(EditorLayoutKindConverter))]
public EditorLayoutKind EditorLayout { get; set; } = EditorLayoutKind.Vertical;
[JsonProperty("paste_behavior", Required = Required.Always)]
[JsonConverter(typeof(EditorPasteBehaviorConverter))]
public EditorPasteBehavior PasteBehavior { get; set; } = EditorPasteBehavior.Insert;
[JsonProperty("frame_count", Required = Required.Always)]
public int FrameCount { get; set; } = 20;
[JsonProperty("game_path", Required = Required.Always)]
public string GamePath { get; set; } = "";
}
#region Custom JSON Converter
private class TasSequenceKindConverter : JsonConverter<SequenceKind> {
public override SequenceKind ReadJson(JsonReader reader, Type objectType, SequenceKind existingValue, bool hasExistingValue, JsonSerializer serializer) {
if (reader.TokenType == JsonToken.String) {
var value = (reader.Value as string).Unwrap();
if (SequenceKindHelper.TryParse(value, out var kind)) {
return kind;
} else {
throw new JsonSerializationException($"given string can not be parsed as TasSequenceKind: {value}");
}
} else {
throw new JsonSerializationException($"expect a integer but got {reader.TokenType}");
}
}
public override void WriteJson(JsonWriter writer, SequenceKind value, JsonSerializer serializer) {
writer.WriteValue(SequenceKindHelper.ToString(value));
}
}
private class EditorLayoutKindConverter : JsonConverter<EditorLayoutKind> {
public override EditorLayoutKind ReadJson(JsonReader reader, Type objectType, EditorLayoutKind existingValue, bool hasExistingValue, JsonSerializer serializer) {
if (reader.TokenType == JsonToken.String) {
var value = (reader.Value as string).Unwrap();
if (EditorLayoutKindHelper.TryParse(value, out var kind)) {
return kind;
} else {
throw new JsonSerializationException($"given string can not be parsed as EditorLayoutKind: {value}");
}
} else {
throw new JsonSerializationException($"expect a integer but got {reader.TokenType}");
}
}
public override void WriteJson(JsonWriter writer, EditorLayoutKind value, JsonSerializer serializer) {
writer.WriteValue(EditorLayoutKindHelper.ToString(value));
}
}
private class EditorPasteBehaviorConverter : JsonConverter<EditorPasteBehavior> {
public override EditorPasteBehavior ReadJson(JsonReader reader, Type objectType, EditorPasteBehavior existingValue, bool hasExistingValue, JsonSerializer serializer) {
if (reader.TokenType == JsonToken.String) {
var value = (reader.Value as string).Unwrap();
if (EditorPasteBehaviorHelper.TryParse(value, out var kind)) {
return kind;
} else {
throw new JsonSerializationException($"given string can not be parsed as EditorPasteBehavior: {value}");
}
} else {
throw new JsonSerializationException($"expect a integer but got {reader.TokenType}");
}
}
public override void WriteJson(JsonWriter writer, EditorPasteBehavior value, JsonSerializer serializer) {
writer.WriteValue(EditorPasteBehaviorHelper.ToString(value));
}
}
#endregion
}
}

View File

@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BallanceTasEditor.Frontend.Shared {
public enum SequenceKind {
Array,
DoubleLinkedList
}
public static class SequenceKindHelper {
public static Backend.ITasSequence CreateSequenceByKind(SequenceKind kind) {
return kind switch {
SequenceKind.Array => new Backend.ListTasSequence(),
SequenceKind.DoubleLinkedList => new Backend.LegacyTasSequence(),
_ => throw new UnreachableException(),
};
}
public static bool TryParse(string s, out SequenceKind value) {
switch (s) {
case "array":
value = SequenceKind.Array;
return true;
case "double_linked_list":
value = SequenceKind.DoubleLinkedList;
return true;
default:
value = default;
return false;
}
}
public static string ToString(SequenceKind kind) {
return kind switch {
SequenceKind.Array => "array",
SequenceKind.DoubleLinkedList => "double_linked_list",
_ => throw new UnreachableException(),
};
}
}
public enum EditorLayoutKind {
Vertical,
Horizontal
}
public static class EditorLayoutKindHelper {
public static bool TryParse(string s, out EditorLayoutKind value) {
switch (s) {
case "vertical":
value = EditorLayoutKind.Vertical;
return true;
case "horizontal":
value = EditorLayoutKind.Horizontal;
return true;
default:
value = default;
return false;
}
}
public static string ToString(EditorLayoutKind kind) {
return kind switch {
EditorLayoutKind.Vertical => "vertical",
EditorLayoutKind.Horizontal => "horizontal",
_ => throw new UnreachableException(),
};
}
}
public enum EditorPasteBehavior {
Insert,
Override
}
public static class EditorPasteBehaviorHelper {
public static bool TryParse(string s, out EditorPasteBehavior value) {
switch (s) {
case "insert":
value = EditorPasteBehavior.Insert;
return true;
case "override":
value = EditorPasteBehavior.Override;
return true;
default:
value = default;
return false;
}
}
public static string ToString(EditorPasteBehavior mode) {
return mode switch {
EditorPasteBehavior.Insert => "insert",
EditorPasteBehavior.Override => "override",
_ => throw new UnreachableException(),
};
}
}
}

View File

@@ -2,7 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ControlTemplate x:Key="TextBoxErrorTemplate"> <ControlTemplate x:Key="TextBoxErrorTemplate">
<Grid> <Grid>
<Image Source="/Frontend/Assets/Cancel.ico" RenderOptions.BitmapScalingMode="HighQuality" <Image Source="/Frontend/Assets/FieldError.ico" RenderOptions.BitmapScalingMode="HighQuality"
Width="16" Height="16" Margin="0, 0, 4, 0" VerticalAlignment="Center" HorizontalAlignment="Right"> Width="16" Height="16" Margin="0, 0, 4, 0" VerticalAlignment="Center" HorizontalAlignment="Right">
<Image.ToolTip> <Image.ToolTip>
<ToolTip> <ToolTip>

View File

@@ -15,8 +15,9 @@ namespace BallanceTasEditor.Frontend.ViewModels {
public MainWindow(Shared.IDialogService dialogService) { public MainWindow(Shared.IDialogService dialogService) {
m_DialogService = dialogService; m_DialogService = dialogService;
this.TasFile = null; TasFile = new Models.TasFile();
this.TasFilePath = null; TasFile.PropertyChanged += TasFile_PropertyChanged;
TasFilePath = null;
this.StatusMessage = ""; this.StatusMessage = "";
m_StatusMessageDimmer = new DispatcherTimer(); m_StatusMessageDimmer = new DispatcherTimer();
@@ -32,35 +33,43 @@ namespace BallanceTasEditor.Frontend.ViewModels {
#region File Operation #region File Operation
[ObservableProperty] [ObservableProperty]
[NotifyPropertyChangedFor(nameof(WindowTitle))] private Models.TasFile tasFile;
[NotifyCanExecuteChangedFor(nameof(NewFileCommand))]
[NotifyCanExecuteChangedFor(nameof(OpenFileCommand))]
[NotifyCanExecuteChangedFor(nameof(SaveFileCommand))]
[NotifyCanExecuteChangedFor(nameof(SaveFileAsCommand))]
[NotifyCanExecuteChangedFor(nameof(CloseFileCommand))]
private Backend.ITasSequence? tasFile;
[ObservableProperty] [ObservableProperty]
[NotifyPropertyChangedFor(nameof(WindowTitle))] [NotifyPropertyChangedFor(nameof(WindowTitle))]
private string? tasFilePath; private string? tasFilePath;
private void TasFile_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) {
// YYC MARK:
// Due to the shitty limit of MVVM Toolkit,
// I was forced trigger these manually.
OnPropertyChanged(nameof(IsVisibleForLoadedFile));
OnPropertyChanged(nameof(WindowTitle));
OnPropertyChanged(nameof(IsVisibleForNotLoadedFile));
NewFileCommand.NotifyCanExecuteChanged();
OpenFileCommand.NotifyCanExecuteChanged();
SaveFileCommand.NotifyCanExecuteChanged();
SaveFileAsCommand.NotifyCanExecuteChanged();
SaveFileThenRunGameCommand.NotifyCanExecuteChanged();
CloseFileCommand.NotifyCanExecuteChanged();
}
[RelayCommand(CanExecute = nameof(CanNewFile))] [RelayCommand(CanExecute = nameof(CanNewFile))]
private void NewFile() { private void NewFile() {
// Request new file properties // Request new file properties
var dialog = m_DialogService.ShowNewFileDialog(); var dialog = m_DialogService.ShowNewFileDialog();
if (dialog is null) return; if (dialog is null) return;
// Initialize sequence // Create new file
var seq = new Backend.ListTasSequence(); TasFile.NewFile(Shared.SequenceKind.Array, dialog.Count, dialog.Fps);
// Initialize items
Backend.TasStorage.Init(seq, dialog.Count, dialog.Fps);
// Set members // Set members
this.TasFile = seq; TasFilePath = null;
this.TasFilePath = null; // Send notification
UpdateStatusMessage($"New TAS file is created."); UpdateStatusMessage($"New TAS file is created.");
} }
private bool CanNewFile() { private bool CanNewFile() {
return this.TasFile is null; return TasFile.IsFileNotLoaded;
} }
[RelayCommand(CanExecute = nameof(CanOpenFile))] [RelayCommand(CanExecute = nameof(CanOpenFile))]
@@ -69,23 +78,21 @@ namespace BallanceTasEditor.Frontend.ViewModels {
var dialog = m_DialogService.ShowOpenFileDialog(); var dialog = m_DialogService.ShowOpenFileDialog();
if (dialog is null) return; if (dialog is null) return;
// Initialize sequence // Load file
var seq = new Backend.ListTasSequence();
// Load into sequence
try { try {
Backend.TasStorage.Load(dialog.Path, seq); TasFile.LoadFile(Shared.SequenceKind.Array, dialog.Path);
} catch (Exception e) { } catch (Exception e) {
m_DialogService.ShowOpenFileFailedDialog(e); m_DialogService.ShowOpenFileFailedDialog(e);
return; return;
} }
// Set members // Set members
this.TasFile = seq; TasFilePath = dialog.Path;
this.TasFilePath = dialog.Path; // Send notification
UpdateStatusMessage($"TAS file {this.TasFilePath} is loaded."); UpdateStatusMessage($"TAS file {this.TasFilePath} is loaded.");
} }
private bool CanOpenFile() { private bool CanOpenFile() {
return this.TasFile is null; return TasFile.IsFileNotLoaded;
} }
[RelayCommand(CanExecute = nameof(CanSaveFile))] [RelayCommand(CanExecute = nameof(CanSaveFile))]
@@ -102,18 +109,19 @@ namespace BallanceTasEditor.Frontend.ViewModels {
// Save file // Save file
try { try {
Backend.TasStorage.Save(filePath, this.TasFile.Unwrap()); TasFile.SaveFile(filePath);
} catch (Exception e) { } catch (Exception e) {
m_DialogService.ShowSaveFileFailedDialog(e); m_DialogService.ShowSaveFileFailedDialog(e);
return; return;
} }
// Update member // Update member
this.TasFilePath = filePath; TasFilePath = filePath;
// Send notification
UpdateStatusMessage($"TAS file {this.TasFilePath} is saved."); UpdateStatusMessage($"TAS file {this.TasFilePath} is saved.");
} }
private bool CanSaveFile() { private bool CanSaveFile() {
return this.TasFile is not null; return TasFile.IsFileLoaded;
} }
[RelayCommand(CanExecute = nameof(CanSaveFileAs))] [RelayCommand(CanExecute = nameof(CanSaveFileAs))]
@@ -124,29 +132,46 @@ namespace BallanceTasEditor.Frontend.ViewModels {
// Save file // Save file
try { try {
Backend.TasStorage.Save(dialog.Path, this.TasFile.Unwrap()); TasFile.SaveFile(dialog.Path);
} catch (Exception e) { } catch (Exception e) {
m_DialogService.ShowSaveFileFailedDialog(e); m_DialogService.ShowSaveFileFailedDialog(e);
return; return;
} }
// Set file path // Set file path
TasFilePath = dialog.Path; TasFilePath = dialog.Path;
// Send notification
UpdateStatusMessage($"TAS file {this.TasFilePath} is saved."); UpdateStatusMessage($"TAS file {this.TasFilePath} is saved.");
} }
private bool CanSaveFileAs() { private bool CanSaveFileAs() {
return this.TasFile is not null; return TasFile.IsFileLoaded;
} }
[RelayCommand(CanExecute = nameof(CanCloseFile))] [RelayCommand(CanExecute = nameof(CanCloseFile))]
private void CloseFile() { private void CloseFile() {
this.TasFile = null; // Close file
this.TasFilePath = null; TasFile.CloseFile();
// Set members
TasFilePath = null;
// Send notification
UpdateStatusMessage($"TAS file is closed."); UpdateStatusMessage($"TAS file is closed.");
} }
private bool CanCloseFile() { private bool CanCloseFile() {
return this.TasFile is not null; return TasFile.IsFileLoaded;
}
#endregion
#region Special Save with Running Game
[RelayCommand(CanExecute = nameof(CanSaveFileThenRunGame))]
private void SaveFileThenRunGame() {
SaveFile();
}
private bool CanSaveFileThenRunGame() {
return CanSaveFile() && true;
} }
#endregion #endregion
@@ -171,6 +196,133 @@ namespace BallanceTasEditor.Frontend.ViewModels {
#region Edit Menu #region Edit Menu
#region Undo and Redo
[RelayCommand(CanExecute = nameof(CanUndo))]
private void Undo() {
}
private bool CanUndo() {
return true;
}
[RelayCommand(CanExecute = nameof(CanRedo))]
private void Redo() {
}
private bool CanRedo() {
return true;
}
#endregion
#region Viewer Operation
[RelayCommand(CanExecute = nameof(CanPreviousPage))]
private void PreviousPage() {
}
private bool CanPreviousPage() {
return true;
}
[RelayCommand(CanExecute = nameof(CanPreviousItem))]
private void PreviousItem() {
}
private bool CanPreviousItem() {
return true;
}
[RelayCommand(CanExecute = nameof(CanNextPage))]
private void NextPage() {
}
private bool CanNextPage() {
return true;
}
[RelayCommand(CanExecute = nameof(CanNextItem))]
private void NextItem() {
}
private bool CanNextItem() {
return true;
}
[RelayCommand(CanExecute = nameof(CanGoto))]
private void Goto() {
}
private bool CanGoto() {
return true;
}
#endregion
#region Tool Mode
[RelayCommand(CanExecute = nameof(CanSelectMode))]
private void SelectMode() {
}
private bool CanSelectMode() {
return true;
}
[RelayCommand(CanExecute = nameof(CanFillMode))]
private void FillMode() {
}
private bool CanFillMode() {
return true;
}
[RelayCommand(CanExecute = nameof(CanDrawMode))]
private void DrawMode() {
}
private bool CanDrawMode() {
return true;
}
#endregion
#region Misc Edit Operations
[RelayCommand(CanExecute = nameof(CanUniformFps))]
private void ClearKeys() {
m_DialogService.ShowConfirmClearKeysDialog();
}
private bool CanClearKeys() {
return true;
}
[RelayCommand(CanExecute = nameof(CanUniformFps))]
private void UniformFps() {
var rv = m_DialogService.ShowEditFpsDialog();
if (rv is not null) {
m_DialogService.ShowConfirmUniformFpsDialog();
}
}
private bool CanUniformFps() {
return true;
}
#endregion
#region Preference #region Preference
@@ -208,6 +360,9 @@ namespace BallanceTasEditor.Frontend.ViewModels {
[ObservableProperty] [ObservableProperty]
private string statusMessage; private string statusMessage;
/// <summary>
/// The UI thread timer for dimming status message after specific duration.
/// </summary>
private DispatcherTimer m_StatusMessageDimmer; private DispatcherTimer m_StatusMessageDimmer;
private void UpdateStatusMessage(string msg) { private void UpdateStatusMessage(string msg) {
@@ -228,7 +383,7 @@ namespace BallanceTasEditor.Frontend.ViewModels {
public string WindowTitle { public string WindowTitle {
get { get {
if (TasFile is null) { if (TasFile.IsFileNotLoaded) {
return "Ballance TAS Editor"; return "Ballance TAS Editor";
} else { } else {
if (TasFilePath is null) { if (TasFilePath is null) {
@@ -240,6 +395,14 @@ namespace BallanceTasEditor.Frontend.ViewModels {
} }
} }
public bool IsVisibleForLoadedFile {
get => TasFile.IsFileLoaded;
}
public bool IsVisibleForNotLoadedFile {
get => TasFile.IsFileNotLoaded;
}
#endregion #endregion
} }

View File

@@ -2,17 +2,47 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace BallanceTasEditor.Frontend.ViewModels { namespace BallanceTasEditor.Frontend.ViewModels {
public partial class PreferenceDialog : ObservableObject { public partial class PreferenceDialog : ObservableValidator {
public PreferenceDialog() { public PreferenceDialog() {
} }
[ObservableProperty]
private Shared.SequenceKind sequenceKind;
[ObservableProperty]
private Shared.EditorLayoutKind layoutKind;
[ObservableProperty]
private Shared.EditorPasteBehavior pasteBehavior;
[ObservableProperty]
[NotifyDataErrorInfo]
[CustomValidation(typeof(PreferenceDialog), nameof(ValidateCount))]
[NotifyCanExecuteChangedFor(nameof(OkCommand))]
private string frameCount;
[ObservableProperty]
private string gamePath;
#region Validators
private static readonly Validators.ValidatorAdapter<string, int, Validators.CountValidator> g_CountValidator =
new Validators.ValidatorAdapter<string, int, Validators.CountValidator>(new Validators.CountValidator());
public static ValidationResult? ValidateCount(string value, ValidationContext context) {
return g_CountValidator.Validate(value, context);
}
//public Shared.NewFileDialogResult GetUserInput() {
// return
//}
#endregion
#region Commands
[RelayCommand(CanExecute = nameof(CanOk))] [RelayCommand(CanExecute = nameof(CanOk))]
private void Ok() { private void Ok() {
@@ -20,8 +50,7 @@ namespace BallanceTasEditor.Frontend.ViewModels {
} }
private bool CanOk() { private bool CanOk() {
// TODO return !HasErrors;
return true;
} }
[RelayCommand] [RelayCommand]
@@ -29,12 +58,13 @@ namespace BallanceTasEditor.Frontend.ViewModels {
OnRequestCloseDialog(false); OnRequestCloseDialog(false);
} }
public event Shared.RequestCloseDialogEventHandler? RequestCloseDialog; public event Shared.RequestCloseDialogEventHandler? RequestCloseDialog;
private void OnRequestCloseDialog(bool result) { private void OnRequestCloseDialog(bool result) {
RequestCloseDialog?.Invoke(new Shared.RequestCloseDialogEventArgs { Result = result }); RequestCloseDialog?.Invoke(new Shared.RequestCloseDialogEventArgs { Result = result });
} }
#endregion
} }
} }

View File

@@ -5,15 +5,15 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BallanceTasEditor.Frontend.Views" xmlns:local="clr-namespace:BallanceTasEditor.Frontend.Views"
xmlns:vm="clr-namespace:BallanceTasEditor.Frontend.ViewModels" xmlns:vm="clr-namespace:BallanceTasEditor.Frontend.ViewModels"
xmlns:conveter="clr-namespace:BallanceTasEditor.Frontend.Converters"
xmlns:widget="clr-namespace:BallanceTasEditor.Frontend.Widgets" xmlns:widget="clr-namespace:BallanceTasEditor.Frontend.Widgets"
mc:Ignorable="d" mc:Ignorable="d"
d:DataContext="{d:DesignInstance vm:MainWindow}" d:DataContext="{d:DesignInstance vm:MainWindow}"
WindowStartupLocation="CenterScreen" WindowStartupLocation="CenterScreen"
Title="{Binding WindowTitle, Mode=OneWay}" Height="600" Width="800" Icon="/Frontend/Assets/App.ico"> Title="{Binding WindowTitle, Mode=OneWay}" Height="800" Width="600" Icon="/Frontend/Assets/App.ico">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/> <RowDefinition Height="auto"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
<RowDefinition Height="auto"/> <RowDefinition Height="auto"/>
@@ -52,27 +52,27 @@
<Separator/> <Separator/>
<MenuItem Header="_Save File" Icon="{StaticResource IconSaveFile}" InputGestureText="Ctrl+S" Command="{Binding SaveFileCommand}"/> <MenuItem Header="_Save File" Icon="{StaticResource IconSaveFile}" InputGestureText="Ctrl+S" Command="{Binding SaveFileCommand}"/>
<MenuItem Header="Save File as ..." Icon="{StaticResource IconSaveFileAs}" Command="{Binding SaveFileAsCommand}"/> <MenuItem Header="Save File as ..." Icon="{StaticResource IconSaveFileAs}" Command="{Binding SaveFileAsCommand}"/>
<MenuItem Header="Save File then _Run Game" Icon="{StaticResource IconSaveFileThenRunGame}" InputGestureText="B"/> <MenuItem Header="Save File then _Run Game" Icon="{StaticResource IconSaveFileThenRunGame}" InputGestureText="B" Command="{Binding SaveFileThenRunGameCommand}"/>
<Separator/> <Separator/>
<MenuItem Header="Close File" Icon="{StaticResource IconCloseFile}" Command="{Binding CloseFileCommand}"/> <MenuItem Header="Close File" Icon="{StaticResource IconCloseFile}" Command="{Binding CloseFileCommand}"/>
<MenuItem Header="Exit" Icon="{StaticResource IconExit}" Command="{Binding ExitCommand}"/> <MenuItem Header="Exit" Icon="{StaticResource IconExit}" Command="{Binding ExitCommand}"/>
</MenuItem> </MenuItem>
<MenuItem Header="_Edit" Padding="5"> <MenuItem Header="_Edit" Padding="5">
<MenuItem Header="_Undo" Icon="{StaticResource IconUndo}" InputGestureText="Ctrl+Z"/> <MenuItem Header="_Undo" Icon="{StaticResource IconUndo}" InputGestureText="Ctrl+Z" Command="{Binding UndoCommand}"/>
<MenuItem Header="_Redo" Icon="{StaticResource IconRedo}" InputGestureText="Ctrl+Y"/> <MenuItem Header="_Redo" Icon="{StaticResource IconRedo}" InputGestureText="Ctrl+Y" Command="{Binding RedoCommand}"/>
<Separator/> <Separator/>
<MenuItem Header="Previous Page" Icon="{StaticResource IconPreviousPage}" InputGestureText="A"/> <MenuItem Header="Previous Page" Icon="{StaticResource IconPreviousPage}" InputGestureText="A" Command="{Binding PreviousPageCommand}"/>
<MenuItem Header="Previous Item" Icon="{StaticResource IconPreviousItem}" InputGestureText="S"/> <MenuItem Header="Previous Item" Icon="{StaticResource IconPreviousItem}" InputGestureText="S" Command="{Binding PreviousItemCommand}"/>
<MenuItem Header="Next Page" Icon="{StaticResource IconNextPage}" InputGestureText="D"/> <MenuItem Header="Next Page" Icon="{StaticResource IconNextPage}" InputGestureText="D" Command="{Binding NextPageCommand}"/>
<MenuItem Header="Next Item" Icon="{StaticResource IconNextItem}" InputGestureText="F"/> <MenuItem Header="Next Item" Icon="{StaticResource IconNextItem}" InputGestureText="F" Command="{Binding NextItemCommand}"/>
<MenuItem Header="Goto Item" Icon="{StaticResource IconGoto}" InputGestureText="G"/> <MenuItem Header="Goto Item" Icon="{StaticResource IconGoto}" InputGestureText="G" Command="{Binding GotoCommand}"/>
<Separator/> <Separator/>
<MenuItem Header="Select Mode" Icon="{StaticResource IconSelectMode}" InputGestureText="Q"/> <MenuItem Header="Select Mode" Icon="{StaticResource IconSelectMode}" InputGestureText="Q" Command="{Binding SelectModeCommand}"/>
<MenuItem Header="Fill Mode" Icon="{StaticResource IconFillMode}" InputGestureText="W"/> <MenuItem Header="Fill Mode" Icon="{StaticResource IconFillMode}" InputGestureText="W" Command="{Binding FillModeCommand}"/>
<MenuItem Header="Draw Mode" Icon="{StaticResource IconDrawMode}" InputGestureText="E"/> <MenuItem Header="Draw Mode" Icon="{StaticResource IconDrawMode}" InputGestureText="E" Command="{Binding DrawModeCommand}"/>
<Separator/> <Separator/>
<MenuItem Header="Clear Keys" Icon="{StaticResource IconClearKeys}"/> <MenuItem Header="Clear Keys" Icon="{StaticResource IconClearKeys}" Command="{Binding ClearKeysCommand}"/>
<MenuItem Header="Uniform FPS" Icon="{StaticResource IconUniformFps}"/> <MenuItem Header="Uniform FPS" Icon="{StaticResource IconUniformFps}" Command="{Binding UniformFpsCommand}"/>
<Separator/> <Separator/>
<MenuItem Header="Preference" Icon="{StaticResource IconPreference}" InputGestureText="Ctrl+P" Command="{Binding PreferenceCommand}"/> <MenuItem Header="Preference" Icon="{StaticResource IconPreference}" InputGestureText="Ctrl+P" Command="{Binding PreferenceCommand}"/>
</MenuItem> </MenuItem>
@@ -82,31 +82,7 @@
</MenuItem> </MenuItem>
</Menu> </Menu>
<Grid Grid.Row="1"> <Grid Grid.Row="1" Visibility="{Binding IsVisibleForNotLoadedFile, Mode=OneWay, Converter={x:Static conveter:IsVisibleToVisibilityConverter.Instance}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Column="0">
<widget:IconButton ButtonText="Select Mode"
ButtonIcon="/Frontend/Assets/SelectMode.ico"
Margin="5" Padding="5"/>
<widget:IconButton ButtonText="Fill Mode"
ButtonIcon="/Frontend/Assets/FillMode.ico"
Margin="5" Padding="5"/>
<widget:IconButton ButtonText="Draw Mode"
ButtonIcon="/Frontend/Assets/DrawMode.ico"
Margin="5" Padding="5"/>
</StackPanel>
<widget:IconButton ButtonText="Save File then Run Game"
ButtonIcon="/Frontend/Assets/SaveFileThenRunGame.ico"
Grid.Column="1" Margin="5" Padding="5"/>
</Grid>
<Grid Grid.Row="2">
<Grid VerticalAlignment="Center" HorizontalAlignment="Center" AllowDrop="True"> <Grid VerticalAlignment="Center" HorizontalAlignment="Center" AllowDrop="True">
<Rectangle StrokeThickness="4" Stroke="Gray" StrokeDashArray="4 4" Fill="Transparent"/> <Rectangle StrokeThickness="4" Stroke="Gray" StrokeDashArray="4 4" Fill="Transparent"/>
<StackPanel Orientation="Horizontal" Margin="20"> <StackPanel Orientation="Horizontal" Margin="20">
@@ -114,46 +90,83 @@
<TextBlock Margin="5" Text="Create, Open or Drop a TAS File in There for Editing" Foreground="Gray" FontSize="16" VerticalAlignment="Center"/> <TextBlock Margin="5" Text="Create, Open or Drop a TAS File in There for Editing" Foreground="Gray" FontSize="16" VerticalAlignment="Center"/>
</StackPanel> </StackPanel>
</Grid> </Grid>
<local:TasViewer>
<local:TasViewer.ContextMenu>
<ContextMenu>
<ContextMenu.Resources>
<!-- Context Menu Icons -->
<Image x:Key="IconSet" Source="/Frontend/Assets/SetCell.ico" RenderOptions.BitmapScalingMode="HighQuality"/>
<Image x:Key="IconUnset" Source="/Frontend/Assets/UnsetCell.ico" RenderOptions.BitmapScalingMode="HighQuality"/>
<Image x:Key="IconFlip" Source="/Frontend/Assets/FlipCell.ico" RenderOptions.BitmapScalingMode="HighQuality"/>
<Image x:Key="IconCut" Source="/Frontend/Assets/CutFrame.ico" RenderOptions.BitmapScalingMode="HighQuality"/>
<Image x:Key="IconCopy" Source="/Frontend/Assets/CopyFrame.ico" RenderOptions.BitmapScalingMode="HighQuality"/>
<Image x:Key="IconPaste" Source="/Frontend/Assets/PasteFrame.ico" RenderOptions.BitmapScalingMode="HighQuality"/>
<Image x:Key="IconDelete" Source="/Frontend/Assets/DeleteFrame.ico" RenderOptions.BitmapScalingMode="HighQuality"/>
<Image x:Key="IconAdd" Source="/Frontend/Assets/AddFrame.ico" RenderOptions.BitmapScalingMode="HighQuality"/>
<Image x:Key="IconFps" Source="/Frontend/Assets/SetFps.ico" RenderOptions.BitmapScalingMode="HighQuality"/>
</ContextMenu.Resources>
<MenuItem Header="Set" Icon="{StaticResource IconSet}"/>
<MenuItem Header="Unset" Icon="{StaticResource IconUnset}"/>
<MenuItem Header="Flip" Icon="{StaticResource IconFlip}"/>
<Separator/>
<MenuItem Header="Cut" Icon="{StaticResource IconCut}" InputGestureText="Ctrl+X"/>
<MenuItem Header="Copy" Icon="{StaticResource IconCopy}" InputGestureText="Ctrl+C"/>
<MenuItem Header="Paste after This" Icon="{StaticResource IconPaste}" InputGestureText="Ctrl+V"/>
<MenuItem Header="Paste before This"/>
<Separator/>
<MenuItem Header="Delete" Icon="{StaticResource IconDelete}"/>
<MenuItem Header="Delete after This" InputGestureText="Del"/>
<MenuItem Header="Delete before This" InputGestureText="Backspace"/>
<Separator/>
<MenuItem Header="Add after This" Icon="{StaticResource IconAdd}"/>
<MenuItem Header="Add before This"/>
<Separator/>
<MenuItem Header="Set FPS" Icon="{StaticResource IconFps}"/>
</ContextMenu>
</local:TasViewer.ContextMenu>
</local:TasViewer>
</Grid> </Grid>
<StatusBar Grid.Row="3"> <Grid Grid.Row="1" Visibility="{Binding IsVisibleForLoadedFile, Mode=OneWay, Converter={x:Static conveter:IsVisibleToVisibilityConverter.Instance}}">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Column="0">
<widget:IconButton ButtonText="Select Mode"
ButtonIcon="/Frontend/Assets/SelectMode.ico"
Margin="5" Padding="5"
Command="{Binding SelectModeCommand}"/>
<widget:IconButton ButtonText="Fill Mode"
ButtonIcon="/Frontend/Assets/FillMode.ico"
Margin="5" Padding="5"
Command="{Binding FillModeCommand}"/>
<widget:IconButton ButtonText="Draw Mode"
ButtonIcon="/Frontend/Assets/DrawMode.ico"
Margin="5" Padding="5"
Command="{Binding DrawModeCommand}"/>
</StackPanel>
<widget:IconButton ButtonText="Save File then Run Game"
ButtonIcon="/Frontend/Assets/SaveFileThenRunGame.ico"
Grid.Column="1" Margin="5" Padding="5"
Command="{Binding SaveFileThenRunGameCommand}"/>
</Grid>
<Grid Grid.Row="1">
<local:TasViewer>
<local:TasViewer.ContextMenu>
<ContextMenu>
<ContextMenu.Resources>
<!-- Context Menu Icons -->
<Image x:Key="IconSet" Source="/Frontend/Assets/SetCell.ico" RenderOptions.BitmapScalingMode="HighQuality"/>
<Image x:Key="IconUnset" Source="/Frontend/Assets/UnsetCell.ico" RenderOptions.BitmapScalingMode="HighQuality"/>
<Image x:Key="IconFlip" Source="/Frontend/Assets/FlipCell.ico" RenderOptions.BitmapScalingMode="HighQuality"/>
<Image x:Key="IconCut" Source="/Frontend/Assets/CutFrame.ico" RenderOptions.BitmapScalingMode="HighQuality"/>
<Image x:Key="IconCopy" Source="/Frontend/Assets/CopyFrame.ico" RenderOptions.BitmapScalingMode="HighQuality"/>
<Image x:Key="IconPaste" Source="/Frontend/Assets/PasteFrame.ico" RenderOptions.BitmapScalingMode="HighQuality"/>
<Image x:Key="IconDelete" Source="/Frontend/Assets/DeleteFrame.ico" RenderOptions.BitmapScalingMode="HighQuality"/>
<Image x:Key="IconAdd" Source="/Frontend/Assets/AddFrame.ico" RenderOptions.BitmapScalingMode="HighQuality"/>
<Image x:Key="IconFps" Source="/Frontend/Assets/SetFps.ico" RenderOptions.BitmapScalingMode="HighQuality"/>
</ContextMenu.Resources>
<MenuItem Header="Set" Icon="{StaticResource IconSet}"/>
<MenuItem Header="Unset" Icon="{StaticResource IconUnset}"/>
<MenuItem Header="Flip" Icon="{StaticResource IconFlip}"/>
<Separator/>
<MenuItem Header="Cut" Icon="{StaticResource IconCut}" InputGestureText="Ctrl+X"/>
<MenuItem Header="Copy" Icon="{StaticResource IconCopy}" InputGestureText="Ctrl+C"/>
<MenuItem Header="Paste after This" Icon="{StaticResource IconPaste}" InputGestureText="Ctrl+V"/>
<MenuItem Header="Paste before This"/>
<Separator/>
<MenuItem Header="Delete" Icon="{StaticResource IconDelete}"/>
<MenuItem Header="Delete after This" InputGestureText="Del"/>
<MenuItem Header="Delete before This" InputGestureText="Backspace"/>
<Separator/>
<MenuItem Header="Add after This" Icon="{StaticResource IconAdd}"/>
<MenuItem Header="Add before This"/>
<Separator/>
<MenuItem Header="Set FPS" Icon="{StaticResource IconFps}"/>
</ContextMenu>
</local:TasViewer.ContextMenu>
</local:TasViewer>
</Grid>
</Grid>
<StatusBar Grid.Row="2">
<!-- 这玩意要逆序排列才能达到我想要的需求也是奇葩 --> <!-- 这玩意要逆序排列才能达到我想要的需求也是奇葩 -->
<StatusBarItem Content="v2.0 stable" DockPanel.Dock="Right" Foreground="Gray" FontStyle="Italic"/> <StatusBarItem Content="v2.0 stable" DockPanel.Dock="Right" Foreground="Gray" FontStyle="Italic"/>
<Separator DockPanel.Dock="Right"/> <Separator DockPanel.Dock="Right"/>

View File

@@ -5,10 +5,12 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BallanceTasEditor.Frontend.Views" xmlns:local="clr-namespace:BallanceTasEditor.Frontend.Views"
xmlns:vm="clr-namespace:BallanceTasEditor.Frontend.ViewModels" xmlns:vm="clr-namespace:BallanceTasEditor.Frontend.ViewModels"
xmlns:conveter="clr-namespace:BallanceTasEditor.Frontend.Converters"
xmlns:shared="clr-namespace:BallanceTasEditor.Frontend.Shared"
xmlns:widget="clr-namespace:BallanceTasEditor.Frontend.Widgets" xmlns:widget="clr-namespace:BallanceTasEditor.Frontend.Widgets"
d:DataContext="{d:DesignInstance vm:PreferenceDialog}" d:DataContext="{d:DesignInstance vm:PreferenceDialog}"
mc:Ignorable="d" WindowStartupLocation="CenterOwner" ResizeMode="NoResize" ShowInTaskbar="False" mc:Ignorable="d" WindowStartupLocation="CenterOwner" ResizeMode="NoResize" ShowInTaskbar="False"
Title="Editor Preference" Height="450" Width="400" Icon="/Frontend/Assets/Preference.ico"> Title="Editor Preference" Height="500" Width="400" Icon="/Frontend/Assets/Preference.ico">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
@@ -17,34 +19,64 @@
<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Visible"> <ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Visible">
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<widget:IconGroupBox GroupBoxText="Editor Layout" <widget:IconGroupBox GroupBoxText="Sequence Kind"
GroupBoxIcon="/Frontend/Assets/EditorLayout.ico" GroupBoxIcon="/Frontend/Assets/SequenceKind.ico"
Margin="10" Padding="10"> Margin="10" Padding="10">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<RadioButton Content="Horizontal Layout" GroupName="EditorLayout" Grid.Column="0"/> <RadioButton Content="Array" GroupName="SequenceKind"
<RadioButton Content="Vertical Layout" GroupName="EditorLayout" Grid.Column="1"/> ToolTip="Good for browsing. Bad for frequently inserting and removing frames."
IsChecked="{Binding SequenceKind, Mode=TwoWay, Converter={x:Static conveter:SequenceKindRadioButtonConverter.Instance}, ConverterParameter={x:Static shared:SequenceKind.Array}}"
Grid.Column="0"/>
<RadioButton Content="Double Linked List" GroupName="SequenceKind"
ToolTip="Good for frequently inserting and removing frames. Bad for browsing."
IsChecked="{Binding SequenceKind, Mode=TwoWay, Converter={x:Static conveter:SequenceKindRadioButtonConverter.Instance}, ConverterParameter={x:Static shared:SequenceKind.DoubleLinkedList}}"
Grid.Column="1"/>
</Grid>
</widget:IconGroupBox>
<widget:IconGroupBox GroupBoxText="Editor Layout"
GroupBoxIcon="/Frontend/Assets/EditorLayout.ico"
Margin="10" Padding="10"
IsEnabled="False">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<RadioButton Content="Horizontal Layout" GroupName="EditorLayout"
IsChecked="{Binding LayoutKind, Mode=TwoWay, Converter={x:Static conveter:EditorLayoutKindRadioButtonConverter.Instance}, ConverterParameter={x:Static shared:EditorLayoutKind.Horizontal}}"
Grid.Column="0"/>
<RadioButton Content="Vertical Layout" GroupName="EditorLayout"
IsChecked="{Binding LayoutKind, Mode=TwoWay, Converter={x:Static conveter:EditorLayoutKindRadioButtonConverter.Instance}, ConverterParameter={x:Static shared:EditorLayoutKind.Vertical}}"
Grid.Column="1"/>
</Grid> </Grid>
</widget:IconGroupBox> </widget:IconGroupBox>
<widget:IconGroupBox GroupBoxText="Paste Behavior" <widget:IconGroupBox GroupBoxText="Paste Behavior"
GroupBoxIcon="/Frontend/Assets/PasteFrame.ico" GroupBoxIcon="/Frontend/Assets/PasteFrame.ico"
Margin="10" Padding="10"> Margin="10" Padding="10"
IsEnabled="False">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<RadioButton Content="Insert Paste" GroupName="PasteBehavior" Grid.Column="0"/> <RadioButton Content="Insert Paste" GroupName="PasteBehavior"
<RadioButton Content="Overwritten Paste" GroupName="PasteBehavior" Grid.Column="1"/> IsChecked="{Binding PasteBehavior, Mode=TwoWay, Converter={x:Static conveter:EditorPasteBehaviorRadioButtonConverter.Instance}, ConverterParameter={x:Static shared:EditorPasteBehavior.Insert}}"
Grid.Column="0"/>
<RadioButton Content="Overwritten Paste" GroupName="PasteBehavior"
IsChecked="{Binding PasteBehavior, Mode=TwoWay, Converter={x:Static conveter:EditorPasteBehaviorRadioButtonConverter.Instance}, ConverterParameter={x:Static shared:EditorPasteBehavior.Override}}"
Grid.Column="1"/>
</Grid> </Grid>
</widget:IconGroupBox> </widget:IconGroupBox>
<widget:IconGroupBox GroupBoxText="Frame Count" <widget:IconGroupBox GroupBoxText="Frame Count"
GroupBoxIcon="/Frontend/Assets/Count.ico" GroupBoxIcon="/Frontend/Assets/Count.ico"
Margin="10" Padding="10"> Margin="10" Padding="10">
<TextBox Padding="3"/> <TextBox Padding="3"
Text="{Binding FrameCount, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}"
Style="{StaticResource TextBoxWithErrorNotifyStyle}"/>
</widget:IconGroupBox> </widget:IconGroupBox>
<widget:IconGroupBox GroupBoxText="Game Path" <widget:IconGroupBox GroupBoxText="Game Path"
GroupBoxIcon="/Frontend/Assets/SaveFileThenRunGame.ico" GroupBoxIcon="/Frontend/Assets/SaveFileThenRunGame.ico"

View File

@@ -0,0 +1,13 @@
<UserControl x:Class="BallanceTasEditor.Frontend.Widgets.FrameHeader"
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.Frontend.Widgets"
xmlns:conveter="clr-namespace:BallanceTasEditor.Frontend.Converters"
mc:Ignorable="d"
x:Name="Root"
d:DesignHeight="120" d:DesignWidth="60">
<Rectangle Stroke="#9E9E9E" StrokeThickness="2"
Fill="{Binding ElementName=Root, Path=IsSelected, Mode=OneWay, Converter={x:Static conveter:CellIsSetToColorConverter.Instance}}"/>
</UserControl>

View File

@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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.Frontend.Widgets {
/// <summary>
/// Interaction logic for FrameHeader.xaml
/// </summary>
public partial class FrameHeader : UserControl {
public FrameHeader() {
InitializeComponent();
}
public bool IsSelected {
get { return (bool)GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); }
}
// Using a DependencyProperty as the backing store for IsSelected. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsSelectedProperty =
DependencyProperty.Register("IsSelected", typeof(bool), typeof(FrameCell), new PropertyMetadata(false));
}
}