Functionality: - Rewrite history class so it tracks all changes, not only a canvas changes Bugs fixed: - In some cases after switching to a symbol dotPanel mouse move causes "Out of range" exception (history.Pre after width change?)
309 lines
12 KiB
C#
309 lines
12 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Runtime.InteropServices;
|
||
using System.Text;
|
||
using System.Threading.Tasks;
|
||
using System.Windows.Forms;
|
||
using static McBitFont.MainForm;
|
||
|
||
namespace McBitFont {
|
||
internal class ChangeHistory {
|
||
private MainForm mainForm;
|
||
private List<ChangeEvent> timeline = [];
|
||
private List<FrameMiniature> canvasChanges = [];
|
||
private List<List<FrameMiniature>> fontChanges = [];
|
||
private List<int> selectionChanges = [];
|
||
private int canvasIndex = 0;
|
||
private int fontIndex = 0;
|
||
private int selectionIndex = 0;
|
||
public int Depth { get; set; }
|
||
public int Index { get; set; } = -1;
|
||
public int Count {
|
||
get { return timeline.Count; }
|
||
}
|
||
public int Undos {
|
||
get { return Index < 0 ? 0 : Index + 1; }
|
||
}
|
||
public int Redos {
|
||
get { return Index < 0 ? Count : Count - Index - 1; }
|
||
}
|
||
public bool Doing { get; set; } = false;
|
||
|
||
// Constructor
|
||
public ChangeHistory(MainForm form, int depth = 100) {
|
||
timeline = [];
|
||
canvasChanges = [];
|
||
fontChanges = [];
|
||
selectionChanges = [];
|
||
mainForm = form;
|
||
Depth = depth;
|
||
ResetIndices();
|
||
Add();
|
||
Doing = false;
|
||
}
|
||
|
||
|
||
|
||
private void ResetIndices() {
|
||
Index = -1;
|
||
canvasIndex = 0;
|
||
fontIndex = 0;
|
||
selectionIndex = 0;
|
||
}
|
||
|
||
public enum ChangeType {
|
||
None = 0,
|
||
Canvas = 1, // Changes made to canvas
|
||
Font = 2, // Symbol width has been changed
|
||
Selection = 3 // Selected another frame
|
||
}
|
||
|
||
public class ChangeEvent(ChangeType type, FrameMiniature? canvas = null) {
|
||
public ChangeType Type { get; set; } = type;
|
||
public FrameMiniature? Canvas { get; set; } = canvas;
|
||
}
|
||
|
||
private static FrameMiniature CopyFrameSimple(FrameMiniature f) {
|
||
FrameMiniature newf = new(f.code, f.width, f.height);
|
||
Array.Copy(f.data, newf.data, f.data.Length);
|
||
return newf;
|
||
}
|
||
|
||
public void Clear() {
|
||
timeline.Clear();
|
||
canvasChanges.Clear();
|
||
fontChanges.Clear();
|
||
selectionChanges.Clear();
|
||
ResetIndices();
|
||
Add();
|
||
}
|
||
|
||
// Remove from a proper list by change type
|
||
private bool RemoveByType(ChangeEvent ce, bool first = true) {
|
||
switch (ce.Type) {
|
||
case ChangeType.Canvas:
|
||
if (canvasChanges.Count <= 1) return false;
|
||
if ((first && canvasIndex > 0) || (!first && canvasIndex == canvasChanges.Count - 1)) canvasIndex--;
|
||
canvasChanges.RemoveAt(first ? 0 : canvasChanges.Count - 1);
|
||
break;
|
||
case ChangeType.Font:
|
||
if (fontChanges.Count <= 1) return false;
|
||
if ((first && fontIndex > 0) || (!first && fontIndex == fontChanges.Count - 1)) fontIndex--;
|
||
if (ce.Canvas != null) {
|
||
if ((first && canvasIndex > 0) || (!first && canvasIndex == canvasChanges.Count - 1)) canvasIndex--;
|
||
canvasChanges.Remove((FrameMiniature)ce.Canvas);
|
||
}
|
||
fontChanges.RemoveAt(first ? 0 : fontChanges.Count - 1);
|
||
break;
|
||
case ChangeType.Selection:
|
||
if (selectionChanges.Count <= 1) return false;
|
||
if ((first && selectionIndex > 0) || (!first && selectionIndex == selectionChanges.Count - 1)) selectionIndex--;
|
||
if (ce.Canvas != null) {
|
||
if ((first && canvasIndex > 0) || (!first && canvasIndex == canvasChanges.Count - 1)) canvasIndex--;
|
||
canvasChanges.Remove((FrameMiniature)ce.Canvas);
|
||
}
|
||
selectionChanges.RemoveAt(first ? 0 : selectionChanges.Count - 1);
|
||
break;
|
||
default:
|
||
return false;
|
||
}
|
||
if ((first && Index > 0) || (!first && Index == Count - 1) || Count == 1) Index--;
|
||
timeline.RemoveAt(first ? 0 : Count - 1);
|
||
|
||
|
||
return true;
|
||
}
|
||
|
||
// Remove oldest event
|
||
private bool RemoveOldest() {
|
||
if (Count == 0) return false;
|
||
ChangeEvent ce = timeline.First();
|
||
RemoveByType(ce);
|
||
|
||
return true;
|
||
}
|
||
|
||
// Remove last event
|
||
public bool RemoveLast() {
|
||
if (Count == 0) return false;
|
||
var ce = timeline.Last();
|
||
RemoveByType(ce, false);
|
||
|
||
return true;
|
||
}
|
||
|
||
// Remove history tail
|
||
private void TruncateTail() {
|
||
// Check if the Index does not point to the last event
|
||
//while (Index < Count - 1) Remove
|
||
if (Index >= -1 && Index < Count - 1) {
|
||
timeline.RemoveRange( Index + 1, Count - Index - 1);
|
||
canvasChanges.RemoveRange( canvasIndex + 1, canvasChanges.Count - canvasIndex - 1);
|
||
fontChanges.RemoveRange( fontIndex + 1, fontChanges.Count - fontIndex - 1);
|
||
selectionChanges.RemoveRange(selectionIndex + 1, selectionChanges.Count - selectionIndex - 1);
|
||
}
|
||
}
|
||
|
||
// Add first states to all lists
|
||
private void Add() {
|
||
Add(mainForm.f, false);
|
||
Add(mainForm.frames, false);
|
||
var fff = mainForm.f; // Marshal-by-reference warning workaround
|
||
int ccс = fff.code; //
|
||
Add(ccс, false);
|
||
}
|
||
|
||
// Add canvas change
|
||
public FrameMiniature? Add(FrameMiniature f, bool useIndex = true) {
|
||
if (Doing) return null ;
|
||
TruncateTail();
|
||
|
||
if (Count >= Depth) RemoveOldest();
|
||
canvasChanges.Add(CopyFrameSimple(f));
|
||
if (useIndex) {
|
||
timeline.Add(new ChangeEvent(ChangeType.Canvas));
|
||
Index++;
|
||
canvasIndex++;
|
||
}
|
||
return canvasChanges.Last();
|
||
}
|
||
|
||
// Add Font change
|
||
public void Add(List<FrameMiniature> ff, bool useIndex = true) {
|
||
if (Doing) return;
|
||
TruncateTail();
|
||
|
||
var l = new List<FrameMiniature>();
|
||
foreach (var f in ff) {
|
||
l.Add(CopyFrameSimple(f));
|
||
}
|
||
|
||
if (Count >= Depth) RemoveOldest();
|
||
fontChanges.Add(l);
|
||
if (useIndex) {
|
||
var canv = Add(mainForm.f, false);
|
||
canvasIndex++;
|
||
timeline.Add(new ChangeEvent(ChangeType.Font, canv));
|
||
Index++;
|
||
fontIndex++;
|
||
}
|
||
}
|
||
|
||
// Add Frame selection change
|
||
public void Add(int code, bool useIndex = true) {
|
||
if (Doing) return;
|
||
TruncateTail();
|
||
|
||
if (Count >= Depth) RemoveOldest();
|
||
selectionChanges.Add(code);
|
||
if (useIndex) {
|
||
var canv = Add(mainForm.f, false);
|
||
canvasIndex++;
|
||
timeline.Add(new ChangeEvent(ChangeType.Selection, canv));
|
||
Index++;
|
||
selectionIndex++;
|
||
}
|
||
|
||
}
|
||
|
||
private void Do(bool undo = true) {
|
||
if (!undo && Index >= Count - 1) return;
|
||
Doing = true;
|
||
var ce = timeline.ElementAt(Index + (undo ? 0 : 1));
|
||
int dIndex = undo ? -1 : 1;
|
||
FrameMiniature fff;
|
||
switch (ce.Type) {
|
||
case ChangeType.Canvas:
|
||
canvasIndex += dIndex;
|
||
mainForm.f = CopyFrameSimple(canvasChanges[canvasIndex]);
|
||
mainForm.SetModified();
|
||
mainForm.nudX.ValueChanged -= mainForm.nudX_ValueChanged;
|
||
mainForm.nudY.ValueChanged -= mainForm.nudY_ValueChanged;
|
||
mainForm.nudY.Value = mainForm.dotHeight = canvasChanges[canvasIndex].height;
|
||
mainForm.nudX.Value = mainForm.dotWidth = canvasChanges[canvasIndex].width;
|
||
mainForm.SetNewWH();
|
||
mainForm.nudX.ValueChanged += mainForm.nudX_ValueChanged;
|
||
mainForm.nudY.ValueChanged += mainForm.nudY_ValueChanged;
|
||
break;
|
||
case ChangeType.Font:
|
||
Cursor.Current = Cursors.WaitCursor;
|
||
string selItem = "";
|
||
int selCode = 0;
|
||
if (mainForm.miniList.SelectedItems.Count > 0) {
|
||
selItem = mainForm.miniList.SelectedItems[0].Name;
|
||
selCode = Convert.ToInt32(selItem);
|
||
}
|
||
fontIndex += dIndex;
|
||
canvasIndex += dIndex;
|
||
mainForm.frames.Clear();
|
||
mainForm.miniList.Clear();
|
||
mainForm.ilMiniatures.Images.Clear();
|
||
foreach (var f in fontChanges[fontIndex]) {
|
||
mainForm.frames.Add(CopyFrameSimple(f));
|
||
}
|
||
mainForm.FillFrameLists();
|
||
|
||
if (selItem != "") {
|
||
var selection = mainForm.miniList.Items.Find(selItem, false);
|
||
if (selection.Length > 0) selection[0].Selected = true;
|
||
fff = mainForm.frames.Find(x => x.code == selCode);
|
||
} else {
|
||
mainForm.miniList.Items[0].Selected = true;
|
||
fff = mainForm.frames[0];
|
||
}
|
||
mainForm.f = mainForm.CopyFrame(fff);
|
||
mainForm.nudX.ValueChanged -= mainForm.nudX_ValueChanged;
|
||
mainForm.nudY.ValueChanged -= mainForm.nudY_ValueChanged;
|
||
mainForm.nudY.Value = mainForm.dotHeight = fff.height;
|
||
mainForm.nudX.Value = mainForm.dotWidth = fff.width;
|
||
mainForm.SetNewWH();
|
||
mainForm.nudX.ValueChanged += mainForm.nudX_ValueChanged;
|
||
mainForm.nudY.ValueChanged += mainForm.nudY_ValueChanged;
|
||
|
||
Cursor.Current = Cursors.Default;
|
||
break;
|
||
case ChangeType.Selection:
|
||
selectionIndex += dIndex;
|
||
canvasIndex += dIndex;
|
||
var s = selectionChanges[selectionIndex].ToString().PadLeft(3, '0');
|
||
var sel = mainForm.miniList.Items.Find(s, false);
|
||
if (sel.Length > 0) sel[0].Selected = true;
|
||
fff = mainForm.frames.Find(x => x.code == selectionChanges[selectionIndex]);
|
||
mainForm.f = CopyFrameSimple(fff);
|
||
mainForm.nudX.ValueChanged -= mainForm.nudX_ValueChanged;
|
||
mainForm.nudY.ValueChanged -= mainForm.nudY_ValueChanged;
|
||
mainForm.nudY.Value = mainForm.dotHeight = fff.height;
|
||
mainForm.nudX.Value = mainForm.dotWidth = fff.width;
|
||
mainForm.SetNewWH();
|
||
mainForm.nudX.ValueChanged += mainForm.nudX_ValueChanged;
|
||
mainForm.nudY.ValueChanged += mainForm.nudY_ValueChanged;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
Index += dIndex;
|
||
Doing = false;
|
||
|
||
}
|
||
|
||
// Undo last change
|
||
public bool Undo() {
|
||
if (Undos < 1) return false;
|
||
Do();
|
||
|
||
return true;
|
||
}
|
||
|
||
// Redo last ondone change
|
||
public bool Redo() {
|
||
if (Redos < 1) return false;
|
||
Do(false);
|
||
|
||
return true;
|
||
}
|
||
|
||
}
|
||
}
|