mirror of
https://github.com/fadden/6502bench.git
synced 2024-08-19 20:29:36 +00:00
Visualization animations are now exported as animated GIFs. The Windows stuff is a bit lame so I threw together some code that stitches a bunch of GIFs together. The GIF doesn't quite match the preview, because the preview scales the individual frames, while the animated GIF uses the largest frame as the size and is then scaled based on that. Animating frames of differing sizes together is bound to be trouble anyway, so I'm not sure how much to fret over this.
346 lines
14 KiB
C#
346 lines
14 KiB
C#
/*
|
|
* Copyright 2019 faddenSoft
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Input;
|
|
|
|
using Asm65;
|
|
using PluginCommon;
|
|
|
|
namespace SourceGen.WpfGui {
|
|
/// <summary>
|
|
/// Visualization set editor.
|
|
/// </summary>
|
|
public partial class EditVisualizationSet : Window, INotifyPropertyChanged {
|
|
/// <summary>
|
|
/// Modified visualization set. Only valid after OK is hit.
|
|
/// </summary>
|
|
public VisualizationSet NewVisSet { get; private set; }
|
|
|
|
/// <summary>
|
|
/// List of Visualization serial numbers that were removed from the set. The caller
|
|
/// can use this to update animations in other sets that referred to the removed items.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// We have to use serial numbers because the user might have edited the Visualization
|
|
/// before removing it.
|
|
/// </remarks>
|
|
public List<int> RemovedSerials { get; private set; }
|
|
|
|
private DisasmProject mProject;
|
|
private Formatter mFormatter;
|
|
private VisualizationSet mOrigSet;
|
|
private int mOffset;
|
|
|
|
/// <summary>
|
|
/// ItemsSource for visualizationGrid.
|
|
/// </summary>
|
|
public ObservableCollection<Visualization> VisualizationList { get; private set; } =
|
|
new ObservableCollection<Visualization>();
|
|
|
|
/// <summary>
|
|
/// True if there are plugins that implement the visualization generation interface.
|
|
/// </summary>
|
|
public bool HasVisPlugins {
|
|
get { return mHasVisPlugins; }
|
|
set { mHasVisPlugins = value; OnPropertyChanged(); }
|
|
}
|
|
private bool mHasVisPlugins;
|
|
|
|
public Visibility ScriptWarningVisible {
|
|
get { return mHasVisPlugins ? Visibility.Collapsed : Visibility.Visible; }
|
|
// this can't change while the dialog is open, so don't need OnPropertyChanged
|
|
}
|
|
|
|
public bool IsEditEnabled {
|
|
get { return mIsEditEnabled; }
|
|
set { mIsEditEnabled = value; OnPropertyChanged(); }
|
|
}
|
|
private bool mIsEditEnabled;
|
|
|
|
public bool IsRemoveEnabled {
|
|
get { return mIsRemoveEnabled; }
|
|
set { mIsRemoveEnabled = value; OnPropertyChanged(); }
|
|
}
|
|
private bool mIsRemoveEnabled;
|
|
|
|
public bool IsUpEnabled {
|
|
get { return mIsUpEnabled; }
|
|
set { mIsUpEnabled = value; OnPropertyChanged(); }
|
|
}
|
|
private bool mIsUpEnabled;
|
|
|
|
public bool IsDownEnabled {
|
|
get { return mIsDownEnabled; }
|
|
set { mIsDownEnabled = value; OnPropertyChanged(); }
|
|
}
|
|
private bool mIsDownEnabled;
|
|
|
|
// INotifyPropertyChanged implementation
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|
private void OnPropertyChanged([CallerMemberName] string propertyName = "") {
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
}
|
|
|
|
public EditVisualizationSet(Window owner, DisasmProject project, Formatter formatter,
|
|
VisualizationSet curSet, int offset) {
|
|
InitializeComponent();
|
|
Owner = owner;
|
|
DataContext = this;
|
|
|
|
mProject = project;
|
|
mFormatter = formatter;
|
|
mOrigSet = curSet;
|
|
mOffset = offset;
|
|
|
|
RemovedSerials = new List<int>();
|
|
|
|
if (curSet != null) {
|
|
// Populate the data grid ItemsSource.
|
|
foreach (Visualization vis in curSet) {
|
|
VisualizationList.Add(vis);
|
|
}
|
|
}
|
|
if (VisualizationList.Count > 0) {
|
|
visualizationGrid.SelectedIndex = 0;
|
|
}
|
|
|
|
// Check to see if we have any relevant plugins. If not, disable New/Edit.
|
|
List<IPlugin> plugins = project.GetActivePlugins();
|
|
foreach (IPlugin chkPlug in plugins) {
|
|
if (chkPlug is IPlugin_Visualizer) {
|
|
HasVisPlugins = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OkButton_Click(object sender, RoutedEventArgs e) {
|
|
NewVisSet = MakeVisSet();
|
|
DialogResult = true;
|
|
}
|
|
|
|
private void Window_Closing(object sender, CancelEventArgs e) {
|
|
if (DialogResult == true) {
|
|
return;
|
|
}
|
|
|
|
// Check to see if changes have been made.
|
|
VisualizationSet newSet = MakeVisSet();
|
|
if (newSet != mOrigSet) {
|
|
string msg = (string)FindResource("str_ConfirmDiscardChanges");
|
|
string caption = (string)FindResource("str_ConfirmDiscardChangesCaption");
|
|
MessageBoxResult result = MessageBox.Show(msg, caption, MessageBoxButton.OKCancel,
|
|
MessageBoxImage.Question);
|
|
if (result == MessageBoxResult.Cancel) {
|
|
e.Cancel = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void VisualizationList_SelectionChanged(object sender,
|
|
SelectionChangedEventArgs e) {
|
|
bool isItemSelected = (visualizationGrid.SelectedItem != null);
|
|
IsEditEnabled = HasVisPlugins && isItemSelected;
|
|
IsRemoveEnabled = isItemSelected;
|
|
IsUpEnabled = isItemSelected && visualizationGrid.SelectedIndex != 0;
|
|
IsDownEnabled = isItemSelected &&
|
|
visualizationGrid.SelectedIndex != VisualizationList.Count - 1;
|
|
}
|
|
|
|
private void VisualizationList_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
|
|
EditSelectedItem();
|
|
}
|
|
|
|
private void NewBitmapButton_Click(object sender, RoutedEventArgs e) {
|
|
EditVisualization dlg = new EditVisualization(this, mProject, mFormatter, mOffset,
|
|
CreateEditedSetList(), null);
|
|
if (dlg.ShowDialog() != true) {
|
|
return;
|
|
}
|
|
VisualizationList.Add(dlg.NewVis);
|
|
visualizationGrid.SelectedIndex = VisualizationList.Count - 1;
|
|
}
|
|
|
|
private void NewBitmapAnimationButton_Click(object sender, RoutedEventArgs e) {
|
|
EditBitmapAnimation dlg = new EditBitmapAnimation(this, mOffset,
|
|
CreateEditedSetList(), null);
|
|
if (dlg.ShowDialog() != true) {
|
|
return;
|
|
}
|
|
VisualizationList.Add(dlg.NewAnim);
|
|
visualizationGrid.SelectedIndex = VisualizationList.Count - 1;
|
|
}
|
|
|
|
private void EditButton_Click(object sender, RoutedEventArgs e) {
|
|
EditSelectedItem();
|
|
}
|
|
|
|
private void EditSelectedItem() {
|
|
if (!IsEditEnabled) {
|
|
// can get called here by a double-click
|
|
return;
|
|
}
|
|
Visualization item = (Visualization)visualizationGrid.SelectedItem;
|
|
Visualization newVis;
|
|
|
|
if (item is VisualizationAnimation) {
|
|
EditBitmapAnimation dlg = new EditBitmapAnimation(this, mOffset,
|
|
CreateEditedSetList(), (VisualizationAnimation)item);
|
|
if (dlg.ShowDialog() != true) {
|
|
return;
|
|
}
|
|
newVis = dlg.NewAnim;
|
|
} else {
|
|
EditVisualization dlg = new EditVisualization(this, mProject, mFormatter, mOffset,
|
|
CreateEditedSetList(), item);
|
|
if (dlg.ShowDialog() != true) {
|
|
return;
|
|
}
|
|
newVis = dlg.NewVis;
|
|
}
|
|
|
|
int index = VisualizationList.IndexOf(item);
|
|
VisualizationList.Remove(item);
|
|
VisualizationList.Insert(index, newVis);
|
|
visualizationGrid.SelectedIndex = index;
|
|
|
|
okButton.Focus();
|
|
}
|
|
|
|
private void RemoveButton_Click(object sender, RoutedEventArgs e) {
|
|
Visualization item = (Visualization)visualizationGrid.SelectedItem;
|
|
int index = VisualizationList.IndexOf(item);
|
|
VisualizationList.Remove(item);
|
|
if (index == VisualizationList.Count) {
|
|
index--;
|
|
}
|
|
if (index >= 0) {
|
|
visualizationGrid.SelectedIndex = index;
|
|
}
|
|
|
|
RemovedSerials.Add(item.SerialNumber);
|
|
|
|
// Update any animations in this set. Animations in other sets will be updated later.
|
|
// (This is a bit awkward because we can't modify VisualizationList while iterating
|
|
// through it, and there's no simple "replace entry" operation on an observable
|
|
// collection. Fortunately we don't do this often and the data sets are small.)
|
|
List<VisualizationAnimation> needsUpdate = new List<VisualizationAnimation>();
|
|
foreach (Visualization vis in VisualizationList) {
|
|
if (vis is VisualizationAnimation) {
|
|
VisualizationAnimation visAnim = (VisualizationAnimation)vis;
|
|
if (visAnim.ContainsSerial(item.SerialNumber)) {
|
|
needsUpdate.Add(visAnim);
|
|
}
|
|
}
|
|
}
|
|
foreach (VisualizationAnimation visAnim in needsUpdate) {
|
|
VisualizationAnimation newAnim;
|
|
if (VisualizationAnimation.StripEntries(visAnim,
|
|
new List<int>(1) { item.SerialNumber }, out newAnim)) {
|
|
if (newAnim.Count == 0) {
|
|
VisualizationList.Remove(visAnim);
|
|
} else {
|
|
newAnim.GenerateImage(CreateEditedSetList());
|
|
index = VisualizationList.IndexOf(visAnim);
|
|
VisualizationList.Remove(visAnim);
|
|
VisualizationList.Insert(index, newAnim);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpButton_Click(object sender, RoutedEventArgs e) {
|
|
Visualization item = (Visualization)visualizationGrid.SelectedItem;
|
|
int index = VisualizationList.IndexOf(item);
|
|
Debug.Assert(index > 0);
|
|
VisualizationList.Remove(item);
|
|
VisualizationList.Insert(index - 1, item);
|
|
visualizationGrid.SelectedIndex = index - 1;
|
|
}
|
|
|
|
private void DownButton_Click(object sender, RoutedEventArgs e) {
|
|
Visualization item = (Visualization)visualizationGrid.SelectedItem;
|
|
int index = VisualizationList.IndexOf(item);
|
|
Debug.Assert(index >= 0 && index < VisualizationList.Count - 1);
|
|
VisualizationList.Remove(item);
|
|
VisualizationList.Insert(index + 1, item);
|
|
visualizationGrid.SelectedIndex = index + 1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a VisualizationSet from the current list of Visualizations.
|
|
/// </summary>
|
|
/// <returns>New VisualizationSet.</returns>
|
|
private VisualizationSet MakeVisSet() {
|
|
VisualizationSet newSet = new VisualizationSet(VisualizationList.Count);
|
|
foreach (Visualization vis in VisualizationList) {
|
|
newSet.Add(vis);
|
|
}
|
|
return newSet;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a list of VisualizationSet references. This is the list from the
|
|
/// DisasmProject, but with the set we're editing added or substituted.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The editors sometimes need access to the full collection of Visualization objects,
|
|
/// such as when testing a tag for uniqueness or getting a list of all bitmap
|
|
/// frames for an animation. The editor needs access to recent edits that have not
|
|
/// been pushed to the project yet.
|
|
/// </remarks>
|
|
/// <returns>List of VisualizationSet.</returns>
|
|
private SortedList<int, VisualizationSet> CreateEditedSetList() {
|
|
SortedList<int, VisualizationSet> mixList =
|
|
new SortedList<int, VisualizationSet>(mProject.VisualizationSets.Count);
|
|
|
|
mixList[mOffset] = MakeVisSet();
|
|
foreach (KeyValuePair<int, VisualizationSet> kvp in mProject.VisualizationSets) {
|
|
// Skip the entry for mOffset (if it exists).
|
|
if (kvp.Key != mOffset) {
|
|
mixList[kvp.Key] = kvp.Value;
|
|
}
|
|
}
|
|
return mixList;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds a Visualization with a matching tag, searching across all sets in the
|
|
/// edited list.
|
|
/// </summary>
|
|
/// <param name="tag">Tag to search for.</param>
|
|
/// <returns>Matching Visualization, or null if not found.</returns>
|
|
public static Visualization FindVisualizationByTag(SortedList<int, VisualizationSet> list,
|
|
string tag) {
|
|
foreach (KeyValuePair<int, VisualizationSet> kvp in list) {
|
|
foreach (Visualization vis in kvp.Value) {
|
|
if (vis.Tag == tag) {
|
|
return vis;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
}
|